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}