Skip to main content

pamoja_lorawan/
session.rs

1//! The activated session and the data frames it secures.
2
3use crate::crypto::Cipher;
4use crate::error::LorawanError;
5use crate::frame::{
6    Direction, PhyPayload, MAX_FRAME, MAX_PAYLOAD, MTYPE_CONFIRMED_DOWN, MTYPE_CONFIRMED_UP,
7    MTYPE_MASK, MTYPE_UNCONFIRMED_DOWN, MTYPE_UNCONFIRMED_UP,
8};
9
10// The fixed header bytes of a data frame: MHDR, DevAddr, FCtrl, and FCnt.
11const FHDR_LEN: usize = 8;
12// The smallest data frame: the fixed header and the MIC, with no port or payload.
13const MIN_FRAME: usize = FHDR_LEN + 4;
14
15// FCtrl flag bits. Bits 6 and 4 mean different things in each direction: an uplink carries
16// ADRACKReq and ClassB there, a downlink a reserved bit and FPending.
17const FCTRL_ADR: u8 = 0x80;
18const FCTRL_ADR_ACK_REQ: u8 = 0x40;
19const FCTRL_ACK: u8 = 0x20;
20const FCTRL_FPENDING: u8 = 0x10;
21const FCTRL_CLASS_B: u8 = 0x10;
22const FCTRL_FOPTS_LEN: u8 = 0x0F;
23
24/// An activated LoRaWAN session: a device address and the two session keys.
25///
26/// This is the state a device holds once it is activated, whether by personalization
27/// (the address and keys provisioned directly) or by a join exchange. It secures every
28/// data frame: the network session key authenticates the whole frame through its MIC, and
29/// the application session key encrypts the payload, with the device address and frame
30/// counter folded into both so a frame is bound to its place in the stream.
31///
32/// # Examples
33///
34/// ```
35/// use pamoja_lorawan::{Session, Uplink};
36///
37/// let session = Session::new(0x2601_1BDA, [0x11; 16], [0x22; 16]);
38/// let frame = session.encode_uplink(&Uplink::new(1, 1, b"hello")).unwrap();
39///
40/// // The receiver, holding the same session, recovers the payload.
41/// let rx = session.decode(frame.as_bytes(), 1).unwrap();
42/// assert_eq!(rx.payload(), b"hello");
43/// ```
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct Session {
46    dev_addr: u32,
47    nwk_skey: [u8; 16],
48    app_skey: [u8; 16],
49}
50
51impl Session {
52    /// Creates a session from a device address and its two session keys.
53    ///
54    /// # Arguments
55    ///
56    /// * `dev_addr` - the device address the network assigned.
57    /// * `nwk_skey` - the network session key, which authenticates frames.
58    /// * `app_skey` - the application session key, which encrypts payloads.
59    ///
60    /// # Returns
61    ///
62    /// The session.
63    pub fn new(dev_addr: u32, nwk_skey: [u8; 16], app_skey: [u8; 16]) -> Self {
64        Session {
65            dev_addr,
66            nwk_skey,
67            app_skey,
68        }
69    }
70
71    /// Returns the device address this session is bound to.
72    ///
73    /// # Returns
74    ///
75    /// The device address.
76    pub fn dev_addr(&self) -> u32 {
77        self.dev_addr
78    }
79
80    /// The network and application session keys, for a device saving its session.
81    pub(crate) const fn keys(&self) -> ([u8; 16], [u8; 16]) {
82        (self.nwk_skey, self.app_skey)
83    }
84
85    /// Returns the root relay session key of the device, TS011-1.0.1 section 4.4.
86    ///
87    /// It comes from the network session key, as LoRaWAN 1.0.x has it, and is what a network
88    /// sends a relay in `UpdateUplinkListReq` so the relay can verify this device.
89    ///
90    /// # Returns
91    ///
92    /// The key.
93    pub fn root_wor_s_key(&self) -> [u8; 16] {
94        crate::relay::root_wor_s_key(&self.nwk_skey)
95    }
96
97    /// Returns the keys the device's wake-on-radio frames are protected with, TS011-1.0.1
98    /// section 4.5.
99    ///
100    /// # Returns
101    ///
102    /// The integrity and encryption keys, derived from
103    /// [`root_wor_s_key`](Session::root_wor_s_key) and the device address.
104    pub fn wor_keys(&self) -> crate::relay::WorKeys {
105        crate::relay::WorKeys::derive(&self.root_wor_s_key(), self.dev_addr)
106    }
107
108    /// Encodes an uplink data frame, encrypting the payload and appending the MIC.
109    ///
110    /// # Arguments
111    ///
112    /// * `uplink` - the uplink to send.
113    ///
114    /// # Returns
115    ///
116    /// The frame ready for the radio.
117    ///
118    /// # Errors
119    ///
120    /// Returns [`LorawanError::PayloadTooLong`] if the payload and options do not fit a
121    /// single frame, and [`LorawanError::MalformedFrame`] for port `0` with frame options,
122    /// which would carry MAC commands in both places at once.
123    pub fn encode_uplink(&self, uplink: &Uplink) -> Result<PhyPayload, LorawanError> {
124        let mtype = if uplink.confirmed {
125            MTYPE_CONFIRMED_UP
126        } else {
127            MTYPE_UNCONFIRMED_UP
128        };
129        let mut fctrl = 0;
130        if uplink.adr {
131            fctrl |= FCTRL_ADR;
132        }
133        if uplink.adr_ack_req {
134            fctrl |= FCTRL_ADR_ACK_REQ;
135        }
136        if uplink.ack {
137            fctrl |= FCTRL_ACK;
138        }
139        self.encode(
140            Direction::Uplink,
141            mtype,
142            fctrl,
143            uplink.fcnt,
144            uplink.fport,
145            uplink.fopts,
146            uplink.payload,
147        )
148    }
149
150    /// Encodes a downlink data frame, encrypting the payload and appending the MIC.
151    ///
152    /// # Arguments
153    ///
154    /// * `downlink` - the downlink to send.
155    ///
156    /// # Returns
157    ///
158    /// The frame ready for the radio.
159    ///
160    /// # Errors
161    ///
162    /// Returns [`LorawanError::PayloadTooLong`] if the payload and options do not fit a
163    /// single frame, and [`LorawanError::MalformedFrame`] for port `0` with frame options.
164    pub fn encode_downlink(&self, downlink: &Downlink) -> Result<PhyPayload, LorawanError> {
165        let mtype = if downlink.confirmed {
166            MTYPE_CONFIRMED_DOWN
167        } else {
168            MTYPE_UNCONFIRMED_DOWN
169        };
170        let mut fctrl = 0;
171        if downlink.adr {
172            fctrl |= FCTRL_ADR;
173        }
174        if downlink.ack {
175            fctrl |= FCTRL_ACK;
176        }
177        if downlink.fpending {
178            fctrl |= FCTRL_FPENDING;
179        }
180        self.encode(
181            Direction::Downlink,
182            mtype,
183            fctrl,
184            downlink.fcnt,
185            downlink.fport,
186            downlink.fopts,
187            downlink.payload,
188        )
189    }
190
191    #[allow(clippy::too_many_arguments)]
192    fn encode(
193        &self,
194        direction: Direction,
195        mtype: u8,
196        fctrl: u8,
197        fcnt: u32,
198        fport: Option<u8>,
199        fopts: &[u8],
200        payload: &[u8],
201    ) -> Result<PhyPayload, LorawanError> {
202        if fopts.len() > usize::from(FCTRL_FOPTS_LEN) {
203            return Err(LorawanError::PayloadTooLong);
204        }
205        // TS001-1.0.4 section 4.3.1.6: with frame options present, port 0 SHALL NOT be used.
206        if fport == Some(0) && !fopts.is_empty() {
207            return Err(LorawanError::MalformedFrame);
208        }
209        let len = MIN_FRAME + fopts.len() + usize::from(fport.is_some()) + payload.len();
210        if len > MAX_FRAME {
211            return Err(LorawanError::PayloadTooLong);
212        }
213
214        let mut buf = [0u8; MAX_FRAME];
215        buf[0] = mtype;
216        buf[1..5].copy_from_slice(&self.dev_addr.to_le_bytes());
217        buf[5] = fctrl | (fopts.len() as u8);
218        buf[6..8].copy_from_slice(&(fcnt as u16).to_le_bytes());
219        let mut at = FHDR_LEN;
220        buf[at..at + fopts.len()].copy_from_slice(fopts);
221        at += fopts.len();
222
223        // A frame with no port carries no payload either; the constructors that leave the
224        // port out take none.
225        if let Some(port) = fport {
226            buf[at] = port;
227            at += 1;
228
229            let key = self.payload_key(port);
230            crypt_payload(
231                key,
232                self.dev_addr,
233                direction,
234                fcnt,
235                payload,
236                &mut buf[at..at + payload.len()],
237            );
238            at += payload.len();
239        }
240
241        let mic = self.mic(direction, fcnt, &buf[..at]);
242        buf[at..at + 4].copy_from_slice(&mic);
243        at += 4;
244
245        PhyPayload::new(&buf[..at])
246    }
247
248    /// Decodes a received data frame: verifies the MIC, then decrypts the payload.
249    ///
250    /// # Arguments
251    ///
252    /// * `bytes` - the raw frame as it came off the radio.
253    /// * `fcnt` - the full 32-bit frame counter expected for this frame; its low 16 bits
254    ///   must match the counter the frame carries.
255    ///
256    /// # Returns
257    ///
258    /// The decoded frame, with its payload decrypted.
259    ///
260    /// # Errors
261    ///
262    /// Returns [`LorawanError::FrameTooShort`] if the frame is too small,
263    /// [`LorawanError::UnsupportedMType`] if it is not a data frame,
264    /// [`LorawanError::FcntMismatch`] if the counter does not match,
265    /// [`LorawanError::MicMismatch`] if the MIC does not verify, or
266    /// [`LorawanError::MalformedFrame`] if an authentic frame carries frame options on port
267    /// `0`, which TS001-1.0.4 section 4.3.1.6 has a device discard.
268    pub fn decode(&self, bytes: &[u8], fcnt: u32) -> Result<RxData, LorawanError> {
269        if bytes.len() < MIN_FRAME {
270            return Err(LorawanError::FrameTooShort);
271        }
272        let mtype = bytes[0] & MTYPE_MASK;
273        let (direction, confirmed) = match mtype {
274            MTYPE_UNCONFIRMED_UP => (Direction::Uplink, false),
275            MTYPE_CONFIRMED_UP => (Direction::Uplink, true),
276            MTYPE_UNCONFIRMED_DOWN => (Direction::Downlink, false),
277            MTYPE_CONFIRMED_DOWN => (Direction::Downlink, true),
278            other => return Err(LorawanError::UnsupportedMType(other)),
279        };
280
281        let dev_addr = u32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
282        let fctrl = bytes[5];
283        let fopts_len = usize::from(fctrl & FCTRL_FOPTS_LEN);
284        let fcnt_low = u16::from_le_bytes([bytes[6], bytes[7]]);
285        if fcnt as u16 != fcnt_low {
286            return Err(LorawanError::FcntMismatch);
287        }
288
289        let mic_start = bytes.len() - 4;
290        let body_start = FHDR_LEN + fopts_len;
291        if mic_start < body_start {
292            return Err(LorawanError::FrameTooShort);
293        }
294        let expected = self.mic(direction, fcnt, &bytes[..mic_start]);
295        if bytes[mic_start..] != expected[..] {
296            return Err(LorawanError::MicMismatch);
297        }
298
299        if fopts_len > 0 && mic_start > body_start && bytes[body_start] == 0 {
300            return Err(LorawanError::MalformedFrame);
301        }
302
303        let mut fopts = [0u8; FCTRL_FOPTS_LEN as usize];
304        fopts[..fopts_len].copy_from_slice(&bytes[FHDR_LEN..FHDR_LEN + fopts_len]);
305
306        let mut payload = [0u8; MAX_PAYLOAD];
307        let (fport, payload_len) = if mic_start > body_start {
308            let fport = bytes[body_start];
309            let encrypted = &bytes[body_start + 1..mic_start];
310            let key = self.payload_key(fport);
311            crypt_payload(
312                key,
313                dev_addr,
314                direction,
315                fcnt,
316                encrypted,
317                &mut payload[..encrypted.len()],
318            );
319            (Some(fport), encrypted.len())
320        } else {
321            (None, 0)
322        };
323
324        let uplink = direction == Direction::Uplink;
325        Ok(RxData {
326            direction,
327            dev_addr,
328            fcnt_low,
329            confirmed,
330            adr: fctrl & FCTRL_ADR != 0,
331            adr_ack_req: uplink && fctrl & FCTRL_ADR_ACK_REQ != 0,
332            ack: fctrl & FCTRL_ACK != 0,
333            fpending: !uplink && fctrl & FCTRL_FPENDING != 0,
334            class_b: uplink && fctrl & FCTRL_CLASS_B != 0,
335            fport,
336            fopts,
337            fopts_len,
338            payload,
339            payload_len,
340        })
341    }
342
343    // The key that encrypts a payload: the network key for port 0 (MAC commands), the
344    // application key for every other port.
345    fn payload_key(&self, fport: u8) -> &[u8; 16] {
346        if fport == 0 {
347            &self.nwk_skey
348        } else {
349            &self.app_skey
350        }
351    }
352
353    // The four-byte MIC over a frame's contents, per the spec's B0 block.
354    fn mic(&self, direction: Direction, fcnt: u32, msg: &[u8]) -> [u8; 4] {
355        let mut block = [0u8; 16 + MAX_FRAME];
356        block[0] = 0x49;
357        block[5] = direction.bit();
358        block[6..10].copy_from_slice(&self.dev_addr.to_le_bytes());
359        block[10..14].copy_from_slice(&fcnt.to_le_bytes());
360        block[15] = msg.len() as u8;
361        block[16..16 + msg.len()].copy_from_slice(msg);
362        let tag = Cipher::new(&self.nwk_skey).cmac(&block[..16 + msg.len()]);
363        [tag[0], tag[1], tag[2], tag[3]]
364    }
365}
366
367// Encrypts (or, being a XOR keystream, decrypts) a payload in place into `output`, per the
368// spec's A_i block construction.
369fn crypt_payload(
370    key: &[u8; 16],
371    dev_addr: u32,
372    direction: Direction,
373    fcnt: u32,
374    input: &[u8],
375    output: &mut [u8],
376) {
377    let cipher = Cipher::new(key);
378    let blocks = input.len().div_ceil(16);
379    for i in 0..blocks {
380        let mut a = [0u8; 16];
381        a[0] = 0x01;
382        a[5] = direction.bit();
383        a[6..10].copy_from_slice(&dev_addr.to_le_bytes());
384        a[10..14].copy_from_slice(&fcnt.to_le_bytes());
385        a[15] = (i + 1) as u8;
386        let stream = cipher.encrypt_block(&a);
387
388        let start = i * 16;
389        let end = (start + 16).min(input.len());
390        for j in start..end {
391            output[j] = input[j] ^ stream[j - start];
392        }
393    }
394}
395
396/// An uplink data frame to encode, built up from the fields a sender sets.
397///
398/// Construct one with [`new`](Uplink::new) and turn on whatever applies; the rest default
399/// off. A higher port carries application data; port `0` carries MAC commands. A frame with
400/// nothing to carry but its header and frame options, such as an answer to a MAC command
401/// with no reading to go with it, is built with [`empty`](Uplink::empty) and has no port.
402///
403/// # Examples
404///
405/// ```
406/// use pamoja_lorawan::Uplink;
407///
408/// let uplink = Uplink::new(7, 2, b"reading").confirmed().with_adr();
409/// ```
410#[derive(Clone, Copy, Debug)]
411pub struct Uplink<'a> {
412    fcnt: u32,
413    fport: Option<u8>,
414    payload: &'a [u8],
415    confirmed: bool,
416    adr: bool,
417    adr_ack_req: bool,
418    ack: bool,
419    fopts: &'a [u8],
420}
421
422impl<'a> Uplink<'a> {
423    /// Creates an unconfirmed uplink with no options set.
424    ///
425    /// # Arguments
426    ///
427    /// * `fcnt` - the frame counter for this uplink.
428    /// * `fport` - the port; `0` for MAC commands, otherwise an application port.
429    /// * `payload` - the application payload to carry.
430    ///
431    /// # Returns
432    ///
433    /// The uplink.
434    pub fn new(fcnt: u32, fport: u8, payload: &'a [u8]) -> Self {
435        Uplink {
436            fcnt,
437            fport: Some(fport),
438            payload,
439            confirmed: false,
440            adr: false,
441            adr_ack_req: false,
442            ack: false,
443            fopts: &[],
444        }
445    }
446
447    /// Creates an uplink with no port and no payload.
448    ///
449    /// TS001-1.0.4 section 4.3.2 makes the port optional when there is no payload. What such
450    /// a frame carries is its header: the counter, the flags, and whatever frame options it
451    /// is given.
452    ///
453    /// # Arguments
454    ///
455    /// * `fcnt` - the frame counter for this uplink.
456    ///
457    /// # Returns
458    ///
459    /// The uplink.
460    ///
461    /// # Examples
462    ///
463    /// ```
464    /// use pamoja_lorawan::mac::MacCommand;
465    /// use pamoja_lorawan::{Session, Uplink};
466    ///
467    /// // A device answering a status request and nothing else: on mains power it cannot
468    /// // measure a battery, and it heard the request 10 dB above the noise.
469    /// let session = Session::new(0x2601_1BDA, [0x2B; 16], [0x99; 16]);
470    /// let mut answer = [0u8; 3];
471    /// MacCommand::DevStatusAns { battery: 255, margin: 10 }.encode(&mut answer)?;
472    /// let frame = session.encode_uplink(&Uplink::empty(3).with_fopts(&answer))?;
473    ///
474    /// // It goes up with no port and no payload, the answer in the frame options.
475    /// let heard = session.decode(frame.as_bytes(), 3)?;
476    /// assert_eq!(heard.fport(), None);
477    /// assert_eq!(heard.fopts(), &answer);
478    /// # Ok::<(), pamoja_lorawan::LorawanError>(())
479    /// ```
480    pub fn empty(fcnt: u32) -> Self {
481        Uplink {
482            fport: None,
483            ..Uplink::new(fcnt, 0, &[])
484        }
485    }
486
487    /// Marks the uplink as confirmed, asking the network to acknowledge it.
488    ///
489    /// # Returns
490    ///
491    /// The uplink, for chaining.
492    pub fn confirmed(mut self) -> Self {
493        self.confirmed = true;
494        self
495    }
496
497    /// Sets the adaptive-data-rate bit, letting the network manage the data rate.
498    ///
499    /// # Returns
500    ///
501    /// The uplink, for chaining.
502    pub fn with_adr(mut self) -> Self {
503        self.adr = true;
504        self
505    }
506
507    /// Sets the ADR acknowledgment request bit, asking the network to send something back.
508    ///
509    /// A device sets it once it has gone [`ADR_ACK_LIMIT`](crate::defaults::ADR_ACK_LIMIT)
510    /// uplinks without hearing anything, and [`Backoff`](crate::adr::Backoff) says when.
511    ///
512    /// # Returns
513    ///
514    /// The uplink, for chaining.
515    pub fn with_adr_ack_req(mut self) -> Self {
516        self.adr_ack_req = true;
517        self
518    }
519
520    /// Sets the acknowledgment bit, confirming a previously received downlink.
521    ///
522    /// # Returns
523    ///
524    /// The uplink, for chaining.
525    pub fn with_ack(mut self) -> Self {
526        self.ack = true;
527        self
528    }
529
530    /// Carries MAC command options in the frame header.
531    ///
532    /// # Arguments
533    ///
534    /// * `fopts` - the frame options, up to 15 bytes.
535    ///
536    /// # Returns
537    ///
538    /// The uplink, for chaining.
539    pub fn with_fopts(mut self, fopts: &'a [u8]) -> Self {
540        self.fopts = fopts;
541        self
542    }
543}
544
545/// A downlink data frame to encode, built up from the fields a sender sets.
546///
547/// Construct one with [`new`](Downlink::new) and turn on whatever applies; the rest
548/// default off.
549#[derive(Clone, Copy, Debug)]
550pub struct Downlink<'a> {
551    fcnt: u32,
552    fport: Option<u8>,
553    payload: &'a [u8],
554    confirmed: bool,
555    adr: bool,
556    ack: bool,
557    fpending: bool,
558    fopts: &'a [u8],
559}
560
561impl<'a> Downlink<'a> {
562    /// Creates an unconfirmed downlink with no options set.
563    ///
564    /// # Arguments
565    ///
566    /// * `fcnt` - the frame counter for this downlink.
567    /// * `fport` - the port; `0` for MAC commands, otherwise an application port.
568    /// * `payload` - the application payload to carry.
569    ///
570    /// # Returns
571    ///
572    /// The downlink.
573    pub fn new(fcnt: u32, fport: u8, payload: &'a [u8]) -> Self {
574        Downlink {
575            fcnt,
576            fport: Some(fport),
577            payload,
578            confirmed: false,
579            adr: false,
580            ack: false,
581            fpending: false,
582            fopts: &[],
583        }
584    }
585
586    /// Creates a downlink with no port and no payload.
587    ///
588    /// This is the frame a network sends when all it has to say is in the header: an
589    /// acknowledgment, an answer to an ADR acknowledgment request, or MAC commands in the
590    /// frame options.
591    ///
592    /// # Arguments
593    ///
594    /// * `fcnt` - the frame counter for this downlink.
595    ///
596    /// # Returns
597    ///
598    /// The downlink.
599    pub fn empty(fcnt: u32) -> Self {
600        Downlink {
601            fport: None,
602            ..Downlink::new(fcnt, 0, &[])
603        }
604    }
605
606    /// Marks the downlink as confirmed, asking the device to acknowledge it.
607    ///
608    /// # Returns
609    ///
610    /// The downlink, for chaining.
611    pub fn confirmed(mut self) -> Self {
612        self.confirmed = true;
613        self
614    }
615
616    /// Sets the adaptive-data-rate bit.
617    ///
618    /// # Returns
619    ///
620    /// The downlink, for chaining.
621    pub fn with_adr(mut self) -> Self {
622        self.adr = true;
623        self
624    }
625
626    /// Sets the acknowledgment bit, confirming a previously received uplink.
627    ///
628    /// # Returns
629    ///
630    /// The downlink, for chaining.
631    pub fn with_ack(mut self) -> Self {
632        self.ack = true;
633        self
634    }
635
636    /// Sets the frame-pending bit, signaling more downlinks are waiting.
637    ///
638    /// # Returns
639    ///
640    /// The downlink, for chaining.
641    pub fn with_fpending(mut self) -> Self {
642        self.fpending = true;
643        self
644    }
645
646    /// Carries MAC command options in the frame header.
647    ///
648    /// # Arguments
649    ///
650    /// * `fopts` - the frame options, up to 15 bytes.
651    ///
652    /// # Returns
653    ///
654    /// The downlink, for chaining.
655    pub fn with_fopts(mut self, fopts: &'a [u8]) -> Self {
656        self.fopts = fopts;
657        self
658    }
659}
660
661/// A decoded data frame, with its payload decrypted.
662///
663/// What [`Session::decode`] returns once a frame's MIC has verified: the header fields and
664/// the recovered payload, held in fixed buffers.
665#[derive(Clone, Copy, Debug, PartialEq, Eq)]
666pub struct RxData {
667    direction: Direction,
668    dev_addr: u32,
669    fcnt_low: u16,
670    confirmed: bool,
671    adr: bool,
672    adr_ack_req: bool,
673    ack: bool,
674    fpending: bool,
675    class_b: bool,
676    fport: Option<u8>,
677    fopts: [u8; FCTRL_FOPTS_LEN as usize],
678    fopts_len: usize,
679    payload: [u8; MAX_PAYLOAD],
680    payload_len: usize,
681}
682
683impl RxData {
684    /// Returns the direction the frame traveled.
685    ///
686    /// # Returns
687    ///
688    /// [`Direction::Uplink`] or [`Direction::Downlink`].
689    pub fn direction(&self) -> Direction {
690        self.direction
691    }
692
693    /// Returns the device address the frame carried.
694    ///
695    /// # Returns
696    ///
697    /// The device address.
698    pub fn dev_addr(&self) -> u32 {
699        self.dev_addr
700    }
701
702    /// Returns the low 16 bits of the frame counter the frame carried.
703    ///
704    /// # Returns
705    ///
706    /// The frame counter's low half.
707    pub fn fcnt(&self) -> u16 {
708        self.fcnt_low
709    }
710
711    /// Reports whether the frame is a confirmed frame that expects an acknowledgment.
712    ///
713    /// # Returns
714    ///
715    /// `true` for a confirmed frame.
716    pub fn confirmed(&self) -> bool {
717        self.confirmed
718    }
719
720    /// Reports whether the adaptive-data-rate bit is set.
721    ///
722    /// # Returns
723    ///
724    /// `true` if the bit is set.
725    pub fn adr(&self) -> bool {
726        self.adr
727    }
728
729    /// Reports whether an uplink asks the network to send something back.
730    ///
731    /// # Returns
732    ///
733    /// `true` if an uplink's ADRACKReq bit is set. A downlink has a reserved bit in that
734    /// place, so this is always `false` for one.
735    pub fn adr_ack_req(&self) -> bool {
736        self.adr_ack_req
737    }
738
739    /// Reports whether the acknowledgment bit is set.
740    ///
741    /// # Returns
742    ///
743    /// `true` if the bit is set.
744    pub fn ack(&self) -> bool {
745        self.ack
746    }
747
748    /// Reports whether a downlink says the network has more waiting.
749    ///
750    /// # Returns
751    ///
752    /// `true` if a downlink's FPending bit is set. An uplink carries its ClassB bit in that
753    /// place instead, so this is always `false` for one; see [`class_b`](RxData::class_b).
754    pub fn fpending(&self) -> bool {
755        self.fpending
756    }
757
758    /// Reports whether an uplink says its device has Class B enabled.
759    ///
760    /// # Returns
761    ///
762    /// `true` if an uplink's ClassB bit is set, and always `false` for a downlink.
763    pub fn class_b(&self) -> bool {
764        self.class_b
765    }
766
767    /// Returns the port the frame was sent on, if it carried a port and payload.
768    ///
769    /// # Returns
770    ///
771    /// The port, or [`None`] for a frame with no port or payload.
772    pub fn fport(&self) -> Option<u8> {
773        self.fport
774    }
775
776    /// Returns the frame options carried in the header.
777    ///
778    /// # Returns
779    ///
780    /// The frame option bytes, which may be empty.
781    pub fn fopts(&self) -> &[u8] {
782        &self.fopts[..self.fopts_len]
783    }
784
785    /// Returns the decrypted payload.
786    ///
787    /// # Returns
788    ///
789    /// The application payload, which may be empty.
790    pub fn payload(&self) -> &[u8] {
791        &self.payload[..self.payload_len]
792    }
793}
794
795#[cfg(test)]
796mod tests {
797    use super::*;
798
799    const NWK_SKEY: [u8; 16] = [0x01; 16];
800    const APP_SKEY: [u8; 16] = [0x02; 16];
801    const DEV_ADDR: u32 = 0x2601_1BDA;
802
803    fn session() -> Session {
804        Session::new(DEV_ADDR, NWK_SKEY, APP_SKEY)
805    }
806
807    #[test]
808    fn an_uplink_round_trips() {
809        let session = session();
810        let frame = session
811            .encode_uplink(&Uplink::new(10, 1, b"temperature"))
812            .unwrap();
813        let rx = session.decode(frame.as_bytes(), 10).unwrap();
814        assert_eq!(rx.direction(), Direction::Uplink);
815        assert_eq!(rx.dev_addr(), DEV_ADDR);
816        assert_eq!(rx.fcnt(), 10);
817        assert_eq!(rx.fport(), Some(1));
818        assert_eq!(rx.payload(), b"temperature");
819        assert!(!rx.confirmed());
820    }
821
822    #[test]
823    fn the_payload_is_encrypted_on_the_wire() {
824        let session = session();
825        let frame = session
826            .encode_uplink(&Uplink::new(1, 1, b"secret"))
827            .unwrap();
828        // The plaintext must not appear in the encoded frame.
829        assert!(frame
830            .as_bytes()
831            .windows(b"secret".len())
832            .all(|window| window != b"secret"));
833    }
834
835    #[test]
836    fn the_header_is_laid_out_as_the_spec_requires() {
837        let session = session();
838        let frame = session
839            .encode_uplink(&Uplink::new(0x0102, 1, b"x"))
840            .unwrap();
841        let bytes = frame.as_bytes();
842        assert_eq!(bytes[0], MTYPE_UNCONFIRMED_UP);
843        // DevAddr little-endian.
844        assert_eq!(&bytes[1..5], &DEV_ADDR.to_le_bytes());
845        // FCnt little-endian, low 16 bits.
846        assert_eq!(&bytes[6..8], &0x0102u16.to_le_bytes());
847    }
848
849    #[test]
850    fn a_confirmed_downlink_round_trips_with_its_flags() {
851        let session = session();
852        let frame = session
853            .encode_downlink(&Downlink::new(5, 2, b"cmd").confirmed().with_fpending())
854            .unwrap();
855        let rx = session.decode(frame.as_bytes(), 5).unwrap();
856        assert_eq!(rx.direction(), Direction::Downlink);
857        assert!(rx.confirmed());
858        assert!(rx.fpending());
859        assert_eq!(rx.payload(), b"cmd");
860    }
861
862    #[test]
863    fn frame_options_round_trip() {
864        let session = session();
865        let frame = session
866            .encode_uplink(&Uplink::new(3, 1, b"d").with_fopts(&[0x02, 0x03]))
867            .unwrap();
868        let rx = session.decode(frame.as_bytes(), 3).unwrap();
869        assert_eq!(rx.fopts(), &[0x02, 0x03]);
870        assert_eq!(rx.payload(), b"d");
871    }
872
873    #[test]
874    fn an_empty_payload_round_trips() {
875        let session = session();
876        let frame = session.encode_uplink(&Uplink::new(1, 1, b"")).unwrap();
877        let rx = session.decode(frame.as_bytes(), 1).unwrap();
878        assert_eq!(rx.payload(), b"");
879        assert_eq!(rx.fport(), Some(1));
880    }
881
882    #[test]
883    fn a_tampered_payload_fails_the_mic() {
884        let session = session();
885        let frame = session.encode_uplink(&Uplink::new(1, 1, b"data")).unwrap();
886        let mut bytes = frame.as_bytes().to_vec();
887        let last = bytes.len() - 5; // a payload byte, before the 4-byte MIC
888        bytes[last] ^= 0xff;
889        assert_eq!(session.decode(&bytes, 1), Err(LorawanError::MicMismatch));
890    }
891
892    #[test]
893    fn the_wrong_counter_is_rejected() {
894        let session = session();
895        let frame = session.encode_uplink(&Uplink::new(7, 1, b"data")).unwrap();
896        assert_eq!(
897            session.decode(frame.as_bytes(), 8),
898            Err(LorawanError::FcntMismatch)
899        );
900    }
901
902    #[test]
903    fn a_join_frame_is_not_decoded_here() {
904        let session = session();
905        // MHDR 0x00 is a join-request, not a data frame.
906        let bytes = [0u8; MIN_FRAME];
907        assert_eq!(
908            session.decode(&bytes, 0),
909            Err(LorawanError::UnsupportedMType(0x00))
910        );
911    }
912
913    #[test]
914    fn a_short_frame_is_rejected() {
915        let session = session();
916        assert_eq!(
917            session.decode(&[0x40, 0x00, 0x00], 0),
918            Err(LorawanError::FrameTooShort)
919        );
920    }
921
922    #[test]
923    fn port_zero_uses_the_network_key() {
924        // A port-0 payload is encrypted with the network key, so decoding it with a
925        // session whose application key differs still recovers it.
926        let session = Session::new(DEV_ADDR, NWK_SKEY, APP_SKEY);
927        let frame = session.encode_uplink(&Uplink::new(1, 0, b"mac")).unwrap();
928        let other = Session::new(DEV_ADDR, NWK_SKEY, [0x33; 16]);
929        let rx = other.decode(frame.as_bytes(), 1).unwrap();
930        assert_eq!(rx.payload(), b"mac");
931    }
932
933    #[test]
934    fn the_largest_payload_round_trips() {
935        let session = session();
936        let payload = [0xAB; MAX_PAYLOAD];
937        let frame = session.encode_uplink(&Uplink::new(1, 1, &payload)).unwrap();
938        assert_eq!(frame.as_bytes().len(), crate::MAX_FRAME);
939        let rx = session.decode(frame.as_bytes(), 1).unwrap();
940        assert_eq!(rx.payload(), &payload[..]);
941    }
942
943    #[test]
944    fn the_full_frame_counter_is_bound_into_the_mic() {
945        let session = session();
946        // Only the low 16 bits of the counter travel on the wire, but the whole 32-bit
947        // value is folded into the MIC.
948        let frame = session
949            .encode_uplink(&Uplink::new(0x0001_0001, 1, b"x"))
950            .unwrap();
951        // The right low bits but the wrong upper bits must still fail the MIC.
952        assert_eq!(
953            session.decode(frame.as_bytes(), 0x0000_0001),
954            Err(LorawanError::MicMismatch)
955        );
956        // The full counter verifies.
957        let rx = session.decode(frame.as_bytes(), 0x0001_0001).unwrap();
958        assert_eq!(rx.fcnt(), 0x0001);
959    }
960
961    #[test]
962    fn another_sessions_keys_cannot_read_a_frame() {
963        let session = session();
964        let frame = session
965            .encode_uplink(&Uplink::new(1, 1, b"secret"))
966            .unwrap();
967        let stranger = Session::new(DEV_ADDR, [0xAA; 16], [0xBB; 16]);
968        assert_eq!(
969            stranger.decode(frame.as_bytes(), 1),
970            Err(LorawanError::MicMismatch)
971        );
972    }
973
974    // Rewrites a frame's FCtrl or port and signs it again, so a test can hand the decoder a
975    // header no builder here produces.
976    fn resigned(
977        direction: Direction,
978        fcnt: u32,
979        frame: &[u8],
980        edit: impl Fn(&mut [u8]),
981    ) -> Vec<u8> {
982        let mut bytes = frame.to_vec();
983        edit(&mut bytes);
984        let at = bytes.len() - 4;
985        let mic = session().mic(direction, fcnt, &bytes[..at]);
986        bytes[at..].copy_from_slice(&mic);
987        bytes
988    }
989
990    #[test]
991    fn an_uplink_asking_for_an_answer_sets_bit_six() {
992        // TS001-1.0.4 table 8 and LoRaWAN 1.0.3 section 4.3.1: an uplink's FCtrl is ADR,
993        // ADRACKReq, ACK, ClassB, then FOptsLen.
994        let session = session();
995        let frame = session
996            .encode_uplink(&Uplink::new(9, 1, b"x").with_adr().with_adr_ack_req())
997            .unwrap();
998        assert_eq!(frame.as_bytes()[5], 0xC0);
999
1000        let rx = session.decode(frame.as_bytes(), 9).unwrap();
1001        assert!(rx.adr());
1002        assert!(rx.adr_ack_req());
1003        assert!(!rx.class_b());
1004        assert!(!rx.fpending());
1005    }
1006
1007    #[test]
1008    fn a_downlink_reads_bit_six_as_reserved() {
1009        // TS001-1.0.4 table 7: a downlink's bit 6 is RFU.
1010        let frame = session()
1011            .encode_downlink(&Downlink::new(4, 2, b"x"))
1012            .unwrap();
1013        let bytes = resigned(Direction::Downlink, 4, frame.as_bytes(), |bytes| {
1014            bytes[5] |= 0x40;
1015        });
1016        let rx = session().decode(&bytes, 4).unwrap();
1017        assert!(!rx.adr_ack_req());
1018    }
1019
1020    #[test]
1021    fn bit_four_is_class_b_going_up_and_frame_pending_coming_down() {
1022        let frame = session().encode_uplink(&Uplink::new(2, 1, b"x")).unwrap();
1023        let bytes = resigned(Direction::Uplink, 2, frame.as_bytes(), |bytes| {
1024            bytes[5] |= 0x10;
1025        });
1026        let up = session().decode(&bytes, 2).unwrap();
1027        assert!(up.class_b());
1028        assert!(!up.fpending(), "an uplink has no pending bit to report");
1029
1030        let frame = session()
1031            .encode_downlink(&Downlink::new(2, 1, b"x").with_fpending())
1032            .unwrap();
1033        assert_eq!(frame.as_bytes()[5], 0x10);
1034        let down = session().decode(frame.as_bytes(), 2).unwrap();
1035        assert!(down.fpending());
1036        assert!(!down.class_b(), "a downlink has no class B bit to report");
1037    }
1038
1039    #[test]
1040    fn an_empty_frame_carries_no_port_byte() {
1041        let session = session();
1042        let answer = [0x06, 0xFF, 0x0A];
1043        let frame = session
1044            .encode_uplink(&Uplink::empty(5).with_fopts(&answer))
1045            .unwrap();
1046        assert_eq!(
1047            frame.as_bytes().len(),
1048            MIN_FRAME + answer.len(),
1049            "the header, the options and the MIC, with no port"
1050        );
1051
1052        let rx = session.decode(frame.as_bytes(), 5).unwrap();
1053        assert_eq!(rx.fport(), None);
1054        assert_eq!(rx.payload(), b"");
1055        assert_eq!(rx.fopts(), &answer);
1056
1057        let ack = session
1058            .encode_downlink(&Downlink::empty(1).with_ack())
1059            .unwrap();
1060        assert_eq!(
1061            ack.as_bytes().len(),
1062            MIN_FRAME,
1063            "an acknowledgment is all header"
1064        );
1065        let rx = session.decode(ack.as_bytes(), 1).unwrap();
1066        assert!(rx.ack());
1067        assert_eq!(rx.fport(), None);
1068    }
1069
1070    #[test]
1071    fn frame_options_on_port_zero_are_refused_both_ways() {
1072        // TS001-1.0.4 section 4.3.1.6: with FOpts present, FPort SHALL NOT be 0, and a device
1073        // SHALL discard a frame carrying MAC commands in both places.
1074        let session = session();
1075        assert_eq!(
1076            session.encode_uplink(&Uplink::new(1, 0, b"mac").with_fopts(&[0x02])),
1077            Err(LorawanError::MalformedFrame)
1078        );
1079        assert_eq!(
1080            session.encode_downlink(&Downlink::new(1, 0, b"mac").with_fopts(&[0x02])),
1081            Err(LorawanError::MalformedFrame)
1082        );
1083
1084        let frame = session
1085            .encode_downlink(&Downlink::new(6, 1, b"mac").with_fopts(&[0x02]))
1086            .unwrap();
1087        let port = FHDR_LEN + 1;
1088        let bytes = resigned(Direction::Downlink, 6, frame.as_bytes(), |bytes| {
1089            bytes[port] = 0;
1090        });
1091        assert_eq!(session.decode(&bytes, 6), Err(LorawanError::MalformedFrame));
1092
1093        // Port 0 with no options, and options on another port, both still decode.
1094        let mac = session.encode_uplink(&Uplink::new(1, 0, b"mac")).unwrap();
1095        assert!(session.decode(mac.as_bytes(), 1).is_ok());
1096    }
1097}