diff --git a/README.md b/README.md index bd80606..f732067 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,12 @@ RPM packages of this extension are available in [» Remi's RPM repository](https DEB packages of this extension are available in [» Ondřej Surý's DEB repository](https://deb.sury.org/) and are named **php-lz4**. +## Installation via PIE + +```bash +pie install kjdev/lz4 +``` + ## Configuration @@ -42,8 +48,10 @@ php.ini: ## Function -* lz4\_compress — LZ4 compression -* lz4\_uncompress — LZ4 decompression +* lz4\_compress — LZ4 compression (block format) +* lz4\_uncompress — LZ4 decompression (block format) +* lz4\_compress\_frame — LZ4 compression (frame format) +* lz4\_uncompress\_frame — LZ4 decompression (frame format) ### lz4\_compress — LZ4 compression @@ -53,7 +61,7 @@ string **lz4\_compress** ( string _$data_ [ , int _$level_ = 0 , string _$extra_ LZ4 compression. -#### Pameters +#### Parameters * _data_ @@ -81,7 +89,7 @@ string **lz4\_uncompress** ( string _$data_ [ , long _$maxsize_ = -1 , long _$of LZ4 decompression. -#### Pameters +#### Parameters * _data_ @@ -99,6 +107,70 @@ LZ4 decompression. Returns the decompressed data or FALSE if an error occurred. + +### lz4\_compress\_frame — LZ4 compression (frame format) + +#### Description + +string **lz4\_compress\_frame** ( string _$data_ [ , int _$level_ = 0 , int _$max_block_size_ = 0 , int _$checksums_ = 0 ] ) + +LZ4 compression to frame. + +#### Parameters + +* _data_ + + The string to compress. + +* _level_ + + The level of compression (1-12, Recommended values are between 4 and 9). + (Default to 0, Not High Compression Mode.) + +* _max\_block\_size_ + + Maximum uncompressed size of each block. + Pass any of the following values: + + * _LZ4\_BLOCK\_SIZE\_64KB_ + * _LZ4\_BLOCK\_SIZE\_256KB_ + * _LZ4\_BLOCK\_SIZE\_1MB_ + * _LZ4\_BLOCK\_SIZE\_4MB_ + + Any other value will be treated as _LZ4\_BLOCK\_SIZE\_64KB_. + +* _checksums_ + + Enable/disable frame-level and block-level checksums. + Pass a bitwise combination of the following constants: + + * _LZ4\_CHECKSUM\_FRAME_: frame-level checksum + * _LZ4\_CHECKSUM\_BLOCK_: block-level checksum + +#### Return Values + +Returns the compressed data or FALSE if an error occurred. + + +### lz4\_uncompress\_frame — LZ4 decompression (frame format) + +#### Description + +string **lz4\_uncompress\_frame** ( string _$data_ ) + +LZ4 decompression from frame. + +#### Parameters + +* _data_ + + The compressed string. + +#### Return Values + +Returns the decompressed data or FALSE if an error occurred. + + ## Examples $data = lz4_compress('test'); diff --git a/config.m4 b/config.m4 index 449a237..d46e9a4 100644 --- a/config.m4 +++ b/config.m4 @@ -59,7 +59,7 @@ if test "$PHP_LZ4" != "no"; then else AC_MSG_RESULT(use bundled version) - PHP_NEW_EXTENSION(lz4, lz4.c lz4/lib/lz4.c lz4/lib/lz4hc.c lz4/lib/xxhash.c, $ext_shared) + PHP_NEW_EXTENSION(lz4, lz4.c lz4/lib/lz4.c lz4/lib/lz4hc.c lz4/lib/lz4frame.c lz4/lib/xxhash.c, $ext_shared) PHP_ADD_BUILD_DIR($ext_builddir/lz4/lib, 1) PHP_ADD_INCLUDE([$ext_srcdir/lz4/lib]) diff --git a/config.w32 b/config.w32 index b97f606..0bb480c 100644 --- a/config.w32 +++ b/config.w32 @@ -6,7 +6,7 @@ if (PHP_LZ4 != "no") { EXTENSION("lz4", "lz4.c", PHP_LZ4_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); } else { EXTENSION("lz4", "lz4.c", PHP_LZ4_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); - ADD_SOURCES("lz4/lib", "lz4.c lz4hc.c xxhash.c", "lz4", "lz4\\lib"); + ADD_SOURCES("lz4/lib", "lz4.c lz4frame.c lz4hc.c xxhash.c", "lz4", "lz4\\lib"); ADD_FLAG("CFLAGS_LZ4", " /I" + configure_module_dirname + " /I" + configure_module_dirname + "/lz4/lib"); } PHP_INSTALL_HEADERS("ext/lz4/", "php_lz4.h"); diff --git a/lz4.c b/lz4.c index 5e2b887..99fce2c 100644 --- a/lz4.c +++ b/lz4.c @@ -39,6 +39,7 @@ /* lz4 */ #include "lz4.h" #include "lz4hc.h" +#include "lz4frame.h" #if defined(LZ4HC_CLEVEL_MAX) /* version >= 1.7.5 */ @@ -66,8 +67,13 @@ #define PHP_LZ4_CLEVEL_MIN 3 #endif +#define PHP_LZ4_CHECKSUM_FRAME (1<<0) +#define PHP_LZ4_CHECKSUM_BLOCK (1<<1) + static ZEND_FUNCTION(lz4_compress); static ZEND_FUNCTION(lz4_uncompress); +static ZEND_FUNCTION(lz4_compress_frame); +static ZEND_FUNCTION(lz4_uncompress_frame); ZEND_BEGIN_ARG_INFO_EX(arginfo_lz4_compress, 0, 0, 1) ZEND_ARG_INFO(0, data) @@ -81,6 +87,17 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_lz4_uncompress, 0, 0, 1) ZEND_ARG_INFO(0, offset) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_lz4_compress_frame, 0, 0, 1) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(0, level) + ZEND_ARG_INFO(0, max_block_size) + ZEND_ARG_INFO(0, checksums) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_lz4_uncompress_frame, 0, 0, 1) + ZEND_ARG_INFO(0, data) +ZEND_END_ARG_INFO() + #if PHP_MAJOR_VERSION >= 7 && defined(HAVE_APCU_SUPPORT) static int APC_SERIALIZER_NAME(lz4)(APC_SERIALIZER_ARGS); static int APC_UNSERIALIZER_NAME(lz4)(APC_UNSERIALIZER_ARGS); @@ -89,6 +106,8 @@ static int APC_UNSERIALIZER_NAME(lz4)(APC_UNSERIALIZER_ARGS); static zend_function_entry lz4_functions[] = { ZEND_FE(lz4_compress, arginfo_lz4_compress) ZEND_FE(lz4_uncompress, arginfo_lz4_uncompress) + ZEND_FE(lz4_compress_frame, arginfo_lz4_compress_frame) + ZEND_FE(lz4_uncompress_frame, arginfo_lz4_uncompress_frame) ZEND_FE_END }; @@ -99,6 +118,12 @@ static PHP_MINIT_FUNCTION(lz4) REGISTER_LONG_CONSTANT("LZ4_CLEVEL_MAX", PHP_LZ4_CLEVEL_MAX, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("LZ4_VERSION_NUMBER", LZ4_versionNumber(), CONST_CS | CONST_PERSISTENT); REGISTER_STRING_CONSTANT("LZ4_VERSION_TEXT", (char *)LZ4_versionString(), CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LZ4_CHECKSUM_FRAME", PHP_LZ4_CHECKSUM_FRAME, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LZ4_CHECKSUM_BLOCK", PHP_LZ4_CHECKSUM_BLOCK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LZ4_BLOCK_SIZE_64KB", LZ4F_max64KB, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LZ4_BLOCK_SIZE_256KB", LZ4F_max256KB, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LZ4_BLOCK_SIZE_1MB", LZ4F_max1MB, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LZ4_BLOCK_SIZE_4MB", LZ4F_max4MB, CONST_CS | CONST_PERSISTENT); #if PHP_MAJOR_VERSION >= 7 && defined(HAVE_APCU_SUPPORT) apc_register_serializer("lz4", @@ -260,6 +285,163 @@ static int php_lz4_uncompress(const char* in, const int in_len, return SUCCESS; } +/** + * @param max_block_size 4: 64KB, 5: 256KB, 6: 1MB, 7: 4MB, all other values: 64KB + * @param checksums 0: none, 1: frame content, 2: each block, 3: frame content + each block + */ +static int php_lz4_compress_frame(char* in, const int in_len, + char** out, int* out_len, + const int level, + int max_block_size, + const int checksums) +{ + int var_len; + LZ4F_preferences_t preferences = LZ4F_INIT_PREFERENCES; + + if (max_block_size < 4 || max_block_size > 7) { + max_block_size = 0; + } + preferences.frameInfo.blockSizeID = max_block_size; + preferences.frameInfo.contentSize = in_len; + preferences.frameInfo.contentChecksumFlag = (checksums & PHP_LZ4_CHECKSUM_FRAME) > 0; + preferences.frameInfo.blockChecksumFlag = (checksums & PHP_LZ4_CHECKSUM_BLOCK) > 0; + preferences.compressionLevel = level; + + var_len = LZ4F_compressFrameBound(in_len, &preferences); + + *out = (char*)emalloc(var_len); + if (!*out) { + zend_error(E_WARNING, "lz4_compress_frame : memory error"); + *out_len = 0; + return FAILURE; + } + + *out_len = LZ4F_compressFrame(*out, var_len, in, in_len, &preferences); + if (LZ4F_isError(*out_len)) { + zend_error(E_WARNING, + "lz4_compress_frame : data error (%s)", + LZ4F_getErrorName(*out_len)); + efree(*out); + *out = NULL; + *out_len = 0; + return FAILURE; + } + + return SUCCESS; +} + +static size_t get_block_size(const LZ4F_frameInfo_t *frame_info) +{ + switch (frame_info->blockSizeID) { + case LZ4F_max256KB: return 1 << 18; + case LZ4F_max1MB: return 1 << 20; + case LZ4F_max4MB: return 1 << 22; + default: return 1 << 16; // 64kibi + } +} + +static int php_lz4_uncompress_frame(const char* in, const int in_len, + char** out, unsigned long int* out_len) +{ + LZ4F_dctx* dctx; + LZ4F_errorCode_t err; + size_t size_next, in_offset, out_offset, in_size_consumed, out_size_decompressed, block_size; + LZ4F_frameInfo_t frame_info; + LZ4F_decompressOptions_t decomp_options = { 0u, 0u, 0u, 0u }; + + err = LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION); + if (LZ4F_isError(err)) { + zend_error(E_WARNING, + "lz4_uncompress_frame : create decompression context (%s)", + LZ4F_getErrorName(err)); + return FAILURE; + } + + in_size_consumed = LZ4F_HEADER_SIZE_MAX; + size_next = LZ4F_getFrameInfo(dctx, &frame_info, in, &in_size_consumed); + if (LZ4F_isError(size_next)) { + zend_error(E_WARNING, + "lz4_uncompress_frame : get frame info (%s)", + LZ4F_getErrorName(size_next)); + LZ4F_freeDecompressionContext(dctx); + return FAILURE; + } + + block_size = get_block_size(&frame_info); + *out_len = block_size; + if (frame_info.contentSize > 0) { + *out_len = frame_info.contentSize; + } + *out = (char*)malloc(*out_len); + if (!*out) { + zend_error(E_WARNING, "lz4_uncompress_frame : memory error"); + LZ4F_freeDecompressionContext(dctx); + return FAILURE; + } + + in_offset = in_size_consumed; + out_offset = 0; + while (size_next > 0) { + if (frame_info.contentSize == 0 && *out_len - out_offset < block_size) { + *out_len += block_size * 3; + char *tmp = (char*)realloc(*out, *out_len); + if (!tmp) { + zend_error(E_WARNING, "lz4_uncompress_frame : memory error"); + LZ4F_freeDecompressionContext(dctx); + free(*out); + *out = NULL; + *out_len = 0; + return FAILURE; + } + *out = tmp; + } + + in_size_consumed = size_next; + out_size_decompressed = *out_len - out_offset; + + size_next = LZ4F_decompress(dctx, + (*out) + out_offset, + &out_size_decompressed, + in + in_offset, + &in_size_consumed, + &decomp_options); + if (LZ4F_isError(size_next)) { + zend_error(E_WARNING, + "lz4_uncompress_frame : data error (%s)", + LZ4F_getErrorName(size_next)); + LZ4F_freeDecompressionContext(dctx); + free(*out); + *out = NULL; + *out_len = 0; + return FAILURE; + } + + in_offset += in_size_consumed; + out_offset += out_size_decompressed; + + if (in_size_consumed == 0) { + zend_error(E_WARNING, + "lz4_uncompress_frame : data error (unexpected uncompressed data size)"); + LZ4F_freeDecompressionContext(dctx); + free(*out); + *out = NULL; + *out_len = 0; + return FAILURE; + } + } + *out_len = out_offset; + + err = LZ4F_freeDecompressionContext(dctx); + if (LZ4F_isError(err)) { + zend_error(E_WARNING, + "lz4_uncompress_frame : free decompression context (%s)", + LZ4F_getErrorName(err)); + return FAILURE; + } + + return SUCCESS; +} + static ZEND_FUNCTION(lz4_compress) { zval *data; @@ -337,6 +519,74 @@ static ZEND_FUNCTION(lz4_uncompress) free(output); } +static ZEND_FUNCTION(lz4_compress_frame) +{ + zval *data; + char *output; + int output_len; + long level = 0; + long max_block_size = 0; + long checksums = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "z|lll", &data, &level, + &max_block_size, &checksums) == FAILURE) { + RETURN_FALSE; + } + + if (Z_TYPE_P(data) != IS_STRING) { + zend_error(E_WARNING, + "lz4_compress_frame : expects parameter to be string."); + RETURN_FALSE; + } + + if (php_lz4_compress_frame(Z_STRVAL_P(data), Z_STRLEN_P(data), + &output, &output_len, + (int)level, + (int)max_block_size, + (int)checksums) == FAILURE) { + RETURN_FALSE; + } +#if ZEND_MODULE_API_NO >= 20141001 + RETVAL_STRINGL(output, output_len); +#else + RETVAL_STRINGL(output, output_len, 1); +#endif + + efree(output); +} + +static ZEND_FUNCTION(lz4_uncompress_frame) +{ + zval *data; + unsigned long int output_len; + char *output; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "z", &data) == FAILURE) { + RETURN_FALSE; + } + + if (Z_TYPE_P(data) != IS_STRING) { + zend_error(E_WARNING, + "lz4_uncompress_frame : expects parameter to be string."); + RETURN_FALSE; + } + + if (php_lz4_uncompress_frame(Z_STRVAL_P(data), Z_STRLEN_P(data), + &output, &output_len) == FAILURE) { + RETURN_FALSE; + } + +#if ZEND_MODULE_API_NO >= 20141001 + RETVAL_STRINGL(output, output_len); +#else + RETVAL_STRINGL(output, output_len, 1); +#endif + + free(output); +} + #if PHP_MAJOR_VERSION >= 7 && defined(HAVE_APCU_SUPPORT) static int APC_SERIALIZER_NAME(lz4)(APC_SERIALIZER_ARGS) { diff --git a/lz4.stub.php b/lz4.stub.php index 7e91613..94b8511 100644 --- a/lz4.stub.php +++ b/lz4.stub.php @@ -26,10 +26,55 @@ */ const LZ4_VERSION_NUMBER = UNKNOWN; + /** + * @var int + * @cvalue LZ4_CHECKSUM_FRAME + */ + const LZ4_CHECKSUM_FRAME = UNKNOWN; + + /** + * @var int + * @cvalue LZ4_CHECKSUM_BLOCK + */ + const LZ4_CHECKSUM_BLOCK = UNKNOWN; + + /** + * @var int + * @cvalue LZ4_BLOCK_SIZE_64KB + */ + const LZ4_BLOCK_SIZE_64KB = UNKNOWN; + + /** + * @var int + * @cvalue LZ4_BLOCK_SIZE_256KB + */ + const LZ4_BLOCK_SIZE_256KB = UNKNOWN; + + /** + * @var int + * @cvalue LZ4_BLOCK_SIZE_1MB + */ + const LZ4_BLOCK_SIZE_1MB = UNKNOWN; + + /** + * @var int + * @cvalue LZ4_BLOCK_SIZE_4MB + */ + const LZ4_BLOCK_SIZE_4MB = UNKNOWN; + function lz4_compress(string $data, int $level = 0, string $extra = NULL): string|false {} function lz4_uncompress(string $data, int $maxsize = -1, int $offset = -1): string|false {} + /** + * @param int $max_block_size any of the LZ4_BLOCK_SIZE_* constants + * @param int $checksums bitmask of the LZ4_CHECKSUM_* constants + */ + function lz4_compress_frame(string $data, int $level = 0, int $max_block_size = 0, int $checksums = 0): string|false {} + + + function lz4_uncompress_frame(string $data): string|false {} + } diff --git a/tests/frame_001.phpt b/tests/frame_001.phpt new file mode 100644 index 0000000..63b84cb --- /dev/null +++ b/tests/frame_001.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test lz4_compress_frame() function : basic functionality +--SKIPIF-- +--FILE-- + +===Done=== +--EXPECT-- +*** Testing lz4_compress_frame() : basic functionality *** +-- Compression -- +int(0) +-- Compression -- +string(100) "04224d1868401b00000000000000fa1b0000804120736d616c6c20737472696e6720746f20636f6d70726573730a00000000" +int(0) +===Done=== diff --git a/tests/frame_002.phpt b/tests/frame_002.phpt new file mode 100644 index 0000000..c5d5012 --- /dev/null +++ b/tests/frame_002.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test lz4_compress_frame() function : with all parameters +--SKIPIF-- +--FILE-- + +===Done=== +--EXPECT-- +*** Testing lz4_compress_frame() : with all parameters *** +-- Compression -- +int(0) +-- Compression -- +string(116) "04224d187c401b00000000000000981b0000804120736d616c6c20737472696e6720746f20636f6d70726573730a2f4da318000000002f4da318" +int(0) +===Done===