fix: accept identifier-only stream as empty payload#8
Merged
GrapeBaBa merged 1 commit intoblockblaz:mainfrom Apr 29, 2026
Merged
fix: accept identifier-only stream as empty payload#8GrapeBaBa merged 1 commit intoblockblaz:mainfrom
GrapeBaBa merged 1 commit intoblockblaz:mainfrom
Conversation
decode and decodeFromReader rejected a stream that contained only the 10-byte stream-identifier chunk with FrameError.NotFramed, even though the Snappy framing spec treats it as a valid representation of an empty payload. Go's snappy.NewReader and Rust's snap::read::FrameDecoder both accept the same input and decode it to an empty slice; cross-client interop fixtures (e.g. leanSpec) emit exactly this 10-byte form for empty input. The terminal post-loop check in both decode paths now requires that both saw_stream_identifier and saw_data_chunk are unset to declare the input unframed. A stream with the identifier alone — and no data chunks — returns an empty slice. Adds two regression tests against the canonical 10-byte "\xff\x06\x00\x00sNaPpY" input (decode and decodeFromReader). The existing "frame roundtrip samples" test already covered round-tripping "" through the lib's own encoder, but the encoder appends an empty data chunk in finish(), which masked the gap on the decode side. Closes blockblaz#7.
There was a problem hiding this comment.
Pull request overview
Adjusts Snappy frame decoding to treat a stream containing only the stream-identifier chunk (no data chunks) as a valid empty payload, matching the Snappy framing spec and other implementations (Go/Rust), and adds regressions for this case.
Changes:
- Loosened the end-of-decode guard in both
decodeFramedanddecodeFromReaderto accept identifier-only streams as empty. - Added regression tests covering the canonical 10-byte identifier-only input for both decode paths.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
GrapeBaBa
added a commit
to blockblaz/zeam
that referenced
this pull request
Apr 29, 2026
blockblaz/snappyframesz#8 (closes #7) makes decode accept a identifier-only frame as a valid empty payload. Bump the pin to the post-merge tip and drop the local skip in the snappy_frame runner. The snappy_frame_empty fixture now exercises the same round-trip path as every other snappy fixture: leanSpec's 10-byte "\xff\x06\x00\x00sNaPpY" stream decodes to an empty slice through zeam's snappyframesz, and zeam's encoder→decoder pair round-trips the empty input cleanly. Total runtime skips drop from 83 to 82.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #7. `decode` (and `decodeFromReader`) rejected a 10-byte stream that contained only the magic identifier chunk and no data chunks, returning `FrameError.NotFramed`. The Snappy framing spec, Go's `snappy.NewReader`, and Rust's `snap::read::FrameDecoder` all accept the same input and decode it to an empty slice; cross-client interop fixtures (notably leanSpec's `networking_codec` suite) emit exactly this form for empty input.
Fix
The terminal post-loop guard in both decode paths required `saw_data_chunk` to be set:
```zig
if (!saw_data_chunk) return FrameError.NotFramed;
```
Loosen it to accept any stream that has at least seen the identifier:
```zig
if (!saw_stream_identifier and !saw_data_chunk) return FrameError.NotFramed;
```
A stream with the identifier alone (no data chunks) is a valid empty payload and now returns an empty slice.
Why existing tests didn't catch it
`test "frame roundtrip samples"` already round-trips `""`, but the lib's own encoder appends an empty data chunk in `finish()` (`writeEmptyChunk`), so the encoded output never has only the identifier. The bug was decode-side only.
Two new regression tests cover the canonical 10-byte input (`\xff\x06\x00\x00sNaPpY`) for both `decode` and `decodeFromReader`:
```
test "decode accepts identifier-only stream as empty payload"
test "decodeFromReader accepts identifier-only stream as empty payload"
```
Both fail on `main` and pass on this branch.
Test plan
Discovered while implementing the leanSpec `networking_codec` runner in zeam PR #715.