1extern crate alloc;
2
3use alloc::vec;
4use alloc::vec::Vec;
5
6use hashlink::LruCache;
7#[cfg(feature = "std")]
8use parking_lot::Mutex;
9use smallvec::SmallVec;
10#[cfg(not(feature = "std"))]
11use spin::Mutex;
12
13use crate::Field;
14use crate::errors::Error;
15use crate::matrix::Matrix;
16
17use super::{
18 CodecFamily, CodecOptions, DATA_DECODE_MATRIX_CACHE_MAX_CAPACITY,
19 DATA_DECODE_MATRIX_CACHE_MIN_CAPACITY, MatrixMode, ReedSolomon,
20};
21#[cfg(feature = "std")]
22use super::{
23 ReconstructionCacheMetrics, ReconstructionCacheStats, RuntimeProfileMetrics,
24 RuntimeProfileStats,
25};
26
27impl<F: Field> ReedSolomon<F> {
28 pub(crate) fn normalize_inversion_cache_capacity(
29 data_shards: usize,
30 parity_shards: usize,
31 requested_capacity: usize,
32 ) -> usize {
33 if requested_capacity > 0 {
34 return requested_capacity;
35 }
36
37 Self::recommended_inversion_cache_capacity(data_shards, parity_shards)
38 }
39
40 pub(crate) fn derive_inversion_cache_capacity(
41 data_shards: usize,
42 parity_shards: usize,
43 ) -> usize {
44 let total_shards = data_shards.saturating_add(parity_shards);
45 let heuristic = total_shards
46 .saturating_mul(parity_shards.max(1))
47 .saturating_mul(2);
48 let rounded = heuristic
49 .checked_next_power_of_two()
50 .unwrap_or(DATA_DECODE_MATRIX_CACHE_MAX_CAPACITY);
51
52 rounded.clamp(
53 DATA_DECODE_MATRIX_CACHE_MIN_CAPACITY,
54 DATA_DECODE_MATRIX_CACHE_MAX_CAPACITY,
55 )
56 }
57
58 pub(crate) fn get_parity_rows(&self) -> SmallVec<[&[F::Elem]; 32]> {
59 let mut parity_rows = SmallVec::with_capacity(self.parity_shard_count);
60 let matrix = &self.matrix;
61 for i in self.data_shard_count..self.total_shard_count {
62 parity_rows.push(matrix.get_row(i));
63 }
64
65 parity_rows
66 }
67
68 pub(crate) fn build_matrix(
69 data_shards: usize,
70 total_shards: usize,
71 ) -> Result<Matrix<F>, Error> {
72 let vandermonde = Matrix::vandermonde(total_shards, data_shards);
73
74 let top = vandermonde.sub_matrix(0, 0, data_shards, data_shards);
75 let top_inverted = top.invert().map_err(|_| Error::InvalidCustomMatrix)?;
76
77 vandermonde
78 .multiply(&top_inverted)
79 .map_err(|_| Error::InvalidCustomMatrix)
80 }
81
82 pub(crate) fn build_cauchy_matrix(
83 data_shards: usize,
84 total_shards: usize,
85 ) -> Result<Matrix<F>, Error> {
86 let mut result = Matrix::new(total_shards, data_shards);
87
88 for r in 0..total_shards {
89 if r < data_shards {
90 result.set(r, r, F::one());
91 } else {
92 for c in 0..data_shards {
93 let denominator = F::add(F::nth(r), F::nth(c));
94 if denominator == F::zero() {
95 return Err(Error::InvalidCustomMatrix);
96 }
97 result.set(r, c, F::div(F::one(), denominator));
98 }
99 }
100 }
101
102 Ok(result)
103 }
104
105 pub(crate) fn build_jerasure_like_matrix(
106 data_shards: usize,
107 total_shards: usize,
108 ) -> Result<Matrix<F>, Error> {
109 let mut vm = Matrix::vandermonde(total_shards, data_shards);
110
111 vm.set(0, 0, F::one());
112 for i in 1..data_shards {
113 vm.set(0, i, F::zero());
114 }
115
116 for i in 0..data_shards.saturating_sub(1) {
117 vm.set(total_shards - 1, i, F::zero());
118 }
119 vm.set(total_shards - 1, data_shards - 1, F::one());
120
121 for i in 0..data_shards {
122 let mut r = i;
123 while r < total_shards && vm.get(r, i) == F::zero() {
124 r += 1;
125 }
126 if r != i {
127 vm.swap_rows(r, i);
128 } else if vm.get(i, i) == F::zero() {
129 return Err(Error::InvalidCustomMatrix);
130 }
131
132 let scale = match vm.get(i, i) {
133 diagonal if diagonal == F::zero() => {
134 return Err(Error::InvalidCustomMatrix);
135 }
136 diagonal if diagonal != F::one() => F::div(F::one(), diagonal),
137 _ => F::one(),
138 };
139 if scale != F::one() {
140 for row in 0..total_shards {
141 vm.set(row, i, F::mul(vm.get(row, i), scale));
142 }
143 }
144
145 for j in 0..data_shards {
146 let value = vm.get(i, j);
147 if j != i && value != F::zero() {
148 for row in 0..total_shards {
149 vm.set(
150 row,
151 j,
152 F::add(vm.get(row, j), F::mul(value, vm.get(row, i))),
153 );
154 }
155 }
156 }
157 }
158
159 for j in 0..data_shards {
160 let value = vm.get(data_shards, j);
161 if value == F::zero() {
162 return Err(Error::InvalidCustomMatrix);
163 }
164
165 if value != F::one() {
166 let scale = F::div(F::one(), value);
167 for row in data_shards..total_shards {
168 vm.set(row, j, F::mul(vm.get(row, j), scale));
169 }
170 }
171 }
172
173 for row in (data_shards + 1)..total_shards {
174 let value = vm.get(row, 0);
175 if value == F::zero() {
176 return Err(Error::InvalidCustomMatrix);
177 }
178
179 if value != F::one() {
180 let scale = F::div(F::one(), value);
181 for col in 0..data_shards {
182 vm.set(row, col, F::mul(vm.get(row, col), scale));
183 }
184 }
185 }
186
187 Ok(vm)
188 }
189
190 pub(crate) fn build_custom_matrix(
191 data_shards: usize,
192 total_shards: usize,
193 custom_matrix: &[Vec<F::Elem>],
194 ) -> Result<Matrix<F>, Error> {
195 let parity_shards = total_shards.saturating_sub(data_shards);
196 if custom_matrix.len() < parity_shards {
197 return Err(Error::InvalidCustomMatrix);
198 }
199 if custom_matrix
200 .iter()
201 .take(parity_shards)
202 .any(|row| row.len() < data_shards)
203 {
204 return Err(Error::InvalidCustomMatrix);
205 }
206
207 let mut result = Matrix::new(total_shards, data_shards);
208 for row in 0..data_shards {
209 result.set(row, row, F::one());
210 }
211 for (offset, row) in custom_matrix.iter().take(parity_shards).enumerate() {
212 for (col, value) in row.iter().take(data_shards).enumerate() {
213 result.set(data_shards + offset, col, *value);
214 }
215 }
216
217 Ok(result)
218 }
219
220 pub(crate) fn build_matrix_with_options(
221 data_shards: usize,
222 total_shards: usize,
223 options: CodecOptions,
224 ) -> Result<Matrix<F>, Error> {
225 if options.codec_family != CodecFamily::Classic {
226 return Err(Error::UnsupportedCodecFamily);
227 }
228
229 match options.matrix_mode {
230 MatrixMode::Vandermonde => Self::build_matrix(data_shards, total_shards),
231 MatrixMode::Cauchy => Self::build_cauchy_matrix(data_shards, total_shards),
232 MatrixMode::JerasureLike => Self::build_jerasure_like_matrix(data_shards, total_shards),
233 MatrixMode::Custom => Err(Error::InvalidCustomMatrix),
234 }
235 }
236
237 pub fn new(data_shards: usize, parity_shards: usize) -> Result<ReedSolomon<F>, Error> {
242 Self::with_options(data_shards, parity_shards, CodecOptions::default())
243 }
244
245 pub fn with_options(
247 data_shards: usize,
248 parity_shards: usize,
249 mut options: CodecOptions,
250 ) -> Result<ReedSolomon<F>, Error> {
251 if data_shards == 0 {
252 return Err(Error::TooFewDataShards);
253 }
254 if parity_shards == 0 {
255 return Err(Error::TooFewParityShards);
256 }
257 let total_shards = data_shards
258 .checked_add(parity_shards)
259 .ok_or(Error::TooManyShards)?;
260
261 options.codec_family = super::leopard::resolve_codec_family(
266 options.codec_family,
267 options.leopard_mode,
268 super::leopard::is_byte_field::<F>(),
269 total_shards,
270 parity_shards,
271 );
272
273 if total_shards > super::leopard::max_total_shards_for_family::<F>(options.codec_family) {
278 return Err(Error::TooManyShards);
279 }
280
281 super::leopard::validate_leopard_family::<F>(
282 options.codec_family,
283 data_shards,
284 parity_shards,
285 )?;
286
287 options.inversion_cache_capacity = Self::normalize_inversion_cache_capacity(
288 data_shards,
289 parity_shards,
290 options.inversion_cache_capacity,
291 );
292
293 let matrix = match options.codec_family {
294 CodecFamily::Classic => {
295 Self::build_matrix_with_options(data_shards, total_shards, options)?
296 }
297 CodecFamily::LeopardGF8 => Self::build_matrix(data_shards, total_shards)?,
298 CodecFamily::LeopardGF16 => Matrix::new(0, 0),
305 };
306 let family_state = super::leopard::build_family_state(
307 options.codec_family,
308 data_shards,
309 parity_shards,
310 &matrix,
311 )?;
312 #[cfg(feature = "std")]
313 let policy_cache = Self::resolve_policy_cache_with_options(options);
314
315 Ok(ReedSolomon {
316 data_shard_count: data_shards,
317 parity_shard_count: parity_shards,
318 total_shard_count: total_shards,
319 codec_family: options.codec_family,
320 family_state,
321 matrix,
322 options,
323 #[cfg(feature = "std")]
324 policy_cache,
325 data_decode_matrix_cache: Mutex::new(LruCache::new(options.inversion_cache_capacity)),
326 #[cfg(feature = "std")]
327 reconstruction_cache_metrics: ReconstructionCacheMetrics::default(),
328 #[cfg(feature = "std")]
329 runtime_profile_metrics: RuntimeProfileMetrics::default(),
330 })
331 }
332
333 pub fn data_shard_count(&self) -> usize {
335 self.data_shard_count
336 }
337
338 pub fn parity_shard_count(&self) -> usize {
340 self.parity_shard_count
341 }
342
343 pub fn total_shard_count(&self) -> usize {
345 self.total_shard_count
346 }
347
348 pub fn codec_family(&self) -> CodecFamily {
350 self.codec_family
351 }
352
353 pub fn leopard_setup_matrix_shape(&self) -> Option<(usize, usize)> {
355 let codec = super::leopard::leopard_gf8_state(&self.family_state).ok()?;
356 Some(codec.setup_shape())
357 }
358
359 pub fn inversion_cache_capacity(&self) -> usize {
361 self.options.inversion_cache_capacity
362 }
363
364 pub fn recommended_inversion_cache_capacity(data_shards: usize, parity_shards: usize) -> usize {
366 Self::derive_inversion_cache_capacity(data_shards, parity_shards)
367 }
368
369 #[cfg(feature = "std")]
371 pub fn reconstruction_cache_stats(&self) -> ReconstructionCacheStats {
372 self.reconstruction_cache_metrics.snapshot()
373 }
374
375 #[cfg(feature = "std")]
377 pub fn runtime_profile_stats(&self) -> RuntimeProfileStats {
378 self.runtime_profile_metrics.snapshot()
379 }
380
381 #[cfg(feature = "std")]
383 pub fn reset_runtime_profile_stats(&self) {
384 self.runtime_profile_metrics.reset();
385 }
386
387 #[cfg(feature = "std")]
388 pub(crate) fn record_reconstruct_entry_path(&self, parallel: bool) {
389 self.runtime_profile_metrics
390 .record_reconstruct_entry(parallel);
391 }
392
393 #[cfg(feature = "std")]
394 pub(crate) fn record_reconstruct_opt_fallback_serial_path(&self) {
395 self.runtime_profile_metrics
396 .record_reconstruct_opt_fallback_serial();
397 }
398
399 #[cfg(feature = "std")]
400 pub(crate) fn record_reconstruct_runtime(
401 &self,
402 data_only: bool,
403 missing_data_count: usize,
404 missing_parity_count: usize,
405 all_present: bool,
406 ) {
407 self.runtime_profile_metrics.record_reconstruct(
408 data_only,
409 missing_data_count,
410 missing_parity_count,
411 all_present,
412 );
413 }
414
415 #[cfg(feature = "std")]
416 pub(crate) fn record_reconstruct_data_stage_runtime(
417 &self,
418 shard_len: usize,
419 output_count: usize,
420 ) {
421 self.runtime_profile_metrics
422 .record_reconstruct_data_stage(shard_len, output_count);
423 }
424
425 #[cfg(feature = "std")]
426 pub(crate) fn record_reconstruct_parity_stage_runtime(
427 &self,
428 shard_len: usize,
429 output_count: usize,
430 ) {
431 self.runtime_profile_metrics
432 .record_reconstruct_parity_stage(shard_len, output_count);
433 }
434
435 pub fn split(&self, data: &[F::Elem]) -> Result<Vec<Vec<F::Elem>>, Error> {
439 let data_shards = self.data_shard_count;
440 let shard_len = if data.is_empty() {
441 0
442 } else {
443 data.len().div_ceil(data_shards)
444 };
445
446 let mut shards = Vec::with_capacity(data_shards);
447 for i in 0..data_shards {
448 let start = i * shard_len;
449 let end = core::cmp::min(start + shard_len, data.len());
450 let mut shard = vec![F::zero(); shard_len];
451 if start < data.len() {
452 shard[..end - start].copy_from_slice(&data[start..end]);
453 }
454 shards.push(shard);
455 }
456
457 Ok(shards)
458 }
459
460 pub fn join<T: AsRef<[F::Elem]>>(
464 &self,
465 shards: &[T],
466 out_len: usize,
467 ) -> Result<Vec<F::Elem>, Error> {
468 check_piece_count!(data => self, shards);
469 check_slices!(multi => shards);
470
471 let available = shards
472 .iter()
473 .map(|shard| shard.as_ref().len())
474 .sum::<usize>();
475 let target_len = core::cmp::min(out_len, available);
476 let mut result = Vec::with_capacity(target_len);
477
478 for shard in shards {
479 let remaining = target_len.saturating_sub(result.len());
480 if remaining == 0 {
481 break;
482 }
483
484 let data = shard.as_ref();
485 let to_take = core::cmp::min(remaining, data.len());
486 result.extend_from_slice(&data[..to_take]);
487 }
488
489 result.truncate(target_len);
490 Ok(result)
491 }
492
493 pub fn with_custom_matrix(
495 data_shards: usize,
496 parity_shards: usize,
497 custom_matrix: &[Vec<F::Elem>],
498 mut options: CodecOptions,
499 ) -> Result<ReedSolomon<F>, Error> {
500 if data_shards == 0 {
501 return Err(Error::TooFewDataShards);
502 }
503 if parity_shards == 0 {
504 return Err(Error::TooFewParityShards);
505 }
506 let total_shards = data_shards
507 .checked_add(parity_shards)
508 .ok_or(Error::TooManyShards)?;
509 if total_shards > F::ORDER {
510 return Err(Error::TooManyShards);
511 }
512
513 if options.codec_family != CodecFamily::Classic {
518 return Err(Error::UnsupportedCodecFamily);
519 }
520
521 options.matrix_mode = MatrixMode::Custom;
522 options.inversion_cache_capacity = Self::normalize_inversion_cache_capacity(
523 data_shards,
524 parity_shards,
525 options.inversion_cache_capacity,
526 );
527
528 let matrix = Self::build_custom_matrix(data_shards, total_shards, custom_matrix)?;
529 let family_state = super::leopard::build_family_state(
530 options.codec_family,
531 data_shards,
532 parity_shards,
533 &matrix,
534 )?;
535 #[cfg(feature = "std")]
536 let policy_cache = Self::resolve_policy_cache_with_options(options);
537
538 Ok(ReedSolomon {
539 data_shard_count: data_shards,
540 parity_shard_count: parity_shards,
541 total_shard_count: total_shards,
542 codec_family: options.codec_family,
543 family_state,
544 matrix,
545 options,
546 #[cfg(feature = "std")]
547 policy_cache,
548 data_decode_matrix_cache: Mutex::new(LruCache::new(options.inversion_cache_capacity)),
549 #[cfg(feature = "std")]
550 reconstruction_cache_metrics: ReconstructionCacheMetrics::default(),
551 #[cfg(feature = "std")]
552 runtime_profile_metrics: RuntimeProfileMetrics::default(),
553 })
554 }
555}