Skip to main content

ezsp/apis_saltans/conversion/
event.rs

1//! APS data confirmations, membership, and network-state event conversions.
2//!
3//! Acknowledged direct-unicast `messageSent` callbacks recover the coordinator
4//! correlation counter from the EZSP message tag and become APSDE data
5//! confirmations. Child callbacks become join or leave events. Trust-center
6//! callbacks distinguish unsecured joins, secured/unsecured rejoins, and
7//! leaves. Only network up/down/opened/closed stack statuses have hardware
8//! event variants.
9
10use apis_saltans_hw::aps::apsde::{
11    ConfirmStatus, DataConfirm, Destination, IndividualEndpoint, NetworkAddress,
12};
13use apis_saltans_hw::core::Endpoint;
14use apis_saltans_hw::{ApsdeEvent, DeviceEvent, Event, NetworkEvent};
15
16use crate::ember::Status;
17use crate::ember::aps::Options;
18use crate::ember::device::Update;
19use crate::ember::message::Outgoing;
20use crate::parameters::messaging::handler::MessageSent;
21use crate::parameters::networking::handler::ChildJoin;
22use crate::parameters::trust_center::handler::TrustCenterJoin;
23
24impl TryFrom<MessageSent> for Event {
25    type Error = MessageSent;
26
27    fn try_from(message_sent: MessageSent) -> Result<Self, Self::Error> {
28        if !message_sent.aps_frame().options().contains(Options::RETRY) {
29            return Err(message_sent);
30        }
31        let source_endpoint =
32            IndividualEndpoint::new(Endpoint::from(message_sent.aps_frame().source_endpoint()))
33                .ok_or_else(|| message_sent.clone())?;
34        let destination_endpoint = Endpoint::from(message_sent.aps_frame().destination_endpoint());
35        let destination = match message_sent.typ().map_err(|_| message_sent.clone())? {
36            Outgoing::Direct => Destination::Network {
37                address: NetworkAddress::new(message_sent.index_or_destination())
38                    .ok_or_else(|| message_sent.clone())?,
39                endpoint: destination_endpoint,
40            },
41            Outgoing::ViaAddressTable
42            | Outgoing::ViaBinding
43            | Outgoing::Multicast
44            | Outgoing::Broadcast => return Err(message_sent),
45        };
46        let status = match message_sent.status() {
47            Ok(Status::Success) => ConfirmStatus::success(),
48            Ok(status) => ConfirmStatus::Network(status.into()),
49            Err(status) => ConfirmStatus::Network(status),
50        };
51        let confirmation = DataConfirm::new(destination, source_endpoint, status, ());
52
53        Ok(Self::Apsde(ApsdeEvent::DataConfirm {
54            counter: message_sent.message_tag(),
55            confirmation,
56        }))
57    }
58}
59
60impl TryFrom<ChildJoin> for Event {
61    type Error = ChildJoin;
62
63    fn try_from(child_join: ChildJoin) -> Result<Self, Self::Error> {
64        let event = if child_join.joining() {
65            DeviceEvent::Joined(child_join.try_into()?)
66        } else {
67            DeviceEvent::Left(child_join.try_into()?)
68        };
69
70        Ok(Self::Device(event))
71    }
72}
73
74impl TryFrom<Status> for Event {
75    type Error = Status;
76
77    fn try_from(status: Status) -> Result<Self, Self::Error> {
78        let event = match status {
79            Status::NetworkUp => NetworkEvent::Up,
80            Status::NetworkDown => NetworkEvent::Down,
81            Status::NetworkOpened => NetworkEvent::Opened,
82            Status::NetworkClosed => NetworkEvent::Closed,
83            other => return Err(other),
84        };
85
86        Ok(Self::Network(event))
87    }
88}
89
90impl TryFrom<TrustCenterJoin> for Event {
91    type Error = TrustCenterJoin;
92
93    fn try_from(trust_center_join: TrustCenterJoin) -> Result<Self, Self::Error> {
94        let Ok(status) = trust_center_join.status() else {
95            return Err(trust_center_join);
96        };
97
98        let event = match status {
99            Update::StandardSecurityUnsecuredJoin => {
100                DeviceEvent::Joined(trust_center_join.try_into()?)
101            }
102            Update::StandardSecurityUnsecuredRejoin => DeviceEvent::Rejoined {
103                address: trust_center_join.try_into()?,
104                secured: false,
105            },
106            Update::StandardSecuritySecuredRejoin => DeviceEvent::Rejoined {
107                address: trust_center_join.try_into()?,
108                secured: true,
109            },
110            Update::DeviceLeft => DeviceEvent::Left(trust_center_join.try_into()?),
111        };
112
113        Ok(Self::Device(event))
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use apis_saltans_hw::aps::apsde::{ConfirmStatus, Destination};
120    use apis_saltans_hw::{ApsdeEvent, Event};
121    use le_stream::FromLeStream;
122
123    use crate::parameters::messaging::handler::MessageSent;
124
125    const MESSAGE_TAG: u8 = 0x34;
126    const APS_SEQUENCE: u8 = 0x56;
127    const OPTIONS_INDEX: usize = 9;
128    const STATUS_INDEX: usize = 15;
129    const STATUS_SUCCESS: u8 = 0x00;
130    const STATUS_DELIVERY_FAILED: u8 = 0x66;
131    const MESSAGE_SENT_BYTES: [u8; 17] = [
132        0x00,
133        0x78,
134        0x56,
135        0x04,
136        0x01,
137        0x06,
138        0x03,
139        0x01,
140        0x02,
141        0x40,
142        0x00,
143        0x00,
144        0x00,
145        APS_SEQUENCE,
146        MESSAGE_TAG,
147        STATUS_SUCCESS,
148        0x00,
149    ];
150
151    fn message_sent(status: u8) -> MessageSent {
152        let mut bytes = MESSAGE_SENT_BYTES;
153        bytes[STATUS_INDEX] = status;
154        MessageSent::from_le_stream(bytes.into_iter())
155            .expect("messageSent test callback is complete")
156    }
157
158    #[test]
159    fn converts_successful_message_sent_to_data_confirmation() {
160        let event = Event::try_from(message_sent(STATUS_SUCCESS))
161            .expect("direct messageSent callback is representable");
162        let Event::Apsde(ApsdeEvent::DataConfirm {
163            counter,
164            confirmation,
165        }) = event
166        else {
167            panic!("messageSent must become a data confirmation");
168        };
169
170        assert_eq!(counter, MESSAGE_TAG);
171        assert_eq!(confirmation.status(), ConfirmStatus::success());
172        assert!(matches!(
173            confirmation.destination(),
174            Destination::Network { .. }
175        ));
176    }
177
178    #[test]
179    fn preserves_failed_message_sent_status() {
180        let event = Event::try_from(message_sent(STATUS_DELIVERY_FAILED))
181            .expect("direct messageSent callback is representable");
182        let Event::Apsde(ApsdeEvent::DataConfirm { confirmation, .. }) = event else {
183            panic!("messageSent must become a data confirmation");
184        };
185
186        assert_eq!(
187            confirmation.status(),
188            ConfirmStatus::Network(STATUS_DELIVERY_FAILED)
189        );
190    }
191
192    #[test]
193    fn rejects_unacknowledged_message_sent() {
194        let mut message = MESSAGE_SENT_BYTES;
195        message[OPTIONS_INDEX] = 0;
196        let message = MessageSent::from_le_stream(message.into_iter())
197            .expect("messageSent test callback is complete");
198
199        assert!(Event::try_from(message).is_err());
200    }
201}