#[cfg(feature = "crypto")] use aes::{ cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt}, Aes128, Block, }; use anyhow::Result; #[cfg(feature = "crypto")] use cmac::{Cmac, Mac}; #[cfg(feature = "serde")] use serde::Serialize; use super::maccommand::{MACCommand, MACCommandSet}; use super::mhdr::{MType, MHDR}; use super::payload::{FRMPayload, MACPayload, Payload}; #[cfg(feature = "crypto")] use super::{ aes128::AES128Key, devaddr::DevAddr, eui64::EUI64, payload::{JoinAcceptPayload, JoinType}, }; use crate::relay::{ForwardDownlinkReq, ForwardUplinkReq}; use crate::LA_FPORT_RELAY; #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(Serialize))] pub enum MACVersion { LoRaWAN1_0, LoRaWAN1_1, } /// PhyPayload represents the LoRaWAN PHY payload. /// /// Join-request example: /// ```rust /// use std::str::FromStr; /// use lrwn::*; /// /// let app_key = AES128Key::from_str("0102030405060708090a0b0c0d0e0f10").unwrap(); /// /// let mut phy = PhyPayload { /// mhdr: MHDR { /// m_type: MType::JoinRequest, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::JoinRequest(JoinRequestPayload { /// join_eui: EUI64::from_str("0101010101010101").unwrap(), /// dev_eui: EUI64::from_str("0202020202020202").unwrap(), /// dev_nonce: 771, /// }), /// mic: None, /// }; /// /// phy.set_join_request_mic(&app_key).unwrap(); /// assert_eq!([0x9, 0xb9, 0x7b, 0x32], phy.mic.unwrap()); /// /// let bytes = phy.to_vec().unwrap(); /// assert_eq!(vec![0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x09, 0xb9, 0x7b, 0x32], bytes); /// /// let phy_decoded = PhyPayload::from_slice(&bytes).unwrap(); /// assert_eq!(phy, phy_decoded); /// assert_eq!(true, phy_decoded.validate_join_request_mic(&app_key).unwrap()); /// ``` /// /// LoRaWAN 1.0.x Join-accept example: /// ```rust /// use std::str::FromStr; /// use lrwn::*; /// /// let app_key = AES128Key::from_str("0102030405060708090a0b0c0d0e0f10").unwrap(); /// let join_eui = EUI64::from_str("0807060504030201").unwrap(); /// let dev_nonce = 258; /// /// let mut phy = PhyPayload { /// mhdr: MHDR { /// m_type: MType::JoinAccept, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::JoinAccept(JoinAcceptPayload { /// join_nonce: 65793, /// home_netid: NetID::from_str("020202").unwrap(), /// devaddr: DevAddr::from_str("01020304").unwrap(), /// dl_settings: DLSettings { /// opt_neg: false, /// rx2_dr: 0, /// rx1_dr_offset: 0, /// }, /// cflist: None, /// rx_delay: 0, /// }), /// mic: None, /// }; /// /// phy.set_join_accept_mic(JoinType::Join, &join_eui, dev_nonce, &app_key).unwrap(); /// assert_eq!([0x34, 0x49, 0xf2, 0x12], phy.mic.unwrap()); /// /// phy.encrypt_join_accept_payload(&app_key).unwrap(); /// /// let bytes = phy.to_vec().unwrap(); /// assert_eq!(vec![0x20, 0x23, 0xcf, 0x33, 0x54, 0x89, 0xaa, 0xe3, 0x18, 0x3c, 0x0b, 0xe0, 0xba, 0xa8, 0xde, 0xe5, 0xf3], bytes); /// /// let mut phy_decoded = PhyPayload::from_slice(&bytes).unwrap(); /// phy_decoded.decrypt_join_accept_payload(&app_key).unwrap(); /// assert_eq!(true, phy_decoded.validate_join_accept_mic(JoinType::Join, &join_eui, dev_nonce, &app_key).unwrap()); /// /// assert_eq!(PhyPayload { /// mhdr: MHDR { /// m_type: MType::JoinAccept, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::JoinAccept(JoinAcceptPayload { /// join_nonce: 65793, /// home_netid: NetID::from_str("020202").unwrap(), /// devaddr: DevAddr::from_str("01020304").unwrap(), /// dl_settings: DLSettings { /// opt_neg: false, /// rx2_dr: 0, /// rx1_dr_offset: 0, /// }, /// cflist: None, /// rx_delay: 0, /// }), /// mic: Some([0x34, 0x49, 0xf2, 0x12]), /// }, phy_decoded); /// ``` /// /// LoRaWAN 1.1.x Join-accept example: /// ```rust /// use std::str::FromStr; /// use lrwn::*; /// /// let app_key = AES128Key::from_str("0102030405060708090a0b0c0d0e0f10").unwrap(); /// let join_eui = EUI64::from_str("0807060504030201").unwrap(); /// let dev_nonce = 258; /// /// let mut phy = PhyPayload { /// mhdr: MHDR { /// m_type: MType::JoinAccept, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::JoinAccept(JoinAcceptPayload { /// join_nonce: 65793, /// home_netid: NetID::from_str("020202").unwrap(), /// devaddr: DevAddr::from_str("01020304").unwrap(), /// dl_settings: DLSettings { /// opt_neg: true, // Note that opt_neg is set to true! /// rx2_dr: 0, /// rx1_dr_offset: 0, /// }, /// cflist: None, /// rx_delay: 0, /// }), /// mic: None, /// }; /// /// phy.set_join_accept_mic(JoinType::Join, &join_eui, dev_nonce, &app_key).unwrap(); /// assert_eq!([0x93, 0xff, 0x9a, 0x3a], phy.mic.unwrap()); /// /// phy.encrypt_join_accept_payload(&app_key).unwrap(); /// /// let bytes = phy.to_vec().unwrap(); /// assert_eq!(vec![0x20, 0x7a, 0xbe, 0xea, 0x06, 0xb0, 0x29, 0x20, 0xf1, 0x1c, 0x02, 0xd0, 0x34, 0x8f, 0xcf, 0x18, 0x15], bytes); /// /// let mut phy_decoded = PhyPayload::from_slice(&bytes).unwrap(); /// phy_decoded.decrypt_join_accept_payload(&app_key).unwrap(); /// assert_eq!(true, phy_decoded.validate_join_accept_mic(JoinType::Join, &join_eui, dev_nonce, &app_key).unwrap()); /// /// assert_eq!(PhyPayload { /// mhdr: MHDR { /// m_type: MType::JoinAccept, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::JoinAccept(JoinAcceptPayload { /// join_nonce: 65793, /// home_netid: NetID::from_str("020202").unwrap(), /// devaddr: DevAddr::from_str("01020304").unwrap(), /// dl_settings: DLSettings { /// opt_neg: true, /// rx2_dr: 0, /// rx1_dr_offset: 0, /// }, /// cflist: None, /// rx_delay: 0, /// }), /// mic: Some([0x93, 0xff, 0x9a, 0x3a]), /// }, phy_decoded); /// ``` /// /// LoRaWAN 1.0.x confirmed uplink example: /// ```rust /// use std::str::FromStr; /// use lrwn::*; /// /// let nwk_s_key = AES128Key::from_str("0102030405060708090a0b0c0d0e0f10").unwrap(); /// let app_s_key = AES128Key::from_str("100f0e0d0c0b0a090807060504030201").unwrap(); /// /// let mut phy = PhyPayload { /// mhdr: MHDR { /// m_type: MType::ConfirmedDataUp, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::MACPayload(MACPayload{ /// fhdr: FHDR{ /// devaddr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), /// f_ctrl: FCtrl::default(), /// f_cnt: 0, /// f_opts: MACCommandSet::new(vec![ /// MACCommand::DevStatusAns(DevStatusAnsPayload{ /// battery: 115, /// margin: 7, /// }), /// ]), /// }, /// f_port: Some(10), /// frm_payload: Some(FRMPayload::Raw(vec![0x01, 0x02, 0x03, 0x04])), /// }), /// mic: None, /// }; /// /// phy.encrypt_frm_payload(&app_s_key).unwrap(); /// phy.set_uplink_data_mic(MACVersion::LoRaWAN1_0, 0, 0, 0, &nwk_s_key, &nwk_s_key); /// /// let bytes = phy.to_vec().unwrap(); /// assert_eq!(vec![0x80, 0x04, 0x03, 0x02, 0x01, 0x03, 0x00, 0x00, 0x06, 0x73, 0x07, 0x0a, 0xe2, 0x64, 0xd4, 0xf7, 0xe1, 0x17, 0xd2, 0xc0], bytes); /// /// let mut phy_decoded = PhyPayload::from_slice(&bytes).unwrap(); /// assert_eq!(true, phy_decoded.validate_uplink_data_mic(MACVersion::LoRaWAN1_0, 0, 0, 0, &nwk_s_key, &nwk_s_key).unwrap()); /// /// phy_decoded.decrypt_frm_payload(&app_s_key).unwrap(); /// /// if let Payload::MACPayload(pl) = &phy_decoded.payload { /// if let FRMPayload::Raw(b) = &pl.frm_payload.as_ref().unwrap() { /// assert_eq!(&vec![0x01, 0x02, 0x03, 0x04], b); /// } else { /// panic!("No FrmPayload!"); /// } /// } else { /// panic!("No MacPayload!"); /// } /// ``` /// /// LoRaWAN 1.1.x downlink with encrypted f_opts example: /// ```rust /// use std::str::FromStr; /// use lrwn::*; /// /// let s_nwk_s_int_key = AES128Key::from_str("01010101010101010101010101010100").unwrap(); /// let nwk_s_enc_key = AES128Key::from_str("01010101010101010101010101010200").unwrap(); /// let app_s_key = AES128Key::from_str("100f0e0d0c0b0a090807060504030201").unwrap(); /// /// let mut phy = PhyPayload { /// mhdr: MHDR{ /// m_type: MType::UnconfirmedDataDown, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::MACPayload(MACPayload{ /// fhdr: FHDR{ /// devaddr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), /// f_ctrl: FCtrl::default(), /// f_cnt: 0, /// f_opts: MACCommandSet::new(vec![ /// MACCommand::LinkCheckAns(LinkCheckAnsPayload{ /// margin: 7, /// gw_cnt: 1, /// }), /// ]), /// }, /// f_port: Some(1), /// frm_payload: Some(FRMPayload::Raw(vec![0x01, 0x02, 0x03, 0x04])), /// }), /// mic: None, /// }; /// /// phy.encrypt_f_opts(&nwk_s_enc_key).unwrap(); /// phy.encrypt_frm_payload(&app_s_key).unwrap(); /// phy.set_downlink_data_mic(MACVersion::LoRaWAN1_1, 0, &s_nwk_s_int_key).unwrap(); /// /// let bytes = phy.to_vec().unwrap(); /// assert_eq!(vec![0x60, 0x04, 0x03, 0x02, 0x01, 0x03, 0x00, 0x00, 0x22, 0xac, 0x0a, 0x01, 0xf0, 0xb4, 0x68, 0xdd, 0xaa, 0x5e, 0xd1, 0x3a], bytes); /// /// let mut phy_decoded = PhyPayload::from_slice(&bytes).unwrap(); /// assert_eq!(true, phy_decoded.validate_downlink_data_mic(MACVersion::LoRaWAN1_1, 0, &s_nwk_s_int_key).unwrap()); /// /// phy_decoded.decrypt_f_opts(&nwk_s_enc_key).unwrap(); /// phy_decoded.decrypt_frm_payload(&app_s_key).unwrap(); /// /// if let Payload::MACPayload(pl) = &phy_decoded.payload { /// assert_eq!(MACCommandSet::new(vec![ /// MACCommand::LinkCheckAns(LinkCheckAnsPayload{ /// margin: 7, /// gw_cnt: 1, /// }), /// ]), pl.fhdr.f_opts); /// /// if let FRMPayload::Raw(b) = &pl.frm_payload.as_ref().unwrap() { /// assert_eq!(&vec![0x01, 0x02, 0x03, 0x04], b); /// } else { /// panic!("No FrmPayload!"); /// } /// } else { /// panic!("No MacPayload"); /// } /// ``` /// /// Proprietary example: /// ```rust /// use std::str::FromStr; /// use lrwn::*; /// /// let phy = PhyPayload { /// mhdr: MHDR { /// m_type: MType::Proprietary, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::Raw(vec![0x01, 0x02, 0x03]), /// mic: None, /// }; /// /// let bytes = phy.to_vec().unwrap(); /// assert_eq!(vec![0xe0, 0x01, 0x02, 0x03], bytes); /// /// let phy_decoded = PhyPayload::from_slice(&bytes).unwrap(); /// assert_eq!(phy, phy_decoded); /// ``` /// /// LoRaWAN 1.0.x Relay ForwardUplinkReq example: /// ```rust /// use std::str::FromStr; /// use lrwn::*; /// /// // Payload from the end-device. /// let ed_app_key = AES128Key::from_str("01020304050607080102030405060708").unwrap(); /// let mut ed_phy = PhyPayload { /// mhdr: MHDR { /// m_type: MType::JoinRequest, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::JoinRequest(JoinRequestPayload { /// join_eui: EUI64::from_str("0101010101010101").unwrap(), /// dev_eui: EUI64::from_str("0202020202020202").unwrap(), /// dev_nonce: 771, /// }), /// mic: None, /// }; /// /// ed_phy.set_join_request_mic(&ed_app_key).unwrap(); /// /// // Relay ForwardUplinkReq (which will forward the end-device payload). /// let relay_nwk_s_key = AES128Key::from_str("08070605040302010807060504030201").unwrap(); /// let mut relay_phy = PhyPayload { /// mhdr: MHDR { /// m_type: MType::UnconfirmedDataUp, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::MACPayload(MACPayload { /// fhdr: FHDR { /// devaddr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), /// f_cnt: 10, /// ..Default::default() /// }, /// f_port: Some(226), /// frm_payload: Some(FRMPayload::ForwardUplinkReq(ForwardUplinkReq { /// metadata: UplinkMetadata { /// dr: 5, /// snr: 7, /// rssi: -80, /// wor_channel: 1, /// }, /// frequency: 868100000, /// payload: Box::new(ed_phy.clone()), /// })), /// }), /// mic: None, /// }; /// relay_phy.encrypt_frm_payload(&relay_nwk_s_key).unwrap(); /// relay_phy.set_uplink_data_mic(MACVersion::LoRaWAN1_0, 0, 0, 0, &relay_nwk_s_key, &relay_nwk_s_key); /// /// let bytes = relay_phy.to_vec().unwrap(); /// assert_eq!(vec![0x40, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0a, 0x00, 0xe2, 0x2f, 0x68, 0xf4, 0xa5, 0x0a, 0xdf, 0xfb, 0x64, 0xef, 0x37, 0x91, 0x0f, 0x14, 0x6a, 0x6c, 0x2b, 0xda, 0x4f, 0x7e, 0x2d, 0xb9, 0x6a, 0xc8, 0x99, 0xa8, 0xa4, 0x72, 0x7d, 0x0a, 0xbd, 0xc9, 0xae, 0x51], bytes); /// /// let mut relay_phy_decoded = PhyPayload::from_slice(&bytes).unwrap(); /// assert_eq!(relay_phy, relay_phy_decoded); /// /// relay_phy_decoded.decrypt_frm_payload(&relay_nwk_s_key).unwrap(); /// assert_eq!(PhyPayload{ /// mhdr: MHDR { /// m_type: MType::UnconfirmedDataUp, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::MACPayload(MACPayload { /// fhdr: FHDR { /// devaddr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), /// f_cnt: 10, /// ..Default::default() /// }, /// f_port: Some(226), /// frm_payload: Some(FRMPayload::ForwardUplinkReq(ForwardUplinkReq { /// metadata: UplinkMetadata { /// dr: 5, /// snr: 7, /// rssi: -80, /// wor_channel: 1, /// }, /// frequency: 868100000, /// payload: Box::new(ed_phy), /// })), /// }), /// mic: Some([0xbd, 0xc9, 0xae, 0x51]), /// }, relay_phy_decoded); /// ``` /// /// LoRaWAN 1.0.x Relay ForwardDownlinkReq example: /// ```rust /// use std::str::FromStr; /// use lrwn::*; /// /// // Payload for the end-device. /// let ed_app_key = AES128Key::from_str("0102030405060708090a0b0c0d0e0f10").unwrap(); /// let ed_join_eui = EUI64::from_str("0807060504030201").unwrap(); /// let ed_dev_nonce = 258; /// let mut ed_phy = PhyPayload { /// mhdr: MHDR { /// m_type: MType::JoinAccept, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::JoinAccept(JoinAcceptPayload { /// join_nonce: 65793, /// home_netid: NetID::from_str("020202").unwrap(), /// devaddr: DevAddr::from_str("01020304").unwrap(), /// dl_settings: DLSettings { /// opt_neg: false, /// rx2_dr: 0, /// rx1_dr_offset: 0, /// }, /// cflist: None, /// rx_delay: 0, /// }), /// mic: None, /// }; /// /// ed_phy.set_join_accept_mic(JoinType::Join, &ed_join_eui, ed_dev_nonce, &ed_app_key).unwrap(); /// ed_phy.encrypt_join_accept_payload(&ed_app_key).unwrap(); /// /// // Payload for the Relay containing the ForwardDownlinkReq. /// let relay_nwk_s_key = AES128Key::from_str("08070605040302010807060504030201").unwrap(); /// let mut relay_phy = PhyPayload { /// mhdr: MHDR { /// m_type: MType::UnconfirmedDataDown, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::MACPayload(MACPayload { /// fhdr: FHDR { /// devaddr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), /// f_cnt: 10, /// ..Default::default() /// }, /// f_port: Some(226), /// frm_payload: Some(FRMPayload::ForwardDownlinkReq(ForwardDownlinkReq { /// payload: Box::new(ed_phy.clone()), /// })), /// }), /// mic: None, /// }; /// relay_phy.encrypt_frm_payload(&relay_nwk_s_key).unwrap(); /// relay_phy.set_downlink_data_mic(MACVersion::LoRaWAN1_0, 0, &relay_nwk_s_key).unwrap(); /// /// let bytes = relay_phy.to_vec().unwrap(); /// assert_eq!(vec![0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0a, 0x00, 0xe2, 0xc9, 0x60, 0x41, 0x64, 0xc9, 0x7d, 0x76, 0xf9, 0xea, 0x8e, 0x1a, 0x79, 0x2b, 0xa0, 0x87, 0x9b, 0x85, 0x24, 0x3e, 0x5a, 0xf5], bytes); /// /// let mut relay_phy_decoded = PhyPayload::from_slice(&bytes).unwrap(); /// assert_eq!(relay_phy, relay_phy_decoded); /// /// relay_phy_decoded.decrypt_frm_payload(&relay_nwk_s_key).unwrap(); /// assert_eq!(PhyPayload { /// mhdr: MHDR { /// m_type: MType::UnconfirmedDataDown, /// major: Major::LoRaWANR1, /// }, /// payload: Payload::MACPayload(MACPayload { /// fhdr: FHDR { /// devaddr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), /// f_cnt: 10, /// ..Default::default() /// }, /// f_port: Some(226), /// frm_payload: Some(FRMPayload::ForwardDownlinkReq(ForwardDownlinkReq { /// payload: Box::new(ed_phy), /// })), /// }), /// mic: Some([0x24, 0x3e, 0x5a, 0xf5]), /// }, relay_phy_decoded); /// ``` #[derive(Debug, PartialEq, Eq, Clone)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct PhyPayload { pub mhdr: MHDR, pub payload: Payload, /// This field is not used in case of the proprietary message-type. pub mic: Option<[u8; 4]>, } impl PhyPayload { pub fn to_vec(&self) -> Result> { let mut b = Vec::new(); b.extend_from_slice(&self.mhdr.to_le_bytes()); b.append(&mut self.payload.to_vec()?); if let Some(v) = &self.mic { b.extend_from_slice(&v.clone()); } Ok(b) } pub fn from_slice(b: &[u8]) -> Result { let b_len = b.len(); // We need 1 byte to decode the mhdr. if b_len == 0 { return Err(anyhow!("at least 1 byte required to decode PhyPayload")); } let mhdr = { let mhdr: [u8; 1] = [b[0]]; MHDR::from_le_bytes(mhdr)? }; if mhdr.m_type == MType::Proprietary { return Ok(PhyPayload { mhdr, payload: Payload::from_slice(MType::Proprietary, &b[1..])?, mic: None, }); } // Validate the minimum required bytes for not running into slicing errors. if b_len < 5 { return Err(anyhow!( "at least 5 bytes are required to decode PhyPayload" )); } let m_type = mhdr.m_type; let mut mic: [u8; 4] = [0; 4]; mic.clone_from_slice(&b[b_len - 4..]); Ok(PhyPayload { mhdr, payload: Payload::from_slice(m_type, &b[1..b_len - 4])?, mic: Some(mic), }) } /// Calculate and set the MIC field for uplink data frames. /// The conf_f_cnt, tx_dr, tx_ch and s_nwk_s_int_key are only required for LoRaWAN 1.1 and can /// be left blank for LoRaWAN 1.0. #[cfg(feature = "crypto")] pub fn set_uplink_data_mic( &mut self, mac_version: MACVersion, conf_f_cnt: u32, tx_dr: u8, tx_ch: u8, f_nwk_s_int_key: &AES128Key, s_nwk_s_int_key: &AES128Key, ) -> Result<()> { self.mic = Some(self.calculate_uplink_data_mic( mac_version, conf_f_cnt, tx_dr, tx_ch, f_nwk_s_int_key, s_nwk_s_int_key, )?); Ok(()) } /// Validate the MIC of an uplink data frame. /// In order to validate the MIC, the f_cnt value must be first set to the full 32 bit /// frame-counter value, as only the 16 lsb are transmitted over the air. /// The conf_f_cnt, tx_dr, tx_ch and s_nwk_s_int_key are only required for LoRaWAN 1.1 and can /// be left blank for LoRaWAN 1.0. #[cfg(feature = "crypto")] pub fn validate_uplink_data_mic( &self, mac_version: MACVersion, conf_f_cnt: u32, tx_dr: u8, tx_ch: u8, f_nwk_s_int_key: &AES128Key, s_nwk_s_int_key: &AES128Key, ) -> Result { if let Some(mic) = self.mic { return Ok(mic == self.calculate_uplink_data_mic( mac_version, conf_f_cnt, tx_dr, tx_ch, f_nwk_s_int_key, s_nwk_s_int_key, )?); } Ok(false) } /// Set the MIC for downlink data frames. /// The conf_f_cnt is only required for LoRaWAN 1.1 and can be left blank for LoRaWAN 1.0. #[cfg(feature = "crypto")] pub fn set_downlink_data_mic( &mut self, mac_version: MACVersion, conf_f_cnt: u32, s_nwk_s_int_key: &AES128Key, ) -> Result<()> { self.mic = Some(self.calculate_downlink_data_mic(mac_version, conf_f_cnt, s_nwk_s_int_key)?); Ok(()) } #[cfg(feature = "crypto")] pub fn validate_downlink_data_mic( &mut self, mac_version: MACVersion, conf_f_cnt: u32, s_nwk_s_int_key: &AES128Key, ) -> Result { if let Some(mic) = self.mic { return Ok(mic == self.calculate_downlink_data_mic(mac_version, conf_f_cnt, s_nwk_s_int_key)?); } Ok(false) } /// Validate the cmacF part of the uplink data MIC (LoRaWAN 1.1 only). /// In order to validate the MIC, the f_cnt value must be first set to the full 32 bit /// frame-counter value, as only the 16 lsb are transmitted over the air. #[cfg(feature = "crypto")] pub fn validate_uplink_data_micf(&self, f_nwk_s_int_key: &AES128Key) -> Result { // We are only interested in mic[2:] (cmacF bytes), therefore there is no // need to pass the correct confFCnt, txDR, txCh and sNwkSIntKey parameters. if let Some(v) = self.mic { let mic = self.calculate_uplink_data_mic( MACVersion::LoRaWAN1_1, 0, 0, 0, f_nwk_s_int_key, f_nwk_s_int_key, )?; return Ok(v[2..] == mic[2..]); } Ok(false) } /// Set the join-request MIC. #[cfg(feature = "crypto")] pub fn set_join_request_mic(&mut self, key: &AES128Key) -> Result<()> { self.mic = Some(self.calculate_upink_join_mic(key)?); Ok(()) } /// Validate the join-request MIC. #[cfg(feature = "crypto")] pub fn validate_join_request_mic(&self, key: &AES128Key) -> Result { if let Some(v) = self.mic { let mic = self.calculate_upink_join_mic(key)?; return Ok(v == mic); } Ok(false) } /// Set the the downlink join-accept MIC. #[cfg(feature = "crypto")] pub fn set_join_accept_mic( &mut self, join_req_type: JoinType, join_eui: &EUI64, dev_nonce: u16, key: &AES128Key, ) -> Result<()> { self.mic = Some(self.calculate_downlink_join_mic(join_req_type, join_eui, dev_nonce, key)?); Ok(()) } /// Validate the downlink join-accept MIC. #[cfg(feature = "crypto")] pub fn validate_join_accept_mic( &self, join_req_type: JoinType, join_eui: &EUI64, dev_nonce: u16, key: &AES128Key, ) -> Result { if let Some(v) = self.mic { let mic = self.calculate_downlink_join_mic(join_req_type, join_eui, dev_nonce, key)?; return Ok(v == mic); } Ok(false) } /// Encrypt the join-accept payload with the given key. /// Note that the encryption must be performed after setting the MIC, since the MIC is part of /// the encrypted payload. /// For encrypting a join-request response, use the nwk_key, for rejoin-request 0, 1 and 3 /// response, use the js_enc_key. #[cfg(feature = "crypto")] pub fn encrypt_join_accept_payload(&mut self, key: &AES128Key) -> Result<()> { use aes::cipher::KeyInit; if self.mic.is_none() { return Err(anyhow!("mic must be set first")); } if let Payload::JoinAccept(pl) = &self.payload { let mut pt = pl.to_vec()?; pt.extend_from_slice(&self.mic.unwrap()); if pt.len() % 16 != 0 { return Err(anyhow!("plaintext must be a multiple of 16 bytes")); } let key_bytes = key.to_bytes(); let key = GenericArray::from_slice(&key_bytes); let cipher = Aes128::new(key); let mut ct = Vec::new(); for i in 0..(pt.len() / 16) { let index = i * 16; let mut block = Block::clone_from_slice(&pt[index..index + 16]); cipher.decrypt_block(&mut block); ct.extend_from_slice(block.as_slice()); } self.payload = Payload::Raw(ct[0..ct.len() - 4].to_vec()); let mut mic: [u8; 4] = [0; 4]; mic.clone_from_slice(&ct[ct.len() - 4..]); self.mic = Some(mic); return Ok(()); } Err(anyhow!("payload must be of type JoinAcceptPayload")) } /// Decrypt the join-accept payload with the given key. /// Note that the decryption must be performed before validating the MIC, since the MIC is part /// of the encrypted payload. /// For decrypting a join-request response, use the nwk_key, for rejoin-request 0, 1 and 3 /// response, use the js_enc_key. #[cfg(feature = "crypto")] pub fn decrypt_join_accept_payload(&mut self, key: &AES128Key) -> Result<()> { use aes::cipher::KeyInit; if self.mic.is_none() { return Err(anyhow!("mic must be set first")); } if let Payload::Raw(pl) = &self.payload { // append MIC since it is encrypted too let mut ct = pl.clone(); ct.extend_from_slice(&self.mic.unwrap()); if ct.len() % 16 != 0 { return Err(anyhow!("ciphertext must be a multiple of 16 bytes")); } let key_bytes = key.to_bytes(); let key = GenericArray::from_slice(&key_bytes); let cipher = Aes128::new(key); let mut pt = Vec::new(); for i in 0..(ct.len() / 16) { let index = i * 16; let mut block = Block::clone_from_slice(&ct[index..index + 16]); cipher.encrypt_block(&mut block); pt.extend_from_slice(block.as_slice()); } let mut mic: [u8; 4] = [0; 4]; mic.clone_from_slice(&pt[pt.len() - 4..]); self.mic = Some(mic); self.payload = Payload::JoinAccept(JoinAcceptPayload::from_slice(&pt[..pt.len() - 4])?); return Ok(()); } Err(anyhow!("payload must be of type Raw")) } /// Encrypt the f_opts with the given key. #[cfg(feature = "crypto")] pub fn encrypt_f_opts(&mut self, nwk_s_enc_key: &AES128Key) -> Result<()> { if let Payload::MACPayload(pl) = &mut self.payload { let f_opts_bytes = pl.fhdr.f_opts.to_vec()?; if f_opts_bytes.is_empty() { return Ok(()); } let uplink = is_uplink(self.mhdr.m_type); // a_fcnt_down is used on downlink when f_port > 0 let a_fcnt_down = !uplink && pl.f_port.is_some() && pl.f_port.unwrap() > 0; let f_opts_enc = encrypt_f_opts( nwk_s_enc_key, a_fcnt_down, uplink, &pl.fhdr.devaddr, pl.fhdr.f_cnt, &f_opts_bytes, )?; pl.fhdr.f_opts = MACCommandSet::new(vec![MACCommand::Raw(f_opts_enc)]); return Ok(()); } Err(anyhow!("payload must be of type MACPayload")) } /// Decrypt the f_opts with the given key. /// This automatically calls decode_f_opts_to_mac_commands. #[cfg(feature = "crypto")] pub fn decrypt_f_opts(&mut self, nwk_s_enc_key: &AES128Key) -> Result<()> { self.encrypt_f_opts(nwk_s_enc_key)?; self.decode_f_opts_to_mac_commands()?; Ok(()) } /// Decode f_opts to mac-commands. pub fn decode_f_opts_to_mac_commands(&mut self) -> Result<()> { if let Payload::MACPayload(pl) = &mut self.payload { let uplink = is_uplink(self.mhdr.m_type); pl.fhdr.f_opts.decode_from_raw(uplink)?; } Ok(()) } /// Decode frm_payload payload. /// /// This will decode as follow based on f_port: /// 0: MACCommandSet /// 226: ForwardDownlinkReq / ForwardDownlinkReq /// /// For other f_port values, it will not try to decode the payload. /// Note that this requires a decrypted frm_payload. pub fn decode_frm_payload(&mut self) -> Result<()> { if let Payload::MACPayload(pl) = &mut self.payload { let uplink = is_uplink(self.mhdr.m_type); let f_port = pl.f_port.unwrap_or(0); let b = match &pl.frm_payload { Some(FRMPayload::Raw(v)) => v.clone(), _ => { // Nothing to do. return Ok(()); } }; return decode_frm_payload(pl, uplink, f_port, b); } Ok(()) } /// Encrypt the frm_payload with the given key. #[cfg(feature = "crypto")] pub fn encrypt_frm_payload(&mut self, key: &AES128Key) -> Result<()> { if let Payload::MACPayload(pl) = &mut self.payload { // nothing to do if pl.frm_payload.is_none() { return Ok(()); } let uplink = is_uplink(self.mhdr.m_type); let data = pl.frm_payload.as_ref().unwrap().to_vec()?; let data = encrypt_frm_payload(key, uplink, &pl.fhdr.devaddr, pl.fhdr.f_cnt, &data)?; pl.frm_payload = Some(FRMPayload::Raw(data)); return Ok(()); } Err(anyhow!("payload must be of type MACPayload")) } /// Decrypt the frm_payload with the given key. /// /// This will automatically call decode_frm_payload. #[cfg(feature = "crypto")] pub fn decrypt_frm_payload(&mut self, key: &AES128Key) -> Result<()> { if let Payload::MACPayload(pl) = &mut self.payload { // nothing to do if pl.frm_payload.is_none() { return Ok(()); } let uplink = is_uplink(self.mhdr.m_type); let data = pl.frm_payload.as_ref().unwrap().to_vec()?; let data = encrypt_frm_payload(key, uplink, &pl.fhdr.devaddr, pl.fhdr.f_cnt, &data)?; return decode_frm_payload(pl, uplink, pl.f_port.unwrap_or(0), data); } Err(anyhow!("payload must be of type MACPayload")) } #[cfg(feature = "crypto")] fn calculate_uplink_data_mic( &self, mac_version: MACVersion, conf_f_cnt: u32, tx_dr: u8, tx_ch: u8, f_nwk_s_int_key: &AES128Key, s_nwk_s_int_key: &AES128Key, ) -> Result<[u8; 4]> { if let Payload::MACPayload(pl) = &self.payload { // set to 0 if the uplink does not contain an ACK let mut conf_f_cnt = conf_f_cnt; if !pl.fhdr.f_ctrl.ack { conf_f_cnt = 0; } // truncate to 16 lsb let conf_f_cnt = (conf_f_cnt % (1 << 16)) as u16; let mut mic_bytes = Vec::new(); mic_bytes.extend_from_slice(&self.mhdr.to_le_bytes()); mic_bytes.extend_from_slice(&self.payload.to_vec()?); let mut b0: [u8; 16] = [0; 16]; let mut b1: [u8; 16] = [0; 16]; b0[0] = 0x49; b1[0] = 0x49; // devaddr let devaddr_b = pl.fhdr.devaddr.to_le_bytes(); b0[6..10].clone_from_slice(&devaddr_b); b1[6..10].clone_from_slice(&devaddr_b); // fcntup b0[10..14].clone_from_slice(&pl.fhdr.f_cnt.to_le_bytes()); b1[10..14].clone_from_slice(&pl.fhdr.f_cnt.to_le_bytes()); // msg len b0[15] = mic_bytes.len() as u8; b1[15] = mic_bytes.len() as u8; // remaining b1 fields b1[1..3].clone_from_slice(&conf_f_cnt.to_le_bytes()); b1[3] = tx_dr; b1[4] = tx_ch; let mut mac = Cmac::::new_from_slice(&s_nwk_s_int_key.to_bytes()).unwrap(); mac.update(&b1); mac.update(&mic_bytes); let cmac_s = mac.finalize().into_bytes(); if cmac_s.len() < 4 { return Err(anyhow!("cmac_s is less than 4 bytes")); } let mut mac = Cmac::::new_from_slice(&f_nwk_s_int_key.to_bytes()).unwrap(); mac.update(&b0); mac.update(&mic_bytes); let cmac_f = mac.finalize().into_bytes(); if cmac_f.len() < 4 { return Err(anyhow!("cmac_f is less than 4 bytes")); } let mut mic: [u8; 4] = [0; 4]; if mac_version == MACVersion::LoRaWAN1_0 { mic.clone_from_slice(&cmac_f[0..4]); return Ok(mic); } else { mic[0..2].clone_from_slice(&cmac_s[0..2]); mic[2..4].clone_from_slice(&cmac_f[0..2]); return Ok(mic); } } Err(anyhow!("payload must be of type MACPayload")) } #[cfg(feature = "crypto")] fn calculate_downlink_data_mic( &self, mac_version: MACVersion, conf_f_cnt: u32, s_nwk_s_int_key: &AES128Key, ) -> Result<[u8; 4]> { if let Payload::MACPayload(pl) = &self.payload { // set to 0 if the downlink does not contain an ack or in case of LoRaWAN 1.0 let mut conf_f_cnt = conf_f_cnt; if mac_version == MACVersion::LoRaWAN1_0 || !pl.fhdr.f_ctrl.ack { conf_f_cnt = 0; } // truncate to 16 lsb let conf_f_cnt = (conf_f_cnt % (1 << 16)) as u16; // mic bytes let mut mic_bytes = Vec::new(); mic_bytes.extend_from_slice(&self.mhdr.to_le_bytes()); mic_bytes.extend_from_slice(&self.payload.to_vec()?); // b0 let mut b0: [u8; 16] = [0; 16]; b0[0] = 0x49; b0[1..3].clone_from_slice(&conf_f_cnt.to_le_bytes()); b0[5] = 0x01; b0[6..10].clone_from_slice(&pl.fhdr.devaddr.to_le_bytes()); b0[10..14].clone_from_slice(&pl.fhdr.f_cnt.to_le_bytes()); b0[15] = mic_bytes.len() as u8; let mut mac = Cmac::::new_from_slice(&s_nwk_s_int_key.to_bytes()).unwrap(); mac.update(&b0); mac.update(&mic_bytes); let hash = mac.finalize().into_bytes(); if hash.len() < 4 { return Err(anyhow!("hash is less than 4 bytes")); } let mut mic: [u8; 4] = [0; 4]; mic.clone_from_slice(&hash[0..4]); return Ok(mic); } Err(anyhow!("payload must be of type MACPayload")) } #[cfg(feature = "crypto")] fn calculate_upink_join_mic(&self, key: &AES128Key) -> Result<[u8; 4]> { // mic bytes let mut mic_bytes = Vec::new(); mic_bytes.extend_from_slice(&self.mhdr.to_le_bytes()); mic_bytes.extend_from_slice(&self.payload.to_vec()?); let mut mac = Cmac::::new_from_slice(&key.to_bytes()).unwrap(); mac.update(&mic_bytes); let hash = mac.finalize().into_bytes(); if hash.len() < 4 { return Err(anyhow!("hash is less than 4 bytes")); } let mut mic: [u8; 4] = [0; 4]; mic.clone_from_slice(&hash[0..4]); Ok(mic) } #[cfg(feature = "crypto")] fn calculate_downlink_join_mic( &self, join_req_type: JoinType, join_eui: &EUI64, dev_nonce: u16, key: &AES128Key, ) -> Result<[u8; 4]> { if let Payload::JoinAccept(pl) = &self.payload { let mut mic_bytes = Vec::new(); // LoRaWAN 1.1 if pl.dl_settings.opt_neg { mic_bytes.push(match join_req_type { JoinType::Join => 0xff, JoinType::RejoinType0 => 0x00, JoinType::RejoinType1 => 0x01, JoinType::RejoinType2 => 0x02, }); mic_bytes.extend_from_slice(&join_eui.to_le_bytes()); mic_bytes.extend_from_slice(&dev_nonce.to_le_bytes()); } mic_bytes.extend_from_slice(&self.mhdr.to_le_bytes()); // JoinNonce | NetID | DevAddr | DLSettings | RxDelay | CFList mic_bytes.extend_from_slice(&pl.to_vec()?); let mut mac = Cmac::::new_from_slice(&key.to_bytes()).unwrap(); mac.update(&mic_bytes); let hash = mac.finalize().into_bytes(); if hash.len() < 4 { return Err(anyhow!("hash is less than 4 bytes")); } let mut mic: [u8; 4] = [0; 4]; mic.clone_from_slice(&hash[0..4]); return Ok(mic); } Err(anyhow!("payload must be of type JoinAcceptPayload")) } } /// Encrypt f_opts mac-command data. /// For uplink: /// Set the a_fcnt_down to false and use the f_cnt_up as f_cnt. /// For downlink if f_port is unset or equal to 0: /// Set the a_fcnt_down to false and use the n_fcnt_down as f_cnt. /// For downlink if f_port > 0: /// Set the a_fcnt_down to true and use the a_f_cnt_down as f_cnt. #[cfg(feature = "crypto")] pub fn encrypt_f_opts( nwk_s_enc_key: &AES128Key, a_fcnt_down: bool, uplink: bool, devaddr: &DevAddr, f_cnt: u32, data: &[u8], ) -> Result> { use aes::cipher::KeyInit; if data.len() > 15 { return Err(anyhow!("max size of f_opts is 15 bytes")); } let key_bytes = nwk_s_enc_key.to_bytes(); let key = GenericArray::from_slice(&key_bytes); let cipher = Aes128::new(key); let mut a = vec![0; 16]; a[0] = 0x01; if a_fcnt_down { a[4] = 0x02; } else { a[4] = 0x01; } if !uplink { a[5] = 0x01; } a[6..10].clone_from_slice(&devaddr.to_le_bytes()); a[10..14].clone_from_slice(&f_cnt.to_le_bytes()); a[15] = 0x01; let block = Block::from_mut_slice(&mut a); cipher.encrypt_block(block); let mut out = vec![0; data.len()]; for i in 0..data.len() { out[i] = data[i] ^ block[i]; } Ok(out) } /// Encrypt (and decrypt) the frm_payload. /// Note that the same function is used for encryption and decryption. #[cfg(feature = "crypto")] pub fn encrypt_frm_payload( key: &AES128Key, uplink: bool, devaddr: &DevAddr, f_cnt: u32, data: &[u8], ) -> Result> { use aes::cipher::KeyInit; let mut data = data.to_vec(); let data_len = data.len(); // make pt length multiple of 16 if data.len() % 16 != 0 { data.append(&mut vec![0; 16 - (data.len() % 16)]); } let key_bytes = key.to_bytes(); let key = GenericArray::from_slice(&key_bytes); let cipher = Aes128::new(key); let mut a = vec![0; 16]; a[0] = 0x01; if !uplink { a[5] = 0x01; } a[6..10].clone_from_slice(&devaddr.to_le_bytes()); a[10..14].clone_from_slice(&f_cnt.to_le_bytes()); for i in 0..(data.len() / 16) { a[15] = (i + 1) as u8; let mut block = Block::clone_from_slice(&a); cipher.encrypt_block(&mut block); for j in 0..16 { data[(i * 16) + j] ^= block[j]; } } Ok(data[0..data_len].to_vec()) } fn is_uplink(m_type: MType) -> bool { match m_type { MType::JoinRequest | MType::UnconfirmedDataUp | MType::ConfirmedDataUp | MType::RejoinRequest => true, MType::JoinAccept | MType::UnconfirmedDataDown | MType::ConfirmedDataDown => false, MType::Proprietary => false, } } fn decode_frm_payload(pl: &mut MACPayload, uplink: bool, f_port: u8, b: Vec) -> Result<()> { if f_port == 0 { let mut macs = MACCommandSet::new(vec![MACCommand::Raw(b)]); macs.decode_from_raw(uplink)?; pl.frm_payload = Some(FRMPayload::MACCommandSet(macs)); } else if f_port == LA_FPORT_RELAY && uplink { pl.frm_payload = Some(FRMPayload::ForwardUplinkReq(ForwardUplinkReq::from_slice( &b, )?)); } else if f_port == LA_FPORT_RELAY && !uplink { pl.frm_payload = Some(FRMPayload::ForwardDownlinkReq( ForwardDownlinkReq::from_slice(&b)?, )); } else { pl.frm_payload = Some(FRMPayload::Raw(b)); } Ok(()) } #[cfg(test)] mod tests { use std::str::FromStr; use super::super::eui64::EUI64; use super::super::mhdr::Major; use super::super::payload::JoinRequestPayload; use super::*; struct PhyPayloadTest { phy: PhyPayload, bytes: Vec, } #[test] fn test_proprietary() { let tests = vec![ PhyPayloadTest { phy: PhyPayload { mhdr: MHDR { m_type: MType::Proprietary, major: Major::LoRaWANR1, }, payload: Payload::Raw(vec![]), mic: None, }, bytes: vec![0xe0], }, PhyPayloadTest { phy: PhyPayload { mhdr: MHDR { m_type: MType::Proprietary, major: Major::LoRaWANR1, }, payload: Payload::Raw(vec![0x01, 0x02, 0x03]), mic: None, }, bytes: vec![0xe0, 0x01, 0x02, 0x03], }, ]; for tst in tests { assert_eq!(tst.bytes, tst.phy.to_vec().unwrap()); assert_eq!(tst.phy, PhyPayload::from_slice(&tst.bytes).unwrap()); } } #[test] // No need to test all the different mtypes, this is handled by the Payload type. fn test_non_proprietary() { let tests = vec![PhyPayloadTest { phy: PhyPayload { mhdr: MHDR { m_type: MType::JoinRequest, major: Major::LoRaWANR1, }, payload: Payload::JoinRequest(JoinRequestPayload { join_eui: EUI64::from_str("0102030405060708").unwrap(), dev_eui: EUI64::from_str("0807060504030201").unwrap(), dev_nonce: 1024, }), mic: Some([0x01, 0x02, 0x03, 0x04]), }, bytes: vec![ 0x00, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04, ], }]; for tst in tests { assert_eq!(tst.bytes, tst.phy.to_vec().unwrap()); assert_eq!(tst.phy, PhyPayload::from_slice(&tst.bytes).unwrap()); } } }