use core::fmt;
use crate::error::{Error, Result};
use crate::wire::{Reader, Writer, align_up};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Tag(pub u16);
impl Tag {
pub const HELLO: Self = Self(0x0001);
pub const HELLO_ACK: Self = Self(0x0002);
pub const REFUSAL: Self = Self(0x0003);
pub const SCAN_REQUEST: Self = Self(0x0010);
pub const RANGE_REQUEST: Self = Self(0x0020);
pub const BATCH: Self = Self(0x0030);
pub const EXPERIMENTAL_BASE: Self = Self(0xFF00);
#[must_use]
pub const fn is_experimental(self) -> bool {
self.0 >= Self::EXPERIMENTAL_BASE.0
}
#[must_use]
pub const fn name(self) -> Option<&'static str> {
match self {
Self::HELLO => Some("Hello"),
Self::HELLO_ACK => Some("HelloAck"),
Self::REFUSAL => Some("Refusal"),
Self::SCAN_REQUEST => Some("ScanRequest"),
Self::RANGE_REQUEST => Some("RangeRequest"),
Self::BATCH => Some("Batch"),
_ => None,
}
}
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.name() {
Some(name) => write!(f, "{name} (0x{:04x})", self.0),
None if self.is_experimental() => write!(f, "experimental 0x{:04x}", self.0),
None => write!(f, "unknown 0x{:04x}", self.0),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Header {
pub tag: Tag,
pub version: u16,
pub len: u32,
}
impl Header {
pub const SIZE: usize = 8;
}
impl<'a> Reader<'a> {
pub fn record(&mut self) -> Result<(Header, Reader<'a>)> {
let tag = Tag(self.u16()?);
let version = self.u16()?;
let len = self.u32()?;
let payload_len = usize::try_from(len).map_err(|_| Error::LengthOverflow)?;
let padded = align_up(payload_len);
let mut body = self.sub(padded.min(self.remaining()))?;
if body.remaining() < payload_len {
return Err(Error::Truncated {
needed: payload_len,
available: body.remaining(),
});
}
let payload = body.sub(payload_len)?;
Ok((Header { tag, version, len }, payload))
}
}
impl Writer<'_> {
pub fn record(
&mut self,
tag: Tag,
version: u16,
body: impl FnOnce(&mut Self) -> Result<()>,
) -> Result<()> {
self.u16(tag.0)?;
self.u16(version)?;
let len_at = self.position();
self.u32(0)?;
let start = self.position();
body(self)?;
let len = u32::try_from(self.position() - start).map_err(|_| Error::LengthOverflow)?;
self.patch_u32(len_at, len)?;
self.align()
}
}