feat: M1 lumen-core (FEC/crypto/packet/session + C ABI) and workspace scaffold

Ground-up low-latency streaming stack per docs/implementation-plan.md. M1 is
complete and tested; Linux host backends are cfg-gated stubs to be filled in on
real hardware (M0/M2).

lumen-core (built + tested on macOS/aarch64 — 21 tests):
- fec: ErasureCoder over GF(2^8) (reed-solomon-erasure, Moonlight-compatible)
  and GF(2^16) Leopard-RS (reed-solomon-simd, the >1 Gbps wall-breaker); proptested
- packet: zero-copy #[repr(C)] framing, multi-block, FEC-aware reassembly
- crypto: AES-128-GCM with per-direction nonce salts + sequence-as-AAD
- session: host submit / client poll hot paths + input; loopback & UDP transports
- abi: opaque handles, versioned LumenConfig, panic guards; cbindgen-generated header
- acceptance: Rust loopback+proptest and a C harness that links the staticlib

Scaffold (compiles green on all platforms): lumen-host (vdisplay/capture/encode/
inject/web/pipeline seams under cfg(linux)), lumen-client-rs, tools/{loss-harness,
latency-probe}, Apple/Android client stubs, Gitea CI, docs.

Hardened against a multi-agent adversarial review (13 verified findings fixed,
regression-tested): reassembler memory-DoS bounds + block-consistency validation,
GCM nonce-reuse direction separation, ABI struct_size guard + range checks, FEC
shard-length guards, shard_payload datagram bound, key zeroization + Debug redaction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 00:02:52 +02:00
parent 4a1e3cd2fd
commit a988909196
46 changed files with 5719 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
//! GF(2⁸) classic ReedSolomon backend (`reed-solomon-erasure`), equivalent to the
//! `nanors` library Moonlight uses. Hard ceiling: data + recovery ≤ 255 shards/block.
use super::{validate_block_shape, validate_encode_shape, ErasureCoder, FecError};
use crate::config::FecScheme;
use reed_solomon_erasure::galois_8::ReedSolomon;
pub struct Gf8Coder;
impl ErasureCoder for Gf8Coder {
fn scheme(&self) -> FecScheme {
FecScheme::Gf8
}
fn encode(&self, data: &[Vec<u8>], recovery_count: usize) -> Result<Vec<Vec<u8>>, FecError> {
if recovery_count == 0 {
return Ok(Vec::new());
}
validate_encode_shape(data)?;
let k = data.len();
let shard_len = data[0].len();
let rs = ReedSolomon::new(k, recovery_count)
.map_err(|_| FecError::Config("invalid GF(2^8) shard counts"))?;
// reed-solomon-erasure fills parity in place: shards = data || zeroed parity.
let mut shards: Vec<Vec<u8>> = Vec::with_capacity(k + recovery_count);
shards.extend_from_slice(data);
shards.resize_with(k + recovery_count, || vec![0u8; shard_len]);
rs.encode(&mut shards)
.map_err(|_| FecError::Backend("gf8 encode"))?;
Ok(shards.split_off(k))
}
fn reconstruct(
&self,
data_count: usize,
recovery_count: usize,
received: &mut [Option<Vec<u8>>],
) -> Result<Vec<Vec<u8>>, FecError> {
validate_block_shape(received, data_count, recovery_count)?;
let present = received.iter().filter(|s| s.is_some()).count();
if present < data_count {
return Err(FecError::TooFewShards {
have: present,
need: data_count,
});
}
if recovery_count == 0 {
// No FEC: every original must already be present.
return collect_originals(received, data_count);
}
let rs = ReedSolomon::new(data_count, recovery_count)
.map_err(|_| FecError::Config("invalid GF(2^8) shard counts"))?;
rs.reconstruct_data(received)
.map_err(|_| FecError::Backend("gf8 reconstruct"))?;
collect_originals(received, data_count)
}
}
fn collect_originals(
received: &[Option<Vec<u8>>],
data_count: usize,
) -> Result<Vec<Vec<u8>>, FecError> {
let mut out = Vec::with_capacity(data_count);
for slot in received.iter().take(data_count) {
out.push(
slot.clone()
.ok_or(FecError::Backend("reconstruction left an original missing"))?,
);
}
Ok(out)
}