1use core::slice;
17use core::sync::atomic::AtomicU8;
18use core::sync::atomic::AtomicU32;
19use core::sync::atomic::Ordering;
20
21use byte::BytesExt;
22use byte::TryRead;
23use management::NlmeEdScanConfirm;
24use management::NlmeEdScanRequest;
25use management::NlmeJoinConfirm;
26use management::NlmeJoinRequest;
27use management::NlmeJoinStatus;
28use management::NlmeLeaveConfirm;
29use management::NlmeLeaveIndication;
30use management::NlmeLeaveRequest;
31use management::NlmeLeaveStatus;
32use management::NlmeNetworkDiscoveryConfirm;
33use management::NlmeNetworkFormationConfirm;
34use management::NlmeNetworkFormationRequest;
35use management::NlmeNwkStatusIndication;
36use management::NlmePermitJoiningConfirm;
37use management::NlmePermitJoiningRequest;
38use management::NlmeStartRouterConfirm;
39use management::NlmeStartRouterRequest;
40use management::RejoinNetwork;
41use thiserror::Error;
42use zigbee_mac::Address;
43use zigbee_mac::AssociationStatus;
44use zigbee_mac::MacShortAddress;
45use zigbee_mac::PanId;
46use zigbee_mac::mlme::MacConfig;
47use zigbee_mac::mlme::MacError;
48use zigbee_mac::mlme::Mlme;
49use zigbee_mac::mlme::ScanType;
50use zigbee_types::IeeeAddress;
51use zigbee_types::ShortAddress;
52use zigbee_types::StorageVec;
53use zigbee_types::sync::Signal;
54use zigbee_types::sync::with_timeout;
55
56use crate::nwk::frame::CommandFrame as NwkCommandFrame;
57use crate::nwk::frame::DataFrame as NwkDataFrame;
58use crate::nwk::frame::Frame as NwkFrame;
59use crate::nwk::frame::command::Command;
60use crate::nwk::frame::command::end_device_timeout_request;
61use crate::nwk::frame::command::end_device_timeout_request::EndDeviceTimeoutRequest;
62use crate::nwk::frame::command::end_device_timeout_response::EndDeviceTimeoutResponse;
63use crate::nwk::frame::command::leave::CommandOptions as LeaveCommandOptions;
64use crate::nwk::frame::command::leave::Leave;
65use crate::nwk::frame::command::network_status::NetworkStatusCode;
66use crate::nwk::frame::command::rejoin_request;
67use crate::nwk::frame::command::rejoin_request::RejoinRequest;
68use crate::nwk::frame::command::rejoin_response::RejoinResponse;
69use crate::nwk::frame::frame_control::DiscoverRoute;
70use crate::nwk::frame::frame_control::FrameControl as NwkFrameControl;
71use crate::nwk::frame::frame_control::FrameType as NwkFrameType;
72use crate::nwk::frame::header::Header as NwkHeader;
73use crate::nwk::nib;
74use crate::nwk::nib::CapabilityInformation;
75use crate::nwk::nib::DeviceType;
76use crate::nwk::nib::MAX_PARENT_LINK_COST;
77use crate::nwk::nib::NWK_BROADCAST_ADDRESS_MIN;
78use crate::nwk::nib::NWK_BROADCAST_ALL;
79use crate::nwk::nib::NWK_COORDINATOR_ADDRESS;
80use crate::nwk::nib::NWK_UNASSIGNED_ADDRESS;
81use crate::nwk::nib::Nib;
82use crate::nwk::nib::NwkNeighbor;
83use crate::nwk::nib::link_cost_from_lqi;
84use crate::nwk::nib::relationship;
85use crate::security::SecurityContext;
86
87pub mod management;
89
90const NO_PENDING_TIMEOUT: u8 = 0xff;
92
93const REJOIN_RESPONSE_POLL_RETRIES: u8 = 20;
96
97const TIMEOUT_RESPONSE_POLL_RETRIES: u8 = 3;
100
101const PARENT_TIMEOUT_INACTIVE: u32 = 0;
104
105const MAX_PARENT_TRANSMIT_FAILURES: u8 = 3;
108
109#[derive(Debug, Error)]
110pub enum NetworkError {
111 #[error("mac error: {0}")]
112 MacError(#[from] MacError),
113 #[error("not joined to a network")]
114 NotJoined,
115 #[error("no transport key received from coordinator")]
116 NoTransportKey,
117 #[error("frame parse error")]
118 ParseError,
119 #[error("invalid frame")]
120 InvalidFrame,
121 #[error("frame does not fit a single APS frame")]
122 FrameTooLong,
123 #[error("no APS acknowledgement received")]
124 AckTimeout,
125 #[error("parent link failure")]
126 ParentLinkFailure,
127 #[error("security error: {0}")]
128 SecurityError(#[from] crate::security::SecurityError),
129}
130
131impl From<byte::Error> for NetworkError {
132 fn from(_: byte::Error) -> Self {
133 Self::ParseError
134 }
135}
136
137pub struct Nlme<M> {
143 mac: M,
144 nwk_seq: AtomicU8,
145 pending_timeout_request: AtomicU8,
147 parent_timeout_remaining_ms: AtomicU32,
152 rejoin_response: Signal<RejoinResponse>,
155 timeout_response: Signal<EndDeviceTimeoutResponse>,
158 leave_indication: Signal<NlmeLeaveIndication>,
161 nwk_status_indication: Signal<NlmeNwkStatusIndication>,
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum KeepaliveMethod {
169 MacDataPoll,
172 TimeoutRequest,
175 None,
178}
179
180impl<M> Nlme<M>
181where
182 M: Mlme,
183{
184 pub fn new(mac: M) -> Self {
191 nib::get_ref().update_ieee_address(|value| *value = mac.ieee_address());
192 Self {
193 mac,
194 nwk_seq: AtomicU8::new(0),
195 pending_timeout_request: AtomicU8::new(NO_PENDING_TIMEOUT),
196 parent_timeout_remaining_ms: AtomicU32::new(PARENT_TIMEOUT_INACTIVE),
197 rejoin_response: Signal::new(),
198 timeout_response: Signal::new(),
199 leave_indication: Signal::new(),
200 nwk_status_indication: Signal::new(),
201 }
202 }
203
204 pub fn take_leave_indication(&self) -> Option<NlmeLeaveIndication> {
210 self.leave_indication.try_take()
211 }
212
213 pub async fn wait_leave_indication(&self) -> NlmeLeaveIndication {
215 self.leave_indication.wait().await
216 }
217
218 pub fn take_nwk_status_indication(&self) -> Option<NlmeNwkStatusIndication> {
225 self.nwk_status_indication.try_take()
226 }
227
228 pub async fn wait_nwk_status_indication(&self) -> NlmeNwkStatusIndication {
230 self.nwk_status_indication.wait().await
231 }
232
233 fn next_nwk_seq(&self) -> u8 {
234 self.nwk_seq.fetch_add(1, Ordering::Relaxed).wrapping_add(1)
235 }
236
237 fn build_nwk_data_frame(
242 &self,
243 destination: ShortAddress,
244 secure: bool,
245 payload: &[u8],
246 buf: &mut [u8],
247 ) -> Result<usize, NetworkError> {
248 let nib = self.nib();
249 let frame_control = NwkFrameControl(0)
250 .set_frame_type(NwkFrameType::Data)
251 .set_protocol_version(2)
252 .set_discover_route(DiscoverRoute::Suppress)
253 .set_security_flag(secure);
254
255 let seq = self.next_nwk_seq();
256 let header = NwkHeader {
257 frame_control,
258 destination,
259 source: ShortAddress(*nib.network_address()),
260 radius: 30,
261 sequence_number: seq,
262 destination_ieee: None,
263 source_ieee: None,
264 multicast_control: None,
265 source_route_subframe: None,
266 };
267
268 if secure {
269 let nwk_frame = NwkFrame::Data(NwkDataFrame { header, payload });
270 let cx = SecurityContext::get();
271 let len = cx.encrypt_nwk_frame_in_place(nwk_frame, buf)?;
272 Ok(len)
273 } else {
274 let offset = &mut 0;
275 buf.write_with(offset, header, ())?;
276 let hdr_len = *offset;
277 let payload_len = payload.len().min(buf.len() - hdr_len);
278 buf[hdr_len..hdr_len + payload_len].copy_from_slice(&payload[..payload_len]);
279 Ok(hdr_len + payload_len)
280 }
281 }
282
283 fn build_nwk_command_frame(
289 &self,
290 destination: ShortAddress,
291 command: Command<'_>,
292 secure: bool,
293 buf: &mut [u8],
294 ) -> Result<usize, NetworkError> {
295 let nib = self.nib();
296 let destination_ieee = self.neighbor_ieee_address(destination);
299 let frame_control = NwkFrameControl(0)
300 .set_frame_type(NwkFrameType::NwkCommand)
301 .set_protocol_version(2)
302 .set_discover_route(DiscoverRoute::Suppress)
303 .set_security_flag(secure)
304 .set_destination_ieee_flag(destination_ieee.is_some())
305 .set_source_ieee_flag(true);
306
307 let header = NwkHeader {
308 frame_control,
309 destination,
310 source: ShortAddress(*nib.network_address()),
311 radius: 1,
312 sequence_number: self.next_nwk_seq(),
313 destination_ieee,
314 source_ieee: Some(*nib.ieee_address()),
315 multicast_control: None,
316 source_route_subframe: None,
317 };
318
319 if !secure {
320 let offset = &mut 0;
321 buf.write_with(offset, header, ())?;
322 buf.write_with(offset, command, ())?;
323 return Ok(*offset);
324 }
325
326 let nwk_frame = NwkFrame::NwkCommand(NwkCommandFrame { header, command });
327 let cx = SecurityContext::get();
328 let len = cx.encrypt_nwk_frame_in_place(nwk_frame, buf)?;
329 Ok(len)
330 }
331
332 fn join_failure(status: NlmeJoinStatus) -> NlmeJoinConfirm {
334 NlmeJoinConfirm {
335 status,
336 network_address: ShortAddress(0xffff),
337 extended_pan_id: IeeeAddress(0u64),
338 channel: 0,
339 enhanced_beacon_type: false,
340 mac_interface_index: 0u8,
341 }
342 }
343
344 pub async fn send_end_device_timeout_request(&self) -> Result<(), NetworkError> {
351 let requested_timeout = *self.nib().end_device_timeout_default();
352 let command = Command::EndDeviceTimeoutRequest(EndDeviceTimeoutRequest {
353 requested_timeout,
354 end_device_configuration: 0x00,
356 });
357
358 let mut buf = [0u8; 256];
359 let len =
360 self.build_nwk_command_frame(self.parent_short_address()?, command, true, &mut buf)?;
361 self.pending_timeout_request
362 .store(requested_timeout, Ordering::Relaxed);
363 self.mac
364 .transmit_data(self.parent_address()?, &buf[..len])
365 .await?;
366 Ok(())
367 }
368
369 pub async fn resume(&self, channel: u8) -> bool {
378 let nib = self.nib();
379 let network_address = *nib.network_address();
380 let on_a_network = network_address != NWK_UNASSIGNED_ADDRESS;
381
382 let config = if on_a_network {
383 MacConfig::joined(channel, PanId(*nib.panid()), ShortAddress(network_address))
384 } else {
385 MacConfig::channel(channel)
386 };
387 self.mac.configure(config).await;
388
389 on_a_network
390 }
391
392 pub async fn negotiate_end_device_timeout(&self) -> Result<bool, NetworkError> {
400 self.timeout_response.reset();
403 self.send_end_device_timeout_request().await?;
404
405 let response = with_timeout(
406 self.timeout_response.wait(),
407 self.poll_until_timeout_response(TIMEOUT_RESPONSE_POLL_RETRIES),
408 )
409 .await
410 .or_else(|| self.timeout_response.try_take());
411
412 match response {
413 Some(response) if response.status == EndDeviceTimeoutResponse::STATUS_SUCCESS => {
414 Ok(true)
415 }
416 Some(response) => {
417 log::warn!(
418 "[NWK] end-device timeout rejected (status={:#04x})",
419 response.status
420 );
421 Ok(false)
422 }
423 None => {
424 log::warn!("[NWK] no end-device timeout response, assuming legacy parent");
425 Ok(false)
426 }
427 }
428 }
429
430 pub fn negotiated_poll_interval_ms(&self) -> Option<u32> {
436 if self.keepalive_method() != KeepaliveMethod::MacDataPoll {
437 return None;
438 }
439 self.keepalive_interval_ms()
440 }
441
442 pub fn keepalive_method(&self) -> KeepaliveMethod {
447 let nib = self.nib();
448 if end_device_timeout_request::timeout_seconds(*nib.end_device_timeout()).is_none() {
449 return KeepaliveMethod::None;
450 }
451 let parent_information = *nib.parent_information();
452 if parent_information & EndDeviceTimeoutResponse::MAC_DATA_POLL_KEEPALIVE != 0 {
453 KeepaliveMethod::MacDataPoll
454 } else if parent_information & EndDeviceTimeoutResponse::TIMEOUT_REQUEST_KEEPALIVE != 0 {
455 KeepaliveMethod::TimeoutRequest
456 } else {
457 KeepaliveMethod::None
458 }
459 }
460
461 pub fn keepalive_interval_ms(&self) -> Option<u32> {
466 Some(self.negotiated_timeout_ms()? / 3)
467 }
468
469 fn negotiated_timeout_ms(&self) -> Option<u32> {
471 let seconds =
472 end_device_timeout_request::timeout_seconds(*self.nib().end_device_timeout())?;
473 Some(seconds.saturating_mul(1000))
474 }
475
476 pub async fn send_keepalive(&self) -> Result<(), NetworkError> {
485 match self.keepalive_method() {
486 KeepaliveMethod::MacDataPoll => self.mac_data_poll_keepalive().await,
487 KeepaliveMethod::TimeoutRequest => self.timeout_request_keepalive().await,
488 KeepaliveMethod::None => Ok(()),
489 }
490 }
491
492 async fn mac_data_poll_keepalive(&self) -> Result<(), NetworkError> {
496 let mut buf = [0u8; 256];
497 match self.poll_nwk_frame(&mut buf).await {
498 Ok(Some(_)) => log::debug!("[NWK] keepalive poll dropped a data frame"),
499 Ok(None) => (),
500 Err(e) => return Err(e),
501 }
502 self.refresh_parent_timeout();
503 Ok(())
504 }
505
506 async fn timeout_request_keepalive(&self) -> Result<(), NetworkError> {
510 self.timeout_response.reset();
513 if let Err(e) = self.send_end_device_timeout_request().await {
514 log::warn!("[NWK] keepalive transmission failed ({e:?})");
515 return Err(self.parent_link_failure());
516 }
517
518 let response = with_timeout(
519 self.timeout_response.wait(),
520 self.poll_until_timeout_response(TIMEOUT_RESPONSE_POLL_RETRIES),
521 )
522 .await
523 .or_else(|| self.timeout_response.try_take());
525
526 match response {
527 Some(response) if response.status == EndDeviceTimeoutResponse::STATUS_SUCCESS => {
528 self.refresh_parent_timeout();
529 Ok(())
530 }
531 Some(response) => {
532 log::warn!("[NWK] keepalive rejected (status={:#04x})", response.status);
533 Err(self.parent_link_failure())
534 }
535 None => {
536 log::warn!("[NWK] no keepalive response from parent");
537 Err(self.parent_link_failure())
538 }
539 }
540 }
541
542 async fn poll_until_timeout_response(&self, retries: u8) {
546 let mut buf = [0u8; 128];
547 for _ in 0..retries {
548 if self.timeout_response.is_signaled() {
549 return;
550 }
551 match self.poll_parent(&mut buf).await {
552 Ok(Some(len)) => match self.process_received_nwk_frame(&mut buf[..len]).await {
553 Ok(Some(_)) => log::debug!("[NWK] keepalive poll dropped a data frame"),
554 Ok(None) => (),
555 Err(e) => log::debug!("[NWK] keepalive frame not processed: {e:?}"),
556 },
557 Ok(None) => (),
558 Err(e) => log::debug!("[NWK] keepalive poll failed: {e:?}"),
559 }
560 }
561 }
562
563 pub fn refresh_parent_timeout(&self) {
566 let remaining = self
567 .negotiated_timeout_ms()
568 .unwrap_or(PARENT_TIMEOUT_INACTIVE);
569 self.parent_timeout_remaining_ms
570 .store(remaining, Ordering::Relaxed);
571 }
572
573 pub fn tick_parent_timeout(&self, elapsed_ms: u32) -> bool {
581 let remaining = self.parent_timeout_remaining_ms.load(Ordering::Relaxed);
582 if remaining == PARENT_TIMEOUT_INACTIVE {
583 return false;
584 }
585 let remaining = remaining.saturating_sub(elapsed_ms.max(1));
586 self.parent_timeout_remaining_ms
587 .store(remaining, Ordering::Relaxed);
588 if remaining != PARENT_TIMEOUT_INACTIVE {
589 return false;
590 }
591 log::warn!("[NWK] end-device timeout elapsed, assuming aged out by parent");
592 self.parent_link_failure();
593 true
594 }
595
596 async fn transmit_via_parent(&self, buf: &[u8]) -> Result<(), NetworkError> {
604 let parent = self.parent_address()?;
605 let Err(e) = self.mac.transmit_data(parent, buf).await else {
606 self.update_parent_neighbor(|neighbor| neighbor.transmit_failure = 0);
609 self.refresh_parent_timeout();
610 return Ok(());
611 };
612
613 let mut failures = 0;
614 self.update_parent_neighbor(|neighbor| {
615 neighbor.transmit_failure = neighbor.transmit_failure.saturating_add(1);
616 failures = neighbor.transmit_failure;
617 });
618 log::debug!("[NWK] transmission via parent failed ({e:?}), failures={failures}");
619 if failures < MAX_PARENT_TRANSMIT_FAILURES {
620 return Err(e.into());
621 }
622
623 self.update_parent_neighbor(|neighbor| neighbor.transmit_failure = 0);
624 Err(self.parent_link_failure())
625 }
626
627 fn update_parent_neighbor(&self, update: impl FnOnce(&mut NwkNeighbor)) {
629 self.nib().update_neighbor_table(|table| {
630 if let Some(parent) = table
631 .iter_mut()
632 .find(|n| n.relationship == relationship::PARENT)
633 {
634 update(parent);
635 }
636 });
637 }
638
639 fn parent_link_failure(&self) -> NetworkError {
641 let network_address = self
642 .parent_short_address()
643 .unwrap_or(ShortAddress(NWK_UNASSIGNED_ADDRESS));
644 self.nwk_status_indication.signal(NlmeNwkStatusIndication {
645 status: NetworkStatusCode::ParentLinkFailure,
646 network_address,
647 });
648 NetworkError::ParentLinkFailure
649 }
650
651 pub fn nib(&self) -> &'static Nib {
653 nib::get_ref()
654 }
655
656 fn select_parent_candidates(
658 &self,
659 extended_pan_id: IeeeAddress,
660 join_as_router: bool,
661 ) -> heapless::Vec<usize, 16> {
662 self.select_candidates(extended_pan_id, join_as_router, true)
663 }
664
665 fn select_rejoin_candidates(
670 &self,
671 extended_pan_id: IeeeAddress,
672 join_as_router: bool,
673 ) -> heapless::Vec<usize, 16> {
674 self.select_candidates(extended_pan_id, join_as_router, false)
675 }
676
677 fn select_candidates(
678 &self,
679 extended_pan_id: IeeeAddress,
680 join_as_router: bool,
681 require_permit_joining: bool,
682 ) -> heapless::Vec<usize, 16> {
683 let table = self.nib().neighbor_table();
684 log::debug!("[NWK-JOIN] neighbor table: {table:#?}");
685 let stack_profile = self.nib().stack_profile();
686 let permits = |n: &NwkNeighbor| !require_permit_joining || n.permit_joining;
687
688 let mut matching = table.iter().filter(|n| {
692 n.extended_pan_id == extended_pan_id && permits(n) && n.potential_parent == 1
693 });
694
695 let Some(first) = matching.next() else {
696 return heapless::Vec::new();
697 };
698
699 let best_update_id = matching
700 .map(|n| n.update_id)
701 .fold(first.update_id, |best, id| {
702 if id.wrapping_sub(best).cast_signed() > 0 {
704 id
705 } else {
706 best
707 }
708 });
709
710 let mut candidates: heapless::Vec<usize, 16> = table
711 .iter()
712 .enumerate()
713 .filter(|(_, n)| {
714 n.extended_pan_id == extended_pan_id
718 && permits(n)
719 && if join_as_router {
720 n.router_capacity
721 } else {
722 n.end_device_capacity
723 }
724 && link_cost_from_lqi(n.lqi) <= MAX_PARENT_LINK_COST
725 && n.potential_parent == 1
726 && n.update_id == best_update_id
727 })
728 .map(|(i, _)| i)
729 .collect();
730
731 if *stack_profile == 1 {
733 candidates.sort_unstable_by_key(|&i| table[i].depth);
734 }
735
736 candidates
737 }
738
739 fn build_mac_capabilities(cap: &CapabilityInformation) -> zigbee_mac::CapabilityInformation {
742 zigbee_mac::CapabilityInformation {
743 full_function_device: cap.device_type(),
745 mains_power: cap.power_source(),
747 idle_receive: cap.receiver_on_when_idle(),
749 frame_protection: cap.security_capability(),
751 allocate_address: cap.allocate_address(),
753 }
754 }
755
756 fn reset_parent_negotiation(&self) {
759 let nib = self.nib();
760 nib.update_parent_information(|value| *value = 0);
761 nib.update_end_device_timeout(|value| *value = NO_PENDING_TIMEOUT);
762 self.parent_timeout_remaining_ms
763 .store(PARENT_TIMEOUT_INACTIVE, Ordering::Relaxed);
764 }
765
766 fn parent_address(&self) -> Result<Address, NetworkError> {
768 let addr = Address::Short(
769 PanId(*self.nib().panid()),
770 MacShortAddress(self.parent_short_address()?.0),
771 );
772 Ok(addr)
773 }
774
775 fn neighbor_ieee_address(&self, network_address: ShortAddress) -> Option<IeeeAddress> {
778 self.nib()
779 .neighbor_table()
780 .iter()
781 .find(|n| n.network_address == network_address)
782 .map(|n| n.extended_address)
783 .filter(|ieee| ieee.0 != 0)
784 }
785
786 pub(crate) fn short_address_for_ieee(&self, ieee: IeeeAddress) -> Option<ShortAddress> {
789 self.nib()
790 .neighbor_table()
791 .iter()
792 .find(|n| n.extended_address == ieee)
793 .map(|n| n.network_address)
794 }
795
796 fn learn_neighbor_ieee_address(&self, header: &NwkHeader<'_>) {
799 let Some(source_ieee) = header.source_ieee else {
800 return;
801 };
802 self.nib().update_neighbor_table(|table| {
803 if let Some(neighbor) = table
804 .iter_mut()
805 .find(|n| n.network_address == header.source)
806 {
807 neighbor.extended_address = source_ieee;
808 }
809 });
810 }
811
812 fn parent_short_address(&self) -> Result<ShortAddress, NetworkError> {
814 let table = self.nib().neighbor_table();
815 let parent = table
816 .iter()
817 .find(|n| n.relationship == relationship::PARENT)
818 .ok_or(NetworkError::NotJoined)?;
819 Ok(parent.network_address)
820 }
821
822 async fn poll_parent(&self, buf: &mut [u8]) -> Result<Option<usize>, NetworkError> {
826 let coord_addr = self.parent_address()?;
827 match self.mac.poll_data(coord_addr, buf).await {
828 Ok((len, _lqi)) => Ok(Some(len)),
829 Err(MacError::NoData) => Ok(None),
830 Err(e) => Err(e.into()),
831 }
832 }
833
834 pub async fn poll_nwk_frame<'a>(
841 &self,
842 buf: &'a mut [u8],
843 ) -> Result<Option<NwkDataFrame<'a>>, NetworkError> {
844 let Some(len) = self.poll_parent(buf).await? else {
845 return Ok(None);
846 };
847 self.process_received_nwk_frame(&mut buf[..len]).await
848 }
849
850 pub async fn receive_nwk_frame<'a>(
856 &self,
857 buf: &'a mut [u8],
858 ) -> Result<Option<NwkDataFrame<'a>>, NetworkError> {
859 let (len, _lqi) = self.mac.receive(buf).await?;
860 self.process_received_nwk_frame(&mut buf[..len]).await
861 }
862
863 async fn process_received_nwk_frame<'a>(
865 &self,
866 frame_buf: &'a mut [u8],
867 ) -> Result<Option<NwkDataFrame<'a>>, NetworkError> {
868 let cx = SecurityContext::get();
869 let nwk_frame = cx.decrypt_nwk_frame_in_place(frame_buf)?;
870
871 match nwk_frame {
872 NwkFrame::Data(data_frame) => {
873 self.learn_neighbor_ieee_address(&data_frame.header);
874 Ok(Some(data_frame))
875 }
876 NwkFrame::NwkCommand(command_frame) => {
880 self.learn_neighbor_ieee_address(&command_frame.header);
881 self.handle_nwk_command(&command_frame.header, command_frame.command)
882 .await;
883 Ok(None)
884 }
885 _ => Ok(None),
886 }
887 }
888
889 async fn handle_nwk_command(&self, header: &NwkHeader<'_>, command: Command<'_>) {
896 match command {
897 Command::RouteRequest(_) => log::trace!("[NWK] route request (ignored)"),
900 Command::RouteReply(_) => log::trace!("[NWK] route reply (ignored)"),
901 Command::RouteRecord(_) => log::trace!("[NWK] route record (ignored)"),
902 Command::NetworkStatus(_) => log::trace!("[NWK] network status (ignored)"),
904 Command::NetworkReport(_) => log::trace!("[NWK] network report (ignored)"),
905 Command::NetworkUpdate(_) => log::trace!("[NWK] network update (ignored)"),
906 Command::Leave(leave) => self.handle_leave_command(header, &leave).await,
907 Command::RejoinRequest(_) => log::trace!("[NWK] rejoin request (ignored)"),
911 Command::RejoinResponse(response) => {
912 log::debug!(
913 "[NWK] rejoin response: address={:?}, status={:#04x}",
914 response.network_address,
915 response.status
916 );
917 if response.status == u8::from(AssociationStatus::Successful) {
918 self.nib()
921 .update_network_address(|value| *value = response.network_address.0);
922 }
923 self.rejoin_response.signal(response);
924 }
925 Command::LinkStatus(_) => log::trace!("[NWK] link status (ignored)"),
927 Command::LinkPowerDelta(_) => log::trace!("[NWK] link power delta (ignored)"),
928 Command::EndDeviceTimeoutRequest(_) => {
930 log::trace!("[NWK] end-device timeout request (ignored)");
931 }
932 Command::EndDeviceTimeoutResponse(response) => {
935 let pending = self
936 .pending_timeout_request
937 .swap(NO_PENDING_TIMEOUT, Ordering::Relaxed);
938 if response.status == EndDeviceTimeoutResponse::STATUS_SUCCESS
939 && pending != NO_PENDING_TIMEOUT
940 {
941 let nib = self.nib();
942 nib.update_parent_information(|value| *value = response.parent_information);
943 nib.update_end_device_timeout(|value| *value = pending);
944 self.refresh_parent_timeout();
947 log::info!(
948 "[NWK] end-device timeout negotiated: enum={pending}, parent_information={:#04x}",
949 response.parent_information
950 );
951 } else {
952 log::warn!(
953 "[NWK] end-device timeout response rejected or unexpected (status={:#04x})",
954 response.status
955 );
956 }
957 self.timeout_response.signal(response);
959 }
960 Command::Reserved(id) => log::trace!("[NWK] reserved command {id:#04x} (ignored)"),
961 }
962 }
963
964 async fn handle_leave_command(&self, header: &NwkHeader<'_>, leave: &Leave) {
969 let options = leave.command_options;
970 let is_from_parent = self
971 .parent_short_address()
972 .is_ok_and(|parent| parent == header.source);
973
974 if !options.request() {
975 self.nib().update_neighbor_table(|table| {
978 table.retain(|n| n.network_address != header.source);
979 });
980
981 if is_from_parent {
982 self.nib().update_extended_panid(|value| *value = 0);
985 log::info!("[NWK] removed by parent (leave notification)");
986 self.leave_indication.signal(NlmeLeaveIndication {
987 device_address: None,
988 rejoin: options.rejoin(),
989 });
990 } else {
991 log::debug!(
992 "[NWK] neighbor {:?} left the network (rejoin={})",
993 header.source_ieee,
994 options.rejoin()
995 );
996 self.leave_indication.signal(NlmeLeaveIndication {
997 device_address: header.source_ieee,
998 rejoin: options.rejoin(),
999 });
1000 }
1001 return;
1002 }
1003
1004 let from_parent = self
1008 .parent_short_address()
1009 .is_ok_and(|parent| parent == header.source);
1010 if !self.accepts_leave_request(header.destination, from_parent) {
1011 return;
1012 }
1013
1014 if let Err(e) = self
1015 .leave_network(options.remove_children(), options.rejoin())
1016 .await
1017 {
1018 log::warn!("[NWK] failed to announce leave: {e:?}");
1019 }
1020
1021 self.leave_indication.signal(NlmeLeaveIndication {
1025 device_address: None,
1026 rejoin: options.rejoin(),
1027 });
1028 }
1029
1030 pub fn accepts_leave_request(&self, destination: ShortAddress, from_parent: bool) -> bool {
1039 if *self.nib().network_address() == NWK_COORDINATOR_ADDRESS
1042 || destination.0 >= NWK_BROADCAST_ADDRESS_MIN
1043 {
1044 log::trace!("[NWK] leave request dropped (coordinator or broadcast destination)");
1045 return false;
1046 }
1047
1048 if self.nib().capability_information().device_type() {
1051 let allowed = *self.nib().leave_request_allowed();
1052 if !allowed {
1053 log::trace!("[NWK] leave request refused (nwkLeaveRequestAllowed is FALSE)");
1054 }
1055 return allowed;
1056 }
1057
1058 if !from_parent {
1060 log::trace!("[NWK] leave request refused (sender is not our parent)");
1061 }
1062 from_parent
1063 }
1064
1065 pub async fn leave(&self, request: NlmeLeaveRequest) -> NlmeLeaveConfirm {
1071 if *self.nib().network_address() == NWK_UNASSIGNED_ADDRESS {
1072 return NlmeLeaveConfirm {
1073 status: NlmeLeaveStatus::InvalidRequest,
1074 device_address: request.device_address,
1075 };
1076 }
1077
1078 let is_self = request
1079 .device_address
1080 .is_none_or(|addr| addr == *self.nib().ieee_address());
1081 if !is_self {
1082 return NlmeLeaveConfirm {
1083 status: NlmeLeaveStatus::UnknownDevice,
1084 device_address: request.device_address,
1085 };
1086 }
1087
1088 let status = match self
1089 .leave_network(request.remove_children, request.rejoin)
1090 .await
1091 {
1092 Ok(()) => NlmeLeaveStatus::Success,
1093 Err(_) => NlmeLeaveStatus::MacError,
1094 };
1095 NlmeLeaveConfirm {
1096 status,
1097 device_address: None,
1098 }
1099 }
1100
1101 async fn leave_network(&self, remove_children: bool, rejoin: bool) -> Result<(), NetworkError> {
1107 let result = self.announce_leave(remove_children, rejoin).await;
1108 self.local_leave(rejoin);
1109 result
1110 }
1111
1112 async fn announce_leave(
1116 &self,
1117 remove_children: bool,
1118 rejoin: bool,
1119 ) -> Result<(), NetworkError> {
1120 let is_router_or_coordinator = self.nib().capability_information().device_type()
1121 || *self.nib().network_address() == NWK_COORDINATOR_ADDRESS;
1122
1123 let mut buf = [0u8; 64];
1124 if is_router_or_coordinator {
1125 let command = Command::Leave(Leave {
1126 command_options: LeaveCommandOptions(0)
1127 .set_remove_children(remove_children)
1128 .set_rejoin(rejoin),
1129 });
1130 let len = self.build_nwk_command_frame(
1131 ShortAddress(NWK_BROADCAST_ALL),
1132 command,
1133 true,
1134 &mut buf,
1135 )?;
1136 self.mac
1137 .transmit_data(
1138 Address::Short(
1139 PanId(*self.nib().panid()),
1140 MacShortAddress(NWK_BROADCAST_ALL),
1141 ),
1142 &buf[..len],
1143 )
1144 .await?;
1145 return Ok(());
1146 }
1147
1148 let command = Command::Leave(Leave {
1151 command_options: LeaveCommandOptions(0).set_rejoin(rejoin),
1152 });
1153 let len =
1154 self.build_nwk_command_frame(self.parent_short_address()?, command, true, &mut buf)?;
1155 let result = self
1156 .mac
1157 .transmit_data(self.parent_address()?, &buf[..len])
1158 .await;
1159 self.nib().update_extended_panid(|value| *value = 0);
1162 result.map_err(Into::into)
1163 }
1164
1165 fn local_leave(&self, rejoin: bool) {
1167 if rejoin {
1168 log::info!("[NWK] left the network, rejoin requested");
1173 return;
1174 }
1175
1176 self.clear_nib_on_leave();
1177 }
1178
1179 fn clear_nib_on_leave(&self) {
1182 let nib = self.nib();
1183 nib.update_neighbor_table(|value| value.clear());
1184 nib.update_route_table(|value| value.clear());
1185 nib.update_manager_addr(|value| *value = 0x0000);
1186 nib.update_update_id(|value| *value = 0x00);
1187 nib.update_network_address(|value| *value = NWK_UNASSIGNED_ADDRESS);
1188 nib.update_group_idtable(|value| value.clear());
1189 nib.update_extended_panid(|value| *value = 0);
1190 nib.update_route_record_table(|value| value.clear());
1191 nib.update_is_concentrator(|value| *value = false);
1192 nib.update_concentrator_radius(|value| *value = 0);
1193 nib.update_security_material_set(|value| value.clear());
1194 nib.update_active_key_seq_number(|value| *value = 0x00);
1195 nib.update_address_map(|value| value.clear());
1196 nib.update_panid(|value| *value = 0xffff);
1197 nib.update_tx_total(|value| *value = 0);
1198 nib.update_parent_information(|value| *value = 0x00);
1199 }
1200
1201 pub async fn network_discovery(
1203 &self,
1204 channels: core::ops::Range<u8>,
1205 duration: u8,
1206 ) -> Result<NlmeNetworkDiscoveryConfirm, NetworkError> {
1207 let scan_result = self
1208 .mac
1209 .scan_network(ScanType::Active, channels, duration)
1210 .await?;
1211
1212 let neighbor_table = scan_result
1215 .pan_descriptor
1216 .iter()
1217 .filter_map(|pd| match pd.coord_address {
1218 Address::Short(_pan_id, short_address) => Some(NwkNeighbor {
1219 extended_address: IeeeAddress(0),
1222 network_address: ShortAddress(short_address.0),
1223 device_type: if short_address.0 == NWK_COORDINATOR_ADDRESS {
1224 DeviceType::Coordinator
1225 } else {
1226 DeviceType::Router
1227 },
1228 rx_on_when_idle: false,
1229 end_device_configuration: 0,
1230 relationship: relationship::NONE,
1231 transmit_failure: 0,
1232 lqi: pd.link_quality,
1233 outgoing_cost: 0,
1234 age: 0,
1235 keepalive_received: false,
1236 extended_pan_id: pd.zigbee_beacon.extended_pan_id,
1238 logical_channel: pd.channel,
1239 depth: pd.zigbee_beacon.stack_profile.device_depth(),
1240 permit_joining: pd.superframe_spec.association_permit,
1241 potential_parent: u8::from(
1245 pd.zigbee_beacon.stack_profile.router_capacity()
1246 || pd.zigbee_beacon.stack_profile.end_device_capacity()
1247 || short_address.0 == NWK_COORDINATOR_ADDRESS,
1248 ),
1249 router_capacity: pd.zigbee_beacon.stack_profile.router_capacity(),
1250 end_device_capacity: pd.zigbee_beacon.stack_profile.end_device_capacity(),
1251 update_id: pd.zigbee_beacon.update_id,
1252 pan_id: pd.coord_pan_id.0,
1253 }),
1254 Address::Extended(_, _) => None,
1255 })
1256 .collect();
1257
1258 self.nib()
1259 .update_neighbor_table(|value| *value = StorageVec(neighbor_table));
1260
1261 let network_descriptors = scan_result
1263 .pan_descriptor
1264 .into_iter()
1265 .map(From::from)
1266 .collect();
1267
1268 Ok(NlmeNetworkDiscoveryConfirm {
1269 network_descriptor: network_descriptors,
1270 })
1271 }
1272
1273 #[allow(clippy::unused_async)]
1275 pub async fn network_formation(
1276 &self,
1277 _request: NlmeNetworkFormationRequest,
1278 ) -> NlmeNetworkFormationConfirm {
1279 todo!()
1280 }
1281
1282 #[allow(clippy::unused_async, clippy::unused_async_trait_impl)]
1285 pub async fn permit_joining(
1286 &self,
1287 _request: NlmePermitJoiningRequest,
1288 ) -> NlmePermitJoiningConfirm {
1289 NlmePermitJoiningConfirm {
1290 status: NlmeJoinStatus::InvalidRequest,
1291 }
1292 }
1293
1294 #[allow(clippy::unused_async)]
1296 pub async fn start_router(&self, _request: NlmeStartRouterRequest) -> NlmeStartRouterConfirm {
1297 todo!()
1298 }
1299
1300 #[allow(clippy::unused_async)]
1302 pub async fn ed_scan(&self, _request: NlmeEdScanRequest) -> NlmeEdScanConfirm {
1303 todo!()
1304 }
1305
1306 #[allow(clippy::too_many_lines)]
1310 pub async fn join(&self, request: NlmeJoinRequest) -> NlmeJoinConfirm {
1311 if request.rejoin_network == RejoinNetwork::NwkRejoin {
1312 return self.nwk_rejoin(request).await;
1313 }
1314 let fail = Self::join_failure;
1315
1316 if request.rejoin_network != RejoinNetwork::Association {
1319 return fail(NlmeJoinStatus::InvalidRequest);
1320 }
1321
1322 if *self.nib().network_address() != 0xffff {
1324 return fail(NlmeJoinStatus::InvalidRequest);
1325 }
1326
1327 self.reset_parent_negotiation();
1330
1331 let join_as_router = request.capability_information.device_type();
1332
1333 let candidates = self.select_parent_candidates(request.extended_pan_id, join_as_router);
1334
1335 if candidates.is_empty() {
1336 log::warn!("[NWK-JOIN] no suitable neighbors");
1337 return fail(NlmeJoinStatus::NotPermitted);
1338 }
1339
1340 log::debug!("[NWK-JOIN] neighbor candidates: {candidates:?}");
1341
1342 let mac_caps = Self::build_mac_capabilities(&request.capability_information);
1345
1346 self.nib()
1349 .update_capability_information(|value| *value = request.capability_information);
1350
1351 let mut last_status = NlmeJoinStatus::NotPermitted;
1353
1354 for &candidate_idx in &candidates {
1355 let table = self.nib().neighbor_table();
1356 let neighbor = &table[candidate_idx];
1357 let channel = neighbor.logical_channel;
1358 let pan_id = PanId(neighbor.pan_id);
1359 let dest = Address::Short(pan_id, MacShortAddress(neighbor.network_address.0));
1360 drop(table);
1361
1362 match self.mac.associate(channel, dest, mac_caps).await {
1363 Ok(response) => {
1364 match response.status {
1365 AssociationStatus::Successful => {
1366 let assigned_addr = response.association_address;
1367 self.nib()
1368 .update_network_address(|value| *value = assigned_addr.0);
1369 self.nib()
1370 .update_ieee_address(|value| *value = response.device_address);
1371 self.nib()
1372 .update_extended_panid(|value| *value = request.extended_pan_id.0);
1373 self.nib().update_panid(|value| *value = pan_id.0);
1374
1375 let parent_update_id =
1378 self.nib().neighbor_table()[candidate_idx].update_id;
1379 let parent_channel =
1380 self.nib().neighbor_table()[candidate_idx].logical_channel;
1381 self.nib()
1382 .update_update_id(|value| *value = parent_update_id);
1383
1384 self.nib().update_neighbor_table(|table| {
1389 table[candidate_idx].relationship = relationship::PARENT;
1390 for neighbor in table.iter_mut() {
1391 neighbor.extended_pan_id = IeeeAddress(0);
1392 neighbor.logical_channel = 0;
1393 neighbor.depth = 0;
1394 neighbor.permit_joining = false;
1395 neighbor.potential_parent = 0;
1396 neighbor.router_capacity = false;
1397 neighbor.end_device_capacity = false;
1398 neighbor.update_id = 0;
1399 neighbor.pan_id = 0xffff;
1400 }
1401 });
1402
1403 return NlmeJoinConfirm {
1404 status: NlmeJoinStatus::Success,
1405 network_address: assigned_addr,
1406 extended_pan_id: request.extended_pan_id,
1407 channel: parent_channel,
1408 enhanced_beacon_type: false,
1409 mac_interface_index: 0u8,
1410 };
1411 }
1412 AssociationStatus::NetworkAtCapacity => {
1413 self.nib().update_neighbor_table(|table| {
1416 table[candidate_idx].potential_parent = 0;
1417 });
1418 last_status = NlmeJoinStatus::PanAtCapacity;
1419 }
1420 AssociationStatus::AccessDenied => {
1421 self.nib().update_neighbor_table(|table| {
1422 table[candidate_idx].potential_parent = 0;
1423 });
1424 last_status = NlmeJoinStatus::PanAccessDenied;
1425 }
1426 _ => {
1428 last_status = NlmeJoinStatus::MacError;
1429 }
1430 }
1431 }
1432 Err(_mac_err) => {
1433 last_status = NlmeJoinStatus::MacError;
1434 }
1435 }
1436 }
1437
1438 fail(last_status)
1440 }
1441
1442 async fn nwk_rejoin(&self, request: NlmeJoinRequest) -> NlmeJoinConfirm {
1458 let fail = Self::join_failure;
1459
1460 let remembered_epid = *self.nib().extended_panid();
1466 if *self.nib().network_address() == 0xffff
1467 || (remembered_epid != 0 && remembered_epid != request.extended_pan_id.0)
1468 {
1469 return fail(NlmeJoinStatus::InvalidRequest);
1470 }
1471
1472 self.reset_parent_negotiation();
1475 self.nib()
1476 .update_capability_information(|value| *value = request.capability_information);
1477
1478 let mut last_status = NlmeJoinStatus::InvalidRequest;
1481 if let Ok(parent) = self.parent_short_address() {
1482 match self.rejoin_via_parent(parent, &request).await {
1483 Ok(response) => return self.rejoin_confirm(response, &request, 0).await,
1485 Err(status) => {
1486 log::debug!("[NWK-REJOIN] remembered parent failed ({status:?})");
1487 last_status = status;
1488 }
1489 }
1490 }
1491
1492 if request.scan_channels.is_empty() {
1493 return fail(last_status);
1494 }
1495
1496 log::debug!(
1497 "[NWK-REJOIN] scanning {:?} for another parent",
1498 request.scan_channels
1499 );
1500 if let Err(e) = self
1501 .network_discovery(request.scan_channels.clone(), request.scan_duration)
1502 .await
1503 {
1504 log::warn!("[NWK-REJOIN] scan failed: {e:?}");
1505 return fail(NlmeJoinStatus::MacError);
1506 }
1507
1508 let join_as_router = request.capability_information.device_type();
1509 let candidates = self.select_rejoin_candidates(request.extended_pan_id, join_as_router);
1510 if candidates.is_empty() {
1511 log::warn!("[NWK-REJOIN] no parent candidate found");
1512 return fail(NlmeJoinStatus::NotPermitted);
1513 }
1514
1515 for candidate_idx in candidates {
1516 let Some((parent, channel)) = self.adopt_parent(candidate_idx).await else {
1517 continue;
1518 };
1519 match self.rejoin_via_parent(parent, &request).await {
1520 Ok(response) => return self.rejoin_confirm(response, &request, channel).await,
1521 Err(status) => {
1522 log::debug!("[NWK-REJOIN] candidate {parent:?} failed ({status:?})");
1523 last_status = status;
1524 }
1525 }
1526 }
1527
1528 fail(last_status)
1529 }
1530
1531 async fn rejoin_via_parent(
1533 &self,
1534 parent: ShortAddress,
1535 request: &NlmeJoinRequest,
1536 ) -> Result<RejoinResponse, NlmeJoinStatus> {
1537 let command = Command::RejoinRequest(RejoinRequest {
1538 capability_information: rejoin_request::CapabilityInformation(
1541 request.capability_information.0,
1542 ),
1543 });
1544
1545 let mut buf = [0u8; 128];
1546 let len = self
1547 .build_nwk_command_frame(parent, command, request.security_enabled, &mut buf)
1548 .map_err(|_| NlmeJoinStatus::MacError)?;
1549 let parent_addr = self
1550 .parent_address()
1551 .map_err(|_| NlmeJoinStatus::InvalidRequest)?;
1552
1553 self.rejoin_response.reset();
1556 self.mac
1557 .transmit_data(parent_addr, &buf[..len])
1558 .await
1559 .map_err(|_| NlmeJoinStatus::MacError)?;
1560
1561 let response = self
1562 .await_rejoin_response(REJOIN_RESPONSE_POLL_RETRIES)
1563 .await
1564 .ok_or(NlmeJoinStatus::MacError)?;
1565
1566 match response.status {
1567 status if status == u8::from(AssociationStatus::Successful) => Ok(response),
1568 status if status == u8::from(AssociationStatus::NetworkAtCapacity) => {
1569 Err(NlmeJoinStatus::PanAtCapacity)
1570 }
1571 status if status == u8::from(AssociationStatus::AccessDenied) => {
1572 Err(NlmeJoinStatus::PanAccessDenied)
1573 }
1574 _ => Err(NlmeJoinStatus::MacError),
1575 }
1576 }
1577
1578 async fn rejoin_confirm(
1580 &self,
1581 response: RejoinResponse,
1582 request: &NlmeJoinRequest,
1583 channel: u8,
1584 ) -> NlmeJoinConfirm {
1585 self.mac
1588 .configure(MacConfig::short_address(response.network_address))
1589 .await;
1590 self.nib()
1592 .update_extended_panid(|value| *value = request.extended_pan_id.0);
1593
1594 NlmeJoinConfirm {
1595 status: NlmeJoinStatus::Success,
1596 network_address: response.network_address,
1597 extended_pan_id: request.extended_pan_id,
1598 channel,
1599 enhanced_beacon_type: false,
1600 mac_interface_index: 0u8,
1601 }
1602 }
1603
1604 async fn adopt_parent(&self, candidate_idx: usize) -> Option<(ShortAddress, u8)> {
1610 let nib = self.nib();
1611 let (parent, channel, pan_id, update_id) = {
1612 let table = nib.neighbor_table();
1613 let neighbor = table.get(candidate_idx)?;
1614 (
1615 neighbor.network_address,
1616 neighbor.logical_channel,
1617 neighbor.pan_id,
1618 neighbor.update_id,
1619 )
1620 };
1621
1622 nib.update_neighbor_table(|table| {
1623 for (index, neighbor) in table.iter_mut().enumerate() {
1624 neighbor.relationship = if index == candidate_idx {
1625 relationship::PARENT
1626 } else {
1627 relationship::NONE
1628 };
1629 }
1630 });
1631 nib.update_panid(|value| *value = pan_id);
1632 nib.update_update_id(|value| *value = update_id);
1633 self.mac.configure(MacConfig::channel(channel)).await;
1634
1635 Some((parent, channel))
1636 }
1637
1638 async fn await_rejoin_response(&self, retries: u8) -> Option<RejoinResponse> {
1643 with_timeout(
1644 self.rejoin_response.wait(),
1645 self.poll_until_rejoin_response(retries),
1646 )
1647 .await
1648 .or_else(|| self.rejoin_response.try_take())
1650 }
1651
1652 async fn poll_until_rejoin_response(&self, retries: u8) {
1658 let mut buf = [0u8; 128];
1659 for _ in 0..retries {
1660 if self.rejoin_response.is_signaled() {
1661 return;
1662 }
1663 match self.poll_parent(&mut buf).await {
1664 Ok(Some(len)) => match self.process_received_nwk_frame(&mut buf[..len]).await {
1665 Ok(Some(_)) => log::debug!("[NWK-REJOIN] polled data frame dropped"),
1666 Ok(None) => (),
1667 Err(e) => log::debug!("[NWK-REJOIN] polled frame not processed: {e:?}"),
1668 },
1669 Ok(None) => (),
1670 Err(e) => log::debug!("[NWK-REJOIN] poll failed: {e:?}"),
1671 }
1672 }
1673 }
1674
1675 pub async fn poll_nwk_data<'a>(
1678 &self,
1679 buf: &'a mut [u8],
1680 retries: u8,
1681 ) -> Result<NwkDataFrame<'a>, NetworkError> {
1682 for _ in 0..retries {
1683 let buf = unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr(), buf.len()) };
1687 match self.poll_nwk_frame(buf).await {
1688 Ok(Some(data_frame)) => {
1689 return Ok(data_frame);
1690 }
1691 Ok(None) | Err(NetworkError::SecurityError(_) | NetworkError::ParseError) => (),
1696 Err(e) => return Err(e),
1697 }
1698 }
1699
1700 Err(NetworkError::MacError(MacError::NoData))
1701 }
1702
1703 pub async fn broadcast_data(
1711 &self,
1712 destination: ShortAddress,
1713 secure: bool,
1714 payload: &[u8],
1715 ) -> Result<(), NetworkError> {
1716 let mut buf = [0u8; 256];
1717 let total_len = self.build_nwk_data_frame(destination, secure, payload, &mut buf)?;
1718 self.transmit_via_parent(&buf[..total_len]).await
1721 }
1722
1723 pub async fn send_data(
1731 &self,
1732 destination: ShortAddress,
1733 secure: bool,
1734 payload: &[u8],
1735 ) -> Result<(), NetworkError> {
1736 let mut buf = [0u8; 256];
1737 let total_len = self.build_nwk_data_frame(destination, secure, payload, &mut buf)?;
1738 self.transmit_via_parent(&buf[..total_len]).await
1740 }
1741}
1742
1743#[cfg(test)]
1744mod tests {
1745 use core::future::Future;
1746
1747 use zigbee_mac::AssociationStatus;
1748 use zigbee_mac::mlme::AssociationResponse;
1749 use zigbee_mac::mlme::MacConfig;
1750 use zigbee_mac::mlme::MacError;
1751 use zigbee_mac::mlme::ScanResult;
1752 use zigbee_mac::mlme::ScanType;
1753
1754 use super::*;
1755 use crate::TEST_MUTEX;
1758
1759 #[allow(clippy::panic)]
1762 fn block_on<F: Future>(f: F) -> F::Output {
1763 use core::pin::pin;
1764 use core::task::Context;
1765 use core::task::Poll;
1766 use core::task::RawWaker;
1767 use core::task::RawWakerVTable;
1768 use core::task::Waker;
1769
1770 fn noop(_: *const ()) {}
1771 fn clone(p: *const ()) -> RawWaker {
1772 RawWaker::new(p, &VTABLE)
1773 }
1774 static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, noop, noop, noop);
1775
1776 let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) };
1777 let mut cx = Context::from_waker(&waker);
1778 let mut f = pin!(f);
1779
1780 match f.as_mut().poll(&mut cx) {
1781 Poll::Ready(val) => val,
1782 Poll::Pending => panic!("block_on: future returned Pending"),
1783 }
1784 }
1785
1786 mockall::mock! {
1787 Mlme {}
1788 impl Mlme for Mlme {
1789 fn ieee_address(&self) -> IeeeAddress;
1790 async fn configure(&self, config: MacConfig);
1791 async fn scan_network(
1792 &self,
1793 ty: ScanType,
1794 channels: core::ops::Range<u8>,
1795 duration: u8,
1796 ) -> Result<ScanResult, MacError>;
1797 async fn associate(
1798 &self,
1799 channel: u8,
1800 dest: Address,
1801 capabilities: zigbee_mac::CapabilityInformation,
1802 ) -> Result<AssociationResponse, MacError>;
1803 async fn poll_data(
1804 &self,
1805 coord_address: Address,
1806 buf: &mut [u8],
1807 ) -> Result<(usize, u8), MacError>;
1808 async fn receive(
1809 &self,
1810 buf: &mut [u8],
1811 ) -> Result<(usize, u8), MacError>;
1812 async fn transmit_data(
1813 &self,
1814 dest: Address,
1815 payload: &[u8],
1816 ) -> Result<(), MacError>;
1817 }
1818 }
1819
1820 fn make_neighbor(pan_id: u16, short_addr: u16, epid: u64, lqi: u8, depth: u8) -> NwkNeighbor {
1822 NwkNeighbor {
1823 extended_address: IeeeAddress(0),
1824 network_address: ShortAddress(short_addr),
1825 device_type: if short_addr == 0 {
1826 DeviceType::Coordinator
1827 } else {
1828 DeviceType::Router
1829 },
1830 rx_on_when_idle: false,
1831 end_device_configuration: 0,
1832 relationship: 0x03,
1833 transmit_failure: 0,
1834 lqi,
1835 outgoing_cost: 0,
1836 age: 0,
1837 keepalive_received: false,
1838 extended_pan_id: IeeeAddress(epid),
1839 logical_channel: 11,
1840 depth,
1841 permit_joining: true,
1842 potential_parent: 1,
1843 router_capacity: true,
1844 end_device_capacity: true,
1845 update_id: 0,
1846 pan_id,
1847 }
1848 }
1849
1850 fn make_nlme(mut mac: MockMlme) -> (std::sync::MutexGuard<'static, ()>, Nlme<MockMlme>) {
1851 let guard = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
1852 use crate::nwk::nib;
1853 nib::try_init();
1854 nib::reset();
1855 crate::aps::aib::try_init();
1857 nib::get_ref().update_neighbor_table(|value| *value = StorageVec::new());
1860 mac.expect_ieee_address()
1861 .return_const(IeeeAddress(0xa4c1_0000_0000_0001));
1862 (guard, Nlme::new(mac))
1863 }
1864
1865 fn default_join_request(epid: u64) -> NlmeJoinRequest {
1866 NlmeJoinRequest {
1867 extended_pan_id: IeeeAddress(epid),
1868 rejoin_network: RejoinNetwork::Association,
1869 capability_information: CapabilityInformation(0x80),
1870 security_enabled: false,
1871 scan_channels: 0..0,
1872 scan_duration: 0,
1873 }
1874 }
1875
1876 #[test]
1877 fn select_parent_no_neighbors() {
1878 let (_guard, nlme) = make_nlme(MockMlme::new());
1879 let candidates = nlme.select_parent_candidates(IeeeAddress(0x1234), false);
1880 assert!(candidates.is_empty());
1881 }
1882
1883 #[test]
1884 fn select_parent_filters_by_extended_pan_id() {
1885 let (_guard, nlme) = make_nlme(MockMlme::new());
1886
1887 let mut table = StorageVec::new();
1888 table
1890 .push(make_neighbor(0xAAAA, 0x0000, 0x1234, 200, 0))
1891 .unwrap();
1892 table
1894 .push(make_neighbor(0xBBBB, 0x0001, 0x9999, 200, 0))
1895 .unwrap();
1896 nlme.nib().update_neighbor_table(|value| *value = table);
1897
1898 let candidates = nlme.select_parent_candidates(IeeeAddress(0x1234), false);
1899 assert_eq!(candidates.len(), 1);
1900 assert_eq!(candidates[0], 0);
1901 }
1902
1903 #[test]
1904 fn select_parent_filters_by_link_cost() {
1905 let (_guard, nlme) = make_nlme(MockMlme::new());
1906
1907 let mut table = StorageVec::new();
1908 table
1910 .push(make_neighbor(0xAAAA, 0x0000, 0x1234, 200, 0))
1911 .unwrap();
1912 table
1914 .push(make_neighbor(0xAAAA, 0x0001, 0x1234, 10, 0))
1915 .unwrap();
1916 nlme.nib().update_neighbor_table(|value| *value = table);
1917
1918 let candidates = nlme.select_parent_candidates(IeeeAddress(0x1234), false);
1919 assert_eq!(candidates.len(), 1);
1920 assert_eq!(candidates[0], 0);
1921 }
1922
1923 #[test]
1924 fn select_parent_filters_by_end_device_capacity() {
1925 let (_guard, nlme) = make_nlme(MockMlme::new());
1926
1927 let mut table = StorageVec::new();
1928 let mut n = make_neighbor(0xAAAA, 0x0000, 0x1234, 200, 0);
1929 n.end_device_capacity = false;
1930 table.push(n).unwrap();
1931 nlme.nib().update_neighbor_table(|value| *value = table);
1932
1933 let candidates = nlme.select_parent_candidates(IeeeAddress(0x1234), false);
1934 assert!(candidates.is_empty());
1935 }
1936
1937 #[test]
1938 fn select_parent_filters_by_router_capacity() {
1939 let (_guard, nlme) = make_nlme(MockMlme::new());
1940
1941 let mut table = StorageVec::new();
1942 let mut n = make_neighbor(0xAAAA, 0x0000, 0x1234, 200, 0);
1943 n.router_capacity = false;
1944 table.push(n).unwrap();
1945 nlme.nib().update_neighbor_table(|value| *value = table);
1946
1947 let candidates = nlme.select_parent_candidates(IeeeAddress(0x1234), true);
1948 assert!(candidates.is_empty());
1949 }
1950
1951 #[test]
1952 fn select_parent_end_device_accepts_router_without_router_capacity() {
1953 let (_guard, nlme) = make_nlme(MockMlme::new());
1957
1958 let mut table = StorageVec::new();
1959 let mut n = make_neighbor(0xAAAA, 0x1234, 0x1234, 200, 0);
1960 n.router_capacity = false;
1961 n.end_device_capacity = true;
1962 table.push(n).unwrap();
1963 nlme.nib().update_neighbor_table(|value| *value = table);
1964
1965 let candidates = nlme.select_parent_candidates(IeeeAddress(0x1234), false);
1966 assert_eq!(candidates.len(), 1);
1967 }
1968
1969 #[test]
1970 fn select_parent_sorts_by_depth_for_stack_profile_1() {
1971 let (_guard, nlme) = make_nlme(MockMlme::new());
1972 nlme.nib().update_stack_profile(|value| *value = 1);
1973
1974 let mut table = StorageVec::new();
1975 table
1976 .push(make_neighbor(0xAAAA, 0x0000, 0x1234, 200, 3))
1977 .unwrap();
1978 table
1979 .push(make_neighbor(0xAAAA, 0x0001, 0x1234, 200, 1))
1980 .unwrap();
1981 table
1982 .push(make_neighbor(0xAAAA, 0x0002, 0x1234, 200, 2))
1983 .unwrap();
1984 nlme.nib().update_neighbor_table(|value| *value = table);
1985
1986 let candidates = nlme.select_parent_candidates(IeeeAddress(0x1234), false);
1987 assert_eq!(candidates.len(), 3);
1988 assert_eq!(candidates[0], 1);
1990 assert_eq!(candidates[1], 2);
1991 assert_eq!(candidates[2], 0);
1992 }
1993
1994 #[test]
1995 fn select_parent_filters_not_permitting_join() {
1996 let (_guard, nlme) = make_nlme(MockMlme::new());
1997
1998 let mut table = StorageVec::new();
1999 let mut n = make_neighbor(0xAAAA, 0x0000, 0x1234, 200, 0);
2000 n.permit_joining = false;
2001 table.push(n).unwrap();
2002 nlme.nib().update_neighbor_table(|value| *value = table);
2003
2004 let candidates = nlme.select_parent_candidates(IeeeAddress(0x1234), false);
2005 assert!(candidates.is_empty());
2006 }
2007
2008 #[test]
2009 fn select_parent_filters_non_potential_parent() {
2010 let (_guard, nlme) = make_nlme(MockMlme::new());
2011
2012 let mut table = StorageVec::new();
2013 let mut n = make_neighbor(0xAAAA, 0x0000, 0x1234, 200, 0);
2014 n.potential_parent = 0;
2015 table.push(n).unwrap();
2016 nlme.nib().update_neighbor_table(|value| *value = table);
2017
2018 let candidates = nlme.select_parent_candidates(IeeeAddress(0x1234), false);
2019 assert!(candidates.is_empty());
2020 }
2021
2022 #[test]
2023 fn select_parent_prefers_most_recent_update_id() {
2024 let (_guard, nlme) = make_nlme(MockMlme::new());
2025
2026 let mut table = StorageVec::new();
2027 let mut n1 = make_neighbor(0xAAAA, 0x0000, 0x1234, 200, 0);
2028 n1.update_id = 5;
2029 table.push(n1).unwrap();
2030 let mut n2 = make_neighbor(0xAAAA, 0x0001, 0x1234, 200, 0);
2031 n2.update_id = 3;
2032 table.push(n2).unwrap();
2033 nlme.nib().update_neighbor_table(|value| *value = table);
2034
2035 let candidates = nlme.select_parent_candidates(IeeeAddress(0x1234), false);
2036 assert_eq!(candidates.len(), 1);
2037 assert_eq!(candidates[0], 0);
2038 }
2039
2040 #[test]
2041 fn join_successful_association() {
2042 let mut mac = MockMlme::new();
2043 mac.expect_associate().returning(|_, _, _| {
2044 Ok(AssociationResponse {
2045 device_address: IeeeAddress(0),
2046 association_address: ShortAddress(0x1234),
2047 status: AssociationStatus::Successful,
2048 })
2049 });
2050
2051 let (_guard, nlme) = make_nlme(mac);
2052
2053 let mut table = StorageVec::new();
2054 table
2055 .push(make_neighbor(0xAAAA, 0x0000, 0xDEAD, 200, 0))
2056 .unwrap();
2057 nlme.nib().update_neighbor_table(|value| *value = table);
2058
2059 let confirm = block_on(nlme.join(default_join_request(0xDEAD)));
2060
2061 assert_eq!(confirm.status, NlmeJoinStatus::Success);
2062 assert_eq!(confirm.network_address.0, 0x1234);
2063 assert_eq!(confirm.extended_pan_id.0, 0xDEAD);
2064 assert_eq!(confirm.channel, 11);
2065
2066 assert_eq!(*nlme.nib().network_address(), 0x1234);
2067 assert_eq!(*nlme.nib().extended_panid(), 0xDEAD);
2068 assert_eq!(*nlme.nib().panid(), 0xAAAA);
2069 assert_eq!(*nlme.nib().update_id(), 0);
2070
2071 let table = nlme.nib().neighbor_table();
2072 assert_eq!(table[0].relationship, 0x00);
2073 }
2074
2075 #[test]
2076 fn join_sets_nwk_update_id_from_parent() {
2077 let mut mac = MockMlme::new();
2078 mac.expect_associate().returning(|_, _, _| {
2079 Ok(AssociationResponse {
2080 device_address: IeeeAddress(0),
2081 association_address: ShortAddress(0x1234),
2082 status: AssociationStatus::Successful,
2083 })
2084 });
2085
2086 let (_guard, nlme) = make_nlme(mac);
2087
2088 let mut n = make_neighbor(0xAAAA, 0x0000, 0xDEAD, 200, 0);
2089 n.update_id = 7;
2090 let mut table = StorageVec::new();
2091 table.push(n).unwrap();
2092 nlme.nib().update_neighbor_table(|value| *value = table);
2093
2094 let confirm = block_on(nlme.join(default_join_request(0xDEAD)));
2095 assert_eq!(confirm.status, NlmeJoinStatus::Success);
2096 assert_eq!(*nlme.nib().update_id(), 7);
2097 }
2098
2099 #[test]
2100 fn join_fails_when_no_candidates() {
2101 let mac = MockMlme::new();
2102 let (_guard, nlme) = make_nlme(mac);
2103 nlme.nib()
2104 .update_neighbor_table(|value| *value = StorageVec::new());
2105 let confirm = block_on(nlme.join(default_join_request(0xDEAD)));
2106 assert_eq!(confirm.status, NlmeJoinStatus::NotPermitted);
2107 }
2108
2109 #[test]
2110 fn join_fails_when_already_joined() {
2111 let mac = MockMlme::new();
2112 let (_guard, nlme) = make_nlme(mac);
2113 nlme.nib().update_network_address(|value| *value = 0x0001);
2114
2115 let confirm = block_on(nlme.join(default_join_request(0xDEAD)));
2116 assert_eq!(confirm.status, NlmeJoinStatus::InvalidRequest);
2117 }
2118
2119 #[test]
2120 fn join_skips_capacity_rejected_parent_tries_next() {
2121 let mut mac = MockMlme::new();
2122 let mut seq = mockall::Sequence::new();
2123 mac.expect_associate()
2124 .times(1)
2125 .in_sequence(&mut seq)
2126 .returning(|_, _, _| {
2127 Ok(AssociationResponse {
2128 device_address: IeeeAddress(0),
2129 association_address: ShortAddress(0),
2130 status: AssociationStatus::NetworkAtCapacity,
2131 })
2132 });
2133 mac.expect_associate()
2134 .times(1)
2135 .in_sequence(&mut seq)
2136 .returning(|_, _, _| {
2137 Ok(AssociationResponse {
2138 device_address: IeeeAddress(0),
2139 association_address: ShortAddress(0x5678),
2140 status: AssociationStatus::Successful,
2141 })
2142 });
2143
2144 let (_guard, nlme) = make_nlme(mac);
2145
2146 let mut table = StorageVec::new();
2147 table
2148 .push(make_neighbor(0xAAAA, 0x0000, 0xDEAD, 200, 0))
2149 .unwrap();
2150 table
2151 .push(make_neighbor(0xAAAA, 0x0001, 0xDEAD, 200, 0))
2152 .unwrap();
2153 nlme.nib().update_neighbor_table(|value| *value = table);
2154
2155 let confirm = block_on(nlme.join(default_join_request(0xDEAD)));
2156 assert_eq!(confirm.status, NlmeJoinStatus::Success);
2157 assert_eq!(confirm.network_address.0, 0x5678);
2158
2159 let table = nlme.nib().neighbor_table();
2160 assert_eq!(table[0].potential_parent, 0);
2161 assert_eq!(table[1].relationship, 0x00);
2162 }
2163
2164 #[test]
2165 fn join_all_candidates_rejected() {
2166 let mut mac = MockMlme::new();
2167 mac.expect_associate().returning(|_, _, _| {
2168 Ok(AssociationResponse {
2169 device_address: IeeeAddress(0),
2170 association_address: ShortAddress(0),
2171 status: AssociationStatus::AccessDenied,
2172 })
2173 });
2174
2175 let (_guard, nlme) = make_nlme(mac);
2176
2177 let mut table = StorageVec::new();
2178 table
2179 .push(make_neighbor(0xAAAA, 0x0000, 0xDEAD, 200, 0))
2180 .unwrap();
2181 nlme.nib().update_neighbor_table(|value| *value = table);
2182
2183 let confirm = block_on(nlme.join(default_join_request(0xDEAD)));
2184 assert_eq!(confirm.status, NlmeJoinStatus::PanAccessDenied);
2185 assert_eq!(confirm.network_address.0, 0xffff);
2186 }
2187
2188 #[test]
2189 fn join_mac_error_reported() {
2190 let mut mac = MockMlme::new();
2191 mac.expect_associate()
2192 .returning(|_, _, _| Err(MacError::NoAck));
2193
2194 let (_guard, nlme) = make_nlme(mac);
2195
2196 let mut table = StorageVec::new();
2197 table
2198 .push(make_neighbor(0xAAAA, 0x0000, 0xDEAD, 200, 0))
2199 .unwrap();
2200 nlme.nib().update_neighbor_table(|value| *value = table);
2201
2202 let confirm = block_on(nlme.join(default_join_request(0xDEAD)));
2203 assert_eq!(confirm.status, NlmeJoinStatus::MacError);
2204 }
2205
2206 #[test]
2207 fn join_invalid_rejoin_network() {
2208 let mac = MockMlme::new();
2209 let (_guard, nlme) = make_nlme(mac);
2210
2211 let mut req = default_join_request(0xDEAD);
2212 req.rejoin_network = RejoinNetwork::Orphan;
2213
2214 let confirm = block_on(nlme.join(req));
2215 assert_eq!(confirm.status, NlmeJoinStatus::InvalidRequest);
2216 }
2217
2218 fn rejoin_request(epid: u64) -> NlmeJoinRequest {
2219 NlmeJoinRequest {
2220 extended_pan_id: IeeeAddress(epid),
2221 rejoin_network: RejoinNetwork::NwkRejoin,
2222 capability_information: CapabilityInformation(0x80),
2223 security_enabled: true,
2224 scan_channels: 0..0,
2226 scan_duration: 0,
2227 }
2228 }
2229
2230 fn encrypted_rejoin_response(network_address: u16, status: u8) -> heapless::Vec<u8, 128> {
2233 let nib = nib::get_ref();
2234 let header = NwkHeader {
2235 frame_control: NwkFrameControl(0)
2236 .set_frame_type(NwkFrameType::NwkCommand)
2237 .set_protocol_version(2)
2238 .set_discover_route(DiscoverRoute::Suppress)
2239 .set_security_flag(true)
2240 .set_source_ieee_flag(true),
2241 destination: ShortAddress(*nib.network_address()),
2242 source: ShortAddress(0x0000),
2243 radius: 1,
2244 sequence_number: 0,
2245 destination_ieee: None,
2246 source_ieee: Some(*nib.ieee_address()),
2247 multicast_control: None,
2248 source_route_subframe: None,
2249 };
2250 let command = Command::RejoinResponse(RejoinResponse {
2251 network_address: ShortAddress(network_address),
2252 status,
2253 });
2254 let nwk_frame = NwkFrame::NwkCommand(NwkCommandFrame { header, command });
2255 let mut buf = heapless::Vec::<u8, 128>::new();
2256 buf.resize(128, 0).unwrap();
2257 let len = SecurityContext::get()
2258 .encrypt_nwk_frame_in_place(nwk_frame, &mut buf)
2259 .unwrap();
2260 buf.truncate(len);
2261 buf
2262 }
2263
2264 #[test]
2265 fn nwk_rejoin_not_joined_is_invalid() {
2266 let (_guard, nlme) = make_nlme(MockMlme::new());
2267
2268 let confirm = block_on(nlme.join(rejoin_request(0xDEAD)));
2269 assert_eq!(confirm.status, NlmeJoinStatus::InvalidRequest);
2270 }
2271
2272 #[test]
2273 fn nwk_rejoin_without_security_is_sent_unsecured() {
2274 let mut mac = MockMlme::new();
2276 mac.expect_transmit_data()
2277 .times(1)
2278 .withf(|_dest, payload| {
2279 let (header, _) = NwkHeader::try_read(payload, ()).unwrap();
2280 !header.frame_control.security_flag()
2281 })
2282 .returning(|_, _| Ok(()));
2283 mac.expect_poll_data()
2284 .returning(|_, _| Err(MacError::NoData));
2285
2286 let (_guard, nlme) = make_nlme(mac);
2287 seed_joined_nib(&nlme);
2288
2289 let mut req = rejoin_request(0xDEAD);
2290 req.security_enabled = false;
2291
2292 let confirm = block_on(nlme.join(req));
2293 assert_eq!(confirm.status, NlmeJoinStatus::MacError);
2294 }
2295
2296 #[test]
2297 fn nwk_rejoin_epid_mismatch_is_invalid() {
2298 let (_guard, nlme) = make_nlme(MockMlme::new());
2299 seed_joined_nib(&nlme);
2300
2301 let confirm = block_on(nlme.join(rejoin_request(0xBEEF)));
2302 assert_eq!(confirm.status, NlmeJoinStatus::InvalidRequest);
2303 }
2304
2305 #[test]
2306 fn nwk_rejoin_success_sends_request_and_updates_address() {
2307 let mut mac = MockMlme::new();
2308 mac.expect_transmit_data()
2309 .times(1)
2310 .withf(|dest, payload| {
2311 let mut buf = [0u8; 256];
2312 buf[..payload.len()].copy_from_slice(payload);
2313 let Ok(NwkFrame::NwkCommand(command_frame)) =
2314 SecurityContext::get().decrypt_nwk_frame_in_place(&mut buf[..payload.len()])
2315 else {
2316 return false;
2317 };
2318 let Command::RejoinRequest(request) = command_frame.command else {
2319 return false;
2320 };
2321 *dest == Address::Short(PanId(0xAAAA), MacShortAddress(0x0000))
2322 && request.capability_information.device_type() == 0
2325 && request.capability_information.allocate_address() == 1
2326 })
2327 .returning(|_, _| Ok(()));
2328 mac.expect_poll_data().times(1).returning(|_, buf| {
2329 let response = encrypted_rejoin_response(0x5678, 0x00);
2330 buf[..response.len()].copy_from_slice(&response);
2331 Ok((response.len(), 200u8))
2332 });
2333 mac.expect_configure()
2335 .times(1)
2336 .withf(|config| config.short_address == Some(ShortAddress(0x5678)))
2337 .returning(|_| ());
2338
2339 let (_guard, nlme) = make_nlme(mac);
2340 seed_joined_nib(&nlme);
2341
2342 let confirm = block_on(nlme.join(rejoin_request(0xDEAD)));
2343 assert_eq!(confirm.status, NlmeJoinStatus::Success);
2344 assert_eq!(confirm.network_address, ShortAddress(0x5678));
2345 assert_eq!(*nlme.nib().network_address(), 0x5678);
2346 }
2347
2348 #[test]
2349 fn rejoin_response_from_receive_path_applies_address_and_signals() {
2350 let (_guard, nlme) = make_nlme(MockMlme::new());
2353 seed_joined_nib(&nlme);
2354
2355 block_on(nlme.handle_nwk_command(
2356 &dummy_header(0x0000),
2357 Command::RejoinResponse(RejoinResponse {
2358 network_address: ShortAddress(0x5678),
2359 status: 0x00,
2360 }),
2361 ));
2362
2363 assert_eq!(*nlme.nib().network_address(), 0x5678);
2364 block_on(nlme.rejoin_response.wait());
2365 }
2366
2367 #[test]
2368 fn nwk_rejoin_network_at_capacity() {
2369 let mut mac = MockMlme::new();
2370 mac.expect_transmit_data().times(1).returning(|_, _| Ok(()));
2371 mac.expect_poll_data().times(1).returning(|_, buf| {
2372 let response = encrypted_rejoin_response(0xffff, 0x01);
2373 buf[..response.len()].copy_from_slice(&response);
2374 Ok((response.len(), 200u8))
2375 });
2376
2377 let (_guard, nlme) = make_nlme(mac);
2378 seed_joined_nib(&nlme);
2379
2380 let confirm = block_on(nlme.join(rejoin_request(0xDEAD)));
2381 assert_eq!(confirm.status, NlmeJoinStatus::PanAtCapacity);
2382 }
2383
2384 #[test]
2385 fn nwk_rejoin_no_response_is_mac_error() {
2386 let mut mac = MockMlme::new();
2387 mac.expect_transmit_data().times(1).returning(|_, _| Ok(()));
2388 mac.expect_poll_data()
2389 .returning(|_, _| Err(MacError::NoData));
2390
2391 let (_guard, nlme) = make_nlme(mac);
2392 seed_joined_nib(&nlme);
2393
2394 let confirm = block_on(nlme.join(rejoin_request(0xDEAD)));
2395 assert_eq!(confirm.status, NlmeJoinStatus::MacError);
2396 }
2397
2398 #[test]
2399 fn nwk_rejoin_transmit_failure_is_mac_error() {
2400 let mut mac = MockMlme::new();
2401 mac.expect_transmit_data()
2402 .times(1)
2403 .returning(|_, _| Err(MacError::NoAck));
2404
2405 let (_guard, nlme) = make_nlme(mac);
2406 seed_joined_nib(&nlme);
2407
2408 let confirm = block_on(nlme.join(rejoin_request(0xDEAD)));
2409 assert_eq!(confirm.status, NlmeJoinStatus::MacError);
2410 }
2411
2412 fn dummy_header(source: u16) -> NwkHeader<'static> {
2415 NwkHeader {
2416 frame_control: NwkFrameControl(0).set_frame_type(NwkFrameType::NwkCommand),
2417 destination: ShortAddress(0x0000),
2418 source: ShortAddress(source),
2419 radius: 1,
2420 sequence_number: 0,
2421 destination_ieee: None,
2422 source_ieee: None,
2423 multicast_control: None,
2424 source_route_subframe: None,
2425 }
2426 }
2427
2428 fn seed_joined_nib(nlme: &Nlme<MockMlme>) {
2431 use zigbee_types::ByteArray;
2432
2433 use crate::nwk::nib::NetworkSecurityMaterialDescriptor;
2434
2435 let nib = nlme.nib();
2436 nib.update_panid(|value| *value = 0xAAAA);
2437 nib.update_network_address(|value| *value = 0x1234);
2438 nib.update_extended_panid(|value| *value = 0xDEAD);
2439
2440 let mut table = StorageVec::new();
2441 let mut parent = make_neighbor(0xAAAA, 0x0000, 0xDEAD, 200, 0);
2442 parent.relationship = relationship::PARENT;
2443 table.push(parent).unwrap();
2444 nib.update_neighbor_table(|value| *value = table);
2445
2446 let mut set = StorageVec::new();
2447 set.push(NetworkSecurityMaterialDescriptor {
2448 key_seq_number: 0,
2449 outgoing_frame_counter: 1,
2450 incoming_frame_counter_set: StorageVec::new(),
2451 key: ByteArray([0x42; 16]),
2452 network_key_type: 0,
2453 })
2454 .unwrap();
2455 nib.update_security_material_set(|value| *value = set);
2456 }
2457
2458 #[test]
2459 fn send_end_device_timeout_request_encodes_command() {
2460 let mut mac = MockMlme::new();
2461 mac.expect_transmit_data()
2462 .times(1)
2463 .withf(|dest, payload| {
2464 assert_eq!(
2465 *dest,
2466 Address::Short(PanId(0xAAAA), MacShortAddress(0x0000))
2467 );
2468 let (header, _) = NwkHeader::try_read(payload, ()).unwrap();
2470 assert_eq!(header.frame_control.frame_type(), NwkFrameType::NwkCommand);
2471 assert!(header.frame_control.security_flag());
2472 assert!(header.frame_control.source_ieee_flag());
2473 assert_eq!(header.radius, 1);
2474 assert_eq!(header.destination, ShortAddress(0x0000));
2475 assert_eq!(header.source, ShortAddress(0x1234));
2476
2477 let mut buf = [0u8; 256];
2479 buf[..payload.len()].copy_from_slice(payload);
2480 let frame = SecurityContext::get()
2481 .decrypt_nwk_frame_in_place(&mut buf[..payload.len()])
2482 .unwrap();
2483 let NwkFrame::NwkCommand(command_frame) = frame else {
2484 panic!("expected NWK command frame");
2485 };
2486 let Command::EndDeviceTimeoutRequest(request) = command_frame.command else {
2487 panic!("expected end device timeout request");
2488 };
2489 assert_eq!(request.requested_timeout, 0x08);
2490 assert_eq!(request.end_device_configuration, 0x00);
2491 true
2492 })
2493 .returning(|_, _| Ok(()));
2494
2495 let (_guard, nlme) = make_nlme(mac);
2496 seed_joined_nib(&nlme);
2497
2498 block_on(nlme.send_end_device_timeout_request()).unwrap();
2499 assert_eq!(
2500 nlme.pending_timeout_request.load(Ordering::Relaxed),
2501 *nlme.nib().end_device_timeout_default()
2502 );
2503 }
2504
2505 #[test]
2506 fn send_end_device_timeout_request_not_joined() {
2507 let (_guard, nlme) = make_nlme(MockMlme::new());
2508 let result = block_on(nlme.send_end_device_timeout_request());
2509 assert!(matches!(result, Err(NetworkError::NotJoined)));
2510 }
2511
2512 #[test]
2513 fn end_device_timeout_response_success_updates_nib() {
2514 let (_guard, nlme) = make_nlme(MockMlme::new());
2515 nlme.pending_timeout_request.store(0x03, Ordering::Relaxed);
2516
2517 block_on(nlme.handle_nwk_command(
2518 &dummy_header(0x0000),
2519 Command::EndDeviceTimeoutResponse(EndDeviceTimeoutResponse {
2520 status: EndDeviceTimeoutResponse::STATUS_SUCCESS,
2521 parent_information: 0b011,
2522 }),
2523 ));
2524
2525 let nib = nlme.nib();
2526 assert_eq!(*nib.parent_information(), 0b011);
2527 assert_eq!(*nib.end_device_timeout(), 0x03);
2528 assert_eq!(
2530 nlme.pending_timeout_request.load(Ordering::Relaxed),
2531 NO_PENDING_TIMEOUT
2532 );
2533 }
2534
2535 #[test]
2536 fn end_device_timeout_response_rejected_leaves_nib_untouched() {
2537 let (_guard, nlme) = make_nlme(MockMlme::new());
2538 nlme.pending_timeout_request.store(0x03, Ordering::Relaxed);
2539
2540 block_on(nlme.handle_nwk_command(
2541 &dummy_header(0x0000),
2542 Command::EndDeviceTimeoutResponse(EndDeviceTimeoutResponse {
2543 status: EndDeviceTimeoutResponse::STATUS_INCORRECT_VALUE,
2544 parent_information: 0b001,
2545 }),
2546 ));
2547
2548 let nib = nlme.nib();
2549 assert_eq!(*nib.parent_information(), 0x00);
2550 assert_eq!(*nib.end_device_timeout(), NO_PENDING_TIMEOUT);
2551 }
2552
2553 #[test]
2554 fn end_device_timeout_response_unsolicited_ignored() {
2555 let (_guard, nlme) = make_nlme(MockMlme::new());
2556
2557 block_on(nlme.handle_nwk_command(
2558 &dummy_header(0x0000),
2559 Command::EndDeviceTimeoutResponse(EndDeviceTimeoutResponse {
2560 status: EndDeviceTimeoutResponse::STATUS_SUCCESS,
2561 parent_information: 0b001,
2562 }),
2563 ));
2564
2565 let nib = nlme.nib();
2566 assert_eq!(*nib.parent_information(), 0x00);
2567 assert_eq!(*nib.end_device_timeout(), NO_PENDING_TIMEOUT);
2568 }
2569
2570 #[test]
2571 fn leave_not_joined_is_invalid() {
2572 let (_guard, nlme) = make_nlme(MockMlme::new());
2573
2574 let confirm = block_on(nlme.leave(NlmeLeaveRequest {
2575 device_address: None,
2576 remove_children: false,
2577 rejoin: false,
2578 }));
2579 assert_eq!(confirm.status, NlmeLeaveStatus::InvalidRequest);
2580 }
2581
2582 #[test]
2583 fn leave_other_device_is_unknown() {
2584 let (_guard, nlme) = make_nlme(MockMlme::new());
2585 seed_joined_nib(&nlme);
2586
2587 let confirm = block_on(nlme.leave(NlmeLeaveRequest {
2588 device_address: Some(IeeeAddress(0x1111_1111_1111_1111)),
2589 remove_children: false,
2590 rejoin: false,
2591 }));
2592 assert_eq!(confirm.status, NlmeLeaveStatus::UnknownDevice);
2593 }
2594
2595 #[test]
2596 fn leave_self_end_device_unicasts_to_parent_and_clears_nib() {
2597 let mut mac = MockMlme::new();
2598 mac.expect_transmit_data()
2599 .times(1)
2600 .withf(|dest, payload| {
2601 assert_eq!(
2602 *dest,
2603 Address::Short(PanId(0xAAAA), MacShortAddress(0x0000))
2604 );
2605 let mut buf = [0u8; 256];
2606 buf[..payload.len()].copy_from_slice(payload);
2607 let frame = SecurityContext::get()
2608 .decrypt_nwk_frame_in_place(&mut buf[..payload.len()])
2609 .unwrap();
2610 let NwkFrame::NwkCommand(command_frame) = frame else {
2611 panic!("expected NWK command frame");
2612 };
2613 let Command::Leave(leave) = command_frame.command else {
2614 panic!("expected leave command");
2615 };
2616 assert!(!leave.command_options.request());
2617 assert!(!leave.command_options.rejoin());
2618 assert!(!leave.command_options.remove_children());
2619 true
2620 })
2621 .returning(|_, _| Ok(()));
2622
2623 let (_guard, nlme) = make_nlme(mac);
2624 seed_joined_nib(&nlme);
2625
2626 let confirm = block_on(nlme.leave(NlmeLeaveRequest {
2629 device_address: None,
2630 remove_children: true,
2631 rejoin: false,
2632 }));
2633 assert_eq!(confirm.status, NlmeLeaveStatus::Success);
2634 assert_eq!(confirm.device_address, None);
2635
2636 let nib = nlme.nib();
2637 assert_eq!(*nib.network_address(), 0xffff);
2638 assert_eq!(*nib.extended_panid(), 0);
2639 assert!(nib.neighbor_table().is_empty());
2640 assert!(nib.security_material_set().is_empty());
2641 }
2642
2643 #[test]
2644 fn leave_self_clears_nib_even_when_transmit_fails() {
2645 let mut mac = MockMlme::new();
2646 mac.expect_transmit_data()
2647 .times(1)
2648 .returning(|_, _| Err(MacError::NoAck));
2649
2650 let (_guard, nlme) = make_nlme(mac);
2651 seed_joined_nib(&nlme);
2652
2653 let confirm = block_on(nlme.leave(NlmeLeaveRequest {
2654 device_address: None,
2655 remove_children: false,
2656 rejoin: false,
2657 }));
2658 assert_eq!(confirm.status, NlmeLeaveStatus::MacError);
2660 assert_eq!(*nlme.nib().network_address(), NWK_UNASSIGNED_ADDRESS);
2661 assert!(nlme.nib().security_material_set().is_empty());
2662 }
2663
2664 #[test]
2665 fn leave_request_to_broadcast_address_is_dropped() {
2666 let (_guard, nlme) = make_nlme(MockMlme::new());
2667 seed_joined_nib(&nlme);
2668
2669 let header = NwkHeader {
2670 destination: ShortAddress(0xfffd),
2671 ..dummy_header(0x0000)
2672 };
2673 let leave = Leave {
2674 command_options: LeaveCommandOptions(0).set_request(true),
2675 };
2676 block_on(nlme.handle_nwk_command(&header, Command::Leave(leave)));
2678
2679 assert_eq!(*nlme.nib().network_address(), 0x1234);
2680 }
2681
2682 #[test]
2683 fn leave_notification_from_parent_raises_indication() {
2684 let (_guard, nlme) = make_nlme(MockMlme::new());
2685 seed_joined_nib(&nlme);
2686
2687 let leave = Leave {
2688 command_options: LeaveCommandOptions(0).set_rejoin(true),
2689 };
2690 block_on(nlme.handle_nwk_command(&dummy_header(0x0000), Command::Leave(leave)));
2691
2692 assert_eq!(
2694 nlme.take_leave_indication(),
2695 Some(NlmeLeaveIndication {
2696 device_address: None,
2697 rejoin: true,
2698 })
2699 );
2700 }
2701
2702 #[test]
2703 fn leave_self_with_rejoin_leaves_nib_untouched() {
2704 let mut mac = MockMlme::new();
2705 mac.expect_transmit_data().times(1).returning(|_, _| Ok(()));
2706
2707 let (_guard, nlme) = make_nlme(mac);
2708 seed_joined_nib(&nlme);
2709
2710 let confirm = block_on(nlme.leave(NlmeLeaveRequest {
2711 device_address: None,
2712 remove_children: false,
2713 rejoin: true,
2714 }));
2715 assert_eq!(confirm.status, NlmeLeaveStatus::Success);
2716
2717 let nib = nlme.nib();
2718 assert_eq!(*nib.network_address(), 0x1234);
2722 assert_eq!(*nib.extended_panid(), 0);
2723 assert!(!nib.neighbor_table().is_empty());
2724 }
2725
2726 #[test]
2727 fn leave_notification_removes_only_the_notifying_neighbor() {
2728 let (_guard, nlme) = make_nlme(MockMlme::new());
2729 seed_joined_nib(&nlme);
2730 nlme.nib().update_neighbor_table(|table| {
2731 let _ = table.push(make_neighbor(0xAAAA, 0x5678, 0xBEEF, 200, 1));
2732 });
2733 assert_eq!(nlme.nib().neighbor_table().len(), 2);
2734
2735 let leave = Leave {
2736 command_options: LeaveCommandOptions(0),
2737 };
2738 block_on(nlme.handle_nwk_command(&dummy_header(0x5678), Command::Leave(leave)));
2739
2740 let nib = nlme.nib();
2741 assert_eq!(nib.neighbor_table().len(), 1);
2742 assert_eq!(
2743 nib.neighbor_table()[0].network_address,
2744 ShortAddress(0x0000)
2745 );
2746 assert_ne!(*nib.network_address(), 0xffff);
2748 }
2749
2750 #[test]
2751 fn leave_notification_from_parent_marks_device_removed() {
2752 let (_guard, nlme) = make_nlme(MockMlme::new());
2753 seed_joined_nib(&nlme);
2754
2755 let leave = Leave {
2756 command_options: LeaveCommandOptions(0),
2757 };
2758 block_on(nlme.handle_nwk_command(&dummy_header(0x0000), Command::Leave(leave)));
2759
2760 let nib = nlme.nib();
2761 assert_eq!(*nib.extended_panid(), 0);
2762 assert!(nib.neighbor_table().is_empty());
2763 }
2764
2765 #[test]
2766 fn leave_request_from_parent_triggers_self_removal() {
2767 let mut mac = MockMlme::new();
2768 mac.expect_transmit_data().times(1).returning(|_, _| Ok(()));
2769
2770 let (_guard, nlme) = make_nlme(mac);
2771 seed_joined_nib(&nlme);
2772
2773 let leave = Leave {
2774 command_options: LeaveCommandOptions(0).set_request(true),
2775 };
2776 block_on(nlme.handle_nwk_command(&dummy_header(0x0000), Command::Leave(leave)));
2777
2778 assert_eq!(*nlme.nib().network_address(), 0xffff);
2779 }
2780
2781 #[test]
2782 fn leave_request_from_non_parent_is_ignored() {
2783 let (_guard, nlme) = make_nlme(MockMlme::new());
2784 seed_joined_nib(&nlme);
2785
2786 let leave = Leave {
2787 command_options: LeaveCommandOptions(0).set_request(true),
2788 };
2789 block_on(nlme.handle_nwk_command(&dummy_header(0x9999), Command::Leave(leave)));
2791
2792 assert_eq!(*nlme.nib().network_address(), 0x1234);
2793 }
2794
2795 #[test]
2796 fn leave_request_dropped_for_coordinator() {
2797 let (_guard, nlme) = make_nlme(MockMlme::new());
2798 seed_joined_nib(&nlme);
2799 nlme.nib().update_network_address(|value| *value = 0x0000);
2800
2801 let leave = Leave {
2802 command_options: LeaveCommandOptions(0).set_request(true),
2803 };
2804 block_on(nlme.handle_nwk_command(&dummy_header(0x0000), Command::Leave(leave)));
2806
2807 assert_eq!(*nlme.nib().network_address(), 0x0000);
2808 }
2809
2810 #[test]
2811 fn negotiated_poll_interval() {
2812 let (_guard, nlme) = make_nlme(MockMlme::new());
2813 let nib = nlme.nib();
2814
2815 assert_eq!(nlme.negotiated_poll_interval_ms(), None);
2817
2818 nib.update_end_device_timeout(|value| *value = 1);
2820 nib.update_parent_information(|value| {
2821 *value = EndDeviceTimeoutResponse::MAC_DATA_POLL_KEEPALIVE;
2822 });
2823 assert_eq!(nlme.negotiated_poll_interval_ms(), Some(40_000));
2824
2825 nib.update_parent_information(|value| {
2827 *value = EndDeviceTimeoutResponse::TIMEOUT_REQUEST_KEEPALIVE;
2828 });
2829 assert_eq!(nlme.negotiated_poll_interval_ms(), None);
2830 }
2831
2832 #[test]
2833 fn keepalive_method_follows_parent_information() {
2834 let (_guard, nlme) = make_nlme(MockMlme::new());
2835 let nib = nlme.nib();
2836
2837 assert_eq!(nlme.keepalive_method(), KeepaliveMethod::None);
2839
2840 nib.update_end_device_timeout(|value| *value = 1);
2841 nib.update_parent_information(|value| {
2842 *value = EndDeviceTimeoutResponse::TIMEOUT_REQUEST_KEEPALIVE;
2843 });
2844 assert_eq!(nlme.keepalive_method(), KeepaliveMethod::TimeoutRequest);
2845
2846 nib.update_parent_information(|value| {
2848 *value = EndDeviceTimeoutResponse::MAC_DATA_POLL_KEEPALIVE
2849 | EndDeviceTimeoutResponse::TIMEOUT_REQUEST_KEEPALIVE;
2850 });
2851 assert_eq!(nlme.keepalive_method(), KeepaliveMethod::MacDataPoll);
2852 }
2853
2854 #[test]
2855 fn local_timeout_expiry_raises_parent_link_failure() {
2856 let (_guard, nlme) = make_nlme(MockMlme::new());
2857 seed_joined_nib(&nlme);
2858 let nib = nlme.nib();
2859
2860 assert!(!nlme.tick_parent_timeout(10_000));
2862 assert!(nlme.take_nwk_status_indication().is_none());
2863
2864 nib.update_end_device_timeout(|value| *value = 0);
2866 nib.update_parent_information(|value| {
2867 *value = EndDeviceTimeoutResponse::MAC_DATA_POLL_KEEPALIVE;
2868 });
2869 nlme.refresh_parent_timeout();
2870
2871 assert!(!nlme.tick_parent_timeout(4_000));
2872 assert!(!nlme.tick_parent_timeout(4_000));
2873 assert!(nlme.tick_parent_timeout(4_000));
2874
2875 let indication = nlme
2876 .take_nwk_status_indication()
2877 .expect("indication raised");
2878 assert_eq!(indication.status, NetworkStatusCode::ParentLinkFailure);
2879 assert_eq!(indication.network_address, ShortAddress(0x0000));
2880
2881 assert!(!nlme.tick_parent_timeout(4_000));
2883 assert!(nlme.take_nwk_status_indication().is_none());
2884 }
2885
2886 #[test]
2887 fn keepalive_without_response_raises_parent_link_failure() {
2888 let mut mac = MockMlme::new();
2889 mac.expect_transmit_data().times(1).returning(|_, _| Ok(()));
2890 mac.expect_poll_data()
2891 .returning(|_, _| Err(MacError::NoData));
2892
2893 let (_guard, nlme) = make_nlme(mac);
2894 seed_joined_nib(&nlme);
2895 let nib = nlme.nib();
2896 nib.update_end_device_timeout(|value| *value = 1);
2897 nib.update_parent_information(|value| {
2898 *value = EndDeviceTimeoutResponse::TIMEOUT_REQUEST_KEEPALIVE;
2899 });
2900
2901 let result = block_on(nlme.send_keepalive());
2902 assert!(matches!(result, Err(NetworkError::ParentLinkFailure)));
2903 assert_eq!(
2904 nlme.take_nwk_status_indication().map(|i| i.status),
2905 Some(NetworkStatusCode::ParentLinkFailure)
2906 );
2907 }
2908
2909 #[test]
2910 fn keepalive_response_refreshes_local_timeout() {
2911 let mut mac = MockMlme::new();
2913 mac.expect_transmit_data().times(1).returning(|_, _| Ok(()));
2914 mac.expect_poll_data().returning(|_, buf| {
2915 let frame = encrypted_timeout_response(EndDeviceTimeoutResponse::STATUS_SUCCESS);
2916 buf[..frame.len()].copy_from_slice(&frame);
2917 Ok((frame.len(), 200))
2918 });
2919
2920 let (_guard, nlme) = make_nlme(mac);
2921 seed_joined_nib(&nlme);
2922 let nib = nlme.nib();
2923 nib.update_end_device_timeout_default(|value| *value = 1);
2925 nib.update_end_device_timeout(|value| *value = 1);
2926 nib.update_parent_information(|value| {
2927 *value = EndDeviceTimeoutResponse::TIMEOUT_REQUEST_KEEPALIVE;
2928 });
2929
2930 assert!(block_on(nlme.send_keepalive()).is_ok());
2931 assert!(nlme.take_nwk_status_indication().is_none());
2932 assert!(!nlme.tick_parent_timeout(119_000));
2934 assert!(nlme.tick_parent_timeout(1_000));
2935 }
2936
2937 #[test]
2938 fn repeated_transmit_failures_report_parent_link_failure() {
2939 let mut mac = MockMlme::new();
2940 mac.expect_transmit_data()
2941 .returning(|_, _| Err(MacError::NoAck));
2942
2943 let (_guard, nlme) = make_nlme(mac);
2944 seed_joined_nib(&nlme);
2945
2946 for _ in 1..MAX_PARENT_TRANSMIT_FAILURES {
2948 assert!(matches!(
2949 block_on(nlme.send_data(ShortAddress(0x0000), true, &[1, 2, 3])),
2950 Err(NetworkError::MacError(_))
2951 ));
2952 assert!(nlme.take_nwk_status_indication().is_none());
2953 }
2954
2955 assert!(matches!(
2957 block_on(nlme.send_data(ShortAddress(0x0000), true, &[1, 2, 3])),
2958 Err(NetworkError::ParentLinkFailure)
2959 ));
2960 assert_eq!(
2961 nlme.take_nwk_status_indication().map(|i| i.status),
2962 Some(NetworkStatusCode::ParentLinkFailure)
2963 );
2964 assert!(matches!(
2966 block_on(nlme.send_data(ShortAddress(0x0000), true, &[1, 2, 3])),
2967 Err(NetworkError::MacError(_))
2968 ));
2969 }
2970
2971 #[test]
2972 fn successful_transmission_clears_the_failure_counter() {
2973 let mut mac = MockMlme::new();
2974 mac.expect_transmit_data()
2975 .times(1)
2976 .returning(|_, _| Err(MacError::NoAck));
2977 mac.expect_transmit_data().times(1).returning(|_, _| Ok(()));
2978
2979 let (_guard, nlme) = make_nlme(mac);
2980 seed_joined_nib(&nlme);
2981
2982 let _ = block_on(nlme.send_data(ShortAddress(0x0000), true, &[1, 2, 3]));
2983 assert!(block_on(nlme.send_data(ShortAddress(0x0000), true, &[1, 2, 3])).is_ok());
2984
2985 let table = nlme.nib().neighbor_table();
2986 let parent = table
2987 .iter()
2988 .find(|n| n.relationship == relationship::PARENT)
2989 .expect("parent");
2990 assert_eq!(parent.transmit_failure, 0);
2991 }
2992
2993 #[test]
2994 fn select_rejoin_candidates_ignores_permit_joining() {
2995 let (_guard, nlme) = make_nlme(MockMlme::new());
2996 let mut closed = make_neighbor(0xAAAA, 0x1234, 0xDEAD, 200, 1);
2997 closed.permit_joining = false;
2998 nlme.nib().update_neighbor_table(|table| {
2999 let _ = table.push(closed);
3000 });
3001
3002 assert!(
3004 nlme.select_parent_candidates(IeeeAddress(0xDEAD), false)
3005 .is_empty()
3006 );
3007 assert_eq!(
3008 nlme.select_rejoin_candidates(IeeeAddress(0xDEAD), false),
3009 heapless::Vec::<usize, 16>::from_slice(&[0]).unwrap()
3010 );
3011 }
3012
3013 #[test]
3014 fn rejoin_scans_when_the_known_parent_is_gone() {
3015 let mut mac = MockMlme::new();
3016 mac.expect_transmit_data()
3018 .times(1)
3019 .returning(|_, _| Err(MacError::NoAck));
3020 mac.expect_scan_network().times(1).returning(|_, _, _| {
3021 Ok(ScanResult {
3022 scan_type: ScanType::Active,
3023 pan_descriptor: Default::default(),
3024 })
3025 });
3026
3027 let (_guard, nlme) = make_nlme(mac);
3028 seed_joined_nib(&nlme);
3029
3030 let mut request = rejoin_request(0xDEAD);
3031 request.scan_channels = 20..21;
3032 let confirm = block_on(nlme.join(request));
3033
3034 assert_eq!(confirm.status, NlmeJoinStatus::NotPermitted);
3036 }
3037
3038 #[test]
3039 fn adopt_parent_follows_the_candidate_channel() {
3040 let mut mac = MockMlme::new();
3041 mac.expect_configure()
3042 .times(1)
3043 .withf(|config| config.channel == Some(20))
3044 .returning(|_| ());
3045
3046 let (_guard, nlme) = make_nlme(mac);
3047 seed_joined_nib(&nlme);
3048 let mut candidate = make_neighbor(0xAAAA, 0x1234, 0xDEAD, 200, 1);
3049 candidate.logical_channel = 20;
3050 candidate.update_id = 7;
3051 nlme.nib().update_neighbor_table(|table| {
3052 let _ = table.push(candidate);
3053 });
3054
3055 let adopted = block_on(nlme.adopt_parent(1));
3056
3057 assert_eq!(adopted, Some((ShortAddress(0x1234), 20)));
3058 assert_eq!(nlme.parent_short_address().unwrap(), ShortAddress(0x1234));
3059 assert_eq!(*nlme.nib().update_id(), 7);
3060 let table = nlme.nib().neighbor_table();
3062 assert_eq!(
3063 table
3064 .iter()
3065 .filter(|n| n.relationship == relationship::PARENT)
3066 .count(),
3067 1
3068 );
3069 }
3070
3071 #[test]
3072 fn rejoin_without_scan_channels_keeps_the_remembered_parent_failure() {
3073 let mut mac = MockMlme::new();
3074 mac.expect_transmit_data()
3075 .times(1)
3076 .returning(|_, _| Err(MacError::NoAck));
3077 let (_guard, nlme) = make_nlme(mac);
3080 seed_joined_nib(&nlme);
3081
3082 let confirm = block_on(nlme.join(rejoin_request(0xDEAD)));
3083 assert_eq!(confirm.status, NlmeJoinStatus::MacError);
3084 }
3085
3086 #[test]
3087 fn leave_request_from_parent_raises_indication_with_rejoin_flag() {
3088 let mut mac = MockMlme::new();
3089 mac.expect_transmit_data().times(1).returning(|_, _| Ok(()));
3090
3091 let (_guard, nlme) = make_nlme(mac);
3092 seed_joined_nib(&nlme);
3093
3094 let leave = Leave {
3095 command_options: LeaveCommandOptions(0).set_request(true).set_rejoin(true),
3096 };
3097 block_on(nlme.handle_nwk_command(&dummy_header(0x0000), Command::Leave(leave)));
3098
3099 assert_eq!(
3102 nlme.take_leave_indication(),
3103 Some(NlmeLeaveIndication {
3104 device_address: None,
3105 rejoin: true,
3106 })
3107 );
3108 }
3109
3110 #[test]
3111 fn rejoin_after_leave_accepts_the_requested_extended_pan_id() {
3112 let mut mac = MockMlme::new();
3113 mac.expect_transmit_data().times(1).returning(|_, _| Ok(()));
3114 mac.expect_poll_data().returning(|_, buf| {
3115 let frame = encrypted_rejoin_response(0x5678, 0x00);
3116 buf[..frame.len()].copy_from_slice(&frame);
3117 Ok((frame.len(), 200))
3118 });
3119 mac.expect_configure().times(1).returning(|_| ());
3120
3121 let (_guard, nlme) = make_nlme(mac);
3122 seed_joined_nib(&nlme);
3123 nlme.nib().update_extended_panid(|value| *value = 0);
3125
3126 let confirm = block_on(nlme.join(rejoin_request(0xDEAD)));
3127
3128 assert_eq!(confirm.status, NlmeJoinStatus::Success);
3129 assert_eq!(*nlme.nib().extended_panid(), 0xDEAD);
3130 }
3131
3132 fn encrypted_timeout_response(status: u8) -> heapless::Vec<u8, 128> {
3134 let nib = nib::get_ref();
3135 let header = NwkHeader {
3136 frame_control: NwkFrameControl(0)
3137 .set_frame_type(NwkFrameType::NwkCommand)
3138 .set_protocol_version(2)
3139 .set_discover_route(DiscoverRoute::Suppress)
3140 .set_security_flag(true)
3141 .set_source_ieee_flag(true),
3142 destination: ShortAddress(*nib.network_address()),
3143 source: ShortAddress(0x0000),
3144 radius: 1,
3145 sequence_number: 0,
3146 destination_ieee: None,
3147 source_ieee: Some(*nib.ieee_address()),
3148 multicast_control: None,
3149 source_route_subframe: None,
3150 };
3151 let command = Command::EndDeviceTimeoutResponse(EndDeviceTimeoutResponse {
3152 status,
3153 parent_information: EndDeviceTimeoutResponse::TIMEOUT_REQUEST_KEEPALIVE,
3154 });
3155 let nwk_frame = NwkFrame::NwkCommand(NwkCommandFrame { header, command });
3156 let mut buf = heapless::Vec::<u8, 128>::new();
3157 buf.resize(128, 0).unwrap();
3158 let len = SecurityContext::get()
3159 .encrypt_nwk_frame_in_place(nwk_frame, &mut buf)
3160 .unwrap();
3161 buf.truncate(len);
3162 buf
3163 }
3164}