use aes::cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt}; use aes::{Aes128, Block}; use anyhow::Result; use cmac::{Cmac, Mac}; use serde::Serialize; use super::aes128::AES128Key; use super::devaddr::DevAddr; use super::eui64::EUI64; use super::maccommand::{MACCommand, MACCommandSet}; use super::mhdr::{MType, MHDR}; use super::payload::{FRMPayload, JoinAcceptPayload, JoinType, Payload}; #[derive(PartialEq, Clone, Copy, 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); /// ``` #[derive(Debug, PartialEq, Clone, 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. 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. 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. 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(()) } 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. 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. 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. 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. 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. 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. 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(()); } return 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. 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(()); } return Err(anyhow!("payload must be of type Raw")); } /// Encrypt the f_opts with the given key. 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(()); } return 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. 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(()) } /// Encrypt the frm_payload with the given key. 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(()); } return Err(anyhow!("payload must be of type MACPayload")); } /// Decrypt the frm_payload with the given key. 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)?; if pl.f_port.is_some() && pl.f_port.unwrap() == 0 { let mut macs = MACCommandSet::new(vec![MACCommand::Raw(data)]); macs.decode_from_raw(uplink)?; pl.frm_payload = Some(FRMPayload::MACCommandSet(macs)); } else { pl.frm_payload = Some(FRMPayload::Raw(data)); } return Ok(()); } return Err(anyhow!("payload must be of type MACPayload")); } 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); } } return Err(anyhow!("payload must be of type MACPayload")); } 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); } return Err(anyhow!("payload must be of type MACPayload")); } 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) } 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); } return 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. 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. 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, } } #[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()); } } }