summaryrefslogtreecommitdiff
path: root/examples/redis-unstable/src/listpack.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/redis-unstable/src/listpack.c')
-rw-r--r--examples/redis-unstable/src/listpack.c3334
1 files changed, 0 insertions, 3334 deletions
diff --git a/examples/redis-unstable/src/listpack.c b/examples/redis-unstable/src/listpack.c
deleted file mode 100644
index ca51c21..0000000
--- a/examples/redis-unstable/src/listpack.c
+++ /dev/null
@@ -1,3334 +0,0 @@
-/* Listpack -- A lists of strings serialization format
- *
- * This file implements the specification you can find at:
- *
- * https://github.com/antirez/listpack
- *
- * Copyright (c) 2017-Present, Redis Ltd.
- * All rights reserved.
- *
- * Licensed under your choice of (a) the Redis Source Available License 2.0
- * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
- * GNU Affero General Public License v3 (AGPLv3).
- */
-
-#include <stdint.h>
-#include <limits.h>
-#include <sys/types.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include "listpack.h"
-#include "listpack_malloc.h"
-#include "redisassert.h"
-#include "util.h"
-
-#define LP_HDR_SIZE 6 /* 32 bit total len + 16 bit number of elements. */
-#define LP_HDR_NUMELE_UNKNOWN UINT16_MAX
-#define LP_MAX_INT_ENCODING_LEN 9
-#define LP_MAX_BACKLEN_SIZE 5
-#define LP_ENCODING_INT 0
-#define LP_ENCODING_STRING 1
-
-#define LP_ENCODING_7BIT_UINT 0
-#define LP_ENCODING_7BIT_UINT_MASK 0x80
-#define LP_ENCODING_IS_7BIT_UINT(byte) (((byte)&LP_ENCODING_7BIT_UINT_MASK)==LP_ENCODING_7BIT_UINT)
-#define LP_ENCODING_7BIT_UINT_ENTRY_SIZE 2
-
-#define LP_ENCODING_6BIT_STR 0x80
-#define LP_ENCODING_6BIT_STR_MASK 0xC0
-#define LP_ENCODING_IS_6BIT_STR(byte) (((byte)&LP_ENCODING_6BIT_STR_MASK)==LP_ENCODING_6BIT_STR)
-
-#define LP_ENCODING_13BIT_INT 0xC0
-#define LP_ENCODING_13BIT_INT_MASK 0xE0
-#define LP_ENCODING_IS_13BIT_INT(byte) (((byte)&LP_ENCODING_13BIT_INT_MASK)==LP_ENCODING_13BIT_INT)
-#define LP_ENCODING_13BIT_INT_ENTRY_SIZE 3
-
-#define LP_ENCODING_12BIT_STR 0xE0
-#define LP_ENCODING_12BIT_STR_MASK 0xF0
-#define LP_ENCODING_IS_12BIT_STR(byte) (((byte)&LP_ENCODING_12BIT_STR_MASK)==LP_ENCODING_12BIT_STR)
-
-#define LP_ENCODING_16BIT_INT 0xF1
-#define LP_ENCODING_16BIT_INT_MASK 0xFF
-#define LP_ENCODING_IS_16BIT_INT(byte) (((byte)&LP_ENCODING_16BIT_INT_MASK)==LP_ENCODING_16BIT_INT)
-#define LP_ENCODING_16BIT_INT_ENTRY_SIZE 4
-
-#define LP_ENCODING_24BIT_INT 0xF2
-#define LP_ENCODING_24BIT_INT_MASK 0xFF
-#define LP_ENCODING_IS_24BIT_INT(byte) (((byte)&LP_ENCODING_24BIT_INT_MASK)==LP_ENCODING_24BIT_INT)
-#define LP_ENCODING_24BIT_INT_ENTRY_SIZE 5
-
-#define LP_ENCODING_32BIT_INT 0xF3
-#define LP_ENCODING_32BIT_INT_MASK 0xFF
-#define LP_ENCODING_IS_32BIT_INT(byte) (((byte)&LP_ENCODING_32BIT_INT_MASK)==LP_ENCODING_32BIT_INT)
-#define LP_ENCODING_32BIT_INT_ENTRY_SIZE 6
-
-#define LP_ENCODING_64BIT_INT 0xF4
-#define LP_ENCODING_64BIT_INT_MASK 0xFF
-#define LP_ENCODING_IS_64BIT_INT(byte) (((byte)&LP_ENCODING_64BIT_INT_MASK)==LP_ENCODING_64BIT_INT)
-#define LP_ENCODING_64BIT_INT_ENTRY_SIZE 10
-
-#define LP_ENCODING_32BIT_STR 0xF0
-#define LP_ENCODING_32BIT_STR_MASK 0xFF
-#define LP_ENCODING_IS_32BIT_STR(byte) (((byte)&LP_ENCODING_32BIT_STR_MASK)==LP_ENCODING_32BIT_STR)
-
-#define LP_EOF 0xFF
-
-#define LP_ENCODING_6BIT_STR_LEN(p) ((p)[0] & 0x3F)
-#define LP_ENCODING_12BIT_STR_LEN(p) ((((p)[0] & 0xF) << 8) | (p)[1])
-#define LP_ENCODING_32BIT_STR_LEN(p) (((uint32_t)(p)[1]<<0) | \
- ((uint32_t)(p)[2]<<8) | \
- ((uint32_t)(p)[3]<<16) | \
- ((uint32_t)(p)[4]<<24))
-
-#define lpGetTotalBytes(p) (((uint32_t)(p)[0]<<0) | \
- ((uint32_t)(p)[1]<<8) | \
- ((uint32_t)(p)[2]<<16) | \
- ((uint32_t)(p)[3]<<24))
-
-#define lpGetNumElements(p) (((uint32_t)(p)[4]<<0) | \
- ((uint32_t)(p)[5]<<8))
-#define lpSetTotalBytes(p,v) do { \
- (p)[0] = (v)&0xff; \
- (p)[1] = ((v)>>8)&0xff; \
- (p)[2] = ((v)>>16)&0xff; \
- (p)[3] = ((v)>>24)&0xff; \
-} while(0)
-
-#define lpSetNumElements(p,v) do { \
- (p)[4] = (v)&0xff; \
- (p)[5] = ((v)>>8)&0xff; \
-} while(0)
-
-/* Validates that 'p' is not outside the listpack.
- * All function that return a pointer to an element in the listpack will assert
- * that this element is valid, so it can be freely used.
- * Generally functions such lpNext and lpDelete assume the input pointer is
- * already validated (since it's the return value of another function). */
-#define ASSERT_INTEGRITY(lp, p) do { \
- assert((p) >= (lp)+LP_HDR_SIZE && (p) < (lp)+lpGetTotalBytes((lp))); \
-} while (0)
-
-/* Similar to the above, but validates the entire element length rather than just
- * it's pointer. */
-#define ASSERT_INTEGRITY_LEN(lp, p, len) do { \
- assert((p) >= (lp)+LP_HDR_SIZE && (p)+(len) < (lp)+lpGetTotalBytes((lp))); \
-} while (0)
-
-static inline void lpAssertValidEntry(unsigned char* lp, size_t lpbytes, unsigned char *p);
-
-/* Don't let listpacks grow over 1GB in any case, don't wanna risk overflow in
- * Total Bytes header field */
-#define LISTPACK_MAX_SAFETY_SIZE (1<<30)
-int lpSafeToAdd(unsigned char* lp, size_t add) {
- size_t len = lp? lpGetTotalBytes(lp): 0;
- if (len + add > LISTPACK_MAX_SAFETY_SIZE)
- return 0;
- return 1;
-}
-
-/* Convert a string into a signed 64 bit integer.
- * The function returns 1 if the string could be parsed into a (non-overflowing)
- * signed 64 bit int, 0 otherwise. The 'value' will be set to the parsed value
- * when the function returns success.
- *
- * Note that this function demands that the string strictly represents
- * a int64 value: no spaces or other characters before or after the string
- * representing the number are accepted, nor zeroes at the start if not
- * for the string "0" representing the zero number.
- *
- * Because of its strictness, it is safe to use this function to check if
- * you can convert a string into a long long, and obtain back the string
- * from the number without any loss in the string representation. *
- *
- * -----------------------------------------------------------------------------
- *
- * Credits: this function was adapted from the Redis source code, file
- * "utils.c", function string2ll(), and is copyright:
- *
- * Copyright(C) 2011, Pieter Noordhuis
- * Copyright(C) 2011-current, Redis Ltd.
- *
- * The function is released under the BSD 3-clause license.
- */
-int lpStringToInt64(const char *s, unsigned long slen, int64_t *value) {
- const char *p = s;
- unsigned long plen = 0;
- int negative = 0;
- uint64_t v;
-
- /* Abort if length indicates this cannot possibly be an int */
- if (slen == 0 || slen >= LONG_STR_SIZE)
- return 0;
-
- /* Special case: first and only digit is 0. */
- if (slen == 1 && p[0] == '0') {
- if (value != NULL) *value = 0;
- return 1;
- }
-
- if (p[0] == '-') {
- negative = 1;
- p++; plen++;
-
- /* Abort on only a negative sign. */
- if (plen == slen)
- return 0;
- }
-
- /* First digit should be 1-9, otherwise the string should just be 0. */
- if (p[0] >= '1' && p[0] <= '9') {
- v = p[0]-'0';
- p++; plen++;
- } else {
- return 0;
- }
-
- while (plen < slen && p[0] >= '0' && p[0] <= '9') {
- if (v > (UINT64_MAX / 10)) /* Overflow. */
- return 0;
- v *= 10;
-
- if (v > (UINT64_MAX - (p[0]-'0'))) /* Overflow. */
- return 0;
- v += p[0]-'0';
-
- p++; plen++;
- }
-
- /* Return if not all bytes were used. */
- if (plen < slen)
- return 0;
-
- if (negative) {
- if (v > ((uint64_t)(-(INT64_MIN+1))+1)) /* Overflow. */
- return 0;
- if (value != NULL) *value = -v;
- } else {
- if (v > INT64_MAX) /* Overflow. */
- return 0;
- if (value != NULL) *value = v;
- }
- return 1;
-}
-
-/* Create a new, empty listpack.
- * On success the new listpack is returned, otherwise an error is returned.
- * Pre-allocate at least `capacity` bytes of memory,
- * over-allocated memory can be shrunk by `lpShrinkToFit`.
- * */
-unsigned char *lpNew(size_t capacity) {
- unsigned char *lp = lp_malloc(capacity > LP_HDR_SIZE+1 ? capacity : LP_HDR_SIZE+1);
- if (lp == NULL) return NULL;
- lpSetTotalBytes(lp,LP_HDR_SIZE+1);
- lpSetNumElements(lp,0);
- lp[LP_HDR_SIZE] = LP_EOF;
- return lp;
-}
-
-/* Free the specified listpack. */
-void lpFree(unsigned char *lp) {
- lp_free(lp);
-}
-
-/* Shrink the memory to fit. */
-unsigned char* lpShrinkToFit(unsigned char *lp) {
- size_t size = lpGetTotalBytes(lp);
- if (size < lp_malloc_size(lp)) {
- return lp_realloc(lp, size);
- } else {
- return lp;
- }
-}
-
-/* Stores the integer encoded representation of 'v' in the 'intenc' buffer. */
-static inline void lpEncodeIntegerGetType(int64_t v, unsigned char *intenc, uint64_t *enclen) {
- if (v >= 0 && v <= 127) {
- /* Single byte 0-127 integer. */
- if (intenc != NULL) intenc[0] = v;
- if (enclen != NULL) *enclen = 1;
- } else if (v >= -4096 && v <= 4095) {
- /* 13 bit integer. */
- if (v < 0) v = ((int64_t)1<<13)+v;
- if (intenc != NULL) {
- intenc[0] = (v>>8)|LP_ENCODING_13BIT_INT;
- intenc[1] = v&0xff;
- }
- if (enclen != NULL) *enclen = 2;
- } else if (v >= -32768 && v <= 32767) {
- /* 16 bit integer. */
- if (v < 0) v = ((int64_t)1<<16)+v;
- if (intenc != NULL) {
- intenc[0] = LP_ENCODING_16BIT_INT;
- intenc[1] = v&0xff;
- intenc[2] = v>>8;
- }
- if (enclen != NULL) *enclen = 3;
- } else if (v >= -8388608 && v <= 8388607) {
- /* 24 bit integer. */
- if (v < 0) v = ((int64_t)1<<24)+v;
- if (intenc != NULL) {
- intenc[0] = LP_ENCODING_24BIT_INT;
- intenc[1] = v&0xff;
- intenc[2] = (v>>8)&0xff;
- intenc[3] = v>>16;
- }
- if (enclen != NULL) *enclen = 4;
- } else if (v >= -2147483648 && v <= 2147483647) {
- /* 32 bit integer. */
- if (v < 0) v = ((int64_t)1<<32)+v;
- if (intenc != NULL) {
- intenc[0] = LP_ENCODING_32BIT_INT;
- intenc[1] = v&0xff;
- intenc[2] = (v>>8)&0xff;
- intenc[3] = (v>>16)&0xff;
- intenc[4] = v>>24;
- }
- if (enclen != NULL) *enclen = 5;
- } else {
- /* 64 bit integer. */
- uint64_t uv = v;
- if (intenc != NULL) {
- intenc[0] = LP_ENCODING_64BIT_INT;
- intenc[1] = uv&0xff;
- intenc[2] = (uv>>8)&0xff;
- intenc[3] = (uv>>16)&0xff;
- intenc[4] = (uv>>24)&0xff;
- intenc[5] = (uv>>32)&0xff;
- intenc[6] = (uv>>40)&0xff;
- intenc[7] = (uv>>48)&0xff;
- intenc[8] = uv>>56;
- }
- if (enclen != NULL) *enclen = 9;
- }
-}
-
-/* Given an element 'ele' of size 'size', determine if the element can be
- * represented inside the listpack encoded as integer, and returns
- * LP_ENCODING_INT if so. Otherwise returns LP_ENCODING_STR if no integer
- * encoding is possible.
- *
- * If the LP_ENCODING_INT is returned, the function stores the integer encoded
- * representation of the element in the 'intenc' buffer.
- *
- * Regardless of the returned encoding, 'enclen' is populated by reference to
- * the number of bytes that the string or integer encoded element will require
- * in order to be represented. */
-static inline int lpEncodeGetType(unsigned char *ele, uint32_t size, unsigned char *intenc, uint64_t *enclen) {
- int64_t v;
- if (lpStringToInt64((const char*)ele, size, &v)) {
- lpEncodeIntegerGetType(v, intenc, enclen);
- return LP_ENCODING_INT;
- } else {
- if (size < 64) *enclen = 1+size;
- else if (size < 4096) *enclen = 2+size;
- else *enclen = 5+(uint64_t)size;
- return LP_ENCODING_STRING;
- }
-}
-
-/* Store a reverse-encoded variable length field, representing the length
- * of the previous element of size 'l', in the target buffer 'buf'.
- * The function returns the number of bytes used to encode it, from
- * 1 to 5. If 'buf' is NULL the function just returns the number of bytes
- * needed in order to encode the backlen. */
-static inline unsigned long lpEncodeBacklen(unsigned char *buf, uint64_t l) {
- if (l <= 127) {
- if (buf) buf[0] = l;
- return 1;
- } else if (l < 16383) {
- if (buf) {
- buf[0] = l>>7;
- buf[1] = (l&127)|128;
- }
- return 2;
- } else if (l < 2097151) {
- if (buf) {
- buf[0] = l>>14;
- buf[1] = ((l>>7)&127)|128;
- buf[2] = (l&127)|128;
- }
- return 3;
- } else if (l < 268435455) {
- if (buf) {
- buf[0] = l>>21;
- buf[1] = ((l>>14)&127)|128;
- buf[2] = ((l>>7)&127)|128;
- buf[3] = (l&127)|128;
- }
- return 4;
- } else {
- if (buf) {
- buf[0] = l>>28;
- buf[1] = ((l>>21)&127)|128;
- buf[2] = ((l>>14)&127)|128;
- buf[3] = ((l>>7)&127)|128;
- buf[4] = (l&127)|128;
- }
- return 5;
- }
-}
-
-/* Calculate the number of bytes required to reverse-encode a variable length
- * field representing the length of the previous element of size 'l', ranging
- * from 1 to 5. */
-static inline unsigned long lpEncodeBacklenBytes(uint64_t l) {
- if (l <= 127) {
- return 1;
- } else if (l < 16383) {
- return 2;
- } else if (l < 2097151) {
- return 3;
- } else if (l < 268435455) {
- return 4;
- } else {
- return 5;
- }
-}
-
-/* Decode the backlen and returns it. If the encoding looks invalid (more than
- * 5 bytes are used), UINT64_MAX is returned to report the problem.
- *
- * Optimized for the common case: most backlen values fit in one or two bytes
- * due to listpack size limits. This version avoids a loop and pointer
- * mutation, reducing overhead in hot paths while keeping the same encoding
- * semantics.
- *
- * Note: the caller guarantees that up to 5 bytes preceding 'p' are readable,
- * as ensured by listpack invariants. */
-static inline uint64_t lpDecodeBacklen(unsigned char *p) {
- uint64_t val;
-
- /* Fast path: single byte (most common for small entries <= 127 bytes) */
- if (likely(!(p[0] & 128))) {
- return p[0] & 127;
- }
-
- /* Two bytes */
- val = (uint64_t)(p[0] & 127);
- if (!(p[-1] & 128)) {
- return val | ((uint64_t)(p[-1] & 127) << 7);
- }
-
- /* Three bytes */
- val |= (uint64_t)(p[-1] & 127) << 7;
- if (!(p[-2] & 128)) {
- return val | ((uint64_t)(p[-2] & 127) << 14);
- }
-
- /* Four bytes */
- val |= (uint64_t)(p[-2] & 127) << 14;
- if (!(p[-3] & 128)) {
- return val | ((uint64_t)(p[-3] & 127) << 21);
- }
-
- /* Five bytes */
- val |= (uint64_t)(p[-3] & 127) << 21;
- if (!(p[-4] & 128)) {
- return val | ((uint64_t)(p[-4] & 127) << 28);
- }
-
- /* Invalid: more than 5 bytes */
- return UINT64_MAX;
-}
-
-/* Encode the string element pointed by 's' of size 'len' in the target
- * buffer 's'. The function should be called with 'buf' having always enough
- * space for encoding the string. This is done by calling lpEncodeGetType()
- * before calling this function. */
-static inline void lpEncodeString(unsigned char *buf, unsigned char *s, uint32_t len) {
- if (len < 64) {
- buf[0] = len | LP_ENCODING_6BIT_STR;
- memcpy(buf+1,s,len);
- } else if (len < 4096) {
- buf[0] = (len >> 8) | LP_ENCODING_12BIT_STR;
- buf[1] = len & 0xff;
- memcpy(buf+2,s,len);
- } else {
- buf[0] = LP_ENCODING_32BIT_STR;
- buf[1] = len & 0xff;
- buf[2] = (len >> 8) & 0xff;
- buf[3] = (len >> 16) & 0xff;
- buf[4] = (len >> 24) & 0xff;
- memcpy(buf+5,s,len);
- }
-}
-
-/* Return the encoded length of the listpack element pointed by 'p'.
- * This includes the encoding byte, length bytes, and the element data itself.
- * If the element encoding is wrong then 0 is returned.
- * Note that this method may access additional bytes (in case of 12 and 32 bit
- * str), so should only be called when we know 'p' was already validated by
- * lpCurrentEncodedSizeBytes or ASSERT_INTEGRITY_LEN (possibly since 'p' is
- * a return value of another function that validated its return. */
-static inline uint32_t lpCurrentEncodedSizeUnsafe(unsigned char *p) {
- if (LP_ENCODING_IS_7BIT_UINT(p[0])) return 1;
- if (LP_ENCODING_IS_6BIT_STR(p[0])) return 1+LP_ENCODING_6BIT_STR_LEN(p);
- if (LP_ENCODING_IS_13BIT_INT(p[0])) return 2;
- if (LP_ENCODING_IS_16BIT_INT(p[0])) return 3;
- if (LP_ENCODING_IS_24BIT_INT(p[0])) return 4;
- if (LP_ENCODING_IS_32BIT_INT(p[0])) return 5;
- if (LP_ENCODING_IS_64BIT_INT(p[0])) return 9;
- if (LP_ENCODING_IS_12BIT_STR(p[0])) return 2+LP_ENCODING_12BIT_STR_LEN(p);
- if (LP_ENCODING_IS_32BIT_STR(p[0])) return 5+LP_ENCODING_32BIT_STR_LEN(p);
- if (p[0] == LP_EOF) return 1;
- return 0;
-}
-
-/* Return bytes needed to encode the length of the listpack element pointed by 'p'.
- * This includes just the encoding byte, and the bytes needed to encode the length
- * of the element (excluding the element data itself)
- * If the element encoding is wrong then 0 is returned. */
-static inline uint32_t lpCurrentEncodedSizeBytes(const unsigned char encoding) {
- if (LP_ENCODING_IS_7BIT_UINT(encoding)) return 1;
- if (LP_ENCODING_IS_6BIT_STR(encoding)) return 1;
- if (LP_ENCODING_IS_13BIT_INT(encoding)) return 1;
- if (LP_ENCODING_IS_16BIT_INT(encoding)) return 1;
- if (LP_ENCODING_IS_24BIT_INT(encoding)) return 1;
- if (LP_ENCODING_IS_32BIT_INT(encoding)) return 1;
- if (LP_ENCODING_IS_64BIT_INT(encoding)) return 1;
- if (LP_ENCODING_IS_12BIT_STR(encoding)) return 2;
- if (LP_ENCODING_IS_32BIT_STR(encoding)) return 5;
- if (encoding == LP_EOF) return 1;
- return 0;
-}
-
-/* Skip the current entry returning the next. It is invalid to call this
- * function if the current element is the EOF element at the end of the
- * listpack, however, while this function is used to implement lpNext(),
- * it does not return NULL when the EOF element is encountered. */
-static inline unsigned char *lpSkip(unsigned char *p) {
- unsigned long entrylen = lpCurrentEncodedSizeUnsafe(p);
- entrylen += lpEncodeBacklenBytes(entrylen);
- p += entrylen;
- return p;
-}
-
-/* This is similar to lpNext() but avoids the inner call to lpBytes when you already know the listpack size. */
-unsigned char *lpNextWithBytes(unsigned char *lp, unsigned char *p, const size_t lpbytes) {
- assert(p);
- p = lpSkip(p);
- if (p[0] == LP_EOF) return NULL;
- lpAssertValidEntry(lp, lpbytes, p);
- return p;
-}
-
-/* If 'p' points to an element of the listpack, calling lpNext() will return
- * the pointer to the next element (the one on the right), or NULL if 'p'
- * already pointed to the last element of the listpack. */
-unsigned char *lpNext(unsigned char *lp, unsigned char *p) {
- assert(p);
- p = lpSkip(p);
- if (p[0] == LP_EOF) return NULL;
- lpAssertValidEntry(lp, lpBytes(lp), p);
- return p;
-}
-
-/* If 'p' points to an element of the listpack, calling lpPrev() will return
- * the pointer to the previous element (the one on the left), or NULL if 'p'
- * already pointed to the first element of the listpack. */
-unsigned char *lpPrev(unsigned char *lp, unsigned char *p) {
- assert(p);
- if (p-lp == LP_HDR_SIZE) return NULL;
- p--; /* Seek the first backlen byte of the last element. */
- uint64_t prevlen = lpDecodeBacklen(p);
- prevlen += lpEncodeBacklenBytes(prevlen);
- p -= prevlen-1; /* Seek the first byte of the previous entry. */
- lpAssertValidEntry(lp, lpBytes(lp), p);
- return p;
-}
-
-/* Return a pointer to the first element of the listpack, or NULL if the
- * listpack has no elements. */
-unsigned char *lpFirst(unsigned char *lp) {
- unsigned char *p = lp + LP_HDR_SIZE; /* Skip the header. */
- if (p[0] == LP_EOF) return NULL;
- lpAssertValidEntry(lp, lpBytes(lp), p);
- return p;
-}
-
-/* Return a pointer to the last element of the listpack, or NULL if the
- * listpack has no elements. */
-unsigned char *lpLast(unsigned char *lp) {
- unsigned char *p = lp+lpGetTotalBytes(lp)-1; /* Seek EOF element. */
- return lpPrev(lp,p); /* Will return NULL if EOF is the only element. */
-}
-
-/* Return the number of elements inside the listpack. This function attempts
- * to use the cached value when within range, otherwise a full scan is
- * needed. As a side effect of calling this function, the listpack header
- * could be modified, because if the count is found to be already within
- * the 'numele' header field range, the new value is set. */
-unsigned long lpLength(unsigned char *lp) {
- uint32_t numele = lpGetNumElements(lp);
- if (numele != LP_HDR_NUMELE_UNKNOWN) return numele;
-
- /* Too many elements inside the listpack. We need to scan in order
- * to get the total number. */
- uint32_t count = 0;
- unsigned char *p = lpFirst(lp);
- while(p) {
- count++;
- p = lpNext(lp,p);
- }
-
- /* If the count is again within range of the header numele field,
- * set it. */
- if (count < LP_HDR_NUMELE_UNKNOWN) lpSetNumElements(lp,count);
- return count;
-}
-
-/* Return the listpack element pointed by 'p'.
- *
- * The function changes behavior depending on the passed 'intbuf' value.
- * Specifically, if 'intbuf' is NULL:
- *
- * If the element is internally encoded as an integer, the function returns
- * NULL and populates the integer value by reference in 'count'. Otherwise if
- * the element is encoded as a string a pointer to the string (pointing inside
- * the listpack itself) is returned, and 'count' is set to the length of the
- * string.
- *
- * If instead 'intbuf' points to a buffer passed by the caller, that must be
- * at least LP_INTBUF_SIZE bytes, the function always returns the element as
- * it was a string (returning the pointer to the string and setting the
- * 'count' argument to the string length by reference). However if the element
- * is encoded as an integer, the 'intbuf' buffer is used in order to store
- * the string representation.
- *
- * The user should use one or the other form depending on what the value will
- * be used for. If there is immediate usage for an integer value returned
- * by the function, than to pass a buffer (and convert it back to a number)
- * is of course useless.
- *
- * If 'entry_size' is not NULL, *entry_size is set to the entry length of the
- * listpack element pointed by 'p'. This includes the encoding bytes, length
- * bytes, the element data itself, and the backlen bytes.
- *
- * If the function is called against a badly encoded ziplist, so that there
- * is no valid way to parse it, the function returns like if there was an
- * integer encoded with value 12345678900000000 + <unrecognized byte>, this may
- * be an hint to understand that something is wrong. To crash in this case is
- * not sensible because of the different requirements of the application using
- * this lib.
- *
- * Similarly, there is no error returned since the listpack normally can be
- * assumed to be valid, so that would be a very high API cost. */
-static inline unsigned char *lpGetWithSize(unsigned char *p, int64_t *count, unsigned char *intbuf, uint64_t *entry_size) {
- int64_t val;
- uint64_t uval, negstart, negmax;
-
- assert(p); /* assertion for valgrind (avoid NPD) */
- if (LP_ENCODING_IS_7BIT_UINT(p[0])) {
- negstart = UINT64_MAX; /* 7 bit ints are always positive. */
- negmax = 0;
- uval = p[0] & 0x7f;
- if (entry_size) *entry_size = LP_ENCODING_7BIT_UINT_ENTRY_SIZE;
- } else if (LP_ENCODING_IS_6BIT_STR(p[0])) {
- *count = LP_ENCODING_6BIT_STR_LEN(p);
- if (entry_size) *entry_size = 1 + *count + lpEncodeBacklenBytes(*count + 1);
- return p+1;
- } else if (LP_ENCODING_IS_13BIT_INT(p[0])) {
- uval = ((p[0]&0x1f)<<8) | p[1];
- negstart = (uint64_t)1<<12;
- negmax = 8191;
- if (entry_size) *entry_size = LP_ENCODING_13BIT_INT_ENTRY_SIZE;
- } else if (LP_ENCODING_IS_16BIT_INT(p[0])) {
- uval = (uint64_t)p[1] |
- (uint64_t)p[2]<<8;
- negstart = (uint64_t)1<<15;
- negmax = UINT16_MAX;
- if (entry_size) *entry_size = LP_ENCODING_16BIT_INT_ENTRY_SIZE;
- } else if (LP_ENCODING_IS_24BIT_INT(p[0])) {
- uval = (uint64_t)p[1] |
- (uint64_t)p[2]<<8 |
- (uint64_t)p[3]<<16;
- negstart = (uint64_t)1<<23;
- negmax = UINT32_MAX>>8;
- if (entry_size) *entry_size = LP_ENCODING_24BIT_INT_ENTRY_SIZE;
- } else if (LP_ENCODING_IS_32BIT_INT(p[0])) {
- uval = (uint64_t)p[1] |
- (uint64_t)p[2]<<8 |
- (uint64_t)p[3]<<16 |
- (uint64_t)p[4]<<24;
- negstart = (uint64_t)1<<31;
- negmax = UINT32_MAX;
- if (entry_size) *entry_size = LP_ENCODING_32BIT_INT_ENTRY_SIZE;
- } else if (LP_ENCODING_IS_64BIT_INT(p[0])) {
- uval = (uint64_t)p[1] |
- (uint64_t)p[2]<<8 |
- (uint64_t)p[3]<<16 |
- (uint64_t)p[4]<<24 |
- (uint64_t)p[5]<<32 |
- (uint64_t)p[6]<<40 |
- (uint64_t)p[7]<<48 |
- (uint64_t)p[8]<<56;
- negstart = (uint64_t)1<<63;
- negmax = UINT64_MAX;
- if (entry_size) *entry_size = LP_ENCODING_64BIT_INT_ENTRY_SIZE;
- } else if (LP_ENCODING_IS_12BIT_STR(p[0])) {
- *count = LP_ENCODING_12BIT_STR_LEN(p);
- if (entry_size) *entry_size = 2 + *count + lpEncodeBacklenBytes(*count + 2);
- return p+2;
- } else if (LP_ENCODING_IS_32BIT_STR(p[0])) {
- *count = LP_ENCODING_32BIT_STR_LEN(p);
- if (entry_size) *entry_size = 5 + *count + lpEncodeBacklenBytes(*count + 5);
- return p+5;
- } else {
- uval = 12345678900000000ULL + p[0];
- negstart = UINT64_MAX;
- negmax = 0;
- }
-
- /* We reach this code path only for integer encodings.
- * Convert the unsigned value to the signed one using two's complement
- * rule. */
- if (uval >= negstart) {
- /* This three steps conversion should avoid undefined behaviors
- * in the unsigned -> signed conversion. */
- uval = negmax-uval;
- val = uval;
- val = -val-1;
- } else {
- val = uval;
- }
-
- /* Return the string representation of the integer or the value itself
- * depending on intbuf being NULL or not. */
- if (intbuf) {
- *count = ll2string((char*)intbuf,LP_INTBUF_SIZE,(long long)val);
- return intbuf;
- } else {
- *count = val;
- return NULL;
- }
-}
-
-/* Return the listpack element pointed by 'p'.
- *
- * The function has the same behaviour as lpGetWithSize when 'entry_size' is NULL,
- * but avoids a lot of unecesarry branching performance penalties. */
-static inline unsigned char *lpGetWithBuf(unsigned char *p, int64_t *count, unsigned char *intbuf) {
- int64_t val;
- uint64_t uval, negstart, negmax;
- assert(p); /* assertion for valgrind (avoid NPD) */
- const unsigned char encoding = p[0];
-
- /* string encoding */
- if (LP_ENCODING_IS_6BIT_STR(encoding)) {
- *count = LP_ENCODING_6BIT_STR_LEN(p);
- return p+1;
- }
- if (LP_ENCODING_IS_12BIT_STR(encoding)) {
- *count = LP_ENCODING_12BIT_STR_LEN(p);
- return p+2;
- }
- if (LP_ENCODING_IS_32BIT_STR(encoding)) {
- *count = LP_ENCODING_32BIT_STR_LEN(p);
- return p+5;
- }
- /* int encoding */
- if (LP_ENCODING_IS_7BIT_UINT(encoding)) {
- negstart = UINT64_MAX; /* 7 bit ints are always positive. */
- negmax = 0;
- uval = encoding & 0x7f;
- } else if (LP_ENCODING_IS_13BIT_INT(encoding)) {
- uval = ((encoding&0x1f)<<8) | p[1];
- negstart = (uint64_t)1<<12;
- negmax = 8191;
- } else if (LP_ENCODING_IS_16BIT_INT(encoding)) {
- uval = (uint64_t)p[1] |
- (uint64_t)p[2]<<8;
- negstart = (uint64_t)1<<15;
- negmax = UINT16_MAX;
- } else if (LP_ENCODING_IS_24BIT_INT(encoding)) {
- uval = (uint64_t)p[1] |
- (uint64_t)p[2]<<8 |
- (uint64_t)p[3]<<16;
- negstart = (uint64_t)1<<23;
- negmax = UINT32_MAX>>8;
- } else if (LP_ENCODING_IS_32BIT_INT(encoding)) {
- uval = (uint64_t)p[1] |
- (uint64_t)p[2]<<8 |
- (uint64_t)p[3]<<16 |
- (uint64_t)p[4]<<24;
- negstart = (uint64_t)1<<31;
- negmax = UINT32_MAX;
- } else if (LP_ENCODING_IS_64BIT_INT(encoding)) {
- uval = (uint64_t)p[1] |
- (uint64_t)p[2]<<8 |
- (uint64_t)p[3]<<16 |
- (uint64_t)p[4]<<24 |
- (uint64_t)p[5]<<32 |
- (uint64_t)p[6]<<40 |
- (uint64_t)p[7]<<48 |
- (uint64_t)p[8]<<56;
- negstart = (uint64_t)1<<63;
- negmax = UINT64_MAX;
- } else {
- uval = 12345678900000000ULL + encoding;
- negstart = UINT64_MAX;
- negmax = 0;
- }
-
- /* We reach this code path only for integer encodings.
- * Convert the unsigned value to the signed one using two's complement
- * rule. */
- if (uval >= negstart) {
- /* This three steps conversion should avoid undefined behaviors
- * in the unsigned -> signed conversion. */
- uval = negmax-uval;
- val = uval;
- val = -val-1;
- } else {
- val = uval;
- }
-
- /* Return the string representation of the integer or the value itself
- * depending on intbuf being NULL or not. */
- if (intbuf) {
- *count = ll2string((char*)intbuf,LP_INTBUF_SIZE,(long long)val);
- return intbuf;
- } else {
- *count = val;
- return NULL;
- }
-}
-
-unsigned char *lpGet(unsigned char *p, int64_t *count, unsigned char *intbuf) {
- return lpGetWithBuf(p, count, intbuf);
-}
-
-/* This is just a wrapper to lpGet() that is able to get entry value directly.
- * When the function returns NULL, it populates the integer value by reference in 'lval'.
- * Otherwise if the element is encoded as a string a pointer to the string (pointing
- * inside the listpack itself) is returned, and 'slen' is set to the length of the
- * string. */
-unsigned char *lpGetValue(unsigned char *p, unsigned int *slen, long long *lval) {
- unsigned char *vstr;
- int64_t ele_len;
-
- vstr = lpGet(p, &ele_len, NULL);
- if (vstr) {
- *slen = ele_len;
- } else {
- *lval = ele_len;
- }
- return vstr;
-}
-
-/* This is just a wrapper to lpGet() that is able to get an integer from an entry directly.
- * Returns 1 and stores the integer in 'lval' if the entry is an integer.
- * Returns 0 if the entry is a string. */
-int lpGetIntegerValue(unsigned char *p, long long *lval) {
- int64_t ele_len;
- if (!lpGet(p, &ele_len, NULL)) {
- *lval = ele_len;
- return 1;
- }
- return 0;
-}
-
-/* Find pointer to the entry with a comparator callback.
- *
- * 'cmp' is a comparator callback. If it returns zero, current entry pointer
- * will be returned. 'user' is passed to this callback.
- * Skip 'skip' entries between every comparison.
- * Returns NULL when the field could not be found. */
-static inline unsigned char *lpFindCbInternal(unsigned char *lp, unsigned char *p,
- void *user, lpCmp cmp, unsigned int skip)
-{
- int skipcnt = 0;
- unsigned char *value;
- int64_t ll;
- uint64_t entry_size = 123456789; /* initialized to avoid warning. */
- uint32_t lp_bytes = lpBytes(lp);
-
- if (!p)
- p = lpFirst(lp);
-
- while (p) {
- if (skipcnt == 0) {
- value = lpGetWithSize(p, &ll, NULL, &entry_size);
- if (value) {
- /* check the value doesn't reach outside the listpack before accessing it */
- assert(p >= lp + LP_HDR_SIZE && p + entry_size < lp + lp_bytes);
- }
-
- if (unlikely(cmp(lp, p, user, value, ll) == 0))
- return p;
-
- /* Reset skip count */
- skipcnt = skip;
- p += entry_size;
- } else {
- /* Skip entry */
- skipcnt--;
-
- /* Move to next entry, avoid use `lpNext` due to `lpAssertValidEntry` in
- * `lpNext` will call `lpBytes`, will cause performance degradation */
- p = lpSkip(p);
- }
-
- /* The next call to lpGetWithSize could read at most 8 bytes past `p`
- * We use the slower validation call only when necessary. */
- if (p + 8 >= lp + lp_bytes)
- lpAssertValidEntry(lp, lp_bytes, p);
- else
- assert(p >= lp + LP_HDR_SIZE && p < lp + lp_bytes);
- if (p[0] == LP_EOF) break;
- }
-
- return NULL;
-}
-
-unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
- void *user, lpCmp cmp, unsigned int skip)
-{
- return lpFindCbInternal(lp, p, user, cmp, skip);
-}
-
-struct lpFindArg {
- unsigned char *s; /* Item to search */
- uint32_t slen; /* Item len */
- int vencoding;
- int64_t vll;
-};
-
-/* Comparator function to find item */
-static inline int lpFindCmp(const unsigned char *lp, unsigned char *p,
- void *user, unsigned char *s, long long slen) {
- (void) lp;
- (void) p;
- struct lpFindArg *arg = user;
-
- if (s) {
- if (slen == arg->slen && memcmp(arg->s, s, slen) == 0) {
- return 0;
- }
- } else {
- /* Find out if the searched field can be encoded. Note that
- * we do it only the first time, once done vencoding is set
- * to non-zero and vll is set to the integer value. */
- if (arg->vencoding == 0) {
- /* If the entry can be encoded as integer we set it to
- * 1, else set it to UCHAR_MAX, so that we don't retry
- * again the next time. */
- if (arg->slen >= 32 || arg->slen == 0 || !lpStringToInt64((const char*)arg->s, arg->slen, &arg->vll)) {
- arg->vencoding = UCHAR_MAX;
- } else {
- arg->vencoding = 1;
- }
- }
-
- /* Compare current entry with specified entry, do it only
- * if vencoding != UCHAR_MAX because if there is no encoding
- * possible for the field it can't be a valid integer. */
- if (arg->vencoding != UCHAR_MAX && slen == arg->vll) {
- return 0;
- }
- }
-
- return 1;
-}
-
-/* Find pointer to the entry equal to the specified entry. Skip 'skip' entries
- * between every comparison. Returns NULL when the field could not be found. */
-unsigned char *lpFind(unsigned char *lp, unsigned char *p, unsigned char *s,
- uint32_t slen, unsigned int skip)
-{
- struct lpFindArg arg = {
- .s = s,
- .slen = slen
- };
- return lpFindCbInternal(lp, p, &arg, lpFindCmp, skip);
-}
-
-/* Insert, delete or replace the specified string element 'elestr' of length
- * 'size' or integer element 'eleint' at the specified position 'p', with 'p'
- * being a listpack element pointer obtained with lpFirst(), lpLast(), lpNext(),
- * lpPrev() or lpSeek().
- *
- * The element is inserted before, after, or replaces the element pointed
- * by 'p' depending on the 'where' argument, that can be LP_BEFORE, LP_AFTER
- * or LP_REPLACE.
- *
- * If both 'elestr' and `eleint` are NULL, the function removes the element
- * pointed by 'p' instead of inserting one.
- * If `eleint` is non-NULL, 'size' is the length of 'eleint', the function insert
- * or replace with a 64 bit integer, which is stored in the 'eleint' buffer.
- * If 'elestr` is non-NULL, 'size' is the length of 'elestr', the function insert
- * or replace with a string, which is stored in the 'elestr' buffer.
- *
- * Returns NULL on out of memory or when the listpack total length would exceed
- * the max allowed size of 2^32-1, otherwise the new pointer to the listpack
- * holding the new element is returned (and the old pointer passed is no longer
- * considered valid)
- *
- * If 'newp' is not NULL, at the end of a successful call '*newp' will be set
- * to the address of the element just added, so that it will be possible to
- * continue an interaction with lpNext() and lpPrev().
- *
- * For deletion operations (both 'elestr' and 'eleint' set to NULL) 'newp' is
- * set to the next element, on the right of the deleted one, or to NULL if the
- * deleted element was the last one. */
-unsigned char *lpInsert(unsigned char *lp, unsigned char *elestr, unsigned char *eleint,
- uint32_t size, unsigned char *p, int where, unsigned char **newp)
-{
- unsigned char intenc[LP_MAX_INT_ENCODING_LEN];
- unsigned char backlen[LP_MAX_BACKLEN_SIZE];
-
- uint64_t enclen; /* The length of the encoded element. */
- int delete = (elestr == NULL && eleint == NULL);
-
- /* when deletion, it is conceptually replacing the element with a
- * zero-length element. So whatever we get passed as 'where', set
- * it to LP_REPLACE. */
- if (delete) where = LP_REPLACE;
-
- /* If we need to insert after the current element, we just jump to the
- * next element (that could be the EOF one) and handle the case of
- * inserting before. So the function will actually deal with just two
- * cases: LP_BEFORE and LP_REPLACE. */
- if (where == LP_AFTER) {
- p = lpSkip(p);
- where = LP_BEFORE;
- ASSERT_INTEGRITY(lp, p);
- }
-
- /* Store the offset of the element 'p', so that we can obtain its
- * address again after a reallocation. */
- unsigned long poff = p-lp;
-
- int enctype;
- if (elestr) {
- /* Calling lpEncodeGetType() results into the encoded version of the
- * element to be stored into 'intenc' in case it is representable as
- * an integer: in that case, the function returns LP_ENCODING_INT.
- * Otherwise if LP_ENCODING_STR is returned, we'll have to call
- * lpEncodeString() to actually write the encoded string on place later.
- *
- * Whatever the returned encoding is, 'enclen' is populated with the
- * length of the encoded element. */
- enctype = lpEncodeGetType(elestr,size,intenc,&enclen);
- if (enctype == LP_ENCODING_INT) eleint = intenc;
- } else if (eleint) {
- enctype = LP_ENCODING_INT;
- enclen = size; /* 'size' is the length of the encoded integer element. */
- } else {
- enctype = -1;
- enclen = 0;
- }
-
- /* We need to also encode the backward-parsable length of the element
- * and append it to the end: this allows to traverse the listpack from
- * the end to the start. */
- unsigned long backlen_size = (!delete) ? lpEncodeBacklen(backlen,enclen) : 0;
- uint64_t old_listpack_bytes = lpGetTotalBytes(lp);
- uint32_t replaced_len = 0;
- if (where == LP_REPLACE) {
- replaced_len = lpCurrentEncodedSizeUnsafe(p);
- replaced_len += lpEncodeBacklenBytes(replaced_len);
- ASSERT_INTEGRITY_LEN(lp, p, replaced_len);
- }
-
- uint64_t new_listpack_bytes = old_listpack_bytes + enclen + backlen_size
- - replaced_len;
- if (new_listpack_bytes > UINT32_MAX) return NULL;
-
- /* We now need to reallocate in order to make space or shrink the
- * allocation (in case 'when' value is LP_REPLACE and the new element is
- * smaller). However we do that before memmoving the memory to
- * make room for the new element if the final allocation will get
- * larger, or we do it after if the final allocation will get smaller. */
-
- unsigned char *dst = lp + poff; /* May be updated after reallocation. */
-
- /* Realloc before: we need more room. */
- if (new_listpack_bytes > old_listpack_bytes &&
- new_listpack_bytes > lp_malloc_size(lp)) {
- if ((lp = lp_realloc(lp,new_listpack_bytes)) == NULL) return NULL;
- dst = lp + poff;
- }
-
- /* Setup the listpack relocating the elements to make the exact room
- * we need to store the new one. */
- if (where == LP_BEFORE) {
- memmove(dst+enclen+backlen_size,dst,old_listpack_bytes-poff);
- } else { /* LP_REPLACE. */
- memmove(dst+enclen+backlen_size,
- dst+replaced_len,
- old_listpack_bytes-poff-replaced_len);
- }
-
- /* Realloc after: we need to free space. */
- if (new_listpack_bytes < old_listpack_bytes) {
- if ((lp = lp_realloc(lp,new_listpack_bytes)) == NULL) return NULL;
- dst = lp + poff;
- }
-
- /* Store the entry. */
- if (newp) {
- *newp = dst;
- /* In case of deletion, set 'newp' to NULL if the next element is
- * the EOF element. */
- if (delete && dst[0] == LP_EOF) *newp = NULL;
- }
- if (!delete) {
- if (enctype == LP_ENCODING_INT) {
- memcpy(dst,eleint,enclen);
- } else if (elestr) {
- lpEncodeString(dst,elestr,size);
- } else {
- redis_unreachable();
- }
- dst += enclen;
- memcpy(dst,backlen,backlen_size);
- dst += backlen_size;
- }
-
- /* Update header. */
- if (where != LP_REPLACE || delete) {
- uint32_t num_elements = lpGetNumElements(lp);
- if (num_elements != LP_HDR_NUMELE_UNKNOWN) {
- if (!delete)
- lpSetNumElements(lp,num_elements+1);
- else
- lpSetNumElements(lp,num_elements-1);
- }
- }
- lpSetTotalBytes(lp,new_listpack_bytes);
-
-#if 0
- /* This code path is normally disabled: what it does is to force listpack
- * to return *always* a new pointer after performing some modification to
- * the listpack, even if the previous allocation was enough. This is useful
- * in order to spot bugs in code using listpacks: by doing so we can find
- * if the caller forgets to set the new pointer where the listpack reference
- * is stored, after an update. */
- unsigned char *oldlp = lp;
- lp = lp_malloc(new_listpack_bytes);
- memcpy(lp,oldlp,new_listpack_bytes);
- if (newp) {
- unsigned long offset = (*newp)-oldlp;
- *newp = lp + offset;
- }
- /* Make sure the old allocation contains garbage. */
- memset(oldlp,'A',new_listpack_bytes);
- lp_free(oldlp);
-#endif
-
- return lp;
-}
-
-/* Insert the specified elements with 'entries' and 'len' at the specified
- * position 'p', with 'p' being a listpack element pointer obtained with
- * lpFirst(), lpLast(), lpNext(), lpPrev() or lpSeek().
- *
- * This is similar to lpInsert() but allows you to insert batch of entries in
- * one call. This function is more efficient than inserting entries one by one
- * as it does single realloc()/memmove() calls for all the entries.
- *
- * In each listpackEntry, if 'sval' is not null, it is assumed entry is string
- * and 'sval' and 'slen' will be used. Otherwise, 'lval' will be used to append
- * the integer entry.
- *
- * The elements are inserted before or after the element pointed by 'p'
- * depending on the 'where' argument, that can be LP_BEFORE or LP_AFTER.
- *
- * If 'newp' is not NULL, at the end of a successful call '*newp' will be set
- * to the address of the element just added, so that it will be possible to
- * continue an interaction with lpNext() and lpPrev().
- *
- * Returns NULL on out of memory or when the listpack total length would exceed
- * the max allowed size of 2^32-1, otherwise the new pointer to the listpack
- * holding the new element is returned (and the old pointer passed is no longer
- * considered valid). */
-unsigned char *lpBatchInsert(unsigned char *lp, unsigned char *p, int where,
- listpackEntry *entries, unsigned int len,
- unsigned char **newp)
-{
- assert(where == LP_BEFORE || where == LP_AFTER);
- assert(entries != NULL && len > 0);
-
- struct listpackInsertEntry {
- int enctype;
- uint64_t enclen;
- unsigned char intenc[LP_MAX_INT_ENCODING_LEN];
- unsigned char backlen[LP_MAX_BACKLEN_SIZE];
- unsigned long backlen_size;
- };
-
- uint64_t addedlen = 0; /* The encoded length of the added elements. */
- struct listpackInsertEntry tmp[3]; /* Encoded entries */
- struct listpackInsertEntry *enc = tmp;
-
- if (len > sizeof(tmp) / sizeof(struct listpackInsertEntry)) {
- /* If 'len' is larger than local buffer size, allocate on heap. */
- enc = zmalloc(len * sizeof(struct listpackInsertEntry));
- }
-
- /* If we need to insert after the current element, we just jump to the
- * next element (that could be the EOF one) and handle the case of
- * inserting before. So the function will actually deal with just one
- * case: LP_BEFORE. */
- if (where == LP_AFTER) {
- p = lpSkip(p);
- where = LP_BEFORE;
- ASSERT_INTEGRITY(lp, p);
- }
-
- for (unsigned int i = 0; i < len; i++) {
- listpackEntry *e = &entries[i];
- if (e->sval) {
- /* Calling lpEncodeGetType() results into the encoded version of the
- * element to be stored into 'intenc' in case it is representable as
- * an integer: in that case, the function returns LP_ENCODING_INT.
- * Otherwise, if LP_ENCODING_STR is returned, we'll have to call
- * lpEncodeString() to actually write the encoded string on place
- * later.
- *
- * Whatever the returned encoding is, 'enclen' is populated with the
- * length of the encoded element. */
- enc[i].enctype = lpEncodeGetType(e->sval, e->slen,
- enc[i].intenc, &enc[i].enclen);
- } else {
- enc[i].enctype = LP_ENCODING_INT;
- lpEncodeIntegerGetType(e->lval, enc[i].intenc, &enc[i].enclen);
- }
- addedlen += enc[i].enclen;
-
- /* We need to also encode the backward-parsable length of the element
- * and append it to the end: this allows to traverse the listpack from
- * the end to the start. */
- enc[i].backlen_size = lpEncodeBacklen(enc[i].backlen, enc[i].enclen);
- addedlen += enc[i].backlen_size;
- }
-
- uint64_t old_listpack_bytes = lpGetTotalBytes(lp);
- uint64_t new_listpack_bytes = old_listpack_bytes + addedlen;
- if (new_listpack_bytes > UINT32_MAX) return NULL;
-
- /* Store the offset of the element 'p', so that we can obtain its
- * address again after a reallocation. */
- unsigned long poff = p-lp;
- unsigned char *dst = lp + poff; /* May be updated after reallocation. */
-
- /* Realloc before: we need more room. */
- if (new_listpack_bytes > old_listpack_bytes &&
- new_listpack_bytes > lp_malloc_size(lp)) {
- if ((lp = lp_realloc(lp,new_listpack_bytes)) == NULL) return NULL;
- dst = lp + poff;
- }
-
- /* Setup the listpack relocating the elements to make the exact room
- * we need to store the new ones. */
- memmove(dst+addedlen,dst,old_listpack_bytes-poff);
-
- for (unsigned int i = 0; i < len; i++) {
- listpackEntry *ent = &entries[i];
-
- if (newp)
- *newp = dst;
-
- if (enc[i].enctype == LP_ENCODING_INT)
- memcpy(dst, enc[i].intenc, enc[i].enclen);
- else
- lpEncodeString(dst, ent->sval, ent->slen);
-
- dst += enc[i].enclen;
- memcpy(dst, enc[i].backlen, enc[i].backlen_size);
- dst += enc[i].backlen_size;
- }
-
- /* Update header. */
- uint32_t num_elements = lpGetNumElements(lp);
- if (num_elements != LP_HDR_NUMELE_UNKNOWN) {
- if ((int64_t) len > (int64_t) LP_HDR_NUMELE_UNKNOWN - (int64_t) num_elements)
- lpSetNumElements(lp, LP_HDR_NUMELE_UNKNOWN);
- else
- lpSetNumElements(lp,num_elements + len);
- }
- lpSetTotalBytes(lp,new_listpack_bytes);
- if (enc != tmp) lp_free(enc);
-
- return lp;
-}
-
-/* This is just a wrapper for lpInsert() to directly use a string. */
-unsigned char *lpInsertString(unsigned char *lp, unsigned char *s, uint32_t slen,
- unsigned char *p, int where, unsigned char **newp)
-{
- return lpInsert(lp, s, NULL, slen, p, where, newp);
-}
-
-/* This is just a wrapper for lpInsert() to directly use a 64 bit integer
- * instead of a string. */
-unsigned char *lpInsertInteger(unsigned char *lp, long long lval, unsigned char *p, int where, unsigned char **newp) {
- uint64_t enclen; /* The length of the encoded element. */
- unsigned char intenc[LP_MAX_INT_ENCODING_LEN];
-
- lpEncodeIntegerGetType(lval, intenc, &enclen);
- return lpInsert(lp, NULL, intenc, enclen, p, where, newp);
-}
-
-/* Append the specified element 's' of length 'slen' at the head of the listpack. */
-unsigned char *lpPrepend(unsigned char *lp, unsigned char *s, uint32_t slen) {
- unsigned char *p = lpFirst(lp);
- if (!p) return lpAppend(lp, s, slen);
- return lpInsert(lp, s, NULL, slen, p, LP_BEFORE, NULL);
-}
-
-/* Append the specified integer element 'lval' at the head of the listpack. */
-unsigned char *lpPrependInteger(unsigned char *lp, long long lval) {
- unsigned char *p = lpFirst(lp);
- if (!p) return lpAppendInteger(lp, lval);
- return lpInsertInteger(lp, lval, p, LP_BEFORE, NULL);
-}
-
-/* Append the specified element 'ele' of length 'size' at the end of the
- * listpack. It is implemented in terms of lpInsert(), so the return value is
- * the same as lpInsert(). */
-unsigned char *lpAppend(unsigned char *lp, unsigned char *ele, uint32_t size) {
- uint64_t listpack_bytes = lpGetTotalBytes(lp);
- unsigned char *eofptr = lp + listpack_bytes - 1;
- return lpInsert(lp,ele,NULL,size,eofptr,LP_BEFORE,NULL);
-}
-
-/* Append the specified integer element 'lval' at the end of the listpack. */
-unsigned char *lpAppendInteger(unsigned char *lp, long long lval) {
- uint64_t listpack_bytes = lpGetTotalBytes(lp);
- unsigned char *eofptr = lp + listpack_bytes - 1;
- return lpInsertInteger(lp, lval, eofptr, LP_BEFORE, NULL);
-}
-
-/* Append batch of entries to the listpack.
- *
- * This call is more efficient than multiple lpAppend() calls as it only does
- * a single realloc() for all the given entries.
- *
- * In each listpackEntry, if 'sval' is not null, it is assumed entry is string
- * and 'sval' and 'slen' will be used. Otherwise, 'lval' will be used to append
- * the integer entry. */
-unsigned char *lpBatchAppend(unsigned char *lp, listpackEntry *entries, unsigned long len) {
- uint64_t listpack_bytes = lpGetTotalBytes(lp);
- unsigned char *eofptr = lp + listpack_bytes - 1;
- return lpBatchInsert(lp, eofptr, LP_BEFORE, entries, len, NULL);
-}
-
-/* This is just a wrapper for lpInsert() to directly use a string to replace
- * the current element. The function returns the new listpack as return
- * value, and also updates the current cursor by updating '*p'. */
-unsigned char *lpReplace(unsigned char *lp, unsigned char **p, unsigned char *s, uint32_t slen) {
- return lpInsert(lp, s, NULL, slen, *p, LP_REPLACE, p);
-}
-
-/* This is just a wrapper for lpInsertInteger() to directly use a 64 bit integer
- * instead of a string to replace the current element. The function returns
- * the new listpack as return value, and also updates the current cursor
- * by updating '*p'. */
-unsigned char *lpReplaceInteger(unsigned char *lp, unsigned char **p, long long lval) {
- return lpInsertInteger(lp, lval, *p, LP_REPLACE, p);
-}
-
-/* Remove the element pointed by 'p', and return the resulting listpack.
- * If 'newp' is not NULL, the next element pointer (to the right of the
- * deleted one) is returned by reference. If the deleted element was the
- * last one, '*newp' is set to NULL. */
-unsigned char *lpDelete(unsigned char *lp, unsigned char *p, unsigned char **newp) {
- return lpInsert(lp,NULL,NULL,0,p,LP_REPLACE,newp);
-}
-
-/* Delete a range of entries from the listpack start with the element pointed by 'p'. */
-unsigned char *lpDeleteRangeWithEntry(unsigned char *lp, unsigned char **p, unsigned long num) {
- size_t bytes = lpBytes(lp);
- unsigned long deleted = 0;
- unsigned char *eofptr = lp + bytes - 1;
- unsigned char *first, *tail;
- first = tail = *p;
-
- if (num == 0) return lp; /* Nothing to delete, return ASAP. */
-
- /* Find the next entry to the last entry that needs to be deleted.
- * lpLength may be unreliable due to corrupt data, so we cannot
- * treat 'num' as the number of elements to be deleted. */
- while (num--) {
- deleted++;
- tail = lpSkip(tail);
- if (tail[0] == LP_EOF) break;
- lpAssertValidEntry(lp, bytes, tail);
- }
-
- /* Store the offset of the element 'first', so that we can obtain its
- * address again after a reallocation. */
- unsigned long poff = first-lp;
-
- /* Move tail to the front of the listpack */
- memmove(first, tail, eofptr - tail + 1);
- lpSetTotalBytes(lp, bytes - (tail - first));
- uint32_t numele = lpGetNumElements(lp);
- if (numele != LP_HDR_NUMELE_UNKNOWN)
- lpSetNumElements(lp, numele-deleted);
- lp = lpShrinkToFit(lp);
-
- /* Store the entry. */
- *p = lp+poff;
- if ((*p)[0] == LP_EOF) *p = NULL;
-
- return lp;
-}
-
-/* Delete a range of entries from the listpack. */
-unsigned char *lpDeleteRange(unsigned char *lp, long index, unsigned long num) {
- unsigned char *p;
- uint32_t numele = lpGetNumElements(lp);
-
- if (num == 0) return lp; /* Nothing to delete, return ASAP. */
- if ((p = lpSeek(lp, index)) == NULL) return lp;
-
- /* If we know we're gonna delete beyond the end of the listpack, we can just move
- * the EOF marker, and there's no need to iterate through the entries,
- * but if we can't be sure how many entries there are, we rather avoid calling lpLength
- * since that means an additional iteration on all elements.
- *
- * Note that index could overflow, but we use the value after seek, so when we
- * use it no overflow happens. */
- if (numele != LP_HDR_NUMELE_UNKNOWN && index < 0) index = (long)numele + index;
- if (numele != LP_HDR_NUMELE_UNKNOWN && (numele - (unsigned long)index) <= num) {
- p[0] = LP_EOF;
- lpSetTotalBytes(lp, p - lp + 1);
- lpSetNumElements(lp, index);
- lp = lpShrinkToFit(lp);
- } else {
- lp = lpDeleteRangeWithEntry(lp, &p, num);
- }
-
- return lp;
-}
-
-/* Delete the elements 'ps' passed as an array of 'count' element pointers and
- * return the resulting listpack. The elements must be given in the same order
- * as they apper in the listpack. */
-unsigned char *lpBatchDelete(unsigned char *lp, unsigned char **ps, unsigned long count) {
- if (count == 0) return lp;
- unsigned char *dst = ps[0];
- size_t total_bytes = lpGetTotalBytes(lp);
- unsigned char *lp_end = lp + total_bytes; /* After the EOF element. */
- assert(lp_end[-1] == LP_EOF);
- /*
- * ----+--------+-----------+--------+---------+-----+---+
- * ... | Delete | Keep | Delete | Keep | ... |EOF|
- * ... |xxxxxxxx| |xxxxxxxx| | ... | |
- * ----+--------+-----------+--------+---------+-----+---+
- * ^ ^ ^ ^
- * | | | |
- * ps[i] | ps[i+1] |
- * skip keep_start keep_end lp_end
- *
- * The loop memmoves the bytes between keep_start and keep_end to dst.
- */
- for (unsigned long i = 0; i < count; i++) {
- unsigned char *skip = ps[i];
- assert(skip != NULL && skip[0] != LP_EOF);
- unsigned char *keep_start = lpSkip(skip);
- unsigned char *keep_end;
- if (i + 1 < count) {
- keep_end = ps[i + 1];
- /* Deleting consecutive elements. Nothing to keep between them. */
- if (keep_start == keep_end) continue;
- } else {
- /* Keep the rest of the listpack including the EOF marker. */
- keep_end = lp_end;
- }
- assert(keep_end > keep_start);
- size_t bytes_to_keep = keep_end - keep_start;
- memmove(dst, keep_start, bytes_to_keep);
- dst += bytes_to_keep;
- }
- /* Update total size and num elements. */
- size_t deleted_bytes = lp_end - dst;
- total_bytes -= deleted_bytes;
- assert(lp[total_bytes - 1] == LP_EOF);
- lpSetTotalBytes(lp, total_bytes);
- uint32_t numele = lpGetNumElements(lp);
- if (numele != LP_HDR_NUMELE_UNKNOWN) lpSetNumElements(lp, numele - count);
- return lpShrinkToFit(lp);
-}
-
-/* Merge listpacks 'first' and 'second' by appending 'second' to 'first'.
- *
- * NOTE: The larger listpack is reallocated to contain the new merged listpack.
- * Either 'first' or 'second' can be used for the result. The parameter not
- * used will be free'd and set to NULL.
- *
- * After calling this function, the input parameters are no longer valid since
- * they are changed and free'd in-place.
- *
- * The result listpack is the contents of 'first' followed by 'second'.
- *
- * On failure: returns NULL if the merge is impossible.
- * On success: returns the merged listpack (which is expanded version of either
- * 'first' or 'second', also frees the other unused input listpack, and sets the
- * input listpack argument equal to newly reallocated listpack return value. */
-unsigned char *lpMerge(unsigned char **first, unsigned char **second) {
- /* If any params are null, we can't merge, so NULL. */
- if (first == NULL || *first == NULL || second == NULL || *second == NULL)
- return NULL;
-
- /* Can't merge same list into itself. */
- if (*first == *second)
- return NULL;
-
- size_t first_bytes = lpBytes(*first);
- unsigned long first_len = lpLength(*first);
-
- size_t second_bytes = lpBytes(*second);
- unsigned long second_len = lpLength(*second);
-
- int append;
- unsigned char *source, *target;
- size_t target_bytes, source_bytes;
- /* Pick the largest listpack so we can resize easily in-place.
- * We must also track if we are now appending or prepending to
- * the target listpack. */
- if (first_bytes >= second_bytes) {
- /* retain first, append second to first. */
- target = *first;
- target_bytes = first_bytes;
- source = *second;
- source_bytes = second_bytes;
- append = 1;
- } else {
- /* else, retain second, prepend first to second. */
- target = *second;
- target_bytes = second_bytes;
- source = *first;
- source_bytes = first_bytes;
- append = 0;
- }
-
- /* Calculate final bytes (subtract one pair of metadata) */
- unsigned long long lpbytes = (unsigned long long)first_bytes + second_bytes - LP_HDR_SIZE - 1;
- assert(lpbytes < UINT32_MAX); /* larger values can't be stored */
- unsigned long lplength = first_len + second_len;
-
- /* Combined lp length should be limited within UINT16_MAX */
- lplength = lplength < UINT16_MAX ? lplength : UINT16_MAX;
-
- /* Extend target to new lpbytes then append or prepend source. */
- target = lp_realloc(target, lpbytes);
- if (append) {
- /* append == appending to target */
- /* Copy source after target (copying over original [END]):
- * [TARGET - END, SOURCE - HEADER] */
- memcpy(target + target_bytes - 1,
- source + LP_HDR_SIZE,
- source_bytes - LP_HDR_SIZE);
- } else {
- /* !append == prepending to target */
- /* Move target *contents* exactly size of (source - [END]),
- * then copy source into vacated space (source - [END]):
- * [SOURCE - END, TARGET - HEADER] */
- memmove(target + source_bytes - 1,
- target + LP_HDR_SIZE,
- target_bytes - LP_HDR_SIZE);
- memcpy(target, source, source_bytes - 1);
- }
-
- lpSetNumElements(target, lplength);
- lpSetTotalBytes(target, lpbytes);
-
- /* Now free and NULL out what we didn't realloc */
- if (append) {
- lp_free(*second);
- *second = NULL;
- *first = target;
- } else {
- lp_free(*first);
- *first = NULL;
- *second = target;
- }
-
- return target;
-}
-
-unsigned char *lpDup(unsigned char *lp) {
- size_t lpbytes = lpBytes(lp);
- unsigned char *newlp = lp_malloc(lpbytes);
- memcpy(newlp, lp, lpbytes);
- return newlp;
-}
-
-/* Return the total number of bytes the listpack is composed of. */
-size_t lpBytes(unsigned char *lp) {
- return lpGetTotalBytes(lp);
-}
-
-/* Returns the size 'lval' will require when encoded, in bytes */
-size_t lpEntrySizeInteger(long long lval) {
- uint64_t enclen;
- lpEncodeIntegerGetType(lval, NULL, &enclen);
- unsigned long backlen = lpEncodeBacklenBytes(enclen);
- return enclen + backlen;
-}
-
-/* Returns the size of a listpack consisting of an integer repeated 'rep' times. */
-size_t lpEstimateBytesRepeatedInteger(long long lval, unsigned long rep) {
- return LP_HDR_SIZE + lpEntrySizeInteger(lval) * rep + 1;
-}
-
-/* Seek the specified element and returns the pointer to the seeked element.
- * Positive indexes specify the zero-based element to seek from the head to
- * the tail, negative indexes specify elements starting from the tail, where
- * -1 means the last element, -2 the penultimate and so forth. If the index
- * is out of range, NULL is returned. */
-unsigned char *lpSeek(unsigned char *lp, long index) {
- int forward = 1; /* Seek forward by default. */
-
- /* We want to seek from left to right or the other way around
- * depending on the listpack length and the element position.
- * However if the listpack length cannot be obtained in constant time,
- * we always seek from left to right. */
- uint32_t numele = lpGetNumElements(lp);
- if (numele != LP_HDR_NUMELE_UNKNOWN) {
- if (index < 0) index = (long)numele+index;
- if (index < 0) return NULL; /* Index still < 0 means out of range. */
- if (index >= (long)numele) return NULL; /* Out of range the other side. */
- /* We want to scan right-to-left if the element we are looking for
- * is past the half of the listpack. */
- if (index > (long)numele/2) {
- forward = 0;
- /* Right to left scanning always expects a negative index. Convert
- * our index to negative form. */
- index -= numele;
- }
- } else {
- /* If the listpack length is unspecified, for negative indexes we
- * want to always scan right-to-left. */
- if (index < 0) forward = 0;
- }
-
- /* Forward and backward scanning is trivially based on lpNext()/lpPrev(). */
- if (forward) {
- unsigned char *ele = lpFirst(lp);
- while (index > 0 && ele) {
- ele = lpNext(lp,ele);
- index--;
- }
- return ele;
- } else {
- unsigned char *ele = lpLast(lp);
- while (index < -1 && ele) {
- ele = lpPrev(lp,ele);
- index++;
- }
- return ele;
- }
-}
-
-/* Same as lpFirst but without validation assert, to be used right before lpValidateNext. */
-unsigned char *lpValidateFirst(unsigned char *lp) {
- unsigned char *p = lp + LP_HDR_SIZE; /* Skip the header. */
- if (p[0] == LP_EOF) return NULL;
- return p;
-}
-
-/* Validate the integrity of a single listpack entry and move to the next one.
- * The input argument 'pp' is a reference to the current record and is advanced on exit.
- * the data pointed to by 'lp' will not be modified by the function.
- * Returns 1 if valid, 0 if invalid. */
-int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes) {
-#define OUT_OF_RANGE(p) ( \
- (p) < lp + LP_HDR_SIZE || \
- (p) > lp + lpbytes - 1)
- unsigned char *p = *pp;
- if (!p)
- return 0;
-
- /* Before accessing p, make sure it's valid. */
- if (OUT_OF_RANGE(p))
- return 0;
-
- if (*p == LP_EOF) {
- *pp = NULL;
- return 1;
- }
-
- /* check that we can read the encoded size */
- uint32_t lenbytes = lpCurrentEncodedSizeBytes(p[0]);
- if (!lenbytes)
- return 0;
-
- /* make sure the encoded entry length doesn't reach outside the edge of the listpack */
- if (OUT_OF_RANGE(p + lenbytes))
- return 0;
-
- /* get the entry length and encoded backlen. */
- unsigned long entrylen = lpCurrentEncodedSizeUnsafe(p);
- unsigned long encodedBacklen = lpEncodeBacklenBytes(entrylen);
- entrylen += encodedBacklen;
-
- /* make sure the entry doesn't reach outside the edge of the listpack */
- if (OUT_OF_RANGE(p + entrylen))
- return 0;
-
- /* move to the next entry */
- p += entrylen;
-
- /* make sure the encoded length at the end patches the one at the beginning. */
- uint64_t prevlen = lpDecodeBacklen(p-1);
- if (prevlen + encodedBacklen != entrylen)
- return 0;
-
- *pp = p;
- return 1;
-#undef OUT_OF_RANGE
-}
-
-/* Validate that the entry doesn't reach outside the listpack allocation. */
-static inline void lpAssertValidEntry(unsigned char* lp, size_t lpbytes, unsigned char *p) {
- assert(lpValidateNext(lp, &p, lpbytes));
-}
-
-/* Validate the integrity of the data structure.
- * when `deep` is 0, only the integrity of the header is validated.
- * when `deep` is 1, we scan all the entries one by one. */
-int lpValidateIntegrity(unsigned char *lp, size_t size, int deep,
- listpackValidateEntryCB entry_cb, void *cb_userdata) {
- /* Check that we can actually read the header. (and EOF) */
- if (size < LP_HDR_SIZE + 1)
- return 0;
-
- /* Check that the encoded size in the header must match the allocated size. */
- size_t bytes = lpGetTotalBytes(lp);
- if (bytes != size)
- return 0;
-
- /* The last byte must be the terminator. */
- if (lp[size-1] != LP_EOF)
- return 0;
-
- if (!deep)
- return 1;
-
- /* Validate the individual entries. */
- uint32_t count = 0;
- uint32_t numele = lpGetNumElements(lp);
- unsigned char *p = lp + LP_HDR_SIZE;
- while(p && p[0] != LP_EOF) {
- unsigned char *prev = p;
-
- /* Validate this entry and move to the next entry in advance
- * to avoid callback crash due to corrupt listpack. */
- if (!lpValidateNext(lp, &p, bytes))
- return 0;
-
- /* Optionally let the caller validate the entry too. */
- if (entry_cb && !entry_cb(prev, numele, cb_userdata))
- return 0;
-
- count++;
- }
-
- /* Make sure 'p' really does point to the end of the listpack. */
- if (p != lp + size - 1)
- return 0;
-
- /* Check that the count in the header is correct */
- if (numele != LP_HDR_NUMELE_UNKNOWN && numele != count)
- return 0;
-
- return 1;
-}
-
-/* Compare entry pointer to by 'p' with string 's' of length 'slen'.
- * Return 1 if equal. */
-unsigned int lpCompare(unsigned char *p, unsigned char *s, uint32_t slen,
- long long *cached_longval, int *cached_valid) {
- unsigned char *value;
- int64_t sz;
- if (p[0] == LP_EOF) return 0;
-
- value = lpGet(p, &sz, NULL);
- if (value) {
- return (slen == sz) && memcmp(value,s,slen) == 0;
- } else {
- int64_t sval;
- /* We use lpStringToInt64() to get an integer representation of the
- * string 's' and compare it to 'sval', it's much faster than convert
- * integer to string and comparing. */
- if (cached_valid != NULL) {
- /* Use caching */
- if (*cached_valid == 0) {
- if (lpStringToInt64((const char*)s, slen, (int64_t*)cached_longval)) {
- *cached_valid = 1;
- } else {
- *cached_valid = -1;
- }
- }
- return (*cached_valid == 1 && sz == *cached_longval);
- } else {
- /* No caching - direct conversion */
- if (lpStringToInt64((const char*)s, slen, &sval))
- return sz == sval;
- }
- }
-
- return 0;
-}
-
-/* uint compare for qsort */
-static int uintCompare(const void *a, const void *b) {
- return (*(unsigned int *) a - *(unsigned int *) b);
-}
-
-/* Helper method to store a string from val or lval into dest */
-static inline void lpSaveValue(unsigned char *val, unsigned int len, int64_t lval, listpackEntry *dest) {
- dest->sval = val;
- dest->slen = len;
- dest->lval = lval;
-}
-
-/* Randomly select a pair of key and value.
- * total_count is a pre-computed length/2 of the listpack (to avoid calls to lpLength)
- * 'key' and 'val' are used to store the result key value pair.
- * 'val' can be NULL if the value is not needed.
- * 'tuple_len' indicates entry count of a single logical item. It should be 2
- * if listpack was saved as key-value pair or more for key-value-...(n_entries). */
-void lpRandomPair(unsigned char *lp, unsigned long total_count,
- listpackEntry *key, listpackEntry *val, int tuple_len)
-{
- unsigned char *p;
-
- assert(tuple_len >= 2);
-
- /* Avoid div by zero on corrupt listpack */
- assert(total_count);
-
- int r = (rand() % total_count) * tuple_len;
- assert((p = lpSeek(lp, r)));
- key->sval = lpGetValue(p, &(key->slen), &(key->lval));
-
- if (!val)
- return;
- assert((p = lpNext(lp, p)));
- val->sval = lpGetValue(p, &(val->slen), &(val->lval));
-}
-
-/* Randomly select 'count' entries and store them in the 'entries' array, which
- * needs to have space for 'count' listpackEntry structs. The order is random
- * and duplicates are possible. */
-void lpRandomEntries(unsigned char *lp, unsigned int count, listpackEntry *entries) {
- struct pick {
- unsigned int index;
- unsigned int order;
- } *picks = lp_malloc(count * sizeof(struct pick));
- unsigned int total_size = lpLength(lp);
- assert(total_size);
- for (unsigned int i = 0; i < count; i++) {
- picks[i].index = rand() % total_size;
- picks[i].order = i;
- }
-
- /* Sort by index. */
- qsort(picks, count, sizeof(struct pick), uintCompare);
-
- /* Iterate over listpack in index order and store the values in the entries
- * array respecting the original order. */
- unsigned char *p = lpFirst(lp);
- unsigned int j = 0; /* index in listpack */
- for (unsigned int i = 0; i < count; i++) {
- /* Advance listpack pointer to until we reach 'index' listpack. */
- while (j < picks[i].index) {
- p = lpNext(lp, p);
- j++;
- }
- int storeorder = picks[i].order;
- unsigned int len = 0;
- long long llval = 0;
- unsigned char *str = lpGetValue(p, &len, &llval);
- lpSaveValue(str, len, llval, &entries[storeorder]);
- }
- lp_free(picks);
-}
-
-/* Randomly select count of key value pairs and store into 'keys' and
- * 'vals' args. The order of the picked entries is random, and the selections
- * are non-unique (repetitions are possible).
- * The 'vals' arg can be NULL in which case we skip these.
- * 'tuple_len' indicates entry count of a single logical item. It should be 2
- * if listpack was saved as key-value pair or more for key-value-...(n_entries). */
-void lpRandomPairs(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals, int tuple_len) {
- unsigned char *p, *key, *value;
- unsigned int klen = 0, vlen = 0;
- long long klval = 0, vlval = 0;
-
- assert(tuple_len >= 2);
-
- /* Notice: the index member must be first due to the use in uintCompare */
- typedef struct {
- unsigned int index;
- unsigned int order;
- } rand_pick;
- rand_pick *picks = lp_malloc(sizeof(rand_pick)*count);
- unsigned int total_size = lpLength(lp)/tuple_len;
-
- /* Avoid div by zero on corrupt listpack */
- assert(total_size);
-
- /* create a pool of random indexes (some may be duplicate). */
- for (unsigned int i = 0; i < count; i++) {
- /* Generate indexes that key exist at */
- picks[i].index = (rand() % total_size) * tuple_len;
- /* keep track of the order we picked them */
- picks[i].order = i;
- }
-
- /* sort by indexes. */
- qsort(picks, count, sizeof(rand_pick), uintCompare);
-
- /* fetch the elements form the listpack into a output array respecting the original order. */
- unsigned int lpindex = picks[0].index, pickindex = 0;
- p = lpSeek(lp, lpindex);
- while (p && pickindex < count) {
- key = lpGetValue(p, &klen, &klval);
- assert((p = lpNext(lp, p)));
- value = lpGetValue(p, &vlen, &vlval);
- while (pickindex < count && lpindex == picks[pickindex].index) {
- int storeorder = picks[pickindex].order;
- lpSaveValue(key, klen, klval, &keys[storeorder]);
- if (vals)
- lpSaveValue(value, vlen, vlval, &vals[storeorder]);
- pickindex++;
- }
- lpindex += tuple_len;
-
- for (int i = 0; i < tuple_len - 1; i++) {
- p = lpNext(lp, p);
- }
- }
-
- lp_free(picks);
-}
-
-/* Randomly select count of key value pairs and store into 'keys' and
- * 'vals' args. The selections are unique (no repetitions), and the order of
- * the picked entries is NOT-random.
- * The 'vals' arg can be NULL in which case we skip these.
- * 'tuple_len' indicates entry count of a single logical item. It should be 2
- * if listpack was saved as key-value pair or more for key-value-...(n_entries).
- * The return value is the number of items picked which can be lower than the
- * requested count if the listpack doesn't hold enough pairs. */
-unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count,
- listpackEntry *keys, listpackEntry *vals,
- int tuple_len)
-{
- assert(tuple_len >= 2);
-
- unsigned char *p, *key;
- unsigned int klen = 0;
- long long klval = 0;
- unsigned int total_size = lpLength(lp)/tuple_len;
- unsigned int index = 0;
- if (count > total_size)
- count = total_size;
-
- p = lpFirst(lp);
- unsigned int picked = 0, remaining = count;
- while (picked < count && p) {
- assert((p = lpNextRandom(lp, p, &index, remaining, tuple_len)));
- key = lpGetValue(p, &klen, &klval);
- lpSaveValue(key, klen, klval, &keys[picked]);
- assert((p = lpNext(lp, p)));
- index++;
- if (vals) {
- key = lpGetValue(p, &klen, &klval);
- lpSaveValue(key, klen, klval, &vals[picked]);
- }
- p = lpNext(lp, p);
- remaining--;
- picked++;
- index++;
- }
- return picked;
-}
-
-/* Iterates forward to the "next random" element, given we are yet to pick
- * 'remaining' unique elements between the starting element 'p' (inclusive) and
- * the end of the list. The 'index' needs to be initialized according to the
- * current zero-based index matching the position of the starting element 'p'
- * and is updated to match the returned element's zero-based index. If
- * 'tuple_len' indicates entry count of a single logical item. e.g. This is
- * useful if listpack represents key-value pairs. In this case, tuple_len should
- * be two and even indexes will be picked.
- *
- * Note that this function can return p. In order to skip the previously
- * returned element, you need to call lpNext() or lpDelete() after each call to
- * lpNextRandom(). Idea:
- *
- * assert(remaining <= lpLength(lp));
- * p = lpFirst(lp);
- * i = 0;
- * while (remaining > 0) {
- * p = lpNextRandom(lp, p, &i, remaining--, 1);
- *
- * // ... Do stuff with p ...
- *
- * p = lpNext(lp, p);
- * i++;
- * }
- */
-unsigned char *lpNextRandom(unsigned char *lp, unsigned char *p, unsigned int *index,
- unsigned int remaining, int tuple_len)
-{
- assert(tuple_len > 0);
- /* To only iterate once, every time we try to pick a member, the probability
- * we pick it is the quotient of the count left we want to pick and the
- * count still we haven't visited. This way, we could make every member be
- * equally likely to be picked. */
- unsigned int i = *index;
- unsigned int total_size = lpLength(lp);
- while (i < total_size && p != NULL) {
- if (i % tuple_len != 0) {
- p = lpNext(lp, p);
- i++;
- continue;
- }
-
- /* Do we pick this element? */
- unsigned int available = (total_size - i) / tuple_len;
- double randomDouble = ((double)rand()) / RAND_MAX;
- double threshold = ((double)remaining) / available;
- if (randomDouble <= threshold) {
- *index = i;
- return p;
- }
-
- p = lpNext(lp, p);
- i++;
- }
-
- return NULL;
-}
-
-/* Print info of listpack which is used in debugCommand */
-void lpRepr(unsigned char *lp) {
- unsigned char *p, *vstr;
- int64_t vlen;
- unsigned char intbuf[LP_INTBUF_SIZE];
- int index = 0;
-
- printf("{total bytes %zu} {num entries %lu}\n", lpBytes(lp), lpLength(lp));
-
- p = lpFirst(lp);
- while(p) {
- uint32_t encoded_size_bytes = lpCurrentEncodedSizeBytes(p[0]);
- uint32_t encoded_size = lpCurrentEncodedSizeUnsafe(p);
- unsigned long back_len = lpEncodeBacklenBytes(encoded_size);
- printf(
- "{\n"
- "\taddr: 0x%08lx,\n"
- "\tindex: %2d,\n"
- "\toffset: %1lu,\n"
- "\thdr+entrylen+backlen: %2lu,\n"
- "\thdrlen: %3u,\n"
- "\tbacklen: %2lu,\n"
- "\tpayload: %1u\n",
- (long unsigned)p,
- index,
- (unsigned long) (p-lp),
- encoded_size + back_len,
- encoded_size_bytes,
- back_len,
- encoded_size - encoded_size_bytes);
- printf("\tbytes: ");
- for (unsigned int i = 0; i < (encoded_size + back_len); i++) {
- printf("%02x|",p[i]);
- }
- printf("\n");
-
- vstr = lpGet(p, &vlen, intbuf);
- printf("\t[str]");
- if (vlen > 40) {
- if (fwrite(vstr, 40, 1, stdout) == 0) perror("fwrite");
- printf("...");
- } else {
- if (fwrite(vstr, vlen, 1, stdout) == 0) perror("fwrite");
- }
- printf("\n}\n");
- index++;
- p = lpNext(lp, p);
- }
- printf("{end}\n\n");
-}
-
-#ifdef REDIS_TEST
-
-#include <sys/time.h>
-#include "adlist.h"
-#include "sds.h"
-#include "testhelp.h"
-
-#define UNUSED(x) (void)(x)
-#define TEST(name) printf("test — %s\n", name);
-
-char *mixlist[] = {"hello", "foo", "quux", "1024"};
-char *intlist[] = {"4294967296", "-100", "100", "128000",
- "non integer", "much much longer non integer"};
-
-static unsigned char *createList(void) {
- unsigned char *lp = lpNew(0);
- lp = lpAppend(lp, (unsigned char*)mixlist[1], strlen(mixlist[1]));
- lp = lpAppend(lp, (unsigned char*)mixlist[2], strlen(mixlist[2]));
- lp = lpPrepend(lp, (unsigned char*)mixlist[0], strlen(mixlist[0]));
- lp = lpAppend(lp, (unsigned char*)mixlist[3], strlen(mixlist[3]));
- return lp;
-}
-
-static unsigned char *createIntList(void) {
- unsigned char *lp = lpNew(0);
- lp = lpAppend(lp, (unsigned char*)intlist[2], strlen(intlist[2]));
- lp = lpAppend(lp, (unsigned char*)intlist[3], strlen(intlist[3]));
- lp = lpPrepend(lp, (unsigned char*)intlist[1], strlen(intlist[1]));
- lp = lpPrepend(lp, (unsigned char*)intlist[0], strlen(intlist[0]));
- lp = lpAppend(lp, (unsigned char*)intlist[4], strlen(intlist[4]));
- lp = lpAppend(lp, (unsigned char*)intlist[5], strlen(intlist[5]));
- return lp;
-}
-
-static long long usec(void) {
- struct timeval tv;
- gettimeofday(&tv, NULL);
- return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
-}
-
-static void stress(int pos, int num, int maxsize, int dnum) {
- int i, j, k;
- unsigned char *lp;
- char posstr[2][5] = { "HEAD", "TAIL" };
- long long start;
- for (i = 0; i < maxsize; i+=dnum) {
- lp = lpNew(0);
- for (j = 0; j < i; j++) {
- lp = lpAppend(lp, (unsigned char*)"quux", 4);
- }
-
- /* Do num times a push+pop from pos */
- start = usec();
- for (k = 0; k < num; k++) {
- if (pos == 0) {
- lp = lpPrepend(lp, (unsigned char*)"quux", 4);
- } else {
- lp = lpAppend(lp, (unsigned char*)"quux", 4);
-
- }
- lp = lpDelete(lp, lpFirst(lp), NULL);
- }
- printf("List size: %8d, bytes: %8zu, %dx push+pop (%s): %6lld usec\n",
- i, lpBytes(lp), num, posstr[pos], usec()-start);
- lpFree(lp);
- }
-}
-
-static unsigned char *pop(unsigned char *lp, int where) {
- unsigned char *p, *vstr;
- int64_t vlen;
-
- p = lpSeek(lp, where == 0 ? 0 : -1);
- vstr = lpGet(p, &vlen, NULL);
- if (where == 0)
- printf("Pop head: ");
- else
- printf("Pop tail: ");
-
- if (vstr) {
- if (vlen && fwrite(vstr, vlen, 1, stdout) == 0) perror("fwrite");
- } else {
- printf("%lld", (long long)vlen);
- }
-
- printf("\n");
- return lpDelete(lp, p, &p);
-}
-
-static int randstring(char *target, unsigned int min, unsigned int max) {
- int p = 0;
- int len = min+rand()%(max-min+1);
- int minval, maxval;
- switch(rand() % 3) {
- case 0:
- minval = 0;
- maxval = 255;
- break;
- case 1:
- minval = 48;
- maxval = 122;
- break;
- case 2:
- minval = 48;
- maxval = 52;
- break;
- default:
- assert(NULL);
- }
-
- while(p < len)
- target[p++] = minval+rand()%(maxval-minval+1);
- return len;
-}
-
-static void verifyEntry(unsigned char *p, unsigned char *s, size_t slen) {
- assert(lpCompare(p, s, slen, NULL, NULL));
-}
-
-static int lpValidation(unsigned char *p, unsigned int head_count, void *userdata) {
- UNUSED(p);
- UNUSED(head_count);
-
- int ret;
- long *count = userdata;
- ret = lpCompare(p, (unsigned char *)mixlist[*count], strlen(mixlist[*count]), NULL, NULL);
- (*count)++;
- return ret;
-}
-
-static int lpFindCbCmp(const unsigned char *lp, unsigned char *p, void *user, unsigned char *s, long long slen) {
- assert(lp);
- assert(p);
-
- char *n = user;
-
- if (!s) {
- int64_t sval;
- if (lpStringToInt64((const char*)n, strlen(n), &sval))
- return slen == sval ? 0 : 1;
- } else {
- if (strlen(n) == (size_t) slen && memcmp(n, s, slen) == 0)
- return 0;
- }
-
- return 1;
-}
-
-int listpackTest(int argc, char *argv[], int flags) {
- UNUSED(argc);
- UNUSED(argv);
-
- int i;
- unsigned char *lp, *p, *vstr;
- int64_t vlen;
- unsigned char intbuf[LP_INTBUF_SIZE];
- int accurate = (flags & REDIS_TEST_ACCURATE);
-
- TEST("Create int list") {
- lp = createIntList();
- assert(lpLength(lp) == 6);
- lpFree(lp);
- }
-
- TEST("Create list") {
- lp = createList();
- assert(lpLength(lp) == 4);
- lpFree(lp);
- }
-
- TEST("Test lpPrepend") {
- lp = lpNew(0);
- lp = lpPrepend(lp, (unsigned char*)"abc", 3);
- lp = lpPrepend(lp, (unsigned char*)"1024", 4);
- verifyEntry(lpSeek(lp, 0), (unsigned char*)"1024", 4);
- verifyEntry(lpSeek(lp, 1), (unsigned char*)"abc", 3);
- lpFree(lp);
- }
-
- TEST("Test lpPrependInteger") {
- lp = lpNew(0);
- lp = lpPrependInteger(lp, 127);
- lp = lpPrependInteger(lp, 4095);
- lp = lpPrependInteger(lp, 32767);
- lp = lpPrependInteger(lp, 8388607);
- lp = lpPrependInteger(lp, 2147483647);
- lp = lpPrependInteger(lp, 9223372036854775807);
- verifyEntry(lpSeek(lp, 0), (unsigned char*)"9223372036854775807", 19);
- verifyEntry(lpSeek(lp, -1), (unsigned char*)"127", 3);
- lpFree(lp);
- }
-
- TEST("Get element at index") {
- lp = createList();
- verifyEntry(lpSeek(lp, 0), (unsigned char*)"hello", 5);
- verifyEntry(lpSeek(lp, 3), (unsigned char*)"1024", 4);
- verifyEntry(lpSeek(lp, -1), (unsigned char*)"1024", 4);
- verifyEntry(lpSeek(lp, -4), (unsigned char*)"hello", 5);
- assert(lpSeek(lp, 4) == NULL);
- assert(lpSeek(lp, -5) == NULL);
- lpFree(lp);
- }
-
- TEST("Pop list") {
- lp = createList();
- lp = pop(lp, 1);
- lp = pop(lp, 0);
- lp = pop(lp, 1);
- lp = pop(lp, 1);
- lpFree(lp);
- }
-
- TEST("Get element at index") {
- lp = createList();
- verifyEntry(lpSeek(lp, 0), (unsigned char*)"hello", 5);
- verifyEntry(lpSeek(lp, 3), (unsigned char*)"1024", 4);
- verifyEntry(lpSeek(lp, -1), (unsigned char*)"1024", 4);
- verifyEntry(lpSeek(lp, -4), (unsigned char*)"hello", 5);
- assert(lpSeek(lp, 4) == NULL);
- assert(lpSeek(lp, -5) == NULL);
- lpFree(lp);
- }
-
- TEST("Iterate list from 0 to end") {
- lp = createList();
- p = lpFirst(lp);
- i = 0;
- while (p) {
- verifyEntry(p, (unsigned char*)mixlist[i], strlen(mixlist[i]));
- p = lpNext(lp, p);
- i++;
- }
- lpFree(lp);
- }
-
- TEST("Iterate list from 1 to end") {
- lp = createList();
- i = 1;
- p = lpSeek(lp, i);
- while (p) {
- verifyEntry(p, (unsigned char*)mixlist[i], strlen(mixlist[i]));
- p = lpNext(lp, p);
- i++;
- }
- lpFree(lp);
- }
-
- TEST("Iterate list from 2 to end") {
- lp = createList();
- i = 2;
- p = lpSeek(lp, i);
- while (p) {
- verifyEntry(p, (unsigned char*)mixlist[i], strlen(mixlist[i]));
- p = lpNext(lp, p);
- i++;
- }
- lpFree(lp);
- }
-
- TEST("Iterate from back to front") {
- lp = createList();
- p = lpLast(lp);
- i = 3;
- while (p) {
- verifyEntry(p, (unsigned char*)mixlist[i], strlen(mixlist[i]));
- p = lpPrev(lp, p);
- i--;
- }
- lpFree(lp);
- }
-
- TEST("Iterate from back to front, deleting all items") {
- lp = createList();
- p = lpLast(lp);
- i = 3;
- while ((p = lpLast(lp))) {
- verifyEntry(p, (unsigned char*)mixlist[i], strlen(mixlist[i]));
- lp = lpDelete(lp, p, &p);
- assert(p == NULL);
- i--;
- }
- lpFree(lp);
- }
-
- TEST("Delete whole listpack when num == -1");
- {
- lp = createList();
- lp = lpDeleteRange(lp, 0, -1);
- assert(lpLength(lp) == 0);
- assert(lp[LP_HDR_SIZE] == LP_EOF);
- assert(lpBytes(lp) == (LP_HDR_SIZE + 1));
- zfree(lp);
-
- lp = createList();
- unsigned char *ptr = lpFirst(lp);
- lp = lpDeleteRangeWithEntry(lp, &ptr, -1);
- assert(lpLength(lp) == 0);
- assert(lp[LP_HDR_SIZE] == LP_EOF);
- assert(lpBytes(lp) == (LP_HDR_SIZE + 1));
- zfree(lp);
- }
-
- TEST("Delete whole listpack with negative index");
- {
- lp = createList();
- lp = lpDeleteRange(lp, -4, 4);
- assert(lpLength(lp) == 0);
- assert(lp[LP_HDR_SIZE] == LP_EOF);
- assert(lpBytes(lp) == (LP_HDR_SIZE + 1));
- zfree(lp);
-
- lp = createList();
- unsigned char *ptr = lpSeek(lp, -4);
- lp = lpDeleteRangeWithEntry(lp, &ptr, 4);
- assert(lpLength(lp) == 0);
- assert(lp[LP_HDR_SIZE] == LP_EOF);
- assert(lpBytes(lp) == (LP_HDR_SIZE + 1));
- zfree(lp);
- }
-
- TEST("Delete inclusive range 0,0");
- {
- lp = createList();
- lp = lpDeleteRange(lp, 0, 1);
- assert(lpLength(lp) == 3);
- assert(lpSkip(lpLast(lp))[0] == LP_EOF); /* check set LP_EOF correctly */
- zfree(lp);
-
- lp = createList();
- unsigned char *ptr = lpFirst(lp);
- lp = lpDeleteRangeWithEntry(lp, &ptr, 1);
- assert(lpLength(lp) == 3);
- assert(lpSkip(lpLast(lp))[0] == LP_EOF); /* check set LP_EOF correctly */
- zfree(lp);
- }
-
- TEST("Delete inclusive range 0,1");
- {
- lp = createList();
- lp = lpDeleteRange(lp, 0, 2);
- assert(lpLength(lp) == 2);
- verifyEntry(lpFirst(lp), (unsigned char*)mixlist[2], strlen(mixlist[2]));
- zfree(lp);
-
- lp = createList();
- unsigned char *ptr = lpFirst(lp);
- lp = lpDeleteRangeWithEntry(lp, &ptr, 2);
- assert(lpLength(lp) == 2);
- verifyEntry(lpFirst(lp), (unsigned char*)mixlist[2], strlen(mixlist[2]));
- zfree(lp);
- }
-
- TEST("Delete inclusive range 1,2");
- {
- lp = createList();
- lp = lpDeleteRange(lp, 1, 2);
- assert(lpLength(lp) == 2);
- verifyEntry(lpFirst(lp), (unsigned char*)mixlist[0], strlen(mixlist[0]));
- zfree(lp);
-
- lp = createList();
- unsigned char *ptr = lpSeek(lp, 1);
- lp = lpDeleteRangeWithEntry(lp, &ptr, 2);
- assert(lpLength(lp) == 2);
- verifyEntry(lpFirst(lp), (unsigned char*)mixlist[0], strlen(mixlist[0]));
- zfree(lp);
- }
-
- TEST("Delete with start index out of range");
- {
- lp = createList();
- lp = lpDeleteRange(lp, 5, 1);
- assert(lpLength(lp) == 4);
- zfree(lp);
- }
-
- TEST("Delete with num overflow");
- {
- lp = createList();
- lp = lpDeleteRange(lp, 1, 5);
- assert(lpLength(lp) == 1);
- verifyEntry(lpFirst(lp), (unsigned char*)mixlist[0], strlen(mixlist[0]));
- zfree(lp);
-
- lp = createList();
- unsigned char *ptr = lpSeek(lp, 1);
- lp = lpDeleteRangeWithEntry(lp, &ptr, 5);
- assert(lpLength(lp) == 1);
- verifyEntry(lpFirst(lp), (unsigned char*)mixlist[0], strlen(mixlist[0]));
- zfree(lp);
- }
-
- TEST("Batch append") {
- listpackEntry ent[6] = {
- {.sval = (unsigned char*)mixlist[0], .slen = strlen(mixlist[0])},
- {.sval = (unsigned char*)mixlist[1], .slen = strlen(mixlist[1])},
- {.sval = (unsigned char*)mixlist[2], .slen = strlen(mixlist[2])},
- {.lval = 4294967296},
- {.sval = (unsigned char*)mixlist[3], .slen = strlen(mixlist[3])},
- {.lval = -100}
- };
-
- lp = lpNew(0);
- lp = lpBatchAppend(lp, ent, 2);
- verifyEntry(lpSeek(lp, 0), ent[0].sval, ent[0].slen);
- verifyEntry(lpSeek(lp, 1), ent[1].sval, ent[1].slen);
- assert(lpLength(lp) == 2);
-
- lp = lpBatchAppend(lp, &ent[2], 1);
- verifyEntry(lpSeek(lp, 0), ent[0].sval, ent[0].slen);
- verifyEntry(lpSeek(lp, 1), ent[1].sval, ent[1].slen);
- verifyEntry(lpSeek(lp, 2), ent[2].sval, ent[2].slen);
- assert(lpLength(lp) == 3);
-
- lp = lpDeleteRange(lp, 1, 1);
- verifyEntry(lpSeek(lp, 0), ent[0].sval, ent[0].slen);
- verifyEntry(lpSeek(lp, 1), ent[2].sval, ent[2].slen);
- assert(lpLength(lp) == 2);
-
- lp = lpBatchAppend(lp, &ent[3], 3);
- verifyEntry(lpSeek(lp, 0), ent[0].sval, ent[0].slen);
- verifyEntry(lpSeek(lp, 1), ent[2].sval, ent[2].slen);
- verifyEntry(lpSeek(lp, 2), (unsigned char*) "4294967296", 10);
- verifyEntry(lpSeek(lp, 3), ent[4].sval, ent[4].slen);
- verifyEntry(lpSeek(lp, 4), (unsigned char*) "-100", 4);
- assert(lpLength(lp) == 5);
-
- lp = lpDeleteRange(lp, 1, 3);
- verifyEntry(lpSeek(lp, 0), ent[0].sval, ent[0].slen);
- verifyEntry(lpSeek(lp, 1), (unsigned char*) "-100", 4);
- assert(lpLength(lp) == 2);
-
- lpFree(lp);
- }
-
- TEST("Batch insert") {
- lp = lpNew(0);
- listpackEntry ent[6] = {
- {.sval = (unsigned char*)mixlist[0], .slen = strlen(mixlist[0])},
- {.sval = (unsigned char*)mixlist[1], .slen = strlen(mixlist[1])},
- {.sval = (unsigned char*)mixlist[2], .slen = strlen(mixlist[2])},
- {.lval = 4294967296},
- {.sval = (unsigned char*)mixlist[3], .slen = strlen(mixlist[3])},
- {.lval = -100}
- };
-
- lp = lpBatchAppend(lp, ent, 4);
- assert(lpLength(lp) == 4);
- verifyEntry(lpSeek(lp, 0), ent[0].sval, ent[0].slen);
- verifyEntry(lpSeek(lp, 1), ent[1].sval, ent[1].slen);
- verifyEntry(lpSeek(lp, 2), ent[2].sval, ent[2].slen);
- verifyEntry(lpSeek(lp, 3), (unsigned char*)"4294967296", 10);
-
- /* Insert with LP_BEFORE */
- p = lpSeek(lp, 3);
- lp = lpBatchInsert(lp, p, LP_BEFORE, &ent[4], 2, &p);
- verifyEntry(p, (unsigned char*)"-100", 4);
- assert(lpLength(lp) == 6);
- verifyEntry(lpSeek(lp, 0), ent[0].sval, ent[0].slen);
- verifyEntry(lpSeek(lp, 1), ent[1].sval, ent[1].slen);
- verifyEntry(lpSeek(lp, 2), ent[2].sval, ent[2].slen);
- verifyEntry(lpSeek(lp, 3), ent[4].sval, ent[4].slen);
- verifyEntry(lpSeek(lp, 4), (unsigned char*)"-100", 4);
- verifyEntry(lpSeek(lp, 5), (unsigned char*)"4294967296", 10);
-
- lp = lpDeleteRange(lp, 1, 2);
- assert(lpLength(lp) == 4);
- verifyEntry(lpSeek(lp, 0), ent[0].sval, ent[0].slen);
- verifyEntry(lpSeek(lp, 1), ent[4].sval, ent[4].slen);
- verifyEntry(lpSeek(lp, 2), (unsigned char*)"-100", 4);
- verifyEntry(lpSeek(lp, 3), (unsigned char*)"4294967296", 10);
-
- /* Insert with LP_AFTER */
- p = lpSeek(lp, 0);
- lp = lpBatchInsert(lp, p, LP_AFTER, &ent[1], 2, &p);
- verifyEntry(p, ent[2].sval, ent[2].slen);
- assert(lpLength(lp) == 6);
- verifyEntry(lpSeek(lp, 0), ent[0].sval, ent[0].slen);
- verifyEntry(lpSeek(lp, 1), ent[1].sval, ent[1].slen);
- verifyEntry(lpSeek(lp, 2), ent[2].sval, ent[2].slen);
- verifyEntry(lpSeek(lp, 3), ent[4].sval, ent[4].slen);
- verifyEntry(lpSeek(lp, 4), (unsigned char*)"-100", 4);
- verifyEntry(lpSeek(lp, 5), (unsigned char*)"4294967296", 10);
-
- lp = lpDeleteRange(lp, 2, 4);
- assert(lpLength(lp) == 2);
- p = lpSeek(lp, 1);
- lp = lpBatchInsert(lp, p, LP_AFTER, &ent[2], 1, &p);
- verifyEntry(p, ent[2].sval, ent[2].slen);
- assert(lpLength(lp) == 3);
- verifyEntry(lpSeek(lp, 0), ent[0].sval, ent[0].slen);
- verifyEntry(lpSeek(lp, 1), ent[1].sval, ent[1].slen);
- verifyEntry(lpSeek(lp, 2), ent[2].sval, ent[2].slen);
-
- lpFree(lp);
- }
-
- TEST("Batch delete") {
- unsigned char *lp = createList(); /* char *mixlist[] = {"hello", "foo", "quux", "1024"} */
- assert(lpLength(lp) == 4); /* Pre-condition */
- unsigned char *p0 = lpFirst(lp),
- *p1 = lpNext(lp, p0),
- *p2 = lpNext(lp, p1),
- *p3 = lpNext(lp, p2);
- unsigned char *ps[] = {p0, p1, p3};
- lp = lpBatchDelete(lp, ps, 3);
- assert(lpLength(lp) == 1);
- verifyEntry(lpFirst(lp), (unsigned char*)mixlist[2], strlen(mixlist[2]));
- assert(lpValidateIntegrity(lp, lpBytes(lp), 1, NULL, NULL) == 1);
- lpFree(lp);
- }
-
- TEST("Delete foo while iterating") {
- lp = createList();
- p = lpFirst(lp);
- while (p) {
- if (lpCompare(p, (unsigned char*)"foo", 3, NULL, NULL)) {
- lp = lpDelete(lp, p, &p);
- } else {
- p = lpNext(lp, p);
- }
- }
- lpFree(lp);
- }
-
- TEST("Replace with same size") {
- lp = createList(); /* "hello", "foo", "quux", "1024" */
- unsigned char *orig_lp = lp;
- p = lpSeek(lp, 0);
- lp = lpReplace(lp, &p, (unsigned char*)"zoink", 5);
- p = lpSeek(lp, 3);
- lp = lpReplace(lp, &p, (unsigned char*)"y", 1);
- p = lpSeek(lp, 1);
- lp = lpReplace(lp, &p, (unsigned char*)"65536", 5);
- p = lpSeek(lp, 0);
- assert(!memcmp((char*)p,
- "\x85zoink\x06"
- "\xf2\x00\x00\x01\x04" /* 65536 as int24 */
- "\x84quux\05" "\x81y\x02" "\xff",
- 22));
- assert(lp == orig_lp); /* no reallocations have happened */
- lpFree(lp);
- }
-
- TEST("Replace with different size") {
- lp = createList(); /* "hello", "foo", "quux", "1024" */
- p = lpSeek(lp, 1);
- lp = lpReplace(lp, &p, (unsigned char*)"squirrel", 8);
- p = lpSeek(lp, 0);
- assert(!strncmp((char*)p,
- "\x85hello\x06" "\x88squirrel\x09" "\x84quux\x05"
- "\xc4\x00\x02" "\xff",
- 27));
- lpFree(lp);
- }
-
- TEST("Regression test for >255 byte strings") {
- char v1[257] = {0}, v2[257] = {0};
- memset(v1,'x',256);
- memset(v2,'y',256);
- lp = lpNew(0);
- lp = lpAppend(lp, (unsigned char*)v1 ,strlen(v1));
- lp = lpAppend(lp, (unsigned char*)v2 ,strlen(v2));
-
- /* Pop values again and compare their value. */
- p = lpFirst(lp);
- vstr = lpGet(p, &vlen, NULL);
- assert(strncmp(v1, (char*)vstr, vlen) == 0);
- p = lpSeek(lp, 1);
- vstr = lpGet(p, &vlen, NULL);
- assert(strncmp(v2, (char*)vstr, vlen) == 0);
- lpFree(lp);
- }
-
- TEST("Create long list and check indices") {
- lp = lpNew(0);
- char buf[32];
- int i,len;
- for (i = 0; i < 1000; i++) {
- len = snprintf(buf, sizeof(buf), "%d", i);
- lp = lpAppend(lp, (unsigned char*)buf, len);
- }
- for (i = 0; i < 1000; i++) {
- p = lpSeek(lp, i);
- vstr = lpGet(p, &vlen, NULL);
- assert(i == vlen);
-
- p = lpSeek(lp, -i-1);
- vstr = lpGet(p, &vlen, NULL);
- assert(999-i == vlen);
- }
- lpFree(lp);
- }
-
- TEST("Compare strings with listpack entries") {
- lp = createList();
- p = lpSeek(lp,0);
- assert(lpCompare(p,(unsigned char*)"hello",5,NULL,NULL));
- assert(!lpCompare(p,(unsigned char*)"hella",5,NULL,NULL));
-
- p = lpSeek(lp,3);
- assert(lpCompare(p,(unsigned char*)"1024",4,NULL,NULL));
- assert(!lpCompare(p,(unsigned char*)"1025",4,NULL,NULL));
- lpFree(lp);
- }
-
- TEST("lpMerge two empty listpacks") {
- unsigned char *lp1 = lpNew(0);
- unsigned char *lp2 = lpNew(0);
-
- /* Merge two empty listpacks, get empty result back. */
- lp1 = lpMerge(&lp1, &lp2);
- assert(lpLength(lp1) == 0);
- zfree(lp1);
- }
-
- TEST("lpMerge two listpacks - first larger than second") {
- unsigned char *lp1 = createIntList();
- unsigned char *lp2 = createList();
-
- size_t lp1_bytes = lpBytes(lp1);
- size_t lp2_bytes = lpBytes(lp2);
- unsigned long lp1_len = lpLength(lp1);
- unsigned long lp2_len = lpLength(lp2);
-
- unsigned char *lp3 = lpMerge(&lp1, &lp2);
- assert(lp3 == lp1);
- assert(lp2 == NULL);
- assert(lpLength(lp3) == (lp1_len + lp2_len));
- assert(lpBytes(lp3) == (lp1_bytes + lp2_bytes - LP_HDR_SIZE - 1));
- verifyEntry(lpSeek(lp3, 0), (unsigned char*)"4294967296", 10);
- verifyEntry(lpSeek(lp3, 5), (unsigned char*)"much much longer non integer", 28);
- verifyEntry(lpSeek(lp3, 6), (unsigned char*)"hello", 5);
- verifyEntry(lpSeek(lp3, -1), (unsigned char*)"1024", 4);
- zfree(lp3);
- }
-
- TEST("lpMerge two listpacks - second larger than first") {
- unsigned char *lp1 = createList();
- unsigned char *lp2 = createIntList();
-
- size_t lp1_bytes = lpBytes(lp1);
- size_t lp2_bytes = lpBytes(lp2);
- unsigned long lp1_len = lpLength(lp1);
- unsigned long lp2_len = lpLength(lp2);
-
- unsigned char *lp3 = lpMerge(&lp1, &lp2);
- assert(lp3 == lp2);
- assert(lp1 == NULL);
- assert(lpLength(lp3) == (lp1_len + lp2_len));
- assert(lpBytes(lp3) == (lp1_bytes + lp2_bytes - LP_HDR_SIZE - 1));
- verifyEntry(lpSeek(lp3, 0), (unsigned char*)"hello", 5);
- verifyEntry(lpSeek(lp3, 3), (unsigned char*)"1024", 4);
- verifyEntry(lpSeek(lp3, 4), (unsigned char*)"4294967296", 10);
- verifyEntry(lpSeek(lp3, -1), (unsigned char*)"much much longer non integer", 28);
- zfree(lp3);
- }
-
- TEST("lpNextRandom normal usage") {
- /* Create some data */
- unsigned char *lp = lpNew(0);
- unsigned char buf[100] = "asdf";
- unsigned int size = 100;
- for (size_t i = 0; i < size; i++) {
- lp = lpAppend(lp, buf, i);
- }
- assert(lpLength(lp) == size);
-
- /* Pick a subset of the elements of every possible subset size */
- for (unsigned int count = 0; count <= size; count++) {
- unsigned int remaining = count;
- unsigned char *p = lpFirst(lp);
- unsigned char *prev = NULL;
- unsigned index = 0;
- while (remaining > 0) {
- assert(p != NULL);
- p = lpNextRandom(lp, p, &index, remaining--, 1);
- assert(p != NULL);
- assert(p != prev);
- prev = p;
- p = lpNext(lp, p);
- index++;
- }
- }
- lpFree(lp);
- }
-
- TEST("lpNextRandom corner cases") {
- unsigned char *lp = lpNew(0);
- unsigned i = 0;
-
- /* Pick from empty listpack returns NULL. */
- assert(lpNextRandom(lp, NULL, &i, 2, 1) == NULL);
-
- /* Add some elements and find their pointers within the listpack. */
- lp = lpAppend(lp, (unsigned char *)"abc", 3);
- lp = lpAppend(lp, (unsigned char *)"def", 3);
- lp = lpAppend(lp, (unsigned char *)"ghi", 3);
- assert(lpLength(lp) == 3);
- unsigned char *p0 = lpFirst(lp);
- unsigned char *p1 = lpNext(lp, p0);
- unsigned char *p2 = lpNext(lp, p1);
- assert(lpNext(lp, p2) == NULL);
-
- /* Pick zero elements returns NULL. */
- i = 0; assert(lpNextRandom(lp, lpFirst(lp), &i, 0, 1) == NULL);
-
- /* Pick all returns all. */
- i = 0; assert(lpNextRandom(lp, p0, &i, 3, 1) == p0 && i == 0);
- i = 1; assert(lpNextRandom(lp, p1, &i, 2, 1) == p1 && i == 1);
- i = 2; assert(lpNextRandom(lp, p2, &i, 1, 1) == p2 && i == 2);
-
- /* Pick more than one when there's only one left returns the last one. */
- i = 2; assert(lpNextRandom(lp, p2, &i, 42, 1) == p2 && i == 2);
-
- /* Pick all even elements returns p0 and p2. */
- i = 0; assert(lpNextRandom(lp, p0, &i, 10, 2) == p0 && i == 0);
- i = 1; assert(lpNextRandom(lp, p1, &i, 10, 2) == p2 && i == 2);
-
- /* Don't crash even for bad index. */
- for (int j = 0; j < 100; j++) {
- unsigned char *p;
- switch (j % 4) {
- case 0: p = p0; break;
- case 1: p = p1; break;
- case 2: p = p2; break;
- case 3: p = NULL; break;
- }
- i = j % 7;
- unsigned int remaining = j % 5;
- p = lpNextRandom(lp, p, &i, remaining, 1);
- assert(p == p0 || p == p1 || p == p2 || p == NULL);
- }
- lpFree(lp);
- }
-
- TEST("Random pair with one element") {
- listpackEntry key, val;
- unsigned char *lp = lpNew(0);
- lp = lpAppend(lp, (unsigned char*)"abc", 3);
- lp = lpAppend(lp, (unsigned char*)"123", 3);
- lpRandomPair(lp, 1, &key, &val, 2);
- assert(memcmp(key.sval, "abc", key.slen) == 0);
- assert(val.lval == 123);
- lpFree(lp);
- }
-
- TEST("Random pair with many elements") {
- listpackEntry key, val;
- unsigned char *lp = lpNew(0);
- lp = lpAppend(lp, (unsigned char*)"abc", 3);
- lp = lpAppend(lp, (unsigned char*)"123", 3);
- lp = lpAppend(lp, (unsigned char*)"456", 3);
- lp = lpAppend(lp, (unsigned char*)"def", 3);
- lpRandomPair(lp, 2, &key, &val, 2);
- if (key.sval) {
- assert(!memcmp(key.sval, "abc", key.slen));
- assert(key.slen == 3);
- assert(val.lval == 123);
- }
- if (!key.sval) {
- assert(key.lval == 456);
- assert(!memcmp(val.sval, "def", val.slen));
- }
- lpFree(lp);
- }
-
- TEST("Random pair with tuple_len 3") {
- listpackEntry key, val;
- unsigned char *lp = lpNew(0);
- lp = lpAppend(lp, (unsigned char*)"abc", 3);
- lp = lpAppend(lp, (unsigned char*)"123", 3);
- lp = lpAppend(lp, (unsigned char*)"xxx", 3);
- lp = lpAppend(lp, (unsigned char*)"456", 3);
- lp = lpAppend(lp, (unsigned char*)"def", 3);
- lp = lpAppend(lp, (unsigned char*)"xxx", 3);
- lp = lpAppend(lp, (unsigned char*)"281474976710655", 15);
- lp = lpAppend(lp, (unsigned char*)"789", 3);
- lp = lpAppend(lp, (unsigned char*)"xxx", 3);
-
- for (int i = 0; i < 5; i++) {
- lpRandomPair(lp, 3, &key, &val, 3);
- if (key.sval) {
- if (!memcmp(key.sval, "abc", key.slen)) {
- assert(key.slen == 3);
- assert(val.lval == 123);
- } else {
- assert(0);
- };
- }
- if (!key.sval) {
- if (key.lval == 456)
- assert(!memcmp(val.sval, "def", val.slen));
- else if (key.lval == 281474976710655LL)
- assert(val.lval == 789);
- else
- assert(0);
- }
- }
-
- lpFree(lp);
- }
-
- TEST("Random pairs with one element") {
- int count = 5;
- unsigned char *lp = lpNew(0);
- listpackEntry *keys = zmalloc(sizeof(listpackEntry) * count);
- listpackEntry *vals = zmalloc(sizeof(listpackEntry) * count);
-
- lp = lpAppend(lp, (unsigned char*)"abc", 3);
- lp = lpAppend(lp, (unsigned char*)"123", 3);
- lpRandomPairs(lp, count, keys, vals, 2);
- assert(memcmp(keys[4].sval, "abc", keys[4].slen) == 0);
- assert(vals[4].lval == 123);
- zfree(keys);
- zfree(vals);
- lpFree(lp);
- }
-
- TEST("Random pairs with many elements") {
- int count = 5;
- lp = lpNew(0);
- listpackEntry *keys = zmalloc(sizeof(listpackEntry) * count);
- listpackEntry *vals = zmalloc(sizeof(listpackEntry) * count);
-
- lp = lpAppend(lp, (unsigned char*)"abc", 3);
- lp = lpAppend(lp, (unsigned char*)"123", 3);
- lp = lpAppend(lp, (unsigned char*)"456", 3);
- lp = lpAppend(lp, (unsigned char*)"def", 3);
- lpRandomPairs(lp, count, keys, vals, 2);
- for (int i = 0; i < count; i++) {
- if (keys[i].sval) {
- assert(!memcmp(keys[i].sval, "abc", keys[i].slen));
- assert(keys[i].slen == 3);
- assert(vals[i].lval == 123);
- }
- if (!keys[i].sval) {
- assert(keys[i].lval == 456);
- assert(!memcmp(vals[i].sval, "def", vals[i].slen));
- }
- }
- zfree(keys);
- zfree(vals);
- lpFree(lp);
- }
-
- TEST("Random pairs with many elements and tuple_len 3") {
- int count = 5;
- lp = lpNew(0);
- listpackEntry *keys = zcalloc(sizeof(listpackEntry) * count);
- listpackEntry *vals = zcalloc(sizeof(listpackEntry) * count);
-
- lp = lpAppend(lp, (unsigned char*)"abc", 3);
- lp = lpAppend(lp, (unsigned char*)"123", 3);
- lp = lpAppend(lp, (unsigned char*)"xxx", 3);
- lp = lpAppend(lp, (unsigned char*)"456", 3);
- lp = lpAppend(lp, (unsigned char*)"def", 3);
- lp = lpAppend(lp, (unsigned char*)"xxx", 3);
- lp = lpAppend(lp, (unsigned char*)"281474976710655", 15);
- lp = lpAppend(lp, (unsigned char*)"789", 3);
- lp = lpAppend(lp, (unsigned char*)"xxx", 3);
-
- lpRandomPairs(lp, count, keys, vals, 3);
- for (int i = 0; i < count; i++) {
- if (keys[i].sval) {
- if (!memcmp(keys[i].sval, "abc", keys[i].slen)) {
- assert(keys[i].slen == 3);
- assert(vals[i].lval == 123);
- } else {
- assert(0);
- };
- }
- if (!keys[i].sval) {
- if (keys[i].lval == 456)
- assert(!memcmp(vals[i].sval, "def", vals[i].slen));
- else if (keys[i].lval == 281474976710655LL)
- assert(vals[i].lval == 789);
- else
- assert(0);
- }
- }
-
- zfree(keys);
- zfree(vals);
- lpFree(lp);
- }
-
- TEST("Random pairs unique with one element") {
- unsigned picked;
- int count = 5;
- lp = lpNew(0);
- listpackEntry *keys = zmalloc(sizeof(listpackEntry) * count);
- listpackEntry *vals = zmalloc(sizeof(listpackEntry) * count);
-
- lp = lpAppend(lp, (unsigned char*)"abc", 3);
- lp = lpAppend(lp, (unsigned char*)"123", 3);
- picked = lpRandomPairsUnique(lp, count, keys, vals, 2);
- assert(picked == 1);
- assert(memcmp(keys[0].sval, "abc", keys[0].slen) == 0);
- assert(vals[0].lval == 123);
- zfree(keys);
- zfree(vals);
- lpFree(lp);
- }
-
- TEST("Random pairs unique with many elements") {
- unsigned picked;
- int count = 5;
- lp = lpNew(0);
- listpackEntry *keys = zmalloc(sizeof(listpackEntry) * count);
- listpackEntry *vals = zmalloc(sizeof(listpackEntry) * count);
-
- lp = lpAppend(lp, (unsigned char*)"abc", 3);
- lp = lpAppend(lp, (unsigned char*)"123", 3);
- lp = lpAppend(lp, (unsigned char*)"456", 3);
- lp = lpAppend(lp, (unsigned char*)"def", 3);
- picked = lpRandomPairsUnique(lp, count, keys, vals, 2);
- assert(picked == 2);
- for (int i = 0; i < 2; i++) {
- if (keys[i].sval) {
- assert(!memcmp(keys[i].sval, "abc", keys[i].slen));
- assert(keys[i].slen == 3);
- assert(vals[i].lval == 123);
- }
- if (!keys[i].sval) {
- assert(keys[i].lval == 456);
- assert(!memcmp(vals[i].sval, "def", vals[i].slen));
- }
- }
- zfree(keys);
- zfree(vals);
- lpFree(lp);
- }
-
- TEST("Random pairs unique with many elements and tuple_len 3") {
- unsigned picked;
- int count = 5;
- lp = lpNew(0);
- listpackEntry *keys = zmalloc(sizeof(listpackEntry) * count);
- listpackEntry *vals = zmalloc(sizeof(listpackEntry) * count);
-
- lp = lpAppend(lp, (unsigned char*)"abc", 3);
- lp = lpAppend(lp, (unsigned char*)"123", 3);
- lp = lpAppend(lp, (unsigned char*)"xxx", 3);
- lp = lpAppend(lp, (unsigned char*)"456", 3);
- lp = lpAppend(lp, (unsigned char*)"def", 3);
- lp = lpAppend(lp, (unsigned char*)"xxx", 3);
- lp = lpAppend(lp, (unsigned char*)"281474976710655", 15);
- lp = lpAppend(lp, (unsigned char*)"789", 3);
- lp = lpAppend(lp, (unsigned char*)"xxx", 3);
- picked = lpRandomPairsUnique(lp, count, keys, vals, 3);
- assert(picked == 3);
- for (int i = 0; i < 3; i++) {
- if (keys[i].sval) {
- if (!memcmp(keys[i].sval, "abc", keys[i].slen)) {
- assert(keys[i].slen == 3);
- assert(vals[i].lval == 123);
- } else {
- assert(0);
- };
- }
- if (!keys[i].sval) {
- if (keys[i].lval == 456)
- assert(!memcmp(vals[i].sval, "def", vals[i].slen));
- else if (keys[i].lval == 281474976710655LL)
- assert(vals[i].lval == 789);
- else
- assert(0);
- }
- }
- zfree(keys);
- zfree(vals);
- lpFree(lp);
- }
-
- TEST("push various encodings") {
- lp = lpNew(0);
-
- /* Push integer encode element using lpAppend */
- lp = lpAppend(lp, (unsigned char*)"127", 3);
- assert(LP_ENCODING_IS_7BIT_UINT(lpLast(lp)[0]));
- lp = lpAppend(lp, (unsigned char*)"4095", 4);
- assert(LP_ENCODING_IS_13BIT_INT(lpLast(lp)[0]));
- lp = lpAppend(lp, (unsigned char*)"32767", 5);
- assert(LP_ENCODING_IS_16BIT_INT(lpLast(lp)[0]));
- lp = lpAppend(lp, (unsigned char*)"8388607", 7);
- assert(LP_ENCODING_IS_24BIT_INT(lpLast(lp)[0]));
- lp = lpAppend(lp, (unsigned char*)"2147483647", 10);
- assert(LP_ENCODING_IS_32BIT_INT(lpLast(lp)[0]));
- lp = lpAppend(lp, (unsigned char*)"9223372036854775807", 19);
- assert(LP_ENCODING_IS_64BIT_INT(lpLast(lp)[0]));
-
- /* Push integer encode element using lpAppendInteger */
- lp = lpAppendInteger(lp, 127);
- assert(LP_ENCODING_IS_7BIT_UINT(lpLast(lp)[0]));
- verifyEntry(lpLast(lp), (unsigned char*)"127", 3);
- lp = lpAppendInteger(lp, 4095);
- verifyEntry(lpLast(lp), (unsigned char*)"4095", 4);
- assert(LP_ENCODING_IS_13BIT_INT(lpLast(lp)[0]));
- lp = lpAppendInteger(lp, 32767);
- verifyEntry(lpLast(lp), (unsigned char*)"32767", 5);
- assert(LP_ENCODING_IS_16BIT_INT(lpLast(lp)[0]));
- lp = lpAppendInteger(lp, 8388607);
- verifyEntry(lpLast(lp), (unsigned char*)"8388607", 7);
- assert(LP_ENCODING_IS_24BIT_INT(lpLast(lp)[0]));
- lp = lpAppendInteger(lp, 2147483647);
- verifyEntry(lpLast(lp), (unsigned char*)"2147483647", 10);
- assert(LP_ENCODING_IS_32BIT_INT(lpLast(lp)[0]));
- lp = lpAppendInteger(lp, 9223372036854775807);
- verifyEntry(lpLast(lp), (unsigned char*)"9223372036854775807", 19);
- assert(LP_ENCODING_IS_64BIT_INT(lpLast(lp)[0]));
-
- /* string encode */
- unsigned char *str = zmalloc(65535);
- memset(str, 0, 65535);
- lp = lpAppend(lp, (unsigned char*)str, 63);
- assert(LP_ENCODING_IS_6BIT_STR(lpLast(lp)[0]));
- lp = lpAppend(lp, (unsigned char*)str, 4095);
- assert(LP_ENCODING_IS_12BIT_STR(lpLast(lp)[0]));
- lp = lpAppend(lp, (unsigned char*)str, 65535);
- assert(LP_ENCODING_IS_32BIT_STR(lpLast(lp)[0]));
- zfree(str);
- lpFree(lp);
- }
-
- TEST("Test lpFind") {
- lp = createList();
- assert(lpFind(lp, lpFirst(lp), (unsigned char*)"abc", 3, 0) == NULL);
- verifyEntry(lpFind(lp, lpFirst(lp), (unsigned char*)"hello", 5, 0), (unsigned char*)"hello", 5);
- verifyEntry(lpFind(lp, lpFirst(lp), (unsigned char*)"1024", 4, 0), (unsigned char*)"1024", 4);
- lpFree(lp);
- }
-
- TEST("Test lpFindCb") {
- lp = createList(); /* "hello", "foo", "quux", "1024" */
- assert(lpFindCb(lp, lpFirst(lp), "abc", lpFindCbCmp, 0) == NULL);
- verifyEntry(lpFindCb(lp, NULL, "hello", lpFindCbCmp, 0), (unsigned char*)"hello", 5);
- verifyEntry(lpFindCb(lp, NULL, "1024", lpFindCbCmp, 0), (unsigned char*)"1024", 4);
- verifyEntry(lpFindCb(lp, NULL, "quux", lpFindCbCmp, 0), (unsigned char*)"quux", 4);
- verifyEntry(lpFindCb(lp, NULL, "foo", lpFindCbCmp, 0), (unsigned char*)"foo", 3);
- lpFree(lp);
-
- lp = lpNew(0);
- assert(lpFindCb(lp, lpFirst(lp), "hello", lpFindCbCmp, 0) == NULL);
- assert(lpFindCb(lp, lpFirst(lp), "1024", lpFindCbCmp, 0) == NULL);
- lpFree(lp);
- }
-
- TEST("Test lpValidateIntegrity") {
- lp = createList();
- long count = 0;
- assert(lpValidateIntegrity(lp, lpBytes(lp), 1, lpValidation, &count) == 1);
- lpFree(lp);
- }
-
- TEST("Test number of elements exceeds LP_HDR_NUMELE_UNKNOWN") {
- lp = lpNew(0);
- for (int i = 0; i < LP_HDR_NUMELE_UNKNOWN + 1; i++)
- lp = lpAppend(lp, (unsigned char*)"1", 1);
-
- assert(lpGetNumElements(lp) == LP_HDR_NUMELE_UNKNOWN);
- assert(lpLength(lp) == LP_HDR_NUMELE_UNKNOWN+1);
-
- lp = lpDeleteRange(lp, -2, 2);
- assert(lpGetNumElements(lp) == LP_HDR_NUMELE_UNKNOWN);
- assert(lpLength(lp) == LP_HDR_NUMELE_UNKNOWN-1);
- assert(lpGetNumElements(lp) == LP_HDR_NUMELE_UNKNOWN-1); /* update length after lpLength */
- lpFree(lp);
- }
-
- TEST("Test number of elements exceeds LP_HDR_NUMELE_UNKNOWN with batch insert") {
- listpackEntry ent[2] = {
- {.sval = (unsigned char*)mixlist[0], .slen = strlen(mixlist[0])},
- {.sval = (unsigned char*)mixlist[1], .slen = strlen(mixlist[1])}
- };
-
- lp = lpNew(0);
- for (int i = 0; i < (LP_HDR_NUMELE_UNKNOWN/2) + 1; i++)
- lp = lpBatchAppend(lp, ent, 2);
-
- assert(lpGetNumElements(lp) == LP_HDR_NUMELE_UNKNOWN);
- assert(lpLength(lp) == LP_HDR_NUMELE_UNKNOWN+1);
-
- lp = lpDeleteRange(lp, -2, 2);
- assert(lpGetNumElements(lp) == LP_HDR_NUMELE_UNKNOWN);
- assert(lpLength(lp) == LP_HDR_NUMELE_UNKNOWN-1);
- assert(lpGetNumElements(lp) == LP_HDR_NUMELE_UNKNOWN-1); /* update length after lpLength */
- lpFree(lp);
- }
-
- TEST("Stress with random payloads of different encoding") {
- unsigned long long start = usec();
- int i,j,len,where;
- unsigned char *p;
- char buf[1024];
- int buflen;
- list *ref;
- listNode *refnode;
-
- int iteration = accurate ? 20000 : 20;
- for (i = 0; i < iteration; i++) {
- lp = lpNew(0);
- ref = listCreate();
- listSetFreeMethod(ref, sdsfreegeneric);
- len = rand() % 256;
-
- /* Create lists */
- for (j = 0; j < len; j++) {
- where = (rand() & 1) ? 0 : 1;
- if (rand() % 2) {
- buflen = randstring(buf,1,sizeof(buf)-1);
- } else {
- switch(rand() % 3) {
- case 0:
- buflen = snprintf(buf,sizeof(buf),"%lld",(0LL + rand()) >> 20);
- break;
- case 1:
- buflen = snprintf(buf,sizeof(buf),"%lld",(0LL + rand()));
- break;
- case 2:
- buflen = snprintf(buf,sizeof(buf),"%lld",(0LL + rand()) << 20);
- break;
- default:
- assert(NULL);
- }
- }
-
- /* Add to listpack */
- if (where == 0) {
- lp = lpPrepend(lp, (unsigned char*)buf, buflen);
- } else {
- lp = lpAppend(lp, (unsigned char*)buf, buflen);
- }
-
- /* Add to reference list */
- if (where == 0) {
- listAddNodeHead(ref,sdsnewlen(buf, buflen));
- } else if (where == 1) {
- listAddNodeTail(ref,sdsnewlen(buf, buflen));
- } else {
- assert(NULL);
- }
- }
-
- assert(listLength(ref) == lpLength(lp));
- for (j = 0; j < len; j++) {
- /* Naive way to get elements, but similar to the stresser
- * executed from the Tcl test suite. */
- p = lpSeek(lp,j);
- refnode = listIndex(ref,j);
-
- vstr = lpGet(p, &vlen, intbuf);
- assert(memcmp(vstr,listNodeValue(refnode),vlen) == 0);
- }
- lpFree(lp);
- listRelease(ref);
- }
- printf("Done. usec=%lld\n\n", usec()-start);
- }
-
- TEST("Stress with variable listpack size") {
- unsigned long long start = usec();
- int maxsize = accurate ? 16384 : 16;
- stress(0,100000,maxsize,256);
- stress(1,100000,maxsize,256);
- printf("Done. usec=%lld\n\n", usec()-start);
- }
-
- /* Benchmarks */
- {
- int iteration = accurate ? 100000 : 100;
- lp = lpNew(0);
- TEST("Benchmark lpAppend") {
- unsigned long long start = usec();
- for (int i=0; i<iteration; i++) {
- char buf[4096] = "asdf";
- lp = lpAppend(lp, (unsigned char*)buf, 4);
- lp = lpAppend(lp, (unsigned char*)buf, 40);
- lp = lpAppend(lp, (unsigned char*)buf, 400);
- lp = lpAppend(lp, (unsigned char*)buf, 4000);
- lp = lpAppend(lp, (unsigned char*)"1", 1);
- lp = lpAppend(lp, (unsigned char*)"10", 2);
- lp = lpAppend(lp, (unsigned char*)"100", 3);
- lp = lpAppend(lp, (unsigned char*)"1000", 4);
- lp = lpAppend(lp, (unsigned char*)"10000", 5);
- lp = lpAppend(lp, (unsigned char*)"100000", 6);
- }
- printf("Done. usec=%lld\n", usec()-start);
- }
-
- TEST("Benchmark lpFind string") {
- unsigned long long start = usec();
- for (int i = 0; i < 2000; i++) {
- unsigned char *fptr = lpFirst(lp);
- fptr = lpFind(lp, fptr, (unsigned char*)"nothing", 7, 1);
- }
- printf("Done. usec=%lld\n", usec()-start);
- }
-
- TEST("Benchmark lpFind number") {
- unsigned long long start = usec();
- for (int i = 0; i < 2000; i++) {
- unsigned char *fptr = lpFirst(lp);
- fptr = lpFind(lp, fptr, (unsigned char*)"99999", 5, 1);
- }
- printf("Done. usec=%lld\n", usec()-start);
- }
-
- TEST("Benchmark lpSeek") {
- unsigned long long start = usec();
- for (int i = 0; i < 2000; i++) {
- lpSeek(lp, 99999);
- }
- printf("Done. usec=%lld\n", usec()-start);
- }
-
- TEST("Benchmark lpValidateIntegrity") {
- unsigned long long start = usec();
- for (int i = 0; i < 2000; i++) {
- lpValidateIntegrity(lp, lpBytes(lp), 1, NULL, NULL);
- }
- printf("Done. usec=%lld\n", usec()-start);
- }
-
- TEST("Benchmark lpCompare with string") {
- unsigned long long start = usec();
- for (int i = 0; i < 2000; i++) {
- unsigned char *eptr = lpSeek(lp,0);
- while (eptr != NULL) {
- lpCompare(eptr,(unsigned char*)"nothing",7,NULL,NULL);
- eptr = lpNext(lp,eptr);
- }
- }
- printf("Done. usec=%lld\n", usec()-start);
- }
-
- TEST("Benchmark lpCompare with number") {
- unsigned long long start = usec();
- for (int i = 0; i < 2000; i++) {
- unsigned char *eptr = lpSeek(lp,0);
- while (eptr != NULL) {
- lpCompare(eptr, (unsigned char*)"99999", 5, NULL, NULL);
- eptr = lpNext(lp,eptr);
- }
- }
- printf("Done. usec=%lld\n", usec()-start);
- }
-
- TEST("Benchmark lpCompare with number and caching") {
- unsigned long long start = usec();
- for (int i = 0; i < 2000; i++) {
- unsigned char *eptr = lpSeek(lp,0);
- long long cached_val = 0;
- int cached_valid = 0;
- while (eptr != NULL) {
- lpCompare(eptr, (unsigned char*)"99999", 5, &cached_val, &cached_valid);
- eptr = lpNext(lp,eptr);
- }
- }
- printf("Done. usec=%lld\n", usec()-start);
- }
-
- lpFree(lp);
- }
-
- return 0;
-}
-
-#endif