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;
26const FCTRL_ACK: u8 = 0x20;
27const FCTRL_FPENDING: u8 = 0x10;
28const FCTRL_FOPTS_LEN: u8 = 0x0F;
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub enum MessageType {
33 JoinRequest,
35 JoinAccept,
37 UnconfirmedUp,
39 ConfirmedUp,
41 UnconfirmedDown,
43 ConfirmedDown,
45}
46
47impl MessageType {
48 pub fn is_data(self) -> bool {
54 !matches!(self, MessageType::JoinRequest | MessageType::JoinAccept)
55 }
56
57 pub fn direction(self) -> Option<Direction> {
64 match self {
65 MessageType::JoinRequest | MessageType::UnconfirmedUp | MessageType::ConfirmedUp => {
66 Some(Direction::Uplink)
67 }
68 MessageType::UnconfirmedDown | MessageType::ConfirmedDown => Some(Direction::Downlink),
69 MessageType::JoinAccept => None,
70 }
71 }
72}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub struct FrameHeader {
98 message_type: MessageType,
99 dev_addr: Option<u32>,
100 fcnt: Option<u16>,
101 fport: Option<u8>,
102 adr: bool,
103 ack: bool,
104 fpending: bool,
105 fopts_len: usize,
106 payload_len: usize,
107}
108
109impl FrameHeader {
110 pub fn parse(bytes: &[u8]) -> Result<FrameHeader, LorawanError> {
127 if bytes.is_empty() {
128 return Err(LorawanError::FrameTooShort);
129 }
130
131 let message_type = match bytes[0] & MTYPE_MASK {
132 MTYPE_JOIN_REQUEST => MessageType::JoinRequest,
133 MTYPE_JOIN_ACCEPT => MessageType::JoinAccept,
134 MTYPE_UNCONFIRMED_UP => MessageType::UnconfirmedUp,
135 MTYPE_CONFIRMED_UP => MessageType::ConfirmedUp,
136 MTYPE_UNCONFIRMED_DOWN => MessageType::UnconfirmedDown,
137 MTYPE_CONFIRMED_DOWN => MessageType::ConfirmedDown,
138 other => return Err(LorawanError::UnsupportedMType(other)),
139 };
140
141 if !message_type.is_data() {
143 return Ok(FrameHeader {
144 message_type,
145 dev_addr: None,
146 fcnt: None,
147 fport: None,
148 adr: false,
149 ack: false,
150 fpending: false,
151 fopts_len: 0,
152 payload_len: 0,
153 });
154 }
155
156 if bytes.len() < MIN_DATA_FRAME {
157 return Err(LorawanError::FrameTooShort);
158 }
159
160 let fctrl = bytes[5];
161 let fopts_len = usize::from(fctrl & FCTRL_FOPTS_LEN);
162 let after_fopts = FHDR_LEN + fopts_len;
163 if bytes.len() < after_fopts + 4 {
164 return Err(LorawanError::MalformedFrame);
165 }
166
167 let remaining = bytes.len() - after_fopts - 4;
169 let (fport, payload_len) = if remaining == 0 {
170 (None, 0)
171 } else {
172 (Some(bytes[after_fopts]), remaining - 1)
173 };
174
175 Ok(FrameHeader {
176 message_type,
177 dev_addr: Some(u32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]])),
178 fcnt: Some(u16::from_le_bytes([bytes[6], bytes[7]])),
179 fport,
180 adr: fctrl & FCTRL_ADR != 0,
181 ack: fctrl & FCTRL_ACK != 0,
182 fpending: fctrl & FCTRL_FPENDING != 0,
183 fopts_len,
184 payload_len,
185 })
186 }
187
188 pub fn message_type(&self) -> MessageType {
194 self.message_type
195 }
196
197 pub fn direction(&self) -> Option<Direction> {
203 self.message_type.direction()
204 }
205
206 pub fn dev_addr(&self) -> Option<u32> {
214 self.dev_addr
215 }
216
217 pub fn fcnt(&self) -> Option<u16> {
223 self.fcnt
224 }
225
226 pub fn fport(&self) -> Option<u8> {
233 self.fport
234 }
235
236 pub fn confirmed(&self) -> bool {
242 matches!(
243 self.message_type,
244 MessageType::ConfirmedUp | MessageType::ConfirmedDown
245 )
246 }
247
248 pub fn adr(&self) -> bool {
254 self.adr
255 }
256
257 pub fn ack(&self) -> bool {
263 self.ack
264 }
265
266 pub fn fpending(&self) -> bool {
272 self.fpending
273 }
274
275 pub fn fopts_len(&self) -> usize {
281 self.fopts_len
282 }
283
284 pub fn payload_len(&self) -> usize {
290 self.payload_len
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use crate::{Device, Downlink, JoinGrant, Session, Uplink};
298
299 const NWK_SKEY: [u8; 16] = [0x2B; 16];
300 const APP_SKEY: [u8; 16] = [0x99; 16];
301 const DEV_ADDR: u32 = 0x2601_1BDA;
302
303 fn session() -> Session {
304 Session::new(DEV_ADDR, NWK_SKEY, APP_SKEY)
305 }
306
307 #[test]
308 fn an_uplink_reports_the_address_a_receiver_routes_by() {
309 let frame = session()
310 .encode_uplink(&Uplink::new(42, 1, b"temp=4.8").confirmed().with_adr())
311 .unwrap();
312 let header = FrameHeader::parse(frame.as_bytes()).unwrap();
313
314 assert_eq!(header.message_type(), MessageType::ConfirmedUp);
315 assert_eq!(header.direction(), Some(Direction::Uplink));
316 assert_eq!(header.dev_addr(), Some(DEV_ADDR));
317 assert_eq!(header.fcnt(), Some(42));
318 assert_eq!(header.fport(), Some(1));
319 assert!(header.confirmed());
320 assert!(header.adr());
321 assert!(!header.ack());
322 assert_eq!(header.fopts_len(), 0);
323 assert_eq!(header.payload_len(), 8);
324 }
325
326 #[test]
327 fn a_downlink_reports_its_options_and_pending_flag() {
328 let fopts = [0x03u8, 0x50, 0x00];
329 let frame = session()
330 .encode_downlink(
331 &Downlink::new(7, 2, b"ack")
332 .with_fpending()
333 .with_fopts(&fopts),
334 )
335 .unwrap();
336 let header = FrameHeader::parse(frame.as_bytes()).unwrap();
337
338 assert_eq!(header.message_type(), MessageType::UnconfirmedDown);
339 assert_eq!(header.direction(), Some(Direction::Downlink));
340 assert!(header.fpending());
341 assert_eq!(header.fopts_len(), fopts.len());
342 assert_eq!(header.fport(), Some(2));
343 assert_eq!(header.payload_len(), 3);
344 }
345
346 #[test]
347 fn a_frame_carrying_only_options_has_no_port() {
348 let fopts = [0x02u8, 0x01];
349 let frame = session()
350 .encode_uplink(&Uplink::new(1, 0, b"").with_fopts(&fopts))
351 .unwrap();
352 let header = FrameHeader::parse(frame.as_bytes()).unwrap();
353
354 assert_eq!(header.fopts_len(), fopts.len());
355 assert_eq!(header.payload_len(), 0);
356 }
357
358 #[test]
359 fn the_join_frames_report_their_type_and_nothing_else() {
360 let device = Device::new([0x11; 8], [0x22; 8], [0xAB; 16]);
361 let request = FrameHeader::parse(device.join_request(0x1234).as_bytes()).unwrap();
362 assert_eq!(request.message_type(), MessageType::JoinRequest);
363 assert!(!request.message_type().is_data());
364 assert_eq!(request.dev_addr(), None);
365 assert_eq!(request.fcnt(), None);
366 assert_eq!(request.direction(), Some(Direction::Uplink));
367
368 let grant = JoinGrant::new(0x0003_0201, 0x0006_0504, DEV_ADDR);
369 let accept = FrameHeader::parse(grant.accept(&[0xAB; 16], 0x1234).as_bytes()).unwrap();
370 assert_eq!(accept.message_type(), MessageType::JoinAccept);
371 assert_eq!(
372 accept.direction(),
373 None,
374 "a join-accept carries no direction bit"
375 );
376 }
377
378 #[test]
379 fn the_header_a_gateway_reads_agrees_with_the_decoded_frame() {
380 let session = session();
381 let frame = session
382 .encode_uplink(&Uplink::new(9, 3, b"reading").with_ack())
383 .unwrap();
384 let header = FrameHeader::parse(frame.as_bytes()).unwrap();
385 let rx = session.decode(frame.as_bytes(), 9).unwrap();
386
387 assert_eq!(header.dev_addr(), Some(rx.dev_addr()));
388 assert_eq!(header.fcnt(), Some(rx.fcnt()));
389 assert_eq!(header.fport(), rx.fport());
390 assert_eq!(header.ack(), rx.ack());
391 assert_eq!(header.payload_len(), rx.payload().len());
392 }
393
394 #[test]
395 fn a_truncated_frame_is_refused() {
396 assert_eq!(FrameHeader::parse(&[]), Err(LorawanError::FrameTooShort));
397 assert_eq!(
398 FrameHeader::parse(&[0x40, 0x01, 0x02]),
399 Err(LorawanError::FrameTooShort)
400 );
401 }
402
403 #[test]
404 fn frame_options_running_past_the_end_are_malformed() {
405 let mut frame = [0u8; MIN_DATA_FRAME];
407 frame[0] = MTYPE_UNCONFIRMED_UP;
408 frame[5] = 0x0F;
409 assert_eq!(
410 FrameHeader::parse(&frame),
411 Err(LorawanError::MalformedFrame)
412 );
413 }
414
415 #[test]
416 fn a_message_type_this_crate_does_not_read_is_reported() {
417 assert_eq!(
419 FrameHeader::parse(&[0xC0; 16]),
420 Err(LorawanError::UnsupportedMType(0xC0))
421 );
422 }
423}