use panweave_codec::Writer;
use panweave_codec::tlv::{TlvIter, write_tlv};
pub const NPDU_MESSAGE: u8 = 0x00;
pub const MAX_VALUE_LEN: usize = 0xF2;
pub const MAX_NPDU_LEN: usize = MAX_VALUE_LEN - 2;
pub const FLAG_SECURITY_ENABLE: u8 = 0x01;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SessionKind {
ZvdProvisioning,
ZddProvisioning,
Authorized,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct NpduMessage<'a> {
pub assume_security: bool,
pub npdu: &'a [u8],
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TunnelError {
Malformed,
TooLong,
Dropped,
}
impl<'a> NpduMessage<'a> {
pub fn parse(value: &'a [u8]) -> Result<Self, TunnelError> {
let (&flags, rest) = value.split_first().ok_or(TunnelError::Malformed)?;
let (&len, rest) = rest.split_first().ok_or(TunnelError::Malformed)?;
if flags & !FLAG_SECURITY_ENABLE != 0 {
return Err(TunnelError::Malformed);
}
let npdu = rest.get(..usize::from(len)).ok_or(TunnelError::Malformed)?;
Ok(NpduMessage {
assume_security: flags & FLAG_SECURITY_ENABLE != 0,
npdu,
})
}
pub fn encode(&self, out: &mut [u8]) -> Result<usize, TunnelError> {
let len = u8::try_from(self.npdu.len())
.ok()
.filter(|&l| usize::from(l) <= MAX_NPDU_LEN)
.ok_or(TunnelError::TooLong)?;
let mut value = [0u8; MAX_VALUE_LEN];
value[0] = u8::from(self.assume_security);
value[1] = len;
value
.get_mut(2..2 + self.npdu.len())
.ok_or(TunnelError::TooLong)?
.copy_from_slice(self.npdu);
let mut w = Writer::new(out);
write_tlv(
&mut w,
NPDU_MESSAGE,
value
.get(..2 + self.npdu.len())
.ok_or(TunnelError::TooLong)?,
)
.map_err(|_| TunnelError::TooLong)?;
Ok(w.position())
}
}
pub fn preprocess(
payload: &[u8],
session: SessionKind,
) -> impl Iterator<Item = Result<NpduMessage<'_>, TunnelError>> {
TlvIter::new(payload).filter_map(move |item| match item {
Err(_) => Some(Err(TunnelError::Malformed)),
Ok(tlv) if tlv.tag != NPDU_MESSAGE => None,
Ok(tlv) => Some(NpduMessage::parse(tlv.value).and_then(|m| {
if m.assume_security && session == SessionKind::ZvdProvisioning {
Err(TunnelError::Dropped)
} else {
Ok(m)
}
})),
})
}
#[cfg(test)]
mod tests {
use super::*;
type Got<'a> = heapless::Vec<Result<NpduMessage<'a>, TunnelError>, 4>;
#[test]
fn npdu_message_round_trip_and_session_rule() {
let npdu = [0x08, 0x02, 0x01, 0x00, 0x02, 0x00, 0x1e, 0x2a, 0xaa];
let m = NpduMessage {
assume_security: true,
npdu: &npdu,
};
let mut out = [0u8; 32];
let n = m.encode(&mut out).unwrap();
assert_eq!(n, 2 + 2 + npdu.len());
assert_eq!(&out[..4], &[0x00, 10, 0x01, 9]);
let got: Got<'_> = preprocess(&out[..n], SessionKind::Authorized).collect();
assert_eq!(got.as_slice(), &[Ok(m)]);
let mut two = [0u8; 64];
two[..n].copy_from_slice(&out[..n]);
let m2 = NpduMessage {
assume_security: false,
npdu: &npdu[..3],
};
let n2 = m2.encode(&mut two[n..]).unwrap();
let got: Got<'_> = preprocess(&two[..n + n2], SessionKind::ZvdProvisioning).collect();
assert_eq!(got.as_slice(), &[Err(TunnelError::Dropped), Ok(m2)]);
assert_eq!(
NpduMessage::parse(&[0x00, 0x01, 0xaa, 0xbb]).unwrap().npdu,
&[0xaa]
);
assert_eq!(
NpduMessage::parse(&[0x00, 0x02, 0xaa]),
Err(TunnelError::Malformed)
);
assert_eq!(
NpduMessage::parse(&[0x02, 0x00]),
Err(TunnelError::Malformed)
);
let big = [0u8; MAX_NPDU_LEN + 1];
assert_eq!(
NpduMessage {
assume_security: false,
npdu: &big
}
.encode(&mut [0u8; 300]),
Err(TunnelError::TooLong)
);
let got: Got<'_> = preprocess(&[0x00, 0x05, 0x00], SessionKind::Authorized).collect();
assert_eq!(got.as_slice(), &[Err(TunnelError::Malformed)]);
}
}