use lora_packet::{
AppEui, AppKey, AppNonce, AppSKey, Data, DevAddr, DevEui, DevNonce, Direction, DlSettings, Error, FCtrl, FNwkSIntKey,
JSEncKey, JSIntKey, JoinAccept, JoinEui, JoinNonce, JoinRequest, JoinServerKeys, LoraPacket, LoraPacketBuilder,
LorawanVersion, MType, Mhdr, NetId, NwkKey, NwkSEncKey, NwkSKey, Payload, RejoinRequest, RootWorSKey, SNwkSIntKey,
SessionKeys10, SessionKeys11, V1_0MicKeys, V1_1MicKeys, WorKeys, WorSEncKey, WorSIntKey, WorSessionKeys,
};
const fn assert_send_sync<T: Send + Sync>() {}
const fn assert_send_sync_static<T: Send + Sync + 'static>() {}
#[test]
fn key_newtypes_are_send_sync() {
assert_send_sync::<AppKey>();
assert_send_sync::<NwkKey>();
assert_send_sync::<AppSKey>();
assert_send_sync::<NwkSKey>();
assert_send_sync::<FNwkSIntKey>();
assert_send_sync::<SNwkSIntKey>();
assert_send_sync::<NwkSEncKey>();
assert_send_sync::<JSIntKey>();
assert_send_sync::<JSEncKey>();
assert_send_sync::<RootWorSKey>();
assert_send_sync::<WorSIntKey>();
assert_send_sync::<WorSEncKey>();
}
#[test]
fn key_newtypes_are_static() {
assert_send_sync_static::<AppKey>();
assert_send_sync_static::<NwkKey>();
assert_send_sync_static::<AppSKey>();
assert_send_sync_static::<NwkSKey>();
assert_send_sync_static::<FNwkSIntKey>();
assert_send_sync_static::<SNwkSIntKey>();
assert_send_sync_static::<NwkSEncKey>();
assert_send_sync_static::<JSIntKey>();
assert_send_sync_static::<JSEncKey>();
assert_send_sync_static::<RootWorSKey>();
assert_send_sync_static::<WorSIntKey>();
assert_send_sync_static::<WorSEncKey>();
}
#[test]
fn identifier_newtypes_are_send_sync() {
assert_send_sync::<DevAddr>();
assert_send_sync::<DevEui>();
assert_send_sync::<AppEui>();
assert_send_sync::<JoinEui>();
assert_send_sync::<NetId>();
assert_send_sync::<DevNonce>();
assert_send_sync::<AppNonce>();
assert_send_sync::<JoinNonce>();
}
#[test]
fn bitfield_wrappers_are_send_sync() {
assert_send_sync::<Mhdr>();
assert_send_sync::<FCtrl>();
assert_send_sync::<DlSettings>();
}
#[test]
fn error_is_send_sync() {
assert_send_sync::<Error>();
assert_send_sync_static::<Error>();
}
#[test]
fn enums_are_send_sync() {
assert_send_sync::<MType>();
assert_send_sync::<Direction>();
assert_send_sync::<LorawanVersion>();
assert_send_sync::<Payload>();
assert_send_sync::<RejoinRequest>();
}
#[test]
fn packet_and_payload_structs_are_send_sync() {
assert_send_sync::<LoraPacket>();
assert_send_sync::<LoraPacketBuilder>();
assert_send_sync::<JoinRequest>();
assert_send_sync::<JoinAccept>();
assert_send_sync::<Data>();
}
#[test]
fn derivation_outputs_are_send_sync() {
assert_send_sync::<SessionKeys10>();
assert_send_sync::<SessionKeys11>();
assert_send_sync::<JoinServerKeys>();
assert_send_sync::<WorSessionKeys>();
assert_send_sync::<WorKeys>();
}
#[test]
fn mic_key_bundles_are_send_sync() {
assert_send_sync::<V1_0MicKeys<'static>>();
assert_send_sync::<V1_1MicKeys<'static>>();
fn check<'a>(_: core::marker::PhantomData<&'a ()>) {
assert_send_sync::<V1_0MicKeys<'a>>();
assert_send_sync::<V1_1MicKeys<'a>>();
}
check(core::marker::PhantomData);
}
#[cfg(feature = "std")]
mod runtime {
use super::*;
use std::sync::Arc;
use std::thread;
fn key_from_hex(s: &str) -> [u8; 16] {
let v = hex::decode(s).expect("valid hex");
let mut arr = [0u8; 16];
arr.copy_from_slice(&v);
arr
}
#[test]
fn parse_verify_decrypt_in_parallel_threads() {
let wire_hex = "40F17DBE4900020001954378762B11FF0D";
let wire: Arc<Vec<u8>> = Arc::new(hex::decode(wire_hex).expect("valid hex"));
let nwk_s_key = Arc::new(NwkSKey::new(key_from_hex("44024241ed4ce9a68c6a8bc055233fd3")));
let app_s_key = Arc::new(AppSKey::new(key_from_hex("ec925802ae430ca77fd3dd73cb2cc588")));
let thread_count = 16;
let iterations = 64;
let handles: Vec<_> = (0..thread_count)
.map(|tid| {
let wire = Arc::clone(&wire);
let nwk_s_key = Arc::clone(&nwk_s_key);
let app_s_key = Arc::clone(&app_s_key);
thread::spawn(move || {
for i in 0..iterations {
let packet = LoraPacket::from_wire(&wire).unwrap_or_else(|e| panic!("thread {tid} iter {i}: parse: {e}"));
let keys = V1_0MicKeys {
nwk_s_key: Some(&nwk_s_key),
..Default::default()
};
let ok = packet
.verify_mic_v1_0(&keys)
.unwrap_or_else(|e| panic!("thread {tid} iter {i}: mic: {e}"));
assert!(ok, "thread {tid} iter {i}: MIC verification failed");
let data = packet.as_data().expect("data frame");
let plaintext = data
.decrypt_payload(&app_s_key, &nwk_s_key, 0)
.unwrap_or_else(|e| panic!("thread {tid} iter {i}: decrypt: {e}"));
assert_eq!(plaintext, b"test", "thread {tid} iter {i}: wrong plaintext");
}
})
})
.collect();
for h in handles {
h.join().expect("worker thread panicked");
}
}
#[test]
fn build_and_encrypt_in_parallel_threads() {
let app_s_key = Arc::new(AppSKey::new([0x11; 16]));
let nwk_s_key = Arc::new(NwkSKey::new([0x22; 16]));
let dev_addr = DevAddr::new([0xa1, 0xb2, 0xc3, 0xd4]);
let handles: Vec<_> = (0..8u16)
.map(|tid| {
let app_s_key = Arc::clone(&app_s_key);
let nwk_s_key = Arc::clone(&nwk_s_key);
thread::spawn(move || {
let f_cnt: u16 = tid * 1_000 + 1;
let payload = format!("hello-{tid}");
let built = LoraPacket::builder()
.data(Direction::Uplink, false)
.dev_addr(dev_addr)
.f_ctrl(FCtrl(0))
.f_cnt(f_cnt)
.f_port(1)
.payload(payload.as_bytes())
.sign_and_encrypt(&app_s_key, &nwk_s_key)
.expect("build");
let wire = built.to_wire();
let reparsed = LoraPacket::from_wire(&wire).expect("re-parse");
let mic_keys = V1_0MicKeys {
nwk_s_key: Some(&nwk_s_key),
..Default::default()
};
assert!(reparsed.verify_mic_v1_0(&mic_keys).expect("verify"));
let plaintext = reparsed
.as_data()
.expect("data frame")
.decrypt_payload(&app_s_key, &nwk_s_key, 0)
.expect("decrypt");
assert_eq!(plaintext, payload.as_bytes());
})
})
.collect();
for h in handles {
h.join().expect("worker thread panicked");
}
}
#[test]
fn derive_session_keys_in_parallel_threads() {
let app_key = Arc::new(AppKey::new([0x33; 16]));
let nwk_key = Arc::new(NwkKey::new([0x44; 16]));
let net_id = NetId::new([0x12, 0x34, 0x56]);
let join_eui = AppEui::new([0xAA; 8]);
let app_nonce = AppNonce::new([0x01, 0x02, 0x03]);
let dev_nonce = DevNonce::new([0x04, 0x05]);
let handles: Vec<_> = (0..8)
.map(|_| {
let app_key = Arc::clone(&app_key);
let nwk_key = Arc::clone(&nwk_key);
thread::spawn(move || {
let v10 = SessionKeys10::derive(&app_key, &net_id, &app_nonce, &dev_nonce);
let v11 = SessionKeys11::derive(&app_key, &nwk_key, &join_eui, &app_nonce, &dev_nonce);
(v10, v11)
})
})
.collect();
let results: Vec<_> = handles
.into_iter()
.map(|h| h.join().expect("worker panicked"))
.collect();
let (first_v10, first_v11) = &results[0];
for (i, (v10, v11)) in results.iter().enumerate().skip(1) {
assert_eq!(
v10.app_s_key.as_bytes(),
first_v10.app_s_key.as_bytes(),
"thread {i}: v1.0 AppSKey diverged",
);
assert_eq!(
v10.nwk_s_key.as_bytes(),
first_v10.nwk_s_key.as_bytes(),
"thread {i}: v1.0 NwkSKey diverged",
);
assert_eq!(
v11.app_s_key.as_bytes(),
first_v11.app_s_key.as_bytes(),
"thread {i}: v1.1 AppSKey diverged",
);
assert_eq!(
v11.f_nwk_s_int_key.as_bytes(),
first_v11.f_nwk_s_int_key.as_bytes(),
"thread {i}: v1.1 FNwkSIntKey diverged",
);
assert_eq!(
v11.s_nwk_s_int_key.as_bytes(),
first_v11.s_nwk_s_int_key.as_bytes(),
"thread {i}: v1.1 SNwkSIntKey diverged",
);
assert_eq!(
v11.nwk_s_enc_key.as_bytes(),
first_v11.nwk_s_enc_key.as_bytes(),
"thread {i}: v1.1 NwkSEncKey diverged",
);
}
}
}