use anyhow::Result; #[cfg(feature = "serde")] use serde::{Serialize, Serializer}; use crate::cflist::CFList; use crate::devaddr::DevAddr; use crate::dl_settings::DLSettings; use crate::eui64::EUI64; use crate::fhdr::FCtrl; use crate::fhdr::FHDR; use crate::maccommand::MACCommandSet; use crate::mhdr::MType; use crate::netid::NetID; use crate::relay::{ForwardDownlinkReq, ForwardUplinkReq}; #[derive(PartialEq, Eq, Debug, Clone)] pub enum Payload { JoinRequest(JoinRequestPayload), JoinAccept(JoinAcceptPayload), MACPayload(MACPayload), RejoinRequestType02(RejoinRequestType02Payload), RejoinRequestType1(RejoinRequestType1Payload), Raw(Vec), } #[cfg(feature = "serde")] impl Serialize for Payload { fn serialize(&self, serializer: S) -> Result where S: Serializer, { match self { Payload::JoinRequest(v) => v.serialize(serializer), Payload::JoinAccept(v) => v.serialize(serializer), Payload::MACPayload(v) => v.serialize(serializer), Payload::RejoinRequestType02(v) => v.serialize(serializer), Payload::RejoinRequestType1(v) => v.serialize(serializer), Payload::Raw(v) => serializer.serialize_str(&hex::encode(v)), } } } #[derive(PartialEq, Eq, Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub enum JoinType { Join, RejoinType0, RejoinType1, RejoinType2, } impl Payload { pub fn from_slice(m_type: MType, b: &[u8]) -> Result { Ok(match m_type { MType::JoinRequest => Payload::JoinRequest(JoinRequestPayload::from_slice(b)?), MType::JoinAccept => Payload::Raw(b.to_vec()), // the join-accept is encrypted MType::UnconfirmedDataUp | MType::ConfirmedDataUp | MType::UnconfirmedDataDown | MType::ConfirmedDataDown => Payload::MACPayload(MACPayload::from_slice(b)?), MType::RejoinRequest => { if b.is_empty() { return Err(anyhow!("RejoinRequest payload is empty")); } match b[0] { 0x00 | 0x02 => { Payload::RejoinRequestType02(RejoinRequestType02Payload::from_slice(b)?) } 0x01 => Payload::RejoinRequestType1(RejoinRequestType1Payload::from_slice(b)?), _ => { return Err(anyhow!("invalid RejoinType")); } } } MType::Proprietary => Payload::Raw(b.to_vec()), }) } pub fn to_vec(&self) -> Result> { match self { Payload::JoinRequest(v) => Ok(v.to_vec()), Payload::JoinAccept(v) => v.to_vec(), Payload::MACPayload(v) => Ok(v.to_vec()?), Payload::RejoinRequestType02(v) => Ok(v.to_vec()?), Payload::RejoinRequestType1(v) => Ok(v.to_vec()?), Payload::Raw(v) => Ok(v.clone()), } } } #[derive(PartialEq, Eq, Debug, Copy, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct JoinRequestPayload { pub join_eui: EUI64, pub dev_eui: EUI64, pub dev_nonce: u16, } impl JoinRequestPayload { pub fn from_slice(b: &[u8]) -> Result { if b.len() != 18 { return Err(anyhow!("join-request payload must be exactly 18 bytes")); } let mut join_eui: [u8; 8] = [0; 8]; let mut dev_eui: [u8; 8] = [0; 8]; join_eui.clone_from_slice(&b[0..8]); dev_eui.clone_from_slice(&b[8..16]); Ok(JoinRequestPayload { join_eui: EUI64::from_le_bytes(join_eui), dev_eui: EUI64::from_le_bytes(dev_eui), dev_nonce: u16::from_le_bytes([b[16], b[17]]), }) } pub fn to_vec(&self) -> Vec { let mut v = Vec::with_capacity(18); v.append(&mut self.join_eui.to_le_bytes().to_vec()); v.append(&mut self.dev_eui.to_le_bytes().to_vec()); v.append(&mut self.dev_nonce.to_le_bytes().to_vec()); v } } #[derive(PartialEq, Eq, Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct JoinAcceptPayload { pub join_nonce: u32, // the actual max value is (2^24 -1) pub home_netid: NetID, pub devaddr: DevAddr, pub dl_settings: DLSettings, pub rx_delay: u8, // 0=1s, 1=1s, ... 15=15s pub cflist: Option, } impl JoinAcceptPayload { pub fn from_slice(b: &[u8]) -> Result { if b.len() != 12 && b.len() != 28 { return Err(anyhow!("12 or 28 bytes are expected for JoinAcceptPayload")); } Ok(JoinAcceptPayload { join_nonce: u32::from_le_bytes([b[0], b[1], b[2], 0x00]), home_netid: { let mut netid: [u8; 3] = [0; 3]; netid.clone_from_slice(&b[3..6]); NetID::from_le_bytes(netid) }, devaddr: { let mut devaddr: [u8; 4] = [0; 4]; devaddr.clone_from_slice(&b[6..10]); DevAddr::from_le_bytes(devaddr) }, dl_settings: { let mut dl_settings: [u8; 1] = [0]; dl_settings.clone_from_slice(&b[10..11]); DLSettings::from_le_bytes(dl_settings) }, rx_delay: b[11], cflist: match b.len() { 28 => { let mut cflist: [u8; 16] = [0; 16]; cflist.clone_from_slice(&b[12..]); Some(CFList::from_bytes(cflist)?) } _ => None, }, }) } pub fn to_vec(&self) -> Result> { if self.rx_delay > 15 { return Err(anyhow!("max value of rx_delay is 15")); } let mut b = Vec::with_capacity(28); b.extend_from_slice(&self.join_nonce.to_le_bytes()[..3]); b.extend_from_slice(&self.home_netid.to_le_bytes()); b.extend_from_slice(&self.devaddr.to_le_bytes()); b.extend_from_slice(&self.dl_settings.to_le_bytes()?); b.push(self.rx_delay); if let Some(v) = &self.cflist { b.extend_from_slice(&v.to_bytes()?); } Ok(b) } } #[derive(PartialEq, Eq, Debug, Clone)] pub enum FRMPayload { Raw(Vec), MACCommandSet(MACCommandSet), ForwardUplinkReq(ForwardUplinkReq), ForwardDownlinkReq(ForwardDownlinkReq), } #[cfg(feature = "serde")] impl Serialize for FRMPayload { fn serialize(&self, serializer: S) -> Result where S: Serializer, { match self { FRMPayload::Raw(v) => serializer.serialize_str(&hex::encode(v)), FRMPayload::MACCommandSet(v) => v.serialize(serializer), FRMPayload::ForwardUplinkReq(v) => { serializer.serialize_newtype_variant("FRMPayload", 2, "ForwardUplinkReq", v) } FRMPayload::ForwardDownlinkReq(v) => { serializer.serialize_newtype_variant("FRMPayload", 3, "ForwardDownlinkReq", v) } } } } impl FRMPayload { pub fn to_vec(&self) -> Result> { Ok(match self { FRMPayload::Raw(v) => v.clone(), FRMPayload::MACCommandSet(v) => v.to_vec()?, FRMPayload::ForwardUplinkReq(v) => v.to_vec()?, FRMPayload::ForwardDownlinkReq(v) => v.to_vec()?, }) } } #[derive(PartialEq, Eq, Debug, Clone, Default)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct MACPayload { pub fhdr: FHDR, pub f_port: Option, pub frm_payload: Option, } impl MACPayload { pub fn from_slice(b: &[u8]) -> Result { let b_len = b.len(); if b_len < 7 { return Err(anyhow!("MACPayload requires at least 7 bytes")); } // decode FCtrl as we need f_opts_len. let mut f_ctrl: [u8; 1] = [0]; f_ctrl.clone_from_slice(&b[4..5]); let f_ctrl = FCtrl::from_le_bytes(f_ctrl); // check that there are at least as many bytes as FOptsLen claims let fhdr_size = 7 + f_ctrl.f_opts_len as usize; if b_len < fhdr_size { return Err(anyhow!("not enough bytes to decode FHDR")); } Ok(MACPayload { fhdr: FHDR::from_slice(&b[..fhdr_size])?, f_port: match b_len > fhdr_size { true => Some(b[fhdr_size]), false => None, }, frm_payload: match b_len > fhdr_size + 1 { true => Some(FRMPayload::Raw(b[fhdr_size + 1..].to_vec())), false => None, }, }) } pub fn to_vec(&self) -> Result> { // validation of frm_payload if self.frm_payload.is_some() { // validate that f_port is set if self.f_port.is_none() { return Err(anyhow!("f_port not not be None when frm_payload is set")); } // mac-commands must have f_port=0 if let FRMPayload::MACCommandSet(_) = &self.frm_payload.as_ref().unwrap() { if self.f_port.unwrap() != 0 { return Err(anyhow!( "f_port must be set to 0 for mac-commands in frm_payload" )); } } } let mut b = Vec::new(); // fhdr b.append(&mut self.fhdr.to_vec()?); // f_port if let Some(v) = self.f_port { b.push(v); } // frm_payload if let Some(v) = &self.frm_payload { b.append(&mut v.to_vec()?); } Ok(b) } } #[derive(PartialEq, Eq, Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct RejoinRequestType02Payload { pub rejoin_type: JoinType, pub netid: NetID, pub dev_eui: EUI64, pub rj_count_0: u16, } impl RejoinRequestType02Payload { pub fn from_slice(b: &[u8]) -> Result { if b.len() != 14 { return Err(anyhow!( "rejoin-request type 0/2 payload must be exactly 14 bytes" )); } Ok(RejoinRequestType02Payload { rejoin_type: match b[0] { 0x00 => JoinType::RejoinType0, 0x02 => JoinType::RejoinType2, _ => { return Err(anyhow!("invalid rejoin_type")); } }, netid: { let mut netid: [u8; 3] = [0; 3]; netid.clone_from_slice(&b[1..4]); NetID::from_le_bytes(netid) }, dev_eui: { let mut deveui: [u8; 8] = [0; 8]; deveui.clone_from_slice(&b[4..12]); EUI64::from_le_bytes(deveui) }, rj_count_0: u16::from_le_bytes([b[12], b[13]]), }) } pub fn to_vec(&self) -> Result> { let mut b = Vec::with_capacity(14); b.push(match self.rejoin_type { JoinType::RejoinType0 => 0x00, JoinType::RejoinType2 => 0x02, _ => { return Err(anyhow!("rejoin_type must be 0 or 2")); } }); b.extend_from_slice(&self.netid.to_le_bytes()); b.extend_from_slice(&self.dev_eui.to_le_bytes()); b.extend_from_slice(&self.rj_count_0.to_le_bytes()); Ok(b) } } #[derive(PartialEq, Eq, Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct RejoinRequestType1Payload { pub rejoin_type: JoinType, pub join_eui: EUI64, pub dev_eui: EUI64, pub rj_count_1: u16, } impl RejoinRequestType1Payload { pub fn from_slice(b: &[u8]) -> Result { if b.len() != 19 { return Err(anyhow!( "rejoin-request type 1 payload must be exactly 19 bytes" )); } Ok(RejoinRequestType1Payload { rejoin_type: match b[0] { 0x01 => JoinType::RejoinType1, _ => { return Err(anyhow!("invalid rejoin_type")); } }, join_eui: { let mut joineui: [u8; 8] = [0; 8]; joineui.clone_from_slice(&b[1..9]); EUI64::from_le_bytes(joineui) }, dev_eui: { let mut deveui: [u8; 8] = [0; 8]; deveui.clone_from_slice(&b[9..17]); EUI64::from_le_bytes(deveui) }, rj_count_1: u16::from_le_bytes([b[17], b[18]]), }) } pub fn to_vec(&self) -> Result> { let mut b = Vec::with_capacity(19); b.push(match self.rejoin_type { JoinType::RejoinType1 => 0x01, _ => { return Err(anyhow!("rejoin_type must be 1")); } }); b.extend_from_slice(&self.join_eui.to_le_bytes()); b.extend_from_slice(&self.dev_eui.to_le_bytes()); b.extend_from_slice(&self.rj_count_1.to_le_bytes()); Ok(b) } } #[cfg(test)] mod tests { use std::str::FromStr; use super::super::cflist::CFListChannels; use super::super::maccommand::MACCommand; use super::*; struct PayloadTest { pl: Payload, bytes: Vec, } struct JoinAcceptTest { pl: JoinAcceptPayload, bytes: Vec, } #[test] fn test_join_request_payload() { let tests = vec![PayloadTest { pl: Payload::JoinRequest(JoinRequestPayload { join_eui: EUI64::from_str("0102030405060708").unwrap(), dev_eui: EUI64::from_str("0807060504030201").unwrap(), dev_nonce: 1024, }), bytes: vec![ 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x04, ], }]; for tst in tests { assert_eq!(tst.bytes, tst.pl.to_vec().unwrap()); assert_eq!( tst.pl, Payload::from_slice(MType::JoinRequest, &tst.bytes).unwrap() ); } } #[test] fn test_join_accept_payload() { // in this case the payload is converted to Raw as the join-accept must first be decrypted // before it can be decoded assert_eq!( Payload::Raw(vec![0x01, 0x02, 0x03]), Payload::from_slice(MType::JoinAccept, &vec![0x01, 0x02, 0x03]).unwrap() ); // test decoding the (decrypted) join-accept payload let tests = vec![ JoinAcceptTest { pl: JoinAcceptPayload { join_nonce: 65793, home_netid: NetID::from_str("020202").unwrap(), devaddr: DevAddr::from_str("01020304").unwrap(), dl_settings: DLSettings { rx2_dr: 7, rx1_dr_offset: 6, opt_neg: false, }, rx_delay: 9, cflist: None, }, bytes: vec![ 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x04, 0x03, 0x02, 0x01, 0x67, 0x09, ], }, JoinAcceptTest { pl: JoinAcceptPayload { join_nonce: 65793, home_netid: NetID::from_str("020202").unwrap(), devaddr: DevAddr::from_str("01020304").unwrap(), dl_settings: DLSettings { rx2_dr: 7, rx1_dr_offset: 6, opt_neg: false, }, rx_delay: 9, cflist: Some(CFList::Channels(CFListChannels::new([ 867100000, 867300000, 867500000, 867700000, 867900000, ]))), }, bytes: vec![ 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x04, 0x03, 0x02, 0x01, 0x67, 0x09, 0x18, 0x4f, 0x84, 0xe8, 0x56, 0x84, 0xb8, 0x5e, 0x84, 0x88, 0x66, 0x84, 0x58, 0x6e, 0x84, 0x00, ], }, ]; for tst in tests { assert_eq!(tst.bytes, tst.pl.to_vec().unwrap()); assert_eq!(tst.pl, JoinAcceptPayload::from_slice(&tst.bytes).unwrap()); } } #[test] fn test_mac_payload() { let tests = vec![ PayloadTest { pl: Payload::MACPayload(MACPayload { fhdr: FHDR { devaddr: DevAddr::from_str("01020304").unwrap(), f_ctrl: FCtrl { adr: true, ..Default::default() }, f_cnt: 123, f_opts: MACCommandSet::new(vec![]), }, f_port: None, frm_payload: None, }), bytes: vec![0x04, 0x03, 0x02, 0x01, 0x80, 0x7b, 0x00], }, PayloadTest { pl: Payload::MACPayload(MACPayload { fhdr: FHDR { devaddr: DevAddr::from_str("01020304").unwrap(), f_ctrl: FCtrl { adr: true, f_opts_len: 3, // only needed for assert_eq! ..Default::default() }, f_cnt: 123, f_opts: MACCommandSet::new(vec![MACCommand::Raw(vec![0x01, 0x02, 0x03])]), }, f_port: None, frm_payload: None, }), bytes: vec![0x04, 0x03, 0x02, 0x01, 0x83, 0x7b, 0x00, 0x01, 0x02, 0x03], }, PayloadTest { pl: Payload::MACPayload(MACPayload { fhdr: FHDR { devaddr: DevAddr::from_str("01020304").unwrap(), f_ctrl: FCtrl { adr: true, ..Default::default() }, f_cnt: 123, f_opts: MACCommandSet::new(vec![]), }, f_port: Some(10), frm_payload: None, }), bytes: vec![0x04, 0x03, 0x02, 0x01, 0x80, 0x7b, 0x00, 0x0a], }, PayloadTest { pl: Payload::MACPayload(MACPayload { fhdr: FHDR { devaddr: DevAddr::from_str("01020304").unwrap(), f_ctrl: FCtrl { adr: true, ..Default::default() }, f_cnt: 123, f_opts: MACCommandSet::new(vec![]), }, f_port: Some(10), frm_payload: Some(FRMPayload::Raw(vec![0x01, 0x02, 0x03])), }), bytes: vec![ 0x04, 0x03, 0x02, 0x01, 0x80, 0x7b, 0x00, 0x0a, 0x01, 0x02, 0x03, ], }, PayloadTest { pl: Payload::MACPayload(MACPayload { fhdr: FHDR { devaddr: DevAddr::from_str("01020304").unwrap(), f_ctrl: FCtrl { adr: true, f_opts_len: 3, ..Default::default() }, f_cnt: 123, f_opts: MACCommandSet::new(vec![MACCommand::Raw(vec![0x03, 0x02, 0x01])]), }, f_port: Some(10), frm_payload: Some(FRMPayload::Raw(vec![0x01, 0x02, 0x03])), }), bytes: vec![ 0x04, 0x03, 0x02, 0x01, 0x83, 0x7b, 0x00, 0x03, 0x02, 0x01, 0x0a, 0x01, 0x02, 0x03, ], }, ]; for tst in tests { assert_eq!(tst.bytes, tst.pl.to_vec().unwrap()); assert_eq!( tst.pl, Payload::from_slice(MType::UnconfirmedDataUp, &tst.bytes).unwrap() ); } } #[test] fn test_rejoin_request() { let tests = vec![ PayloadTest { pl: Payload::RejoinRequestType02(RejoinRequestType02Payload { rejoin_type: JoinType::RejoinType0, netid: NetID::from_str("010203").unwrap(), dev_eui: EUI64::from_str("0102030405060708").unwrap(), rj_count_0: 219, }), bytes: vec![ 0x00, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0xdb, 0x00, ], }, PayloadTest { pl: Payload::RejoinRequestType02(RejoinRequestType02Payload { rejoin_type: JoinType::RejoinType2, netid: NetID::from_str("010203").unwrap(), dev_eui: EUI64::from_str("0102030405060708").unwrap(), rj_count_0: 219, }), bytes: vec![ 0x02, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0xdb, 0x00, ], }, PayloadTest { pl: Payload::RejoinRequestType1(RejoinRequestType1Payload { rejoin_type: JoinType::RejoinType1, join_eui: EUI64::from_str("0102030405060708").unwrap(), dev_eui: EUI64::from_str("0807060504030201").unwrap(), rj_count_1: 219, }), bytes: vec![ 0x01, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xdb, 0x00, ], }, ]; for tst in tests { assert_eq!(tst.bytes, tst.pl.to_vec().unwrap()); assert_eq!( tst.pl, Payload::from_slice(MType::RejoinRequest, &tst.bytes).unwrap() ); } } #[test] fn test_proprietary() { let tests = vec![PayloadTest { pl: Payload::Raw(vec![0x01, 0x02, 0x3]), bytes: vec![0x01, 0x02, 0x03], }]; for tst in tests { assert_eq!(tst.bytes, tst.pl.to_vec().unwrap()); assert_eq!( tst.pl, Payload::from_slice(MType::Proprietary, &tst.bytes).unwrap() ); } } }