1extern crate alloc;
2
3use alloc::vec::Vec;
4
5use crate::Field;
6use crate::errors::Error;
7use crate::matrix::Matrix;
8
9use super::{CodecFamily, LeopardMode};
10
11pub(crate) const LEOPARD_GF8_MAX_SHARDS: usize = 256;
19pub(crate) const LEOPARD_GF16_MAX_SHARDS: usize = 65536;
20
21const LEOPARD_GF8_MAX_PARITY: usize = 128;
25
26pub(crate) fn is_byte_field<F: Field>() -> bool {
34 core::mem::size_of::<F::Elem>() == 1
35}
36
37pub(crate) trait AsLeopardU8: Sized {
42 fn slice_to_u8(slice: &[Self]) -> &[u8];
43 fn slice_to_u8_mut(slice: &mut [Self]) -> &mut [u8];
44}
45
46impl AsLeopardU8 for u8 {
47 #[inline]
48 fn slice_to_u8(slice: &[u8]) -> &[u8] {
49 slice
50 }
51 #[inline]
52 fn slice_to_u8_mut(slice: &mut [u8]) -> &mut [u8] {
53 slice
54 }
55}
56
57#[derive(Debug, Clone, PartialEq)]
58pub(crate) enum FamilyState<F: Field> {
59 Classic,
60 LeopardGF8(LeopardGF8Codec<F>),
61 LeopardGF16,
62}
63
64#[derive(Debug, Clone, PartialEq)]
65pub(crate) struct LeopardGF8Codec<F: Field> {
66 data_shards: usize,
67 parity_shards: usize,
68 total_shards: usize,
69 setup_rows: usize,
70 setup_cols: usize,
71 parity_rows: Vec<Vec<F::Elem>>,
72 _marker: core::marker::PhantomData<F>,
73}
74
75impl<F: Field> LeopardGF8Codec<F> {
76 pub(crate) fn new(
77 data_shards: usize,
78 parity_shards: usize,
79 setup_matrix: Matrix<F>,
80 ) -> Result<Self, Error> {
81 validate_leopard_gf8::<F>(data_shards, parity_shards)?;
82
83 Ok(Self {
84 data_shards,
85 parity_shards,
86 total_shards: data_shards.saturating_add(parity_shards),
87 setup_rows: setup_matrix.row_count(),
88 setup_cols: setup_matrix.col_count(),
89 parity_rows: (data_shards..(data_shards + parity_shards))
90 .map(|row| setup_matrix.get_row(row).to_vec())
91 .collect(),
92 _marker: core::marker::PhantomData,
93 })
94 }
95
96 pub(crate) fn data_shards(&self) -> usize {
97 self.data_shards
98 }
99
100 pub(crate) fn parity_shards(&self) -> usize {
101 self.parity_shards
102 }
103
104 pub(crate) fn total_shards(&self) -> usize {
105 self.total_shards
106 }
107
108 pub(crate) fn setup_shape(&self) -> (usize, usize) {
109 (self.setup_rows, self.setup_cols)
110 }
111
112 pub(crate) fn parity_rows(&self) -> Vec<&[F::Elem]> {
113 self.parity_rows.iter().map(|row| row.as_slice()).collect()
114 }
115}
116
117pub(crate) fn leopard_gf8_state<F: Field>(
118 family_state: &FamilyState<F>,
119) -> Result<&LeopardGF8Codec<F>, Error> {
120 match family_state {
121 FamilyState::LeopardGF8(codec) => Ok(codec),
122 FamilyState::Classic => Err(Error::UnsupportedCodecFamily),
123 FamilyState::LeopardGF16 => Err(Error::UnsupportedLeopardPrototype),
124 }
125}
126
127pub(crate) fn validate_leopard_shard_len(shard_len: usize) -> Result<(), Error> {
128 if shard_len == 0 || !shard_len.is_multiple_of(LEOPARD_SHARD_MULTIPLE) {
129 return Err(Error::IncorrectShardSize);
130 }
131
132 Ok(())
133}
134
135pub const LEOPARD_SHARD_MULTIPLE: usize = 64;
141
142pub fn leopard_aligned_shard_len(data_len: usize, data_shards: usize) -> usize {
154 let shards = if data_shards == 0 { 1 } else { data_shards };
157
158 let per_shard = data_len.div_ceil(shards);
161
162 let remainder = per_shard % LEOPARD_SHARD_MULTIPLE;
165 let rounded = if remainder == 0 {
166 per_shard
167 } else {
168 per_shard.saturating_add(LEOPARD_SHARD_MULTIPLE - remainder)
169 };
170
171 let clamped = rounded.max(LEOPARD_SHARD_MULTIPLE);
175 clamped - (clamped % LEOPARD_SHARD_MULTIPLE)
176}
177
178#[cfg(test)]
181#[allow(clippy::items_after_test_module)]
182mod leopard_shard_len_tests {
183 use super::{LEOPARD_SHARD_MULTIPLE, leopard_aligned_shard_len, validate_leopard_shard_len};
184
185 #[test]
186 fn zero_data_len_clamps_to_multiple() {
187 let len = leopard_aligned_shard_len(0, 4);
188 assert_eq!(len, LEOPARD_SHARD_MULTIPLE);
189 assert!(validate_leopard_shard_len(len).is_ok());
190 }
191
192 #[test]
193 fn non_multiple_payload_rounds_up() {
194 let len = leopard_aligned_shard_len(100, 4);
196 assert_eq!(len, 64);
197 let len2 = leopard_aligned_shard_len(65 * 4, 4);
199 assert_eq!(len2, 128);
200 assert!(len.is_multiple_of(LEOPARD_SHARD_MULTIPLE));
201 assert!(validate_leopard_shard_len(len).is_ok());
202 assert!(validate_leopard_shard_len(len2).is_ok());
203 }
204
205 #[test]
206 fn zero_shards_treated_as_one() {
207 let len = leopard_aligned_shard_len(200, 0);
209 assert_eq!(len, 256);
210 assert!(validate_leopard_shard_len(len).is_ok());
211 }
212
213 #[test]
214 fn near_usize_max_saturates_to_valid_multiple() {
215 let len = leopard_aligned_shard_len(usize::MAX, 1);
216 assert_ne!(len, 0);
217 assert!(len.is_multiple_of(LEOPARD_SHARD_MULTIPLE));
218 assert!(validate_leopard_shard_len(len).is_ok());
219 assert_eq!(len, usize::MAX - (usize::MAX % LEOPARD_SHARD_MULTIPLE));
221 }
222}
223
224pub(crate) fn build_family_state<F: Field>(
225 codec_family: CodecFamily,
226 data_shards: usize,
227 parity_shards: usize,
228 setup_matrix: &Matrix<F>,
229) -> Result<FamilyState<F>, Error> {
230 match codec_family {
231 CodecFamily::Classic => Ok(FamilyState::Classic),
232 CodecFamily::LeopardGF8 => Ok(FamilyState::LeopardGF8(LeopardGF8Codec::new(
233 data_shards,
234 parity_shards,
235 {
236 let mut matrix = Matrix::new(setup_matrix.row_count(), setup_matrix.col_count());
237 for row in 0..setup_matrix.row_count() {
238 for col in 0..setup_matrix.col_count() {
239 matrix.set(row, col, setup_matrix.get(row, col));
240 }
241 }
242 matrix
243 },
244 )?)),
245 CodecFamily::LeopardGF16 => Ok(FamilyState::LeopardGF16),
246 }
247}
248
249pub(crate) fn validate_leopard_family<F: Field>(
250 codec_family: CodecFamily,
251 data_shards: usize,
252 parity_shards: usize,
253) -> Result<(), Error> {
254 match codec_family {
255 CodecFamily::Classic => Ok(()),
256 CodecFamily::LeopardGF8 => validate_leopard_gf8::<F>(data_shards, parity_shards),
257 CodecFamily::LeopardGF16 => validate_leopard_gf16::<F>(data_shards, parity_shards),
258 }
259}
260
261pub(crate) fn max_total_shards_for_family<F: Field>(codec_family: CodecFamily) -> usize {
270 match codec_family {
271 CodecFamily::Classic => F::ORDER,
272 CodecFamily::LeopardGF8 => LEOPARD_GF8_MAX_SHARDS,
273 CodecFamily::LeopardGF16 => LEOPARD_GF16_MAX_SHARDS,
274 }
275}
276
277pub(crate) fn resolve_codec_family(
295 requested: CodecFamily,
296 mode: LeopardMode,
297 is_byte_field: bool,
298 total_shards: usize,
299 parity_shards: usize,
300) -> CodecFamily {
301 if requested != CodecFamily::Classic || !is_byte_field {
305 return requested;
306 }
307
308 match mode {
309 LeopardMode::Disabled => CodecFamily::Classic,
310 LeopardMode::AsNeeded => {
311 if total_shards <= LEOPARD_GF8_MAX_SHARDS {
312 CodecFamily::Classic
313 } else {
314 CodecFamily::LeopardGF16
315 }
316 }
317 LeopardMode::PreferGF16 => CodecFamily::LeopardGF16,
318 LeopardMode::PreferLeopard => {
319 if total_shards <= LEOPARD_GF8_MAX_SHARDS && parity_shards <= LEOPARD_GF8_MAX_PARITY {
320 CodecFamily::LeopardGF8
321 } else {
322 CodecFamily::LeopardGF16
323 }
324 }
325 }
326}
327
328fn validate_leopard_gf8<F: Field>(_data_shards: usize, _parity_shards: usize) -> Result<(), Error> {
334 if !is_byte_field::<F>() {
336 return Err(Error::UnsupportedCodecFamily);
337 }
338 Ok(())
339}
340
341fn validate_leopard_gf16<F: Field>(
342 _data_shards: usize,
343 _parity_shards: usize,
344) -> Result<(), Error> {
345 if !is_byte_field::<F>() {
347 return Err(Error::UnsupportedCodecFamily);
348 }
349
350 if cfg!(target_endian = "big") {
356 return Err(Error::UnsupportedCodecFamily);
357 }
358
359 Ok(())
360}
361
362pub(crate) fn leopard_gf8_encode(
368 data_shards: usize,
369 parity_shards: usize,
370 data: &[&[u8]],
371 parity: &mut [&mut [u8]],
372) -> Result<(), Error> {
373 super::leopard_gf8::encode_with_tables(data_shards, parity_shards, data, parity)?;
374 Ok(())
375}
376
377pub(crate) fn leopard_gf16_encode(
379 data_shards: usize,
380 parity_shards: usize,
381 data: &[&[u8]],
382 parity: &mut [&mut [u8]],
383) -> Result<(), Error> {
384 super::leopard_gf16::encode::encode_with_tables16(data_shards, parity_shards, data, parity)?;
385 Ok(())
386}
387
388pub(crate) fn leopard_gf16_reconstruct(
390 present: &[bool],
391 outputs: &mut [&mut [u8]],
392 input_data: &[Option<&[u8]>],
393 data_shards: usize,
394 parity_shards: usize,
395) -> Result<(), Error> {
396 let tables = super::leopard_gf16::init_leopard_gf16_tables();
397 super::leopard_gf16::decode::reconstruct_with_tables16(
398 present,
399 outputs,
400 input_data,
401 data_shards,
402 parity_shards,
403 tables,
404 )
405}
406
407#[cfg(test)]
408#[allow(clippy::items_after_test_module)]
409mod auto_activation_tests {
410 use super::{CodecFamily, LeopardMode, max_total_shards_for_family, resolve_codec_family};
411 use crate::{galois_8, galois_16};
412
413 fn resolve(mode: LeopardMode, total: usize, parity: usize) -> CodecFamily {
416 resolve_codec_family(CodecFamily::Classic, mode, true, total, parity)
417 }
418
419 #[test]
420 fn disabled_is_always_classic() {
421 for &(total, parity) in &[(2usize, 1usize), (256, 1), (257, 1), (65536, 4)] {
422 assert_eq!(
423 resolve(LeopardMode::Disabled, total, parity),
424 CodecFamily::Classic
425 );
426 }
427 }
428
429 #[test]
430 fn as_needed_switches_to_gf16_past_256() {
431 assert_eq!(resolve(LeopardMode::AsNeeded, 2, 1), CodecFamily::Classic);
432 assert_eq!(resolve(LeopardMode::AsNeeded, 256, 1), CodecFamily::Classic);
433 assert_eq!(
434 resolve(LeopardMode::AsNeeded, 257, 1),
435 CodecFamily::LeopardGF16
436 );
437 assert_eq!(
438 resolve(LeopardMode::AsNeeded, 65536, 4),
439 CodecFamily::LeopardGF16
440 );
441 }
442
443 #[test]
444 fn prefer_gf16_is_always_gf16() {
445 assert_eq!(
446 resolve(LeopardMode::PreferGF16, 2, 1),
447 CodecFamily::LeopardGF16
448 );
449 assert_eq!(
450 resolve(LeopardMode::PreferGF16, 256, 1),
451 CodecFamily::LeopardGF16
452 );
453 assert_eq!(
454 resolve(LeopardMode::PreferGF16, 65536, 4),
455 CodecFamily::LeopardGF16
456 );
457 }
458
459 #[test]
460 fn prefer_leopard_uses_gf8_only_when_small_and_low_parity() {
461 assert_eq!(
463 resolve(LeopardMode::PreferLeopard, 256, 128),
464 CodecFamily::LeopardGF8
465 );
466 assert_eq!(
467 resolve(LeopardMode::PreferLeopard, 6, 2),
468 CodecFamily::LeopardGF8
469 );
470 assert_eq!(
472 resolve(LeopardMode::PreferLeopard, 256, 129),
473 CodecFamily::LeopardGF16
474 );
475 assert_eq!(
477 resolve(LeopardMode::PreferLeopard, 257, 1),
478 CodecFamily::LeopardGF16
479 );
480 }
481
482 #[test]
483 fn explicit_family_is_never_rewritten() {
484 for mode in [
485 LeopardMode::AsNeeded,
486 LeopardMode::PreferGF16,
487 LeopardMode::PreferLeopard,
488 ] {
489 assert_eq!(
490 resolve_codec_family(CodecFamily::LeopardGF8, mode, true, 10, 2),
491 CodecFamily::LeopardGF8
492 );
493 assert_eq!(
494 resolve_codec_family(CodecFamily::LeopardGF16, mode, true, 10, 2),
495 CodecFamily::LeopardGF16
496 );
497 }
498 }
499
500 #[test]
501 fn non_byte_field_ignores_mode() {
502 assert_eq!(
505 resolve_codec_family(CodecFamily::Classic, LeopardMode::PreferGF16, false, 300, 4),
506 CodecFamily::Classic
507 );
508 }
509
510 #[test]
511 fn family_aware_caps() {
512 assert_eq!(
514 max_total_shards_for_family::<galois_8::Field>(CodecFamily::Classic),
515 256
516 );
517 assert_eq!(
518 max_total_shards_for_family::<galois_16::Field>(CodecFamily::Classic),
519 65536
520 );
521 assert_eq!(
523 max_total_shards_for_family::<galois_8::Field>(CodecFamily::LeopardGF8),
524 256
525 );
526 assert_eq!(
527 max_total_shards_for_family::<galois_8::Field>(CodecFamily::LeopardGF16),
528 65536
529 );
530 }
531}