pub use self::{
decoder_work::DecoderWork,
encoder_work::EncoderWork,
rate_default::{DefaultRate, DefaultRateDecoder, DefaultRateEncoder},
rate_high::{HighRate, HighRateDecoder, HighRateEncoder},
rate_low::{LowRate, LowRateDecoder, LowRateEncoder},
};
use crate::reed_solomon::{
DecoderResult, EncoderResult, Error,
engine::{self, Engine, GF_ORDER, GfElement, SHARD_CHUNK_BYTES},
};
use commonware_utils::MAX_ALLOCATION_BYTES;
mod decoder_work;
mod encoder_work;
pub(crate) mod rate_default;
pub(crate) mod rate_high;
pub(crate) mod rate_low;
const fn validate_work_size(shard_bytes: usize, work_count: usize) -> Result<(), Error> {
let max_chunks = MAX_ALLOCATION_BYTES / SHARD_CHUNK_BYTES;
if shard_bytes.div_ceil(SHARD_CHUNK_BYTES) > max_chunks / work_count {
return Err(Error::InvalidShardSize { shard_bytes });
}
Ok(())
}
fn eval_locator<E: Engine>(erasures: &mut [GfElement], end: usize) {
let n = erasures.len();
assert_eq!(n, end.next_power_of_two());
if n == GF_ORDER {
E::eval_poly(erasures.try_into().expect("length is GF_ORDER"), end);
} else {
engine::utils::eval_poly_short(erasures, end);
}
}
pub trait Rate<E: Engine> {
type RateEncoder: RateEncoder<E>;
type RateDecoder: RateDecoder<E>;
fn supports(original_count: usize, recovery_count: usize) -> bool;
fn encoder(
original_count: usize,
recovery_count: usize,
shard_bytes: usize,
engine: E,
work: Option<EncoderWork>,
) -> Result<Self::RateEncoder, Error> {
Self::RateEncoder::new(original_count, recovery_count, shard_bytes, engine, work)
}
fn decoder(
original_count: usize,
recovery_count: usize,
shard_bytes: usize,
engine: E,
work: Option<DecoderWork>,
) -> Result<Self::RateDecoder, Error> {
Self::RateDecoder::new(original_count, recovery_count, shard_bytes, engine, work)
}
fn validate(
original_count: usize,
recovery_count: usize,
shard_bytes: usize,
) -> Result<(), Error> {
if !Self::supports(original_count, recovery_count) {
Err(Error::UnsupportedShardCount {
original_count,
recovery_count,
})
} else if shard_bytes == 0 || shard_bytes & 1 != 0 {
Err(Error::InvalidShardSize { shard_bytes })
} else {
Ok(())
}
}
}
pub trait RateEncoder<E: Engine>
where
Self: Sized,
{
type Rate: Rate<E>;
fn add_original_shard<T: AsRef<[u8]>>(&mut self, original_shard: T) -> Result<(), Error>;
fn encode(&mut self) -> Result<EncoderResult<'_>, Error>;
fn into_parts(self) -> (E, EncoderWork);
fn new(
original_count: usize,
recovery_count: usize,
shard_bytes: usize,
engine: E,
work: Option<EncoderWork>,
) -> Result<Self, Error>;
fn reset(
&mut self,
original_count: usize,
recovery_count: usize,
shard_bytes: usize,
) -> Result<(), Error>;
fn validate(
original_count: usize,
recovery_count: usize,
shard_bytes: usize,
) -> Result<(), Error>;
fn supports(original_count: usize, recovery_count: usize) -> bool {
Self::Rate::supports(original_count, recovery_count)
}
}
pub trait RateDecoder<E: Engine>
where
Self: Sized,
{
type Rate: Rate<E>;
fn add_original_shard<T: AsRef<[u8]>>(
&mut self,
index: usize,
original_shard: T,
) -> Result<(), Error>;
fn add_recovery_shard<T: AsRef<[u8]>>(
&mut self,
index: usize,
recovery_shard: T,
) -> Result<(), Error>;
fn decode(&mut self, compute_recovery: bool) -> Result<Option<DecoderResult<'_>>, Error>;
fn into_parts(self) -> (E, DecoderWork);
fn new(
original_count: usize,
recovery_count: usize,
shard_bytes: usize,
engine: E,
work: Option<DecoderWork>,
) -> Result<Self, Error>;
fn reset(
&mut self,
original_count: usize,
recovery_count: usize,
shard_bytes: usize,
) -> Result<(), Error>;
fn validate(
original_count: usize,
recovery_count: usize,
shard_bytes: usize,
) -> Result<(), Error>;
fn supports(original_count: usize, recovery_count: usize) -> bool {
Self::Rate::supports(original_count, recovery_count)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reed_solomon::{engine::Scalar, test_util};
fn check_capacity(validate: impl Fn(usize) -> Result<(), Error>, work_count: usize) {
let shard_bytes =
(MAX_ALLOCATION_BYTES / SHARD_CHUNK_BYTES / work_count) * SHARD_CHUNK_BYTES;
assert_eq!(validate(shard_bytes), Ok(()));
assert_eq!(
validate(shard_bytes + 2),
Err(Error::InvalidShardSize {
shard_bytes: shard_bytes + 2
})
);
}
fn check_rate_capacity<R: Rate<Scalar>>(original_count: usize, recovery_count: usize) {
check_capacity(
|shard_bytes| R::RateEncoder::validate(original_count, recovery_count, shard_bytes),
12,
);
check_capacity(
|shard_bytes| R::RateDecoder::validate(original_count, recovery_count, shard_bytes),
16,
);
}
#[test]
fn working_space_capacity() {
check_rate_capacity::<HighRate<Scalar>>(9, 3);
check_rate_capacity::<LowRate<Scalar>>(3, 9);
check_rate_capacity::<DefaultRate<Scalar>>(9, 3);
check_rate_capacity::<DefaultRate<Scalar>>(3, 9);
}
#[test]
fn work_and_result_reuse() {
let mut encoder_work = None;
let mut decoder_work = None;
for (phase, (original_count, recovery_count, shard_bytes)) in
[(3, 2, 66), (2, 3, 34), (5, 3, 130), (4, 6, 2)]
.into_iter()
.enumerate()
{
let mut encoder = DefaultRate::<Scalar>::encoder(
original_count,
recovery_count,
shard_bytes,
Scalar::new(),
encoder_work,
)
.unwrap();
let mut decoder = DefaultRate::<Scalar>::decoder(
original_count,
recovery_count,
shard_bytes,
Scalar::new(),
decoder_work,
)
.unwrap();
for round in 0..2 {
let original = test_util::generate_original(
original_count,
shard_bytes,
(phase * 2 + round) as u8,
);
let mut fresh = DefaultRate::<Scalar>::encoder(
original_count,
recovery_count,
shard_bytes,
Scalar::new(),
None,
)
.unwrap();
for shard in &original {
encoder.add_original_shard(shard).unwrap();
fresh.add_original_shard(shard).unwrap();
}
let expected = fresh.encode().unwrap();
{
let encoded = encoder.encode().unwrap();
for index in 0..recovery_count {
assert_eq!(encoded.recovery(index), expected.recovery(index));
}
let mut iter = encoded.recovery_iter();
assert_eq!(iter.next(), expected.recovery(0));
assert_eq!(iter.len(), recovery_count - 1);
}
assert!(matches!(
encoder.encode(),
Err(Error::TooFewOriginalShards {
original_received_count: 0,
..
})
));
for (index, shard) in original.iter().enumerate() {
decoder.add_original_shard(index, shard).unwrap();
}
assert!(decoder.decode(round == 0).unwrap().is_none());
assert!(matches!(
decoder.decode(false),
Err(Error::NotEnoughShards {
original_received_count: 0,
recovery_received_count: 0,
..
})
));
for (index, shard) in original.iter().enumerate().skip(2) {
decoder.add_original_shard(index, shard).unwrap();
}
for index in 0..2 {
decoder
.add_recovery_shard(index, expected.recovery(index).unwrap())
.unwrap();
}
{
let decoded = decoder.decode(true).unwrap().unwrap();
let mut iter = decoded.original_iter();
assert_eq!(iter.next(), Some((0, original[0].as_slice())));
assert_eq!(iter.len(), 1);
assert_eq!(decoded.original(1), Some(original[1].as_slice()));
}
assert!(matches!(
decoder.decode(false),
Err(Error::NotEnoughShards {
original_received_count: 0,
recovery_received_count: 0,
..
})
));
}
let pending = vec![255; shard_bytes];
encoder.add_original_shard(&pending).unwrap();
decoder.add_original_shard(0, &pending).unwrap();
encoder_work = Some(encoder.into_parts().1);
decoder_work = Some(decoder.into_parts().1);
}
}
}