1use std::io::Error;
31use std::mem;
32
33const MIN_BLOCK_SIZE_BYTES: usize = 1024;
35const MAX_BLOCK_SIZE_BYTES: usize = 16 * 1024 * 1024;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum StreamIoMode {
45 Auto,
47 Serial,
49 Parallel,
51}
52
53#[derive(Debug, Clone)]
55pub struct StreamOptions {
56 pub block_size: usize,
61 pub io_mode: StreamIoMode,
63}
64
65impl Default for StreamOptions {
66 fn default() -> Self {
67 Self {
68 block_size: 4 * 1024 * 1024,
69 io_mode: StreamIoMode::Auto,
70 }
71 }
72}
73
74impl StreamOptions {
75 pub fn new() -> Self {
77 Self::default()
78 }
79
80 pub fn with_block_size(mut self, size: usize) -> Self {
82 self.block_size = size.clamp(MIN_BLOCK_SIZE_BYTES, MAX_BLOCK_SIZE_BYTES);
83 self
84 }
85
86 pub fn with_io_mode(mut self, mode: StreamIoMode) -> Self {
88 self.io_mode = mode;
89 self
90 }
91}
92
93#[derive(Debug)]
99pub struct StreamError {
100 pub shard_index: usize,
102 pub kind: StreamErrorKind,
104}
105
106#[derive(Debug)]
108pub enum StreamErrorKind {
109 Read(std::io::Error),
111 Write(std::io::Error),
113 Codec(crate::Error),
115}
116
117impl core::fmt::Display for StreamError {
118 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119 match &self.kind {
120 StreamErrorKind::Read(e) => {
121 write!(f, "read error on shard {}: {}", self.shard_index, e)
122 }
123 StreamErrorKind::Write(e) => {
124 write!(f, "write error on shard {}: {}", self.shard_index, e)
125 }
126 StreamErrorKind::Codec(e) => {
127 write!(f, "codec error on shard {}: {}", self.shard_index, e)
128 }
129 }
130 }
131}
132
133impl std::error::Error for StreamError {
134 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
135 match &self.kind {
136 StreamErrorKind::Read(e) => Some(e),
137 StreamErrorKind::Write(e) => Some(e),
138 StreamErrorKind::Codec(e) => Some(e),
139 }
140 }
141}
142
143impl StreamError {
144 fn read(shard_index: usize, e: std::io::Error) -> Self {
145 Self {
146 shard_index,
147 kind: StreamErrorKind::Read(e),
148 }
149 }
150
151 fn write(shard_index: usize, e: std::io::Error) -> Self {
152 Self {
153 shard_index,
154 kind: StreamErrorKind::Write(e),
155 }
156 }
157
158 fn codec(shard_index: usize, e: crate::Error) -> Self {
159 Self {
160 shard_index,
161 kind: StreamErrorKind::Codec(e),
162 }
163 }
164}
165
166fn take_stream_error(
167 first_error: &std::sync::Mutex<Option<StreamError>>,
168 fallback: impl FnOnce() -> StreamError,
169) -> StreamError {
170 let mut guard = match first_error.lock() {
171 Ok(guard) => guard,
172 Err(poisoned) => poisoned.into_inner(),
173 };
174 guard.take().unwrap_or_else(fallback)
175}
176
177fn record_min_error(first_error: &std::sync::Mutex<Option<StreamError>>, cand: StreamError) {
182 if let Ok(mut guard) = first_error.lock() {
183 let replace = match guard.as_ref() {
184 Some(existing) => cand.shard_index < existing.shard_index,
185 None => true,
186 };
187 if replace {
188 *guard = Some(cand);
189 }
190 }
191}
192
193fn use_parallel_stream_io(options: &StreamOptions, stream_count: usize) -> bool {
198 match options.io_mode {
199 StreamIoMode::Serial => false,
200 StreamIoMode::Parallel => true,
201 StreamIoMode::Auto => use_parallel_stream_io_auto(options.block_size, stream_count),
202 }
203}
204
205fn use_parallel_stream_io_auto(block_size: usize, stream_count: usize) -> bool {
206 block_size >= 4 * 1024 * 1024 && stream_count >= 10
213}
214
215fn read_block_with_mode<R: std::io::Read + Send>(
216 readers: &mut [R],
217 buffers: &mut [Vec<u8>],
218 max_len: usize,
219 use_parallel_io: bool,
220 read_lengths: &mut Vec<(usize, usize)>,
221) -> Result<(bool, usize), StreamError> {
222 if use_parallel_io {
223 read_block_par(readers, buffers, max_len)
224 } else {
225 read_block(readers, buffers, max_len, read_lengths)
226 }
227}
228
229fn write_block_with_mode<W: std::io::Write + Send>(
230 writers: &mut [W],
231 buffers: &[Vec<u8>],
232 len: usize,
233 shard_offset: usize,
234 use_parallel_io: bool,
235) -> Result<(), StreamError> {
236 if use_parallel_io {
237 write_block_par(writers, buffers, len, shard_offset)
238 } else {
239 write_block(writers, buffers, len, shard_offset)
240 }
241}
242
243fn read_block<R: std::io::Read>(
249 readers: &mut [R],
250 buffers: &mut [Vec<u8>],
251 max_len: usize,
252 read_lengths: &mut Vec<(usize, usize)>,
253) -> Result<(bool, usize), StreamError> {
254 read_lengths.clear();
255
256 for (i, (reader, buf)) in readers.iter_mut().zip(buffers.iter_mut()).enumerate() {
257 let total = read_one_stream(reader, buf, max_len).map_err(|e| StreamError::read(i, e))?;
258 read_lengths.push((i, total));
259 }
260
261 let actual_len = read_lengths
262 .iter()
263 .map(|(_, total)| *total)
264 .max()
265 .unwrap_or(0);
266 zero_pad_short_buffers(buffers, read_lengths, actual_len);
267
268 Ok((actual_len == 0, actual_len))
269}
270
271fn prepare_read_buffer(buf: &mut Vec<u8>, max_len: usize) {
272 match buf.len().cmp(&max_len) {
273 core::cmp::Ordering::Less => buf.resize(max_len, 0),
274 core::cmp::Ordering::Greater => buf.truncate(max_len),
275 core::cmp::Ordering::Equal => {}
276 }
277}
278
279fn read_one_stream<R: std::io::Read>(
280 reader: &mut R,
281 buf: &mut Vec<u8>,
282 max_len: usize,
283) -> Result<usize, std::io::Error> {
284 prepare_read_buffer(buf, max_len);
285 let mut total = 0;
286
287 while total < max_len {
288 match reader.read(&mut buf[total..]) {
289 Ok(0) => break,
290 Ok(n) => total += n,
291 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
292 Err(e) => return Err(e),
293 }
294 }
295
296 Ok(total)
297}
298
299fn zero_pad_short_buffers(
300 buffers: &mut [Vec<u8>],
301 read_lengths: &[(usize, usize)],
302 actual_len: usize,
303) {
304 for &(i, total) in read_lengths {
305 if total < actual_len {
306 buffers[i][total..actual_len].fill(0);
307 }
308 }
309}
310
311fn read_present_cursors_with_mode(
312 shards: &mut [std::io::Cursor<Vec<u8>>],
313 buffers: &mut [Vec<u8>],
314 present: &[bool],
315 present_indices: &[usize],
316 max_len: usize,
317 use_parallel_io: bool,
318 read_lengths: &mut Vec<(usize, usize)>,
319) -> Result<(bool, usize), StreamError> {
320 read_lengths.clear();
321
322 if use_parallel_io {
323 use rayon::prelude::*;
324
325 *read_lengths = shards
326 .par_iter_mut()
327 .zip(buffers.par_iter_mut())
328 .enumerate()
329 .filter_map(|(i, item)| present[i].then_some((i, item)))
330 .map(|(i, (shard, buf))| {
331 let total =
332 read_one_stream(shard, buf, max_len).map_err(|e| StreamError::read(i, e))?;
333 buf.truncate(total);
334 Ok::<_, StreamError>((i, total))
335 })
336 .collect::<Result<Vec<_>, _>>()?;
337 } else {
338 for &i in present_indices {
339 let total = read_one_stream(&mut shards[i], &mut buffers[i], max_len)
340 .map_err(|e| StreamError::read(i, e))?;
341 buffers[i].truncate(total);
342 read_lengths.push((i, total));
343 }
344 }
345
346 let actual_len = read_lengths
347 .iter()
348 .map(|(_, total)| *total)
349 .max()
350 .unwrap_or(0);
351 if actual_len != 0 {
352 if let Some(&(bad, _)) = read_lengths.iter().find(|(_, total)| *total != actual_len) {
359 return Err(StreamError::codec(bad, crate::Error::IncorrectShardSize));
360 }
361 }
362
363 Ok((actual_len == 0, actual_len))
364}
365
366fn write_block<W: std::io::Write>(
368 writers: &mut [W],
369 buffers: &[Vec<u8>],
370 len: usize,
371 shard_offset: usize,
372) -> Result<(), StreamError> {
373 for (i, (writer, buf)) in writers.iter_mut().zip(buffers.iter()).enumerate() {
374 let mut written = 0;
375 while written < len {
376 match writer.write(&buf[written..len]) {
377 Ok(0) => {
378 return Err(StreamError::write(
379 shard_offset + i,
380 std::io::Error::new(std::io::ErrorKind::WriteZero, "write returned 0"),
381 ));
382 }
383 Ok(n) => written += n,
384 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
385 Err(e) => return Err(StreamError::write(shard_offset + i, e)),
386 }
387 }
388 }
389 Ok(())
390}
391
392fn read_block_par<R: std::io::Read + Send>(
398 readers: &mut [R],
399 buffers: &mut [Vec<u8>],
400 max_len: usize,
401) -> Result<(bool, usize), StreamError> {
402 use rayon::prelude::*;
403
404 let read_lengths: Vec<(usize, usize)> = readers
405 .par_iter_mut()
406 .zip(buffers.par_iter_mut())
407 .enumerate()
408 .map(|(i, (reader, buf))| {
409 read_one_stream(reader, buf, max_len)
410 .map(|total| (i, total))
411 .map_err(|e| StreamError::read(i, e))
412 })
413 .collect::<Result<Vec<_>, _>>()?;
414
415 let actual_len = read_lengths
416 .iter()
417 .map(|(_, total)| *total)
418 .max()
419 .unwrap_or(0);
420 zero_pad_short_buffers(buffers, &read_lengths, actual_len);
421
422 Ok((actual_len == 0, actual_len))
423}
424
425fn write_block_par<W: std::io::Write + Send>(
427 writers: &mut [W],
428 buffers: &[Vec<u8>],
429 len: usize,
430 shard_offset: usize,
431) -> Result<(), StreamError> {
432 use rayon::prelude::*;
433
434 let first_error: std::sync::Mutex<Option<StreamError>> = std::sync::Mutex::new(None);
435
436 writers
437 .par_iter_mut()
438 .zip(buffers.par_iter())
439 .enumerate()
440 .try_for_each(|(i, (writer, buf))| {
441 let mut written = 0;
442 while written < len {
443 match writer.write(&buf[written..len]) {
444 Ok(0) => {
445 record_min_error(
446 &first_error,
447 StreamError::write(
448 shard_offset + i,
449 std::io::Error::new(
450 std::io::ErrorKind::WriteZero,
451 "write returned 0",
452 ),
453 ),
454 );
455 return Err(());
456 }
457 Ok(n) => written += n,
458 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
459 Err(e) => {
460 record_min_error(&first_error, StreamError::write(shard_offset + i, e));
461 return Err(());
462 }
463 }
464 }
465 Ok(())
466 })
467 .map_err(|()| {
468 take_stream_error(&first_error, || {
471 StreamError::write(
472 shard_offset,
473 Error::other("parallel stream writer error was not reported"),
474 )
475 })
476 })
477}
478
479impl super::ReedSolomon<crate::galois_8::Field> {
484 pub fn encode_stream(
511 &self,
512 data: &mut [impl std::io::Read + Send],
513 parity: &mut [impl std::io::Write + Send],
514 options: &StreamOptions,
515 ) -> Result<(), StreamError> {
516 let block_size = options
522 .block_size
523 .clamp(MIN_BLOCK_SIZE_BYTES, MAX_BLOCK_SIZE_BYTES);
524 if self.is_leopard_gf8_family() || self.is_leopard_gf16_family() {
530 return Err(StreamError::codec(0, crate::Error::UnsupportedCodecFamily));
531 }
532
533 let data_count = self.data_shard_count;
534 let parity_count = self.parity_shard_count;
535 let use_parallel_read = use_parallel_stream_io(options, data_count);
536 let use_parallel_write = use_parallel_stream_io(options, parity_count);
537
538 if data.len() != data_count {
543 return Err(StreamError::codec(0, crate::Error::TooFewShards));
544 }
545 if parity.len() != parity_count {
546 return Err(StreamError::codec(0, crate::Error::TooFewShards));
547 }
548
549 let mut data_bufs: Vec<Vec<u8>> = (0..data_count)
550 .map(|_| Vec::with_capacity(block_size))
551 .collect();
552 let mut parity_bufs: Vec<Vec<u8>> = (0..parity_count)
553 .map(|_| Vec::with_capacity(block_size))
554 .collect();
555 let mut read_lengths = Vec::with_capacity(data_count);
556
557 loop {
558 let (all_eof, actual_len) = read_block_with_mode(
559 data,
560 &mut data_bufs,
561 block_size,
562 use_parallel_read,
563 &mut read_lengths,
564 )?;
565 if all_eof {
566 break;
567 }
568
569 for buf in parity_bufs.iter_mut() {
571 buf.resize(actual_len, 0);
572 }
573
574 let data_refs: Vec<&[u8]> = data_bufs.iter().map(|b| &b[..actual_len]).collect();
576 let mut parity_refs: Vec<&mut [u8]> = parity_bufs
577 .iter_mut()
578 .map(|b| &mut b[..actual_len])
579 .collect();
580
581 self.encode_sep_par(&data_refs, &mut parity_refs)
582 .map_err(|e| StreamError::codec(0, e))?;
583
584 write_block_with_mode(
586 parity,
587 &parity_bufs,
588 actual_len,
589 data_count,
590 use_parallel_write,
591 )?;
592 }
593
594 Ok(())
595 }
596
597 pub fn verify_stream(
608 &self,
609 shards: &mut [impl std::io::Read + Send],
610 options: &StreamOptions,
611 ) -> Result<bool, StreamError> {
612 let block_size = options
618 .block_size
619 .clamp(MIN_BLOCK_SIZE_BYTES, MAX_BLOCK_SIZE_BYTES);
620 if self.is_leopard_gf8_family() || self.is_leopard_gf16_family() {
622 return Err(StreamError::codec(0, crate::Error::UnsupportedCodecFamily));
623 }
624
625 let total = self.total_shard_count;
626 let use_parallel_read = use_parallel_stream_io(options, total);
627
628 if shards.len() != total {
631 return Err(StreamError::codec(0, crate::Error::TooFewShards));
632 }
633
634 let mut bufs: Vec<Vec<u8>> = (0..total).map(|_| Vec::with_capacity(block_size)).collect();
635 let mut read_lengths = Vec::with_capacity(total);
636
637 loop {
638 let (all_eof, actual_len) = read_block_with_mode(
639 shards,
640 &mut bufs,
641 block_size,
642 use_parallel_read,
643 &mut read_lengths,
644 )?;
645 if all_eof {
646 break;
647 }
648
649 let refs: Vec<&[u8]> = bufs.iter().map(|b| &b[..actual_len]).collect();
650
651 let valid = self
652 .verify_par(&refs)
653 .map_err(|e| StreamError::codec(0, e))?;
654 if !valid {
655 return Ok(false);
656 }
657 }
658
659 Ok(true)
660 }
661
662 pub fn reconstruct_stream(
683 &self,
684 shards: &mut [std::io::Cursor<Vec<u8>>],
685 options: &StreamOptions,
686 ) -> Result<(), StreamError> {
687 let block_size = options
693 .block_size
694 .clamp(MIN_BLOCK_SIZE_BYTES, MAX_BLOCK_SIZE_BYTES);
695 let total = self.total_shard_count;
696
697 if shards.len() != total {
700 return Err(StreamError::codec(0, crate::Error::TooFewShards));
701 }
702
703 if self.is_leopard_gf8_family() || self.is_leopard_gf16_family() {
705 return Err(StreamError::codec(0, crate::Error::UnsupportedCodecFamily));
706 }
707
708 let mut present = vec![false; total];
710 for (i, shard) in shards.iter().enumerate() {
711 present[i] = !shard.get_ref().is_empty();
712 }
713
714 let missing_count = present.iter().filter(|&&p| !p).count();
715 let present_count = total - missing_count;
716 if present_count == 0 {
720 return Ok(());
721 }
722 if missing_count > self.parity_shard_count {
723 return Err(StreamError::codec(0, crate::Error::TooFewShardsPresent));
724 }
725 let use_parallel_read = use_parallel_stream_io(options, present_count);
726 let present_indices: Vec<usize> = present
727 .iter()
728 .enumerate()
729 .filter_map(|(i, is_present)| is_present.then_some(i))
730 .collect();
731 let missing_indices: Vec<usize> = present
732 .iter()
733 .enumerate()
734 .filter_map(|(i, is_present)| (!is_present).then_some(i))
735 .collect();
736
737 for &idx in &present_indices {
744 shards[idx].set_position(0);
745 }
746
747 let mut bufs: Vec<Vec<u8>> = (0..total)
754 .map(|i| {
755 if present[i] {
756 Vec::with_capacity(block_size)
757 } else {
758 Vec::new()
759 }
760 })
761 .collect();
762 let mut reconstruct_bufs: Vec<Option<Vec<u8>>> = (0..total).map(|_| None).collect();
763 let mut read_lengths = Vec::with_capacity(total);
764
765 loop {
766 let (all_eof, actual_len) = read_present_cursors_with_mode(
767 shards,
768 &mut bufs,
769 &present,
770 &present_indices,
771 block_size,
772 use_parallel_read,
773 &mut read_lengths,
774 )?;
775 if all_eof {
776 break;
777 }
778
779 for &idx in &present_indices {
782 bufs[idx].resize(actual_len, 0);
783 reconstruct_bufs[idx] = Some(mem::take(&mut bufs[idx]));
784 }
785 for &idx in &missing_indices {
786 reconstruct_bufs[idx] = None;
787 }
788
789 self.reconstruct(&mut reconstruct_bufs)
790 .map_err(|e| StreamError::codec(0, e))?;
791
792 for &idx in &missing_indices {
795 let recovered = reconstruct_bufs[idx]
796 .as_ref()
797 .expect("missing shard buffer");
798 shards[idx]
799 .get_mut()
800 .extend_from_slice(&recovered[..actual_len]);
801 }
802
803 for idx in 0..total {
804 if let Some(buf) = reconstruct_bufs[idx].take() {
805 bufs[idx] = buf;
806 }
807 }
808 }
809
810 Ok(())
811 }
812}
813
814#[cfg(test)]
815mod tests {
816 use super::*;
817 use crate::galois_8::ReedSolomon;
818
819 fn make_codec(data: usize, parity: usize) -> ReedSolomon {
820 ReedSolomon::new(data, parity).unwrap()
821 }
822
823 fn random_data(len: usize) -> Vec<u8> {
824 (0..len)
826 .map(|i| (i.wrapping_mul(73).wrapping_add(17)) as u8)
827 .collect()
828 }
829
830 #[test]
831 fn test_encode_stream_basic() {
832 let rs = make_codec(3, 2);
833 let shard_size = 4096;
834
835 let d0 = random_data(shard_size);
836 let d1 = random_data(shard_size);
837 let d2 = random_data(shard_size);
838
839 let mut readers: Vec<&[u8]> = vec![d0.as_slice(), d1.as_slice(), d2.as_slice()];
840 let mut writers: Vec<Vec<u8>> = vec![Vec::new(), Vec::new()];
841
842 rs.encode_stream(&mut readers, &mut writers, &StreamOptions::default())
843 .unwrap();
844
845 assert_eq!(writers[0].len(), shard_size);
846 assert_eq!(writers[1].len(), shard_size);
847
848 let all: Vec<&[u8]> = vec![
850 d0.as_slice(),
851 d1.as_slice(),
852 d2.as_slice(),
853 writers[0].as_slice(),
854 writers[1].as_slice(),
855 ];
856 assert!(rs.verify(&all).unwrap());
857 }
858
859 #[test]
860 fn test_encode_stream_multi_block() {
861 let rs = make_codec(3, 2);
862 let total_size = 10 * 1024; let block_size = 4096;
864
865 let d0 = random_data(total_size);
866 let d1 = random_data(total_size);
867 let d2 = random_data(total_size);
868
869 let mut readers: Vec<&[u8]> = vec![d0.as_slice(), d1.as_slice(), d2.as_slice()];
870 let mut writers: Vec<Vec<u8>> = vec![Vec::new(), Vec::new()];
871
872 let opts = StreamOptions::new().with_block_size(block_size);
873 rs.encode_stream(&mut readers, &mut writers, &opts).unwrap();
874
875 assert_eq!(writers[0].len(), total_size);
876 assert_eq!(writers[1].len(), total_size);
877 }
878
879 #[test]
880 fn test_encode_stream_empty() {
881 let rs = make_codec(3, 2);
882 let empty: Vec<&[u8]> = vec![&[], &[], &[]];
883 let mut readers = empty.clone();
884 let mut writers: Vec<Vec<u8>> = vec![Vec::new(), Vec::new()];
885
886 rs.encode_stream(&mut readers, &mut writers, &StreamOptions::default())
887 .unwrap();
888
889 assert!(writers[0].is_empty());
891 assert!(writers[1].is_empty());
892 }
893
894 #[test]
895 fn test_encode_stream_unequal_lengths() {
896 let rs = make_codec(3, 2);
897
898 let d0 = random_data(1000);
899 let d1 = random_data(500);
900 let d2 = random_data(800);
901
902 let mut readers: Vec<&[u8]> = vec![d0.as_slice(), d1.as_slice(), d2.as_slice()];
903 let mut writers: Vec<Vec<u8>> = vec![Vec::new(), Vec::new()];
904
905 rs.encode_stream(&mut readers, &mut writers, &StreamOptions::default())
906 .unwrap();
907
908 assert_eq!(writers[0].len(), 1000);
911 assert_eq!(writers[1].len(), 1000);
912 }
913
914 #[test]
915 fn test_encode_stream_10x4() {
916 let rs = make_codec(10, 4);
917 let shard_size = 64 * 1024;
918
919 let data: Vec<Vec<u8>> = (0..10).map(|_| random_data(shard_size)).collect();
920 let mut readers: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
921 let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 4];
922
923 rs.encode_stream(&mut readers, &mut writers, &StreamOptions::default())
924 .unwrap();
925
926 let mut all: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
928 for w in &writers {
929 all.push(w.as_slice());
930 }
931 assert!(rs.verify(&all).unwrap());
932 }
933
934 #[test]
935 fn test_verify_stream_valid() {
936 let rs = make_codec(3, 2);
937 let shard_size = 4096;
938
939 let d = vec![random_data(shard_size); 3];
940 let mut readers: Vec<&[u8]> = d.iter().map(|s| s.as_slice()).collect();
941 let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
942 rs.encode_stream(&mut readers, &mut writers, &StreamOptions::default())
943 .unwrap();
944
945 let mut all_data: Vec<&[u8]> = d.iter().map(|s| s.as_slice()).collect();
947 for w in &writers {
948 all_data.push(w.as_slice());
949 }
950 let mut all_readers: Vec<&[u8]> = all_data;
951
952 assert!(
953 rs.verify_stream(&mut all_readers, &StreamOptions::default())
954 .unwrap()
955 );
956 }
957
958 #[test]
959 fn test_verify_stream_corrupted() {
960 let rs = make_codec(3, 2);
961 let shard_size = 4096;
962
963 let d = vec![random_data(shard_size); 3];
964 let mut readers: Vec<&[u8]> = d.iter().map(|s| s.as_slice()).collect();
965 let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
966 rs.encode_stream(&mut readers, &mut writers, &StreamOptions::default())
967 .unwrap();
968
969 writers[0][0] ^= 0xFF;
971
972 let mut all_data: Vec<&[u8]> = d.iter().map(|s| s.as_slice()).collect();
973 for w in &writers {
974 all_data.push(w.as_slice());
975 }
976 let mut all_readers: Vec<&[u8]> = all_data;
977
978 assert!(
979 !rs.verify_stream(&mut all_readers, &StreamOptions::default())
980 .unwrap()
981 );
982 }
983
984 #[test]
985 fn test_reconstruct_stream_single_missing() {
986 let rs = make_codec(3, 2);
987 let shard_size = 4096;
988
989 let d: Vec<Vec<u8>> = (0..3).map(|_| random_data(shard_size)).collect();
990 let mut readers: Vec<&[u8]> = d.iter().map(|s| s.as_slice()).collect();
991 let mut parity_writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
992 rs.encode_stream(&mut readers, &mut parity_writers, &StreamOptions::default())
993 .unwrap();
994
995 let mut shards: Vec<std::io::Cursor<Vec<u8>>> = vec![
997 std::io::Cursor::new(Vec::new()), std::io::Cursor::new(d[1].clone()),
999 std::io::Cursor::new(d[2].clone()),
1000 std::io::Cursor::new(parity_writers[0].clone()),
1001 std::io::Cursor::new(parity_writers[1].clone()),
1002 ];
1003
1004 rs.reconstruct_stream(&mut shards, &StreamOptions::default())
1005 .unwrap();
1006
1007 assert_eq!(shards[0].get_ref(), &d[0]);
1009 }
1010
1011 #[test]
1012 fn test_reconstruct_non_streaming() {
1013 let rs = make_codec(3, 2);
1015 let shard_size = 4096;
1016
1017 let d: Vec<Vec<u8>> = (0..3).map(|_| random_data(shard_size)).collect();
1018 let data_refs: Vec<&[u8]> = d.iter().map(|s| s.as_slice()).collect();
1019 let mut p0 = vec![0u8; shard_size];
1020 let mut p1 = vec![0u8; shard_size];
1021 let mut parity_refs: Vec<&mut [u8]> = vec![&mut p0, &mut p1];
1022 rs.encode_sep(&data_refs, &mut parity_refs).unwrap();
1023
1024 let mut shards: Vec<Option<Vec<u8>>> = vec![
1026 None,
1027 Some(d[1].clone()),
1028 Some(d[2].clone()),
1029 Some(p0.clone()),
1030 Some(p1.clone()),
1031 ];
1032 rs.reconstruct(&mut shards).unwrap();
1033
1034 assert_eq!(shards[0].as_ref().unwrap(), &d[0]);
1035 }
1036
1037 #[test]
1038 fn test_reconstruct_stream_basic() {
1039 let rs = make_codec(3, 2);
1040 let shard_size = 64;
1041
1042 let d: Vec<Vec<u8>> = (0..3).map(|_| random_data(shard_size)).collect();
1043 let mut readers: Vec<&[u8]> = d.iter().map(|s| s.as_slice()).collect();
1044 let mut parity_writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
1045 rs.encode_stream(&mut readers, &mut parity_writers, &StreamOptions::default())
1046 .unwrap();
1047
1048 let all: Vec<&[u8]> = vec![
1050 d[0].as_slice(),
1051 d[1].as_slice(),
1052 d[2].as_slice(),
1053 parity_writers[0].as_slice(),
1054 parity_writers[1].as_slice(),
1055 ];
1056 assert!(rs.verify(&all).unwrap());
1057
1058 let mut shards: Vec<std::io::Cursor<Vec<u8>>> = vec![
1060 std::io::Cursor::new(Vec::new()), std::io::Cursor::new(d[1].clone()),
1062 std::io::Cursor::new(d[2].clone()),
1063 std::io::Cursor::new(parity_writers[0].clone()),
1064 std::io::Cursor::new(parity_writers[1].clone()),
1065 ];
1066
1067 rs.reconstruct_stream(&mut shards, &StreamOptions::default())
1068 .unwrap();
1069
1070 let recovered = shards[0].get_ref();
1072 assert_eq!(
1073 recovered.len(),
1074 d[0].len(),
1075 "recovered len {} != expected len {}",
1076 recovered.len(),
1077 d[0].len()
1078 );
1079 assert_eq!(
1080 recovered,
1081 &d[0],
1082 "recovered: {:?}, expected: {:?}",
1083 &recovered[..8],
1084 &d[0][..8]
1085 );
1086 }
1087
1088 #[test]
1089 fn test_stream_options_builder() {
1090 let opts = StreamOptions::new().with_block_size(1024 * 1024);
1091 assert_eq!(opts.block_size, 1024 * 1024);
1092 assert_eq!(opts.io_mode, StreamIoMode::Auto);
1093
1094 let opts = StreamOptions::new().with_block_size(100);
1096 assert_eq!(opts.block_size, 1024);
1097
1098 let opts = StreamOptions::new().with_io_mode(StreamIoMode::Serial);
1099 assert_eq!(opts.io_mode, StreamIoMode::Serial);
1100 }
1101
1102 #[test]
1103 fn test_stream_io_auto_decision_thresholds() {
1104 let opts = StreamOptions::new()
1105 .with_block_size(64 * 1024)
1106 .with_io_mode(StreamIoMode::Auto);
1107 assert!(!use_parallel_stream_io(&opts, 14));
1108
1109 let opts = StreamOptions::new()
1110 .with_block_size(1024 * 1024)
1111 .with_io_mode(StreamIoMode::Auto);
1112 assert!(!use_parallel_stream_io(&opts, 6));
1113
1114 let opts = StreamOptions::new()
1115 .with_block_size(4 * 1024 * 1024)
1116 .with_io_mode(StreamIoMode::Auto);
1117 assert!(use_parallel_stream_io(&opts, 10));
1118
1119 let opts = StreamOptions::new()
1120 .with_block_size(64 * 1024)
1121 .with_io_mode(StreamIoMode::Parallel);
1122 assert!(use_parallel_stream_io(&opts, 1));
1123 }
1124
1125 #[test]
1126 fn test_stream_error_display() {
1127 let e = StreamError::read(
1128 3,
1129 std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "eof"),
1130 );
1131 let s = format!("{e}");
1132 assert!(s.contains("shard 3"));
1133 assert!(s.contains("eof"));
1134
1135 let e = StreamError::codec(0, crate::Error::TooFewShardsPresent);
1136 let s = format!("{e}");
1137 assert!(s.contains("codec"));
1138 }
1139
1140 #[test]
1145 fn test_encode_stream_concurrent() {
1146 let rs = make_codec(4, 2);
1147 let shard_size = 8 * 1024;
1148
1149 let data: Vec<Vec<u8>> = (0..4).map(|_| random_data(shard_size)).collect();
1150 let mut readers: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
1151 let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
1152
1153 rs.encode_stream(&mut readers, &mut writers, &StreamOptions::default())
1154 .unwrap();
1155
1156 let mut all: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
1158 for w in &writers {
1159 all.push(w.as_slice());
1160 }
1161 assert!(rs.verify(&all).unwrap());
1162 }
1163
1164 #[test]
1165 fn test_verify_stream_concurrent() {
1166 let rs = make_codec(4, 2);
1167 let shard_size = 8 * 1024;
1168
1169 let data: Vec<Vec<u8>> = (0..4).map(|_| random_data(shard_size)).collect();
1170 let mut readers: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
1171 let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
1172 rs.encode_stream(&mut readers, &mut writers, &StreamOptions::default())
1173 .unwrap();
1174
1175 let mut all: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
1177 for w in &writers {
1178 all.push(w.as_slice());
1179 }
1180 let mut all_readers: Vec<&[u8]> = all;
1181 assert!(
1182 rs.verify_stream(&mut all_readers, &StreamOptions::default())
1183 .unwrap()
1184 );
1185
1186 let mut corrupted = writers[0].clone();
1188 corrupted[0] ^= 0xFF;
1189 let mut all_corrupt: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
1190 all_corrupt.push(corrupted.as_slice());
1191 all_corrupt.push(writers[1].as_slice());
1192 assert!(
1193 !rs.verify_stream(&mut all_corrupt, &StreamOptions::default())
1194 .unwrap()
1195 );
1196 }
1197
1198 #[test]
1199 fn test_reconstruct_stream_concurrent() {
1200 let rs = make_codec(4, 2);
1201 let shard_size = 8 * 1024;
1202
1203 let data: Vec<Vec<u8>> = (0..4).map(|_| random_data(shard_size)).collect();
1204 let mut readers: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
1205 let mut parity_writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
1206 rs.encode_stream(&mut readers, &mut parity_writers, &StreamOptions::default())
1207 .unwrap();
1208
1209 let mut shards: Vec<std::io::Cursor<Vec<u8>>> = vec![
1211 std::io::Cursor::new(data[0].clone()),
1212 std::io::Cursor::new(Vec::new()), std::io::Cursor::new(data[2].clone()),
1214 std::io::Cursor::new(data[3].clone()),
1215 std::io::Cursor::new(Vec::new()), std::io::Cursor::new(parity_writers[1].clone()),
1217 ];
1218
1219 rs.reconstruct_stream(&mut shards, &StreamOptions::default())
1220 .unwrap();
1221
1222 assert_eq!(shards[1].get_ref(), &data[1]);
1223 }
1224
1225 #[test]
1226 fn test_concurrent_stream_large_blocks() {
1227 let rs = make_codec(10, 4);
1228 let total_size = 1024 * 1024; let block_size = 256 * 1024; let data: Vec<Vec<u8>> = (0..10).map(|_| random_data(total_size)).collect();
1232 let mut readers: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
1233 let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 4];
1234
1235 let opts = StreamOptions::new().with_block_size(block_size);
1236 rs.encode_stream(&mut readers, &mut writers, &opts).unwrap();
1237
1238 let mut all: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
1240 for w in &writers {
1241 all.push(w.as_slice());
1242 }
1243 assert!(rs.verify(&all).unwrap());
1244
1245 let mut shards: Vec<std::io::Cursor<Vec<u8>>> = Vec::new();
1247 for d in &data {
1248 shards.push(std::io::Cursor::new(d.clone()));
1249 }
1250 shards[0] = std::io::Cursor::new(Vec::new());
1251 shards[5] = std::io::Cursor::new(Vec::new());
1252 for w in &writers {
1253 shards.push(std::io::Cursor::new(w.clone()));
1254 }
1255
1256 rs.reconstruct_stream(&mut shards, &StreamOptions::default())
1257 .unwrap();
1258
1259 assert_eq!(shards[0].get_ref(), &data[0]);
1260 assert_eq!(shards[5].get_ref(), &data[5]);
1261 }
1262
1263 #[test]
1264 fn test_encode_stream_zero_length() {
1265 let rs = ReedSolomon::new(3, 2).unwrap();
1266 let mut readers: Vec<&[u8]> = vec![b""; 3];
1267 let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
1268 rs.encode_stream(&mut readers, &mut writers, &StreamOptions::default())
1270 .unwrap();
1271 assert!(writers.iter().all(|w| w.is_empty()));
1272 }
1273
1274 #[test]
1275 fn test_encode_stream_single_byte_block() {
1276 let rs = ReedSolomon::new(2, 1).unwrap();
1277 let d0 = vec![0xABu8; 4];
1278 let d1 = vec![0xCDu8; 4];
1279 let mut readers: Vec<&[u8]> = vec![d0.as_slice(), d1.as_slice()];
1280 let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 1];
1281 let opts = StreamOptions::new().with_block_size(1);
1282 rs.encode_stream(&mut readers, &mut writers, &opts).unwrap();
1283
1284 let mut all: Vec<&[u8]> = vec![d0.as_slice(), d1.as_slice()];
1286 for w in &writers {
1287 all.push(w.as_slice());
1288 }
1289 assert!(rs.verify(&all).unwrap());
1290 }
1291
1292 #[test]
1293 fn test_stream_io_modes_encode_verify_match() {
1294 let rs = ReedSolomon::new(4, 2).unwrap();
1295 let shard_size = 32 * 1024;
1296 let data: Vec<Vec<u8>> = (0..4).map(|_| random_data(shard_size)).collect();
1297
1298 let mut expected_parity = Vec::new();
1299 for mode in [
1300 StreamIoMode::Auto,
1301 StreamIoMode::Serial,
1302 StreamIoMode::Parallel,
1303 ] {
1304 let opts = StreamOptions::new()
1305 .with_block_size(8 * 1024)
1306 .with_io_mode(mode);
1307 let mut readers: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
1308 let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
1309
1310 rs.encode_stream(&mut readers, &mut writers, &opts).unwrap();
1311 if expected_parity.is_empty() {
1312 expected_parity = writers.clone();
1313 } else {
1314 assert_eq!(writers, expected_parity);
1315 }
1316
1317 let mut all: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
1318 for parity in &writers {
1319 all.push(parity.as_slice());
1320 }
1321 let mut verify_readers = all;
1322 assert!(rs.verify_stream(&mut verify_readers, &opts).unwrap());
1323 }
1324 }
1325
1326 #[test]
1327 fn test_reconstruct_stream_minimum_present() {
1328 let rs = ReedSolomon::new(3, 2).unwrap();
1329 let shard_len = 16usize;
1330 let data: Vec<Vec<u8>> = (0..3).map(|i| vec![i as u8 + 1; shard_len]).collect();
1331
1332 let refs: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
1334 let mut parity = vec![vec![0u8; shard_len]; 2];
1335 let mut par_refs: Vec<&mut [u8]> = parity.iter_mut().map(|p| &mut p[..]).collect();
1336 rs.encode_sep(&refs, &mut par_refs).unwrap();
1337
1338 let mut shards: Vec<std::io::Cursor<Vec<u8>>> = vec![
1342 std::io::Cursor::new(data[0].clone()),
1343 std::io::Cursor::new(Vec::new()),
1344 std::io::Cursor::new(Vec::new()),
1345 std::io::Cursor::new(parity[0].clone()),
1346 std::io::Cursor::new(parity[1].clone()),
1347 ];
1348
1349 rs.reconstruct_stream(&mut shards, &StreamOptions::default())
1350 .unwrap();
1351
1352 assert_eq!(shards[1].get_ref(), &data[1], "shard 1 mismatch");
1354 assert_eq!(shards[2].get_ref(), &data[2], "shard 2 mismatch");
1355 }
1356
1357 fn leopard_gf8_codec(data: usize, parity: usize) -> ReedSolomon {
1362 use crate::{CodecFamily, CodecOptions};
1363 ReedSolomon::with_options(
1364 data,
1365 parity,
1366 CodecOptions {
1367 codec_family: CodecFamily::LeopardGF8,
1368 ..CodecOptions::default()
1369 },
1370 )
1371 .unwrap()
1372 }
1373
1374 #[test]
1377 fn test_reconstruct_stream_truncated_present_errors() {
1378 let rs = make_codec(3, 2);
1379 let shard = 4096;
1380 let d: Vec<Vec<u8>> = (0..3).map(|_| random_data(shard)).collect();
1381 let mut readers: Vec<&[u8]> = d.iter().map(|s| s.as_slice()).collect();
1382 let mut parity: Vec<Vec<u8>> = vec![Vec::new(); 2];
1383 rs.encode_stream(&mut readers, &mut parity, &StreamOptions::default())
1384 .unwrap();
1385
1386 let mut shards: Vec<std::io::Cursor<Vec<u8>>> = vec![
1388 std::io::Cursor::new(Vec::new()),
1389 std::io::Cursor::new(d[1][..50].to_vec()),
1390 std::io::Cursor::new(d[2].clone()),
1391 std::io::Cursor::new(parity[0].clone()),
1392 std::io::Cursor::new(parity[1].clone()),
1393 ];
1394 let err = rs
1395 .reconstruct_stream(&mut shards, &StreamOptions::default())
1396 .unwrap_err();
1397 assert!(matches!(
1398 err.kind,
1399 StreamErrorKind::Codec(crate::Error::IncorrectShardSize)
1400 ));
1401 }
1402
1403 #[test]
1406 fn test_reconstruct_stream_nonzero_cursor_position() {
1407 use std::io::Write;
1408 let rs = make_codec(3, 2);
1409 let shard = 4096;
1410 let d: Vec<Vec<u8>> = (0..3).map(|_| random_data(shard)).collect();
1411 let mut readers: Vec<&[u8]> = d.iter().map(|s| s.as_slice()).collect();
1412 let mut parity: Vec<Vec<u8>> = vec![Vec::new(); 2];
1413 rs.encode_stream(&mut readers, &mut parity, &StreamOptions::default())
1414 .unwrap();
1415
1416 let mk = |bytes: &[u8]| {
1417 let mut c = std::io::Cursor::new(Vec::new());
1418 c.write_all(bytes).unwrap(); c
1420 };
1421 let mut shards = vec![
1422 std::io::Cursor::new(Vec::new()), mk(&d[1]),
1424 mk(&d[2]),
1425 mk(&parity[0]),
1426 mk(&parity[1]),
1427 ];
1428 rs.reconstruct_stream(&mut shards, &StreamOptions::default())
1429 .unwrap();
1430 assert_eq!(shards[0].get_ref(), &d[0]);
1431 }
1432
1433 #[test]
1436 fn test_encode_stream_zero_block_size_clamped() {
1437 let rs = make_codec(3, 2);
1438 let shard = 4096;
1439 let d: Vec<Vec<u8>> = (0..3).map(|_| random_data(shard)).collect();
1440 let mut readers: Vec<&[u8]> = d.iter().map(|s| s.as_slice()).collect();
1441 let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
1442 let opts = StreamOptions {
1443 block_size: 0,
1444 io_mode: StreamIoMode::Serial,
1445 };
1446 rs.encode_stream(&mut readers, &mut writers, &opts).unwrap();
1447 assert_eq!(writers[0].len(), shard);
1448 assert_eq!(writers[1].len(), shard);
1449 }
1450
1451 #[test]
1454 fn test_encode_stream_wrong_count_errors() {
1455 let rs = make_codec(3, 2);
1456 let d0 = random_data(64);
1457 let mut readers: Vec<&[u8]> = vec![d0.as_slice(), d0.as_slice()]; let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
1459 let err = rs
1460 .encode_stream(&mut readers, &mut writers, &StreamOptions::default())
1461 .unwrap_err();
1462 assert!(matches!(
1463 err.kind,
1464 StreamErrorKind::Codec(crate::Error::TooFewShards)
1465 ));
1466 }
1467
1468 #[test]
1471 fn test_stream_rejects_leopard_family() {
1472 let rs = leopard_gf8_codec(4, 2);
1473 let d: Vec<Vec<u8>> = (0..6).map(|_| random_data(128)).collect();
1474
1475 let mut enc_readers: Vec<&[u8]> = d[..4].iter().map(|s| s.as_slice()).collect();
1476 let mut writers: Vec<Vec<u8>> = vec![Vec::new(); 2];
1477 let err = rs
1478 .encode_stream(&mut enc_readers, &mut writers, &StreamOptions::default())
1479 .unwrap_err();
1480 assert!(matches!(
1481 err.kind,
1482 StreamErrorKind::Codec(crate::Error::UnsupportedCodecFamily)
1483 ));
1484
1485 let mut ver_readers: Vec<&[u8]> = d.iter().map(|s| s.as_slice()).collect();
1486 let err = rs
1487 .verify_stream(&mut ver_readers, &StreamOptions::default())
1488 .unwrap_err();
1489 assert!(matches!(
1490 err.kind,
1491 StreamErrorKind::Codec(crate::Error::UnsupportedCodecFamily)
1492 ));
1493
1494 let mut rec_shards: Vec<std::io::Cursor<Vec<u8>>> =
1495 d.iter().map(|s| std::io::Cursor::new(s.clone())).collect();
1496 let err = rs
1497 .reconstruct_stream(&mut rec_shards, &StreamOptions::default())
1498 .unwrap_err();
1499 assert!(matches!(
1500 err.kind,
1501 StreamErrorKind::Codec(crate::Error::UnsupportedCodecFamily)
1502 ));
1503 }
1504
1505 #[test]
1508 fn test_reconstruct_stream_empty_dataset_ok() {
1509 let rs = make_codec(3, 2);
1510 let mut shards: Vec<std::io::Cursor<Vec<u8>>> =
1511 (0..5).map(|_| std::io::Cursor::new(Vec::new())).collect();
1512 rs.reconstruct_stream(&mut shards, &StreamOptions::default())
1513 .unwrap();
1514 assert!(shards.iter().all(|c| c.get_ref().is_empty()));
1515 }
1516}