use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SourceBlockPartition {
pub transfer_length: u64,
pub encoding_symbol_length: u32,
pub source_symbols: u64,
pub num_blocks: u64,
pub larger_block_len: u64,
pub smaller_block_len: u64,
pub larger_blocks: u64,
}
impl SourceBlockPartition {
pub fn new(
transfer_length: u64,
encoding_symbol_length: u32,
max_source_block_length: u32,
) -> Result<Self> {
if encoding_symbol_length == 0 {
return Err(Error::InvalidField {
what: "Encoding-Symbol-Length",
reason: "must be non-zero",
});
}
if max_source_block_length == 0 {
return Err(Error::InvalidField {
what: "Maximum-Source-Block-Length",
reason: "must be non-zero",
});
}
let e = encoding_symbol_length as u64;
let b = max_source_block_length as u64;
let source_symbols = transfer_length.div_ceil(e);
let num_blocks = if source_symbols == 0 {
0
} else {
source_symbols.div_ceil(b)
};
let (larger_block_len, smaller_block_len, larger_blocks) = if num_blocks == 0 {
(0, 0, 0)
} else {
let a_large = source_symbols.div_ceil(num_blocks);
let a_small = source_symbols / num_blocks;
let i = source_symbols - a_small * num_blocks;
(a_large, a_small, i)
};
Ok(SourceBlockPartition {
transfer_length,
encoding_symbol_length,
source_symbols,
num_blocks,
larger_block_len,
smaller_block_len,
larger_blocks,
})
}
pub fn block_len(&self, index: u64) -> Option<u64> {
if index < self.larger_blocks {
Some(self.larger_block_len)
} else if index < self.num_blocks {
Some(self.smaller_block_len)
} else {
None
}
}
pub fn last_symbol_len(&self) -> Option<u32> {
if self.source_symbols == 0 {
return None;
}
let l = self.transfer_length;
let e = self.encoding_symbol_length as u64;
let len = l - ((l - 1) / e) * e;
Some(len as u32)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
#[test]
fn rejects_zero_divisors() {
assert!(matches!(
SourceBlockPartition::new(100, 0, 10),
Err(Error::InvalidField {
what: "Encoding-Symbol-Length",
..
})
));
assert!(matches!(
SourceBlockPartition::new(100, 10, 0),
Err(Error::InvalidField {
what: "Maximum-Source-Block-Length",
..
})
));
}
#[test]
fn zero_length_object_has_no_symbols_or_blocks() {
let p = SourceBlockPartition::new(0, 1000, 10).unwrap();
assert_eq!(p.source_symbols, 0);
assert_eq!(p.num_blocks, 0);
assert_eq!(p.larger_block_len, 0);
assert_eq!(p.smaller_block_len, 0);
assert_eq!(p.larger_blocks, 0);
assert_eq!(p.block_len(0), None);
assert_eq!(p.last_symbol_len(), None);
}
#[test]
fn exact_multiple_worked_example() {
let p = SourceBlockPartition::new(10_000, 1000, 3).unwrap();
assert_eq!(p.source_symbols, 10);
assert_eq!(p.num_blocks, 4);
assert_eq!(p.larger_block_len, 3);
assert_eq!(p.smaller_block_len, 2);
assert_eq!(p.larger_blocks, 2);
let lens: Vec<u64> = (0..p.num_blocks).map(|i| p.block_len(i).unwrap()).collect();
assert_eq!(lens, [3, 3, 2, 2]);
assert_eq!(lens.iter().sum::<u64>(), p.source_symbols);
assert_eq!(p.block_len(p.num_blocks), None);
assert_eq!(p.last_symbol_len(), Some(1000));
}
#[test]
fn non_exact_multiple_worked_example() {
let p = SourceBlockPartition::new(10_005, 1000, 3).unwrap();
assert_eq!(p.source_symbols, 11);
assert_eq!(p.num_blocks, 4);
assert_eq!(p.larger_block_len, 3);
assert_eq!(p.smaller_block_len, 2);
assert_eq!(p.larger_blocks, 3);
let lens: Vec<u64> = (0..p.num_blocks).map(|i| p.block_len(i).unwrap()).collect();
assert_eq!(lens, [3, 3, 3, 2]);
assert_eq!(lens.iter().sum::<u64>(), p.source_symbols);
assert_eq!(p.last_symbol_len(), Some(5));
}
#[test]
fn max_block_length_exceeding_total_symbols_yields_one_block() {
let p = SourceBlockPartition::new(100, 10, 1000).unwrap();
assert_eq!(p.source_symbols, 10);
assert_eq!(p.num_blocks, 1);
assert_eq!(p.larger_block_len, 10);
assert_eq!(p.smaller_block_len, 10);
assert_eq!(p.larger_blocks, 0);
assert_eq!(p.block_len(0), Some(10));
assert_eq!(p.block_len(1), None);
}
#[test]
fn sub_symbol_object_rounds_up_to_one_symbol() {
let p = SourceBlockPartition::new(5, 1000, 10).unwrap();
assert_eq!(p.source_symbols, 1);
assert_eq!(p.num_blocks, 1);
assert_eq!(p.last_symbol_len(), Some(5));
}
#[test]
fn mutating_transfer_length_changes_partition() {
let a = SourceBlockPartition::new(10_000, 1000, 3).unwrap();
let b = SourceBlockPartition::new(10_005, 1000, 3).unwrap();
assert_ne!(a.source_symbols, b.source_symbols);
assert_ne!(a.larger_blocks, b.larger_blocks);
assert_ne!(a.last_symbol_len(), b.last_symbol_len());
}
}