#[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::{FType, 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,
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct PhyPayload {
pub mhdr: MHDR,
pub payload: Payload,
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();
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.f_type == FType::Proprietary {
return Ok(PhyPayload {
mhdr,
payload: Payload::from_slice(FType::Proprietary, &b[1..])?,
mic: None,
});
}
if b_len < 5 {
return Err(anyhow!(
"at least 5 bytes are required to decode PhyPayload"
));
}
let f_type = mhdr.f_type;
let mut mic: [u8; 4] = [0; 4];
mic.clone_from_slice(&b[b_len - 4..]);
Ok(PhyPayload {
mhdr,
payload: Payload::from_slice(f_type, &b[1..b_len - 4])?,
mic: Some(mic),
})
}
#[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(())
}
#[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)
}
#[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)
}
#[cfg(feature = "crypto")]
pub fn validate_uplink_data_micf(&self, f_nwk_s_int_key: &AES128Key) -> Result<bool> {
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)
}
#[cfg(feature = "crypto")]
pub fn set_join_request_mic(&mut self, key: &AES128Key) -> Result<()> {
self.mic = Some(self.calculate_upink_join_mic(key)?);
Ok(())
}
#[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)
}
#[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(())
}
#[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)
}
#[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"))
}
#[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 {
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"))
}
#[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.f_type);
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"))
}
#[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(())
}
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.f_type);
pl.fhdr.f_opts.decode_from_raw(uplink)?;
}
Ok(())
}
pub fn decode_frm_payload(&mut self) -> Result<()> {
if let Payload::MACPayload(pl) = &mut self.payload {
let uplink = is_uplink(self.mhdr.f_type);
let f_port = pl.f_port.unwrap_or(0);
let b = match &pl.frm_payload {
Some(FRMPayload::Raw(v)) => v.clone(),
_ => {
return Ok(());
}
};
return decode_frm_payload(pl, uplink, f_port, b);
}
Ok(())
}
#[cfg(feature = "crypto")]
pub fn encrypt_frm_payload(&mut self, key: &AES128Key) -> Result<()> {
if let Payload::MACPayload(pl) = &mut self.payload {
if pl.frm_payload.is_none() {
return Ok(());
}
let uplink = is_uplink(self.mhdr.f_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"))
}
#[cfg(feature = "crypto")]
pub fn decrypt_frm_payload(&mut self, key: &AES128Key) -> Result<()> {
if let Payload::MACPayload(pl) = &mut self.payload {
if pl.frm_payload.is_none() {
return Ok(());
}
let uplink = is_uplink(self.mhdr.f_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 {
let mut conf_f_cnt = conf_f_cnt;
if !pl.fhdr.f_ctrl.ack {
conf_f_cnt = 0;
}
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;
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);
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());
b0[15] = mic_bytes.len() as u8;
b1[15] = mic_bytes.len() as u8;
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 {
let mut conf_f_cnt = conf_f_cnt;
if mac_version == MACVersion::LoRaWAN1_0 || !pl.fhdr.f_ctrl.ack {
conf_f_cnt = 0;
}
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];
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]> {
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();
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());
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"))
}
}
#[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)
}
#[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();
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(f_type: FType) -> bool {
match f_type {
FType::JoinRequest
| FType::UnconfirmedDataUp
| FType::ConfirmedDataUp
| FType::RejoinRequest => true,
FType::JoinAccept | FType::UnconfirmedDataDown | FType::ConfirmedDataDown => false,
FType::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 {
f_type: FType::Proprietary,
major: Major::LoRaWANR1,
},
payload: Payload::Raw(vec![]),
mic: None,
},
bytes: vec![0xe0],
},
PhyPayloadTest {
phy: PhyPayload {
mhdr: MHDR {
f_type: FType::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]
fn test_non_proprietary() {
let tests = vec![PhyPayloadTest {
phy: PhyPayload {
mhdr: MHDR {
f_type: FType::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());
}
}
}