Skip to main content

gbp_sframe/
header.rs

1use crate::error::SFrameError;
2
3/// Decoded SFrame header fields.
4///
5/// Wire format (draft-ietf-sframe-enc §4.3):
6/// ```text
7/// ┌─┬──────────┬──────────┬───────────────┬───────────────┐
8/// │V│  K (3)   │  C (4)   │  KID  (var)   │  CTR  (var)   │
9/// └─┴──────────┴──────────┴───────────────┴───────────────┘
10/// ```
11/// * `V` (1 bit) — version, always `0` for SFrame v1.
12/// * `K` (3 bits) — KID length in bytes minus one (0 → 1 byte, 7 → 8 bytes).
13/// * `C` (4 bits) — CTR length in bytes minus one (0 → 1 byte, 15 → 16 bytes).
14/// * `KID` — Key ID: `(epoch << 16) | leaf_index`.
15/// * `CTR` — monotonic per-sender counter, big-endian.
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct SFrameHeader {
18    /// Key ID: encodes both epoch and sender leaf index.
19    pub kid: u64,
20    /// Per-sender monotonic counter.
21    pub ctr: u64,
22}
23
24impl SFrameHeader {
25    /// Constructs the KID from an epoch and a leaf index.
26    ///
27    /// Uses the lower 16 bits of `leaf_index` (supports up to 65 535 senders
28    /// per epoch) and the full 48 available bits of `epoch`.
29    #[inline]
30    pub fn kid_from(epoch: u64, leaf_index: u32) -> u64 {
31        (epoch << 16) | (u64::from(leaf_index) & 0xFFFF)
32    }
33
34    /// Extracts the epoch from a KID.
35    #[inline]
36    pub fn epoch_from_kid(kid: u64) -> u64 {
37        kid >> 16
38    }
39
40    /// Extracts the leaf index from a KID.
41    #[inline]
42    pub fn leaf_from_kid(kid: u64) -> u32 {
43        (kid & 0xFFFF) as u32
44    }
45
46    /// Encodes the header into bytes.
47    ///
48    /// Layout: `[flags_byte] [kid_bytes...] [ctr_bytes...]`
49    /// where `flags_byte = 0 | ((kid_len-1) << 4) | (ctr_len-1)`.
50    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; // 3 bits
55        let c = (ctr_bytes.len() as u8 - 1) & 0x0F; // 4 bits
56        // V=0 (bit 7), K (bits 6-4), C (bits 3-0)
57        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    /// Decodes a header from the front of `data`.
67    ///
68    /// Returns the decoded header and the number of bytes consumed.
69    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        // V must be 0.
75        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; // 1-8 bytes
81        let ctr_len = (flags & 0x0F) as usize + 1; // 1-16 bytes
82        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
97/// Encodes an integer as the minimal number of big-endian bytes (at least 1).
98fn 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
104/// Decodes a big-endian byte slice into a u64.
105fn 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}