use heapless::Vec;
use panweave_security::cipher::BlockCipher;
use panweave_types::{ExtendedAddress, Key128, KeySequenceNumber};
use crate::auth;
use crate::tlv::Psk;
use crate::tunnel::SessionKind;
const APS_TRANSPORT_KEY: u8 = 0x05;
const STANDARD_NETWORK_KEY: u8 = 0x01;
const KEY_TRANSPORT_KEY: u8 = 0x02;
#[derive(Clone, Debug)]
pub struct PastKey {
pub sequence: KeySequenceNumber,
pub key: Key128,
}
#[derive(Clone, Debug, Default)]
pub struct PastNetworkKeys<const N: usize> {
keys: Vec<PastKey, N>,
}
impl<const N: usize> PastNetworkKeys<N> {
pub const ENTRY_LEN: usize = 17;
pub const fn new() -> Self {
PastNetworkKeys { keys: Vec::new() }
}
pub fn record(&mut self, sequence: KeySequenceNumber, key: Key128) {
if let Some(e) = self.keys.iter_mut().find(|e| e.sequence == sequence) {
e.key = key;
return;
}
if self.keys.is_full() && !self.keys.is_empty() {
let _ = self.keys.remove(0);
}
let _ = self.keys.push(PastKey { sequence, key });
}
pub fn get(&self, sequence: KeySequenceNumber) -> Option<&Key128> {
self.keys
.iter()
.find(|e| e.sequence == sequence)
.map(|e| &e.key)
}
pub fn remove(&mut self, sequence: KeySequenceNumber) -> bool {
match self.keys.iter().position(|e| e.sequence == sequence) {
Some(i) => {
let _ = self.keys.remove(i);
true
}
None => false,
}
}
pub fn iter(&self) -> impl Iterator<Item = &PastKey> {
self.keys.iter()
}
pub fn len(&self) -> usize {
self.keys.len()
}
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
pub fn basic_key<C: BlockCipher>(
&self,
zvd: ExtendedAddress,
sequence: KeySequenceNumber,
) -> Option<Key128> {
self.get(sequence).map(|k| auth::basic_key::<C>(zvd, k))
}
pub fn encode(&self, out: &mut [u8]) -> Option<usize> {
let n = self.keys.len() * Self::ENTRY_LEN;
let out = out.get_mut(..n)?;
for (chunk, e) in out.chunks_exact_mut(Self::ENTRY_LEN).zip(self.keys.iter()) {
chunk[0] = e.sequence.0;
chunk[1..].copy_from_slice(e.key.as_bytes());
}
Some(n)
}
pub fn decode(bytes: &[u8]) -> Self {
let mut s = Self::new();
for chunk in bytes.chunks_exact(Self::ENTRY_LEN) {
let mut k = [0u8; 16];
k.copy_from_slice(&chunk[1..]);
s.record(KeySequenceNumber(chunk[0]), Key128::from_bytes(k));
}
s
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SessionClass {
Provisioning,
Basic,
Admin,
Limited,
}
impl SessionClass {
pub fn classify(
psk: Psk,
peer_key_sequence: Option<u8>,
active_sequence: KeySequenceNumber,
) -> Self {
match psk {
Psk::AdminAuthorization => SessionClass::Admin,
Psk::BasicAuthorization => {
if peer_key_sequence.is_some_and(|s| s != active_sequence.0) {
SessionClass::Limited
} else {
SessionClass::Basic
}
}
Psk::SymmetricToken | Psk::InstallCode | Psk::Passcode | Psk::Anonymous => {
SessionClass::Provisioning
}
}
}
pub const fn tunnel_kind(self) -> SessionKind {
match self {
SessionClass::Provisioning | SessionClass::Limited => SessionKind::ZvdProvisioning,
SessionClass::Basic | SessionClass::Admin => SessionKind::Authorized,
}
}
pub const fn allows_commissioning(self) -> bool {
matches!(self, SessionClass::Admin)
}
pub const fn allows_basic_key_translation(self) -> bool {
matches!(self, SessionClass::Limited)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Forwarding {
Forward,
Decline,
}
pub fn forwarding_decision(aps_frame: &[u8]) -> Forwarding {
let Some(&fc) = aps_frame.first() else {
return Forwarding::Forward;
};
if fc & 0x03 != 0x01 {
return Forwarding::Forward;
}
let mut pos = 2;
if fc & 0x80 != 0 {
let ext = aps_frame.get(2).copied().unwrap_or(0);
pos += if ext.trailing_zeros() >= 2 { 1 } else { 3 };
}
if fc & 0x20 == 0 {
let declined = aps_frame.get(pos) == Some(&APS_TRANSPORT_KEY)
&& aps_frame.get(pos + 1) == Some(&STANDARD_NETWORK_KEY);
return if declined {
Forwarding::Decline
} else {
Forwarding::Forward
};
}
match aps_frame.get(pos) {
Some(control) if (control >> 3) & 0x03 == KEY_TRANSPORT_KEY => Forwarding::Decline,
_ => Forwarding::Forward,
}
}
#[cfg(test)]
mod tests {
use super::*;
use panweave_security::cipher::SoftwareAes;
const ZVD: ExtendedAddress = ExtendedAddress(0x001F_EE00_0000_0001);
#[test]
fn past_keys_are_kept_and_derive_basic_keys() {
let mut past: PastNetworkKeys<2> = PastNetworkKeys::new();
assert!(past.is_empty());
let k1 = Key128::from_bytes([1; 16]);
let k2 = Key128::from_bytes([2; 16]);
let k3 = Key128::from_bytes([3; 16]);
past.record(KeySequenceNumber(1), k1.clone());
past.record(KeySequenceNumber(2), k2.clone());
assert_eq!(past.len(), 2);
past.record(KeySequenceNumber(3), k3.clone());
assert!(past.get(KeySequenceNumber(1)).is_none());
assert_eq!(past.get(KeySequenceNumber(2)), Some(&k2));
assert_eq!(past.get(KeySequenceNumber(3)), Some(&k3));
assert_eq!(
past.basic_key::<SoftwareAes>(ZVD, KeySequenceNumber(2)),
Some(auth::basic_key::<SoftwareAes>(ZVD, &k2))
);
assert!(
past.basic_key::<SoftwareAes>(ZVD, KeySequenceNumber(1))
.is_none()
);
past.record(KeySequenceNumber(3), k1.clone());
assert_eq!(past.get(KeySequenceNumber(3)), Some(&k1));
assert_eq!(past.len(), 2);
let mut buf = [0u8; 64];
let n = past.encode(&mut buf).unwrap();
assert_eq!(n, 34);
let back: PastNetworkKeys<2> = PastNetworkKeys::decode(&buf[..n]);
assert_eq!(back.get(KeySequenceNumber(2)), Some(&k2));
assert_eq!(back.get(KeySequenceNumber(3)), Some(&k1));
assert!(past.encode(&mut buf[..10]).is_none());
assert!(past.remove(KeySequenceNumber(2)));
assert!(!past.remove(KeySequenceNumber(2)));
}
#[test]
fn sessions_are_classified() {
let active = KeySequenceNumber(5);
assert_eq!(
SessionClass::classify(Psk::BasicAuthorization, None, active),
SessionClass::Basic
);
assert_eq!(
SessionClass::classify(Psk::BasicAuthorization, Some(5), active),
SessionClass::Basic
);
let limited = SessionClass::classify(Psk::BasicAuthorization, Some(4), active);
assert_eq!(limited, SessionClass::Limited);
assert_eq!(limited.tunnel_kind(), SessionKind::ZvdProvisioning);
assert!(!limited.allows_commissioning());
assert!(limited.allows_basic_key_translation());
let admin = SessionClass::classify(Psk::AdminAuthorization, Some(4), active);
assert_eq!(admin, SessionClass::Admin);
assert!(admin.allows_commissioning());
assert_eq!(admin.tunnel_kind(), SessionKind::Authorized);
assert!(!admin.allows_basic_key_translation());
assert_eq!(
SessionClass::classify(Psk::Anonymous, None, active),
SessionClass::Provisioning
);
assert_eq!(
SessionClass::classify(Psk::InstallCode, Some(1), active).tunnel_kind(),
SessionKind::ZvdProvisioning
);
}
#[test]
fn transport_key_messages_are_not_tunnelled() {
assert_eq!(
forwarding_decision(&[0x01, 0x10, 0x05, 0x01, 0x00]),
Forwarding::Decline
);
assert_eq!(
forwarding_decision(&[0x01, 0x10, 0x05, 0x04]),
Forwarding::Forward
);
assert_eq!(
forwarding_decision(&[0x01, 0x10, 0x0E, 0x01]),
Forwarding::Forward
);
assert_eq!(
forwarding_decision(&[0x21, 0x10, 0x15, 0, 0, 0, 0]),
Forwarding::Decline
);
assert_eq!(
forwarding_decision(&[0x21, 0x10, 0x1D, 0, 0, 0, 0]),
Forwarding::Forward
);
assert_eq!(
forwarding_decision(&[0x81, 0x10, 0x00, 0x05, 0x01]),
Forwarding::Decline
);
assert_eq!(
forwarding_decision(&[0x00, 0x01, 0x06, 0x00, 0x04, 0x01, 0x01, 0x10]),
Forwarding::Forward
);
assert_eq!(forwarding_decision(&[]), Forwarding::Forward);
assert_eq!(forwarding_decision(&[0x01]), Forwarding::Forward);
}
}