Files
mycelium/src/sequence_number.rs
T
2023-11-07 11:45:41 +01:00

136 lines
4.0 KiB
Rust

//! Dedicated logic for
//! [sequence numbers](https://datatracker.ietf.org/doc/html/rfc8966#name-solving-starvation-sequenci).
use core::fmt;
use core::ops::{Add, AddAssign};
/// This value is compared against when deciding if a `SeqNo` is larger or smaller, [as defined in
/// the babel rfc](https://datatracker.ietf.org/doc/html/rfc8966#section-3.2.1).
const SEQNO_COMPARE_TRESHOLD: u16 = 32_768;
/// A sequence number on a route.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct SeqNo(u16);
impl SeqNo {
/// Create a new `SeqNo` with the default value.
pub fn new() -> Self {
Self::default()
}
/// Custom PartialOrd implementation as defined in [the babel rfc](https://datatracker.ietf.org/doc/html/rfc8966#section-3.2.1).
/// Note that we don't implement the [`PartialOrd`](std::cmd::PartialOrd) trait, as the contract on
/// that trait specifically defines that it is transitive, which is clearly not the case here.
///
/// There is a quirk in this equality comparison where values which are exactly 32_768 apart,
/// will result in false in either way of ordering the arguments, which is counterintuitive to
/// our understanding that a < b generally implies !(b < a).
pub fn lt(&self, other: &Self) -> bool {
if self.0 == other.0 {
false
} else {
other.0.wrapping_sub(self.0) < SEQNO_COMPARE_TRESHOLD
}
}
/// Custom PartialOrd implementation as defined in [the babel rfc](https://datatracker.ietf.org/doc/html/rfc8966#section-3.2.1).
/// Note that we don't implement the [`PartialOrd`](std::cmd::PartialOrd) trait, as the contract on
/// that trait specifically defines that it is transitive, which is clearly not the case here.
///
/// There is a quirk in this equality comparison where values which are exactly 32_768 apart,
/// will result in false in either way of ordering the arguments, which is counterintuitive to
/// our understanding that a < b generally implies !(b < a).
pub fn gt(&self, other: &Self) -> bool {
if self.0 == other.0 {
false
} else {
other.0.wrapping_sub(self.0) > SEQNO_COMPARE_TRESHOLD
}
}
}
impl fmt::Display for SeqNo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("{}", self.0))
}
}
impl From<u16> for SeqNo {
fn from(value: u16) -> Self {
SeqNo(value)
}
}
impl From<SeqNo> for u16 {
fn from(value: SeqNo) -> Self {
value.0
}
}
impl Add<u16> for SeqNo {
type Output = Self;
fn add(self, rhs: u16) -> Self::Output {
SeqNo(self.0.wrapping_add(rhs))
}
}
impl AddAssign<u16> for SeqNo {
fn add_assign(&mut self, rhs: u16) {
*self = SeqNo(self.0.wrapping_add(rhs))
}
}
#[cfg(test)]
mod tests {
use super::SeqNo;
#[test]
fn cmp_eq_seqno() {
let s1 = SeqNo::from(1);
let s2 = SeqNo::from(1);
assert_eq!(s1, s2);
let s1 = SeqNo::from(10_000);
let s2 = SeqNo::from(10_000);
assert_eq!(s1, s2);
}
#[test]
fn cmp_small_seqno_increase() {
let s1 = SeqNo::from(1);
let s2 = SeqNo::from(2);
assert!(s1.lt(&s2));
assert!(!s2.lt(&s1));
let s1 = SeqNo::from(3);
let s2 = SeqNo::from(30_000);
assert!(s1.lt(&s2));
assert!(!s2.lt(&s1));
}
#[test]
fn cmp_big_seqno_increase() {
let s1 = SeqNo::from(0);
let s2 = SeqNo::from(32_767);
assert!(s1.lt(&s2));
assert!(!s2.lt(&s1));
// Test equality quirk at cutoff point.
let s1 = SeqNo::from(0);
let s2 = SeqNo::from(32_768);
assert!(!s1.lt(&s2));
assert!(!s2.lt(&s1));
let s1 = SeqNo::from(0);
let s2 = SeqNo::from(32_769);
assert!(!s1.lt(&s2));
assert!(s2.lt(&s1));
let s1 = SeqNo::from(6);
let s2 = SeqNo::from(60_000);
assert!(!s1.lt(&s2));
assert!(s2.lt(&s1));
}
}