Skip to content

Commit b9dc131

Browse files
committed
Add streaming/framed LZ4 compression.
* subversion/include/private/svn_subr_private.h (svn_lz4__compress_ctx_t, svn_lz4__decompress_ctx_t): New types. (svn_lz4__header_size_max, svn_lz4__compress_create, svn_lz4__compress_bound, svn_lz4__compress_update, svn_lz4__compress_flush, svn_lz4__compress_end, svn_lz4__decompress_create, svn_lz4__decompress): New prototypes. * subversion/libsvn_subr/compress_lz4.c: Include svn_error.h, lz2frame.h and lz4hc.c. (free_lz4_cctx, free_lz4_dctx, check_compress_status): New helper functions. (svn_lz4__compress_ctx_t, svn_lz4__decompress_ctx_t): Define the new types. (svn_lz4__header_size_max, svn_lz4__compress_create, svn_lz4__compress_bound, svn_lz4__compress_update, svn_lz4__compress_flush, svn_lz4__compress_end, svn_lz4__decompress_create, svn_lz4__decompress): Implement the LZ4 compression functions. * subversion/tests/svn_test.h (SVN_TEST_BUFFER_ASSERT): New test assertion for comparing binary buffers. * subversion/tests/libsvn_subr/compress-test.c: Include apr_time.h, svn_error.h and svn_string.h, but not svn_pools.h which was not used. (uncompressed, uncompressed_size, compressed_block, compressed_block_size, compressed_frame, compressed_frame_size, empty_frame, empty_frame_size): New, constant data. (test_decompress_lz4, test_compress_lz4): Use the new constant data. (de_compress_lz4): New helper function. (test_compress_lz4_random): New test. (test_compress_lz4_empty): Use de_compress_lz4(). (compress_lz4_stream, decompress_lz4_stream, decompress_lz4_frame, de_compress_lz4_stream, de_compress_lz4_frame_random): New helper functions. (test_decompress_lz4_frame, test_decompress_lz4_frame_stable, test_decompress_lz4_frame_empty, test_compress_lz4_frame_random, test_compress_lz4_frame_random_stable, test_compress_lz4_frame_no_header_space): New tests. (test_funcs): Register the new tests. git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/better-pristines@1931681 13f79535-47bb-0310-9956-ffa450edef68
1 parent c96b948 commit b9dc131

File tree

4 files changed

+605
-25
lines changed

4 files changed

+605
-25
lines changed

subversion/include/private/svn_subr_private.h

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,100 @@ svn__decompress_lz4(const void *data, apr_size_t len,
625625
svn_stringbuf_t *out,
626626
apr_size_t limit);
627627

