1use pamoja_lora::region::{ChannelBlock, ChannelPlan, OwnedChannelPlan};
72use pamoja_lora::LinkSettings;
73use pamoja_lorawan::mac::{encode_all, MacCommand, MacCommands, FOPTS_MAX, MAX_COMMAND};
74use pamoja_lorawan::relay::{ForwardedUplink, UplinkMetadata, LA_FPORT_RELAY};
75use pamoja_lorawan::{
76 Direction, Downlink, FrameHeader, JoinGrant, JoinRequest, LorawanError, MessageType, Session,
77 Version,
78};
79
80use crate::udp::{Rxpk, Txpk};
81
82pub use pamoja_lorawan::defaults::{
86 JOIN_ACCEPT_DELAY1_US, JOIN_ACCEPT_DELAY2_US, MAX_FCNT_GAP, RECEIVE_DELAY1_US,
87 RECEIVE_DELAY2_US,
88};
89
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
99pub enum Rx1Channels {
100 Plan,
105 SameAsUplink,
107 Downstream(ChannelBlock),
110}
111
112impl Rx1Channels {
113 pub const fn us915() -> Rx1Channels {
122 Rx1Channels::Downstream(ChannelBlock::new(923_300_000, 600_000, 8, 8, 13))
123 }
124
125 pub const fn au915() -> Rx1Channels {
131 Rx1Channels::Downstream(ChannelBlock::new(923_300_000, 600_000, 8, 8, 13))
132 }
133}
134
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141pub struct Windows {
142 receive_delay_us: u32,
143 join_delay_us: u32,
144 rx1_data_rate_offset: u8,
145 rx1_channels: Rx1Channels,
146}
147
148impl Windows {
149 pub const fn new() -> Windows {
156 Windows {
157 receive_delay_us: RECEIVE_DELAY1_US,
158 join_delay_us: JOIN_ACCEPT_DELAY1_US,
159 rx1_data_rate_offset: 0,
160 rx1_channels: Rx1Channels::Plan,
161 }
162 }
163
164 pub const fn with_receive_delay_us(mut self, micros: u32) -> Windows {
174 self.receive_delay_us = micros;
175 self
176 }
177
178 pub const fn with_join_delay_us(mut self, micros: u32) -> Windows {
188 self.join_delay_us = micros;
189 self
190 }
191
192 pub const fn with_rx1_data_rate_offset(mut self, offset: u8) -> Windows {
202 self.rx1_data_rate_offset = offset;
203 self
204 }
205
206 pub const fn with_rx1_channels(mut self, channels: Rx1Channels) -> Windows {
216 self.rx1_channels = channels;
217 self
218 }
219
220 pub const fn receive_delay_us(&self) -> u32 {
226 self.receive_delay_us
227 }
228
229 pub const fn join_delay_us(&self) -> u32 {
235 self.join_delay_us
236 }
237
238 pub const fn rx1_data_rate_offset(&self) -> u8 {
244 self.rx1_data_rate_offset
245 }
246
247 pub const fn rx1_channels(&self) -> Rx1Channels {
253 self.rx1_channels
254 }
255
256 pub const fn rx_delay_byte(&self) -> u8 {
266 let seconds = self.receive_delay_us / 1_000_000;
267 if seconds > 15 {
268 return 15;
269 }
270 seconds as u8
271 }
272
273 pub const fn dl_settings_byte(&self, rx2_data_rate: u8) -> u8 {
286 ((self.rx1_data_rate_offset & 0x07) << 4) | (rx2_data_rate & 0x0F)
287 }
288}
289
290impl Default for Windows {
291 fn default() -> Windows {
292 Windows::new()
293 }
294}
295
296#[derive(Clone, Copy)]
299pub struct Registration {
300 dev_eui: [u8; 8],
301 app_eui: [u8; 8],
302 app_key: [u8; 16],
303 version: Version,
304}
305
306impl Registration {
307 pub const fn new(dev_eui: [u8; 8], app_eui: [u8; 8], app_key: [u8; 16]) -> Registration {
320 Registration {
321 dev_eui,
322 app_eui,
323 app_key,
324 version: Version::V1_0_4,
325 }
326 }
327
328 pub const fn with_version(mut self, version: Version) -> Registration {
343 self.version = version;
344 self
345 }
346
347 pub const fn version(&self) -> Version {
353 self.version
354 }
355
356 pub const fn dev_eui(&self) -> [u8; 8] {
362 self.dev_eui
363 }
364
365 pub const fn app_eui(&self) -> [u8; 8] {
371 self.app_eui
372 }
373}
374
375impl core::fmt::Debug for Registration {
377 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
378 f.debug_struct("Registration")
379 .field("dev_eui", &self.dev_eui)
380 .field("app_eui", &self.app_eui)
381 .field("version", &self.version)
382 .finish_non_exhaustive()
383 }
384}
385
386#[derive(Clone, Copy, Debug, PartialEq, Eq)]
388pub struct Relayed {
389 pub relay: u32,
391 pub metadata: UplinkMetadata,
393 pub frequency_hz: u32,
395}
396
397#[derive(Clone, Copy, Debug, PartialEq, Eq)]
399pub struct Notice {
400 pub relay: u32,
402 pub dev_addr: u32,
404 pub rssi_dbm: i16,
406 pub snr_db: i8,
408}
409
410#[derive(Clone, Copy, Debug, PartialEq)]
412pub struct Slot {
413 pub timestamp_us: u32,
415 pub frequency_hz: u32,
417 pub link: LinkSettings,
419}
420
421#[derive(Clone, Debug, PartialEq)]
423pub enum Event {
424 Joined {
426 dev_eui: [u8; 8],
428 dev_addr: u32,
430 accept: Txpk,
432 },
433 Data {
435 dev_addr: u32,
437 fcnt: u32,
439 fport: Option<u8>,
441 payload: Vec<u8>,
443 confirmed: bool,
445 slot: Slot,
447 relay: Option<Relayed>,
450 },
451 Foreign {
454 dev_addr: u32,
456 },
457}
458
459#[derive(Clone, Debug, PartialEq, Eq)]
461pub enum NetworkError {
462 Relayed(LorawanError),
464 NotLora,
466 Frame(LorawanError),
468 UnknownDevice,
470 Replayed {
472 dev_addr: u32,
474 fcnt: u32,
476 },
477 CounterGap {
480 dev_addr: u32,
482 seen: u32,
484 carried: u32,
486 },
487 CounterExhausted {
490 dev_addr: u32,
492 },
493 UnknownDataRate,
496 NoWindow,
499 NoSession {
501 dev_addr: u32,
503 },
504}
505
506impl core::fmt::Display for NetworkError {
507 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
508 match self {
509 NetworkError::NotLora => f.write_str("the packet was not LoRa"),
510 NetworkError::Frame(error) => write!(f, "the frame was refused: {error}"),
511 NetworkError::Relayed(error) => {
512 write!(f, "the frame a relay forwarded was refused: {error}")
513 }
514 NetworkError::UnknownDevice => {
515 f.write_str("no registered key verifies the join request")
516 }
517 NetworkError::Replayed { dev_addr, fcnt } => {
518 write!(f, "frame {fcnt} from {dev_addr:#010x} was already seen")
519 }
520 NetworkError::CounterGap {
521 dev_addr,
522 seen,
523 carried,
524 } => write!(
525 f,
526 "frame {carried} from {dev_addr:#010x} runs too far ahead of {seen}"
527 ),
528 NetworkError::CounterExhausted { dev_addr } => write!(
529 f,
530 "the frame counter of {dev_addr:#010x} has run out; the device must join again"
531 ),
532 NetworkError::UnknownDataRate => {
533 f.write_str("the plan names no data rate for the settings heard")
534 }
535 NetworkError::NoWindow => f.write_str("the plan defines no first receive window"),
536 NetworkError::NoSession { dev_addr } => {
537 write!(f, "no session for {dev_addr:#010x}")
538 }
539 }
540 }
541}
542
543impl core::error::Error for NetworkError {}
544
545struct JoinAccepts {
548 channels: ChannelBlock,
549 start_hz: u32,
550 step_hz: u32,
551}
552
553struct Admitted {
555 dev_eui: [u8; 8],
556 dev_addr: u32,
557 version: Version,
558 session: Session,
559 fcnt_up: Option<u32>,
560 fcnt_down: u32,
561 via_relay: Option<u32>,
562}
563
564impl Admitted {
565 fn uplink_counter(&self, carried: u16) -> Result<u32, NetworkError> {
569 let dev_addr = self.dev_addr;
570 let Some(seen) = self.fcnt_up else {
571 return Ok(u32::from(carried));
572 };
573 let mut candidate = (seen & 0xFFFF_0000) | u32::from(carried);
574 if candidate == seen {
575 return Err(NetworkError::Replayed {
576 dev_addr,
577 fcnt: candidate,
578 });
579 }
580 if candidate < seen {
581 candidate = candidate
582 .checked_add(0x0001_0000)
583 .ok_or(NetworkError::CounterExhausted { dev_addr })?;
584 }
585 if self.version == Version::V1_0_3 && candidate - seen >= MAX_FCNT_GAP {
586 return Err(NetworkError::CounterGap {
587 dev_addr,
588 seen,
589 carried: candidate,
590 });
591 }
592 Ok(candidate)
593 }
594}
595
596pub struct Network {
602 plan: OwnedChannelPlan,
603 join_accepts: Vec<JoinAccepts>,
604 windows: Windows,
605 net_id: u32,
606 registrations: Vec<Registration>,
607 admitted: Vec<Admitted>,
608 next_dev_addr: u32,
609 next_app_nonce: u32,
610 notices: Vec<Notice>,
611}
612
613impl Network {
614 pub fn new(plan: &ChannelPlan<'_>, net_id: u32) -> Network {
629 Network {
630 plan: OwnedChannelPlan::from_plan(plan),
631 join_accepts: plan
632 .join_plans
633 .iter()
634 .map(|run| JoinAccepts {
635 channels: run.channels,
636 start_hz: run.accept_start_hz,
637 step_hz: run.accept_step_hz,
638 })
639 .collect(),
640 windows: Windows::new(),
641 net_id,
642 registrations: Vec::new(),
643 admitted: Vec::new(),
644 next_dev_addr: 1,
645 next_app_nonce: 1,
646 notices: Vec::new(),
647 }
648 }
649
650 pub fn with_windows(mut self, windows: Windows) -> Network {
660 self.windows = windows;
661 self
662 }
663
664 pub fn with_first_dev_addr(mut self, dev_addr: u32) -> Network {
674 self.next_dev_addr = dev_addr;
675 self
676 }
677
678 pub fn register(&mut self, registration: Registration) {
684 self.registrations.push(registration);
685 }
686
687 pub const fn windows(&self) -> Windows {
693 self.windows
694 }
695
696 pub fn session(&self, dev_addr: u32) -> Option<Session> {
706 self.admitted
707 .iter()
708 .find(|held| held.dev_addr == dev_addr)
709 .map(|held| held.session)
710 }
711
712 pub fn uplink(&mut self, heard: &Rxpk) -> Result<Event, NetworkError> {
737 let link = heard.modulation.link().ok_or(NetworkError::NotLora)?;
738 let header = FrameHeader::parse(&heard.payload).map_err(NetworkError::Frame)?;
739
740 match header.message_type() {
741 MessageType::JoinRequest => self.admit(heard, link),
742 MessageType::UnconfirmedUp | MessageType::ConfirmedUp => {
743 self.receive(heard, link, &header)
744 }
745 other => Err(NetworkError::Frame(LorawanError::UnsupportedMType(
746 downlink_mtype(other),
747 ))),
748 }
749 }
750
751 pub fn answer(
769 &mut self,
770 dev_addr: u32,
771 slot: Slot,
772 fport: u8,
773 payload: &[u8],
774 ) -> Result<Txpk, NetworkError> {
775 let held = self
776 .admitted
777 .iter_mut()
778 .find(|held| held.dev_addr == dev_addr)
779 .ok_or(NetworkError::NoSession { dev_addr })?;
780
781 let frame = held
782 .session
783 .encode_downlink(&Downlink::new(held.fcnt_down, fport, payload))
784 .map_err(NetworkError::Frame)?;
785 held.fcnt_down = held.fcnt_down.wrapping_add(1);
786 let relay = held.via_relay;
787
788 let frame = match relay {
789 Some(relay) => self.wrap_for_relay(relay, frame.as_bytes())?,
790 None => frame.as_bytes().to_vec(),
791 };
792 Ok(transmit(slot, frame))
793 }
794
795 pub fn command(
816 &mut self,
817 dev_addr: u32,
818 slot: Slot,
819 commands: &[MacCommand],
820 ) -> Result<Txpk, NetworkError> {
821 let mut bytes = vec![0u8; commands.len() * MAX_COMMAND];
822 let len = encode_all(commands, &mut bytes).map_err(NetworkError::Frame)?;
823 let held = self
824 .admitted
825 .iter_mut()
826 .find(|held| held.dev_addr == dev_addr)
827 .ok_or(NetworkError::NoSession { dev_addr })?;
828
829 let downlink = if len <= FOPTS_MAX {
830 Downlink::empty(held.fcnt_down).with_fopts(&bytes[..len])
831 } else {
832 Downlink::new(held.fcnt_down, 0, &bytes[..len])
833 };
834 let frame = held
835 .session
836 .encode_downlink(&downlink)
837 .map_err(NetworkError::Frame)?;
838 held.fcnt_down = held.fcnt_down.wrapping_add(1);
839 Ok(transmit(slot, frame.as_bytes().to_vec()))
840 }
841
842 pub fn trust_command(
861 &self,
862 dev_addr: u32,
863 index: u8,
864 reload_rate: u8,
865 bucket_size: u8,
866 ) -> Result<MacCommand, NetworkError> {
867 let held = self
868 .admitted
869 .iter()
870 .find(|held| held.dev_addr == dev_addr)
871 .ok_or(NetworkError::NoSession { dev_addr })?;
872 Ok(MacCommand::UpdateUplinkListReq {
873 index,
874 reload_rate,
875 bucket_size,
876 dev_addr,
877 wfcnt: 0,
878 root_wor_s_key: held.session.root_wor_s_key(),
879 })
880 }
881
882 pub fn notices(&mut self) -> Vec<Notice> {
892 core::mem::take(&mut self.notices)
893 }
894
895 #[must_use]
901 pub fn notice_count(&self) -> usize {
902 self.notices.len()
903 }
904
905 fn admit(&mut self, heard: &Rxpk, link: LinkSettings) -> Result<Event, NetworkError> {
907 self.admit_frame(
908 &heard.payload,
909 self.slot(heard, link, self.windows.join_delay_us)?,
910 Some(heard.frequency_hz),
911 None,
912 )
913 }
914
915 fn admit_frame(
918 &mut self,
919 payload: &[u8],
920 mut slot: Slot,
921 accept_hz: Option<u32>,
922 relay: Option<u32>,
923 ) -> Result<Event, NetworkError> {
924 let (registration, request) = self
925 .registrations
926 .iter()
927 .find_map(|registration| {
928 let request = JoinRequest::parse(payload, ®istration.app_key).ok()?;
929 (request.dev_eui() == registration.dev_eui).then_some((*registration, request))
930 })
931 .ok_or(NetworkError::UnknownDevice)?;
932
933 let dev_addr = self.next_dev_addr;
934 let app_nonce = self.next_app_nonce;
935 let grant = JoinGrant::new(app_nonce, self.net_id, dev_addr)
936 .with_dl_settings(
937 self.windows
938 .dl_settings_byte(self.plan.with_plan(|plan| plan.rx2_data_rate)),
939 )
940 .with_rx_delay(self.windows.rx_delay_byte());
941 let accept = grant.accept(®istration.app_key, request.dev_nonce());
942 let session = grant.session(®istration.app_key, request.dev_nonce());
943
944 self.next_dev_addr = self.next_dev_addr.wrapping_add(1);
945 self.next_app_nonce = self.next_app_nonce.wrapping_add(1);
946 self.admitted
947 .retain(|held| held.dev_eui != registration.dev_eui);
948 self.admitted.push(Admitted {
949 dev_eui: registration.dev_eui,
950 dev_addr,
951 version: registration.version,
952 session,
953 fcnt_up: None,
954 fcnt_down: 0,
955 via_relay: relay,
956 });
957
958 if let Some(uplink_hz) = accept_hz {
959 if self.windows.rx1_channels == Rx1Channels::Plan {
960 if let Some(hz) = self.join_accept_hz(uplink_hz) {
961 slot.frequency_hz = hz;
962 }
963 }
964 }
965 let accept = match relay {
966 Some(relay) => self.wrap_for_relay(relay, accept.as_bytes())?,
967 None => accept.as_bytes().to_vec(),
968 };
969 Ok(Event::Joined {
970 dev_eui: registration.dev_eui,
971 dev_addr,
972 accept: transmit(slot, accept),
973 })
974 }
975
976 fn receive(
978 &mut self,
979 heard: &Rxpk,
980 link: LinkSettings,
981 header: &FrameHeader,
982 ) -> Result<Event, NetworkError> {
983 let dev_addr = header
984 .dev_addr()
985 .ok_or(NetworkError::Frame(LorawanError::MalformedFrame))?;
986 let carried = header
987 .fcnt()
988 .ok_or(NetworkError::Frame(LorawanError::MalformedFrame))?;
989 let slot = self.slot(heard, link, self.windows.receive_delay_us)?;
990
991 let Some(held) = self
992 .admitted
993 .iter_mut()
994 .find(|held| held.dev_addr == dev_addr)
995 else {
996 return Ok(Event::Foreign { dev_addr });
997 };
998
999 let fcnt = held.uplink_counter(carried)?;
1000
1001 let data = held
1002 .session
1003 .decode(&heard.payload, fcnt)
1004 .map_err(NetworkError::Frame)?;
1005 held.fcnt_up = Some(fcnt);
1006 held.via_relay = None;
1007
1008 let commands = if data.fport() == Some(0) {
1011 data.payload()
1012 } else {
1013 data.fopts()
1014 };
1015 for command in MacCommands::new(Direction::Uplink, commands).flatten() {
1016 if let MacCommand::NotifyNewEndDeviceReq {
1017 dev_addr: heard_addr,
1018 rssi_dbm,
1019 snr_db,
1020 } = command
1021 {
1022 self.notices.push(Notice {
1023 relay: dev_addr,
1024 dev_addr: heard_addr,
1025 rssi_dbm,
1026 snr_db,
1027 });
1028 }
1029 }
1030
1031 if data.fport() == Some(LA_FPORT_RELAY) {
1032 return self.forwarded(dev_addr, data.payload(), slot);
1033 }
1034
1035 Ok(Event::Data {
1036 dev_addr,
1037 fcnt,
1038 fport: data.fport(),
1039 payload: data.payload().to_vec(),
1040 confirmed: data.confirmed(),
1041 slot,
1042 relay: None,
1043 })
1044 }
1045
1046 fn forwarded(&mut self, relay: u32, payload: &[u8], slot: Slot) -> Result<Event, NetworkError> {
1049 let forwarded = ForwardedUplink::parse(payload).map_err(NetworkError::Relayed)?;
1050 let relayed = Relayed {
1051 relay,
1052 metadata: forwarded.metadata,
1053 frequency_hz: forwarded.frequency_hz,
1054 };
1055 let header = FrameHeader::parse(forwarded.phy_payload).map_err(NetworkError::Relayed)?;
1056 match header.message_type() {
1057 MessageType::JoinRequest => {
1058 self.admit_frame(forwarded.phy_payload, slot, None, Some(relay))
1059 }
1060 MessageType::UnconfirmedUp | MessageType::ConfirmedUp => {
1061 let dev_addr = header
1062 .dev_addr()
1063 .ok_or(NetworkError::Relayed(LorawanError::MalformedFrame))?;
1064 let carried = header
1065 .fcnt()
1066 .ok_or(NetworkError::Relayed(LorawanError::MalformedFrame))?;
1067 let Some(held) = self
1068 .admitted
1069 .iter_mut()
1070 .find(|held| held.dev_addr == dev_addr)
1071 else {
1072 return Ok(Event::Foreign { dev_addr });
1073 };
1074 let fcnt = held.uplink_counter(carried)?;
1075 let data = held
1076 .session
1077 .decode(forwarded.phy_payload, fcnt)
1078 .map_err(NetworkError::Relayed)?;
1079 held.fcnt_up = Some(fcnt);
1080 held.via_relay = Some(relay);
1081 Ok(Event::Data {
1082 dev_addr,
1083 fcnt,
1084 fport: data.fport(),
1085 payload: data.payload().to_vec(),
1086 confirmed: data.confirmed(),
1087 slot,
1088 relay: Some(relayed),
1089 })
1090 }
1091 other => Err(NetworkError::Relayed(LorawanError::UnsupportedMType(
1092 downlink_mtype(other),
1093 ))),
1094 }
1095 }
1096
1097 fn wrap_for_relay(&mut self, relay: u32, frame: &[u8]) -> Result<Vec<u8>, NetworkError> {
1099 let held = self
1100 .admitted
1101 .iter_mut()
1102 .find(|held| held.dev_addr == relay)
1103 .ok_or(NetworkError::NoSession { dev_addr: relay })?;
1104 let wrapped = held
1105 .session
1106 .encode_downlink(&Downlink::new(held.fcnt_down, LA_FPORT_RELAY, frame))
1107 .map_err(NetworkError::Frame)?;
1108 held.fcnt_down = held.fcnt_down.wrapping_add(1);
1109 Ok(wrapped.as_bytes().to_vec())
1110 }
1111
1112 fn slot(&self, heard: &Rxpk, link: LinkSettings, delay_us: u32) -> Result<Slot, NetworkError> {
1114 let uplink_rate = self
1115 .data_rate_of(link)
1116 .ok_or(NetworkError::UnknownDataRate)?;
1117 let settings = self
1118 .plan
1119 .with_plan(|plan| {
1120 plan.rx1_data_rate(uplink_rate, self.windows.rx1_data_rate_offset)
1121 .and_then(|downlink_rate| plan.downlink_data_rate(downlink_rate))
1122 .and_then(|rate| rate.link_settings())
1123 })
1124 .ok_or(NetworkError::NoWindow)?;
1125
1126 Ok(Slot {
1127 timestamp_us: heard.timestamp_us.unwrap_or(0).wrapping_add(delay_us),
1128 frequency_hz: self.rx1_frequency_hz(heard.frequency_hz)?,
1129 link: settings,
1130 })
1131 }
1132
1133 fn rx1_frequency_hz(&self, uplink_hz: u32) -> Result<u32, NetworkError> {
1135 match self.windows.rx1_channels {
1136 Rx1Channels::Plan => {
1137 if self.plan.with_plan(|plan| plan.downlink_channel_count()) == 0 {
1138 return Ok(uplink_hz);
1139 }
1140 let channel = self.channel_of(uplink_hz).ok_or(NetworkError::NoWindow)?;
1141 self.plan
1142 .with_plan(|plan| plan.rx1_frequency_hz(channel, uplink_hz))
1143 .ok_or(NetworkError::NoWindow)
1144 }
1145 Rx1Channels::SameAsUplink => Ok(uplink_hz),
1146 Rx1Channels::Downstream(block) => {
1147 let channel = self.channel_of(uplink_hz).ok_or(NetworkError::NoWindow)?;
1148 block
1149 .frequency_hz(channel % block.count)
1150 .ok_or(NetworkError::NoWindow)
1151 }
1152 }
1153 }
1154
1155 fn join_accept_hz(&self, uplink_hz: u32) -> Option<u32> {
1157 self.join_accepts.iter().find_map(|run| {
1158 (0..run.channels.count)
1159 .find(|offset| run.channels.frequency_hz(*offset) == Some(uplink_hz))
1160 .map(|offset| run.start_hz + run.step_hz * u32::from(offset))
1161 })
1162 }
1163
1164 fn channel_of(&self, frequency_hz: u32) -> Option<u16> {
1167 self.plan.with_plan(|plan| {
1168 let mut first = 0u16;
1169 for block in plan.default_channels {
1170 for index in 0..block.count {
1171 if block.frequency_hz(index) == Some(frequency_hz) {
1172 return Some(first + index);
1173 }
1174 }
1175 first += block.count;
1176 }
1177 None
1178 })
1179 }
1180
1181 fn data_rate_of(&self, link: LinkSettings) -> Option<u8> {
1185 self.plan.with_plan(|plan| {
1186 plan.uplink_data_rates
1187 .iter()
1188 .enumerate()
1189 .find_map(|(number, rate)| {
1190 let settings = rate.as_ref()?.link_settings()?;
1191 (settings.spreading_factor() == link.spreading_factor()
1192 && settings.bandwidth_hz() == link.bandwidth_hz())
1193 .then_some(number as u8)
1194 })
1195 })
1196 }
1197}
1198
1199fn transmit(slot: Slot, payload: Vec<u8>) -> Txpk {
1201 Txpk::at(slot.timestamp_us, slot.frequency_hz, slot.link, payload).with_inverted_polarity(true)
1202}
1203
1204const fn downlink_mtype(message_type: MessageType) -> u8 {
1206 match message_type {
1207 MessageType::JoinRequest => 0x00,
1208 MessageType::JoinAccept => 0x20,
1209 MessageType::UnconfirmedUp => 0x40,
1210 MessageType::UnconfirmedDown => 0x60,
1211 MessageType::ConfirmedUp => 0x80,
1212 MessageType::ConfirmedDown => 0xA0,
1213 }
1214}
1215
1216#[cfg(test)]
1217mod tests {
1218 use super::*;
1219
1220 use pamoja_lora::region::{Cn470Plan, Region};
1221 use pamoja_lorawan::{Device, Uplink};
1222
1223 const DEV_EUI: [u8; 8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77];
1224 const APP_EUI: [u8; 8] = [0x70, 0xB3, 0xD5, 0x7E, 0xD0, 0x00, 0x00, 0x01];
1225 const APP_KEY: [u8; 16] = [
1226 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F,
1227 0x3C,
1228 ];
1229
1230 fn site() -> Network {
1231 let mut network =
1232 Network::new(Region::Eu868.plan(), 0x00_00_2A).with_first_dev_addr(0x2601_0001);
1233 network.register(Registration::new(DEV_EUI, APP_EUI, APP_KEY));
1234 network
1235 }
1236
1237 fn heard(frame: Vec<u8>, timestamp_us: u32) -> Rxpk {
1238 Rxpk::new(868_100_000, LinkSettings::new(7, 125_000), frame).with_timestamp_us(timestamp_us)
1239 }
1240
1241 #[test]
1242 fn the_recommended_delays_are_the_published_ones() {
1243 assert_eq!(RECEIVE_DELAY1_US, 1_000_000);
1246 assert_eq!(RECEIVE_DELAY2_US, 2_000_000);
1247 assert_eq!(JOIN_ACCEPT_DELAY1_US, 5_000_000);
1248 assert_eq!(JOIN_ACCEPT_DELAY2_US, 6_000_000);
1249 assert_eq!(MAX_FCNT_GAP, 16_384);
1250 }
1251
1252 #[test]
1253 fn the_join_accept_bytes_follow_the_specification() {
1254 let windows = Windows::new().with_rx1_data_rate_offset(5);
1256 assert_eq!(windows.dl_settings_byte(0), 0x50);
1257 assert_eq!(windows.dl_settings_byte(0x0F), 0x5F);
1258 assert_eq!(Windows::new().dl_settings_byte(8), 0x08);
1259
1260 assert_eq!(Windows::new().rx_delay_byte(), 1);
1263 assert_eq!(
1264 Windows::new()
1265 .with_receive_delay_us(5_000_000)
1266 .rx_delay_byte(),
1267 5
1268 );
1269 }
1270
1271 #[test]
1272 fn a_registration_never_prints_its_key() {
1273 let printed = format!("{:?}", Registration::new(DEV_EUI, APP_EUI, APP_KEY));
1274 assert!(printed.contains("dev_eui"));
1275 assert!(!printed.contains("2b") && !printed.contains("43"));
1276 }
1277
1278 #[test]
1279 fn a_device_joins_and_is_answered_in_the_join_window() {
1280 let mut network = site();
1281 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
1282 let request = device.join_request(0x0102);
1283
1284 let event = network
1285 .uplink(&heard(request.as_bytes().to_vec(), 1_000_000))
1286 .expect("the request verifies");
1287
1288 let Event::Joined {
1289 dev_eui,
1290 dev_addr,
1291 accept,
1292 } = event
1293 else {
1294 panic!("a join request is admitted");
1295 };
1296 assert_eq!(dev_eui, DEV_EUI);
1297 assert_eq!(dev_addr, 0x2601_0001);
1298 assert_eq!(accept.timestamp_us, Some(6_000_000));
1300 assert_eq!(accept.frequency_hz, 868_100_000);
1301 assert!(accept.invert_polarity);
1302
1303 let granted = device
1305 .accept_join(&accept.payload, 0x0102)
1306 .expect("the accept verifies");
1307 assert_eq!(granted.dev_addr(), 0x2601_0001);
1308 }
1309
1310 #[test]
1311 fn an_uplink_is_decrypted_and_answered_one_second_later() {
1312 let mut network = site();
1313 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
1314 let request = device.join_request(0x0102);
1315 let Event::Joined { accept, .. } = network
1316 .uplink(&heard(request.as_bytes().to_vec(), 1_000_000))
1317 .expect("the request verifies")
1318 else {
1319 panic!("a join request is admitted");
1320 };
1321 let session = device
1322 .accept_join(&accept.payload, 0x0102)
1323 .expect("the accept verifies")
1324 .session();
1325
1326 let frame = session
1327 .encode_uplink(&pamoja_lorawan::Uplink::new(0, 2, b"21.5"))
1328 .expect("it fits one frame");
1329 let event = network
1330 .uplink(&heard(frame.as_bytes().to_vec(), 9_000_000))
1331 .expect("the frame verifies");
1332
1333 let Event::Data {
1334 dev_addr,
1335 fcnt,
1336 fport,
1337 payload,
1338 confirmed,
1339 slot,
1340 ..
1341 } = event
1342 else {
1343 panic!("a data frame is read");
1344 };
1345 assert_eq!(dev_addr, 0x2601_0001);
1346 assert_eq!(fcnt, 0);
1347 assert_eq!(fport, Some(2));
1348 assert_eq!(payload, b"21.5");
1349 assert!(!confirmed);
1350 assert_eq!(slot.timestamp_us, 10_000_000);
1351 assert_eq!(slot.frequency_hz, 868_100_000);
1352 assert_eq!(slot.link.spreading_factor(), 7);
1353
1354 let downlink = network
1356 .answer(dev_addr, slot, 2, b"ok")
1357 .expect("the session is held");
1358 assert_eq!(downlink.timestamp_us, Some(10_000_000));
1359 assert!(downlink.invert_polarity);
1360 let read = session.decode(&downlink.payload, 0).expect("it verifies");
1361 assert_eq!(read.payload(), b"ok");
1362 }
1363
1364 #[test]
1365 fn a_replayed_frame_is_refused() {
1366 let mut network = site();
1367 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
1368 let request = device.join_request(0x0102);
1369 let Event::Joined { accept, .. } = network
1370 .uplink(&heard(request.as_bytes().to_vec(), 1_000_000))
1371 .expect("the request verifies")
1372 else {
1373 panic!("a join request is admitted");
1374 };
1375 let session = device
1376 .accept_join(&accept.payload, 0x0102)
1377 .expect("the accept verifies")
1378 .session();
1379
1380 let first = session
1381 .encode_uplink(&pamoja_lorawan::Uplink::new(1, 2, b"one"))
1382 .expect("it fits one frame");
1383 network
1384 .uplink(&heard(first.as_bytes().to_vec(), 2_000_000))
1385 .expect("the first frame is read");
1386
1387 let error = network
1388 .uplink(&heard(first.as_bytes().to_vec(), 3_000_000))
1389 .expect_err("the same counter twice is a replay");
1390 assert!(matches!(
1391 error,
1392 NetworkError::Replayed {
1393 dev_addr: 0x2601_0001,
1394 ..
1395 }
1396 ));
1397 }
1398
1399 #[test]
1404 fn a_counter_a_whole_gap_ahead_is_refused_from_a_1_0_3_device_alone() {
1405 for version in [Version::V1_0_3, Version::V1_0_4] {
1406 let mut network =
1407 Network::new(Region::Eu868.plan(), 0x00_00_2A).with_first_dev_addr(0x2601_0001);
1408 network.register(Registration::new(DEV_EUI, APP_EUI, APP_KEY).with_version(version));
1409 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
1410 let request = device.join_request(0x0102);
1411 let Event::Joined { accept, .. } = network
1412 .uplink(&heard(request.as_bytes().to_vec(), 1_000_000))
1413 .expect("the request verifies")
1414 else {
1415 panic!("a join request is admitted");
1416 };
1417 let session = device
1418 .accept_join(&accept.payload, 0x0102)
1419 .expect("the accept verifies")
1420 .session();
1421 let frame = |fcnt: u32| {
1422 session
1423 .encode_uplink(&pamoja_lorawan::Uplink::new(fcnt, 2, b"level"))
1424 .expect("it fits one frame")
1425 .as_bytes()
1426 .to_vec()
1427 };
1428
1429 network
1430 .uplink(&heard(frame(1), 2_000_000))
1431 .expect("the first frame is read");
1432 let jumped = network.uplink(&heard(frame(1 + MAX_FCNT_GAP), 3_000_000));
1433 match version {
1434 Version::V1_0_3 => {
1435 assert!(
1436 matches!(
1437 jumped,
1438 Err(NetworkError::CounterGap { seen: 1, carried, .. }) if carried == 1 + MAX_FCNT_GAP
1439 ),
1440 "{jumped:?}"
1441 );
1442 let Event::Data { fcnt, .. } = network
1443 .uplink(&heard(frame(MAX_FCNT_GAP), 4_000_000))
1444 .expect("one short of the gap is followed")
1445 else {
1446 panic!("a data frame is read");
1447 };
1448 assert_eq!(fcnt, MAX_FCNT_GAP);
1449 }
1450 Version::V1_0_4 => {
1451 let Ok(Event::Data { fcnt, .. }) = jumped else {
1452 panic!("a 1.0.4 counter is followed across the gap: {jumped:?}");
1453 };
1454 assert_eq!(fcnt, 1 + MAX_FCNT_GAP);
1455 }
1456 }
1457 }
1458 }
1459
1460 #[test]
1461 fn a_registration_follows_1_0_4_unless_told_otherwise() {
1462 let registration = Registration::new(DEV_EUI, APP_EUI, APP_KEY);
1463 assert_eq!(registration.version(), Version::V1_0_4);
1464 assert_eq!(
1465 registration.with_version(Version::V1_0_3).version(),
1466 Version::V1_0_3
1467 );
1468 }
1469
1470 #[test]
1471 fn a_counter_that_would_pass_32_bits_ends_the_session() {
1472 let mut network = site();
1473 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
1474 let request = device.join_request(0x0102);
1475 let Event::Joined { accept, .. } = network
1476 .uplink(&heard(request.as_bytes().to_vec(), 1_000_000))
1477 .expect("the request verifies")
1478 else {
1479 panic!("a join request is admitted");
1480 };
1481 let session = device
1482 .accept_join(&accept.payload, 0x0102)
1483 .expect("the accept verifies")
1484 .session();
1485 network.admitted[0].fcnt_up = Some(0xFFFF_FFF0);
1486 let frame = session
1487 .encode_uplink(&pamoja_lorawan::Uplink::new(0x0005, 2, b"level"))
1488 .expect("it fits one frame");
1489 let error = network
1490 .uplink(&heard(frame.as_bytes().to_vec(), 2_000_000))
1491 .expect_err("no counter follows 0xFFFFFFF0 with 0x0005 in its low half");
1492 assert_eq!(
1493 error,
1494 NetworkError::CounterExhausted {
1495 dev_addr: 0x2601_0001
1496 }
1497 );
1498 assert!(error.to_string().contains("must join again"));
1499 }
1500
1501 #[test]
1502 fn a_counter_that_wraps_carries_on_where_it_left_off() {
1503 let mut network = site();
1504 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
1505 let request = device.join_request(0x0102);
1506 let Event::Joined { accept, .. } = network
1507 .uplink(&heard(request.as_bytes().to_vec(), 1_000_000))
1508 .expect("the request verifies")
1509 else {
1510 panic!("a join request is admitted");
1511 };
1512 let session = device
1513 .accept_join(&accept.payload, 0x0102)
1514 .expect("the accept verifies")
1515 .session();
1516
1517 let last = session
1519 .encode_uplink(&pamoja_lorawan::Uplink::new(0xFFFF, 2, b"last"))
1520 .expect("it fits one frame");
1521 network
1522 .uplink(&heard(last.as_bytes().to_vec(), 2_000_000))
1523 .expect("the frame is read");
1524
1525 let wrapped = session
1526 .encode_uplink(&pamoja_lorawan::Uplink::new(0x0001_0000, 2, b"next"))
1527 .expect("it fits one frame");
1528 let event = network
1529 .uplink(&heard(wrapped.as_bytes().to_vec(), 3_000_000))
1530 .expect("the wrapped frame is read");
1531
1532 let Event::Data { fcnt, payload, .. } = event else {
1533 panic!("a data frame is read");
1534 };
1535 assert_eq!(fcnt, 0x0001_0000);
1536 assert_eq!(payload, b"next");
1537 }
1538
1539 #[test]
1540 fn a_frame_for_another_network_is_reported_not_refused() {
1541 let mut network = site();
1542 let stranger = Session::new(0x1234_5678, [9; 16], [8; 16]);
1543 let frame = stranger
1544 .encode_uplink(&pamoja_lorawan::Uplink::new(0, 1, b"hello"))
1545 .expect("it fits one frame");
1546
1547 let event = network
1548 .uplink(&heard(frame.as_bytes().to_vec(), 1_000_000))
1549 .expect("a frame from elsewhere is not an error");
1550 assert_eq!(
1551 event,
1552 Event::Foreign {
1553 dev_addr: 0x1234_5678
1554 }
1555 );
1556 }
1557
1558 #[test]
1559 fn the_first_window_follows_the_region() {
1560 let block = match Rx1Channels::us915() {
1563 Rx1Channels::Downstream(block) => block,
1564 other => panic!("US902-928 answers on its own channels, not {other:?}"),
1565 };
1566 assert_eq!(block.frequency_hz(0), Some(923_300_000));
1567 assert_eq!(block.frequency_hz(7), Some(927_500_000));
1568 assert_eq!(block.count, 8);
1569 }
1570
1571 fn joined_on(network: &mut Network, frequency_hz: u32, link: LinkSettings) -> Txpk {
1573 network.register(Registration::new(DEV_EUI, APP_EUI, APP_KEY));
1574 let request = Device::new(DEV_EUI, APP_EUI, APP_KEY).join_request(0x0102);
1575 let heard =
1576 Rxpk::new(frequency_hz, link, request.as_bytes().to_vec()).with_timestamp_us(1_000_000);
1577 match network.uplink(&heard).expect("the request verifies") {
1578 Event::Joined { accept, .. } => accept,
1579 other => panic!("a join request is admitted, not {other:?}"),
1580 }
1581 }
1582
1583 #[test]
1584 fn a_fixed_plan_is_answered_on_its_downlink_channels_at_its_downlink_rates() {
1585 let mut network = Network::new(Region::Us915.plan(), 0x00_00_2A);
1589 let accept = joined_on(&mut network, 902_300_000, LinkSettings::new(10, 125_000));
1590 assert_eq!(accept.frequency_hz, 923_300_000);
1591 let crate::udp::Modulation::Lora(link) = accept.modulation else {
1592 panic!("a LoRa accept");
1593 };
1594 assert_eq!(
1595 (link.spreading_factor(), link.bandwidth_hz()),
1596 (10, 500_000)
1597 );
1598
1599 let session = Device::new(DEV_EUI, APP_EUI, APP_KEY)
1600 .accept_join(&accept.payload, 0x0102)
1601 .expect("the accept verifies")
1602 .session();
1603 let frame = session
1604 .encode_uplink(&pamoja_lorawan::Uplink::new(0, 2, b"21.5"))
1605 .expect("it fits one frame");
1606 let heard = Rxpk::new(
1607 904_600_000,
1608 LinkSettings::new(8, 500_000),
1609 frame.as_bytes().to_vec(),
1610 )
1611 .with_timestamp_us(9_000_000);
1612 let Event::Data { slot, .. } = network.uplink(&heard).expect("the frame verifies") else {
1613 panic!("a data frame is read");
1614 };
1615 assert_eq!(slot.frequency_hz, 923_900_000);
1616 assert_eq!(
1617 (slot.link.spreading_factor(), slot.link.bandwidth_hz()),
1618 (7, 500_000)
1619 );
1620 }
1621
1622 #[test]
1623 fn a_chinese_join_is_answered_where_its_common_join_channel_says() {
1624 let mut network = Network::new(Cn470Plan::Antenna26MhzA.plan(), 0x00_00_2A);
1627 let accept = joined_on(&mut network, 472_300_000, LinkSettings::new(9, 125_000));
1628 assert_eq!(accept.frequency_hz, 492_500_000);
1629
1630 let mut network = Network::new(Region::Cn470.plan(), 0x00_00_2A);
1631 let accept = joined_on(&mut network, 475_700_000, LinkSettings::new(9, 125_000));
1632 assert_eq!(accept.frequency_hz, 489_300_000);
1633 }
1634
1635 #[test]
1636 fn a_window_override_still_wins_over_the_plan() {
1637 let mut network = Network::new(Region::Us915.plan(), 0x00_00_2A)
1638 .with_windows(Windows::new().with_rx1_channels(Rx1Channels::SameAsUplink));
1639 let accept = joined_on(&mut network, 902_300_000, LinkSettings::new(10, 125_000));
1640 assert_eq!(accept.frequency_hz, 902_300_000);
1641 }
1642
1643 #[test]
1644 fn an_fsk_packet_carries_no_frame() {
1645 let mut network = site();
1646 let mut packet = heard(vec![0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0);
1647 packet.modulation = crate::udp::Modulation::Fsk(50_000);
1648 assert_eq!(network.uplink(&packet), Err(NetworkError::NotLora));
1649 }
1650
1651 const RELAY_EUI: [u8; 8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x88];
1652 const RELAY_KEY: [u8; 16] = [0x5A; 16];
1653
1654 fn site_with_relay() -> (Network, Session, u32) {
1657 let mut network = site();
1658 network.register(Registration::new(RELAY_EUI, APP_EUI, RELAY_KEY));
1659 let relay = Device::new(RELAY_EUI, APP_EUI, RELAY_KEY);
1660 let request = relay.join_request(0x0101);
1661 let Event::Joined { dev_addr, .. } = network
1662 .uplink(&heard(request.as_bytes().to_vec(), 1_000))
1663 .expect("the relay joins")
1664 else {
1665 panic!("the relay joined");
1666 };
1667 let session = network.session(dev_addr).expect("a session");
1668 (network, session, dev_addr)
1669 }
1670
1671 fn forwarded(session: &Session, fcnt: u32, phy_payload: &[u8]) -> Vec<u8> {
1673 let uplink = ForwardedUplink {
1674 metadata: UplinkMetadata {
1675 wor_channel: pamoja_lorawan::relay::WorChannel::Default,
1676 rssi_dbm: -92,
1677 snr_db: 5,
1678 data_rate: 5,
1679 },
1680 frequency_hz: 868_300_000,
1681 phy_payload,
1682 };
1683 let mut payload = vec![0u8; phy_payload.len() + 16];
1684 let len = uplink.encode(&mut payload).expect("it encodes");
1685 session
1686 .encode_uplink(&Uplink::new(fcnt, LA_FPORT_RELAY, &payload[..len]))
1687 .expect("the relay's uplink encodes")
1688 .as_bytes()
1689 .to_vec()
1690 }
1691
1692 #[test]
1693 fn a_join_a_relay_forwards_is_admitted_and_its_accept_goes_back_the_same_way() {
1694 let (mut network, relay_session, relay_addr) = site_with_relay();
1695 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
1696 let request = device.join_request(0x1234);
1697
1698 let event = network
1699 .uplink(&heard(
1700 forwarded(&relay_session, 0, request.as_bytes()),
1701 5_000,
1702 ))
1703 .expect("the forwarded request verifies");
1704 let Event::Joined {
1705 dev_eui,
1706 dev_addr,
1707 accept,
1708 } = event
1709 else {
1710 panic!("the device joined: {event:?}");
1711 };
1712 assert_eq!(dev_eui, DEV_EUI);
1713 assert_ne!(dev_addr, relay_addr);
1714
1715 let to_relay = relay_session
1718 .decode(&accept.payload, 0)
1719 .expect("the relay's downlink decodes");
1720 assert_eq!(to_relay.fport(), Some(LA_FPORT_RELAY));
1721 let session = device
1722 .accept_join(to_relay.payload(), 0x1234)
1723 .expect("the device's own accept")
1724 .session();
1725 assert_eq!(session.dev_addr(), dev_addr);
1726 }
1727
1728 #[test]
1729 fn an_uplink_a_relay_forwards_carries_what_it_heard_and_is_answered_through_it() {
1730 let (mut network, relay_session, _) = site_with_relay();
1731 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
1732 let request = device.join_request(0x1234);
1733 let Event::Joined { dev_addr, .. } = network
1734 .uplink(&heard(
1735 forwarded(&relay_session, 0, request.as_bytes()),
1736 5_000,
1737 ))
1738 .expect("the device joins")
1739 else {
1740 panic!("the device joined");
1741 };
1742 let session = network.session(dev_addr).expect("the granted session");
1743
1744 let reading = session
1745 .encode_uplink(&Uplink::new(0, 2, b"21.5"))
1746 .expect("the reading encodes");
1747 let event = network
1748 .uplink(&heard(
1749 forwarded(&relay_session, 1, reading.as_bytes()),
1750 9_000,
1751 ))
1752 .expect("the forwarded reading verifies");
1753 let Event::Data {
1754 dev_addr: from,
1755 payload,
1756 relay,
1757 slot,
1758 ..
1759 } = event
1760 else {
1761 panic!("a reading came through: {event:?}");
1762 };
1763 assert_eq!(from, dev_addr);
1764 assert_eq!(payload, b"21.5");
1765 let relayed = relay.expect("it came through a relay");
1766 assert_eq!(relayed.metadata.rssi_dbm, -92);
1767 assert_eq!(relayed.metadata.snr_db, 5);
1768 assert_eq!(relayed.frequency_hz, 868_300_000);
1769
1770 let answer = network
1772 .answer(dev_addr, slot, 2, b"ok")
1773 .expect("an answer for the device");
1774 let to_relay = relay_session
1776 .decode(&answer.payload, 1)
1777 .expect("the relay's downlink decodes");
1778 assert_eq!(to_relay.fport(), Some(LA_FPORT_RELAY));
1779 let inside = session
1780 .decode(to_relay.payload(), 0)
1781 .expect("the device's own downlink");
1782 assert_eq!(inside.payload(), b"ok");
1783 }
1784
1785 #[test]
1786 fn a_relay_reports_the_devices_it_cannot_verify_and_is_told_to_trust_them() {
1787 let (mut network, relay_session, relay_addr) = site_with_relay();
1788 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
1789 let request = device.join_request(0x1234);
1790 let Event::Joined { dev_addr, .. } = network
1791 .uplink(&heard(
1792 forwarded(&relay_session, 0, request.as_bytes()),
1793 5_000,
1794 ))
1795 .expect("the device joins")
1796 else {
1797 panic!("the device joined");
1798 };
1799
1800 let mut fopts = [0u8; MAX_COMMAND];
1802 let len = encode_all(
1803 &[MacCommand::NotifyNewEndDeviceReq {
1804 dev_addr: 0x2601_9999,
1805 rssi_dbm: -100,
1806 snr_db: 3,
1807 }],
1808 &mut fopts,
1809 )
1810 .expect("the notice encodes");
1811 let uplink = relay_session
1812 .encode_uplink(&Uplink::empty(1).with_fopts(&fopts[..len]))
1813 .expect("the relay's uplink encodes");
1814 let event = network
1815 .uplink(&heard(uplink.as_bytes().to_vec(), 11_000))
1816 .expect("the uplink verifies");
1817 let slot = match event {
1818 Event::Data { slot, .. } => slot,
1819 other => panic!("the relay sent an uplink: {other:?}"),
1820 };
1821 assert_eq!(
1822 network.notices(),
1823 vec![Notice {
1824 relay: relay_addr,
1825 dev_addr: 0x2601_9999,
1826 rssi_dbm: -100,
1827 snr_db: 3,
1828 }]
1829 );
1830 assert!(network.notices().is_empty(), "each notice is read once");
1831
1832 let command = network
1835 .trust_command(dev_addr, 0, 63, 0)
1836 .expect("the device has a session");
1837 let MacCommand::UpdateUplinkListReq {
1838 root_wor_s_key,
1839 dev_addr: trusted,
1840 ..
1841 } = command
1842 else {
1843 panic!("an UpdateUplinkListReq: {command:?}");
1844 };
1845 assert_eq!(trusted, dev_addr);
1846 assert_eq!(
1847 root_wor_s_key,
1848 network
1849 .session(dev_addr)
1850 .expect("a session")
1851 .root_wor_s_key(),
1852 );
1853
1854 let txpk = network
1855 .command(relay_addr, slot, &[command])
1856 .expect("a downlink for the relay");
1857 let to_relay = relay_session
1858 .decode(&txpk.payload, 1)
1859 .expect("the relay's downlink decodes");
1860 let carried: Vec<_> = MacCommands::new(Direction::Downlink, to_relay.payload())
1861 .flatten()
1862 .collect();
1863 assert_eq!(carried, vec![command], "a long command takes port 0");
1864 assert_eq!(to_relay.fport(), Some(0));
1865 }
1866
1867 #[test]
1868 fn a_relay_that_forwards_nonsense_is_refused_without_taking_the_network_down() {
1869 let (mut network, relay_session, _) = site_with_relay();
1870 let uplink = relay_session
1871 .encode_uplink(&Uplink::new(0, LA_FPORT_RELAY, &[0x01, 0x02]))
1872 .expect("the relay's uplink encodes");
1873 assert!(matches!(
1874 network.uplink(&heard(uplink.as_bytes().to_vec(), 3_000)),
1875 Err(NetworkError::Relayed(_))
1876 ));
1877
1878 let stranger = Session::new(0x2601_5555, [0x11; 16], [0x22; 16]);
1879 let frame = stranger
1880 .encode_uplink(&Uplink::new(0, 1, b"hi"))
1881 .expect("it encodes");
1882 assert_eq!(
1883 network.uplink(&heard(
1884 forwarded(&relay_session, 1, frame.as_bytes()),
1885 4_000
1886 )),
1887 Ok(Event::Foreign {
1888 dev_addr: 0x2601_5555
1889 }),
1890 "a device this network never granted",
1891 );
1892 }
1893}