extern crate alloc;
use alloc::vec::Vec;
use crate::Field;
use crate::errors::Error;
use crate::matrix::Matrix;
use super::{CodecFamily, LeopardMode};
pub(crate) const LEOPARD_GF8_MAX_SHARDS: usize = 256;
pub(crate) const LEOPARD_GF16_MAX_SHARDS: usize = 65536;
const LEOPARD_GF8_MAX_PARITY: usize = 128;
pub(crate) fn is_byte_field<F: Field>() -> bool {
core::mem::size_of::<F::Elem>() == 1
}
pub(crate) trait AsLeopardU8: Sized {
fn slice_to_u8(slice: &[Self]) -> &[u8];
fn slice_to_u8_mut(slice: &mut [Self]) -> &mut [u8];
}
impl AsLeopardU8 for u8 {
#[inline]
fn slice_to_u8(slice: &[u8]) -> &[u8] {
slice
}
#[inline]
fn slice_to_u8_mut(slice: &mut [u8]) -> &mut [u8] {
slice
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum FamilyState<F: Field> {
Classic,
LeopardGF8(LeopardGF8Codec<F>),
LeopardGF16,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct LeopardGF8Codec<F: Field> {
data_shards: usize,
parity_shards: usize,
total_shards: usize,
setup_rows: usize,
setup_cols: usize,
parity_rows: Vec<Vec<F::Elem>>,
_marker: core::marker::PhantomData<F>,
}
impl<F: Field> LeopardGF8Codec<F> {
pub(crate) fn new(
data_shards: usize,
parity_shards: usize,
setup_matrix: Matrix<F>,
) -> Result<Self, Error> {
validate_leopard_gf8::<F>(data_shards, parity_shards)?;
Ok(Self {
data_shards,
parity_shards,
total_shards: data_shards.saturating_add(parity_shards),
setup_rows: setup_matrix.row_count(),
setup_cols: setup_matrix.col_count(),
parity_rows: (data_shards..(data_shards + parity_shards))
.map(|row| setup_matrix.get_row(row).to_vec())
.collect(),
_marker: core::marker::PhantomData,
})
}
pub(crate) fn data_shards(&self) -> usize {
self.data_shards
}
pub(crate) fn parity_shards(&self) -> usize {
self.parity_shards
}
pub(crate) fn total_shards(&self) -> usize {
self.total_shards
}
pub(crate) fn setup_shape(&self) -> (usize, usize) {
(self.setup_rows, self.setup_cols)
}
pub(crate) fn parity_rows(&self) -> Vec<&[F::Elem]> {
self.parity_rows.iter().map(|row| row.as_slice()).collect()
}
}
pub(crate) fn leopard_gf8_state<F: Field>(
family_state: &FamilyState<F>,
) -> Result<&LeopardGF8Codec<F>, Error> {
match family_state {
FamilyState::LeopardGF8(codec) => Ok(codec),
FamilyState::Classic => Err(Error::UnsupportedCodecFamily),
FamilyState::LeopardGF16 => Err(Error::UnsupportedLeopardPrototype),
}
}
pub(crate) fn validate_leopard_shard_len(shard_len: usize) -> Result<(), Error> {
if shard_len == 0 || !shard_len.is_multiple_of(LEOPARD_SHARD_MULTIPLE) {
return Err(Error::IncorrectShardSize);
}
Ok(())
}
pub const LEOPARD_SHARD_MULTIPLE: usize = 64;
pub fn leopard_aligned_shard_len(data_len: usize, data_shards: usize) -> usize {
let shards = if data_shards == 0 { 1 } else { data_shards };
let per_shard = data_len.div_ceil(shards);
let remainder = per_shard % LEOPARD_SHARD_MULTIPLE;
let rounded = if remainder == 0 {
per_shard
} else {
per_shard.saturating_add(LEOPARD_SHARD_MULTIPLE - remainder)
};
let clamped = rounded.max(LEOPARD_SHARD_MULTIPLE);
clamped - (clamped % LEOPARD_SHARD_MULTIPLE)
}
#[cfg(test)]
#[allow(clippy::items_after_test_module)]
mod leopard_shard_len_tests {
use super::{LEOPARD_SHARD_MULTIPLE, leopard_aligned_shard_len, validate_leopard_shard_len};
#[test]
fn zero_data_len_clamps_to_multiple() {
let len = leopard_aligned_shard_len(0, 4);
assert_eq!(len, LEOPARD_SHARD_MULTIPLE);
assert!(validate_leopard_shard_len(len).is_ok());
}
#[test]
fn non_multiple_payload_rounds_up() {
let len = leopard_aligned_shard_len(100, 4);
assert_eq!(len, 64);
let len2 = leopard_aligned_shard_len(65 * 4, 4);
assert_eq!(len2, 128);
assert!(len.is_multiple_of(LEOPARD_SHARD_MULTIPLE));
assert!(validate_leopard_shard_len(len).is_ok());
assert!(validate_leopard_shard_len(len2).is_ok());
}
#[test]
fn zero_shards_treated_as_one() {
let len = leopard_aligned_shard_len(200, 0);
assert_eq!(len, 256);
assert!(validate_leopard_shard_len(len).is_ok());
}
#[test]
fn near_usize_max_saturates_to_valid_multiple() {
let len = leopard_aligned_shard_len(usize::MAX, 1);
assert_ne!(len, 0);
assert!(len.is_multiple_of(LEOPARD_SHARD_MULTIPLE));
assert!(validate_leopard_shard_len(len).is_ok());
assert_eq!(len, usize::MAX - (usize::MAX % LEOPARD_SHARD_MULTIPLE));
}
}
pub(crate) fn build_family_state<F: Field>(
codec_family: CodecFamily,
data_shards: usize,
parity_shards: usize,
setup_matrix: &Matrix<F>,
) -> Result<FamilyState<F>, Error> {
match codec_family {
CodecFamily::Classic => Ok(FamilyState::Classic),
CodecFamily::LeopardGF8 => Ok(FamilyState::LeopardGF8(LeopardGF8Codec::new(
data_shards,
parity_shards,
{
let mut matrix = Matrix::new(setup_matrix.row_count(), setup_matrix.col_count());
for row in 0..setup_matrix.row_count() {
for col in 0..setup_matrix.col_count() {
matrix.set(row, col, setup_matrix.get(row, col));
}
}
matrix
},
)?)),
CodecFamily::LeopardGF16 => Ok(FamilyState::LeopardGF16),
}
}
pub(crate) fn validate_leopard_family<F: Field>(
codec_family: CodecFamily,
data_shards: usize,
parity_shards: usize,
) -> Result<(), Error> {
match codec_family {
CodecFamily::Classic => Ok(()),
CodecFamily::LeopardGF8 => validate_leopard_gf8::<F>(data_shards, parity_shards),
CodecFamily::LeopardGF16 => validate_leopard_gf16::<F>(data_shards, parity_shards),
}
}
pub(crate) fn max_total_shards_for_family<F: Field>(codec_family: CodecFamily) -> usize {
match codec_family {
CodecFamily::Classic => F::ORDER,
CodecFamily::LeopardGF8 => LEOPARD_GF8_MAX_SHARDS,
CodecFamily::LeopardGF16 => LEOPARD_GF16_MAX_SHARDS,
}
}
pub(crate) fn resolve_codec_family(
requested: CodecFamily,
mode: LeopardMode,
is_byte_field: bool,
total_shards: usize,
parity_shards: usize,
) -> CodecFamily {
if requested != CodecFamily::Classic || !is_byte_field {
return requested;
}
match mode {
LeopardMode::Disabled => CodecFamily::Classic,
LeopardMode::AsNeeded => {
if total_shards <= LEOPARD_GF8_MAX_SHARDS {
CodecFamily::Classic
} else {
CodecFamily::LeopardGF16
}
}
LeopardMode::PreferGF16 => CodecFamily::LeopardGF16,
LeopardMode::PreferLeopard => {
if total_shards <= LEOPARD_GF8_MAX_SHARDS && parity_shards <= LEOPARD_GF8_MAX_PARITY {
CodecFamily::LeopardGF8
} else {
CodecFamily::LeopardGF16
}
}
}
}
fn validate_leopard_gf8<F: Field>(_data_shards: usize, _parity_shards: usize) -> Result<(), Error> {
if !is_byte_field::<F>() {
return Err(Error::UnsupportedCodecFamily);
}
Ok(())
}
fn validate_leopard_gf16<F: Field>(
_data_shards: usize,
_parity_shards: usize,
) -> Result<(), Error> {
if !is_byte_field::<F>() {
return Err(Error::UnsupportedCodecFamily);
}
if cfg!(target_endian = "big") {
return Err(Error::UnsupportedCodecFamily);
}
Ok(())
}
pub(crate) fn leopard_gf8_encode(
data_shards: usize,
parity_shards: usize,
data: &[&[u8]],
parity: &mut [&mut [u8]],
) -> Result<(), Error> {
super::leopard_gf8::encode_with_tables(data_shards, parity_shards, data, parity)?;
Ok(())
}
pub(crate) fn leopard_gf16_encode(
data_shards: usize,
parity_shards: usize,
data: &[&[u8]],
parity: &mut [&mut [u8]],
) -> Result<(), Error> {
super::leopard_gf16::encode::encode_with_tables16(data_shards, parity_shards, data, parity)?;
Ok(())
}
pub(crate) fn leopard_gf16_reconstruct(
present: &[bool],
outputs: &mut [&mut [u8]],
input_data: &[Option<&[u8]>],
data_shards: usize,
parity_shards: usize,
) -> Result<(), Error> {
let tables = super::leopard_gf16::init_leopard_gf16_tables();
super::leopard_gf16::decode::reconstruct_with_tables16(
present,
outputs,
input_data,
data_shards,
parity_shards,
tables,
)
}
#[cfg(test)]
#[allow(clippy::items_after_test_module)]
mod auto_activation_tests {
use super::{CodecFamily, LeopardMode, max_total_shards_for_family, resolve_codec_family};
use crate::{galois_8, galois_16};
fn resolve(mode: LeopardMode, total: usize, parity: usize) -> CodecFamily {
resolve_codec_family(CodecFamily::Classic, mode, true, total, parity)
}
#[test]
fn disabled_is_always_classic() {
for &(total, parity) in &[(2usize, 1usize), (256, 1), (257, 1), (65536, 4)] {
assert_eq!(
resolve(LeopardMode::Disabled, total, parity),
CodecFamily::Classic
);
}
}
#[test]
fn as_needed_switches_to_gf16_past_256() {
assert_eq!(resolve(LeopardMode::AsNeeded, 2, 1), CodecFamily::Classic);
assert_eq!(resolve(LeopardMode::AsNeeded, 256, 1), CodecFamily::Classic);
assert_eq!(
resolve(LeopardMode::AsNeeded, 257, 1),
CodecFamily::LeopardGF16
);
assert_eq!(
resolve(LeopardMode::AsNeeded, 65536, 4),
CodecFamily::LeopardGF16
);
}
#[test]
fn prefer_gf16_is_always_gf16() {
assert_eq!(
resolve(LeopardMode::PreferGF16, 2, 1),
CodecFamily::LeopardGF16
);
assert_eq!(
resolve(LeopardMode::PreferGF16, 256, 1),
CodecFamily::LeopardGF16
);
assert_eq!(
resolve(LeopardMode::PreferGF16, 65536, 4),
CodecFamily::LeopardGF16
);
}
#[test]
fn prefer_leopard_uses_gf8_only_when_small_and_low_parity() {
assert_eq!(
resolve(LeopardMode::PreferLeopard, 256, 128),
CodecFamily::LeopardGF8
);
assert_eq!(
resolve(LeopardMode::PreferLeopard, 6, 2),
CodecFamily::LeopardGF8
);
assert_eq!(
resolve(LeopardMode::PreferLeopard, 256, 129),
CodecFamily::LeopardGF16
);
assert_eq!(
resolve(LeopardMode::PreferLeopard, 257, 1),
CodecFamily::LeopardGF16
);
}
#[test]
fn explicit_family_is_never_rewritten() {
for mode in [
LeopardMode::AsNeeded,
LeopardMode::PreferGF16,
LeopardMode::PreferLeopard,
] {
assert_eq!(
resolve_codec_family(CodecFamily::LeopardGF8, mode, true, 10, 2),
CodecFamily::LeopardGF8
);
assert_eq!(
resolve_codec_family(CodecFamily::LeopardGF16, mode, true, 10, 2),
CodecFamily::LeopardGF16
);
}
}
#[test]
fn non_byte_field_ignores_mode() {
assert_eq!(
resolve_codec_family(CodecFamily::Classic, LeopardMode::PreferGF16, false, 300, 4),
CodecFamily::Classic
);
}
#[test]
fn family_aware_caps() {
assert_eq!(
max_total_shards_for_family::<galois_8::Field>(CodecFamily::Classic),
256
);
assert_eq!(
max_total_shards_for_family::<galois_16::Field>(CodecFamily::Classic),
65536
);
assert_eq!(
max_total_shards_for_family::<galois_8::Field>(CodecFamily::LeopardGF8),
256
);
assert_eq!(
max_total_shards_for_family::<galois_8::Field>(CodecFamily::LeopardGF16),
65536
);
}
}