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 if data_shards + parity_shards > F::ORDER {
258 return Err(Error::TooManyShards);
259 }
260
261 super::leopard::validate_leopard_family::<F>(
262 options.codec_family,
263 data_shards,
264 parity_shards,
265 )?;
266
267 let total_shards = data_shards + parity_shards;
268
269 options.inversion_cache_capacity = Self::normalize_inversion_cache_capacity(
270 data_shards,
271 parity_shards,
272 options.inversion_cache_capacity,
273 );
274
275 let matrix = match options.codec_family {
276 CodecFamily::Classic => {
277 Self::build_matrix_with_options(data_shards, total_shards, options)?
278 }
279 CodecFamily::LeopardGF8 | CodecFamily::LeopardGF16 => {
280 Self::build_matrix(data_shards, total_shards)?
281 }
282 };
283 let family_state = super::leopard::build_family_state(
284 options.codec_family,
285 data_shards,
286 parity_shards,
287 &matrix,
288 )?;
289 #[cfg(feature = "std")]
290 let policy_cache = Self::resolve_policy_cache_with_options(options);
291
292 Ok(ReedSolomon {
293 data_shard_count: data_shards,
294 parity_shard_count: parity_shards,
295 total_shard_count: total_shards,
296 codec_family: options.codec_family,
297 family_state,
298 matrix,
299 options,
300 #[cfg(feature = "std")]
301 policy_cache,
302 data_decode_matrix_cache: Mutex::new(LruCache::new(options.inversion_cache_capacity)),
303 #[cfg(feature = "std")]
304 reconstruction_cache_metrics: ReconstructionCacheMetrics::default(),
305 #[cfg(feature = "std")]
306 runtime_profile_metrics: RuntimeProfileMetrics::default(),
307 })
308 }
309
310 pub fn data_shard_count(&self) -> usize {
312 self.data_shard_count
313 }
314
315 pub fn parity_shard_count(&self) -> usize {
317 self.parity_shard_count
318 }
319
320 pub fn total_shard_count(&self) -> usize {
322 self.total_shard_count
323 }
324
325 pub fn codec_family(&self) -> CodecFamily {
327 self.codec_family
328 }
329
330 pub fn leopard_setup_matrix_shape(&self) -> Option<(usize, usize)> {
332 let codec = super::leopard::leopard_gf8_state(&self.family_state).ok()?;
333 Some(codec.setup_shape())
334 }
335
336 pub fn inversion_cache_capacity(&self) -> usize {
338 self.options.inversion_cache_capacity
339 }
340
341 pub fn recommended_inversion_cache_capacity(data_shards: usize, parity_shards: usize) -> usize {
343 Self::derive_inversion_cache_capacity(data_shards, parity_shards)
344 }
345
346 #[cfg(feature = "std")]
348 pub fn reconstruction_cache_stats(&self) -> ReconstructionCacheStats {
349 self.reconstruction_cache_metrics.snapshot()
350 }
351
352 #[cfg(feature = "std")]
354 pub fn runtime_profile_stats(&self) -> RuntimeProfileStats {
355 self.runtime_profile_metrics.snapshot()
356 }
357
358 #[cfg(feature = "std")]
360 pub fn reset_runtime_profile_stats(&self) {
361 self.runtime_profile_metrics.reset();
362 }
363
364 #[cfg(feature = "std")]
365 pub(crate) fn record_reconstruct_entry_path(&self, parallel: bool) {
366 self.runtime_profile_metrics
367 .record_reconstruct_entry(parallel);
368 }
369
370 #[cfg(feature = "std")]
371 pub(crate) fn record_reconstruct_opt_fallback_serial_path(&self) {
372 self.runtime_profile_metrics
373 .record_reconstruct_opt_fallback_serial();
374 }
375
376 #[cfg(feature = "std")]
377 pub(crate) fn record_reconstruct_runtime(
378 &self,
379 data_only: bool,
380 missing_data_count: usize,
381 missing_parity_count: usize,
382 all_present: bool,
383 ) {
384 self.runtime_profile_metrics.record_reconstruct(
385 data_only,
386 missing_data_count,
387 missing_parity_count,
388 all_present,
389 );
390 }
391
392 #[cfg(feature = "std")]
393 pub(crate) fn record_reconstruct_data_stage_runtime(
394 &self,
395 shard_len: usize,
396 output_count: usize,
397 ) {
398 self.runtime_profile_metrics
399 .record_reconstruct_data_stage(shard_len, output_count);
400 }
401
402 #[cfg(feature = "std")]
403 pub(crate) fn record_reconstruct_parity_stage_runtime(
404 &self,
405 shard_len: usize,
406 output_count: usize,
407 ) {
408 self.runtime_profile_metrics
409 .record_reconstruct_parity_stage(shard_len, output_count);
410 }
411
412 pub fn split(&self, data: &[F::Elem]) -> Result<Vec<Vec<F::Elem>>, Error> {
416 let data_shards = self.data_shard_count;
417 let shard_len = if data.is_empty() {
418 0
419 } else {
420 data.len().div_ceil(data_shards)
421 };
422
423 let mut shards = Vec::with_capacity(data_shards);
424 for i in 0..data_shards {
425 let start = i * shard_len;
426 let end = core::cmp::min(start + shard_len, data.len());
427 let mut shard = vec![F::zero(); shard_len];
428 if start < data.len() {
429 shard[..end - start].copy_from_slice(&data[start..end]);
430 }
431 shards.push(shard);
432 }
433
434 Ok(shards)
435 }
436
437 pub fn join<T: AsRef<[F::Elem]>>(
441 &self,
442 shards: &[T],
443 out_len: usize,
444 ) -> Result<Vec<F::Elem>, Error> {
445 check_piece_count!(data => self, shards);
446 check_slices!(multi => shards);
447
448 let available = shards
449 .iter()
450 .map(|shard| shard.as_ref().len())
451 .sum::<usize>();
452 let target_len = core::cmp::min(out_len, available);
453 let mut result = Vec::with_capacity(target_len);
454
455 for shard in shards {
456 let remaining = target_len.saturating_sub(result.len());
457 if remaining == 0 {
458 break;
459 }
460
461 let data = shard.as_ref();
462 let to_take = core::cmp::min(remaining, data.len());
463 result.extend_from_slice(&data[..to_take]);
464 }
465
466 result.truncate(target_len);
467 Ok(result)
468 }
469
470 pub fn with_custom_matrix(
472 data_shards: usize,
473 parity_shards: usize,
474 custom_matrix: &[Vec<F::Elem>],
475 mut options: CodecOptions,
476 ) -> Result<ReedSolomon<F>, Error> {
477 if data_shards == 0 {
478 return Err(Error::TooFewDataShards);
479 }
480 if parity_shards == 0 {
481 return Err(Error::TooFewParityShards);
482 }
483 if data_shards + parity_shards > F::ORDER {
484 return Err(Error::TooManyShards);
485 }
486
487 super::leopard::validate_leopard_family::<F>(
488 options.codec_family,
489 data_shards,
490 parity_shards,
491 )?;
492
493 let total_shards = data_shards + parity_shards;
494 options.matrix_mode = MatrixMode::Custom;
495 options.inversion_cache_capacity = Self::normalize_inversion_cache_capacity(
496 data_shards,
497 parity_shards,
498 options.inversion_cache_capacity,
499 );
500
501 if options.codec_family != CodecFamily::Classic {
502 return Err(Error::UnsupportedCodecFamily);
503 }
504
505 let matrix = Self::build_custom_matrix(data_shards, total_shards, custom_matrix)?;
506 let family_state = super::leopard::build_family_state(
507 options.codec_family,
508 data_shards,
509 parity_shards,
510 &matrix,
511 )?;
512 #[cfg(feature = "std")]
513 let policy_cache = Self::resolve_policy_cache_with_options(options);
514
515 Ok(ReedSolomon {
516 data_shard_count: data_shards,
517 parity_shard_count: parity_shards,
518 total_shard_count: total_shards,
519 codec_family: options.codec_family,
520 family_state,
521 matrix,
522 options,
523 #[cfg(feature = "std")]
524 policy_cache,
525 data_decode_matrix_cache: Mutex::new(LruCache::new(options.inversion_cache_capacity)),
526 #[cfg(feature = "std")]
527 reconstruction_cache_metrics: ReconstructionCacheMetrics::default(),
528 #[cfg(feature = "std")]
529 runtime_profile_metrics: RuntimeProfileMetrics::default(),
530 })
531 }
532}