628+
/*
629+
* LZ4 stream compression with framing.
630+
*/
631+
632+
/* The largest size of an LZ4 frame header; see: LZ4F_HEADER_SIZE_MAX */
633+
apr_size_t
634+
svn_lz4__header_size_max(void);
635+
636+
/* LZ4 stream compression and decompression contexts. */
637+
typedef struct svn_lz4__compress_ctx_t svn_lz4__compress_ctx_t;
638+
typedef struct svn_lz4__decompress_ctx_t svn_lz4__decompress_ctx_t;
639+
640+
/* Creates a new LZ4 compression context CCTX, allocated from POOL.
641+
* The context will be freed when the pool is cleared.
642+
*
643+
* If STABLE_INPUT is TRUE, the source buffer must be contiguous for the whole
644+
* compression; this means that all uncompressed source data is available to
645+
* svn_lz4__compress_update() through negative offsets from the INPUT pointer.
646+
* When this option can be set, the compressor can avoid copying and caching
647+
* source data in CCTX.
648+
*/
649+
svn_error_t *
650+
svn_lz4__compress_create(svn_lz4__compress_ctx_t **cctx,
651+
svn_boolean_t stable_input,
652+
apr_pool_t *pool);
653+
654+
/* Returns the minimum CAPACITY of the output buffer to guarantee that
655+
* the next svn_lz4__compress_update() with CCTX and SIZE amount
656+
* of input data will succeed. If SIZE is 0, the returned value is
657+
* computed for svn_lz4__compress_flush()/_end().
658+
*/
659+
apr_size_t
660+
svn_lz4__compress_bound(svn_lz4__compress_ctx_t *cctx, apr_size_t size);
661+
662+
/* Using the context CCTX, compresses SIZE bytes from INPUT into the OUTPUT
663+
* buffer of size CAPACITY. *LENGTH will be the number of bytes actually
664+
* written to OUTPUT and can be 0 if the input data was buffered in the
665+
* context.
666+
*
667+
* At the first call of svn_lz4__compress_update(), CAPACITY must be
668+
* at least svn_lz4__header_size_max() to accomodate the frame header.
669+
*/
670+
svn_error_t *
671+
svn_lz4__compress_update(apr_size_t *length,
672+
svn_lz4__compress_ctx_t *cctx,
673+
void *output, apr_size_t capacity,
674+
const void *input, apr_size_t size);
675+
676+
/* Flushes any data buffered in CCTX to the OUTPUT buffer of size CAPACITY
677+
* and returns the number of bytes written in *LENGTH.
678+
*/
679+
svn_error_t *
680+
svn_lz4__compress_flush(apr_size_t *length,
681+
svn_lz4__compress_ctx_t *cctx,
682+
void *output, apr_size_t capacity);
683+
684+
/* Ends the compression stream, returning the number of bytes written
685+
* to OUTPUT in *LENGTH. Afterwards, CCTX will be available for to start
686+
* a new compression stream with svn_lz4__compress_update().
687+
*/
688+
svn_error_t *
689+
svn_lz4__compress_end(apr_size_t *length,
690+
svn_lz4__compress_ctx_t *cctx,
691+
void *output, apr_size_t capacity);
692+
693+
/* Creates a new LZ4 decompression context DCTX, allocated from POOL.
694+
* The context will be freed when the pool is cleared.
695+
*
696+
* If STABLE_OUTPUT is TRUE, the output buffer must be contiguous for the whole
697+
* decompression; this means that previously decompressed content is available
698+
* to svn_lz4__decompress() through negative offsets from the OUTPUT pointer.
699+
* This is the case, for example, when we store the decompressed data to a
700+
* stringbuf, which remains contiguous even when it's reallocated. When this
701+
* option can be set, the decompressor can avoid copying and caching output
702+
* data in DCTX.
703+
*/
704+
svn_error_t *
705+
svn_lz4__decompress_create(svn_lz4__decompress_ctx_t **dctx,
706+
svn_boolean_t stable_output,
707+
apr_pool_t *pool);
708+
709+
/* Using DCTX, read at most *INPUT_SIZE compressed bytes from the INPUT buffer
710+
* and decompress them to the OUTPUT buffer of size *OUTPUT_SIZE. Upon return,
711+
* *INPUT_SIZE will be the number of bytes actally read from INPUT and
712+
* *OUTPUT_SIZE will be the number of bytes written to *OUTPUT. The returned
713+
* *SIZE_HINT is a hint for the expected OUTPUT_SIZE for the next call, or 0
714+
* if the frame has been completely decoded.
715+
*/
716+
svn_error_t *
717+
svn_lz4__decompress(apr_size_t *size_hint,
718+
svn_lz4__decompress_ctx_t *dctx,
719+
void *output, apr_size_t *output_size,
720+
const void *input, apr_size_t *input_size);
721+
628722
/** @} */
629723

630724
/**

subversion/libsvn_subr/compress_lz4.c

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,26 @@
2323

2424
#include <assert.h>
2525

26+
#include "svn_error.h"
2627
#include "private/svn_subr_private.h"
2728

2829
#include "svn_private_config.h"
30+
#include "svn_types.h"
2931

3032
#ifdef SVN_INTERNAL_LZ4
3133
#include "lz4/lz4internal.h"
34+
#include "lz4/lz4frame.h"
35+
#include "lz4/lz4hc.h"
3236
#else
3337
#include <lz4.h>
38+
#include <lz4frame.h>
39+
#include <lz4hc.h>
3440
#endif
3541

42+
/*
43+
* Simple compression and decompression
44+
*/
45+
3646
svn_error_t *
3747
svn__compress_lz4(const void *data, apr_size_t len,
3848
svn_stringbuf_t *out)
@@ -127,6 +137,212 @@ svn__decompress_lz4(const void *data, apr_size_t len,
127137
return SVN_NO_ERROR;
128138
}
129139

