chirpstack/lrwn/src/phy_payload.rs
2023-05-22 11:04:13 +01:00

1319 lines
43 KiB
Rust

#[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<Vec<u8>> {
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<Self> {
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<bool> {
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<bool> {
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<bool> {
// 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<bool> {
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<bool> {
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::<Aes128>::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::<Aes128>::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::<Aes128>::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::<Aes128>::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::<Aes128>::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<Vec<u8>> {
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<Vec<u8>> {
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<u8>) -> 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<u8>,
}
#[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());
}
}
}