Skip to content

Commit 81cb63c

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 81cb63c

File tree

2 files changed

+46
-6
lines changed

2 files changed

+46
-6
lines changed

zstd.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,7 @@ func Decompress(dst, src []byte) ([]byte, error) {
143143
dst = make([]byte, bound)
144144
}
145145

146-
written := int(C.ZSTD_decompress(
147-
unsafe.Pointer(&dst[0]),
148-
C.size_t(len(dst)),
149-
unsafe.Pointer(&src[0]),
150-
C.size_t(len(src))))
151-
err := getError(written)
146+
written, err := DecompressInto(dst, src)
152147
if err == nil {
153148
return dst[:written], nil
154149
}
@@ -161,3 +156,19 @@ func Decompress(dst, src []byte) ([]byte, error) {
161156
defer r.Close()
162157
return ioutil.ReadAll(r)
163158
}
159+
160+
// DecompressInto decompresses src into dst. Unlike Decompress, DecompressInto
161+
// requires that dst be sufficiently large to hold the decompressed payload.
162+
// DecompressInto may be used when the caller knows the size of the decompressed
163+
// payload before attempting decompression.
164+
//
165+
// It returns the number of bytes copied and an error if any is encountered. If
166+
// dst is too small, DecompressInto errors.
167+
func DecompressInto(dst, src []byte) (int, error) {
168+
written := int(C.ZSTD_decompress(
169+
unsafe.Pointer(&dst[0]),
170+
C.size_t(len(dst)),
171+
unsafe.Pointer(&src[0]),
172+
C.size_t(len(src))))
173+
return written, getError(written)
174+
}

zstd_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,35 @@ 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 n, err := DecompressInto(decompressed, compressed); err != nil {
102+
t.Fatalf("error while decompressing into buffer of size %d: %v",
103+
len(decompressed), err)
104+
} else if n != len(decompressed) {
105+
t.Errorf("DecompressedInto = (%d, nil), want (%d, nil)", n, len(decompressed))
106+
}
107+
if !bytes.Equal(payload, decompressed) {
108+
t.Fatalf("DecompressInto(_, Compress(_, %q)) yielded %q, want %q", payload, decompressed, payload)
109+
}
110+
111+
// Ensure that decompressing into a buffer too small errors appropriately.
112+
smallBuffer := make([]byte, len(payload)-1)
113+
if _, err := DecompressInto(smallBuffer, compressed); !IsDstSizeTooSmallError(err) {
114+
t.Fatalf("DecompressInto(<%d-sized buffer>, Compress(_, %q)) = %v, want 'Destination buffer is too small'",
115+
len(smallBuffer), payload, err)
116+
}
117+
}
118+
90119
func TestCompressLevel(t *testing.T) {
91120
inputs := [][]byte{
92121
nil, {}, {0}, []byte("Hello World!"),

0 commit comments

Comments
 (0)