Skip to content

fix: accept identifier-only stream as empty payload#8

Merged
GrapeBaBa merged 1 commit intoblockblaz:mainfrom
GrapeBaBa:fix/empty-stream-decode
Apr 29, 2026
Merged

fix: accept identifier-only stream as empty payload#8
GrapeBaBa merged 1 commit intoblockblaz:mainfrom
GrapeBaBa:fix/empty-stream-decode

Conversation

@GrapeBaBa
Copy link
Copy Markdown
Member

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.

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.
Copilot AI review requested due to automatic review settings April 29, 2026 14:34
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 decodeFramed and decodeFromReader to 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 GrapeBaBa merged commit cb0a08e into blockblaz:main Apr 29, 2026
6 checks passed
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.
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.

decode rejects header-only stream (valid empty Snappy frame)

2 participants