140+
/*
141+
* Streamy compression
142+
*/
143+
144+
apr_size_t
145+
svn_lz4__header_size_max(void)
146+
{
147+
return LZ4F_HEADER_SIZE_MAX;
148+
}
149+
150+
struct svn_lz4__compress_ctx_t
151+
{
152+
LZ4F_cctx *ctx;
153+
unsigned started;
154+
LZ4F_preferences_t prefs;
155+
LZ4F_compressOptions_t options;
156+
};
157+
158+
static apr_status_t free_lz4_cctx(void *data)
159+
{
160+
svn_lz4__compress_ctx_t *const cctx = data;
161+
const LZ4F_errorCode_t code = LZ4F_freeCompressionContext(cctx->ctx);
162+
if (LZ4F_isError(code))
163+
return SVN_ERR_LZ4_COMPRESSION_FAILED;
164+
return APR_SUCCESS;
165+
}
166+
167+
svn_error_t *
168+
svn_lz4__compress_create(svn_lz4__compress_ctx_t **cctx_out,
169+
svn_boolean_t stable_input,
170+
apr_pool_t *pool)
171+
{
172+
static const LZ4F_preferences_t compression_prefs = {
173+
{
174+
LZ4F_max64KB, /* frameInfo.blockSizeID */
175+
LZ4F_blockLinked, /* frameInfo.blockMode */
176+
LZ4F_noContentChecksum, /* frameInfo.contentChecksumFlag */
177+
LZ4F_frame, /* frameInfo.frameType */
178+
0, /* frameInfo.contentSize */
179+
0, /* frameInfo.dictID */
180+
LZ4F_blockChecksumEnabled /* frameInfo.blockChecksumFlag */
181+
},
182+
/* NOTE: With LZ4 1.10+, the compression level will be 2, faster but less
183+
compressed than with older versions of LZ4, where the value of
184+
this constant was 3. This does not affect compression format
185+
backward compatibility. */
186+
LZ4HC_CLEVEL_MIN, /* compressionLevel */
187+
0, /* autoFlush */
188+
1, /* favorDecSpeed */
189+
{ 0 } /* reserved */
190+
};
191+
192+
LZ4F_cctx *ctx;
193+
svn_lz4__compress_ctx_t *cctx;
194+
LZ4F_errorCode_t code = LZ4F_createCompressionContext(&ctx, LZ4F_VERSION);
195+
196+
if (LZ4F_isError(code))
197+
return svn_error_createf(SVN_ERR_LZ4_COMPRESSION_FAILED, NULL,
198+
_("Create LZ4 compression context: %s"),
199+
LZ4F_getErrorName(code));
200+
201+
cctx = apr_pcalloc(pool, sizeof(*cctx));
202+
cctx->ctx = ctx;
203+
cctx->prefs = compression_prefs;
204+
cctx->options.stableSrc = !!stable_input;
205+
apr_pool_cleanup_register(pool, cctx, free_lz4_cctx, apr_pool_cleanup_null);
206+
*cctx_out = cctx;
207+
return SVN_NO_ERROR;
208+
}
209+
210+
static SVN__FORCE_INLINE svn_error_t *
211+
check_compress_status(apr_size_t *length, apr_size_t status, apr_size_t extra)
212+
{
213+
if (LZ4F_isError(status))
214+
return svn_error_createf(SVN_ERR_LZ4_COMPRESSION_FAILED, NULL,
215+
_("LZ4 compress: %s"),
216+
LZ4F_getErrorName(status));
217+
218+
*length = status + extra;
219+
return SVN_NO_ERROR;
220+
}
221+
222+
apr_size_t
223+
svn_lz4__compress_bound(svn_lz4__compress_ctx_t *cctx,
224+
apr_size_t size)
225+
{
226+
return LZ4F_compressBound(size, &cctx->prefs);
227+
}
228+
229+
svn_error_t *
230+
svn_lz4__compress_update(apr_size_t *length,
231+
svn_lz4__compress_ctx_t *cctx,
232+
void *output, apr_size_t capacity,
233+
const void *input, apr_size_t size)
234+
{
235+
apr_size_t header_size = 0;
236+
apr_size_t status;
237+
238+
if (!cctx->started)
239+
{
240+
if (capacity < LZ4F_HEADER_SIZE_MAX)
241+
return svn_error_create(SVN_ERR_LZ4_COMPRESSION_FAILED, NULL,
242+
_("LZ4 compress: no space for frame header"));
243+
244+
status = LZ4F_compressBegin(cctx->ctx, output, capacity, &cctx->prefs);
245+
SVN_ERR(check_compress_status(&header_size, status, 0));
246+
output = (char*)output + header_size;
247+
capacity -= header_size;
248+
cctx->started = TRUE;
249+
}
250+
251+
status = LZ4F_compressUpdate(cctx->ctx, output, capacity,
252+
input, size, &cctx->options);
253+
return svn_error_trace(check_compress_status(length, status, header_size));
254+
}
255+
256+
svn_error_t *
257+
svn_lz4__compress_flush(apr_size_t *length,
258+
svn_lz4__compress_ctx_t *cctx,
259+
void *output, apr_size_t capacity)
260+
{
261+
const apr_size_t status = LZ4F_flush(cctx->ctx,
262+
output, capacity,
263+
&cctx->options);
264+
return svn_error_trace(check_compress_status(length, status, 0));
265+
}
266+
267+
svn_error_t *
268+
svn_lz4__compress_end(apr_size_t *length,
269+
svn_lz4__compress_ctx_t *cctx,
270+
void *output, apr_size_t capacity)
271+
{
272+
const apr_size_t status = LZ4F_compressEnd(cctx->ctx,
273+
output, capacity,
274+
&cctx->options);
275+
svn_error_t *const err = check_compress_status(length, status, 0);
276+
if (!err)
277+
cctx->started = FALSE;
278+
return svn_error_trace(err);
279+
}
280+
281+
/*
282+
* Streamy decompression
283+
*/
284+
285+
struct svn_lz4__decompress_ctx_t
286+
{
287+
LZ4F_dctx* ctx;
288+
LZ4F_decompressOptions_t options;
289+
};
290+
291+
static apr_status_t free_lz4_dctx(void *data)
292+
{
293+
svn_lz4__decompress_ctx_t *const dctx = data;
294+
const LZ4F_errorCode_t code = LZ4F_freeDecompressionContext(dctx->ctx);
295+
if (LZ4F_isError(code))
296+
return SVN_ERR_LZ4_DECOMPRESSION_FAILED;
297+
return APR_SUCCESS;
298+
}
299+
300+
svn_error_t *
301+
svn_lz4__decompress_create(svn_lz4__decompress_ctx_t **dctx_out,
302+
svn_boolean_t stable_output,
303+
apr_pool_t *pool)
304+
{
305+
LZ4F_dctx* ctx;
306+
svn_lz4__decompress_ctx_t *dctx;
307+
LZ4F_errorCode_t code = LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION);
308+
309+
if (LZ4F_isError(code))
310+
return svn_error_createf(SVN_ERR_LZ4_DECOMPRESSION_FAILED, NULL,
311+
_("Create LZ4 decompression context: %s"),
312+
LZ4F_getErrorName(code));
313+
314+
dctx = apr_pcalloc(pool, sizeof(*dctx));
315+
dctx->ctx = ctx;
316+
dctx->options.stableDst = !!stable_output;
317+
apr_pool_cleanup_register(pool, dctx, free_lz4_dctx, apr_pool_cleanup_null);
318+
*dctx_out = dctx;
319+
return SVN_NO_ERROR;
320+
}
321+
322+
svn_error_t *
323+
svn_lz4__decompress(apr_size_t *size_hint,
324+
svn_lz4__decompress_ctx_t *dctx,
325+
void *output, apr_size_t *output_size,
326+
const void *input, apr_size_t *input_size)
327+
{
328+
const apr_size_t status = LZ4F_decompress(dctx->ctx,
329+
output, output_size,
330+
input, input_size,
331+
&dctx->options);
332+
333+
if (LZ4F_isError(status))
334+
return svn_error_createf(SVN_ERR_LZ4_DECOMPRESSION_FAILED, NULL,
335+
_("LZ4 decompress: %s"),
336+
LZ4F_getErrorName(status));
337+
338+
*size_hint = status;
339+
return SVN_NO_ERROR;
340+
}
341+
342+
/*
343+
* Library version
344+
*/
345+
130346
const char *
131347
svn_lz4__compiled_version(void)
132348
{

0 commit comments

Comments
 (0)