Bug
snappyframesz.decode rejects a stream that contains only the 10-byte stream-identifier chunk (no data chunks), even though the Snappy framing spec makes this a valid representation of an empty payload.
Repro
const std = @import("std");
const frames = @import("snappyframesz");
pub fn main() !void {
const allocator = std.heap.page_allocator;
// Stream identifier chunk only — no data chunks.
// ff 06 00 00 sNaPpY (10 bytes)
const header_only = "\xff\x06\x00\x00sNaPpY";
// Sanity: encoding the empty payload through the same lib produces
// additional chunks because finish() forces an empty data chunk
// when nothing was written.
const encoded_via_lib = try frames.encode(allocator, "");
defer allocator.free(encoded_via_lib);
std.debug.print("lib encode(\"\").len = {d}\n", .{encoded_via_lib.len});
// The header-only form is what every other Snappy implementation
// accepts (Go `snappy.NewReader`, Rust `snap`). snappyframesz
// returns FrameError.NotFramed instead of an empty slice.
const decoded = try frames.decode(allocator, header_only);
defer allocator.free(decoded);
std.debug.print("decode(header_only).len = {d}\n", .{decoded.len});
}
Actual: error: NotFramed
Expected: [] (empty slice)
Root cause
src/frames.zig:276 (current master):
```zig
if (!saw_data_chunk) return FrameError.NotFramed;
```
The function exits successfully only after at least one data chunk has been seen. A stream that contains only the stream identifier — which is a perfectly valid empty Snappy frame — falls through this branch and returns NotFramed. The outer decode then falls back to snappyz.decode on the magic-header bytes, which also fails.
Cross-impl behaviour
Same 10-byte input `ff 06 00 00 sNaPpY`:
This bites cross-client interop testing — leanSpec emits exactly this 10-byte stream as the canonical empty-input fixture for `snappy_frame`, and the zeam spec-test runner currently has to skip that one fixture explicitly.
Suggested fix
Drop the `saw_data_chunk` guard, or make it conditional on having seen neither a stream identifier nor a data chunk:
```zig
if (!saw_stream_identifier and !saw_data_chunk) return FrameError.NotFramed;
```
A stream identifier alone is a complete (empty) frame; the function should return an empty slice in that case.
Discovered via
zeam PR #715 — implementing the leanSpec `networking_codec` spec-test runner. The empty-input snappy_frame fixture (`leanSpec/fixtures/consensus/networking_codec/devnet/networking/test_snappy_codec/test_snappy_frame_empty.json`, expectedFramed=`0xff060000734e61507059`) is the only one that doesn't round-trip through `snappyframesz`.
Bug
snappyframesz.decoderejects a stream that contains only the 10-byte stream-identifier chunk (no data chunks), even though the Snappy framing spec makes this a valid representation of an empty payload.Repro
Actual:
error: NotFramedExpected:
[](empty slice)Root cause
src/frames.zig:276(currentmaster):```zig
if (!saw_data_chunk) return FrameError.NotFramed;
```
The function exits successfully only after at least one data chunk has been seen. A stream that contains only the stream identifier — which is a perfectly valid empty Snappy frame — falls through this branch and returns
NotFramed. The outerdecodethen falls back tosnappyz.decodeon the magic-header bytes, which also fails.Cross-impl behaviour
Same 10-byte input `ff 06 00 00 sNaPpY`:
This bites cross-client interop testing — leanSpec emits exactly this 10-byte stream as the canonical empty-input fixture for `snappy_frame`, and the zeam spec-test runner currently has to skip that one fixture explicitly.
Suggested fix
Drop the `saw_data_chunk` guard, or make it conditional on having seen neither a stream identifier nor a data chunk:
```zig
if (!saw_stream_identifier and !saw_data_chunk) return FrameError.NotFramed;
```
A stream identifier alone is a complete (empty) frame; the function should return an empty slice in that case.
Discovered via
zeam PR #715 — implementing the leanSpec `networking_codec` spec-test runner. The empty-input snappy_frame fixture (`leanSpec/fixtures/consensus/networking_codec/devnet/networking/test_snappy_codec/test_snappy_frame_empty.json`, expectedFramed=`0xff060000734e61507059`) is the only one that doesn't round-trip through `snappyframesz`.