use crate::{
error::{Result, SframeError},
header::KeyId,
util::{fit_into, get_n_lsb_bits},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MlsKeyIdBitRange {
n_epoch_bits: u8,
n_index_bits: u8,
}
impl MlsKeyIdBitRange {
pub const MAX: u8 = u64::BITS as u8 - 1;
pub fn new(n_epoch_bits: u8, n_index_bits: u8) -> Self {
Self::try_new(n_epoch_bits, n_index_bits).unwrap()
}
pub fn try_new(n_epoch_bits: u8, n_index_bits: u8) -> Result<Self> {
if n_epoch_bits > Self::MAX {
return Err(SframeError::OutOfRange {
name: "n_epoch_bits",
value: n_epoch_bits.into(),
min: 0,
max: Self::MAX.into(),
});
}
let max_index_bits = Self::MAX - n_epoch_bits;
if n_index_bits > max_index_bits {
return Err(SframeError::OutOfRange {
name: "n_index_bits",
value: n_index_bits.into(),
min: 0,
max: max_index_bits.into(),
});
}
Ok(Self {
n_epoch_bits,
n_index_bits,
})
}
fn len(&self) -> u8 {
self.n_epoch_bits + self.n_index_bits
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MlsKeyId {
value: u64,
bit_range: MlsKeyIdBitRange,
}
impl MlsKeyId {
pub fn try_new<C, E, M>(
context_id: C,
epoch_number: E,
member_index: M,
bit_range: MlsKeyIdBitRange,
) -> Result<Self>
where
C: Into<u64>,
E: Into<u64>,
M: Into<u64>,
{
let context_id = fit_into(
"context_id",
context_id.into(),
u64::BITS - u32::from(bit_range.len()),
)?;
let member_index = fit_into("member_index", member_index.into(), bit_range.n_index_bits)?;
let epoch_number = get_n_lsb_bits(epoch_number.into(), bit_range.n_epoch_bits);
let value = (context_id << bit_range.len())
| (member_index << bit_range.n_epoch_bits)
| epoch_number;
Ok(Self { value, bit_range })
}
pub fn from_key_id<K>(key_id: K, bit_range: MlsKeyIdBitRange) -> Self
where
K: Into<KeyId>,
{
let value = key_id.into();
Self { value, bit_range }
}
pub fn context_id(&self) -> u64 {
self.value >> self.bit_range.len()
}
pub fn member_index(&self) -> u64 {
get_n_lsb_bits(
self.value >> self.bit_range.n_epoch_bits,
self.bit_range.n_index_bits,
)
}
pub fn epoch_lsb(&self) -> u64 {
get_n_lsb_bits(self.value, self.bit_range.n_epoch_bits)
}
}
impl From<MlsKeyId> for KeyId {
fn from(mls_key_id: MlsKeyId) -> Self {
mls_key_id.value
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::header::KeyId;
use pretty_assertions::assert_eq;
#[test]
fn test_mls_key_id_creation() {
let context_id: u64 = 10;
let epoch_number: u64 = 0b11_101;
let epoch_number_lsb = 0b101;
let member_index: u64 = 6;
let bit_range = MlsKeyIdBitRange::new(3u8, 4u8);
let mls_key_id =
MlsKeyId::try_new(context_id, epoch_number, member_index, bit_range).unwrap();
assert_eq!(mls_key_id.context_id(), context_id);
assert_eq!(mls_key_id.epoch_lsb(), epoch_number_lsb);
assert_eq!(mls_key_id.member_index(), member_index);
}
#[test]
fn test_mls_key_id_from_key_id() {
#[allow(clippy::unusual_byte_groupings)]
let key_id: KeyId = 0b11_010_1010;
let epoch_bits: u8 = 4;
let index_bits: u8 = 3;
let bit_range = MlsKeyIdBitRange::new(epoch_bits, index_bits);
let mls_key_id = MlsKeyId::from_key_id(key_id, bit_range);
assert_eq!(mls_key_id.context_id(), 3);
assert_eq!(mls_key_id.epoch_lsb(), 10);
assert_eq!(mls_key_id.member_index(), 2);
assert_eq!(key_id, KeyId::from(mls_key_id));
}
#[test]
fn rejects_a_bit_range_leaving_no_context_id() {
assert!(MlsKeyIdBitRange::try_new(100, 12).is_err());
assert!(MlsKeyIdBitRange::try_new(10, 60).is_err());
assert!(MlsKeyIdBitRange::try_new(10, MlsKeyIdBitRange::MAX - 10).is_ok());
}
#[test]
fn rejects_values_exceeding_the_bit_range() {
let bit_range = MlsKeyIdBitRange::new(58, 3u8); let epoch_number: u64 = 1;
let too_large: u64 = 0b1_000;
assert!(MlsKeyId::try_new(too_large, epoch_number, 0u64, bit_range).is_err());
assert!(MlsKeyId::try_new(0u64, epoch_number, too_large, bit_range).is_err());
let largest: u64 = 0b111;
assert!(MlsKeyId::try_new(largest, epoch_number, largest, bit_range).is_ok());
}
#[test]
fn encodes_only_the_least_significant_bits_of_the_epoch() {
let bit_range = MlsKeyIdBitRange::new(3u8, 4u8);
let mls_key_id = MlsKeyId::try_new(0u64, 0b11_101u64, 0u64, bit_range).unwrap();
assert_eq!(mls_key_id.epoch_lsb(), 0b101);
}
}