use crate::crypto_suite::CryptoSuite;
use crate::error::Error;
use crate::msg_type::MsgType;
use crate::residency::ResidencyTag;
use crate::version::Version;
pub const HEADER_SIZE: usize = 42;
pub const MAC_SIZE: usize = 16;
pub const MIN_FRAME_SIZE: usize = HEADER_SIZE + MAC_SIZE;
const OFF_VERSION: usize = 0;
const OFF_MSG_TYPE: usize = 1;
const OFF_CRYPTO_SUITE: usize = 2;
const OFF_FLAGS: usize = 3;
const OFF_SENDER_ID: usize = 4;
const OFF_SENDER_ID_END: usize = 36;
const OFF_RESIDENCY: usize = 36;
const OFF_PAYLOAD_LEN: usize = 38;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Flags(u8);
impl Flags {
pub const NONE: Flags = Flags(0);
pub const COMPRESSED: u8 = 0b0000_0001;
pub const FRAGMENTED: u8 = 0b0000_0010;
pub const fn from_byte(b: u8) -> Flags {
Flags(b)
}
pub const fn as_byte(self) -> u8 {
self.0
}
pub const fn has(self, flag: u8) -> bool {
self.0 & flag != 0
}
pub const fn with(self, flag: u8) -> Flags {
Flags(self.0 | flag)
}
pub const fn without(self, flag: u8) -> Flags {
Flags(self.0 & !flag)
}
pub const fn has_unknown_bits(self) -> bool {
self.0 & 0b1111_1100 != 0
}
}
#[derive(Clone, Copy)]
pub struct EnvelopeRef<'a> {
buf: &'a [u8],
payload_len: u32,
}
impl<'a> EnvelopeRef<'a> {
pub fn parse(buf: &'a [u8]) -> Result<Self, Error> {
if buf.len() < MIN_FRAME_SIZE {
return Err(Error::BufferTooShort {
need: MIN_FRAME_SIZE,
have: buf.len(),
});
}
if Version::from_byte(buf[OFF_VERSION]).is_none() {
return Err(Error::UnknownVersion(buf[OFF_VERSION]));
}
if MsgType::from_byte(buf[OFF_MSG_TYPE]).is_none() {
let range = MsgType::range_of(buf[OFF_MSG_TYPE]);
if matches!(range, crate::msg_type::MsgRange::Unknown) {
return Err(Error::UnknownMsgType(buf[OFF_MSG_TYPE]));
}
}
if CryptoSuite::from_byte(buf[OFF_CRYPTO_SUITE]).is_none() {
return Err(Error::UnknownCryptoSuite(buf[OFF_CRYPTO_SUITE]));
}
let pl_bytes: [u8; 4] = [
buf[OFF_PAYLOAD_LEN],
buf[OFF_PAYLOAD_LEN + 1],
buf[OFF_PAYLOAD_LEN + 2],
buf[OFF_PAYLOAD_LEN + 3],
];
let payload_len = u32::from_be_bytes(pl_bytes);
let expected = HEADER_SIZE + payload_len as usize + MAC_SIZE;
if buf.len() < expected {
return Err(Error::BufferTooShort {
need: expected,
have: buf.len(),
});
}
if buf.len() > expected {
return Err(Error::TrailingBytes {
expected,
actual: buf.len(),
});
}
Ok(EnvelopeRef { buf, payload_len })
}
pub fn frame_length(header: &[u8]) -> Option<usize> {
if header.len() < HEADER_SIZE {
return None;
}
let pl_bytes: [u8; 4] = [
header[OFF_PAYLOAD_LEN],
header[OFF_PAYLOAD_LEN + 1],
header[OFF_PAYLOAD_LEN + 2],
header[OFF_PAYLOAD_LEN + 3],
];
let payload_len = u32::from_be_bytes(pl_bytes) as usize;
Some(HEADER_SIZE + payload_len + MAC_SIZE)
}
pub fn version(&self) -> Version {
Version::from_byte(self.buf[OFF_VERSION]).unwrap()
}
pub fn msg_type(&self) -> Option<MsgType> {
MsgType::from_byte(self.buf[OFF_MSG_TYPE])
}
pub fn msg_type_raw(&self) -> u8 {
self.buf[OFF_MSG_TYPE]
}
pub fn crypto_suite(&self) -> CryptoSuite {
CryptoSuite::from_byte(self.buf[OFF_CRYPTO_SUITE]).unwrap()
}
pub fn flags(&self) -> Flags {
Flags::from_byte(self.buf[OFF_FLAGS])
}
pub fn sender_device_id(&self) -> &[u8; 32] {
self.buf[OFF_SENDER_ID..OFF_SENDER_ID_END]
.try_into()
.unwrap()
}
pub fn residency_tag(&self) -> ResidencyTag {
let bytes: [u8; 2] = [self.buf[OFF_RESIDENCY], self.buf[OFF_RESIDENCY + 1]];
ResidencyTag::from_be_bytes(bytes)
}
pub fn payload_length(&self) -> u32 {
self.payload_len
}
pub fn payload(&self) -> &[u8] {
let start = HEADER_SIZE;
let end = start + self.payload_len as usize;
&self.buf[start..end]
}
pub fn mac(&self) -> &[u8; 16] {
let start = HEADER_SIZE + self.payload_len as usize;
self.buf[start..start + MAC_SIZE].try_into().unwrap()
}
pub fn header_bytes(&self) -> &[u8] {
&self.buf[..HEADER_SIZE]
}
pub fn as_bytes(&self) -> &[u8] {
self.buf
}
}
impl<'a> core::fmt::Debug for EnvelopeRef<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EnvelopeRef")
.field("version", &self.version())
.field("msg_type", &self.msg_type())
.field("crypto_suite", &self.crypto_suite())
.field("flags", &self.flags())
.field("sender_device_id", &hex_short(self.sender_device_id()))
.field("residency_tag", &self.residency_tag())
.field("payload_length", &self.payload_len)
.finish()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Envelope {
pub version: Version,
pub msg_type: MsgType,
pub crypto_suite: CryptoSuite,
pub flags: Flags,
pub sender_device_id: [u8; 32],
pub residency_tag: ResidencyTag,
pub payload: alloc::vec::Vec<u8>,
pub mac: [u8; 16],
}
#[cfg(feature = "alloc")]
impl Envelope {
pub fn new(
msg_type: MsgType,
crypto_suite: CryptoSuite,
sender_device_id: [u8; 32],
residency_tag: ResidencyTag,
payload: alloc::vec::Vec<u8>,
mac: [u8; 16],
) -> Self {
Self {
version: Version::CURRENT,
msg_type,
crypto_suite,
flags: Flags::NONE,
sender_device_id,
residency_tag,
payload,
mac,
}
}
pub fn with_flags(mut self, flags: Flags) -> Self {
self.flags = flags;
self
}
pub fn to_bytes(&self) -> alloc::vec::Vec<u8> {
crate::encode::encode_to_vec(self)
}
pub fn from_bytes(buf: &[u8]) -> Result<Self, Error> {
let r = EnvelopeRef::parse(buf)?;
Ok(Envelope {
version: r.version(),
msg_type: r
.msg_type()
.ok_or(Error::UnknownMsgType(r.msg_type_raw()))?,
crypto_suite: r.crypto_suite(),
flags: r.flags(),
sender_device_id: *r.sender_device_id(),
residency_tag: r.residency_tag(),
payload: alloc::vec::Vec::from(r.payload()),
mac: *r.mac(),
})
}
}
struct HexShort<'a>(&'a [u8; 32]);
fn hex_short(key: &[u8; 32]) -> HexShort<'_> {
HexShort(key)
}
impl core::fmt::Debug for HexShort<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{:02x}{:02x}{:02x}{:02x}..{:02x}{:02x}{:02x}{:02x}",
self.0[0],
self.0[1],
self.0[2],
self.0[3],
self.0[28],
self.0[29],
self.0[30],
self.0[31],
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_size_is_42() {
assert_eq!(HEADER_SIZE, 42);
}
#[test]
fn mac_size_is_16() {
assert_eq!(MAC_SIZE, 16);
}
#[test]
fn min_frame_is_header_plus_mac() {
assert_eq!(MIN_FRAME_SIZE, HEADER_SIZE + MAC_SIZE);
assert_eq!(MIN_FRAME_SIZE, 58);
}
#[test]
fn flags_none_is_zero() {
assert_eq!(Flags::NONE.as_byte(), 0);
}
#[test]
fn flags_default_is_none() {
assert_eq!(Flags::default(), Flags::NONE);
}
#[test]
fn flags_set_and_check() {
let f = Flags::NONE.with(Flags::COMPRESSED);
assert!(f.has(Flags::COMPRESSED));
assert!(!f.has(Flags::FRAGMENTED));
}
#[test]
fn flags_set_multiple() {
let f = Flags::NONE.with(Flags::COMPRESSED).with(Flags::FRAGMENTED);
assert!(f.has(Flags::COMPRESSED));
assert!(f.has(Flags::FRAGMENTED));
assert_eq!(f.as_byte(), 0b0000_0011);
}
#[test]
fn flags_clear() {
let f = Flags::from_byte(0b0000_0011);
let f = f.without(Flags::COMPRESSED);
assert!(!f.has(Flags::COMPRESSED));
assert!(f.has(Flags::FRAGMENTED));
assert_eq!(f.as_byte(), 0b0000_0010);
}
#[test]
fn flags_unknown_bits() {
let f = Flags::from_byte(0b0000_0011); assert!(!f.has_unknown_bits());
let f = Flags::from_byte(0b0000_0100); assert!(f.has_unknown_bits());
let f = Flags::from_byte(0b1111_1111); assert!(f.has_unknown_bits());
}
#[test]
fn flags_roundtrip_byte() {
for byte in 0u8..=255 {
let f = Flags::from_byte(byte);
assert_eq!(f.as_byte(), byte);
}
}
#[test]
fn parse_rejects_empty_buffer() {
assert!(matches!(
EnvelopeRef::parse(&[]),
Err(Error::BufferTooShort { need: 58, have: 0 })
));
}
#[test]
fn parse_rejects_one_byte() {
assert!(matches!(
EnvelopeRef::parse(&[0x01]),
Err(Error::BufferTooShort { need: 58, have: 1 })
));
}
#[test]
fn parse_rejects_header_only() {
let buf = [0u8; 42];
assert!(matches!(
EnvelopeRef::parse(&buf),
Err(Error::BufferTooShort { .. })
));
}
#[test]
fn frame_length_from_short_header() {
assert_eq!(EnvelopeRef::frame_length(&[0u8; 10]), None);
assert_eq!(EnvelopeRef::frame_length(&[0u8; 41]), None);
}
#[test]
fn frame_length_from_valid_header() {
let mut header = [0u8; 42];
header[0] = 0x01; header[1] = 0x01; header[2] = 0x01; header[38] = 0;
header[39] = 0;
header[40] = 0;
header[41] = 100;
let len = EnvelopeRef::frame_length(&header).unwrap();
assert_eq!(len, 42 + 100 + 16);
}
#[test]
fn frame_length_zero_payload() {
let mut header = [0u8; 42];
header[0] = 0x01;
header[1] = 0x01;
header[2] = 0x01;
let len = EnvelopeRef::frame_length(&header).unwrap();
assert_eq!(len, MIN_FRAME_SIZE);
}
#[test]
fn sender_id_is_4byte_aligned() {
assert_eq!(OFF_SENDER_ID, 4);
assert_eq!(OFF_SENDER_ID % 4, 0);
}
#[test]
fn field_offsets_are_contiguous() {
assert_eq!(OFF_VERSION, 0);
assert_eq!(OFF_MSG_TYPE, 1);
assert_eq!(OFF_CRYPTO_SUITE, 2);
assert_eq!(OFF_FLAGS, 3);
assert_eq!(OFF_SENDER_ID, 4);
assert_eq!(OFF_SENDER_ID_END, 36);
assert_eq!(OFF_RESIDENCY, 36);
assert_eq!(OFF_PAYLOAD_LEN, 38);
assert_eq!(OFF_PAYLOAD_LEN + 4, HEADER_SIZE);
}
}