Skip to main content

gbp_sframe/
header.rs

1//! SFrame Key ID scheme.
2//!
3//! The on-the-wire header (RFC 9605 §4.3) is produced and parsed entirely by
4//! the [`sframe`] crate. This module only decides how gbp packs
5//! `(epoch, leaf_index)` into the RFC's MLS Key ID, using sframe's own
6//! [`MlsKeyId`] definition (RFC 9605 §5.2).
7
8use sframe::mls::{MlsKeyId, MlsKeyIdBitRange};
9
10/// Bits of the MLS epoch encoded in the KID (its least-significant bits).
11pub const EPOCH_BITS: u8 = 16;
12/// Bits of the sender's member index encoded in the KID (up to 65 535 senders).
13pub const INDEX_BITS: u8 = 16;
14
15/// Context ID component of the KID; `0` yields the shortest RFC Key ID.
16const CONTEXT_ID: u64 = 0;
17
18fn bit_range() -> MlsKeyIdBitRange {
19    MlsKeyIdBitRange::new(EPOCH_BITS, INDEX_BITS)
20}
21
22/// SFrame Key ID helper, backed by sframe's [`MlsKeyId`] (RFC 9605 §5.2).
23///
24/// Packs `(epoch_lsb, member_index)` into the Key ID that the `sframe` crate
25/// carries in the RFC header, and unpacks it again on receipt.
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct SFrameHeader;
28
29impl SFrameHeader {
30    /// Builds the KID for `(epoch, leaf_index)`.
31    #[inline]
32    pub fn kid_from(epoch: u64, leaf_index: u32) -> u64 {
33        MlsKeyId::new(CONTEXT_ID, epoch, u64::from(leaf_index), bit_range()).into()
34    }
35
36    /// Extracts the epoch's low [`EPOCH_BITS`] bits from a KID.
37    #[inline]
38    pub fn epoch_from_kid(kid: u64) -> u64 {
39        MlsKeyId::from_key_id(kid, bit_range()).epoch_lsb()
40    }
41
42    /// Reduces a full epoch to the low [`EPOCH_BITS`] bits stored in a KID, so
43    /// it can be compared against [`epoch_from_kid`](Self::epoch_from_kid).
44    #[inline]
45    pub fn epoch_lsb(epoch: u64) -> u64 {
46        Self::epoch_from_kid(Self::kid_from(epoch, 0))
47    }
48
49    /// Extracts the sender's leaf/member index from a KID.
50    #[inline]
51    pub fn leaf_from_kid(kid: u64) -> u32 {
52        MlsKeyId::from_key_id(kid, bit_range()).member_index() as u32
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn kid_round_trip() {
62        let epoch = 42u64;
63        let leaf = 7u32;
64        let kid = SFrameHeader::kid_from(epoch, leaf);
65        assert_eq!(SFrameHeader::epoch_from_kid(kid), epoch);
66        assert_eq!(SFrameHeader::leaf_from_kid(kid), leaf);
67    }
68}