use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum MType {
JoinRequest = 0b000,
JoinAccept = 0b001,
UnconfirmedDataUp = 0b010,
UnconfirmedDataDown = 0b011,
ConfirmedDataUp = 0b100,
ConfirmedDataDown = 0b101,
RejoinRequest = 0b110,
Proprietary = 0b111,
}
impl MType {
pub const fn from_mhdr(mhdr: u8) -> Result<Self> {
match (mhdr >> 5) & 0b111 {
0b000 => Ok(Self::JoinRequest),
0b001 => Ok(Self::JoinAccept),
0b010 => Ok(Self::UnconfirmedDataUp),
0b011 => Ok(Self::UnconfirmedDataDown),
0b100 => Ok(Self::ConfirmedDataUp),
0b101 => Ok(Self::ConfirmedDataDown),
0b110 => Ok(Self::RejoinRequest),
0b111 => Ok(Self::Proprietary),
n => Err(Error::InvalidMType(n)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Direction {
Uplink,
Downlink,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LorawanVersion {
V1_0,
V1_1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Mhdr(pub u8);
impl Mhdr {
pub const fn new(b: u8) -> Self {
Self(b)
}
pub const fn from_parts(m_type: MType, major: u8) -> Self {
Self(((m_type as u8) << 5) | (major & 0b11))
}
pub const fn m_type(&self) -> Result<MType> {
MType::from_mhdr(self.0)
}
pub const fn major(&self) -> u8 {
self.0 & 0b11
}
pub const fn as_byte(&self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FCtrl(pub u8);
impl FCtrl {
pub const fn new(b: u8) -> Self {
Self(b)
}
pub const fn adr(&self) -> bool {
self.0 & 0b1000_0000 != 0
}
pub const fn adr_ack_req(&self) -> bool {
self.0 & 0b0100_0000 != 0
}
pub const fn ack(&self) -> bool {
self.0 & 0b0010_0000 != 0
}
pub const fn f_pending(&self) -> bool {
self.0 & 0b0001_0000 != 0
}
pub const fn class_b(&self) -> bool {
self.0 & 0b0001_0000 != 0
}
pub const fn f_opts_len(&self) -> u8 {
self.0 & 0b0000_1111
}
pub const fn as_byte(&self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DlSettings(pub u8);
impl DlSettings {
pub const fn new(b: u8) -> Self {
Self(b)
}
pub const fn rx1_dr_offset(&self) -> u8 {
(self.0 >> 4) & 0b111
}
pub const fn rx2_data_rate(&self) -> u8 {
self.0 & 0b1111
}
pub const fn opt_neg(&self) -> bool {
self.0 & 0b1000_0000 != 0
}
pub const fn as_byte(&self) -> u8 {
self.0
}
}
macro_rules! id_newtype {
($(#[$meta:meta])* $name:ident, $len:expr) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct $name(pub [u8; $len]);
impl $name {
pub const fn new(bytes: [u8; $len]) -> Self {
Self(bytes)
}
pub fn from_slice(s: &[u8]) -> Result<Self> {
if s.len() != $len {
return Err(Error::InvalidIdentifierLength { expected: $len, got: s.len() });
}
let mut arr = [0u8; $len];
arr.copy_from_slice(s);
Ok(Self(arr))
}
pub const fn as_bytes(&self) -> &[u8; $len] {
&self.0
}
}
#[cfg(feature = "hex_base64")]
impl $name {
pub fn from_hex(s: &str) -> Result<Self> {
let bytes = hex::decode(s)?;
Self::from_slice(&bytes)
}
pub fn from_base64(s: &str) -> Result<Self> {
use base64::Engine as _;
let bytes = base64::engine::general_purpose::STANDARD.decode(s)?;
Self::from_slice(&bytes)
}
}
};
}
id_newtype!(
DevAddr, 4
);
id_newtype!(
DevEui, 8
);
id_newtype!(
AppEui, 8
);
pub use AppEui as JoinEui;
id_newtype!(
NetId, 3
);
id_newtype!(
DevNonce, 2
);
id_newtype!(
AppNonce, 3
);
pub use AppNonce as JoinNonce;
macro_rules! key_newtype {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Clone, PartialEq, Eq, Hash, zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
pub struct $name([u8; 16]);
impl $name {
pub const fn new(bytes: [u8; 16]) -> Self {
Self(bytes)
}
pub fn from_slice(s: &[u8]) -> Result<Self> {
if s.len() != 16 {
return Err(Error::InvalidKeyLength { expected: 16, got: s.len() });
}
let mut arr = [0u8; 16];
arr.copy_from_slice(s);
Ok(Self(arr))
}
pub const fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
}
impl core::fmt::Debug for $name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, concat!(stringify!($name), "(***)"))
}
}
#[cfg(feature = "hex_base64")]
impl $name {
pub fn from_hex(s: &str) -> Result<Self> {
let bytes = hex::decode(s)?;
Self::from_slice(&bytes)
}
pub fn from_base64(s: &str) -> Result<Self> {
use base64::Engine as _;
let bytes = base64::engine::general_purpose::STANDARD.decode(s)?;
Self::from_slice(&bytes)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for $name {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
serializer.serialize_str(&hex_encode_16(&self.0))
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for $name {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> core::result::Result<Self, D::Error> {
let s = <alloc::string::String as serde::Deserialize>::deserialize(deserializer)?;
let bytes = hex_decode_16(&s).map_err(serde::de::Error::custom)?;
Ok(Self(bytes))
}
}
};
}
#[cfg(feature = "serde")]
fn hex_encode_16(b: &[u8; 16]) -> alloc::string::String {
let mut s = alloc::string::String::with_capacity(32);
for byte in b {
use core::fmt::Write;
let _ = write!(s, "{byte:02x}");
}
s
}
#[cfg(feature = "serde")]
fn hex_decode_16(s: &str) -> core::result::Result<[u8; 16], &'static str> {
if s.len() != 32 {
return Err("expected 32 hex characters for a 16-byte key");
}
let mut out = [0u8; 16];
for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
let hi = hex_nibble(chunk[0])?;
let lo = hex_nibble(chunk[1])?;
out[i] = (hi << 4) | lo;
}
Ok(out)
}
#[cfg(feature = "serde")]
const fn hex_nibble(b: u8) -> core::result::Result<u8, &'static str> {
match b {
b'0'..=b'9' => Ok(b - b'0'),
b'a'..=b'f' => Ok(b - b'a' + 10),
b'A'..=b'F' => Ok(b - b'A' + 10),
_ => Err("invalid hex character"),
}
}
key_newtype!(
AppKey
);
key_newtype!(
NwkKey
);
key_newtype!(
AppSKey
);
key_newtype!(
NwkSKey
);
key_newtype!(
FNwkSIntKey
);
key_newtype!(
SNwkSIntKey
);
key_newtype!(
NwkSEncKey
);
key_newtype!(
JSIntKey
);
key_newtype!(
JSEncKey
);
key_newtype!(
RootWorSKey
);
key_newtype!(
WorSIntKey
);
key_newtype!(
WorSEncKey
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mtype_from_mhdr_unconfirmed_up() {
assert_eq!(MType::from_mhdr(0x40).unwrap(), MType::UnconfirmedDataUp);
}
#[test]
fn mtype_from_mhdr_join_request() {
assert_eq!(MType::from_mhdr(0x00).unwrap(), MType::JoinRequest);
}
#[test]
fn mtype_from_mhdr_join_accept() {
assert_eq!(MType::from_mhdr(0x20).unwrap(), MType::JoinAccept);
}
#[test]
fn mtype_from_mhdr_proprietary() {
assert_eq!(MType::from_mhdr(0xE0).unwrap(), MType::Proprietary);
}
#[test]
fn mtype_from_mhdr_ignores_low_bits() {
assert_eq!(MType::from_mhdr(0b010_00011).unwrap(), MType::UnconfirmedDataUp);
}
#[test]
fn mhdr_from_parts_data_up() {
let m = Mhdr::from_parts(MType::UnconfirmedDataUp, 0);
assert_eq!(m.as_byte(), 0x40);
assert_eq!(m.m_type().unwrap(), MType::UnconfirmedDataUp);
assert_eq!(m.major(), 0);
}
#[test]
fn fctrl_bits() {
let c = FCtrl(0b1010_0110);
assert!(c.adr());
assert!(!c.adr_ack_req());
assert!(c.ack());
assert!(!c.f_pending());
assert_eq!(c.f_opts_len(), 6);
}
#[test]
fn dlsettings_layout() {
let d = DlSettings(0b1011_0010);
assert!(d.opt_neg());
assert_eq!(d.rx1_dr_offset(), 0b011);
assert_eq!(d.rx2_data_rate(), 0b0010);
}
#[test]
fn dev_addr_from_slice_ok() {
let a = DevAddr::from_slice(&[0x49, 0xBE, 0x7D, 0xF1]).unwrap();
assert_eq!(a.as_bytes(), &[0x49, 0xBE, 0x7D, 0xF1]);
}
#[test]
fn dev_addr_from_slice_wrong_length() {
let e = DevAddr::from_slice(&[0x49, 0xBE, 0x7D]).unwrap_err();
match e {
Error::InvalidIdentifierLength { expected, got } => {
assert_eq!(expected, 4);
assert_eq!(got, 3);
}
_ => panic!("wrong error variant"),
}
}
#[test]
fn dev_eui_round_trip() {
let bytes = [0x33, 0x31, 0x38, 0x32, 0x74, 0x35, 0x69, 0x05];
let e = DevEui::new(bytes);
assert_eq!(e.as_bytes(), &bytes);
}
#[test]
fn join_eui_is_app_eui_alias() {
let a: AppEui = JoinEui::new([1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(a.as_bytes(), &[1, 2, 3, 4, 5, 6, 7, 8]);
}
#[test]
fn dev_nonce_two_bytes() {
let n = DevNonce::from_slice(&[0xF1, 0x8E]).unwrap();
assert_eq!(n.as_bytes(), &[0xF1, 0x8E]);
}
use alloc::format;
#[test]
fn app_key_from_slice_ok() {
let k = AppKey::from_slice(&[0u8; 16]).unwrap();
assert_eq!(k.as_bytes(), &[0u8; 16]);
}
#[test]
fn app_key_from_slice_wrong_length() {
let e = AppKey::from_slice(&[0u8; 15]).unwrap_err();
match e {
Error::InvalidKeyLength { expected, got } => {
assert_eq!(expected, 16);
assert_eq!(got, 15);
}
_ => panic!("wrong error variant"),
}
}
#[test]
fn key_debug_is_redacted() {
let k = AppSKey::new([0xAB; 16]);
let s = format!("{k:?}");
assert_eq!(s, "AppSKey(***)");
assert!(!s.contains("ab"));
}
#[test]
fn key_zeroize_wipes_bytes() {
use zeroize::Zeroize;
let mut k = NwkSKey::new([0xFFu8; 16]);
k.zeroize();
assert_eq!(k.as_bytes(), &[0u8; 16]);
}
#[test]
fn key_zeroize_on_drop() {
use zeroize::Zeroize;
let mut k = NwkSKey::new([0xff; 16]);
k.zeroize();
assert_eq!(k.as_bytes(), &[0u8; 16]);
}
}