Found while addressing review on #1074. No behaviour change proposed here — this is for triage.
The writer refuses a shape the reader accepts
segment.AppendCompressed will not emit a segment with compressedLen == 0 and a nonzero uncompressedLen (internal/segment/segment.go:386), rewriting it into the store-as-is fallback. The comment there says why:
a segment with compressedLen==0 and a nonzero uncompressedLen is undecodable by the peer, so guard against it for arbitrary Compressor implementations.
ReadCompressedPayload does not make the same judgement. It hands the empty wire payload to the compressor and accepts whatever comes back, provided the length matches. Measured against Conn.readContinuationSegment with a compressor that expands an empty source, PayloadLen=0, UncompressedLen=9 returns 9 bytes and a nil error — the reader accepts what the writer calls undecodable.
With the shipped lz4 it is unreachable: the decode yields nothing and the length check rejects it. So this is a contract question, not a live bug — should the reader reject the shape outright, on the same grounds the writer does, rather than deferring to the compressor?
lz4 does not reject an empty source
LZ4Compressor.AppendDecompressed (lz4/lz4.go:131) returns (dst, nil) for an empty src with a nonzero uncompressedLength: the uncompressedLength == 0 guard does not fire, and lz4.UncompressBlock short-circuits on len(src) == 0 and returns (0, nil). The mismatch is caught one level up, by the caller's length check.
That is fine as long as every caller has one, but it is undocumented and untested. lz4/lz4_test.go:136 covers a short source (invalid source or destination buffer too short); nothing covers an empty one.
Either a test pinning the current contract or an explicit error would do; the choice depends on the answer to the first question.
Refs: #1074 (comment)
Found while addressing review on #1074. No behaviour change proposed here — this is for triage.
The writer refuses a shape the reader accepts
segment.AppendCompressedwill not emit a segment withcompressedLen == 0and a nonzerouncompressedLen(internal/segment/segment.go:386), rewriting it into the store-as-is fallback. The comment there says why:ReadCompressedPayloaddoes not make the same judgement. It hands the empty wire payload to the compressor and accepts whatever comes back, provided the length matches. Measured againstConn.readContinuationSegmentwith a compressor that expands an empty source,PayloadLen=0, UncompressedLen=9returns 9 bytes and a nil error — the reader accepts what the writer calls undecodable.With the shipped lz4 it is unreachable: the decode yields nothing and the length check rejects it. So this is a contract question, not a live bug — should the reader reject the shape outright, on the same grounds the writer does, rather than deferring to the compressor?
lz4 does not reject an empty source
LZ4Compressor.AppendDecompressed(lz4/lz4.go:131) returns(dst, nil)for an emptysrcwith a nonzerouncompressedLength: theuncompressedLength == 0guard does not fire, andlz4.UncompressBlockshort-circuits onlen(src) == 0and returns(0, nil). The mismatch is caught one level up, by the caller's length check.That is fine as long as every caller has one, but it is undocumented and untested.
lz4/lz4_test.go:136covers a short source (invalid source or destination buffer too short); nothing covers an empty one.Either a test pinning the current contract or an explicit error would do; the choice depends on the answer to the first question.
Refs: #1074 (comment)