commonware_cryptography/reed_solomon/
decoder_result.rs1use crate::reed_solomon::rate::DecoderWork;
2
3pub struct DecoderResult<'a> {
10 work: &'a mut DecoderWork,
11}
12
13impl DecoderResult<'_> {
14 pub fn original(&self, index: usize) -> Option<&[u8]> {
17 self.work.original(index)
18 }
19
20 pub const fn original_iter(&self) -> Originals<'_> {
22 Originals::new(self.work)
23 }
24}
25
26impl<'a> DecoderResult<'a> {
27 pub(crate) const fn new(work: &'a mut DecoderWork) -> Self {
31 Self { work }
32 }
33}
34
35impl Drop for DecoderResult<'_> {
36 fn drop(&mut self) {
37 self.work.reset_received();
38 }
39}
40
41pub struct RecoveryDecoderResult<'a> {
48 inner: DecoderResult<'a>,
49}
50
51impl RecoveryDecoderResult<'_> {
52 pub fn original(&self, index: usize) -> Option<&[u8]> {
55 self.inner.original(index)
56 }
57
58 pub const fn original_iter(&self) -> Originals<'_> {
61 self.inner.original_iter()
62 }
63
64 pub fn recovery(&self, index: usize) -> Option<&[u8]> {
67 self.inner.work.recovery(index)
68 }
69
70 pub const fn recovery_iter(&self) -> Recoveries<'_> {
73 Recoveries::new(self.inner.work)
74 }
75}
76
77impl<'a> RecoveryDecoderResult<'a> {
78 pub(crate) const fn new(inner: DecoderResult<'a>) -> Self {
83 Self { inner }
84 }
85}
86
87pub struct Originals<'a> {
91 remaining: usize,
93 next_index: usize,
95 work: &'a DecoderWork,
96}
97
98impl<'a> Iterator for Originals<'a> {
99 type Item = (usize, &'a [u8]);
100 fn next(&mut self) -> Option<(usize, &'a [u8])> {
101 if self.remaining == 0 {
102 return None;
103 }
104
105 let mut index = self.next_index;
106 while index < self.work.original_count() {
107 if let Some(original) = self.work.original(index) {
108 self.next_index = index + 1;
109 self.remaining -= 1;
110 return Some((index, original));
111 }
112 index += 1;
113 }
114
115 unreachable!("Inconsistency in internal data structures. Please report.");
116 }
117
118 fn size_hint(&self) -> (usize, Option<usize>) {
119 (self.remaining, Some(self.remaining))
120 }
121}
122
123impl ExactSizeIterator for Originals<'_> {}
124
125impl<'a> Originals<'a> {
126 pub(crate) const fn new(work: &'a DecoderWork) -> Self {
128 Self {
129 remaining: work.missing_original_count(),
130 next_index: 0,
131 work,
132 }
133 }
134}
135
136pub struct Recoveries<'a> {
140 remaining: usize,
142 next_index: usize,
144 work: &'a DecoderWork,
145}
146
147impl<'a> Iterator for Recoveries<'a> {
148 type Item = (usize, &'a [u8]);
149 fn next(&mut self) -> Option<(usize, &'a [u8])> {
150 if self.remaining == 0 {
151 return None;
152 }
153
154 let mut index = self.next_index;
155 while index < self.work.recovery_count() {
156 if let Some(recovery) = self.work.recovery(index) {
157 self.next_index = index + 1;
158 self.remaining -= 1;
159 return Some((index, recovery));
160 }
161 index += 1;
162 }
163
164 unreachable!("Inconsistency in internal data structures. Please report.");
165 }
166
167 fn size_hint(&self) -> (usize, Option<usize>) {
168 (self.remaining, Some(self.remaining))
169 }
170}
171
172impl ExactSizeIterator for Recoveries<'_> {}
173
174impl<'a> Recoveries<'a> {
175 pub(crate) const fn new(work: &'a DecoderWork) -> Self {
180 Self {
181 remaining: work.missing_recovery_count(),
182 next_index: 0,
183 work,
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::reed_solomon::{
192 Decoder, Encoder, SHARD_CHUNK_BYTES, rate::rate_low::DIRECT_EVALUATION_LIMIT, test_util,
193 };
194 #[cfg(not(feature = "std"))]
195 use alloc::vec::Vec;
196 use commonware_utils::test_rng;
197 use rand::seq::SliceRandom as _;
198
199 fn simple_roundtrip(shard_size: usize) {
202 let original = test_util::generate_original(3, shard_size, 0);
203
204 let mut encoder = Encoder::new(3, 2, shard_size).unwrap();
205 let mut decoder = Decoder::new(3, 2, shard_size).unwrap();
206
207 for original in &original {
208 encoder.add_original_shard(original).unwrap();
209 }
210
211 let result = encoder.encode().unwrap();
212 let recovery: Vec<_> = result.recovery_iter().collect();
213
214 assert!(recovery.iter().all(|slice| slice.len() == shard_size));
215
216 decoder.add_original_shard(1, &original[1]).unwrap();
217 decoder.add_recovery_shard(0, recovery[0]).unwrap();
218 decoder.add_recovery_shard(1, recovery[1]).unwrap();
219
220 let result = decoder.decode().unwrap().unwrap();
221
222 assert_eq!(result.original(0).unwrap(), original[0]);
223 assert!(result.original(1).is_none());
224 assert_eq!(result.original(2).unwrap(), original[2]);
225 assert!(result.original(3).is_none());
226
227 let mut iter: Originals<'_> = result.original_iter();
228 assert_eq!(iter.next(), Some((0, original[0].as_slice())));
229 assert_eq!(iter.next(), Some((2, original[2].as_slice())));
230 assert_eq!(iter.next(), None);
231 assert_eq!(iter.next(), None);
232 }
233
234 #[test]
236 fn decoder_result() {
237 simple_roundtrip(1024);
238 }
239
240 #[test]
241 fn shard_size_not_divisible_by_chunk_size() {
242 for shard_size in [
243 2,
244 4,
245 6,
246 30,
247 32,
248 34,
249 62,
250 SHARD_CHUNK_BYTES,
251 66,
252 126,
253 128,
254 130,
255 ] {
256 simple_roundtrip(shard_size);
257 }
258 }
259
260 #[test]
261 fn decoder_result_size_hint() {
262 let shard_size = SHARD_CHUNK_BYTES;
263 let original = test_util::generate_original(3, shard_size, 0);
264
265 let mut encoder = Encoder::new(3, 2, shard_size).unwrap();
266 let mut decoder = Decoder::new(3, 2, shard_size).unwrap();
267
268 for original in &original {
269 encoder.add_original_shard(original).unwrap();
270 }
271
272 let result = encoder.encode().unwrap();
273 let recovery: Vec<_> = result.recovery_iter().collect();
274
275 decoder.add_original_shard(1, &original[1]).unwrap();
276 decoder.add_recovery_shard(0, recovery[0]).unwrap();
277 decoder.add_recovery_shard(1, recovery[1]).unwrap();
278
279 let result = decoder.decode().unwrap().unwrap();
280
281 let mut iter: Originals<'_> = result.original_iter();
282
283 assert_eq!(iter.len(), 2);
284
285 assert!(iter.next().is_some());
286 assert_eq!(iter.len(), 1);
287
288 assert!(iter.next().is_some());
289 assert_eq!(iter.len(), 0);
290
291 assert!(iter.next().is_none());
292 assert_eq!(iter.len(), 0);
293 }
294
295 fn recovery_roundtrip(original_count: usize, recovery_count: usize, shard_size: usize) {
301 let original = test_util::generate_original(original_count, shard_size, 0);
302
303 let mut encoder = Encoder::new(original_count, recovery_count, shard_size).unwrap();
304 for original in &original {
305 encoder.add_original_shard(original).unwrap();
306 }
307 let encoding = encoder.encode().unwrap();
308 let recovery: Vec<Vec<u8>> = encoding.recovery_iter().map(<[u8]>::to_vec).collect();
309
310 let mut decoder = Decoder::new(original_count, recovery_count, shard_size).unwrap();
313 for (i, original) in original.iter().enumerate().skip(1) {
314 decoder.add_original_shard(i, original).unwrap();
315 }
316 decoder.add_recovery_shard(1, &recovery[1]).unwrap();
317 let decoding = decoder.decode_with_recovery().unwrap().unwrap();
318
319 assert_eq!(decoding.original(0).unwrap(), original[0].as_slice());
320 for (i, recovery) in recovery.iter().enumerate() {
321 let label = format!("oc={original_count} rc={recovery_count} ss={shard_size} rec={i}");
322 if i == 1 {
323 assert!(decoding.recovery(i).is_none(), "provided recovery: {label}");
324 } else {
325 assert_eq!(
326 decoding.recovery(i).unwrap(),
327 recovery.as_slice(),
328 "restored recovery mismatch: {label}"
329 );
330 }
331 }
332
333 let via_iter: Vec<(usize, Vec<u8>)> = decoding
334 .recovery_iter()
335 .map(|(i, s)| (i, s.to_vec()))
336 .collect();
337 let expected: Vec<(usize, Vec<u8>)> = (0..recovery_count)
338 .filter(|&i| i != 1)
339 .map(|i| (i, recovery[i].clone()))
340 .collect();
341 assert_eq!(via_iter, expected);
342 }
343
344 #[test]
345 fn recovery_matches_encoder() {
346 for shard_size in [2, 34, 62, SHARD_CHUNK_BYTES, 66, 130, 1024] {
348 recovery_roundtrip(3, 2, shard_size);
350 recovery_roundtrip(16, 4, shard_size);
351
352 recovery_roundtrip(4, 8, shard_size);
355 recovery_roundtrip(83, 167, shard_size);
356 }
357 }
358
359 #[test]
360 fn direct_recovery_matches_encoder() {
361 let mut rng = test_rng();
364 let quarter = DIRECT_EVALUATION_LIMIT / 4;
365 let recovery = DIRECT_EVALUATION_LIMIT - quarter.next_power_of_two();
366 for (k, m) in [
367 (7, 13),
368 (84, 166),
369 (quarter, recovery),
370 (quarter, recovery + 1),
371 ] {
372 for shard_size in [2, 66, 1024] {
373 let originals = test_util::generate_original(k, shard_size, 0);
374 let mut encoder = Encoder::new(k, m, shard_size).unwrap();
375 for shard in &originals {
376 encoder.add_original_shard(shard).unwrap();
377 }
378 let encoding = encoder.encode().unwrap();
379 let recoveries: Vec<_> = encoding.recovery_iter().collect();
380 let mut decoder = Decoder::new(k, m, shard_size).unwrap();
381
382 let mut indices: Vec<_> = (1..k + m).collect();
384 for provided in [k, k + 3, k + m - 1] {
385 indices.shuffle(&mut rng);
386 let selected = &indices[..provided];
387 for &i in selected {
388 if i < k {
389 decoder.add_original_shard(i, &originals[i]).unwrap();
390 } else {
391 decoder
392 .add_recovery_shard(i - k, recoveries[i - k])
393 .unwrap();
394 }
395 }
396 let result = decoder.decode_with_recovery().unwrap().unwrap();
397 for (i, shard) in originals.iter().enumerate() {
398 if !selected.contains(&i) {
399 assert_eq!(result.original(i).unwrap(), shard.as_slice());
400 }
401 }
402 for (i, shard) in recoveries.iter().enumerate() {
403 if !selected.contains(&(k + i)) {
404 assert_eq!(result.recovery(i).unwrap(), *shard);
405 }
406 }
407 }
408 }
409 }
410 }
411
412 fn assert_decode_none(original_count: usize, recovery_count: usize) {
415 let shard_size = SHARD_CHUNK_BYTES;
416 let original = test_util::generate_original(original_count, shard_size, 0);
417
418 let mut decoder = Decoder::new(original_count, recovery_count, shard_size).unwrap();
419 for (i, shard) in original.iter().enumerate() {
420 decoder.add_original_shard(i, shard).unwrap();
421 }
422 assert!(decoder.decode().unwrap().is_none());
423
424 let mut decoder = Decoder::new(original_count, recovery_count, shard_size).unwrap();
425 for (i, shard) in original.iter().enumerate() {
426 decoder.add_original_shard(i, shard).unwrap();
427 }
428 assert!(decoder.decode_with_recovery().unwrap().is_none());
429 }
430
431 #[test]
432 fn decode_none_when_all_originals_present() {
433 assert_decode_none(3, 2); assert_decode_none(4, 8); }
436}