diff --git a/.cirrus.yml b/.cirrus.yml index e275246c..98105c63 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -9,4 +9,4 @@ task: - cmake --build build - ctest --test-dir build # Direct compilation without official build system - - cc -O2 -Wall -Werror lib/*.c lib/*/*.c programs/gzip.c programs/prog_util.c programs/tgetopt.c -o libdeflate-gzip + - cc -O2 -Wall -Werror lib/*.c lib/*/*.c programs/gzip.c programs/gzip_compress_by_stream.c programs/gzip_decompress_by_stream.c programs/prog_util.c programs/tgetopt.c -o libdeflate-gzip diff --git a/lib/decompress_template.h b/lib/decompress_template.h index 8c874c36..cf5a807e 100644 --- a/lib/decompress_template.h +++ b/lib/decompress_template.h @@ -41,13 +41,25 @@ # define EXTRACT_VARBITS8(word, count) ((word) & BITMASK((u8)(count))) #endif +#define _DEF_bitstream_byte_restore() do{ \ + bitsleft = (u8)bitsleft; \ + SAFETY_CHECK(overread_count <= (bitsleft >> 3)); \ + in_next -= (bitsleft >> 3) - overread_count; } while(0) +#define _DEF_bitstream_byte_align() do{ \ + _DEF_bitstream_byte_restore(); \ + overread_count = 0; \ + bitbuf = 0; \ + bitsleft = 0; } while(0) + static ATTRIBUTES MAYBE_UNUSED enum libdeflate_result FUNCNAME(struct libdeflate_decompressor * restrict d, const void * restrict in, size_t in_nbytes, - void * restrict out, size_t out_nbytes_avail, - size_t *actual_in_nbytes_ret, size_t *actual_out_nbytes_ret) + void * restrict out, size_t in_dict_nbytes, size_t out_nbytes_avail, + size_t *actual_in_nbytes_ret,size_t *actual_out_nbytes_ret, + enum libdeflate_decompress_stop_by stop_type,int* is_final_block_ret) { - u8 *out_next = out; + enum libdeflate_result result = LIBDEFLATE_SUCCESS; + u8 *out_next = ((u8 *)out)+in_dict_nbytes; u8 * const out_end = out_next + out_nbytes_avail; u8 * const out_fastloop_end = out_end - MIN(out_nbytes_avail, FASTLOOP_MAX_BYTES_WRITTEN); @@ -57,9 +69,9 @@ FUNCNAME(struct libdeflate_decompressor * restrict d, const u8 * const in_end = in_next + in_nbytes; const u8 * const in_fastloop_end = in_end - MIN(in_nbytes, FASTLOOP_MAX_BYTES_READ); - bitbuf_t bitbuf = 0; + bitbuf_t bitbuf = d->bitbuf_back; bitbuf_t saved_bitbuf; - u32 bitsleft = 0; + u32 bitsleft = d->bitsleft_back; size_t overread_count = 0; bool is_final_block; @@ -69,6 +81,8 @@ FUNCNAME(struct libdeflate_decompressor * restrict d, bitbuf_t litlen_tablemask; u32 entry; + _decompress_block_init(d); + next_block: /* Starting to read the next block */ ; @@ -261,12 +275,7 @@ FUNCNAME(struct libdeflate_decompressor * restrict d, * that have been refilled but not actually consumed yet (not * counting overread bytes, which don't increment 'in_next'). */ - bitsleft = (u8)bitsleft; - SAFETY_CHECK(overread_count <= (bitsleft >> 3)); - in_next -= (bitsleft >> 3) - overread_count; - overread_count = 0; - bitbuf = 0; - bitsleft = 0; + _DEF_bitstream_byte_align(); SAFETY_CHECK(in_end - in_next >= 4); len = get_unaligned_le16(in_next); @@ -275,7 +284,7 @@ FUNCNAME(struct libdeflate_decompressor * restrict d, SAFETY_CHECK(len == (u16)~nlen); if (unlikely(len > out_end - out_next)) - return LIBDEFLATE_INSUFFICIENT_SPACE; + goto _on_insufficient_space; SAFETY_CHECK(len <= in_end - in_next); memcpy(out_next, in_next, len); @@ -698,7 +707,7 @@ FUNCNAME(struct libdeflate_decompressor * restrict d, length = entry >> 16; if (entry & HUFFDEC_LITERAL) { if (unlikely(out_next == out_end)) - return LIBDEFLATE_INSUFFICIENT_SPACE; + goto _on_insufficient_space; *out_next++ = length; continue; } @@ -706,7 +715,7 @@ FUNCNAME(struct libdeflate_decompressor * restrict d, goto block_done; length += EXTRACT_VARBITS8(saved_bitbuf, entry) >> (u8)(entry >> 8); if (unlikely(length > out_end - out_next)) - return LIBDEFLATE_INSUFFICIENT_SPACE; + goto _on_insufficient_space; if (!CAN_CONSUME(LENGTH_MAXBITS + OFFSET_MAXBITS)) REFILL_BITS(); @@ -739,36 +748,62 @@ FUNCNAME(struct libdeflate_decompressor * restrict d, block_done: /* Finished decoding a block */ - - if (!is_final_block) - goto next_block; + if (is_final_block) + _DEF_bitstream_byte_align(); + + switch (stop_type){ + case LIBDEFLATE_STOP_BY_FINAL_BLOCK:{ + if (!is_final_block) + goto next_block; + } break; + case LIBDEFLATE_STOP_BY_ANY_BLOCK:{ + // stop, not next block + } break; + case LIBDEFLATE_STOP_BY_ANY_BLOCK_AND_FULL_INPUT:{ + if (in_next-((((u8)bitsleft)>>3)-overread_count)bitsleft_back=bitsleft&7; + d->bitbuf_back=bitbuf&((1<<(bitsleft&7))-1); + } - bitsleft = (u8)bitsleft; - - /* - * If any of the implicit appended zero bytes were consumed (not just - * refilled) before hitting end of stream, then the data is bad. - */ - SAFETY_CHECK(overread_count <= (bitsleft >> 3)); - +_on_return: /* Optionally return the actual number of bytes consumed. */ if (actual_in_nbytes_ret) { - /* Don't count bytes that were refilled but not consumed. */ - in_next -= (bitsleft >> 3) - overread_count; - *actual_in_nbytes_ret = in_next - (u8 *)in; } /* Optionally return the actual number of bytes written. */ if (actual_out_nbytes_ret) { - *actual_out_nbytes_ret = out_next - (u8 *)out; + *actual_out_nbytes_ret = out_next - (((u8 *)out)+in_dict_nbytes); } else { - if (out_next != out_end) - return LIBDEFLATE_SHORT_OUTPUT; + if ( (out_next != out_end) && (result==LIBDEFLATE_SUCCESS) ) + result = LIBDEFLATE_SHORT_OUTPUT; } - return LIBDEFLATE_SUCCESS; + return result; + +_on_bad_data: + result=LIBDEFLATE_BAD_DATA; + goto _on_return; +_on_insufficient_space: + result=LIBDEFLATE_INSUFFICIENT_SPACE; + goto _on_return; } #undef FUNCNAME diff --git a/lib/deflate_compress.c b/lib/deflate_compress.c index f8856d24..f1a8ff45 100644 --- a/lib/deflate_compress.c +++ b/lib/deflate_compress.c @@ -450,11 +450,18 @@ struct block_split_stats { struct deflate_output_bitstream; +/* + * The type for the bitbuffer variable, which temporarily holds bits that are + * being packed into bytes and written to the output buffer. For best + * performance, this should have size equal to a machine word. + */ +typedef machine_word_t bitbuf_t; + /* The main DEFLATE compressor structure */ struct libdeflate_compressor { /* Pointer to the compress() implementation chosen at allocation time */ - void (*impl)(struct libdeflate_compressor *restrict c, const u8 *in, + void (*impl)(struct libdeflate_compressor *restrict c,const u8* in_block_with_dict,size_t dict_nbytes, size_t in_nbytes, struct deflate_output_bitstream *os); /* The free() function for this struct, chosen at allocation time */ @@ -462,6 +469,9 @@ struct libdeflate_compressor { /* The compression level with which this compressor was created */ unsigned compression_level; + bool in_is_final_block; + unsigned bitcount_back_for_block; + bitbuf_t bitbuf_back_for_block; /* Anything of this size or less we won't bother trying to compress. */ size_t max_passthrough_size; @@ -661,12 +671,10 @@ struct libdeflate_compressor { } p; /* (p)arser */ }; -/* - * The type for the bitbuffer variable, which temporarily holds bits that are - * being packed into bytes and written to the output buffer. For best - * performance, this should have size equal to a machine word. - */ -typedef machine_word_t bitbuf_t; +static forceinline void _compress_block_reset(struct libdeflate_compressor* c){ + c->bitbuf_back_for_block=0; + c->bitcount_back_for_block=0; +} /* * The capacity of the bitbuffer, in bits. This is 1 less than the real size, @@ -1738,6 +1746,8 @@ deflate_flush_block(struct libdeflate_compressor *c, ASSERT(block_length >= MIN_BLOCK_LENGTH || (is_final_block && block_length > 0)); + is_final_block=is_final_block&&c->in_is_final_block; + ASSERT(block_length <= MAX_BLOCK_LENGTH); ASSERT(bitcount <= 7); ASSERT((bitbuf & ~(((bitbuf_t)1 << bitcount) - 1)) == 0); @@ -2392,23 +2402,26 @@ choose_max_block_end(const u8 *in_block_begin, const u8 *in_end, */ static size_t deflate_compress_none(const u8 *in, size_t in_nbytes, - u8 *out, size_t out_nbytes_avail) + u8 *out, size_t out_nbytes_avail, + bitbuf_t bitbuf,unsigned bitcount,u8 is_final_block) { const u8 *in_next = in; const u8 * const in_end = in + in_nbytes; u8 *out_next = out; u8 * const out_end = out + out_nbytes_avail; + ASSERT(bitcount<=7); + STATIC_ASSERT(DEFLATE_BLOCKTYPE_UNCOMPRESSED == 0); /* * If the input is zero-length, we still must output a block in order * for the output to be a valid DEFLATE stream. Handle this case * specially to avoid potentially passing NULL to memcpy() below. */ - if (unlikely(in_nbytes == 0)) { - if (out_nbytes_avail < 5) + if (unlikely((in_nbytes == 0)&&(bitcount<=5))) { + if (unlikely(out_nbytes_avail < 5)) return 0; /* BFINAL and BTYPE */ - *out_next++ = 1 | (DEFLATE_BLOCKTYPE_UNCOMPRESSED << 1); + *out_next++ = (u8)((is_final_block< 5)) + *out_next++ = 0; + bitbuf = 0; + bitcount = 0; + } /* Output LEN and NLEN, then the data itself. */ put_unaligned_le16(len, out_next); @@ -2451,17 +2480,26 @@ deflate_compress_none(const u8 *in, size_t in_nbytes, */ static void deflate_compress_fastest(struct libdeflate_compressor * restrict c, - const u8 *in, size_t in_nbytes, + const u8* in_block_with_dict,size_t dict_nbytes, size_t in_nbytes, struct deflate_output_bitstream *os) { - const u8 *in_next = in; + const u8 *in_next = in_block_with_dict+dict_nbytes; const u8 *in_end = in_next + in_nbytes; - const u8 *in_cur_base = in_next; + const u8 *in_cur_base = in_block_with_dict; unsigned max_len = DEFLATE_MAX_MATCH_LEN; unsigned nice_len = MIN(c->nice_match_length, max_len); u32 next_hash = 0; ht_matchfinder_init(&c->p.f.ht_mf); + if (dict_nbytes > 0){ + ASSERT(dict_nbytes<=MATCHFINDER_WINDOW_SIZE); + ht_matchfinder_skip_bytes(&c->p.f.ht_mf, + &in_cur_base, + in_cur_base, + in_end, + dict_nbytes, + &next_hash); + } do { /* Starting a new DEFLATE block */ @@ -2528,17 +2566,25 @@ deflate_compress_fastest(struct libdeflate_compressor * restrict c, */ static void deflate_compress_greedy(struct libdeflate_compressor * restrict c, - const u8 *in, size_t in_nbytes, + const u8* in_block_with_dict,size_t dict_nbytes, size_t in_nbytes, struct deflate_output_bitstream *os) { - const u8 *in_next = in; + const u8 *in_next = in_block_with_dict+dict_nbytes; const u8 *in_end = in_next + in_nbytes; - const u8 *in_cur_base = in_next; + const u8 *in_cur_base = in_block_with_dict; unsigned max_len = DEFLATE_MAX_MATCH_LEN; unsigned nice_len = MIN(c->nice_match_length, max_len); u32 next_hashes[2] = {0, 0}; hc_matchfinder_init(&c->p.g.hc_mf); + if (dict_nbytes > 0){ + hc_matchfinder_skip_bytes(&c->p.g.hc_mf, + &in_cur_base, + in_cur_base, + in_end, + dict_nbytes, + next_hashes); + } do { /* Starting a new DEFLATE block */ @@ -2604,17 +2650,25 @@ deflate_compress_greedy(struct libdeflate_compressor * restrict c, static forceinline void deflate_compress_lazy_generic(struct libdeflate_compressor * restrict c, - const u8 *in, size_t in_nbytes, + const u8 *in_block_with_dict,size_t dict_nbytes, size_t in_nbytes, struct deflate_output_bitstream *os, bool lazy2) { - const u8 *in_next = in; + const u8 *in_next = in_block_with_dict+dict_nbytes; const u8 *in_end = in_next + in_nbytes; - const u8 *in_cur_base = in_next; + const u8 *in_cur_base = in_block_with_dict; unsigned max_len = DEFLATE_MAX_MATCH_LEN; unsigned nice_len = MIN(c->nice_match_length, max_len); u32 next_hashes[2] = {0, 0}; hc_matchfinder_init(&c->p.g.hc_mf); + if (dict_nbytes > 0){ + hc_matchfinder_skip_bytes(&c->p.g.hc_mf, + &in_cur_base, + in_cur_base, + in_end, + dict_nbytes, + next_hashes); + } do { /* Starting a new DEFLATE block */ @@ -2815,10 +2869,10 @@ deflate_compress_lazy_generic(struct libdeflate_compressor * restrict c, */ static void deflate_compress_lazy(struct libdeflate_compressor * restrict c, - const u8 *in, size_t in_nbytes, + const u8 *in_block_with_dict,size_t dict_nbytes, size_t in_nbytes, struct deflate_output_bitstream *os) { - deflate_compress_lazy_generic(c, in, in_nbytes, os, false); + deflate_compress_lazy_generic(c, in_block_with_dict, dict_nbytes, in_nbytes, os, false); } /* @@ -2828,10 +2882,10 @@ deflate_compress_lazy(struct libdeflate_compressor * restrict c, */ static void deflate_compress_lazy2(struct libdeflate_compressor * restrict c, - const u8 *in, size_t in_nbytes, + const u8 *in_block_with_dict,size_t dict_nbytes, size_t in_nbytes, struct deflate_output_bitstream *os) { - deflate_compress_lazy_generic(c, in, in_nbytes, os, true); + deflate_compress_lazy_generic(c, in_block_with_dict, dict_nbytes, in_nbytes, os, true); } #if SUPPORT_NEAR_OPTIMAL_PARSING @@ -3577,6 +3631,13 @@ deflate_near_optimal_clear_old_stats(struct libdeflate_compressor *c) memset(c->p.n.match_len_freqs, 0, sizeof(c->p.n.match_len_freqs)); } + +#define _DEF_near_optimal_next_slide() { in_next_slide = in_cur_base + MATCHFINDER_WINDOW_SIZE; } +#define _DEF_near_optimal_slide() { \ + bt_matchfinder_slide_window(&c->p.n.bt_mf); \ + in_cur_base = in_next; \ + _DEF_near_optimal_next_slide(); } + /* * This is the "near-optimal" DEFLATE compressor. It computes the optimal * representation of each DEFLATE block using a minimum-cost path search over @@ -3592,15 +3653,15 @@ deflate_near_optimal_clear_old_stats(struct libdeflate_compressor *c) */ static void deflate_compress_near_optimal(struct libdeflate_compressor * restrict c, - const u8 *in, size_t in_nbytes, + const u8 *in_block_with_dict,size_t dict_nbytes, size_t in_nbytes, struct deflate_output_bitstream *os) { - const u8 *in_next = in; - const u8 *in_block_begin = in_next; - const u8 *in_end = in_next + in_nbytes; - const u8 *in_cur_base = in_next; - const u8 *in_next_slide = - in_next + MIN(in_end - in_next, MATCHFINDER_WINDOW_SIZE); + const u8* in=in_block_with_dict; + const u8 *in_next=in_block_with_dict; + const u8 *in_block_begin; + const u8 *in_end = in_block_with_dict + dict_nbytes + in_nbytes; + const u8 *in_cur_base=in_block_with_dict; + const u8 *in_next_slide; unsigned max_len = DEFLATE_MAX_MATCH_LEN; unsigned nice_len = MIN(c->nice_match_length, max_len); struct lz_match *cache_ptr = c->p.n.match_cache; @@ -3609,6 +3670,30 @@ deflate_compress_near_optimal(struct libdeflate_compressor * restrict c, bt_matchfinder_init(&c->p.n.bt_mf); deflate_near_optimal_init_stats(c); + _DEF_near_optimal_next_slide(); + + if (dict_nbytes>0){ + ASSERT(dict_nbytes<=MATCHFINDER_WINDOW_SIZE); + const u8 * const in_max_block_end = in_next + dict_nbytes; + for (;;) { + if (in_next == in_next_slide) + _DEF_near_optimal_slide(); + + adjust_max_and_nice_len(&max_len, &nice_len, in_end - in_next); + bt_matchfinder_skip_byte( + &c->p.n.bt_mf, + in_cur_base, + in_next - in_cur_base, + nice_len, + c->max_search_depth, + next_hashes); + in_next++; + + if (in_next >= in_max_block_end) + break; + } + } + in_block_begin = in_next; do { /* Starting a new DEFLATE block */ @@ -3652,12 +3737,8 @@ deflate_compress_near_optimal(struct libdeflate_compressor * restrict c, size_t remaining = in_end - in_next; /* Slide the window forward if needed. */ - if (in_next == in_next_slide) { - bt_matchfinder_slide_window(&c->p.n.bt_mf); - in_cur_base = in_next; - in_next_slide = in_next + - MIN(remaining, MATCHFINDER_WINDOW_SIZE); - } + if (in_next == in_next_slide) + _DEF_near_optimal_slide(); /* * Find matches with the current position using the @@ -3726,14 +3807,8 @@ deflate_compress_near_optimal(struct libdeflate_compressor * restrict c, --best_len; do { remaining = in_end - in_next; - if (in_next == in_next_slide) { - bt_matchfinder_slide_window( - &c->p.n.bt_mf); - in_cur_base = in_next; - in_next_slide = in_next + - MIN(remaining, - MATCHFINDER_WINDOW_SIZE); - } + if (in_next == in_next_slide) + _DEF_near_optimal_slide(); adjust_max_and_nice_len(&max_len, &nice_len, remaining); @@ -3911,6 +3986,8 @@ libdeflate_alloc_compressor_ex(int compression_level, options->free_func : libdeflate_default_free_func; c->compression_level = compression_level; + c->in_is_final_block = false; + _compress_block_reset(c); /* * The higher the compression level, the more we should bother trying to @@ -4026,25 +4103,87 @@ libdeflate_deflate_compress(struct libdeflate_compressor *c, const void *in, size_t in_nbytes, void *out, size_t out_nbytes_avail) { + _compress_block_reset(c); + return libdeflate_deflate_compress_block(c,in,0,in_nbytes,true,out,out_nbytes_avail,false); +} + + +static size_t _deflate_flush_to_byte_align(u8* out,size_t out_nbytes_avail,bitbuf_t bitbuf,unsigned bitcount){ + ASSERT(out<(out+out_nbytes_avail)); + ASSERT((bitcount>=1)&&(bitcount<=7)); + STATIC_ASSERT(WORDBITS >= 32); + switch (bitcount) { + case 1: case 3: case 5: case 7:{ + return deflate_compress_none(0,0,out,out_nbytes_avail,bitbuf,bitcount,0); + } break; + case 2:{ // 2,4,6 add static empty blocks + if (unlikely(out_nbytes_avail<4)) return 0; + bitbuf |= ((2<<20)|(2<<10)|2) <<2; + put_unaligned_le32((u32)bitbuf,out); + return 4; + } break; + case 4:{ + if (unlikely(out_nbytes_avail<3)) return 0; + bitbuf |= ((2<<10)|2) <<4; + put_unaligned_le16((u16)bitbuf,out); + out[2]=(u8)(bitbuf>>16); + return 3; + } break; + case 6:{ + if (unlikely(out_nbytes_avail<2)) return 0; + bitbuf |= 2 <<6; + put_unaligned_le16((u16)bitbuf,out); + return 2; + } break; + default: + return 0; + } +} + +LIBDEFLATEAPI size_t +libdeflate_deflate_compress_block_uncompressed(struct libdeflate_compressor *c, + const void *in_block,size_t in_nbytes,int in_is_final_block, + void *out, size_t out_nbytes_avail){ struct deflate_output_bitstream os; + c->in_is_final_block=in_is_final_block?1:0; + os.bitbuf = c->bitbuf_back_for_block; + os.bitcount = c->bitcount_back_for_block; + _compress_block_reset(c); + return deflate_compress_none(in_block,in_nbytes,out,out_nbytes_avail,os.bitbuf,os.bitcount,c->in_is_final_block); +} + +LIBDEFLATEAPI size_t +libdeflate_deflate_compress_block(struct libdeflate_compressor *c, + const void *in_block_with_dict,size_t dict_nbytes,size_t in_nbytes,int in_is_final_block, + void *out, size_t out_nbytes_avail,int out_is_flush_to_byte_align){ + struct deflate_output_bitstream os; + c->in_is_final_block=in_is_final_block?1:0; + os.bitbuf = c->bitbuf_back_for_block; + os.bitcount = c->bitcount_back_for_block; + c->bitbuf_back_for_block=0; + c->bitcount_back_for_block=0; /* * For extremely short inputs, or for compression level 0, just output * uncompressed blocks. */ - if (unlikely(in_nbytes <= c->max_passthrough_size)) - return deflate_compress_none(in, in_nbytes, - out, out_nbytes_avail); + if (unlikely(in_nbytes <= c->max_passthrough_size)){ + const u8* in=((const u8*)in_block_with_dict)+dict_nbytes; + return deflate_compress_none(in,in_nbytes,out,out_nbytes_avail,os.bitbuf,os.bitcount,c->in_is_final_block); + } + if (dict_nbytes>MATCHFINDER_WINDOW_SIZE){ + in_block_with_dict=(const u8*)in_block_with_dict+dict_nbytes-MATCHFINDER_WINDOW_SIZE; + dict_nbytes=MATCHFINDER_WINDOW_SIZE; + } + /* Initialize the output bitstream structure. */ - os.bitbuf = 0; - os.bitcount = 0; os.next = out; os.end = os.next + out_nbytes_avail; os.overflow = false; /* Call the actual compression function. */ - (*c->impl)(c, in, in_nbytes, &os); + (*c->impl)(c,in_block_with_dict,dict_nbytes,in_nbytes,&os); /* Return 0 if the output buffer is too small. */ if (os.overflow) @@ -4057,8 +4196,17 @@ libdeflate_deflate_compress(struct libdeflate_compressor *c, */ ASSERT(os.bitcount <= 7); if (os.bitcount) { - ASSERT(os.next < os.end); - *os.next++ = os.bitbuf; + if (in_is_final_block){ + ASSERT(os.next < os.end); + *os.next++ = os.bitbuf; + }else if (out_is_flush_to_byte_align){ + size_t fbytes=_deflate_flush_to_byte_align(os.next,os.end-os.next,os.bitbuf,os.bitcount); + if (unlikely(fbytes==0)) return 0; + os.next+=fbytes; + }else{ //backup for next block + c->bitbuf_back_for_block=os.bitbuf; + c->bitcount_back_for_block=os.bitcount; + } } /* Return the compressed size in bytes. */ @@ -4127,3 +4275,24 @@ libdeflate_deflate_compress_bound(struct libdeflate_compressor *c, */ return (5 * max_blocks) + in_nbytes; } + + +static forceinline size_t +_compress_bound_block(size_t _one_block){ + return libdeflate_deflate_compress_bound(NULL,_one_block)+5; +} + +LIBDEFLATEAPI size_t +libdeflate_deflate_compress_bound_block(size_t in_block_nbytes){ + return _compress_bound_block(in_block_nbytes); +} + +LIBDEFLATEAPI uint64_t +libdeflate_deflate_compress_bound_blocks(uint64_t in_stream_nbytes,size_t in_block_nbytes){ + uint64_t min_blocks; + size_t last_block_nbytes; + ASSERT(in_block_nbytes>0); + min_blocks=in_stream_nbytes/in_block_nbytes; + last_block_nbytes=in_stream_nbytes-in_block_nbytes*min_blocks; + return _compress_bound_block(in_block_nbytes)*min_blocks+_compress_bound_block(last_block_nbytes); +} \ No newline at end of file diff --git a/lib/deflate_decompress.c b/lib/deflate_decompress.c index 63726c7a..9be9100c 100644 --- a/lib/deflate_decompress.c +++ b/lib/deflate_decompress.c @@ -57,7 +57,7 @@ # pragma message("UNSAFE DECOMPRESSION IS ENABLED. THIS MUST ONLY BE USED IF THE DECOMPRESSOR INPUT WILL ALWAYS BE TRUSTED!") # define SAFETY_CHECK(expr) (void)(expr) #else -# define SAFETY_CHECK(expr) if (unlikely(!(expr))) return LIBDEFLATE_BAD_DATA +# define SAFETY_CHECK(expr) if (unlikely(!(expr))) goto _on_bad_data; #endif /***************************************************************************** @@ -669,6 +669,8 @@ struct libdeflate_decompressor { /* used only during build_decode_table() */ u16 sorted_syms[DEFLATE_MAX_NUM_SYMS]; + u32 bitsleft_back; + bitbuf_t bitbuf_back; bool static_codes_loaded; unsigned litlen_tablebits; @@ -676,6 +678,11 @@ struct libdeflate_decompressor { free_func_t free_func; }; +static forceinline void _decompress_block_init(struct libdeflate_decompressor* d){ + d->bitbuf_back=0; + d->bitsleft_back=0; +} + /* * Build a table for fast decoding of symbols from a Huffman code. As input, * this function takes the codeword length of each symbol which may be used in @@ -1072,8 +1079,9 @@ build_offset_decode_table(struct libdeflate_decompressor *d, typedef enum libdeflate_result (*decompress_func_t) (struct libdeflate_decompressor * restrict d, const void * restrict in, size_t in_nbytes, - void * restrict out, size_t out_nbytes_avail, - size_t *actual_in_nbytes_ret, size_t *actual_out_nbytes_ret); + void * restrict out, size_t in_dict_nbytes, size_t out_nbytes_avail, + size_t *actual_in_nbytes_ret,size_t *actual_out_nbytes_ret, + enum libdeflate_decompress_stop_by stop_type,int* is_final_block_ret); #define FUNCNAME deflate_decompress_default #undef ATTRIBUTES @@ -1096,8 +1104,9 @@ typedef enum libdeflate_result (*decompress_func_t) static enum libdeflate_result dispatch_decomp(struct libdeflate_decompressor *d, const void *in, size_t in_nbytes, - void *out, size_t out_nbytes_avail, - size_t *actual_in_nbytes_ret, size_t *actual_out_nbytes_ret); + void *out, size_t in_dict_nbytes, size_t out_nbytes_avail, + size_t *actual_in_nbytes_ret,size_t *actual_out_nbytes_ret, + enum libdeflate_decompress_stop_by stop_type,int* is_final_block_ret); static volatile decompress_func_t decompress_impl = dispatch_decomp; @@ -1105,8 +1114,9 @@ static volatile decompress_func_t decompress_impl = dispatch_decomp; static enum libdeflate_result dispatch_decomp(struct libdeflate_decompressor *d, const void *in, size_t in_nbytes, - void *out, size_t out_nbytes_avail, - size_t *actual_in_nbytes_ret, size_t *actual_out_nbytes_ret) + void *out, size_t in_dict_nbytes, size_t out_nbytes_avail, + size_t *actual_in_nbytes_ret,size_t *actual_out_nbytes_ret, + enum libdeflate_decompress_stop_by stop_type,int* is_final_block_ret) { decompress_func_t f = arch_select_decompress_func(); @@ -1114,14 +1124,42 @@ dispatch_decomp(struct libdeflate_decompressor *d, f = DEFAULT_IMPL; decompress_impl = f; - return f(d, in, in_nbytes, out, out_nbytes_avail, - actual_in_nbytes_ret, actual_out_nbytes_ret); + return f(d, in, in_nbytes, out,in_dict_nbytes, out_nbytes_avail, + actual_in_nbytes_ret, actual_out_nbytes_ret, stop_type, is_final_block_ret); } #else /* The best implementation is statically known, so call it directly. */ # define decompress_impl DEFAULT_IMPL #endif + +LIBDEFLATEAPI enum libdeflate_result +libdeflate_deflate_decompress_block(struct libdeflate_decompressor *d, + const void *in_part, size_t in_part_nbytes_bound, + void *out_block_with_in_dict,size_t in_dict_nbytes, size_t out_block_nbytes, + size_t *actual_in_nbytes_ret,size_t *actual_out_nbytes_ret, + enum libdeflate_decompress_stop_by stop_type,int* is_final_block_ret){ + return decompress_impl(d, in_part, in_part_nbytes_bound, + out_block_with_in_dict, in_dict_nbytes, out_block_nbytes, + actual_in_nbytes_ret, actual_out_nbytes_ret, stop_type, is_final_block_ret); +} + +LIBDEFLATEAPI uint16_t libdeflate_deflate_decompress_get_state(struct libdeflate_decompressor *d){ + ASSERT(d->bitsleft_back<=7); + ASSERT(d->bitbuf_back==(uint16_t)(d->bitbuf_back<<3>>3)); + return (d->bitbuf_back<<3) | d->bitsleft_back; +} + +LIBDEFLATEAPI void libdeflate_deflate_decompress_set_state(struct libdeflate_decompressor *d,uint16_t state){ + d->bitsleft_back=state&7; + d->bitbuf_back=state>>3; +} + +LIBDEFLATEAPI void +libdeflate_deflate_decompress_block_reset(struct libdeflate_decompressor *d){ + _decompress_block_init(d); +} + /* * This is the main DEFLATE decompression routine. See libdeflate.h for the * documentation. @@ -1137,8 +1175,10 @@ libdeflate_deflate_decompress_ex(struct libdeflate_decompressor *d, size_t *actual_in_nbytes_ret, size_t *actual_out_nbytes_ret) { - return decompress_impl(d, in, in_nbytes, out, out_nbytes_avail, - actual_in_nbytes_ret, actual_out_nbytes_ret); + _decompress_block_init(d); + return decompress_impl(d,in,in_nbytes,out,0,out_nbytes_avail, + actual_in_nbytes_ret,actual_out_nbytes_ret, + LIBDEFLATE_STOP_BY_FINAL_BLOCK,NULL); } LIBDEFLATEAPI enum libdeflate_result @@ -1147,9 +1187,10 @@ libdeflate_deflate_decompress(struct libdeflate_decompressor *d, void *out, size_t out_nbytes_avail, size_t *actual_out_nbytes_ret) { - return libdeflate_deflate_decompress_ex(d, in, in_nbytes, - out, out_nbytes_avail, - NULL, actual_out_nbytes_ret); + _decompress_block_init(d); + return decompress_impl(d,in,in_nbytes,out,0,out_nbytes_avail, + NULL,actual_out_nbytes_ret, + LIBDEFLATE_STOP_BY_FINAL_BLOCK,NULL); } LIBDEFLATEAPI struct libdeflate_decompressor * diff --git a/lib/gzip_compress.c b/lib/gzip_compress.c index b7d5076e..df2799f1 100644 --- a/lib/gzip_compress.c +++ b/lib/gzip_compress.c @@ -27,16 +27,13 @@ #include "deflate_compress.h" #include "gzip_constants.h" +#include "gzip_overhead.h" -LIBDEFLATEAPI size_t -libdeflate_gzip_compress(struct libdeflate_compressor *c, - const void *in, size_t in_nbytes, +size_t libdeflate_gzip_compress_head(unsigned compression_level,size_t in_nbytes, void *out, size_t out_nbytes_avail) { u8 *out_next = out; - unsigned compression_level; u8 xfl; - size_t deflate_size; if (out_nbytes_avail <= GZIP_MIN_OVERHEAD) return 0; @@ -54,7 +51,6 @@ libdeflate_gzip_compress(struct libdeflate_compressor *c, out_next += 4; /* XFL */ xfl = 0; - compression_level = libdeflate_get_compression_level(c); if (compression_level < 2) xfl |= GZIP_XFL_FASTEST_COMPRESSION; else if (compression_level >= 8) @@ -63,15 +59,41 @@ libdeflate_gzip_compress(struct libdeflate_compressor *c, /* OS */ *out_next++ = GZIP_OS_UNKNOWN; /* OS */ - /* Compressed data */ - deflate_size = libdeflate_deflate_compress(c, in, in_nbytes, out_next, - out_nbytes_avail - GZIP_MIN_OVERHEAD); - if (deflate_size == 0) + return out_next - (u8 *)out; +} + +#define _do_compress_step(_call_compress) { \ + deflate_size=_call_compress; \ + if (deflate_size == 0) \ + return 0; \ + out_next += deflate_size; \ + out_nbytes_avail -= deflate_size; \ +} + +size_t libdeflate_gzip_compress(struct libdeflate_compressor *c, + const void *in, size_t in_nbytes, + void *out, size_t out_nbytes_avail) +{ + u8 *out_next = out; + size_t deflate_size; + + _do_compress_step(libdeflate_gzip_compress_head(libdeflate_get_compression_level(c), + in_nbytes,out_next,out_nbytes_avail)); + _do_compress_step(libdeflate_deflate_compress(c, in, in_nbytes, out_next, + out_nbytes_avail)); + _do_compress_step(libdeflate_gzip_compress_foot(libdeflate_crc32(0, in, in_nbytes), + in_nbytes,out_next,out_nbytes_avail)); + return out_next - (u8 *)out; +} + +size_t libdeflate_gzip_compress_foot(uint32_t in_crc, size_t in_nbytes, void *out, size_t out_nbytes_avail) +{ + u8 *out_next = out; + if (out_nbytes_avail <= GZIP_FOOTER_SIZE) return 0; - out_next += deflate_size; /* CRC32 */ - put_unaligned_le32(libdeflate_crc32(0, in, in_nbytes), out_next); + put_unaligned_le32(in_crc, out_next); out_next += 4; /* ISIZE */ diff --git a/lib/gzip_decompress.c b/lib/gzip_decompress.c index 76b74f69..9c63382f 100644 --- a/lib/gzip_decompress.c +++ b/lib/gzip_decompress.c @@ -25,22 +25,15 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include "lib_common.h" +#include "gzip_overhead.h" #include "gzip_constants.h" -LIBDEFLATEAPI enum libdeflate_result -libdeflate_gzip_decompress_ex(struct libdeflate_decompressor *d, - const void *in, size_t in_nbytes, - void *out, size_t out_nbytes_avail, - size_t *actual_in_nbytes_ret, - size_t *actual_out_nbytes_ret) +int libdeflate_gzip_decompress_head(const void *in, size_t in_nbytes, + size_t *actual_in_nbytes_ret) { const u8 *in_next = in; const u8 * const in_end = in_next + in_nbytes; u8 flg; - size_t actual_in_nbytes; - size_t actual_out_nbytes; - enum libdeflate_result result; if (in_nbytes < GZIP_MIN_OVERHEAD) return LIBDEFLATE_BAD_DATA; @@ -99,31 +92,78 @@ libdeflate_gzip_decompress_ex(struct libdeflate_decompressor *d, return LIBDEFLATE_BAD_DATA; } + if (actual_in_nbytes_ret) + *actual_in_nbytes_ret = in_next - (u8 *)in; + + return LIBDEFLATE_SUCCESS; +} + + +#define _do_decompress_step(_call_decompress) { \ + result=_call_decompress; \ + if (result != LIBDEFLATE_SUCCESS) \ + return result; \ + in_next += actual_in_nbytes; \ +} + +LIBDEFLATEAPI enum libdeflate_result +libdeflate_gzip_decompress_ex(struct libdeflate_decompressor *d, + const void *in, size_t in_nbytes, + void *out, size_t out_nbytes_avail, + size_t *actual_in_nbytes_ret, + size_t *actual_out_nbytes_ret) +{ + const u8 *in_next = in; + const u8 * const in_end = in_next + in_nbytes; + size_t actual_in_nbytes; + size_t actual_out_nbytes; + enum libdeflate_result result; + u32 saved_crc; + u32 saved_uncompress_nbytes; + + _do_decompress_step(libdeflate_gzip_decompress_head(in_next, + in_end - in_next, &actual_in_nbytes)); + /* Compressed data */ - result = libdeflate_deflate_decompress_ex(d, in_next, + _do_decompress_step(libdeflate_deflate_decompress_ex(d, in_next, in_end - GZIP_FOOTER_SIZE - in_next, out, out_nbytes_avail, &actual_in_nbytes, - actual_out_nbytes_ret); - if (result != LIBDEFLATE_SUCCESS) - return result; - + &actual_out_nbytes)); if (actual_out_nbytes_ret) - actual_out_nbytes = *actual_out_nbytes_ret; - else - actual_out_nbytes = out_nbytes_avail; - - in_next += actual_in_nbytes; + *actual_out_nbytes_ret=actual_out_nbytes; + _do_decompress_step(libdeflate_gzip_decompress_foot(in_next, in_end - in_next, + &saved_crc, &saved_uncompress_nbytes, &actual_in_nbytes)); + /* CRC32 */ - if (libdeflate_crc32(0, out, actual_out_nbytes) != - get_unaligned_le32(in_next)) + if (libdeflate_crc32(0, out, actual_out_nbytes) != saved_crc) return LIBDEFLATE_BAD_DATA; - in_next += 4; /* ISIZE */ - if ((u32)actual_out_nbytes != get_unaligned_le32(in_next)) + if ((u32)actual_out_nbytes != saved_uncompress_nbytes) + return LIBDEFLATE_BAD_DATA; + + if (actual_in_nbytes_ret) + *actual_in_nbytes_ret = in_next - (u8 *)in; + + return LIBDEFLATE_SUCCESS; +} + +int libdeflate_gzip_decompress_foot(const void *in, size_t in_nbytes, + u32* saved_crc,u32* saved_uncompress_nbytes, + size_t *actual_in_nbytes_ret) +{ + const u8 *in_next = in; + if (in_nbytes < GZIP_FOOTER_SIZE) return LIBDEFLATE_BAD_DATA; + + /* CRC32 */ + *saved_crc=get_unaligned_le32(in_next); + in_next += 4; + + /* ISIZE */ + *saved_uncompress_nbytes=get_unaligned_le32(in_next); in_next += 4; if (actual_in_nbytes_ret) diff --git a/lib/gzip_overhead.h b/lib/gzip_overhead.h new file mode 100644 index 00000000..0d67ed83 --- /dev/null +++ b/lib/gzip_overhead.h @@ -0,0 +1,20 @@ +#ifndef LIB_GZIP_OVERHEAD_H +#define LIB_GZIP_OVERHEAD_H + +#include "lib_common.h" +#ifdef __cplusplus +extern "C" { +#endif +size_t libdeflate_gzip_compress_head(unsigned compression_level,size_t in_nbytes, + void *out, size_t out_nbytes_avail); +size_t libdeflate_gzip_compress_foot(uint32_t in_crc, size_t in_nbytes, + void *out, size_t out_nbytes_avail); +int libdeflate_gzip_decompress_head(const void *in, size_t in_nbytes, + size_t *actual_in_nbytes_ret); +int libdeflate_gzip_decompress_foot(const void *in, size_t in_nbytes, + u32* saved_crc,u32* saved_uncompress_nbytes, + size_t *actual_in_nbytes_ret); +#ifdef __cplusplus +} +#endif +#endif /* LIB_GZIP_OVERHEAD_H */ diff --git a/lib/ht_matchfinder.h b/lib/ht_matchfinder.h index 6e5a187c..1d01df1a 100644 --- a/lib/ht_matchfinder.h +++ b/lib/ht_matchfinder.h @@ -205,7 +205,6 @@ ht_matchfinder_skip_bytes(struct ht_matchfinder * const mf, s32 cur_pos = in_next - *in_base_p; u32 hash; u32 remaining = count; - int i; if (unlikely(count + HT_MATCHFINDER_REQUIRED_NBYTES > in_end - in_next)) return; @@ -218,8 +217,15 @@ ht_matchfinder_skip_bytes(struct ht_matchfinder * const mf, hash = *next_hash; do { +#if HT_MATCHFINDER_BUCKET_SIZE == 1 +#elif HT_MATCHFINDER_BUCKET_SIZE == 2 + mf->hash_tab[hash][1] = mf->hash_tab[hash][0]; +#else + /* Generic version for HT_MATCHFINDER_BUCKET_SIZE > 2 */ + int i; for (i = HT_MATCHFINDER_BUCKET_SIZE - 1; i > 0; i--) mf->hash_tab[hash][i] = mf->hash_tab[hash][i - 1]; +#endif mf->hash_tab[hash][0] = cur_pos; hash = lz_hash(get_unaligned_le32(++in_next), diff --git a/libdeflate.h b/libdeflate.h index b0bf0243..73dc11ba 100644 --- a/libdeflate.h +++ b/libdeflate.h @@ -114,6 +114,58 @@ LIBDEFLATEAPI size_t libdeflate_deflate_compress_bound(struct libdeflate_compressor *compressor, size_t in_nbytes); +/* + * Large stream data can be compress by calling libdeflate_deflate_compress_block() + * multiple times. Each time call this function, 'in_block_with_dict' have + * 'dict_nbytes' repeat of the last called's tail data as dictionary data, and + * 'in_block_nbytes' new data will be compressed; The dictionary data size + * dict_nbytes<=32k, if it is greater than 32k, the extra part of the previous + * part of the dictionary data is invalid. + * libdeflate_deflate_compress_bound_block() can got the upper limit + * of 'out_part' required space 'out_part_nbytes_avail'. + * If (in_is_final_block!=0) means input data is the end block of the stream data, + * the process of multiple calling will finished. + * if (out_is_flush_to_byte_align!=0) means requires output DEFLATE bit stream must + * flushed to a byte-aligned position; Note: this request will increase output + * length slightly, will output some empty block bits data; The feature can be + * used in multi-thread compression, the compressed part encoding for each thread + * can be legally spliced into the final compressed encoding stream. + * + * If compression is successful, return the new compressed part data's byte length; + * else if fail, return 0. + */ +LIBDEFLATEAPI size_t +libdeflate_deflate_compress_block(struct libdeflate_compressor *compressor, + const void *in_block_with_dict,size_t dict_nbytes,size_t in_block_nbytes,int in_is_final_block, + void *out_part, size_t out_part_nbytes_avail,int out_is_flush_to_byte_align); + +/* + * Similar to libdeflate_deflate_compress_block(), but the data is encoded to + * output stream with uncompressed manner. + */ +LIBDEFLATEAPI size_t +libdeflate_deflate_compress_block_uncompressed(struct libdeflate_compressor *compressor, + const void *in_block,size_t in_block_nbytes,int in_is_final_block, + void *out_part, size_t out_part_nbytes_avail); + +/* + * Large stream data can be compress by calling libdeflate_deflate_compress_block() + * multiple times. + * libdeflate_deflate_compress_bound_block(in_block_nbytes) can got the upper limit of + * out_part required space 'out_part_nbytes_avail'. + */ +LIBDEFLATEAPI size_t +libdeflate_deflate_compress_bound_block(size_t in_block_nbytes); + +/* + * Large stream data can be compress by calling libdeflate_deflate_compress_block() + * multiple times. + * libdeflate_deflate_compress_bound_blocks(in_stream_nbytes) can got the upper limit of + * compressed data sum byte length. + */ +LIBDEFLATEAPI uint64_t +libdeflate_deflate_compress_bound_blocks(uint64_t in_stream_nbytes,size_t in_block_nbytes); + /* * Like libdeflate_deflate_compress(), but uses the zlib wrapper format instead * of raw DEFLATE. @@ -257,6 +309,57 @@ libdeflate_deflate_decompress_ex(struct libdeflate_decompressor *decompressor, size_t *actual_in_nbytes_ret, size_t *actual_out_nbytes_ret); +/* ctrl libdeflate_deflate_decompress_block() stop condition */ +enum libdeflate_decompress_stop_by { + LIBDEFLATE_STOP_BY_FINAL_BLOCK = 0, + LIBDEFLATE_STOP_BY_ANY_BLOCK = 1, + LIBDEFLATE_STOP_BY_ANY_BLOCK_AND_FULL_INPUT = 2, + LIBDEFLATE_STOP_BY_ANY_BLOCK_AND_FULL_OUTPUT = 3, + LIBDEFLATE_STOP_BY_ANY_BLOCK_AND_FULL_OUTPUT_AND_IN_BYTE_ALIGN = 4, +}; + +/* + * Large stream data can be decompress by calling libdeflate_deflate_decompress_block() + * multiple times. Each time call this function, 'out_block_with_in_dict' have + * 'in_dict_nbytes' repeat of the last called's tail outputed uncompressed data as + * dictionary data, and 'out_block_nbytes' new uncompressed data want be decompressed; + * The dictionary data size in_dict_nbytes<=32k, if it is greater than 32k, the extra + * part of the previous part of the dictionary data is invalid. + * libdeflate_deflate_compress_bound_block(out_block_nbytes) can got the upper limit + * of 'in_part' required space 'in_part_nbytes_bound'. + * 'is_final_block_ret' can NULL. + * + * WARNING: This function must decompressed one full DEFLATE block before stop; + * so 'in_part_nbytes_bound' must possess a block end flag, and "out_block_nbytes" + * must be able to store uncompressed data of this block decompressed; + * This feature is not compatible with the DEFLATE stream decoding standard, + * this function can't support a single DEFLATE block that may have any length. + */ +LIBDEFLATEAPI enum libdeflate_result +libdeflate_deflate_decompress_block(struct libdeflate_decompressor *decompressor, + const void *in_part, size_t in_part_nbytes_bound, + void *out_block_with_in_dict,size_t in_dict_nbytes, size_t out_block_nbytes, + size_t *actual_in_nbytes_ret,size_t *actual_out_nbytes_ret, + enum libdeflate_decompress_stop_by stop_type,int* is_final_block_ret); + +/* + * You can get (save) & set (restore) the decompressor's state; + * If the data inputed is not enough or ouput space full, you can restore the state of + * the decompressor and provide more data or space to continue attempt decompress. + */ +LIBDEFLATEAPI uint16_t libdeflate_deflate_decompress_get_state(struct libdeflate_decompressor *decompressor); +LIBDEFLATEAPI void libdeflate_deflate_decompress_set_state(struct libdeflate_decompressor *decompressor,uint16_t state); + +/* + * Clear the state saved between calls libdeflate_deflate_decompress_block(); + * if you know the next block does not depend on the inputed data of the previous + * block, you can call this function reset 'decompressor'; + * Note: if next block depend on the inputed data of the previous block, reset will + * cause libdeflate_deflate_decompress_block() to fail. + */ +LIBDEFLATEAPI void +libdeflate_deflate_decompress_block_reset(struct libdeflate_decompressor *decompressor); + /* * Like libdeflate_deflate_decompress(), but assumes the zlib wrapper format * instead of raw DEFLATE. diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index e707a25f..7a92b6c1 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -42,7 +42,7 @@ endif() # Build and install libdeflate-gzip and its alias libdeflate-gunzip. if(LIBDEFLATE_BUILD_GZIP) - add_executable(libdeflate-gzip gzip.c) + add_executable(libdeflate-gzip gzip.c gzip_compress_by_stream.c gzip_decompress_by_stream.c) target_link_libraries(libdeflate-gzip PRIVATE libdeflate_prog_utils) install(TARGETS libdeflate-gzip DESTINATION ${CMAKE_INSTALL_BINDIR}) if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14") @@ -61,7 +61,7 @@ if(LIBDEFLATE_BUILD_GZIP) else() # The cmake version is too old to support file(CREATE_LINK). # Just compile gzip.c again to build libdeflate-gunzip. - add_executable(libdeflate-gunzip gzip.c) + add_executable(libdeflate-gunzip gzip.c gzip_compress_by_stream.c gzip_decompress_by_stream.c) target_link_libraries(libdeflate-gunzip PRIVATE libdeflate_prog_utils) install(TARGETS libdeflate-gunzip DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() diff --git a/programs/gzip.c b/programs/gzip.c index 2c753778..444a7e17 100644 --- a/programs/gzip.c +++ b/programs/gzip.c @@ -26,6 +26,8 @@ */ #include "prog_util.h" +#include "gzip_compress_by_stream.h" +#include "gzip_decompress_by_stream.h" #include #include @@ -37,12 +39,6 @@ # include #endif -#define GZIP_MIN_HEADER_SIZE 10 -#define GZIP_FOOTER_SIZE 8 -#define GZIP_MIN_OVERHEAD (GZIP_MIN_HEADER_SIZE + GZIP_FOOTER_SIZE) -#define GZIP_ID1 0x1F -#define GZIP_ID2 0x8B - struct options { bool to_stdout; bool decompress; @@ -146,162 +142,6 @@ append_suffix(const tchar *path, const tchar *suffix) return suffixed_path; } -static int -do_compress(struct libdeflate_compressor *compressor, - struct file_stream *in, struct file_stream *out) -{ - const void *uncompressed_data = in->mmap_mem; - size_t uncompressed_size = in->mmap_size; - void *compressed_data; - size_t actual_compressed_size; - size_t max_compressed_size; - int ret; - - max_compressed_size = libdeflate_gzip_compress_bound(compressor, - uncompressed_size); - compressed_data = xmalloc(max_compressed_size); - if (compressed_data == NULL) { - msg("%"TS": file is probably too large to be processed by this " - "program", in->name); - ret = -1; - goto out; - } - - actual_compressed_size = libdeflate_gzip_compress(compressor, - uncompressed_data, - uncompressed_size, - compressed_data, - max_compressed_size); - if (actual_compressed_size == 0) { - msg("Bug in libdeflate_gzip_compress_bound()!"); - ret = -1; - goto out; - } - - ret = full_write(out, compressed_data, actual_compressed_size); -out: - free(compressed_data); - return ret; -} - -static int -do_decompress(struct libdeflate_decompressor *decompressor, - struct file_stream *in, struct file_stream *out, - const struct options *options) -{ - const u8 *compressed_data = in->mmap_mem; - size_t compressed_size = in->mmap_size; - void *uncompressed_data = NULL; - size_t uncompressed_size; - size_t max_uncompressed_size; - size_t actual_in_nbytes; - size_t actual_out_nbytes; - enum libdeflate_result result; - int ret = 0; - - if (compressed_size < GZIP_MIN_OVERHEAD || - compressed_data[0] != GZIP_ID1 || - compressed_data[1] != GZIP_ID2) { - if (options->force && options->to_stdout) - return full_write(out, compressed_data, compressed_size); - msg("%"TS": not in gzip format", in->name); - return -1; - } - - /* - * Use the ISIZE field as a hint for the decompressed data size. It may - * need to be increased later, however, because the file may contain - * multiple gzip members and the particular ISIZE we happen to use may - * not be the largest; or the real size may be >= 4 GiB, causing ISIZE - * to overflow. In any case, make sure to allocate at least one byte. - */ - uncompressed_size = - get_unaligned_le32(&compressed_data[compressed_size - 4]); - if (uncompressed_size == 0) - uncompressed_size = 1; - - /* - * DEFLATE cannot expand data more than 1032x, so there's no need to - * ever allocate a buffer more than 1032 times larger than the - * compressed data. This is a fail-safe, albeit not a very good one, if - * ISIZE becomes corrupted on a small file. (The 1032x number comes - * from each 2 bits generating a 258-byte match. This is a hard upper - * bound; the real upper bound is slightly smaller due to overhead.) - */ - if (compressed_size <= SIZE_MAX / 1032) - max_uncompressed_size = compressed_size * 1032; - else - max_uncompressed_size = SIZE_MAX; - - do { - if (uncompressed_data == NULL) { - uncompressed_size = MIN(uncompressed_size, - max_uncompressed_size); - uncompressed_data = xmalloc(uncompressed_size); - if (uncompressed_data == NULL) { - msg("%"TS": file is probably too large to be " - "processed by this program", in->name); - ret = -1; - goto out; - } - } - - result = libdeflate_gzip_decompress_ex(decompressor, - compressed_data, - compressed_size, - uncompressed_data, - uncompressed_size, - &actual_in_nbytes, - &actual_out_nbytes); - - if (result == LIBDEFLATE_INSUFFICIENT_SPACE) { - if (uncompressed_size >= max_uncompressed_size) { - msg("Bug in libdeflate_gzip_decompress_ex(): data expanded too much!"); - ret = -1; - goto out; - } - if (uncompressed_size * 2 <= uncompressed_size) { - msg("%"TS": file corrupt or too large to be " - "processed by this program", in->name); - ret = -1; - goto out; - } - uncompressed_size *= 2; - free(uncompressed_data); - uncompressed_data = NULL; - continue; - } - - if (result != LIBDEFLATE_SUCCESS) { - msg("%"TS": file corrupt or not in gzip format", - in->name); - ret = -1; - goto out; - } - - if (actual_in_nbytes == 0 || - actual_in_nbytes > compressed_size || - actual_out_nbytes > uncompressed_size) { - msg("Bug in libdeflate_gzip_decompress_ex(): impossible actual_nbytes value!"); - ret = -1; - goto out; - } - - if (!options->test) { - ret = full_write(out, uncompressed_data, actual_out_nbytes); - if (ret != 0) - goto out; - } - - compressed_data += actual_in_nbytes; - compressed_size -= actual_in_nbytes; - - } while (compressed_size != 0); -out: - free(uncompressed_data); - return ret; -} - static int stat_file(struct file_stream *in, stat_t *stbuf, bool allow_hard_links) { @@ -448,14 +288,12 @@ decompress_file(struct libdeflate_decompressor *decompressor, const tchar *path, if (ret != 0) goto out_close_in; - /* TODO: need a streaming-friendly solution */ - ret = map_file_contents(&in, stbuf.st_size); - if (ret != 0) - goto out_close_out; - - ret = do_decompress(decompressor, &in, &out, options); - if (ret != 0) + ret = gzips_decompress_by_stream(decompressor, &in, stbuf.st_size, (options->test?0:&out), + NULL, NULL); + if (ret != 0){ + msg("\nERROR: gzips_decompress_by_stream() error code %d\n\n",ret); goto out_close_out; + } if (oldpath != NULL && newpath != NULL) restore_metadata(&out, newpath, &stbuf); @@ -479,7 +317,7 @@ decompress_file(struct libdeflate_decompressor *decompressor, const tchar *path, } static int -compress_file(struct libdeflate_compressor *compressor, const tchar *path, +compress_file(int compression_level, const tchar *path, const struct options *options) { tchar *newpath = NULL; @@ -520,14 +358,11 @@ compress_file(struct libdeflate_compressor *compressor, const tchar *path, goto out_close_out; } - /* TODO: need a streaming-friendly solution */ - ret = map_file_contents(&in, stbuf.st_size); - if (ret != 0) - goto out_close_out; - - ret = do_compress(compressor, &in, &out); - if (ret != 0) + ret = gzip_compress_by_stream(compression_level, &in, stbuf.st_size,&out, NULL); + if (ret != 0){ + msg("\nERROR: gzip_compress_by_stream() error code %d\n\n",ret); goto out_close_out; + } if (path != NULL && newpath != NULL) restore_metadata(&out, newpath, &stbuf); @@ -660,16 +495,8 @@ tmain(int argc, tchar *argv[]) libdeflate_free_decompressor(d); } else { - struct libdeflate_compressor *c; - - c = alloc_compressor(options.compression_level); - if (c == NULL) - return 1; - for (i = 0; i < argc; i++) - ret |= -compress_file(c, argv[i], &options); - - libdeflate_free_compressor(c); + ret |= -compress_file(options.compression_level, argv[i], &options); } switch (ret) { diff --git a/programs/gzip_compress_by_stream.c b/programs/gzip_compress_by_stream.c new file mode 100644 index 00000000..1f4d5144 --- /dev/null +++ b/programs/gzip_compress_by_stream.c @@ -0,0 +1,93 @@ +/* + * gzip_compress_by_stream.c + * added compress by stream, 2023--2024 housisong + */ +#include "../lib/gzip_overhead.h" +#include "gzip_compress_by_stream.h" +#include + +static const size_t kDictSize = (1<<15); //MATCHFINDER_WINDOW_SIZE +static const size_t kStepSize_better= (size_t)1024*1024*2; +static const size_t kStepSize_min =(size_t)1024*256; +#define _check(v,_ret_errCode) do { if (!(v)) {err_code=_ret_errCode; goto _out; } } while (0) +static inline size_t _dictSize_avail(u64 uncompressed_pos) { + return (uncompressed_posin_size)) in_step_size=in_size; + if (unlikely(in_step_size0, LIBDEFLATE_ENSTREAM_GZIP_HEAD_ERROR); + int w_ret=full_write(out,pmem,code_nbytes); + _check(w_ret==0, LIBDEFLATE_ENSTREAM_WRITE_FILE_ERROR); + out_cur+=code_nbytes; + } + + { // compress blocks single thread; you can compress blocks with multi-thread (set is_byte_align=1); + const int is_byte_align = 0; + u8* pdata=pmem; + u8* pcode=pdata+kDictSize+in_step_size; + for (u64 in_cur=0;in_cur=in_size); + size_t in_nbytes=is_final_block?in_size-in_cur:in_step_size; + size_t dict_size=_dictSize_avail(in_cur); + + //read block data + ssize_t r_len=xread(in,pdata+dict_size,in_nbytes); + _check(r_len==in_nbytes, LIBDEFLATE_ENSTREAM_READ_FILE_ERROR); + in_crc=libdeflate_crc32(in_crc,pdata+dict_size,in_nbytes); + + //compress the block + size_t code_nbytes=libdeflate_deflate_compress_block(c,pdata,dict_size,in_nbytes,is_final_block, + pcode,block_bound,is_byte_align); + _check(code_nbytes>0, LIBDEFLATE_ENSTREAM_COMPRESS_BLOCK_ERROR); + + //write the block's code + int w_ret=full_write(out,pcode,code_nbytes); + _check(w_ret==0, LIBDEFLATE_ENSTREAM_WRITE_FILE_ERROR); + out_cur+=code_nbytes; + + //dict data for next block + in_cur+=in_nbytes; + size_t nextDictSize=_dictSize_avail(in_cur); + memmove(pdata,pdata+dict_size+in_nbytes-nextDictSize,nextDictSize); + } + } + + {//gzip foot + size_t code_nbytes=libdeflate_gzip_compress_foot(in_crc,in_size,pmem,block_bound); + _check(code_nbytes>0, LIBDEFLATE_ENSTREAM_GZIP_FOOT_ERROR); + int w_ret=full_write(out,pmem,code_nbytes); + _check(w_ret==0, LIBDEFLATE_ENSTREAM_WRITE_FILE_ERROR); + out_cur+=code_nbytes; + } + + if (actual_out_nbytes_ret) + *actual_out_nbytes_ret=out_cur; + +_out: + if (c) libdeflate_free_compressor(c); + if (pmem) free(pmem); + return err_code; +} + diff --git a/programs/gzip_compress_by_stream.h b/programs/gzip_compress_by_stream.h new file mode 100644 index 00000000..4d7a8d81 --- /dev/null +++ b/programs/gzip_compress_by_stream.h @@ -0,0 +1,32 @@ +/* + * gzip_compress_by_stream.h + * added compress by stream, 2023--2024 housisong + */ +#ifndef PROGRAMS_PROG_GZIP_COMPRESS_STREAM_H +#define PROGRAMS_PROG_GZIP_COMPRESS_STREAM_H +#ifdef __cplusplus +extern "C" { +#endif +#include "prog_util.h" + +enum libdeflate_enstream_result{ + LIBDEFLATE_ENSTREAM_SUCCESS = LIBDEFLATE_SUCCESS, + LIBDEFLATE_ENSTREAM_MEM_ALLOC_ERROR =30, + LIBDEFLATE_ENSTREAM_READ_FILE_ERROR, + LIBDEFLATE_ENSTREAM_WRITE_FILE_ERROR, + LIBDEFLATE_ENSTREAM_ALLOC_COMPRESSOR_ERROR, + LIBDEFLATE_ENSTREAM_GZIP_HEAD_ERROR, + LIBDEFLATE_ENSTREAM_GZIP_FOOT_ERROR, //35 + LIBDEFLATE_ENSTREAM_COMPRESS_BLOCK_ERROR, +}; + +//compress gzip by stream +// actual_out_nbytes_ret can NULL; +// return value is libdeflate_enstream_result. +int gzip_compress_by_stream(int compression_level,struct file_stream *in,u64 in_size, + struct file_stream *out,u64* actual_out_nbytes_ret); + +#ifdef __cplusplus +} +#endif +#endif /* PROGRAMS_PROG_GZIP_COMPRESS_STREAM_H */ diff --git a/programs/gzip_decompress_by_stream.c b/programs/gzip_decompress_by_stream.c new file mode 100644 index 00000000..1c589b23 --- /dev/null +++ b/programs/gzip_decompress_by_stream.c @@ -0,0 +1,201 @@ +/* + * gzip_decompress_by_stream.c + * added decompress by stream, 2023 housisong + */ +#include +#include //memcpy +#include "../lib/gzip_overhead.h" +#include "../lib/gzip_constants.h" +#include "gzip_decompress_by_stream.h" + +#define kDictSize (1<<15) //MATCHFINDER_WINDOW_SIZE +static const size_t kMaxDeflateBlockSize = 301000; //default starting value, if input DeflateBlockSize greater than this, then will auto increase; +static const size_t kMaxDeflateBlockSize_min =1024*4; +static const size_t kMaxDeflateBlockSize_max = ((~(size_t)0)-kDictSize)/2; +static const size_t kInputSufficientSize = (1<<16); +#define _check(v,_ret_errCode) do { if (!(v)) {err_code=_ret_errCode; goto _out; } } while (0) +#define _check_d(_d_ret) _check(_d_ret==LIBDEFLATE_SUCCESS, _d_ret) + +typedef ssize_t (*xread_t)(struct file_stream *strm, void *buf, size_t count); +typedef int (*full_write_t)(struct file_stream *strm, const void *buf, size_t count); + +#define _read_code_from_file() do{ \ + size_t read_len=code_cur; \ + memmove(code_buf,code_buf+code_cur,code_buf_size-code_cur); \ + code_cur=0; \ + if (in_cur+read_len>in_size){ \ + code_buf_size-=read_len-(size_t)(in_size-in_cur); \ + read_len=in_size-in_cur; \ + } \ + _check(read_len==xread_proc(in,code_buf+code_buf_size-read_len,read_len), \ + LIBDEFLATE_DESTREAM_READ_FILE_ERROR); \ + in_cur+=read_len; \ +} while(0) + +static inline size_t _limitMaxDefBSize(size_t maxDeflateBlockSize){ + if (unlikely(maxDeflateBlockSizekMaxDeflateBlockSize_max)) return kMaxDeflateBlockSize_max; + return maxDeflateBlockSize; +} + +static int _gzip_decompress_by_stream(struct libdeflate_decompressor *d, + struct file_stream *in, u64 in_size,bool isMultiGz,struct file_stream *out, + xread_t xread_proc,full_write_t full_write_proc, + u64* _actual_in_nbytes_ret,u64* _actual_out_nbytes_ret){ + if (isMultiGz) assert(_actual_in_nbytes_ret==0); + int err_code=0; + u8* data_buf=0; + u8* code_buf=0; + u8* _dict_buf_back=0; + u64 in_cur=0; + u64 out_cur=0; + u64 last_out_cur=0; + size_t curDeflateBlockSize=kMaxDeflateBlockSize; + size_t curBlockSize=_limitMaxDefBSize(curDeflateBlockSize); + size_t data_buf_size=curBlockSize+kDictSize; + size_t code_buf_size=(curBlockSizecode_buf_size) + _read_code_from_file(); + ret=libdeflate_gzip_decompress_head(code_buf+code_cur,code_buf_size-code_cur,&actual_in_nbytes_ret); + _check_d(ret); + code_cur+=actual_in_nbytes_ret; + } + + while(1){ + // [ ( dict ) | dataBuf ] [ codeBuf ] + // ^ ^ ^ ^ ^ ^ ^ + // data_buf out_cur data_cur data_buf_size code_buf code_cur code_buf_size + size_t kLimitDataSize=curBlockSize/2+kDictSize; + size_t kLimitCodeSize=code_buf_size/2; + size_t actual_out_nbytes_ret; + uint16_t dec_state; + __datas_prepare: + if (is_final_block_ret||(data_cur>kLimitDataSize)){//save data to out file + if (out) + _check(0==full_write_proc(out,data_buf+kDictSize,data_cur-kDictSize), LIBDEFLATE_DESTREAM_WRITE_FILE_ERROR); + data_crc=libdeflate_crc32(data_crc,data_buf+kDictSize,data_cur-kDictSize); + out_cur+=data_cur-kDictSize; + if (isMultiGz){//dict data for next block + memmove(data_buf,data_buf+data_cur-kDictSize,kDictSize); + data_cur=kDictSize; + } + if (is_final_block_ret) + break; + } + if (code_cur>kLimitCodeSize) + _read_code_from_file(); + dec_state=libdeflate_deflate_decompress_get_state(d); + ret=libdeflate_deflate_decompress_block(d,code_buf+code_cur,code_buf_size-code_cur, + data_buf+data_cur-kDictSize,kDictSize,data_buf_size-data_cur, + &actual_in_nbytes_ret,&actual_out_nbytes_ret, + LIBDEFLATE_STOP_BY_ANY_BLOCK,&is_final_block_ret); + if (ret!=LIBDEFLATE_SUCCESS){ + if (((in_cur==in_size)||((size_t)((code_buf_size-code_cur)-actual_in_nbytes_ret)>kInputSufficientSize)) + &&(ret!=LIBDEFLATE_INSUFFICIENT_SPACE)) + _check_d(ret); + kLimitDataSize=kDictSize; + kLimitCodeSize=0; + if ((data_cur>kDictSize)||((code_cur>0)&&(in_curcode_buf_size){ + u8* _code_buf=(u8*)realloc(code_buf,_code_buf_size); + _check(_code_buf!=0, LIBDEFLATE_DESTREAM_MEM_ALLOC_ERROR); + code_buf=_code_buf; _code_buf=0; + memcpy(code_buf+_code_buf_size-loaded_in_size,code_buf+code_cur,loaded_in_size); + code_cur+=_code_buf_size-code_buf_size; + code_buf_size=_code_buf_size; + } + { + data_buf=(u8*)malloc(_data_buf_size); + _check(data_buf!=0, LIBDEFLATE_DESTREAM_MEM_ALLOC_ERROR); + memcpy(data_buf,_dict_buf_back,kDictSize); + data_buf_size=_data_buf_size; + } + }else{ //decompress fail, can't increase buf + _check_d(ret); + } + libdeflate_deflate_decompress_set_state(d,dec_state); + goto __datas_prepare; //retry by more datas + } + //decompress ok + code_cur+=actual_in_nbytes_ret; + data_cur+=actual_out_nbytes_ret; + } + + {//gzip foot + uint32_t saved_crc; + uint32_t saved_uncompress_nbytes; + if (code_cur+GZIP_FOOTER_SIZE>code_buf_size) + _read_code_from_file(); + ret=libdeflate_gzip_decompress_foot(code_buf+code_cur,code_buf_size-code_cur, + &saved_crc,&saved_uncompress_nbytes,&actual_in_nbytes_ret); + _check_d(ret); + code_cur+=actual_in_nbytes_ret; + + _check(saved_crc==data_crc, LIBDEFLATE_DESTREAM_DATA_CRC_ERROR); + _check(saved_uncompress_nbytes==(u32)(out_cur-last_out_cur), LIBDEFLATE_DESTREAM_DATA_SIZE_ERROR); + last_out_cur=out_cur; + } + } while (isMultiGz && (0<(u64)((in_size-in_cur)+(code_buf_size-code_cur)))); + + if (_actual_in_nbytes_ret) + *_actual_in_nbytes_ret=(in_cur-(size_t)(code_buf_size-code_cur)); + if (_actual_out_nbytes_ret) + *_actual_out_nbytes_ret=out_cur; + +_out: + libdeflate_deflate_decompress_block_reset(d); + if (data_buf) free(data_buf); + if (code_buf) free(code_buf); + if (_dict_buf_back) free(_dict_buf_back); + return err_code; +} + + +int gzip_decompress_by_stream(struct libdeflate_decompressor *d, + struct file_stream *in, u64 in_size, struct file_stream *out, + u64* actual_in_nbytes_ret,u64* actual_out_nbytes_ret){ + return _gzip_decompress_by_stream(d,in,in_size,false,out,xread,full_write, + actual_in_nbytes_ret,actual_out_nbytes_ret); +} + +int gzips_decompress_by_stream(struct libdeflate_decompressor *d, + struct file_stream *in, u64 in_size, struct file_stream *out, + u64* actual_out_nbytes_ret){ + return _gzip_decompress_by_stream(d,in,in_size,true,out,xread,full_write, + 0,actual_out_nbytes_ret); +} diff --git a/programs/gzip_decompress_by_stream.h b/programs/gzip_decompress_by_stream.h new file mode 100644 index 00000000..b0a74df9 --- /dev/null +++ b/programs/gzip_decompress_by_stream.h @@ -0,0 +1,36 @@ +/* + * gzip_decompress_by_stream.h + * added decompress by stream, 2023 housisong + */ +#ifndef PROGRAMS_PROG_GZIP_DECOMPRESS_STREAM_H +#define PROGRAMS_PROG_GZIP_DECOMPRESS_STREAM_H +#ifdef __cplusplus +extern "C" { +#endif +#include "prog_util.h" + +enum libdeflate_destream_result{ + LIBDEFLATE_DESTREAM_MEM_ALLOC_ERROR =20, + LIBDEFLATE_DESTREAM_READ_FILE_ERROR, + LIBDEFLATE_DESTREAM_WRITE_FILE_ERROR, + LIBDEFLATE_DESTREAM_DATA_CRC_ERROR, + LIBDEFLATE_DESTREAM_DATA_SIZE_ERROR, +}; + +//decompress one gzip by stream +// 'out','actual_in_nbytes_ret','actual_out_nbytes_ret' can NULL; +// return value is libdeflate_result or libdeflate_destream_result. +int gzip_decompress_by_stream(struct libdeflate_decompressor *decompressor, + struct file_stream *in, u64 in_size, struct file_stream *out, + u64* actual_in_nbytes_ret,u64* actual_out_nbytes_ret); + + +//decompress multi concatenated gzip by stream +int gzips_decompress_by_stream(struct libdeflate_decompressor *decompressor, + struct file_stream *in, u64 in_size, struct file_stream *out, + u64* actual_out_nbytes_ret); + +#ifdef __cplusplus +} +#endif +#endif /* PROGRAMS_PROG_GZIP_DECOMPRESS_STREAM_H */