fix: accept identifier-only stream as empty payload (zig-0.16 rebase of #8)#9
Merged
GrapeBaBa merged 1 commit intoMay 11, 2026
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's `test_snappy_frame_empty`) 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` be 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. This is the zig-0.16-ready version of blockblaz#8 (which was cut against the 0.15.2 commit base and never reconciled with the `v0.16.0` branch).
1 task
GrapeBaBa
added a commit
that referenced
this pull request
May 11, 2026
Reconcile the `main` and `v0.16.0` branches so the default branch ships zig 0.16 support together with the empty-stream decode fix. Both branches independently cherry-picked the same logical empty-fix on different bases (main on 0.15.2 via #8, v0.16.0 on 0.16 via #9), which produced two collisions when merging: 1. The terminal `if (!saw_data_chunk)` check in `decodeFromReader` and `decodeFramed` is the same line on both sides; keep one copy with v0.16.0's longer comment (more interop context). 2. `test "decode accepts identifier-only stream as empty payload"` and `test "decodeFromReader accepts identifier-only stream as empty payload"` appear on both branches. The main copy still uses `std.io.fixedBufferStream` / `ArrayListUnmanaged.writer`, which were removed in zig 0.16 — drop those duplicates and keep the v0.16.0 versions that use the new `std.Io.Reader.fixed(...)` / `Writer.Allocating` API. After the merge `main` carries the zig 0.16 upgrade (`df262c6`, `f939ed6`) plus a single canonical empty-fix regression suite, and `zig build test` is green under zig 0.16.0 (3/3 build steps, 13/13 tests). Downstream consumers can now point at the default branch instead of the `v0.16.0` release-style branch (which has been the only 0.16-ready snapshot until today).
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
decodeanddecodeFromReaderaccept a stream that contains the identifier chunk only (no data chunks) and decode it to an empty slice — matching Go'ssnappy.NewReaderand Rust'ssnap::read::FrameDecoderand unblocking cross-client interop fixtures (e.g. leanSpec'stest_snappy_frame_empty).v0.16.0branch. fix: accept identifier-only stream as empty payload #8 was cut against the 0.15.2 commit base; its empty-fix never landed on the 0.16.0 branch, so downstream consumers on zig 0.16 currently have to choose between the fix (stuck on 0.15) and the upgrade (no fix).Why
The terminal
if (!saw_data_chunk) return FrameError.NotFramedrejects the canonical 10-byte"\xff\x06\x00\x00sNaPpY"empty-stream encoding that the Snappy framing spec allows. Peer clients accept it. zeam vendored the patched version locally to pass leanSpec interop; reconciling here removes that vendoring need.What changed
src/frames.zig: the terminal check in bothdecodeFromReaderanddecodeFramedis nowif (!saw_stream_identifier and !saw_data_chunk). Identifier-only streams fall through and return empty output; truly unframed input is still rejected.decode accepts identifier-only stream as empty payloaddecodeFromReader accepts identifier-only stream as empty payload(uses 0.16'sstd.Io.Reader.fixed(...)/Writer.Allocatingpattern, matching the existingdecodeFromReader matches decodetest in this branch.)frame roundtrip samplestest covered""through the lib's own encoder, but the encoder appends an empty data chunk infinish()— that masked the gap on the decode side, which is why the bug only surfaces under peer-emitted frames.Test plan
zig build teston zig 0.16.0 — Build Summary: 3/3 steps succeeded; 13/13 tests passed.frames.zigproduction change and re-running — both fail witherror.NotFramed).