Skip to content

fix: accept identifier-only stream as empty payload (zig-0.16 rebase of #8)#9

Merged
GrapeBaBa merged 1 commit into
blockblaz:v0.16.0from
GrapeBaBa:fix/empty-stream-on-zig-0.16
May 11, 2026
Merged

fix: accept identifier-only stream as empty payload (zig-0.16 rebase of #8)#9
GrapeBaBa merged 1 commit into
blockblaz:v0.16.0from
GrapeBaBa:fix/empty-stream-on-zig-0.16

Conversation

@GrapeBaBa
Copy link
Copy Markdown
Member

Summary

  • decode and decodeFromReader accept a stream that contains the identifier chunk only (no data chunks) and decode it to an empty slice — matching Go's snappy.NewReader and Rust's snap::read::FrameDecoder and unblocking cross-client interop fixtures (e.g. leanSpec's test_snappy_frame_empty).
  • Rebase of #8 onto the v0.16.0 branch. 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.NotFramed rejects 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 both decodeFromReader and decodeFramed is now if (!saw_stream_identifier and !saw_data_chunk). Identifier-only streams fall through and return empty output; truly unframed input is still rejected.
  • Two regression tests pin the contract:
    • decode accepts identifier-only stream as empty payload
    • decodeFromReader accepts identifier-only stream as empty payload (uses 0.16's std.Io.Reader.fixed(...) / Writer.Allocating pattern, matching the existing decodeFromReader matches decode test in this branch.)
  • The existing frame roundtrip samples test covered "" through the lib's own encoder, but the encoder appends an empty data chunk in finish() — that masked the gap on the decode side, which is why the bug only surfaces under peer-emitted frames.

Test plan

  • zig build test on zig 0.16.0 — Build Summary: 3/3 steps succeeded; 13/13 tests passed.
  • The two new tests catch the regression (verified by reverting just the frames.zig production change and re-running — both fail with error.NotFramed).

`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).
@GrapeBaBa GrapeBaBa merged commit d3054a8 into blockblaz:v0.16.0 May 11, 2026
2 checks passed
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant