Skip to content

Commit cd60686

Browse files
committed
fix(gzip): reuse caller output buffer
- Inflate single-member streams directly into the supplied output buffer. - Preserve ISIZE bounds checks without allocating and copying a temporary buffer. - Add regression coverage for buffer identity and decoded contents.
1 parent 50cc6f1 commit cd60686

2 files changed

Lines changed: 39 additions & 13 deletions

File tree

src/gzip.mbt

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,23 @@ fn gzhl(o : GzipOptions) -> Int {
102102
10 + (if o.filename.length() > 0 { o.filename.length() + 1 } else { 0 })
103103
}
104104

105+
///|
106+
/// Select the fixed output buffer for the single-member fast path.
107+
fn single_member_gunzip_buffer(
108+
isize : Int,
109+
out : FixedArray[Byte]?,
110+
) -> FixedArray[Byte] raise FzipError {
111+
match out {
112+
Some(buf) => {
113+
if isize > buf.length() {
114+
raise fzip_err(InvalidZipData, msg="output buffer too small")
115+
}
116+
buf
117+
}
118+
None => FixedArray::make(isize, b'\x00')
119+
}
120+
}
121+
105122
///|
106123
/// Try the fixed-buffer one-shot path used by ordinary single-member streams.
107124
/// Return `None` when the first DEFLATE stream ends before the final footer or
@@ -120,11 +137,12 @@ fn try_gunzip_single_member(
120137
None
121138
}
122139
let inflate_st = InflateState::new(2)
140+
let output = single_member_gunzip_buffer(isize, opts.out)
123141
let (buf, len) = try
124142
inflt(
125143
data,
126144
inflate_st,
127-
Some(FixedArray::make(isize, b'\x00')),
145+
Some(output),
128146
opts.dictionary,
129147
opts.max_input_size,
130148
opts.max_output_size,
@@ -160,18 +178,7 @@ fn try_gunzip_single_member(
160178
if len != isize {
161179
raise fzip_err(InvalidZipData, msg="gzip ISIZE mismatch")
162180
}
163-
Some(
164-
match opts.out {
165-
Some(out) => {
166-
if len > out.length() {
167-
raise fzip_err(InvalidZipData, msg="output buffer too small")
168-
}
169-
buf.blit_to(out, len~, dst_offset=0)
170-
trim_buf(out, len)
171-
}
172-
None => trim_buf(buf, len)
173-
},
174-
)
181+
Some(trim_buf(buf, len))
175182
}
176183

177184
///|

src/gzip_wbtest.mbt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,25 @@ test "gunzip - trims oversized caller output buffer" {
144144
}
145145
}
146146

147+
///|
148+
test "gunzip - single-member fast path reuses caller output buffer" {
149+
let out = FixedArray::make(5, b'\x00')
150+
let selected = single_member_gunzip_buffer(5, Some(out))
151+
assert_true(physical_equal(selected, out))
152+
let data : FixedArray[Byte] = [b'H', b'e', b'l', b'l', b'o']
153+
let result = gunzip_sync(gzip_sync(data), opts={
154+
out: Some(out),
155+
dictionary: None,
156+
max_output_size: default_max_output_size,
157+
max_input_size: default_max_input_size,
158+
verify_checksum: true,
159+
})
160+
assert_true(physical_equal(result, out))
161+
for i in 0..<data.length() {
162+
assert_eq(result[i], data[i])
163+
}
164+
}
165+
147166
///|
148167
test "gzip level 9" {
149168
let data : FixedArray[Byte] = [1, 2, 3, 4, 5]

0 commit comments

Comments
 (0)