use crate::{
common::RLNCError,
primitives::{field::Field, packet::RLNCPacket},
};
#[derive(Debug)]
pub(crate) struct Matrix<F: Field> {
chunk_count: usize,
data: Vec<RLNCPacket<F>>,
pivots: Vec<Option<usize>>,
rank: usize,
}
impl<F: Field> Matrix<F> {
pub(crate) fn new(chunk_count: usize) -> Self {
Self {
chunk_count,
data: Vec::with_capacity(chunk_count),
pivots: vec![None; chunk_count],
rank: 0,
}
}
pub(crate) fn decode(&self, chunk_size: usize) -> Result<Vec<u8>, RLNCError> {
if !self.can_decode() {
return Err(RLNCError::NotEnoughPackets(self.rank, self.chunk_count));
}
let symbols_per_chunk = chunk_size.div_ceil(F::SAFE_CAPACITY);
let mut chunk_symbols = vec![vec![F::ZERO; symbols_per_chunk]; self.chunk_count];
for (col, row_idx) in self
.pivots
.iter()
.enumerate()
.filter_map(|(i, &r)| r.map(|r| (i, r)))
.take(self.chunk_count)
{
let row = &self.data[row_idx];
chunk_symbols[col].copy_from_slice(&row.data);
}
let mut decoded = Vec::with_capacity(chunk_size * self.chunk_count);
for chunk in chunk_symbols {
let chunk_bytes = chunk.iter().flat_map(|s| s.to_bytes()).collect::<Vec<_>>();
decoded.extend_from_slice(&chunk_bytes);
}
let Some(boundary_pos) = decoded.iter().rposition(|&b| b == crate::common::BOUNDARY_MARKER)
else {
return Err(RLNCError::InvalidEncoding);
};
decoded.truncate(boundary_pos);
Ok(decoded)
}
pub(crate) fn push_rref(&mut self, mut packet: RLNCPacket<F>) -> bool {
self.eliminate(&mut packet);
if let Some(col) = packet.leading_coefficient() {
if self.pivots[col].is_none() {
packet.normalize();
self.pivots[col] = Some(self.data.len());
self.data.push(packet);
self.back_substitute(self.data.len() - 1);
self.rank += 1;
return self.can_decode();
}
}
false
}
fn eliminate(&mut self, packet: &mut RLNCPacket<F>) {
for (col, row) in self
.pivots
.iter()
.enumerate()
.filter_map(|(i, &r)| r.map(|r| (i, r)))
.take(self.chunk_count)
{
let coeff = packet.coding_vector[col];
if !coeff.is_zero_vartime() {
let pivot_row = &self.data[row];
let pivot_coeff = pivot_row.coding_vector[col];
let factor = coeff * pivot_coeff.invert().unwrap();
packet.subtract_row(pivot_row, factor);
}
}
}
fn back_substitute(&mut self, new_row_idx: usize) {
let new_row = &self.data[new_row_idx];
let Some(new_pivot_col) = new_row.leading_coefficient() else {
return;
};
let new_row = new_row.clone();
for i in 0..new_row_idx {
let coeff = self.data[i].coding_vector[new_pivot_col];
if !coeff.is_zero_vartime() {
let factor = coeff;
self.data[i].subtract_row(&new_row, factor);
}
}
}
#[inline]
pub(crate) const fn rank(&self) -> usize {
self.rank
}
#[inline]
pub(crate) const fn can_decode(&self) -> bool {
self.rank >= self.chunk_count
}
}