use alloc::vec;
use alloc::vec::Vec;
use crate::error::MatrixError;
use crate::gf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Matrix {
rows: usize,
cols: usize,
data: Vec<u8>,
}
const fn vandermonde_safe(k: usize, m: usize) -> bool {
k <= 3 || (k == 4 && m <= 25) || (k == 5 && m <= 10) || (k <= 21 && m - k == 4) || m - k <= 3
}
fn check_dims(k: usize, p: usize) -> Result<(), MatrixError> {
if k == 0 || p == 0 || !k.checked_add(p).is_some_and(|m| m <= 255) {
return Err(MatrixError::Dimensions { k, p });
}
Ok(())
}
impl Matrix {
pub fn reed_solomon(k: usize, p: usize) -> Result<Self, MatrixError> {
check_dims(k, p)?;
let m = k + p;
if !vandermonde_safe(k, m) {
return Err(MatrixError::VandermondeUnsafe { k, p });
}
let mut data = vec![0u8; m * k];
for i in 0..k {
data[k * i + i] = 1;
}
let mut row_gen: u8 = 1;
for i in k..m {
let mut coeff: u8 = 1;
for j in 0..k {
data[k * i + j] = coeff;
coeff = gf::mul(coeff, row_gen);
}
row_gen = gf::mul(row_gen, 2);
}
Ok(Self {
rows: m,
cols: k,
data,
})
}
pub fn cauchy(k: usize, p: usize) -> Result<Self, MatrixError> {
check_dims(k, p)?;
let m = k + p;
let mut data = vec![0u8; m * k];
for i in 0..k {
data[k * i + i] = 1;
}
for i in k..m {
for j in 0..k {
data[k * i + j] = gf::inv((i ^ j) as u8);
}
}
Ok(Self {
rows: m,
cols: k,
data,
})
}
pub fn from_bytes(rows: usize, cols: usize, data: Vec<u8>) -> Result<Self, MatrixError> {
if rows == 0 || cols == 0 || rows > 256 || cols > 256 || data.len() != rows * cols {
return Err(MatrixError::Dimensions {
k: cols,
p: rows.saturating_sub(cols),
});
}
Ok(Self { rows, cols, data })
}
pub fn rows(&self) -> usize {
self.rows
}
pub fn cols(&self) -> usize {
self.cols
}
pub fn get(&self, row: usize, col: usize) -> Option<u8> {
if row < self.rows && col < self.cols {
Some(self.data[self.cols * row + col])
} else {
None
}
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn parity_bytes(&self) -> &[u8] {
&self.data[self.cols * self.cols..]
}
pub fn select_rows(&self, indices: &[usize]) -> Result<Self, MatrixError> {
if indices.is_empty() || indices.len() > 256 {
return Err(MatrixError::Dimensions { k: self.cols, p: 0 });
}
let mut data = Vec::with_capacity(indices.len() * self.cols);
for &r in indices {
if r >= self.rows {
return Err(MatrixError::Dimensions { k: self.cols, p: 0 });
}
data.extend_from_slice(&self.data[self.cols * r..self.cols * (r + 1)]);
}
Ok(Self {
rows: indices.len(),
cols: self.cols,
data,
})
}
pub fn invert(&self) -> Result<Self, MatrixError> {
if self.rows != self.cols {
return Err(MatrixError::Dimensions {
k: self.cols,
p: self.rows.saturating_sub(self.cols),
});
}
let n = self.rows;
let mut a = self.data.clone();
let mut out = vec![0u8; n * n];
invert_gauss_jordan(&mut a, &mut out, n)?;
Ok(Self {
rows: n,
cols: n,
data: out,
})
}
pub fn multiply(&self, rhs: &Self) -> Result<Self, MatrixError> {
if self.cols != rhs.rows {
return Err(MatrixError::Dimensions { k: rhs.rows, p: 0 });
}
let mut data = vec![0u8; self.rows * rhs.cols];
for i in 0..self.rows {
for j in 0..rhs.cols {
let mut s = 0u8;
for t in 0..self.cols {
s ^= gf::mul(self.data[self.cols * i + t], rhs.data[rhs.cols * t + j]);
}
data[rhs.cols * i + j] = s;
}
}
Ok(Self {
rows: self.rows,
cols: rhs.cols,
data,
})
}
pub fn is_identity(&self) -> bool {
self.rows == self.cols
&& self
.data
.iter()
.enumerate()
.all(|(idx, &v)| v == u8::from(idx / self.cols == idx % self.cols))
}
}
pub fn invert_gauss_jordan(a: &mut [u8], out: &mut [u8], n: usize) -> Result<(), MatrixError> {
if n == 0
|| !n
.checked_mul(n)
.is_some_and(|nn| a.len() == nn && out.len() == nn)
{
return Err(MatrixError::Dimensions { k: n, p: 0 });
}
out.fill(0);
for i in 0..n {
out[n * i + i] = 1;
}
for i in 0..n {
if a[n * i + i] == 0 {
let mut pivot = None;
for j in i + 1..n {
if a[n * j + i] != 0 {
pivot = Some(j);
break;
}
}
let Some(j) = pivot else {
return Err(MatrixError::Singular);
};
for c in 0..n {
a.swap(n * i + c, n * j + c);
out.swap(n * i + c, n * j + c);
}
}
let scale = gf::inv(a[n * i + i]);
for c in 0..n {
a[n * i + c] = gf::mul(a[n * i + c], scale);
out[n * i + c] = gf::mul(out[n * i + c], scale);
}
for j in 0..n {
if j == i {
continue;
}
let f = a[n * j + i];
if f == 0 {
continue;
}
for c in 0..n {
out[n * j + c] ^= gf::mul(f, out[n * i + c]);
a[n * j + c] ^= gf::mul(f, a[n * i + c]);
}
}
}
Ok(())
}