use crate::reed_solomon::{
Error,
engine::{DefaultEngine, GfElement},
rate::{rate_default::use_high_rate, rate_high, rate_low},
};
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use fixedbitset::FixedBitSet;
pub struct Plan {
original_count: usize,
recovery_count: usize,
high_rate: bool,
received: FixedBitSet,
coefficients: Vec<GfElement>,
}
impl Plan {
pub fn new(
original_count: usize,
recovery_count: usize,
original_indices: impl IntoIterator<Item = usize>,
recovery_indices: impl IntoIterator<Item = usize>,
) -> Result<Self, Error> {
let high_rate = use_high_rate(original_count, recovery_count)?;
let (original_base, recovery_base) = if high_rate {
(recovery_count.next_power_of_two(), 0)
} else {
(0, original_count.next_power_of_two())
};
let end = (original_base + original_count).max(recovery_base + recovery_count);
let mut received = FixedBitSet::with_capacity(end);
let mut original_received = 0;
let mut recovery_received = 0;
for index in original_indices {
if index >= original_count {
return Err(Error::InvalidOriginalShardIndex {
original_count,
index,
});
}
let pos = original_base + index;
if received[pos] {
return Err(Error::DuplicateOriginalShardIndex { index });
}
received.set(pos, true);
original_received += 1;
}
for index in recovery_indices {
if index >= recovery_count {
return Err(Error::InvalidRecoveryShardIndex {
recovery_count,
index,
});
}
let pos = recovery_base + index;
if received[pos] {
return Err(Error::DuplicateRecoveryShardIndex { index });
}
received.set(pos, true);
recovery_received += 1;
}
if original_received + recovery_received < original_count {
return Err(Error::NotEnoughShards {
original_count,
original_received_count: original_received,
recovery_received_count: recovery_received,
});
}
let coefficients = if original_received == original_count {
Vec::new()
} else if high_rate {
let mut erasures = vec![0; end.next_power_of_two()];
rate_high::eval_erasures::<DefaultEngine>(
&mut erasures,
original_count,
recovery_count,
&received,
);
erasures.truncate(end);
erasures
} else {
rate_low::with_erasures::<DefaultEngine, _>(
original_count,
recovery_count,
&received,
|erasures| erasures.to_vec(),
)
};
Ok(Self {
original_count,
recovery_count,
high_rate,
received,
coefficients,
})
}
pub(crate) fn coefficients(&self) -> &[GfElement] {
&self.coefficients
}
pub(crate) fn matches(
&self,
original_count: usize,
recovery_count: usize,
high_rate: bool,
received: &FixedBitSet,
) -> bool {
self.original_count == original_count
&& self.recovery_count == recovery_count
&& self.high_rate == high_rate
&& received.as_slice().starts_with(self.received.as_slice())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reed_solomon::{Decoder, Encoder, test_util};
#[test]
fn rejects_invalid_received_indices() {
for (k, m) in [(3, 5), (5, 3)] {
assert!(matches!(
Plan::new(k, m, [k], []),
Err(Error::InvalidOriginalShardIndex { .. })
));
assert!(matches!(
Plan::new(k, m, [], [m]),
Err(Error::InvalidRecoveryShardIndex { .. })
));
assert!(matches!(
Plan::new(k, m, [0, 0], []),
Err(Error::DuplicateOriginalShardIndex { index: 0 })
));
assert!(matches!(
Plan::new(k, m, [], [0, 0]),
Err(Error::DuplicateRecoveryShardIndex { index: 0 })
));
assert!(matches!(
Plan::new(k, m, [0], [0]),
Err(Error::NotEnoughShards {
original_received_count: 1,
recovery_received_count: 1,
..
})
));
}
}
#[test]
fn decoders_reject_mismatched_plans() {
type Shape = (usize, usize, &'static [usize], &'static [usize]);
let cases: [(Shape, Shape); 6] = [
((5, 1, &[0, 1, 3, 4], &[0]), (5, 3, &[0, 1], &[0, 1, 2])),
((2, 4, &[0], &[0]), (2, 3, &[0], &[0])),
((4, 5, &[0, 1], &[0, 1]), (3, 5, &[0, 1], &[0, 1])),
((4, 2, &[0, 1], &[0, 1]), (3, 2, &[0, 1], &[0, 1])),
((2, 4, &[0], &[0, 1]), (2, 4, &[0], &[0])),
((5, 3, &[0, 1, 2, 3], &[0, 1]), (5, 3, &[0, 1, 2, 3], &[0])),
];
for ((k, m, originals, recoveries), (plan_k, plan_m, plan_originals, plan_recoveries)) in
cases
{
let data = test_util::generate_original(k, 64, 0);
let mut encoder = Encoder::new(k, m, 64).unwrap();
for shard in &data {
encoder.add_original_shard(shard).unwrap();
}
let encoded = encoder.encode().unwrap();
let mut decoder = Decoder::new(k, m, 64).unwrap();
for &i in originals {
decoder.add_original_shard(i, &data[i]).unwrap();
}
for &i in recoveries {
decoder
.add_recovery_shard(i, encoded.recovery(i).unwrap())
.unwrap();
}
let plan = Plan::new(
plan_k,
plan_m,
plan_originals.iter().copied(),
plan_recoveries.iter().copied(),
)
.unwrap();
assert!(matches!(
decoder.decode_with_plan(&plan),
Err(Error::PlanMismatch)
));
assert!(matches!(
decoder.decode_with_recovery_plan(&plan),
Err(Error::PlanMismatch)
));
let own =
Plan::new(k, m, originals.iter().copied(), recoveries.iter().copied()).unwrap();
let decoded = decoder.decode_with_plan(&own).unwrap().unwrap();
for (i, shard) in data.iter().enumerate() {
if !originals.contains(&i) {
assert_eq!(decoded.original(i), Some(shard.as_slice()));
}
}
}
}
}