1#![expect(incomplete_features, reason = "generic_const_*")]
2#![feature(
3 generic_const_args,
4 generic_const_items,
5 macroless_generic_const_args,
6 min_generic_const_args
7)]
8#![no_std]
9
10use core::fmt;
11use reed_solomon_simd::Error;
12use reed_solomon_simd::engine::DefaultEngine;
13use reed_solomon_simd::rate::{HighRateDecoder, HighRateEncoder, RateDecoder, RateEncoder};
14
15#[derive(Debug, Clone, PartialEq, thiserror::Error)]
17pub enum ErasureCodingError {
18 #[error("Decoder error: {0}")]
20 DecoderError(#[from] Error),
21}
22
23const NUM_WORDS<const NUM_SHARDS: usize>: usize = NUM_SHARDS.div_ceil(u64::BITS as usize);
25
26#[derive(Copy, Clone, Eq, PartialEq)]
31pub struct ShardsBitmap<const NUM_SHARDS: usize> {
32 words: [u64; NUM_WORDS::<NUM_SHARDS>],
33}
34
35impl<const NUM_SHARDS: usize> fmt::Debug for ShardsBitmap<NUM_SHARDS> {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.debug_struct("ShardsBitmap")
38 .field("num_shards", &NUM_SHARDS)
39 .field("num_set", &self.count())
40 .finish()
41 }
42}
43
44impl<const NUM_SHARDS: usize> Default for ShardsBitmap<NUM_SHARDS> {
45 #[inline(always)]
46 fn default() -> Self {
47 Self::none()
48 }
49}
50
51impl<const NUM_SHARDS: usize> ShardsBitmap<NUM_SHARDS> {
52 #[inline(always)]
54 pub const fn none() -> Self {
55 Self {
56 words: [0; NUM_WORDS::<NUM_SHARDS>],
57 }
58 }
59
60 #[inline(always)]
62 pub const fn all() -> Self {
63 let mut this = Self {
64 words: [u64::MAX; NUM_WORDS::<NUM_SHARDS>],
65 };
66
67 let unused_bits = NUM_WORDS::<NUM_SHARDS> * u64::BITS as usize - NUM_SHARDS;
69 if unused_bits > 0 {
70 this.words[NUM_WORDS::<NUM_SHARDS> - 1] >>= unused_bits;
71 }
72
73 this
74 }
75
76 #[inline(always)]
78 pub const fn get(&self, index: usize) -> bool {
79 if index >= NUM_SHARDS {
80 return false;
81 }
82
83 (self.words[index / u64::BITS as usize] >> (index % u64::BITS as usize)) & 1 == 1
84 }
85
86 #[inline(always)]
88 pub const fn set(&mut self, index: usize) {
89 if index < NUM_SHARDS {
90 self.words[index / u64::BITS as usize] |= 1 << (index % u64::BITS as usize);
91 }
92 }
93
94 #[inline(always)]
96 pub const fn unset(&mut self, index: usize) {
97 if index < NUM_SHARDS {
98 self.words[index / u64::BITS as usize] &= !(1 << (index % u64::BITS as usize));
99 }
100 }
101
102 #[inline]
104 pub const fn count(&self) -> usize {
105 let mut count = 0;
106 let mut word_index = 0;
107 while word_index < NUM_WORDS::<NUM_SHARDS> {
108 count += self.words[word_index].count_ones() as usize;
109 word_index += 1;
110 }
111
112 count
113 }
114}
115
116#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
118pub struct ShardsPresent<const NUM_SHARDS: usize> {
119 pub source: ShardsBitmap<NUM_SHARDS>,
121 pub parity: ShardsBitmap<NUM_SHARDS>,
123}
124
125impl<const NUM_SHARDS: usize> ShardsPresent<NUM_SHARDS> {
126 #[inline(always)]
128 pub const fn none() -> Self {
129 Self {
130 source: ShardsBitmap::none(),
131 parity: ShardsBitmap::none(),
132 }
133 }
134
135 #[inline(always)]
137 pub const fn all() -> Self {
138 Self {
139 source: ShardsBitmap::all(),
140 parity: ShardsBitmap::all(),
141 }
142 }
143}
144
145#[derive(Debug, Clone)]
149pub struct ErasureCoding;
150
151impl Default for ErasureCoding {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157impl ErasureCoding {
158 pub fn new() -> Self {
160 Self {}
161 }
162
163 pub fn extend<const NUM_SHARDS: usize, const SHARD_BYTES: usize>(
165 &self,
166 source: &[[u8; SHARD_BYTES]; NUM_SHARDS],
167 parity: &mut [[u8; SHARD_BYTES]; NUM_SHARDS],
168 ) -> Result<(), ErasureCodingError> {
169 let mut encoder = new_encoder::<NUM_SHARDS, SHARD_BYTES>()?;
170
171 for shard in source {
172 encoder.add_original_shard(shard)?;
173 }
174
175 let result = encoder.encode()?;
176
177 for (input, output) in result.recovery_iter().zip(parity) {
178 output.copy_from_slice(input);
179 }
180
181 Ok(())
182 }
183
184 pub fn extend_scattered<
186 const NUM_SHARDS: usize,
187 const SHARD_BYTES: usize,
188 SourceShard,
189 ParityShard,
190 >(
191 &self,
192 source: [SourceShard; NUM_SHARDS],
193 parity: [ParityShard; NUM_SHARDS],
194 ) -> Result<(), ErasureCodingError>
195 where
196 SourceShard: AsRef<[u8; SHARD_BYTES]>,
197 ParityShard: AsMut<[u8; SHARD_BYTES]>,
198 {
199 let mut encoder = new_encoder::<NUM_SHARDS, SHARD_BYTES>()?;
200
201 for shard in &source {
202 encoder.add_original_shard(shard.as_ref())?;
203 }
204
205 let result = encoder.encode()?;
206
207 let mut parity = parity;
208 for (input, output) in result.recovery_iter().zip(&mut parity) {
209 output.as_mut().copy_from_slice(input);
210 }
211
212 Ok(())
213 }
214
215 pub fn recover_source<const NUM_SHARDS: usize, const SHARD_BYTES: usize>(
220 &self,
221 source: &mut [[u8; SHARD_BYTES]; NUM_SHARDS],
222 parity: &[[u8; SHARD_BYTES]; NUM_SHARDS],
223 present: &ShardsPresent<NUM_SHARDS>,
224 ) -> Result<(), ErasureCodingError> {
225 let mut decoder = new_decoder::<NUM_SHARDS, SHARD_BYTES>()?;
226
227 for (index, shard) in source.iter().enumerate() {
228 if present.source.get(index) {
229 decoder.add_original_shard(index, shard)?;
230 }
231 }
232 for (index, shard) in parity.iter().enumerate() {
233 if present.parity.get(index) {
234 decoder.add_recovery_shard(index, shard)?;
235 }
236 }
237
238 let result = decoder.decode()?;
239
240 for (index, shard) in source.iter_mut().enumerate() {
241 if !present.source.get(index) {
242 shard.copy_from_slice(restored_original(&result, index));
243 }
244 }
245
246 Ok(())
247 }
248
249 pub fn recover_source_scattered<
253 const NUM_SHARDS: usize,
254 const SHARD_BYTES: usize,
255 SourceShard,
256 ParityShard,
257 >(
258 &self,
259 source: [SourceShard; NUM_SHARDS],
260 source_present: &ShardsBitmap<NUM_SHARDS>,
261 parity: [Option<ParityShard>; NUM_SHARDS],
262 ) -> Result<(), ErasureCodingError>
263 where
264 SourceShard: AsMut<[u8; SHARD_BYTES]>,
265 ParityShard: AsRef<[u8; SHARD_BYTES]>,
266 {
267 let mut decoder = new_decoder::<NUM_SHARDS, SHARD_BYTES>()?;
268
269 let mut source = source;
270 for (index, shard) in source.iter_mut().enumerate() {
271 if source_present.get(index) {
272 decoder.add_original_shard(index, shard.as_mut().as_slice())?;
273 }
274 }
275 for (index, shard) in parity.iter().enumerate() {
276 if let Some(shard) = shard {
277 decoder.add_recovery_shard(index, shard.as_ref())?;
278 }
279 }
280
281 let result = decoder.decode()?;
282
283 for (index, shard) in source.iter_mut().enumerate() {
284 if !source_present.get(index) {
285 shard
286 .as_mut()
287 .copy_from_slice(restored_original(&result, index));
288 }
289 }
290
291 Ok(())
292 }
293
294 pub fn recover_all<const NUM_SHARDS: usize, const SHARD_BYTES: usize>(
298 &self,
299 source: &mut [[u8; SHARD_BYTES]; NUM_SHARDS],
300 parity: &mut [[u8; SHARD_BYTES]; NUM_SHARDS],
301 present: &ShardsPresent<NUM_SHARDS>,
302 ) -> Result<(), ErasureCodingError> {
303 self.recover_source(source, parity, present)?;
304
305 if present.parity.count() == NUM_SHARDS {
306 return Ok(());
307 }
308
309 let mut encoder = new_encoder::<NUM_SHARDS, SHARD_BYTES>()?;
312
313 for shard in &*source {
314 encoder.add_original_shard(shard)?;
315 }
316
317 let result = encoder.encode()?;
318
319 for (index, shard) in parity.iter_mut().enumerate() {
320 if !present.parity.get(index) {
321 shard.copy_from_slice(recovery(&result, index));
322 }
323 }
324
325 Ok(())
326 }
327
328 pub fn recover_all_scattered<
330 const NUM_SHARDS: usize,
331 const SHARD_BYTES: usize,
332 SourceShard,
333 ParityShard,
334 >(
335 &self,
336 source: [SourceShard; NUM_SHARDS],
337 parity: [ParityShard; NUM_SHARDS],
338 present: &ShardsPresent<NUM_SHARDS>,
339 ) -> Result<(), ErasureCodingError>
340 where
341 SourceShard: AsMut<[u8; SHARD_BYTES]>,
342 ParityShard: AsMut<[u8; SHARD_BYTES]>,
343 {
344 let mut source = source;
345 let mut parity = parity;
346
347 {
348 let mut decoder = new_decoder::<NUM_SHARDS, SHARD_BYTES>()?;
349
350 for (index, shard) in source.iter_mut().enumerate() {
351 if present.source.get(index) {
352 decoder.add_original_shard(index, shard.as_mut().as_slice())?;
353 }
354 }
355 for (index, shard) in parity.iter_mut().enumerate() {
356 if present.parity.get(index) {
357 decoder.add_recovery_shard(index, shard.as_mut().as_slice())?;
358 }
359 }
360
361 let result = decoder.decode()?;
362
363 for (index, shard) in source.iter_mut().enumerate() {
364 if !present.source.get(index) {
365 shard
366 .as_mut()
367 .copy_from_slice(restored_original(&result, index));
368 }
369 }
370 }
371
372 if present.parity.count() == NUM_SHARDS {
373 return Ok(());
374 }
375
376 let mut encoder = new_encoder::<NUM_SHARDS, SHARD_BYTES>()?;
377
378 for shard in &mut source {
379 encoder.add_original_shard(shard.as_mut().as_slice())?;
380 }
381
382 let result = encoder.encode()?;
383
384 for (index, shard) in parity.iter_mut().enumerate() {
385 if !present.parity.get(index) {
386 shard.as_mut().copy_from_slice(recovery(&result, index));
387 }
388 }
389
390 Ok(())
391 }
392}
393
394#[inline(always)]
395fn new_encoder<const NUM_SHARDS: usize, const SHARD_BYTES: usize>()
396-> Result<HighRateEncoder<DefaultEngine>, ErasureCodingError> {
397 Ok(HighRateEncoder::new(
398 NUM_SHARDS,
399 NUM_SHARDS,
400 SHARD_BYTES,
401 DefaultEngine::new(),
402 None,
403 )?)
404}
405
406#[inline(always)]
407fn new_decoder<const NUM_SHARDS: usize, const SHARD_BYTES: usize>()
408-> Result<HighRateDecoder<DefaultEngine>, ErasureCodingError> {
409 Ok(HighRateDecoder::new(
410 NUM_SHARDS,
411 NUM_SHARDS,
412 SHARD_BYTES,
413 DefaultEngine::new(),
414 None,
415 )?)
416}
417
418#[inline(always)]
419fn restored_original<'a>(
420 result: &'a reed_solomon_simd::DecoderResult<'_>,
421 index: usize,
422) -> &'a [u8] {
423 result
424 .restored_original(index)
425 .expect("Always corresponds to a missing source shard; qed")
426}
427
428#[inline(always)]
429fn recovery<'a>(result: &'a reed_solomon_simd::EncoderResult<'_>, index: usize) -> &'a [u8] {
430 result
431 .recovery(index)
432 .expect("Always corresponds to a missing parity shard; qed")
433}