1use crate::error::SFrameError;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct SFrameHeader {
18 pub kid: u64,
20 pub ctr: u64,
22}
23
24impl SFrameHeader {
25 #[inline]
30 pub fn kid_from(epoch: u64, leaf_index: u32) -> u64 {
31 (epoch << 16) | (u64::from(leaf_index) & 0xFFFF)
32 }
33
34 #[inline]
36 pub fn epoch_from_kid(kid: u64) -> u64 {
37 kid >> 16
38 }
39
40 #[inline]
42 pub fn leaf_from_kid(kid: u64) -> u32 {
43 (kid & 0xFFFF) as u32
44 }
45
46 pub fn encode(self) -> Vec<u8> {
51 let kid_bytes = encode_uint(self.kid);
52 let ctr_bytes = encode_uint(self.ctr);
53
54 let k = (kid_bytes.len() as u8 - 1) & 0x07; let c = (ctr_bytes.len() as u8 - 1) & 0x0F; let flags = (k << 4) | c;
58
59 let mut out = Vec::with_capacity(1 + kid_bytes.len() + ctr_bytes.len());
60 out.push(flags);
61 out.extend_from_slice(&kid_bytes);
62 out.extend_from_slice(&ctr_bytes);
63 out
64 }
65
66 pub fn decode(data: &[u8]) -> Result<(Self, usize), SFrameError> {
70 if data.is_empty() {
71 return Err(SFrameError::Header("empty payload".into()));
72 }
73 let flags = data[0];
74 if flags & 0x80 != 0 {
76 return Err(SFrameError::Header(format!(
77 "unsupported SFrame version (V bit set in flags {flags:#04x})"
78 )));
79 }
80 let kid_len = ((flags >> 4) & 0x07) as usize + 1; let ctr_len = (flags & 0x0F) as usize + 1; let total = 1 + kid_len + ctr_len;
83
84 if data.len() < total {
85 return Err(SFrameError::Header(format!(
86 "need {total} bytes for header but only {} available",
87 data.len()
88 )));
89 }
90
91 let kid = decode_uint_be(&data[1..1 + kid_len]);
92 let ctr = decode_uint_be(&data[1 + kid_len..total]);
93 Ok((Self { kid, ctr }, total))
94 }
95}
96
97fn encode_uint(v: u64) -> Vec<u8> {
99 let needed = ((64 - v.leading_zeros() + 7) / 8) as usize;
100 let needed = needed.max(1);
101 v.to_be_bytes()[8 - needed..].to_vec()
102}
103
104fn decode_uint_be(data: &[u8]) -> u64 {
106 data.iter().fold(0u64, |acc, &b| (acc << 8) | u64::from(b))
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn encode_decode_roundtrip() {
115 for (kid, ctr) in [
116 (0, 0),
117 (1, 1),
118 (0xFFFF, 0),
119 (0x0001_FFFF, 0xDEAD),
120 (u64::MAX >> 16, u64::from(u16::MAX)),
121 ] {
122 let hdr = SFrameHeader { kid, ctr };
123 let encoded = hdr.encode();
124 let (decoded, consumed) = SFrameHeader::decode(&encoded).unwrap();
125 assert_eq!(decoded, hdr, "kid={kid:#x} ctr={ctr:#x}");
126 assert_eq!(consumed, encoded.len());
127 }
128 }
129
130 #[test]
131 fn kid_round_trip() {
132 let epoch = 42u64;
133 let leaf = 7u32;
134 let kid = SFrameHeader::kid_from(epoch, leaf);
135 assert_eq!(SFrameHeader::epoch_from_kid(kid), epoch);
136 assert_eq!(SFrameHeader::leaf_from_kid(kid), leaf);
137 }
138}