feat(core): zero-copy pooled reassembly — shards land at their final AU offset
Rewrite the client Reassembler around one whole-frame buffer per frame: frame_bytes rides in every header and packetize geometry is deterministic (every non-final block is exactly max_data_per_block data shards), so a data shard's final AU offset is computable on arrival — copy it there once, straight from the decrypt ring. New ErasureCoder::reconstruct_into decodes ONLY the missing shards directly into the frame buffer's holes (gf16 native; gf8 legacy shim); received recovery shards ride pooled shard-sized buffers. The completed buffer IS Frame::data. Deletes the per-shard to_vec + per-block concat + final AU concat (~178k allocs and a double copy of every byte per second at 2 Gbps — the pump wall the 2026-07-14 sweeps measured at 98.9% of an M3 Ultra core). Reassembly now costs ~0.4 µs/packet in-stream. The eager buffer changes the hostile-header exposure, so two new firewalls: derived-geometry validation (a header lying about its data_shards/block_count vs its own frame_bytes is dropped before it can scribble across another shard's range) and an in-flight allocation budget (IN_FLIGHT_BUF_FACTOR × max_frame_bytes) so a window of tiny first-shards can't commit gigabytes. Behavior parity pinned by the existing suite (all green unchanged) plus new end-to-end roundtrips through the real Packetizer (multi-block + partial tail, loss within budget, reversed delivery, duplicates, empty frame, unrecoverable block ages out, budget enforcement). loss-harness recovery curve identical; pipeline bench: gf8/1MB +42%, gf16 neutral (host-encode dominated). Known pre-existing quirk kept as-is: reversed delivery reconstructs early (data+recovery ≥ k) and counts late-not-lost shards into fec_recovered_shards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,9 @@
|
||||
//! client (unlike Vandermonde RS, whose parity is not interoperable). Hard ceiling: data +
|
||||
//! recovery ≤ 255 shards/block.
|
||||
|
||||
use super::{validate_block_shape, validate_encode_shape, ErasureCoder, FecError};
|
||||
use super::{
|
||||
validate_block_shape, validate_encode_shape, validate_into_shape, ErasureCoder, FecError,
|
||||
};
|
||||
use crate::config::FecScheme;
|
||||
use fec_rs::ReedSolomon;
|
||||
|
||||
@@ -56,6 +58,44 @@ impl ErasureCoder for Gf8Coder {
|
||||
.map_err(|_| FecError::Backend("gf8 reconstruct"))?;
|
||||
collect_originals(received, data_count)
|
||||
}
|
||||
|
||||
fn reconstruct_into(
|
||||
&self,
|
||||
recovery_count: usize,
|
||||
data: &mut [&mut [u8]],
|
||||
have: &[bool],
|
||||
recovery: &[(usize, &[u8])],
|
||||
) -> Result<(), FecError> {
|
||||
validate_into_shape(data, have, recovery, recovery_count)?;
|
||||
if have.iter().all(|h| *h) {
|
||||
return Ok(());
|
||||
}
|
||||
// Legacy-scheme shim: fec-rs reconstructs through owned `Option<Vec<u8>>` slots, so copy
|
||||
// the present shards into that shape and the recovered ones back out. Only P1/gf8
|
||||
// sessions on loss pay this — the hot gf16 path decodes straight into the caller's slots.
|
||||
let data_count = data.len();
|
||||
let mut received: Vec<Option<Vec<u8>>> = Vec::with_capacity(data_count + recovery_count);
|
||||
for (s, h) in data.iter().zip(have) {
|
||||
received.push(h.then(|| s.to_vec()));
|
||||
}
|
||||
received.resize(data_count + recovery_count, None);
|
||||
for &(j, bytes) in recovery {
|
||||
received[data_count + j] = Some(bytes.to_vec());
|
||||
}
|
||||
let rs = ReedSolomon::new(data_count, recovery_count)
|
||||
.map_err(|_| FecError::Config("invalid GF(2^8) shard counts"))?;
|
||||
rs.reconstruct_data(&mut received)
|
||||
.map_err(|_| FecError::Backend("gf8 reconstruct"))?;
|
||||
for (i, h) in have.iter().enumerate() {
|
||||
if !*h {
|
||||
let shard = received[i]
|
||||
.as_ref()
|
||||
.ok_or(FecError::Backend("reconstruction left an original missing"))?;
|
||||
data[i].copy_from_slice(shard);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_originals(
|
||||
|
||||
Reference in New Issue
Block a user