@@ -217,6 +217,45 @@ mod tests {
217217 assert ! ( decompressed. is_empty( ) ) ;
218218 }
219219
220+ #[ test]
221+ fn incompressible_packets_round_trip ( ) {
222+ // Incompressible input makes deflate output land on or past the
223+ // `input.len() + 10` reservation, so the compress loops must keep
224+ // going when the buffer comes back exactly full, on both `compress`
225+ // and `compress_into`.
226+ let mut comp = Compress :: None ;
227+ let mut decomp = Decompress :: None ;
228+ Compression :: Zlib . init_compress ( & mut comp) ;
229+ Compression :: Zlib . init_decompress ( & mut decomp) ;
230+
231+ let mut seed = 0x9e37_79b9_7f4a_7c15_u64 ;
232+ for & len in & [ 1usize , 100 , 4096 , 32768 , 65536 , 200_000 , 262_144 ] {
233+ let payload: Vec < u8 > = ( 0 ..len)
234+ . map ( |_| {
235+ seed ^= seed << 13 ;
236+ seed ^= seed >> 7 ;
237+ seed ^= seed << 17 ;
238+ ( seed >> 24 ) as u8
239+ } )
240+ . collect ( ) ;
241+
242+ let mut cbuf = Vec :: new ( ) ;
243+ let compressed = comp. compress ( & payload, & mut cbuf) . unwrap ( ) . to_vec ( ) ;
244+ let mut dbuf = Vec :: new ( ) ;
245+ let out = decomp. decompress ( & compressed, & mut dbuf) . unwrap ( ) ;
246+ assert_eq ! ( out, payload. as_slice( ) , "compress: {len} bytes" ) ;
247+
248+ let prefix = b"hdr" . len ( ) ;
249+ let mut cbuf = b"hdr" . to_vec ( ) ;
250+ let n = comp. compress_into ( & payload, & mut cbuf, prefix) . unwrap ( ) ;
251+ assert_eq ! ( & cbuf[ ..prefix] , b"hdr" ) ;
252+ assert_eq ! ( cbuf. len( ) , prefix + n) ;
253+ let mut dbuf = Vec :: new ( ) ;
254+ let out = decomp. decompress ( & cbuf[ prefix..] , & mut dbuf) . unwrap ( ) ;
255+ assert_eq ! ( out, payload. as_slice( ) , "compress_into: {len} bytes" ) ;
256+ }
257+ }
258+
220259 #[ test]
221260 fn partial_flush_packets_round_trip ( ) {
222261 // Real SSH packets are partial flushes on one continuous stream, not
@@ -262,10 +301,17 @@ impl Compress {
262301 let n_out_ = z. total_out ( ) as usize - n_out;
263302 #[ allow( clippy:: indexing_slicing) ] // length checked
264303 let c = z. compress ( & input[ n_in_..] , & mut output[ n_out_..] , flush) ?;
304+ // A flush is complete only once deflate leaves room in
305+ // the output buffer; `Ok` with the buffer exactly full
306+ // means more is pending.
307+ let output_full = z. total_out ( ) as usize - n_out == output. len ( ) ;
265308 match c {
266309 flate2:: Status :: BufError => {
267310 output. resize ( output. len ( ) * 2 , 0 ) ;
268311 }
312+ _ if output_full => {
313+ output. resize ( output. len ( ) * 2 , 0 ) ;
314+ }
269315 _ => break ,
270316 }
271317 }
@@ -300,11 +346,17 @@ impl Compress {
300346 let n_out_ = z. total_out ( ) as usize - n_out;
301347 #[ allow( clippy:: indexing_slicing) ] // length checked
302348 let c = z. compress ( & input[ n_in_..] , & mut output[ start_len + n_out_..] , flush) ?;
349+ // See `compress`: full buffer on `Ok` means more pending.
350+ let output_full = start_len + ( z. total_out ( ) as usize - n_out) == output. len ( ) ;
303351 match c {
304352 flate2:: Status :: BufError => {
305353 let growth = output. len ( ) . saturating_sub ( start_len) . max ( 1 ) ;
306354 output. resize ( output. len ( ) + growth, 0 ) ;
307355 }
356+ _ if output_full => {
357+ let growth = output. len ( ) . saturating_sub ( start_len) . max ( 1 ) ;
358+ output. resize ( output. len ( ) + growth, 0 ) ;
359+ }
308360 _ => break ,
309361 }
310362 }
@@ -341,11 +393,16 @@ impl Decompress {
341393 let d = z. decompress ( & input[ n_in_..] , & mut output[ n_out_..] , flush) ;
342394 match d? {
343395 flate2:: Status :: Ok | flate2:: Status :: BufError => {
344- let consumed_all_input =
345- z . total_in ( ) as usize - n_in == input . len ( ) ;
396+ let made_progress = z . total_in ( ) as usize - n_in != n_in_
397+ || z . total_out ( ) as usize - n_out != n_out_ ;
346398 let output_full = z. total_out ( ) as usize - n_out == output. len ( ) ;
347399
348- if !output_full && consumed_all_input {
400+ // Keep going while calls still produce output: the
401+ // inflater can return `BufError` with all input
402+ // consumed and decoded bytes still buffered inside
403+ // it (miniz_oxide >= 0.9 does exactly that). Only
404+ // a call that moves nothing means we're done.
405+ if !output_full && !made_progress {
349406 break ;
350407 }
351408
0 commit comments