use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EsiPartition {
donor_index: u32,
donor_count: u32,
}
impl EsiPartition {
pub fn new(donor_index: u32, donor_count: u32) -> Result<Self, EsiPartitionError> {
if donor_count == 0 {
return Err(EsiPartitionError::ZeroDonors);
}
if donor_index >= donor_count {
return Err(EsiPartitionError::DonorIndexOutOfRange {
donor_index,
donor_count,
});
}
Ok(Self {
donor_index,
donor_count,
})
}
#[must_use]
pub const fn donor_index(self) -> u32 {
self.donor_index
}
#[must_use]
pub const fn donor_count(self) -> u32 {
self.donor_count
}
#[must_use]
pub fn esi_for_sequence(self, sequence: u32) -> Option<u32> {
sequence
.checked_mul(self.donor_count)
.and_then(|base| base.checked_add(self.donor_index))
}
#[must_use]
pub fn owns_esi(self, esi: u32) -> bool {
esi % self.donor_count == self.donor_index
}
#[must_use]
pub const fn stream(self) -> DonorEsiStream {
DonorEsiStream {
partition: self,
next_sequence: Some(0),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EsiPartitionError {
ZeroDonors,
DonorIndexOutOfRange { donor_index: u32, donor_count: u32 },
SequenceOverflow {
donor_index: u32,
donor_count: u32,
sequence: u32,
},
}
impl fmt::Display for EsiPartitionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ZeroDonors => f.write_str("channel bonding ESI partition has zero donors"),
Self::DonorIndexOutOfRange {
donor_index,
donor_count,
} => write!(
f,
"channel bonding donor index {donor_index} is outside 0..{donor_count}"
),
Self::SequenceOverflow {
donor_index,
donor_count,
sequence,
} => write!(
f,
"channel bonding donor {donor_index}/{donor_count} sequence {sequence} exceeds u32 ESI space"
),
}
}
}
impl std::error::Error for EsiPartitionError {}
pub fn esi_for_donor(
donor_index: u32,
donor_count: u32,
sequence: u32,
) -> Result<u32, EsiPartitionError> {
let partition = EsiPartition::new(donor_index, donor_count)?;
partition
.esi_for_sequence(sequence)
.ok_or(EsiPartitionError::SequenceOverflow {
donor_index,
donor_count,
sequence,
})
}
pub fn owns_esi(donor_index: u32, donor_count: u32, esi: u32) -> Result<bool, EsiPartitionError> {
Ok(EsiPartition::new(donor_index, donor_count)?.owns_esi(esi))
}
pub fn donor_esi_stream(
donor_index: u32,
donor_count: u32,
) -> Result<DonorEsiStream, EsiPartitionError> {
Ok(EsiPartition::new(donor_index, donor_count)?.stream())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DonorEsiStream {
partition: EsiPartition,
next_sequence: Option<u32>,
}
impl Iterator for DonorEsiStream {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
let sequence = self.next_sequence?;
match self.partition.esi_for_sequence(sequence) {
Some(esi) => {
self.next_sequence = sequence.checked_add(1);
Some(esi)
}
None => {
self.next_sequence = None;
None
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
const DONOR_COUNTS: [u32; 5] = [1, 2, 3, 5, 8];
const COVERAGE_LIMIT: u32 = 257;
#[test]
fn static_partitions_cover_prefix_without_overlap() {
for donor_count in DONOR_COUNTS {
let mut owner_by_esi = vec![None; COVERAGE_LIMIT as usize];
for donor_index in 0..donor_count {
let partition =
EsiPartition::new(donor_index, donor_count).expect("valid donor partition");
for esi in 0..COVERAGE_LIMIT {
let owned = partition.owns_esi(esi);
assert_eq!(owned, esi % donor_count == donor_index);
if owned {
let owner = &mut owner_by_esi[esi as usize];
assert!(
owner.is_none(),
"ESI {esi} already owned by donor {:?} when donor {donor_index} claimed it",
owner
);
*owner = Some(donor_index);
}
}
}
for (esi, owner) in owner_by_esi.iter().enumerate() {
assert!(
owner.is_some(),
"donor_count {donor_count} left ESI {esi} unassigned"
);
}
}
}
#[test]
fn static_partitions_span_source_and_repair_ranges() {
const SOURCE_SYMBOLS: u32 = 37;
const REPAIR_LIMIT: u32 = SOURCE_SYMBOLS + 89;
for donor_count in DONOR_COUNTS {
let mut source_seen = BTreeSet::new();
let mut repair_seen = BTreeSet::new();
for donor_index in 0..donor_count {
let partition =
EsiPartition::new(donor_index, donor_count).expect("valid donor partition");
let source_owned = (0..SOURCE_SYMBOLS)
.filter(|esi| partition.owns_esi(*esi))
.collect::<Vec<_>>();
let repair_owned = (SOURCE_SYMBOLS..REPAIR_LIMIT)
.filter(|esi| partition.owns_esi(*esi))
.collect::<Vec<_>>();
assert!(
!source_owned.is_empty(),
"donor {donor_index}/{donor_count} owns no source ESIs"
);
assert!(
!repair_owned.is_empty(),
"donor {donor_index}/{donor_count} owns no repair ESIs"
);
for esi in source_owned {
assert!(esi < SOURCE_SYMBOLS);
assert_eq!(esi % donor_count, donor_index);
assert!(
source_seen.insert(esi),
"source ESI {esi} was assigned to multiple donors"
);
}
for esi in repair_owned {
assert!(esi >= SOURCE_SYMBOLS);
assert!(esi < REPAIR_LIMIT);
assert_eq!(esi % donor_count, donor_index);
assert!(
repair_seen.insert(esi),
"repair ESI {esi} was assigned to multiple donors"
);
}
}
assert_eq!(source_seen.len(), SOURCE_SYMBOLS as usize);
assert_eq!(repair_seen.len(), (REPAIR_LIMIT - SOURCE_SYMBOLS) as usize);
}
}
#[test]
fn donor_streams_are_pairwise_disjoint() {
for donor_count in DONOR_COUNTS {
let mut seen = BTreeSet::new();
for donor_index in 0..donor_count {
for esi in donor_esi_stream(donor_index, donor_count)
.expect("valid donor stream")
.take(64)
{
assert!(
seen.insert(esi),
"donor_count {donor_count} assigned ESI {esi} to multiple donors"
);
}
}
}
}
#[test]
fn donor_count_one_owns_every_esi() {
let partition = EsiPartition::new(0, 1).expect("single-donor partition");
for esi in [0, 1, 2, 127, u32::MAX] {
assert!(partition.owns_esi(esi));
assert_eq!(owns_esi(0, 1, esi), Ok(true));
}
assert_eq!(
donor_esi_stream(0, 1)
.expect("single donor stream")
.take(6)
.collect::<Vec<_>>(),
vec![0, 1, 2, 3, 4, 5]
);
}
#[test]
fn esi_for_donor_matches_residue_sequence() {
for donor_count in DONOR_COUNTS {
for donor_index in 0..donor_count {
let expected = (0..16)
.map(|sequence| donor_index + donor_count * sequence)
.collect::<Vec<_>>();
let actual = donor_esi_stream(donor_index, donor_count)
.expect("valid donor stream")
.take(expected.len())
.collect::<Vec<_>>();
assert_eq!(actual, expected);
for (sequence, esi) in expected.into_iter().enumerate() {
assert_eq!(
esi_for_donor(donor_index, donor_count, sequence as u32),
Ok(esi)
);
assert_eq!(owns_esi(donor_index, donor_count, esi), Ok(true));
}
}
}
}
#[test]
fn invalid_partitions_fail_closed() {
assert_eq!(EsiPartition::new(0, 0), Err(EsiPartitionError::ZeroDonors));
assert_eq!(
EsiPartition::new(2, 2),
Err(EsiPartitionError::DonorIndexOutOfRange {
donor_index: 2,
donor_count: 2,
})
);
assert_eq!(
owns_esi(2, 2, 0),
Err(EsiPartitionError::DonorIndexOutOfRange {
donor_index: 2,
donor_count: 2,
})
);
}
#[test]
fn overflow_is_reported_and_stream_exhausts_cleanly() {
assert_eq!(
esi_for_donor(u32::MAX - 1, u32::MAX, 1),
Err(EsiPartitionError::SequenceOverflow {
donor_index: u32::MAX - 1,
donor_count: u32::MAX,
sequence: 1,
})
);
let mut sparse_stream =
donor_esi_stream(u32::MAX - 1, u32::MAX).expect("largest valid donor stream");
assert_eq!(sparse_stream.next(), Some(u32::MAX - 1));
assert_eq!(sparse_stream.next(), None);
assert_eq!(sparse_stream.next(), None);
let mut full_u32_stream = donor_esi_stream(0, 1).expect("single donor stream");
full_u32_stream.next_sequence = Some(u32::MAX);
assert_eq!(full_u32_stream.next(), Some(u32::MAX));
assert_eq!(full_u32_stream.next(), None);
}
}