1use 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
10const FHDR_LEN: usize = 8;
12const MIN_FRAME: usize = FHDR_LEN + 4;
14
15const FCTRL_ADR: u8 = 0x80;
17const FCTRL_ACK: u8 = 0x20;
18const FCTRL_FPENDING: u8 = 0x10;
19const FCTRL_FOPTS_LEN: u8 = 0x0F;
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct Session {
43 dev_addr: u32,
44 nwk_skey: [u8; 16],
45 app_skey: [u8; 16],
46}
47
48impl Session {
49 pub fn new(dev_addr: u32, nwk_skey: [u8; 16], app_skey: [u8; 16]) -> Self {
61 Session {
62 dev_addr,
63 nwk_skey,
64 app_skey,
65 }
66 }
67
68 pub fn dev_addr(&self) -> u32 {
74 self.dev_addr
75 }
76
77 pub fn encode_uplink(&self, uplink: &Uplink) -> Result<PhyPayload, LorawanError> {
92 let mtype = if uplink.confirmed {
93 MTYPE_CONFIRMED_UP
94 } else {
95 MTYPE_UNCONFIRMED_UP
96 };
97 let mut fctrl = 0;
98 if uplink.adr {
99 fctrl |= FCTRL_ADR;
100 }
101 if uplink.ack {
102 fctrl |= FCTRL_ACK;
103 }
104 self.encode(
105 Direction::Uplink,
106 mtype,
107 fctrl,
108 uplink.fcnt,
109 uplink.fport,
110 uplink.fopts,
111 uplink.payload,
112 )
113 }
114
115 pub fn encode_downlink(&self, downlink: &Downlink) -> Result<PhyPayload, LorawanError> {
130 let mtype = if downlink.confirmed {
131 MTYPE_CONFIRMED_DOWN
132 } else {
133 MTYPE_UNCONFIRMED_DOWN
134 };
135 let mut fctrl = 0;
136 if downlink.adr {
137 fctrl |= FCTRL_ADR;
138 }
139 if downlink.ack {
140 fctrl |= FCTRL_ACK;
141 }
142 if downlink.fpending {
143 fctrl |= FCTRL_FPENDING;
144 }
145 self.encode(
146 Direction::Downlink,
147 mtype,
148 fctrl,
149 downlink.fcnt,
150 downlink.fport,
151 downlink.fopts,
152 downlink.payload,
153 )
154 }
155
156 #[allow(clippy::too_many_arguments)]
157 fn encode(
158 &self,
159 direction: Direction,
160 mtype: u8,
161 fctrl: u8,
162 fcnt: u32,
163 fport: u8,
164 fopts: &[u8],
165 payload: &[u8],
166 ) -> Result<PhyPayload, LorawanError> {
167 if fopts.len() > usize::from(FCTRL_FOPTS_LEN) {
168 return Err(LorawanError::PayloadTooLong);
169 }
170 let len = MIN_FRAME + fopts.len() + 1 + payload.len();
171 if len > MAX_FRAME {
172 return Err(LorawanError::PayloadTooLong);
173 }
174
175 let mut buf = [0u8; MAX_FRAME];
176 buf[0] = mtype;
177 buf[1..5].copy_from_slice(&self.dev_addr.to_le_bytes());
178 buf[5] = fctrl | (fopts.len() as u8);
179 buf[6..8].copy_from_slice(&(fcnt as u16).to_le_bytes());
180 let mut at = FHDR_LEN;
181 buf[at..at + fopts.len()].copy_from_slice(fopts);
182 at += fopts.len();
183 buf[at] = fport;
184 at += 1;
185
186 let key = self.payload_key(fport);
187 crypt_payload(
188 key,
189 self.dev_addr,
190 direction,
191 fcnt,
192 payload,
193 &mut buf[at..at + payload.len()],
194 );
195 at += payload.len();
196
197 let mic = self.mic(direction, fcnt, &buf[..at]);
198 buf[at..at + 4].copy_from_slice(&mic);
199 at += 4;
200
201 PhyPayload::new(&buf[..at])
202 }
203
204 pub fn decode(&self, bytes: &[u8], fcnt: u32) -> Result<RxData, LorawanError> {
223 if bytes.len() < MIN_FRAME {
224 return Err(LorawanError::FrameTooShort);
225 }
226 let mtype = bytes[0] & MTYPE_MASK;
227 let (direction, confirmed) = match mtype {
228 MTYPE_UNCONFIRMED_UP => (Direction::Uplink, false),
229 MTYPE_CONFIRMED_UP => (Direction::Uplink, true),
230 MTYPE_UNCONFIRMED_DOWN => (Direction::Downlink, false),
231 MTYPE_CONFIRMED_DOWN => (Direction::Downlink, true),
232 other => return Err(LorawanError::UnsupportedMType(other)),
233 };
234
235 let dev_addr = u32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
236 let fctrl = bytes[5];
237 let fopts_len = usize::from(fctrl & FCTRL_FOPTS_LEN);
238 let fcnt_low = u16::from_le_bytes([bytes[6], bytes[7]]);
239 if fcnt as u16 != fcnt_low {
240 return Err(LorawanError::FcntMismatch);
241 }
242
243 let mic_start = bytes.len() - 4;
244 let body_start = FHDR_LEN + fopts_len;
245 if mic_start < body_start {
246 return Err(LorawanError::FrameTooShort);
247 }
248 let expected = self.mic(direction, fcnt, &bytes[..mic_start]);
249 if bytes[mic_start..] != expected[..] {
250 return Err(LorawanError::MicMismatch);
251 }
252
253 let mut fopts = [0u8; FCTRL_FOPTS_LEN as usize];
254 fopts[..fopts_len].copy_from_slice(&bytes[FHDR_LEN..FHDR_LEN + fopts_len]);
255
256 let mut payload = [0u8; MAX_PAYLOAD];
257 let (fport, payload_len) = if mic_start > body_start {
258 let fport = bytes[body_start];
259 let encrypted = &bytes[body_start + 1..mic_start];
260 let key = self.payload_key(fport);
261 crypt_payload(
262 key,
263 dev_addr,
264 direction,
265 fcnt,
266 encrypted,
267 &mut payload[..encrypted.len()],
268 );
269 (Some(fport), encrypted.len())
270 } else {
271 (None, 0)
272 };
273
274 Ok(RxData {
275 direction,
276 dev_addr,
277 fcnt_low,
278 confirmed,
279 adr: fctrl & FCTRL_ADR != 0,
280 ack: fctrl & FCTRL_ACK != 0,
281 fpending: fctrl & FCTRL_FPENDING != 0,
282 fport,
283 fopts,
284 fopts_len,
285 payload,
286 payload_len,
287 })
288 }
289
290 fn payload_key(&self, fport: u8) -> &[u8; 16] {
293 if fport == 0 {
294 &self.nwk_skey
295 } else {
296 &self.app_skey
297 }
298 }
299
300 fn mic(&self, direction: Direction, fcnt: u32, msg: &[u8]) -> [u8; 4] {
302 let mut block = [0u8; 16 + MAX_FRAME];
303 block[0] = 0x49;
304 block[5] = direction.bit();
305 block[6..10].copy_from_slice(&self.dev_addr.to_le_bytes());
306 block[10..14].copy_from_slice(&fcnt.to_le_bytes());
307 block[15] = msg.len() as u8;
308 block[16..16 + msg.len()].copy_from_slice(msg);
309 let tag = Cipher::new(&self.nwk_skey).cmac(&block[..16 + msg.len()]);
310 [tag[0], tag[1], tag[2], tag[3]]
311 }
312}
313
314fn crypt_payload(
317 key: &[u8; 16],
318 dev_addr: u32,
319 direction: Direction,
320 fcnt: u32,
321 input: &[u8],
322 output: &mut [u8],
323) {
324 let cipher = Cipher::new(key);
325 let blocks = input.len().div_ceil(16);
326 for i in 0..blocks {
327 let mut a = [0u8; 16];
328 a[0] = 0x01;
329 a[5] = direction.bit();
330 a[6..10].copy_from_slice(&dev_addr.to_le_bytes());
331 a[10..14].copy_from_slice(&fcnt.to_le_bytes());
332 a[15] = (i + 1) as u8;
333 let stream = cipher.encrypt_block(&a);
334
335 let start = i * 16;
336 let end = (start + 16).min(input.len());
337 for j in start..end {
338 output[j] = input[j] ^ stream[j - start];
339 }
340 }
341}
342
343#[derive(Clone, Copy, Debug)]
356pub struct Uplink<'a> {
357 fcnt: u32,
358 fport: u8,
359 payload: &'a [u8],
360 confirmed: bool,
361 adr: bool,
362 ack: bool,
363 fopts: &'a [u8],
364}
365
366impl<'a> Uplink<'a> {
367 pub fn new(fcnt: u32, fport: u8, payload: &'a [u8]) -> Self {
379 Uplink {
380 fcnt,
381 fport,
382 payload,
383 confirmed: false,
384 adr: false,
385 ack: false,
386 fopts: &[],
387 }
388 }
389
390 pub fn confirmed(mut self) -> Self {
396 self.confirmed = true;
397 self
398 }
399
400 pub fn with_adr(mut self) -> Self {
406 self.adr = true;
407 self
408 }
409
410 pub fn with_ack(mut self) -> Self {
416 self.ack = true;
417 self
418 }
419
420 pub fn with_fopts(mut self, fopts: &'a [u8]) -> Self {
430 self.fopts = fopts;
431 self
432 }
433}
434
435#[derive(Clone, Copy, Debug)]
440pub struct Downlink<'a> {
441 fcnt: u32,
442 fport: u8,
443 payload: &'a [u8],
444 confirmed: bool,
445 adr: bool,
446 ack: bool,
447 fpending: bool,
448 fopts: &'a [u8],
449}
450
451impl<'a> Downlink<'a> {
452 pub fn new(fcnt: u32, fport: u8, payload: &'a [u8]) -> Self {
464 Downlink {
465 fcnt,
466 fport,
467 payload,
468 confirmed: false,
469 adr: false,
470 ack: false,
471 fpending: false,
472 fopts: &[],
473 }
474 }
475
476 pub fn confirmed(mut self) -> Self {
482 self.confirmed = true;
483 self
484 }
485
486 pub fn with_adr(mut self) -> Self {
492 self.adr = true;
493 self
494 }
495
496 pub fn with_ack(mut self) -> Self {
502 self.ack = true;
503 self
504 }
505
506 pub fn with_fpending(mut self) -> Self {
512 self.fpending = true;
513 self
514 }
515
516 pub fn with_fopts(mut self, fopts: &'a [u8]) -> Self {
526 self.fopts = fopts;
527 self
528 }
529}
530
531#[derive(Clone, Copy, Debug, PartialEq, Eq)]
536pub struct RxData {
537 direction: Direction,
538 dev_addr: u32,
539 fcnt_low: u16,
540 confirmed: bool,
541 adr: bool,
542 ack: bool,
543 fpending: bool,
544 fport: Option<u8>,
545 fopts: [u8; FCTRL_FOPTS_LEN as usize],
546 fopts_len: usize,
547 payload: [u8; MAX_PAYLOAD],
548 payload_len: usize,
549}
550
551impl RxData {
552 pub fn direction(&self) -> Direction {
558 self.direction
559 }
560
561 pub fn dev_addr(&self) -> u32 {
567 self.dev_addr
568 }
569
570 pub fn fcnt(&self) -> u16 {
576 self.fcnt_low
577 }
578
579 pub fn confirmed(&self) -> bool {
585 self.confirmed
586 }
587
588 pub fn adr(&self) -> bool {
594 self.adr
595 }
596
597 pub fn ack(&self) -> bool {
603 self.ack
604 }
605
606 pub fn fpending(&self) -> bool {
612 self.fpending
613 }
614
615 pub fn fport(&self) -> Option<u8> {
621 self.fport
622 }
623
624 pub fn fopts(&self) -> &[u8] {
630 &self.fopts[..self.fopts_len]
631 }
632
633 pub fn payload(&self) -> &[u8] {
639 &self.payload[..self.payload_len]
640 }
641}
642
643#[cfg(test)]
644mod tests {
645 use super::*;
646
647 const NWK_SKEY: [u8; 16] = [0x01; 16];
648 const APP_SKEY: [u8; 16] = [0x02; 16];
649 const DEV_ADDR: u32 = 0x2601_1BDA;
650
651 fn session() -> Session {
652 Session::new(DEV_ADDR, NWK_SKEY, APP_SKEY)
653 }
654
655 #[test]
656 fn an_uplink_round_trips() {
657 let session = session();
658 let frame = session
659 .encode_uplink(&Uplink::new(10, 1, b"temperature"))
660 .unwrap();
661 let rx = session.decode(frame.as_bytes(), 10).unwrap();
662 assert_eq!(rx.direction(), Direction::Uplink);
663 assert_eq!(rx.dev_addr(), DEV_ADDR);
664 assert_eq!(rx.fcnt(), 10);
665 assert_eq!(rx.fport(), Some(1));
666 assert_eq!(rx.payload(), b"temperature");
667 assert!(!rx.confirmed());
668 }
669
670 #[test]
671 fn the_payload_is_encrypted_on_the_wire() {
672 let session = session();
673 let frame = session
674 .encode_uplink(&Uplink::new(1, 1, b"secret"))
675 .unwrap();
676 assert!(frame
678 .as_bytes()
679 .windows(b"secret".len())
680 .all(|window| window != b"secret"));
681 }
682
683 #[test]
684 fn the_header_is_laid_out_as_the_spec_requires() {
685 let session = session();
686 let frame = session
687 .encode_uplink(&Uplink::new(0x0102, 1, b"x"))
688 .unwrap();
689 let bytes = frame.as_bytes();
690 assert_eq!(bytes[0], MTYPE_UNCONFIRMED_UP);
691 assert_eq!(&bytes[1..5], &DEV_ADDR.to_le_bytes());
693 assert_eq!(&bytes[6..8], &0x0102u16.to_le_bytes());
695 }
696
697 #[test]
698 fn a_confirmed_downlink_round_trips_with_its_flags() {
699 let session = session();
700 let frame = session
701 .encode_downlink(&Downlink::new(5, 2, b"cmd").confirmed().with_fpending())
702 .unwrap();
703 let rx = session.decode(frame.as_bytes(), 5).unwrap();
704 assert_eq!(rx.direction(), Direction::Downlink);
705 assert!(rx.confirmed());
706 assert!(rx.fpending());
707 assert_eq!(rx.payload(), b"cmd");
708 }
709
710 #[test]
711 fn frame_options_round_trip() {
712 let session = session();
713 let frame = session
714 .encode_uplink(&Uplink::new(3, 1, b"d").with_fopts(&[0x02, 0x03]))
715 .unwrap();
716 let rx = session.decode(frame.as_bytes(), 3).unwrap();
717 assert_eq!(rx.fopts(), &[0x02, 0x03]);
718 assert_eq!(rx.payload(), b"d");
719 }
720
721 #[test]
722 fn an_empty_payload_round_trips() {
723 let session = session();
724 let frame = session.encode_uplink(&Uplink::new(1, 1, b"")).unwrap();
725 let rx = session.decode(frame.as_bytes(), 1).unwrap();
726 assert_eq!(rx.payload(), b"");
727 assert_eq!(rx.fport(), Some(1));
728 }
729
730 #[test]
731 fn a_tampered_payload_fails_the_mic() {
732 let session = session();
733 let frame = session.encode_uplink(&Uplink::new(1, 1, b"data")).unwrap();
734 let mut bytes = frame.as_bytes().to_vec();
735 let last = bytes.len() - 5; bytes[last] ^= 0xff;
737 assert_eq!(session.decode(&bytes, 1), Err(LorawanError::MicMismatch));
738 }
739
740 #[test]
741 fn the_wrong_counter_is_rejected() {
742 let session = session();
743 let frame = session.encode_uplink(&Uplink::new(7, 1, b"data")).unwrap();
744 assert_eq!(
745 session.decode(frame.as_bytes(), 8),
746 Err(LorawanError::FcntMismatch)
747 );
748 }
749
750 #[test]
751 fn a_join_frame_is_not_decoded_here() {
752 let session = session();
753 let bytes = [0u8; MIN_FRAME];
755 assert_eq!(
756 session.decode(&bytes, 0),
757 Err(LorawanError::UnsupportedMType(0x00))
758 );
759 }
760
761 #[test]
762 fn a_short_frame_is_rejected() {
763 let session = session();
764 assert_eq!(
765 session.decode(&[0x40, 0x00, 0x00], 0),
766 Err(LorawanError::FrameTooShort)
767 );
768 }
769
770 #[test]
771 fn port_zero_uses_the_network_key() {
772 let session = Session::new(DEV_ADDR, NWK_SKEY, APP_SKEY);
775 let frame = session.encode_uplink(&Uplink::new(1, 0, b"mac")).unwrap();
776 let other = Session::new(DEV_ADDR, NWK_SKEY, [0x33; 16]);
777 let rx = other.decode(frame.as_bytes(), 1).unwrap();
778 assert_eq!(rx.payload(), b"mac");
779 }
780
781 #[test]
782 fn the_largest_payload_round_trips() {
783 let session = session();
784 let payload = [0xAB; MAX_PAYLOAD];
785 let frame = session.encode_uplink(&Uplink::new(1, 1, &payload)).unwrap();
786 assert_eq!(frame.as_bytes().len(), crate::MAX_FRAME);
787 let rx = session.decode(frame.as_bytes(), 1).unwrap();
788 assert_eq!(rx.payload(), &payload[..]);
789 }
790
791 #[test]
792 fn the_full_frame_counter_is_bound_into_the_mic() {
793 let session = session();
794 let frame = session
797 .encode_uplink(&Uplink::new(0x0001_0001, 1, b"x"))
798 .unwrap();
799 assert_eq!(
801 session.decode(frame.as_bytes(), 0x0000_0001),
802 Err(LorawanError::MicMismatch)
803 );
804 let rx = session.decode(frame.as_bytes(), 0x0001_0001).unwrap();
806 assert_eq!(rx.fcnt(), 0x0001);
807 }
808
809 #[test]
810 fn another_sessions_keys_cannot_read_a_frame() {
811 let session = session();
812 let frame = session
813 .encode_uplink(&Uplink::new(1, 1, b"secret"))
814 .unwrap();
815 let stranger = Session::new(DEV_ADDR, [0xAA; 16], [0xBB; 16]);
816 assert_eq!(
817 stranger.decode(frame.as_bytes(), 1),
818 Err(LorawanError::MicMismatch)
819 );
820 }
821}