Skip to content

Commit 7002da1

Browse files
committed
add DecompressInto for decompression of payloads with known sizes
In some systems, compressed payloads are prefixed or tagged with the size of the original payload. This allows allocation of a perfectly-sized buffer to hold the decompressed payload. The current Decompress interface prohibits this type of usage, because Decompress allocates if the destination buffer is smaller than the worst-case bound. In practice, a perfectly-sized buffer will always be smaller than what's required for the worst-case payload. This commit introduces an additional DecompressInto entrypoint. DecompressInto accepts a destination buffer and requires that the decompressed payload fit into the buffer. If it does not, it returns the original error returned by zstd.
1 parent 5f14d6a commit 7002da1

File tree

2 files changed

+42
-0
lines changed

2 files changed

+42
-0
lines changed

zstd.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,18 @@ func Decompress(dst, src []byte) ([]byte, error) {
161161
defer r.Close()
162162
return ioutil.ReadAll(r)
163163
}
164+
165+
// DecompressInto decompresses src into dst. Unlike Decompress, DecompressInto
166+
// requires that dst be sufficiently large to hold the decompressed payload.
167+
// DecompressInto may be used when the caller knows the size of the decompressed
168+
// payload before attempting decompression.
169+
//
170+
// If dst is too small, DecompressInto returns an error.
171+
func DecompressInto(dst, src []byte) error {
172+
written := int(C.ZSTD_decompress(
173+
unsafe.Pointer(&dst[0]),
174+
C.size_t(len(dst)),
175+
unsafe.Pointer(&src[0]),
176+
C.size_t(len(src))))
177+
return getError(written)
178+
}

zstd_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,33 @@ func TestCompressDecompress(t *testing.T) {
8787
}
8888
}
8989

90+
func TestCompressDecompressInto(t *testing.T) {
91+
payload := []byte("Hello World!")
92+
compressed, err := Compress(make([]byte, CompressBound(len(payload))), payload)
93+
if err != nil {
94+
t.Fatalf("Error while compressing: %v", err)
95+
}
96+
t.Logf("Compressed: %v", compressed)
97+
98+
// We know the size of the payload; construct a buffer that perfectly fits
99+
// the payload and use DecompressInto.
100+
decompressed := make([]byte, len(payload))
101+
if err := DecompressInto(decompressed, compressed); err != nil {
102+
t.Fatalf("error while decompressing into buffer of size %d: %v",
103+
len(decompressed), err)
104+
}
105+
if !bytes.Equal(payload, decompressed) {
106+
t.Fatalf("DecompressInto(_, Compress(_, %q)) yielded %q, want %q", payload, decompressed, payload)
107+
}
108+
109+
// Ensure that decompressing into a buffer too small errors appropriately.
110+
smallBuffer := make([]byte, len(payload)-1)
111+
if err := DecompressInto(smallBuffer, compressed); !IsDstSizeTooSmallError(err) {
112+
t.Fatalf("DecompressInto(<%d-sized buffer>, Compress(_, %q)) = %v, want 'Destination buffer is too small'",
113+
len(smallBuffer), payload, err)
114+
}
115+
}
116+
90117
func TestCompressLevel(t *testing.T) {
91118
inputs := [][]byte{
92119
nil, {}, {0}, []byte("Hello World!"),

0 commit comments

Comments
 (0)