1use crate::error::LorawanError;
14use crate::frame::{
15 Direction, MTYPE_CONFIRMED_DOWN, MTYPE_CONFIRMED_UP, MTYPE_JOIN_ACCEPT, MTYPE_JOIN_REQUEST,
16 MTYPE_MASK, MTYPE_UNCONFIRMED_DOWN, MTYPE_UNCONFIRMED_UP,
17};
18
19const FHDR_LEN: usize = 8;
21const MIN_DATA_FRAME: usize = FHDR_LEN + 4;
23
24const FCTRL_ADR: u8 = 0x80;
27const FCTRL_ADR_ACK_REQ: u8 = 0x40;
28const FCTRL_ACK: u8 = 0x20;
29const FCTRL_FPENDING: u8 = 0x10;
30const FCTRL_CLASS_B: u8 = 0x10;
31const FCTRL_FOPTS_LEN: u8 = 0x0F;
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum MessageType {
36 JoinRequest,
38 JoinAccept,
40 UnconfirmedUp,
42 ConfirmedUp,
44 UnconfirmedDown,
46 ConfirmedDown,
48}
49
50impl MessageType {
51 pub fn is_data(self) -> bool {
57 !matches!(self, MessageType::JoinRequest | MessageType::JoinAccept)
58 }
59
60 pub fn direction(self) -> Option<Direction> {
67 match self {
68 MessageType::JoinRequest | MessageType::UnconfirmedUp | MessageType::ConfirmedUp => {
69 Some(Direction::Uplink)
70 }
71 MessageType::UnconfirmedDown | MessageType::ConfirmedDown => Some(Direction::Downlink),
72 MessageType::JoinAccept => None,
73 }
74 }
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
100pub struct FrameHeader {
101 message_type: MessageType,
102 dev_addr: Option<u32>,
103 fcnt: Option<u16>,
104 fport: Option<u8>,
105 adr: bool,
106 adr_ack_req: bool,
107 ack: bool,
108 fpending: bool,
109 class_b: bool,
110 fopts_len: usize,
111 payload_len: usize,
112}
113
114impl FrameHeader {
115 pub fn parse(bytes: &[u8]) -> Result<FrameHeader, LorawanError> {
132 if bytes.is_empty() {
133 return Err(LorawanError::FrameTooShort);
134 }
135
136 let message_type = match bytes[0] & MTYPE_MASK {
137 MTYPE_JOIN_REQUEST => MessageType::JoinRequest,
138 MTYPE_JOIN_ACCEPT => MessageType::JoinAccept,
139 MTYPE_UNCONFIRMED_UP => MessageType::UnconfirmedUp,
140 MTYPE_CONFIRMED_UP => MessageType::ConfirmedUp,
141 MTYPE_UNCONFIRMED_DOWN => MessageType::UnconfirmedDown,
142 MTYPE_CONFIRMED_DOWN => MessageType::ConfirmedDown,
143 other => return Err(LorawanError::UnsupportedMType(other)),
144 };
145
146 if !message_type.is_data() {
148 return Ok(FrameHeader {
149 message_type,
150 dev_addr: None,
151 fcnt: None,
152 fport: None,
153 adr: false,
154 adr_ack_req: false,
155 ack: false,
156 fpending: false,
157 class_b: false,
158 fopts_len: 0,
159 payload_len: 0,
160 });
161 }
162
163 if bytes.len() < MIN_DATA_FRAME {
164 return Err(LorawanError::FrameTooShort);
165 }
166
167 let uplink = message_type.direction() == Some(Direction::Uplink);
168 let fctrl = bytes[5];
169 let fopts_len = usize::from(fctrl & FCTRL_FOPTS_LEN);
170 let after_fopts = FHDR_LEN + fopts_len;
171 if bytes.len() < after_fopts + 4 {
172 return Err(LorawanError::MalformedFrame);
173 }
174
175 let remaining = bytes.len() - after_fopts - 4;
177 let (fport, payload_len) = if remaining == 0 {
178 (None, 0)
179 } else {
180 (Some(bytes[after_fopts]), remaining - 1)
181 };
182
183 Ok(FrameHeader {
184 message_type,
185 dev_addr: Some(u32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]])),
186 fcnt: Some(u16::from_le_bytes([bytes[6], bytes[7]])),
187 fport,
188 adr: fctrl & FCTRL_ADR != 0,
189 adr_ack_req: uplink && fctrl & FCTRL_ADR_ACK_REQ != 0,
190 ack: fctrl & FCTRL_ACK != 0,
191 fpending: !uplink && fctrl & FCTRL_FPENDING != 0,
192 class_b: uplink && fctrl & FCTRL_CLASS_B != 0,
193 fopts_len,
194 payload_len,
195 })
196 }
197
198 pub fn message_type(&self) -> MessageType {
204 self.message_type
205 }
206
207 pub fn direction(&self) -> Option<Direction> {
213 self.message_type.direction()
214 }
215
216 pub fn dev_addr(&self) -> Option<u32> {
224 self.dev_addr
225 }
226
227 pub fn fcnt(&self) -> Option<u16> {
233 self.fcnt
234 }
235
236 pub fn fport(&self) -> Option<u8> {
243 self.fport
244 }
245
246 pub fn confirmed(&self) -> bool {
252 matches!(
253 self.message_type,
254 MessageType::ConfirmedUp | MessageType::ConfirmedDown
255 )
256 }
257
258 pub fn adr(&self) -> bool {
264 self.adr
265 }
266
267 pub fn adr_ack_req(&self) -> bool {
274 self.adr_ack_req
275 }
276
277 pub fn ack(&self) -> bool {
283 self.ack
284 }
285
286 pub fn fpending(&self) -> bool {
293 self.fpending
294 }
295
296 pub fn class_b(&self) -> bool {
302 self.class_b
303 }
304
305 pub fn fopts_len(&self) -> usize {
311 self.fopts_len
312 }
313
314 pub fn payload_len(&self) -> usize {
320 self.payload_len
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::{Device, Downlink, JoinGrant, Session, Uplink};
328
329 const NWK_SKEY: [u8; 16] = [0x2B; 16];
330 const APP_SKEY: [u8; 16] = [0x99; 16];
331 const DEV_ADDR: u32 = 0x2601_1BDA;
332
333 fn session() -> Session {
334 Session::new(DEV_ADDR, NWK_SKEY, APP_SKEY)
335 }
336
337 #[test]
338 fn an_uplink_reports_the_address_a_receiver_routes_by() {
339 let frame = session()
340 .encode_uplink(&Uplink::new(42, 1, b"temp=4.8").confirmed().with_adr())
341 .unwrap();
342 let header = FrameHeader::parse(frame.as_bytes()).unwrap();
343
344 assert_eq!(header.message_type(), MessageType::ConfirmedUp);
345 assert_eq!(header.direction(), Some(Direction::Uplink));
346 assert_eq!(header.dev_addr(), Some(DEV_ADDR));
347 assert_eq!(header.fcnt(), Some(42));
348 assert_eq!(header.fport(), Some(1));
349 assert!(header.confirmed());
350 assert!(header.adr());
351 assert!(!header.ack());
352 assert_eq!(header.fopts_len(), 0);
353 assert_eq!(header.payload_len(), 8);
354 }
355
356 #[test]
357 fn a_downlink_reports_its_options_and_pending_flag() {
358 let fopts = [0x03u8, 0x50, 0x00];
359 let frame = session()
360 .encode_downlink(
361 &Downlink::new(7, 2, b"ack")
362 .with_fpending()
363 .with_fopts(&fopts),
364 )
365 .unwrap();
366 let header = FrameHeader::parse(frame.as_bytes()).unwrap();
367
368 assert_eq!(header.message_type(), MessageType::UnconfirmedDown);
369 assert_eq!(header.direction(), Some(Direction::Downlink));
370 assert!(header.fpending());
371 assert_eq!(header.fopts_len(), fopts.len());
372 assert_eq!(header.fport(), Some(2));
373 assert_eq!(header.payload_len(), 3);
374 }
375
376 #[test]
377 fn a_frame_carrying_only_options_has_no_port() {
378 let fopts = [0x02u8, 0x01];
379 let frame = session()
380 .encode_uplink(&Uplink::empty(1).with_fopts(&fopts))
381 .unwrap();
382 let header = FrameHeader::parse(frame.as_bytes()).unwrap();
383
384 assert_eq!(header.fport(), None);
385 assert_eq!(header.fopts_len(), fopts.len());
386 assert_eq!(header.payload_len(), 0);
387 }
388
389 #[test]
390 fn the_direction_decides_what_bits_six_and_four_mean() {
391 let up = session()
392 .encode_uplink(&Uplink::new(3, 1, b"x").with_adr_ack_req())
393 .unwrap();
394 let header = FrameHeader::parse(up.as_bytes()).unwrap();
395 assert!(header.adr_ack_req());
396 assert!(!header.fpending());
397
398 let mut all = up.as_bytes().to_vec();
400 all[5] |= 0xF0;
401 let header = FrameHeader::parse(&all).unwrap();
402 assert!(header.adr() && header.adr_ack_req() && header.ack() && header.class_b());
403 assert!(!header.fpending());
404
405 let down = session()
407 .encode_downlink(&Downlink::new(3, 1, b"x"))
408 .unwrap();
409 let mut all = down.as_bytes().to_vec();
410 all[5] |= 0xF0;
411 let header = FrameHeader::parse(&all).unwrap();
412 assert!(header.adr() && header.ack() && header.fpending());
413 assert!(!header.adr_ack_req());
414 assert!(!header.class_b());
415 }
416
417 #[test]
418 fn the_join_frames_report_their_type_and_nothing_else() {
419 let device = Device::new([0x11; 8], [0x22; 8], [0xAB; 16]);
420 let request = FrameHeader::parse(device.join_request(0x1234).as_bytes()).unwrap();
421 assert_eq!(request.message_type(), MessageType::JoinRequest);
422 assert!(!request.message_type().is_data());
423 assert_eq!(request.dev_addr(), None);
424 assert_eq!(request.fcnt(), None);
425 assert_eq!(request.direction(), Some(Direction::Uplink));
426
427 let grant = JoinGrant::new(0x0003_0201, 0x0006_0504, DEV_ADDR);
428 let accept = FrameHeader::parse(grant.accept(&[0xAB; 16], 0x1234).as_bytes()).unwrap();
429 assert_eq!(accept.message_type(), MessageType::JoinAccept);
430 assert_eq!(
431 accept.direction(),
432 None,
433 "a join-accept carries no direction bit"
434 );
435 }
436
437 #[test]
438 fn the_header_a_gateway_reads_agrees_with_the_decoded_frame() {
439 let session = session();
440 let frame = session
441 .encode_uplink(&Uplink::new(9, 3, b"reading").with_ack())
442 .unwrap();
443 let header = FrameHeader::parse(frame.as_bytes()).unwrap();
444 let rx = session.decode(frame.as_bytes(), 9).unwrap();
445
446 assert_eq!(header.dev_addr(), Some(rx.dev_addr()));
447 assert_eq!(header.fcnt(), Some(rx.fcnt()));
448 assert_eq!(header.fport(), rx.fport());
449 assert_eq!(header.ack(), rx.ack());
450 assert_eq!(header.payload_len(), rx.payload().len());
451 }
452
453 #[test]
454 fn a_truncated_frame_is_refused() {
455 assert_eq!(FrameHeader::parse(&[]), Err(LorawanError::FrameTooShort));
456 assert_eq!(
457 FrameHeader::parse(&[0x40, 0x01, 0x02]),
458 Err(LorawanError::FrameTooShort)
459 );
460 }
461
462 #[test]
463 fn frame_options_running_past_the_end_are_malformed() {
464 let mut frame = [0u8; MIN_DATA_FRAME];
466 frame[0] = MTYPE_UNCONFIRMED_UP;
467 frame[5] = 0x0F;
468 assert_eq!(
469 FrameHeader::parse(&frame),
470 Err(LorawanError::MalformedFrame)
471 );
472 }
473
474 #[test]
475 fn a_message_type_this_crate_does_not_read_is_reported() {
476 assert_eq!(
478 FrameHeader::parse(&[0xC0; 16]),
479 Err(LorawanError::UnsupportedMType(0xC0))
480 );
481 }
482}