use std::fmt::Display;
use crate::{
error::{Result, SframeError},
header::KeyId,
util::{fit_into, get_n_lsb_bits},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Generation(u64);
impl From<u64> for Generation {
fn from(generation: u64) -> Self {
Self(generation)
}
}
impl From<Generation> for u64 {
fn from(generation: Generation) -> Self {
generation.0
}
}
impl Display for Generation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RatchetStep(u64);
impl From<u64> for RatchetStep {
fn from(step: u64) -> Self {
Self(step)
}
}
impl From<RatchetStep> for u64 {
fn from(step: RatchetStep) -> Self {
step.0
}
}
impl Display for RatchetStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct RatchetStepDiff(u64);
impl RatchetStepDiff {
pub const NONE: Self = Self(0);
pub const ONE: Self = Self(1);
}
impl From<u64> for RatchetStepDiff {
fn from(steps: u64) -> Self {
Self(steps)
}
}
impl From<RatchetStepDiff> for u64 {
fn from(steps: RatchetStepDiff) -> Self {
steps.0
}
}
impl Display for RatchetStepDiff {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RatchetBits(u8);
impl RatchetBits {
pub const MIN: u8 = 1;
pub const MAX: u8 = u64::BITS as u8 - 1;
pub fn new(n_bits: u8) -> Self {
Self::try_new(n_bits).unwrap()
}
pub fn try_new(n_bits: u8) -> Result<Self> {
if !(Self::MIN..=Self::MAX).contains(&n_bits) {
return Err(SframeError::OutOfRange {
name: "n_ratchet_bits",
value: n_bits.into(),
min: Self::MIN.into(),
max: Self::MAX.into(),
});
}
Ok(Self(n_bits))
}
pub fn max_step(self) -> RatchetStep {
self.wrap_step(u64::MAX)
}
pub fn wrap_step(self, step: u64) -> RatchetStep {
RatchetStep(get_n_lsb_bits(step, self.0))
}
pub fn steps_between(self, from: RatchetStep, to: RatchetStep) -> RatchetStepDiff {
RatchetStepDiff(u64::from(self.wrap_step(to.0.wrapping_sub(from.0))))
}
pub fn max_distinguishable_steps(self) -> RatchetStepDiff {
RatchetStepDiff(((1u64 << self.0) >> 1).saturating_sub(1))
}
}
impl From<RatchetBits> for u8 {
fn from(bits: RatchetBits) -> Self {
bits.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RatchetingKeyId {
value: u64,
n_ratchet_bits: RatchetBits,
}
impl RatchetingKeyId {
pub fn new<G>(generation: G, n_ratchet_bits: RatchetBits) -> Self
where
G: Into<u64>,
{
Self::try_new(generation, n_ratchet_bits).unwrap()
}
pub fn try_new<G>(generation: G, n_ratchet_bits: RatchetBits) -> Result<Self>
where
G: Into<u64>,
{
let n_bits = u8::from(n_ratchet_bits);
let generation = fit_into(
"generation",
generation.into(),
u64::BITS - u32::from(n_bits),
)?;
Ok(Self {
value: generation << n_bits,
n_ratchet_bits,
})
}
pub fn from_key_id<K>(key_id: K, n_ratchet_bits: RatchetBits) -> Self
where
K: Into<KeyId>,
{
Self {
value: key_id.into(),
n_ratchet_bits,
}
}
pub fn generation(&self) -> Generation {
Generation(self.value >> u8::from(self.n_ratchet_bits))
}
pub fn ratchet_step(&self) -> RatchetStep {
self.n_ratchet_bits.wrap_step(self.value)
}
pub fn n_ratchet_bits(&self) -> RatchetBits {
self.n_ratchet_bits
}
pub fn steps_to(&self, other: RatchetingKeyId) -> Result<RatchetStepDiff> {
if self.generation() != other.generation() || self.n_ratchet_bits != other.n_ratchet_bits {
return Err(SframeError::RatchetingFailure);
}
Ok(self
.n_ratchet_bits
.steps_between(self.ratchet_step(), other.ratchet_step()))
}
pub(crate) fn inc_ratchet_step(&self) -> Self {
let max_step = self.n_ratchet_bits.max_step();
let mut value = self.value;
if self.ratchet_step() == max_step {
value ^= u64::from(max_step);
} else {
value = value.wrapping_add(1);
}
Self {
value,
n_ratchet_bits: self.n_ratchet_bits,
}
}
}
impl PartialEq<KeyId> for RatchetingKeyId {
fn eq(&self, other: &u64) -> bool {
self.value == *other
}
}
impl PartialEq<RatchetingKeyId> for KeyId {
fn eq(&self, other: &RatchetingKeyId) -> bool {
*self == other.value
}
}
impl From<RatchetingKeyId> for KeyId {
fn from(ratcheting: RatchetingKeyId) -> Self {
ratcheting.value
}
}
#[cfg(test)]
mod test {
use crate::{
error::SframeError,
header::KeyId,
ratchet::key_id::{Generation, RatchetBits, RatchetStep, RatchetStepDiff, RatchetingKeyId},
};
use pretty_assertions::assert_eq;
const GENERATION: u64 = 42;
fn generation(generation: u64) -> Generation {
Generation::from(generation)
}
fn step(step: u64) -> RatchetStep {
RatchetStep::from(step)
}
fn diff(steps: u64) -> RatchetStepDiff {
RatchetStepDiff::from(steps)
}
fn key_id(n_ratchet_bits: RatchetBits) -> RatchetingKeyId {
RatchetingKeyId::new(GENERATION, n_ratchet_bits)
}
fn advanced_by(key_id: RatchetingKeyId, n_steps: u64) -> RatchetingKeyId {
(0..n_steps).fold(key_id, |key_id, _| key_id.inc_ratchet_step())
}
#[test]
fn returns_correct_ratcheting_params() {
let expected_generation: u64 = 0xFF;
let key_id = RatchetingKeyId::new(expected_generation, RatchetBits::new(8));
assert_eq!(generation(expected_generation), key_id.generation());
assert_eq!(step(0), key_id.ratchet_step());
let expected_on_wire: KeyId = 0x0000_FF00;
assert_eq!(expected_on_wire, KeyId::from(key_id));
}
#[test]
fn rejects_too_few_ratcheting_bits() {
for n_bits in 0..RatchetBits::MIN {
assert!(matches!(
RatchetBits::try_new(n_bits),
Err(SframeError::OutOfRange { value, min, .. })
if value == u64::from(n_bits) && min == u64::from(RatchetBits::MIN)
));
}
}
#[test]
fn inc_ratchet_step() {
let n_ratchet_bits = RatchetBits::new(2);
let n_ratchet_steps: u64 = 1 << u8::from(n_ratchet_bits);
let mut key_id = key_id(n_ratchet_bits);
for i in 0..n_ratchet_steps {
assert_eq!(step(i), key_id.ratchet_step());
assert_eq!(generation(GENERATION), key_id.generation());
key_id = key_id.inc_ratchet_step();
}
assert_eq!(step(0), key_id.ratchet_step());
assert_eq!(generation(GENERATION), key_id.generation());
}
#[test]
fn rejects_more_ratchet_bits_than_a_key_id_holds() {
assert!(RatchetBits::try_new(RatchetBits::MAX).is_ok());
assert!(RatchetBits::try_new(RatchetBits::MAX + 1).is_err());
assert!(RatchetBits::try_new(255).is_err());
}
#[test]
fn works_with_the_maximum_of_ratchet_bits() {
let n_ratchet_bits = RatchetBits::new(RatchetBits::MAX);
let key_id = RatchetingKeyId::from_key_id(u64::MAX, n_ratchet_bits);
assert_eq!(generation(1), key_id.generation());
assert_eq!(step(u64::MAX >> 1), key_id.ratchet_step());
let key_id = key_id.inc_ratchet_step();
assert_eq!(step(0), key_id.ratchet_step());
}
#[test]
fn keeps_the_largest_generation_which_fits() {
let n_ratchet_bits = RatchetBits::new(8);
let largest = u64::MAX >> u8::from(n_ratchet_bits);
let key_id = RatchetingKeyId::new(largest, n_ratchet_bits);
assert_eq!(generation(largest), key_id.generation());
}
#[test]
fn rejects_a_generation_which_does_not_fit() {
let n_ratchet_bits = RatchetBits::new(8);
let one_too_big = (u64::MAX >> u8::from(n_ratchet_bits)) + 1;
let key_id = RatchetingKeyId::try_new(one_too_big, n_ratchet_bits);
assert!(key_id.is_err());
}
#[test]
fn separates_the_generation_from_the_ratchet_step() {
let n_ratchet_bits = RatchetBits::new(1);
let step_0 = key_id(n_ratchet_bits);
let step_1 = advanced_by(step_0, 1);
assert_eq!(step_0.generation(), step_1.generation());
assert_ne!(step_0, step_1);
}
#[test]
fn counts_the_steps_between_two_ratchet_steps() {
let n_ratchet_bits = RatchetBits::new(2);
assert_eq!(diff(1), n_ratchet_bits.steps_between(step(2), step(3)));
assert_eq!(diff(3), n_ratchet_bits.steps_between(step(3), step(2)));
assert_eq!(diff(0), n_ratchet_bits.steps_between(step(1), step(1)));
}
#[test]
fn calculates_the_ratchet_steps_between_key_ids() {
let start = key_id(RatchetBits::new(4));
let advance = 2;
let steps = start.steps_to(advanced_by(start, advance));
assert_eq!(diff(advance), steps.unwrap());
}
#[test]
fn calculates_no_step_for_the_same_key_id() {
let start = key_id(RatchetBits::new(4));
let steps = start.steps_to(start);
assert_eq!(RatchetStepDiff::NONE, steps.unwrap());
}
#[test]
fn calculates_the_ratchet_steps_between_key_ids_with_wrap_around() {
let n_ratchet_bits = RatchetBits::new(2);
let start = advanced_by(key_id(n_ratchet_bits), 1);
let steps = start.steps_to(advanced_by(start, u64::from(n_ratchet_bits.max_step())));
assert_eq!(diff(3), steps.unwrap());
}
#[test]
fn fails_if_n_ratchet_bits_mismatches() {
let start = key_id(RatchetBits::new(2));
let other_bits = advanced_by(key_id(RatchetBits::new(4)), 1);
assert!(start.steps_to(other_bits).is_err());
}
#[test]
fn fails_if_generation_mismatches() {
let n_ratchet_bits = RatchetBits::new(4);
let start = key_id(n_ratchet_bits);
let other_generation = RatchetingKeyId::new(GENERATION + 1, n_ratchet_bits);
assert!(start.steps_to(other_generation).is_err());
}
#[test]
fn tells_apart_less_than_half_of_the_ratchet_steps() {
assert_eq!(diff(7), RatchetBits::new(4).max_distinguishable_steps());
assert_eq!(diff(1), RatchetBits::new(2).max_distinguishable_steps());
assert_eq!(
RatchetStepDiff::NONE,
RatchetBits::new(1).max_distinguishable_steps()
);
}
}