lorawan_device/mac/
mod.rs

1//! LoRaWAN MAC layer implementation written as a non-async state machine (leveraged by `async_device` and `nb_device`).
2//! Manages state internally while providing client with transmit and receive frequencies, while writing to and
3//! decrypting from send and receive buffers.
4
5use crate::{
6    radio::{self, RadioBuffer, RfConfig, RxConfig, RxMode},
7    region, AppSKey, Downlink, NewSKey,
8};
9use heapless::Vec;
10use lorawan::{self, keys::CryptoFactory};
11use lorawan::{maccommands::DownlinkMacCommand, parser::DevAddr};
12
13pub type FcntDown = u32;
14pub type FcntUp = u32;
15
16mod session;
17use rand_core::RngCore;
18pub use session::{Session, SessionKeys};
19
20mod otaa;
21pub use otaa::NetworkCredentials;
22
23use crate::async_device;
24use crate::nb_device;
25
26pub(crate) mod uplink;
27
28#[derive(Copy, Clone, Debug)]
29pub(crate) enum Frame {
30    Join,
31    Data,
32}
33
34#[derive(Copy, Clone, Debug)]
35pub(crate) enum Window {
36    _1,
37    _2,
38}
39
40#[derive(Debug, PartialEq, Clone, Copy)]
41#[cfg_attr(feature = "defmt", derive(defmt::Format))]
42/// LoRaWAN Session and Network Configurations
43pub struct Configuration {
44    pub(crate) data_rate: region::DR,
45    rx1_delay: u32,
46    join_accept_delay1: u32,
47    join_accept_delay2: u32,
48}
49
50impl Configuration {
51    fn handle_downlink_macs(
52        &mut self,
53        region: &mut region::Configuration,
54        uplink: &mut uplink::Uplink,
55        cmds: lorawan::maccommands::MacCommandIterator<DownlinkMacCommand>,
56    ) {
57        use uplink::MacAnsTrait;
58        for cmd in cmds {
59            match cmd {
60                DownlinkMacCommand::LinkADRReq(payload) => {
61                    // we ignore DR and TxPwr
62                    region.set_channel_mask(
63                        payload.redundancy().channel_mask_control(),
64                        payload.channel_mask(),
65                    );
66                    uplink.adr_ans.add();
67                }
68                DownlinkMacCommand::RXTimingSetupReq(payload) => {
69                    self.rx1_delay = del_to_delay_ms(payload.delay());
70                    uplink.ack_rx_delay();
71                }
72                _ => (),
73            }
74        }
75    }
76}
77
78pub(crate) struct Mac {
79    pub configuration: Configuration,
80    pub region: region::Configuration,
81    board_eirp: BoardEirp,
82    state: State,
83}
84
85struct BoardEirp {
86    max_power: u8,
87    antenna_gain: i8,
88}
89
90#[allow(clippy::large_enum_variant)]
91enum State {
92    Joined(Session),
93    Otaa(otaa::Otaa),
94    Unjoined,
95}
96
97#[derive(Debug)]
98#[cfg_attr(feature = "defmt", derive(defmt::Format))]
99pub enum Error {
100    NotJoined,
101    InvalidResponse(Response),
102}
103
104pub struct SendData<'a> {
105    pub data: &'a [u8],
106    pub fport: u8,
107    pub confirmed: bool,
108}
109
110pub(crate) type Result<T = ()> = core::result::Result<T, Error>;
111
112impl Mac {
113    pub(crate) fn new(region: region::Configuration, max_power: u8, antenna_gain: i8) -> Self {
114        let data_rate = region.get_default_datarate();
115        Self {
116            board_eirp: BoardEirp { max_power, antenna_gain },
117            region,
118            state: State::Unjoined,
119            configuration: Configuration {
120                data_rate,
121                rx1_delay: region::constants::RECEIVE_DELAY1,
122                join_accept_delay1: region::constants::JOIN_ACCEPT_DELAY1,
123                join_accept_delay2: region::constants::JOIN_ACCEPT_DELAY2,
124            },
125        }
126    }
127
128    /// Prepare the radio buffer with transmitting a join request frame and provides the radio
129    /// configuration for the transmission.
130    pub(crate) fn join_otaa<C: CryptoFactory + Default, RNG: RngCore, const N: usize>(
131        &mut self,
132        rng: &mut RNG,
133        credentials: NetworkCredentials,
134        buf: &mut RadioBuffer<N>,
135    ) -> (radio::TxConfig, u16) {
136        let mut otaa = otaa::Otaa::new(credentials);
137        let dev_nonce = otaa.prepare_buffer::<C, RNG, N>(rng, buf);
138        self.state = State::Otaa(otaa);
139        let mut tx_config =
140            self.region.create_tx_config(rng, self.configuration.data_rate, &Frame::Join);
141        tx_config.adjust_power(self.board_eirp.max_power, self.board_eirp.antenna_gain);
142        (tx_config, dev_nonce)
143    }
144
145    /// Join via ABP. This does not transmit a join request frame, but instead sets the session.
146    pub(crate) fn join_abp(
147        &mut self,
148        newskey: NewSKey,
149        appskey: AppSKey,
150        devaddr: DevAddr<[u8; 4]>,
151    ) {
152        self.state = State::Joined(Session::new(newskey, appskey, devaddr));
153    }
154
155    /// Join via ABP. This does not transmit a join request frame, but instead sets the session.
156    pub(crate) fn set_session(&mut self, session: Session) {
157        self.state = State::Joined(session);
158    }
159
160    /// Prepare the radio buffer for transmitting a data frame and provide the radio configuration
161    /// for the transmission. Returns an error if the device is not joined.
162    pub(crate) fn send<C: CryptoFactory + Default, RNG: RngCore, const N: usize>(
163        &mut self,
164        rng: &mut RNG,
165        buf: &mut RadioBuffer<N>,
166        send_data: &SendData,
167    ) -> Result<(radio::TxConfig, FcntUp)> {
168        let fcnt = match &mut self.state {
169            State::Joined(ref mut session) => Ok(session.prepare_buffer::<C, N>(send_data, buf)),
170            State::Otaa(_) => Err(Error::NotJoined),
171            State::Unjoined => Err(Error::NotJoined),
172        }?;
173        let mut tx_config =
174            self.region.create_tx_config(rng, self.configuration.data_rate, &Frame::Data);
175        tx_config.adjust_power(self.board_eirp.max_power, self.board_eirp.antenna_gain);
176        Ok((tx_config, fcnt))
177    }
178
179    pub(crate) fn get_rx_delay(&self, frame: &Frame, window: &Window) -> u32 {
180        match frame {
181            Frame::Join => match window {
182                Window::_1 => self.configuration.join_accept_delay1,
183                Window::_2 => self.configuration.join_accept_delay2,
184            },
185            Frame::Data => match window {
186                Window::_1 => self.configuration.rx1_delay,
187                // RECEIVE_DELAY2 is not configurable. LoRaWAN 1.0.3 Section 5.7:
188                // "The second reception slot opens one second after the first reception slot."
189                Window::_2 => self.configuration.rx1_delay + 1000,
190            },
191        }
192    }
193
194    /// Gets the radio configuration and timing for a given frame type and window.
195    pub(crate) fn get_rx_parameters_legacy(
196        &mut self,
197        frame: &Frame,
198        window: &Window,
199    ) -> (RfConfig, u32) {
200        (
201            self.region.get_rx_config(self.configuration.data_rate, frame, window),
202            self.get_rx_delay(frame, window),
203        )
204    }
205
206    /// Handles a received RF frame. Returns None is unparseable, fails decryption, or fails MIC
207    /// verification. Upon successful join, provides Response::JoinSuccess. Upon successful data
208    /// rx, provides Response::DownlinkReceived. User must take the downlink from vec for
209    /// application data.
210    pub(crate) fn handle_rx<C: CryptoFactory + Default, const N: usize, const D: usize>(
211        &mut self,
212        buf: &mut RadioBuffer<N>,
213        dl: &mut Vec<Downlink, D>,
214    ) -> Response {
215        match &mut self.state {
216            State::Joined(ref mut session) => session.handle_rx::<C, N, D>(
217                &mut self.region,
218                &mut self.configuration,
219                buf,
220                dl,
221                false,
222            ),
223            State::Otaa(ref mut otaa) => {
224                if let Some(session) =
225                    otaa.handle_rx::<C, N>(&mut self.region, &mut self.configuration, buf)
226                {
227                    self.state = State::Joined(session);
228                    Response::JoinSuccess
229                } else {
230                    Response::NoUpdate
231                }
232            }
233            State::Unjoined => Response::NoUpdate,
234        }
235    }
236
237    /// Handles a received RF frame during RXC window. Returns None if unparseable, fails decryption,
238    /// or fails MIC verification. Upon successful data rx, provides Response::DownlinkReceived.
239    /// User must later call `take_downlink()` on the device to get the application data.
240    pub(crate) fn handle_rxc<C: CryptoFactory + Default, const N: usize, const D: usize>(
241        &mut self,
242        buf: &mut RadioBuffer<N>,
243        dl: &mut Vec<Downlink, D>,
244    ) -> Result<Response> {
245        match &mut self.state {
246            State::Joined(ref mut session) => Ok(session.handle_rx::<C, N, D>(
247                &mut self.region,
248                &mut self.configuration,
249                buf,
250                dl,
251                true,
252            )),
253            State::Otaa(_) => Err(Error::NotJoined),
254            State::Unjoined => Err(Error::NotJoined),
255        }
256    }
257
258    pub(crate) fn rx2_complete(&mut self) -> Response {
259        match &mut self.state {
260            State::Joined(session) => session.rx2_complete(),
261            State::Otaa(otaa) => otaa.rx2_complete(),
262            State::Unjoined => Response::NoUpdate,
263        }
264    }
265
266    pub(crate) fn get_session_keys(&self) -> Option<SessionKeys> {
267        match &self.state {
268            State::Joined(session) => session.get_session_keys(),
269            State::Otaa(_) => None,
270            State::Unjoined => None,
271        }
272    }
273
274    pub(crate) fn get_session(&self) -> Option<&Session> {
275        match &self.state {
276            State::Joined(session) => Some(session),
277            State::Otaa(_) => None,
278            State::Unjoined => None,
279        }
280    }
281
282    pub(crate) fn is_joined(&self) -> bool {
283        matches!(&self.state, State::Joined(_))
284    }
285
286    pub(crate) fn get_fcnt_up(&self) -> Option<FcntUp> {
287        match &self.state {
288            State::Joined(session) => Some(session.fcnt_up),
289            State::Otaa(_) => None,
290            State::Unjoined => None,
291        }
292    }
293
294    pub(crate) fn get_rx_config(&self, buffer_ms: u32, frame: &Frame, window: &Window) -> RxConfig {
295        RxConfig {
296            rf: self.region.get_rx_config(self.configuration.data_rate, frame, window),
297            mode: RxMode::Single { ms: buffer_ms },
298        }
299    }
300
301    pub(crate) fn get_rxc_config(&self) -> RxConfig {
302        RxConfig {
303            rf: self.region.get_rxc_config(self.configuration.data_rate),
304            mode: RxMode::Continuous,
305        }
306    }
307}
308
309#[cfg_attr(feature = "defmt", derive(defmt::Format))]
310#[derive(Debug)]
311pub enum Response {
312    NoAck,
313    SessionExpired,
314    DownlinkReceived(FcntDown),
315    NoJoinAccept,
316    JoinSuccess,
317    NoUpdate,
318    RxComplete,
319}
320
321impl From<Response> for nb_device::Response {
322    fn from(r: Response) -> Self {
323        match r {
324            Response::SessionExpired => nb_device::Response::SessionExpired,
325            Response::DownlinkReceived(fcnt) => nb_device::Response::DownlinkReceived(fcnt),
326            Response::NoAck => nb_device::Response::NoAck,
327            Response::NoJoinAccept => nb_device::Response::NoJoinAccept,
328            Response::JoinSuccess => nb_device::Response::JoinSuccess,
329            Response::NoUpdate => nb_device::Response::NoUpdate,
330            Response::RxComplete => nb_device::Response::RxComplete,
331        }
332    }
333}
334
335impl TryFrom<Response> for async_device::SendResponse {
336    type Error = Error;
337
338    fn try_from(r: Response) -> Result<async_device::SendResponse> {
339        match r {
340            Response::SessionExpired => Ok(async_device::SendResponse::SessionExpired),
341            Response::DownlinkReceived(fcnt) => {
342                Ok(async_device::SendResponse::DownlinkReceived(fcnt))
343            }
344            Response::NoAck => Ok(async_device::SendResponse::NoAck),
345            Response::RxComplete => Ok(async_device::SendResponse::RxComplete),
346            r => Err(Error::InvalidResponse(r)),
347        }
348    }
349}
350
351impl TryFrom<Response> for async_device::JoinResponse {
352    type Error = Error;
353
354    fn try_from(r: Response) -> Result<async_device::JoinResponse> {
355        match r {
356            Response::NoJoinAccept => Ok(async_device::JoinResponse::NoJoinAccept),
357            Response::JoinSuccess => Ok(async_device::JoinResponse::JoinSuccess),
358            r => Err(Error::InvalidResponse(r)),
359        }
360    }
361}
362
363fn del_to_delay_ms(del: u8) -> u32 {
364    match del {
365        2..=15 => del as u32 * 1000,
366        _ => region::constants::RECEIVE_DELAY1,
367    }
368}