use crate::error::SFrameError;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SFrameHeader {
pub kid: u64,
pub ctr: u64,
}
impl SFrameHeader {
#[inline]
pub fn kid_from(epoch: u64, leaf_index: u32) -> u64 {
(epoch << 16) | (u64::from(leaf_index) & 0xFFFF)
}
#[inline]
pub fn epoch_from_kid(kid: u64) -> u64 {
kid >> 16
}
#[inline]
pub fn leaf_from_kid(kid: u64) -> u32 {
(kid & 0xFFFF) as u32
}
pub fn encode(self) -> Vec<u8> {
let kid_bytes = encode_uint(self.kid);
let ctr_bytes = encode_uint(self.ctr);
let k = (kid_bytes.len() as u8 - 1) & 0x07; let c = (ctr_bytes.len() as u8 - 1) & 0x0F; let flags = (k << 4) | c;
let mut out = Vec::with_capacity(1 + kid_bytes.len() + ctr_bytes.len());
out.push(flags);
out.extend_from_slice(&kid_bytes);
out.extend_from_slice(&ctr_bytes);
out
}
pub fn decode(data: &[u8]) -> Result<(Self, usize), SFrameError> {
if data.is_empty() {
return Err(SFrameError::Header("empty payload".into()));
}
let flags = data[0];
if flags & 0x80 != 0 {
return Err(SFrameError::Header(format!(
"unsupported SFrame version (V bit set in flags {flags:#04x})"
)));
}
let kid_len = ((flags >> 4) & 0x07) as usize + 1; let ctr_len = (flags & 0x0F) as usize + 1; let total = 1 + kid_len + ctr_len;
if data.len() < total {
return Err(SFrameError::Header(format!(
"need {total} bytes for header but only {} available",
data.len()
)));
}
let kid = decode_uint_be(&data[1..1 + kid_len]);
let ctr = decode_uint_be(&data[1 + kid_len..total]);
Ok((Self { kid, ctr }, total))
}
}
fn encode_uint(v: u64) -> Vec<u8> {
let needed = ((64 - v.leading_zeros() + 7) / 8) as usize;
let needed = needed.max(1);
v.to_be_bytes()[8 - needed..].to_vec()
}
fn decode_uint_be(data: &[u8]) -> u64 {
data.iter().fold(0u64, |acc, &b| (acc << 8) | u64::from(b))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_decode_roundtrip() {
for (kid, ctr) in [
(0, 0),
(1, 1),
(0xFFFF, 0),
(0x0001_FFFF, 0xDEAD),
(u64::MAX >> 16, u64::from(u16::MAX)),
] {
let hdr = SFrameHeader { kid, ctr };
let encoded = hdr.encode();
let (decoded, consumed) = SFrameHeader::decode(&encoded).unwrap();
assert_eq!(decoded, hdr, "kid={kid:#x} ctr={ctr:#x}");
assert_eq!(consumed, encoded.len());
}
}
#[test]
fn kid_round_trip() {
let epoch = 42u64;
let leaf = 7u32;
let kid = SFrameHeader::kid_from(epoch, leaf);
assert_eq!(SFrameHeader::epoch_from_kid(kid), epoch);
assert_eq!(SFrameHeader::leaf_from_kid(kid), leaf);
}
}