Skip to content

Commit 175d6a9

Browse files
LessUpCopilot
andcommitted
fix(rust): restore D7/D8/D10 streaming trait adapters
Restores spec compliance for Rust streaming API requirements D7/D8/D10 by reintroducing StreamingEncoder/StreamingDecoder wrappers for Huffman, Arithmetic, and RLE algorithms. Previous commit 31b8b2f removed too much - it correctly eliminated duplicate src/bin/*.rs files and lib_base.rs, but also removed the streaming trait implementations required by the spec. Changes: - Add StreamingEncoder/StreamingDecoder to huffman/rust/src/lib.rs - Add StreamingEncoder/StreamingDecoder to arithmetic/rust/src/lib.rs - Add StreamingEncoder/StreamingDecoder to rle/rust/src/lib.rs - Add compresskit-codec dependency to all three algorithm crates - Add integration tests in shared/rust/tests/lifecycle.rs that verify: * All algorithms implement Encoder/Decoder traits * Roundtrip encode/decode works via trait objects Implementation uses buffered wrapper strategy (not incremental codec): - process() accumulates input in memory buffer - flush() transitions state but produces no output - finish() performs full encode/decode and writes to output buffer - Follows exact same pattern as range/rust reference implementation Scope preserved: - NO src/bin/*.rs duplicates reintroduced - NO lib_base.rs duplicates reintroduced - main.rs remains CLI source of truth Tests: - cargo test --manifest-path algorithms/shared/rust/Cargo.toml All 9 tests pass (lifecycle + integration) - cargo build for huffman/arithmetic/rle: all succeed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 31b8b2f commit 175d6a9

12 files changed

Lines changed: 846 additions & 3 deletions

File tree

algorithms/arithmetic/rust/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

algorithms/arithmetic/rust/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ rust-version = "1.70"
88
name = "arithmetic"
99
path = "src/lib.rs"
1010

11+
[dependencies]
12+
compresskit-codec = { path = "../../shared/rust" }
13+
1114
[dev-dependencies]

algorithms/arithmetic/rust/src/lib.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,193 @@ pub fn decode(input: &[u8]) -> Result<Vec<u8>, io::Error> {
234234

235235
Ok(output)
236236
}
237+
238+
// Streaming adapters
239+
use compresskit_codec::codec::{CodecError, Decoder as CodecDecoder, Encoder as CodecEncoder, State};
240+
241+
fn io_error_to_codec_error(e: io::Error) -> CodecError {
242+
match e.kind() {
243+
io::ErrorKind::UnexpectedEof => CodecError::Truncated,
244+
io::ErrorKind::InvalidData => {
245+
let msg = e.to_string();
246+
if msg.contains("truncated") || msg.contains("too short") {
247+
CodecError::Truncated
248+
} else if msg.contains("invalid") || msg.contains("bad") {
249+
CodecError::Corrupt
250+
} else {
251+
CodecError::Other(msg)
252+
}
253+
}
254+
_ => CodecError::Other(e.to_string()),
255+
}
256+
}
257+
258+
pub struct StreamingEncoder {
259+
state: State,
260+
buffer: Vec<u8>,
261+
}
262+
263+
impl StreamingEncoder {
264+
pub fn new() -> Self {
265+
StreamingEncoder {
266+
state: State::Ready,
267+
buffer: Vec::new(),
268+
}
269+
}
270+
}
271+
272+
impl Default for StreamingEncoder {
273+
fn default() -> Self {
274+
Self::new()
275+
}
276+
}
277+
278+
impl CodecEncoder for StreamingEncoder {
279+
fn process(&mut self, input: &[u8], _output: &mut [u8]) -> Result<usize, CodecError> {
280+
match self.state {
281+
State::Ready | State::Flushing => {
282+
self.state = State::Streaming;
283+
self.buffer.extend_from_slice(input);
284+
Ok(0)
285+
}
286+
State::Streaming => {
287+
self.buffer.extend_from_slice(input);
288+
Ok(0)
289+
}
290+
State::Finished => {
291+
self.state = State::Error;
292+
Err(CodecError::InvalidState)
293+
}
294+
State::Error => Err(CodecError::InvalidState),
295+
}
296+
}
297+
298+
fn flush(&mut self, _output: &mut [u8]) -> Result<usize, CodecError> {
299+
match self.state {
300+
State::Ready => Ok(0),
301+
State::Streaming => {
302+
self.state = State::Flushing;
303+
Ok(0)
304+
}
305+
State::Flushing => Ok(0),
306+
State::Finished => {
307+
self.state = State::Error;
308+
Err(CodecError::InvalidState)
309+
}
310+
State::Error => Err(CodecError::InvalidState),
311+
}
312+
}
313+
314+
fn finish(&mut self, output: &mut [u8]) -> Result<usize, CodecError> {
315+
match self.state {
316+
State::Ready | State::Streaming | State::Flushing => {
317+
let encoded = encode(&self.buffer).map_err(io_error_to_codec_error)?;
318+
if output.len() < encoded.len() {
319+
return Err(CodecError::BufTooSmall);
320+
}
321+
output[..encoded.len()].copy_from_slice(&encoded);
322+
self.state = State::Finished;
323+
Ok(encoded.len())
324+
}
325+
State::Finished => {
326+
self.state = State::Error;
327+
Err(CodecError::InvalidState)
328+
}
329+
State::Error => Err(CodecError::InvalidState),
330+
}
331+
}
332+
333+
fn reset(&mut self) {
334+
self.state = State::Ready;
335+
self.buffer.clear();
336+
}
337+
338+
fn state(&self) -> State {
339+
self.state
340+
}
341+
}
342+
343+
pub struct StreamingDecoder {
344+
state: State,
345+
buffer: Vec<u8>,
346+
}
347+
348+
impl StreamingDecoder {
349+
pub fn new() -> Self {
350+
StreamingDecoder {
351+
state: State::Ready,
352+
buffer: Vec::new(),
353+
}
354+
}
355+
}
356+
357+
impl Default for StreamingDecoder {
358+
fn default() -> Self {
359+
Self::new()
360+
}
361+
}
362+
363+
impl CodecDecoder for StreamingDecoder {
364+
fn process(&mut self, input: &[u8], _output: &mut [u8]) -> Result<usize, CodecError> {
365+
match self.state {
366+
State::Ready | State::Flushing => {
367+
self.state = State::Streaming;
368+
self.buffer.extend_from_slice(input);
369+
Ok(0)
370+
}
371+
State::Streaming => {
372+
self.buffer.extend_from_slice(input);
373+
Ok(0)
374+
}
375+
State::Finished => {
376+
self.state = State::Error;
377+
Err(CodecError::InvalidState)
378+
}
379+
State::Error => Err(CodecError::InvalidState),
380+
}
381+
}
382+
383+
fn flush(&mut self, _output: &mut [u8]) -> Result<usize, CodecError> {
384+
match self.state {
385+
State::Ready => Ok(0),
386+
State::Streaming => {
387+
self.state = State::Flushing;
388+
Ok(0)
389+
}
390+
State::Flushing => Ok(0),
391+
State::Finished => {
392+
self.state = State::Error;
393+
Err(CodecError::InvalidState)
394+
}
395+
State::Error => Err(CodecError::InvalidState),
396+
}
397+
}
398+
399+
fn finish(&mut self, output: &mut [u8]) -> Result<usize, CodecError> {
400+
match self.state {
401+
State::Ready | State::Streaming | State::Flushing => {
402+
let decoded = decode(&self.buffer).map_err(io_error_to_codec_error)?;
403+
if output.len() < decoded.len() {
404+
return Err(CodecError::BufTooSmall);
405+
}
406+
output[..decoded.len()].copy_from_slice(&decoded);
407+
self.state = State::Finished;
408+
Ok(decoded.len())
409+
}
410+
State::Finished => {
411+
self.state = State::Error;
412+
Err(CodecError::InvalidState)
413+
}
414+
State::Error => Err(CodecError::InvalidState),
415+
}
416+
}
417+
418+
fn reset(&mut self) {
419+
self.state = State::Ready;
420+
self.buffer.clear();
421+
}
422+
423+
fn state(&self) -> State {
424+
self.state
425+
}
426+
}

algorithms/huffman/rust/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

algorithms/huffman/rust/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ rust-version = "1.70"
88
name = "huffman"
99
path = "src/lib.rs"
1010

11+
[dependencies]
12+
compresskit-codec = { path = "../../shared/rust" }
13+
1114
[dev-dependencies]

0 commit comments

Comments
 (0)