2525#include <zstd.h>
2626#endif
2727
28+ #ifdef HAVE_REDIS_LZ4
29+ #include <lz4.h>
30+ #include <lz4hc.h>
31+
32+ /* uint8_t crf + int length */
33+ #define REDIS_LZ4_HDR_SIZE (sizeof(uint8_t) + sizeof(int))
34+ #if defined(LZ4HC_CLEVEL_MAX )
35+ /* version >= 1.7.5 */
36+ #define REDIS_LZ4_MAX_CLEVEL LZ4HC_CLEVEL_MAX
37+
38+ #elif defined (LZ4HC_MAX_CLEVEL )
39+ /* version >= 1.7.3 */
40+ #define REDIS_LZ4_MAX_CLEVEL LZ4HC_MAX_CLEVEL
41+
42+ #else
43+ /* older versions */
44+ #define REDIS_LZ4_MAX_CLEVEL 12
45+ #endif
46+ #endif
47+
2848#include <zend_exceptions.h>
2949#include "php_redis.h"
3050#include "library.h"
@@ -2280,6 +2300,26 @@ PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock)
22802300 efree (redis_sock );
22812301}
22822302
2303+ #ifdef HAVE_REDIS_LZ4
2304+ /* Implementation of CRC8 for our LZ4 checksum value */
2305+ static uint8_t crc8 (unsigned char * input , size_t len ) {
2306+ size_t i ;
2307+ uint8_t crc = 0xFF ;
2308+
2309+ while (len -- ) {
2310+ crc ^= * input ++ ;
2311+ for (i = 0 ; i < 8 ; i ++ ) {
2312+ if (crc & 0x80 )
2313+ crc = (uint8_t )(crc << 1 ) ^ 0x31 ;
2314+ else
2315+ crc <<= 1 ;
2316+ }
2317+ }
2318+
2319+ return crc ;
2320+ }
2321+ #endif
2322+
22832323PHP_REDIS_API int
22842324redis_pack (RedisSock * redis_sock , zval * z , char * * val , size_t * val_len )
22852325{
@@ -2288,6 +2328,12 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len)
22882328 size_t len ;
22892329
22902330 valfree = redis_serialize (redis_sock , z , & buf , & len );
2331+ if (redis_sock -> compression == REDIS_COMPRESSION_NONE ) {
2332+ * val = buf ;
2333+ * val_len = len ;
2334+ return valfree ;
2335+ }
2336+
22912337 switch (redis_sock -> compression ) {
22922338 case REDIS_COMPRESSION_LZF :
22932339#ifdef HAVE_REDIS_LZF
@@ -2340,6 +2386,54 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len)
23402386 }
23412387 efree (data );
23422388 }
2389+ #endif
2390+ break ;
2391+ case REDIS_COMPRESSION_LZ4 :
2392+ #ifdef HAVE_REDIS_LZ4
2393+ {
2394+ /* Compressing empty data is pointless */
2395+ if (len < 1 )
2396+ break ;
2397+
2398+ /* Compressing more than INT_MAX bytes would require multiple blocks */
2399+ if (len > INT_MAX ) {
2400+ php_error_docref (NULL , E_WARNING ,
2401+ "LZ4: compressing > %d bytes not supported" , INT_MAX );
2402+ break ;
2403+ }
2404+
2405+ int old_len = len , lz4len , lz4bound ;
2406+ uint8_t crc = crc8 ((unsigned char * )& old_len , sizeof (old_len ));
2407+ char * lz4buf , * lz4pos ;
2408+
2409+ lz4bound = LZ4_compressBound (len );
2410+ lz4buf = emalloc (REDIS_LZ4_HDR_SIZE + lz4bound );
2411+ lz4pos = lz4buf ;
2412+
2413+ /* Copy and move past crc8 length checksum */
2414+ memcpy (lz4pos , & crc , sizeof (crc ));
2415+ lz4pos += sizeof (crc );
2416+
2417+ /* Copy and advance past length */
2418+ memcpy (lz4pos , & old_len , sizeof (old_len ));
2419+ lz4pos += sizeof (old_len );
2420+
2421+ if (redis_sock -> compression_level <= 0 || redis_sock -> compression_level > REDIS_LZ4_MAX_CLEVEL ) {
2422+ lz4len = LZ4_compress_default (buf , lz4pos , old_len , lz4bound );
2423+ } else {
2424+ lz4len = LZ4_compress_HC (buf , lz4pos , old_len , lz4bound , redis_sock -> compression_level );
2425+ }
2426+
2427+ if (lz4len <= 0 ) {
2428+ efree (lz4buf );
2429+ break ;
2430+ }
2431+
2432+ if (valfree ) efree (buf );
2433+ * val = lz4buf ;
2434+ * val_len = lz4len + REDIS_LZ4_HDR_SIZE ;
2435+ return 1 ;
2436+ }
23432437#endif
23442438 break ;
23452439 }
@@ -2359,9 +2453,12 @@ redis_unpack(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret)
23592453 int i ;
23602454 uint32_t res ;
23612455
2362- errno = E2BIG ;
2456+ if (val_len == 0 )
2457+ break ;
2458+
23632459 /* start from two-times bigger buffer and
23642460 * increase it exponentially if needed */
2461+ errno = E2BIG ;
23652462 for (i = 2 ; errno == E2BIG ; i *= 2 ) {
23662463 data = emalloc (i * val_len );
23672464 if ((res = lzf_decompress (val , val_len , data , i * val_len )) == 0 ) {
@@ -2397,6 +2494,45 @@ redis_unpack(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret)
23972494 return 1 ;
23982495 }
23992496 }
2497+ #endif
2498+ break ;
2499+ case REDIS_COMPRESSION_LZ4 :
2500+ #ifdef HAVE_REDIS_LZ4
2501+ {
2502+ char * data ;
2503+ int datalen ;
2504+ uint8_t lz4crc ;
2505+
2506+ /* We must have at least enough bytes for our header, and can't have more than
2507+ * INT_MAX + our header size. */
2508+ if (val_len < REDIS_LZ4_HDR_SIZE || val_len > INT_MAX + REDIS_LZ4_HDR_SIZE )
2509+ break ;
2510+
2511+ /* Operate on copies in case our CRC fails */
2512+ const char * copy = val ;
2513+ size_t copylen = val_len ;
2514+
2515+ /* Read in our header bytes */
2516+ memcpy (& lz4crc , copy , sizeof (uint8_t ));
2517+ copy += sizeof (uint8_t ); copylen -= sizeof (uint8_t );
2518+ memcpy (& datalen , copy , sizeof (int ));
2519+ copy += sizeof (int ); copylen -= sizeof (int );
2520+
2521+ /* Make sure our CRC matches (TODO: Maybe issue a docref error?) */
2522+ if (crc8 ((unsigned char * )& datalen , sizeof (datalen )) != lz4crc )
2523+ break ;
2524+
2525+ /* Finally attempt decompression */
2526+ data = emalloc (datalen );
2527+ if (LZ4_decompress_safe (copy , data , copylen , datalen ) > 0 ) {
2528+ if (redis_unserialize (redis_sock , data , datalen , z_ret ) == 0 ) {
2529+ ZVAL_STRINGL (z_ret , data , datalen );
2530+ }
2531+ efree (data );
2532+ return 1 ;
2533+ }
2534+ efree (data );
2535+ }
24002536#endif
24012537 break ;
24022538 }
0 commit comments