diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ae7c8b2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Prerequisites + run: | + sudo apt-get update + sudo apt-get install -y cmake libssl-dev valgrind git + git clone https://git.cryptomilk.org/projects/cmocka.git + cd cmocka + mkdir build + cd build + cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local + make + ctest + sudo make install + + shell: bash + + - name: Build and run project + run: | + export LD_LIBRARY_PATH=/usr/local/lib/ + make + working-directory: ${{github.workspace}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e11e228 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +bin/* +lib/* +.idea/* +/**/*.o +/**/*.d + +test/log/* +test/tmp/* +test/test_static_lib +test/test_lib + +examples/example1 +examples/dumps/*.json + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..29b7b7e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023, Redis Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6ee5527 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +all: + $(MAKE) -C src -f Makefile $@ + $(MAKE) -C src/ext -f Makefile $@ + $(MAKE) -C examples -f Makefile $@ + $(MAKE) -C test -f Makefile $@ + ./runtests -v + +lib: + $(MAKE) -C src -f Makefile all + $(MAKE) -C src/ext -f Makefile all + $(MAKE) -C examples -f Makefile all + +clean: + $(MAKE) -C src -f Makefile $@ + $(MAKE) -C src/ext -f Makefile $@ + $(MAKE) -C examples -f Makefile $@ + $(MAKE) -C test -f Makefile $@ + +example: + cd examples && export LD_LIBRARY_PATH=../lib && ./example1 + +test: + ./runtests + +valgrind: + ./runtests -v + +help: + @echo "Target rules:" + @echo " all - Build parser libraries, tests, and run tests." + @echo " lib - Build parser libraries." + @echo " test - Run tests with shared lib." + @echo " valgrind - Run tests with static lib and valgrind." + @echo " example - Run the example." + @echo " clean - " + @echo " help - Prints this message." + + +.PHONY: all clean test help valgrind lib \ No newline at end of file diff --git a/README.md b/README.md index 774c3e6..ad0762a 100644 --- a/README.md +++ b/README.md @@ -1 +1,341 @@ -# librdb +# librdb - DRAFT + +This is a C library for parsing RDB files. + +The Parser is implemented in the spirit of SAX parser. It fires off a series of events as +it reads the RDB file from beginning to end, and callbacks to handlers registered on +selected types of data. + +## Current status +The project is currently in its early phase and is considered to be a draft. At present, +the parser is only capable of handling string and list data types. We are actively seeking +feedback on the design, API, and implementation to refine the project before proceeding +with further development. Community contributions are welcome, yet please note that +the codebase is still undergoing significant changes and may evolve in the future. + +## Motivation behind this project +There is a genuine need by the Redis community for a versatile RDB file parser that can +export data, perform data analysis, or merely extract raw data from RDB and RESTORE it +against a live Redis server. However, available parsers have shortcomings in some aspects +such as lack of long-term support, lagging far behind the latest Redis release, and +usually not being optimized for memory, performance, or high-traffic streaming for +production environments. Additionally, most of them are not written in C, which limits the +reuse of Redis components and potential to contribute back to Redis repo. To address these +issues, it is worthwhile to develop a new parser with a modern architecture, that maybe +can also challenge the current integrated RDB parser of Redis and even replace it in the +future. + +## Getting Started +To build and run tests, you need to have cmocka unit testing framework installed and then: + + make + +If you just wish to get a basic understanding of the library's functionality, without +running tests: + + make lib + make example + +To see parser internal state printouts, execute the command `export LIBRDB_DEBUG_DATA=1` beforehand. + +### Replacing the integrated RDB parser of Redis? +It is necessary to address first the reasons and missing features in the available parser, +making replacing it a feasible option: + +1. The current parser is not designed to extend and customize it. +2. Lacks of a unit testing framework. +3. Does not support asynchronous parsing - That is, today reading of RDB sources is + possible only in blocking IO mode, without the option to let Redis thread to carry on to + other tasks and notify it asynchronously once a read operation is done. +4. Doesn’t support pause and resume capabilities - A parsing of RDB is being made from + start till completion as a single operation, without the option to indicate the parser + to pause and save its state, in order to do other tasks in between. +5. Once we decide to write RDB parser library, it is better to maintain a single parser + rather than two. + +Although it is challenging to develop a reusable and extensible parser that can match in +performance, according to our initial evaluation as long as the new parser will avoid +redundant copies and allocation of data, it is expected that it will show similar performance, +or minor degradation at most, and yet we will gain all the advantages mentioned above. Having +said that, our primary focus is to develop an “independent” RDB parser library. + +## Main building blocks +The RDB library parser composed of 3 main building blocks: + + +--------+ +--------+ +----------+ + | READER | --> | PARSER | --> | HANDLERS | + +--------+ +--------+ +----------+ + +### Reader +The **Reader** gives interface to the parser to access the RDB source. As first phase we will support: + * Reading from a file (Status: Done) + * Reading from a socket (Status: Todo) + * User defined reader (Status: Done) + + (Possible extensions might be reading from S3, gz file, or a live redis + instance) + +### Parser +The **Parser** is the core engine. It will parse RDB file and trigger registered handlers. + +The parser supports 3 sets of handlers to register, at 3 different levels of the + parsed data: + +#### Level0 - Registration on raw data +For example, if a user wants to restore from RDB source, then he doesn't care much about +the different data-types, neither the internal RDB data structure, only to get raw data of +whatever serialized and replay it against a live Redis server with RESTORE command. +In that case registration on Level0 will do the magic. + +#### Level1 - Registration on RDB data-structures +If required to analyze memory consumption, for example, then there is no escape but to +inspect "low-level" data structures of RDB file. The best way to achieve it, is +registration at level1 and pouring the logic to analyze each of the RDB data-structures +into corresponding callbacks. + +#### Level2 - Registration on Redis data-types +If we only care about DB logical data-types, for example in order to export data to +another framework, then we better register our callbacks at level2. + +Note, the set of handlers at Level1 get the data further parsed than the one "below it" at +Level0. The same goes between Level2 and Level1 correspondingly. + +### Handlers +The **Handlers** represent a set of builtin or user-defined functions that will be called on the +parsed data. Future plan to support built-in Handlers: +* Convert RDB to JSON file handlers. (Status: WIP) +* Convert RDB to RESP protocol handlers. (Status: Todo) +* Memory Analyze (Status: Todo) + +#### Using multiple sets of Handlers +It is possible to attach to parser more than one set of handlers at the same level. +That is, for a given data at a given level, the parser will call each of the handlers that +registered at that level. + +One reason to do so can be because usually retrieving RDB file is the most time-consuming +task of the parser, and it can save time by making a single parse yet invoke multiple sets +of handlers. + +More common reason is that a handlers can be used also as a Filter to decide whether to +propagate data to the next set of handlers in-line (Such built-in filters can be +found at extension library of this project). Note that for any given level, order of +calls to handlers will be the opposite to order of their registration to that level. +It's also possible to mix multiple registrations from level1 and level2, but not level0. + +## Usage +Following examples avoid error check to keep it concise. Full example can be found in +`examples` directory. + +- Converting RDB file to JSON file: + + RdbParser *parser = RDB_createParserRdb(NULL); + RDBX_createReaderFile(parser, "dump.rdb"); + RDBX_createHandlersRdb2Json(parser, encoding, "db.json", RDB_LEVEL_DATA); + RDB_parse(parser); + RDB_deleteParser(parser); /* delete also reader & Handlers */ + +- Parsing RDB file to RESP Commands: + + RdbParser *parser = RDB_createParserRdb(NULL); + RDBX_createReaderFile(parser, "dump.rdb"); + RDBX_CreateHandlersRdbRaw2Redis(parser, encoding, sockfd_Redis); + RDB_parse(parser); + RDB_deleteParser(parser); + +- Parsing RDB file with user callbacks: + + RdbRes myHandleNewKey(RdbParser *p, void *userData, RdbBulk key,...) { + printf("%s\n", key); + return RDB_OK; + } + + RdbParser *parser = RDB_createParserRdb(NULL); + RDBX_createReaderFile(parser, "dump.rdb"); + RdbHandlersRawCallbacks callbacks = { .handleNewKey = myHandleNewKey }; + RDB_createHandlersRaw(parser, &callbacks, myUserData, NULL); + RDB_parse(parser); + RDB_deleteParser(parser); + +- Use builtin Handlers (filters) to propagate only specific keys + + RdbParser *parser = RDB_createParserRdb(NULL); + RDBX_createReaderFile(parser, "dump.rdb"); + RDBX_createHandlersRdb2Json(parser, encoding, "redis.json", RDB_LEVEL_DATA); + RDBX_createHandlersFilterKey(parser, "id_*", 0, RDB_LEVEL_DATA); + RDB_parse(parser); + RDB_deleteParser(parser); + +- Parsing in memory data (without reader) + + unsigned char rdbContent[] = {'R', 'E', 'D', 'I', 'S', .... }; + RdbParser *parser = RDB_createParserRdb(NULL); + RDBX_createHandlersRdb2Json(parser, encoding, "redis.json", RDB_LEVEL_DATA); + RDB_parseBuff(parser, rdbContent, sizeof(rdbContent), 1 /*EOF*/); + RDB_deleteParser(parser); + + +Whether it is Reader or Handlers, once a new block is created, it is being attached to the +parser and the parse will take ownership and will release the blocks either during its own +destruction, or when newer block replacing old one. + +## Advanced +### Customized Reader +The built-in readers should be sufficient for most purposes. However, if they do not meet +your specific needs, you can use the `RDB_createReaderRdb()` helper function to create a +custom reader with its own reader function. The built-in reader file +([readerFile.c](src/ext/readerFile.c)) can serve as a code reference for this purpose. + +### Asynchronous parser +The parser has been designed to handle asynchronous situation where it may temporarily +not have data to read from the RDB-reader, or not feed yet with more input buffers. + +Building on what was discussed previously, a reader can be implemented to support +asynchronous reads by returning `RDB_STATUS_WAIT_MORE_DATA` for read requests. In such a +case, it's necessary to provide a mechanism for indicating to the application when the +asynchronous operation is complete, so the application can call `RDB_parse()` again. The +async indication for read completion from the customized reader to the application is +beyond the scope of this library. A conceptual invocation of such flow can be: + + myAsyncReader = RDB_createReaderRdb(p, myAsyncRdFunc, myAsyncRdData, myAsyncRdDeleteFunc); + while(RDB_parse(p) == RDB_STATUS_WAIT_MORE_DATA) { + my_reader_completed_await(myAsyncReader); + } + +Another way to work asynchronously with the parser is just feeding the parser with chunks +of streamed buffers by using the `RDB_parseBuff()` function: + + int parseRdb2Json(int file_descriptor, const char *fnameOut) + { + RdbStatus status; + const int BUFF_SIZE = 200000; + RdbParser *parser = RDB_createParserRdb(NULL); + RDBX_createHandlersRdb2Json(parser, encoding, fnameOut, RDB_LEVEL_DATA); + void *buf = malloc(BUFF_SIZE); + do { + int bytes_read = read(file_descriptor, buf, BUFF_SIZE); + if (bytes_read < 0) break; /* error */ + status = RDB_parseBuff(parser, buf, bytes_read, bytes_read == 0); + } while (status == RDB_STATUS_WAIT_MORE_DATA); + RDB_deleteParser(parser); + free(buf); + } + +### Cancel parser execution +To cancel parsing in the middle of execution, the trigger should come from the registered +handlers, Simply by returning `RDB_ERR_CANCEL_PARSING`. If the parser is using builtin +handlers for parsing, and yet, you want that the parser will stop when some condition is +met, then it is required to write a dedicated customized handlers, user-defined callbacks, +to give this indication and register it as well. + +### Pause parser and resume +At times, the application may need to execute additional tasks during parsing intervals, +such as updating a progress bar or performing other computations. To facilitate this, the +parser can be configured with a pause interval that specifies the number of bytes to be +read from RDB source before pausing. This means that each time the parser is invoked, it +will continue parsing until it has read a number of bytes equal to or greater than the +configured interval, at which point it will automatically pause and return +'RDB_STATUS_PAUSED' in order to allow the application to perform other tasks. Example: + + size_t intervalBytes = 1048576; + RdbParser *parser = RDB_createParserRdb(memAlloc); + RDBX_createReaderFile(parser, "dump.rdb"); + RDBX_createHandlersRdb2Json(parser, encoding, "db.json", RDB_LEVEL_DATA); + RDB_setPauseInterval(parser, intervalBytes); + while (RDB_parse(parser) == RDB_STATUS_PAUSED) { + /* do something else in between */ + } + RDB_deleteParser(parser); + +Note, if pause interval has elapsed and at the same time the parser need to return +indication to wait for more data, then the parser will suppress pause indication and +return `RDB_STATUS_WAIT_MORE_DATA` instead. + +However, there may be cases where it is more appropriate for the callback handlers to +determine when to suspend the parser. In such cases, the callback should call +`RDB_pauseParser()` to pause the parser. Note that, the parser may still call one or a few +more callbacks before actual pausing. + +Special cautious should be given when using this feature along with `RDB_parseBuff()`. +Since the parser doesn't owns the buffer it reads from, when the application intends to +call again to `RDB_parseBuff()` to resume parsing after pause, it must call with the same +buffer that it supplied before the pause and only partially processed. The function +`RDB_parseBuff()` will verify that the buffer reference is identical as before and continue +with the same offset it reached. This also implies that the buffer must remain persistent +in such a scenario. Whereas it might seem redundant API to pass again the same values on +resume, yet it highlights the required persistence of the reused buffer. + +## Implementation notes +The Redis RDB file format consists of a series of opcodes followed by the actual data that +is being persisted. Each opcode represents a specific operation that needs to be performed +when encoding or decoding data. The parsing process has been organized into separate +**parsing-elements** which primarily align with RDB opcodes. Each parsing-element that +correspond to RDB opcode usually carries out the following steps: + + 1. Reads from RDB file required amount of data to process current state of parsing-element. + 2. If required, calls app's callbacks or store the parsed data for later use. + 3. Updates the state of the next parsing-element to be called. + +### Async support +As mentioned above, instead of blocking the parser on read command, the reader (and in +turn the parser) can return to the caller `RDB_STATUS_WAIT_MORE_DATA` and will be ready to +be called again to continue parsing once more data become available. + +In such scenario, any incomplete parsing-element will preserve its current state. As for +any data that has already been read from the RDB reader - it cannot be read again from the +reader. To address this issue, a **serialized-pool** data structure is used to route all +data being read from the RDB reader. It stores a reference to the allocations in a queue, +and enable to **rollback** and replay later once more data becomes available, in an +attempt to complete the parsing-element state. The **rollback** command basically rewinds +the queue of allocation and allows the exact same sequence of allocation requests to be +provided to the caller, however, instead of creating new allocations, the allocator +returns the next item in the queue. Otherwise, if the parser managed to reach a new +parsing-element state, then all cached data in the pool will be **flushed**. + +Serialized-pool is also known as parsing-element's **cache**. To learn more about it, +refer to the comment at the start of the file [bulkAlloc.h](src/bulkAlloc.h). + +### Parsing-Element states +Having gained understanding of the importance of serialized-pool rollback and replay for +async parsing, it is necessary to address two crucial questions: + + 1. If we are parsing, say a large list, is it necessary for the parser to rollback all + the way to the beginning of current parsing-element? + 2. If the parser has already called app callbacks, will it call them again on rollback? + +#### 1. Parsing-element internal states +It is essential to break down complex parsing elements with multiple items into internal +iterative states. This ensures that any asynchronous event will cause the parser to +rollback to its last valid iterative state, rather than all the way back to the beginning +of the parsing element. For instance, in the case of a list opcode, the corresponding +parsing element (See function `elementList`) will comprise an entry state that parses +the number of elements in the list from the RDB file and an iterative state that parses +each subsequent node in the list. In case of rollback only the last node will be parsed +again rather than parsing the entire list from start. + +The parsing-element cache gets flushed on each parsing-element state transition. This +prevents the parser from reading outdated buffers from the cache that belong to the +previous state in case of a rollback scenario, ensuring that consecutive states are +clearly differentiated. + +#### 2. Defining Safe-state +Regarding the second question, before making a callback to the application, the parsing +element must first ensure that its state reached a **safe state**. That is, there should +be no new attempts to read the RDB file until the end of current state that may result in +rollbacks. Otherwise, on rollback, the parser may end up calling the same application +callback more than once. + +### Caching and garbage-collector +As mentioned above the parsing-element cache will be flushed whenever next parsing-element +is set or when the parsing-element state is updated. This way we also gain along the way +a cache with garbage collector capabilities at hand that can also serve parsing-element +for its own internal allocations, for example, after reading compressed data from RDB +file, the parser can allocate a new buffer from cache for decompression operation without +the worry to release it at the end or in case of an error. + +### State machine parser +The parsing-elements in the implementation are partially designed using a state machine +approach. Each parsing-element calls indirectly the next one, and the main parsing loop +triggers the next parsing element through the `parserMainLoop` function. This +approach not only adds an extra layer of control to the parser along execution steps, but +also enables parsing of customized RDB files or even specific parts of the file. This +functionality can be further enhanced as needed. diff --git a/api/librdb-api.h b/api/librdb-api.h new file mode 100644 index 0000000..86ef7a8 --- /dev/null +++ b/api/librdb-api.h @@ -0,0 +1,386 @@ +#ifndef LIBRDB_API_H +#define LIBRDB_API_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _LIBRDB_API +#define _LIBRDB_API +#endif + +/**************************************************************** + * Incomplete structures for compiler checks but opaque access + ****************************************************************/ + +typedef char *RdbBulk; +typedef char *RdbBulkCopy; + +typedef struct RdbReader RdbReader; +typedef struct RdbParser RdbParser; +typedef struct RdbHandlers RdbHandlers; +typedef struct RdbMemAlloc RdbMemAlloc; + +/**************************************************************** + * Enums & Typedefs + ****************************************************************/ + +typedef enum RdbRes { + + RDB_OK=0, + + /* RDB_OK_DONT_PROPAGATE - allowed to be returned only by handlers + * callbacks to prevent propagation of the data to the next + * Handlers in-line (of the same callback type). It can be useful to + * implement sets of Handlers as Filters. As reference, see + * implementation RDBX_createHandlersFilterKey */ + RDB_OK_DONT_PROPAGATE, + + /* Handlers callbacks can indicate to cancel parsing immediately */ + RDB_ERR_CANCEL_PARSING, + + /*** error codes - reported by parser's blocks ***/ + + RDB_ERR_GENERAL, + + RDB_ERR_FAILED_OPEN_LOG_FILE, + RDB_ERR_FAILED_READ_RDB_FILE, + RDB_ERR_NO_MEMORY, + RDB_ERR_FAILED_OPEN_RDB_FILE, + RDB_ERR_WRONG_FILE_SIGNATURE, + RDB_ERR_WRONG_FILE_VERSION, + RDB_ERR_FAILED_PARTIAL_READ_RDB_FILE, + RDB_ERR_PARSER_RETURNED_INVALID_LIBRDB_STATUS, + RDB_ERR_INVALID_LEN_ENCODING, + RDB_ERR_INVALID_INT_ENCODING, + RDB_ERR_STRING_INVALID_LZF_COMPRESSED, + RDB_ERR_STRING_UNKNOWN_ENCODING_TYPE, + RDB_ERR_NOT_SUPPORTED_RDB_ENCODING_TYPE, + RDB_ERR_UNKNOWN_RDB_ENCODING_TYPE, + RDB_ERR_QUICK_LIST_INTEG_CHECK, + RDB_ERR_STRING_INVALID_STATE, + RDB_ERR_QUICK_LIST_INVALID_STATE, + RDB_ERR_INVALID_BULK_ALLOC_TYPE, + RDB_ERR_INVALID_BULK_CLONE_REQUEST, + RDB_ERR_INVALID_BULK_LENGTH_REQUEST, + RDB_ERR_SP_INVALID_ALLOCATION_TYPE, + RDB_ERR_INVALID_IS_REF_BULK, + RDB_ERR_EXP_EOF_BUT_PARSER_WAIT_MORE_DATA, + RDB_ERR_EXP_WAIT_MORE_DATA_BUT_PARSER_EOF, + RDB_ERR_CHECKSUM_FAILURE, + RDB_ERR_PARSEBUF_AFTER_PAUSE_NOT_SAME_BUFF, + RDB_ERR_MAX_RAW_LEN_EXCEEDED_FOR_KEY, + RDB_ERR_EXCLUSIVE_RAW_HANDLERS, + + /*** api-ext error codes (see file: rp-ext-api.h) ***/ + _RDB_ERR_EXTENSION_FIRST = 0x1000, + + /*** user-defined error codes - reported by user-defined handlers or reader ***/ + _RDB_ERR_USER_DEFINED_FIRST = 0x2000, + +} RdbRes; + +typedef enum RdbState { + RDB_STATECONFIGURING=0, + RDB_STATERUNNING, + RDB_STATEPAUSED, + RDB_STATEENDED, + RDB_STATEERROR, +} RdbState; + +typedef enum RdbStatus { + RDB_STATUS_OK = 0, + RDB_STATUS_WAIT_MORE_DATA, + RDB_STATUS_PAUSED, + RDB_STATUS_ERROR +} RdbStatus; + +typedef enum RdbHandlersLevel { + RDB_LEVEL_RAW=0, /* A set of handlers that get raw data */ + RDB_LEVEL_STRUCT, /* A set of handlers that get "low level" RDB data structures */ + RDB_LEVEL_DATA, /* A set of handlers that get "high level" Redis data types */ + RDB_LEVEL_MAX, +} RdbHandlersLevel; + +typedef enum RdbLogLevel { + RDB_LOG_ERROR, + RDB_LOG_WARNNING, + RDB_LOG_INFO, + RDB_LOG_DBG +} RdbLogLevel; + +/* for explanation, read "Memory management" section below */ +typedef enum RdbBulkAllocType { + RDB_BULK_ALLOC_STACK, + RDB_BULK_ALLOC_HEAP, + RDB_BULK_ALLOC_EXTERN, + RDB_BULK_ALLOC_EXTERN_OPT, + RDB_BULK_ALLOC_MAX, +} RdbBulkAllocType; + +typedef struct RdbKeyInfo { + long long expiretime; + uint64_t lru_idle; + int lfu_freq; +} RdbKeyInfo; + +/* misc function pointer typedefs */ +typedef RdbStatus (*RdbReaderFunc) (RdbParser *p, void *readerData, void *buf, size_t len); +typedef void (*RdbFreeFunc) (RdbParser *p, void *obj); +typedef void (*RdbLoggerCB) (RdbLogLevel l, const char *msg); + +/**************************************************************** + * Handlers callbacks struct + ****************************************************************/ + +/* TODO: Pass "RdbParser *p" for each cb? User can hold it as well in "userData" */ + +/* avoid nested structures */ +#define HANDLERS_COMMON_CALLBACKS \ + RdbRes (*handleModuleDatatype)(RdbParser *p, void *userData, RdbBulk value); \ + RdbRes (*handleNewDb)(RdbParser *p, void *userData, int db); \ + RdbRes (*handleEndRdb)(RdbParser *p, void *userData); \ + RdbRes (*handleDbSize)(RdbParser *p, void *userData, uint64_t db_size, uint64_t exp_size); \ + RdbRes (*handleAuxField)(RdbParser *p, void *userData, RdbBulk auxkey, RdbBulk auxval); \ + RdbRes (*handleNewKey)(RdbParser *p, void *userData, RdbBulk key, RdbKeyInfo *info); \ + RdbRes (*handleEndKey)(RdbParser *p, void *userData); + +typedef struct RdbHandlersRawCallbacks { + HANDLERS_COMMON_CALLBACKS + RdbRes (*handleBegin)(RdbParser *p, void *userData, size_t size); + RdbRes (*handleFrag)(RdbParser *p, void *userData, RdbBulk frag); + RdbRes (*handleEnd)(RdbParser *p, void *userData); +// RdbRes (*handleBeginModuleAux)(RdbParser *p, void *userData, RdbBulk name, int encver, int when); +} RdbHandlersRawCallbacks; + +typedef struct RdbHandlersStructCallbacks { + HANDLERS_COMMON_CALLBACKS + RdbRes (*handleStringValue)(RdbParser *p, void *userData, RdbBulk str); + RdbRes (*handlerQListNode)(RdbParser *p, void *userData, RdbBulk listNode); + RdbRes (*handlerPlainNode)(RdbParser *p, void *userData, RdbBulk node); + /* ... TBD ... */ +} RdbHandlersStructCallbacks; + +typedef struct RdbHandlersDataCallbacks { + HANDLERS_COMMON_CALLBACKS + RdbRes (*handleStringValue)(RdbParser *p, void *userData, RdbBulk str); + RdbRes (*handleListElement)(RdbParser *p, void *userData, RdbBulk str); +// RdbRes (*handleSetElement)(RdbParser *p, void *userData, RdbBulk str, unsigned long sizeHint); +// RdbRes (*handleZsetElement)(RdbParser *p, void *userData, RdbBulk str, double score, unsigned long sizeHint); +// RdbRes (*handleHashElement)(RdbParser *p, void *userData, RdbBulk field, RdbBulk value, unsigned long sizeHint); + /* ... TBD ... */ +} RdbHandlersDataCallbacks; + +/**************************************************************** + * Parser creation and deletion + ****************************************************************/ +_LIBRDB_API RdbParser *RDB_createParserRdb(RdbMemAlloc *memAlloc); +_LIBRDB_API void RDB_deleteParser(RdbParser *p); + +/**************************************************************** + * Execute parser + ****************************************************************/ +_LIBRDB_API RdbStatus RDB_parse(RdbParser *p); + +/* parse in mem buffers without a reader */ +_LIBRDB_API RdbStatus RDB_parseBuff(RdbParser *p, + unsigned char *buff, + size_t size, int isEOF); + +/**************************************************************** + * Create Reader + * Used by: RDBX_createReaderFile + * + ****************************************************************/ +_LIBRDB_API RdbReader *RDB_createReaderRdb(RdbParser *p, + RdbReaderFunc r, + void *readerData, + RdbFreeFunc freeReaderData); + +/**************************************************************** + * Create Handlers + * + * Create set of handlers, at requested level (lvl), to be filled up with + * callback handlers. + * + * Used by: RDBX_createHandlersRdb2Json + * RDBX_createHandlersFilterKey + * + ****************************************************************/ +_LIBRDB_API RdbHandlers *RDB_createHandlersRaw(RdbParser *p, + RdbHandlersRawCallbacks *callbacks, + void *userData, + RdbFreeFunc freeUserData); + +_LIBRDB_API RdbHandlers *RDB_createHandlersStruct(RdbParser *p, + RdbHandlersStructCallbacks *callbacks, + void *userData, + RdbFreeFunc freeUserData); + +_LIBRDB_API RdbHandlers *RDB_createHandlersData(RdbParser *p, + RdbHandlersDataCallbacks *callbacks, + void *userData, + RdbFreeFunc freeUserData); + +/**************************************************************** + * Handlers prevent data propagation + * + * When Handlers is being called by the parser, it can decide not to propagate + * the data to the next Handlers in-line. It can be useful to implement sets of + * Handlers as Filters. Use this function only from inside Handlers callbacks. + * As reference, see implementation of RDBX_createHandlersFilterKey + ****************************************************************/ + _LIBRDB_API void RDB_dontPropagate(RdbParser *p); + +/**************************************************************** + * Parser setters & getters + ****************************************************************/ + +_LIBRDB_API void RDB_setMaxRawLenHandling(RdbParser *p, size_t size); +_LIBRDB_API void RDB_setDeepIntegCheck(RdbParser *p, int deep); +_LIBRDB_API size_t RDB_getBytesProcessed(RdbParser *p); +_LIBRDB_API RdbState RDB_getState(RdbParser *p); +_LIBRDB_API void RDB_IgnoreChecksum(RdbParser *p); + +/* logger */ +_LIBRDB_API void RDB_setLogLevel(RdbParser *p, RdbLogLevel l); +_LIBRDB_API void RDB_setLogger(RdbParser *p, RdbLoggerCB f); +_LIBRDB_API void RDB_log(RdbParser *p, RdbLogLevel lvl, const char *format, ...); + +/**************************************************************** + * Pause the Parser + * + * the parser can be configured with a pause interval that specifies the number + * of bytes to be processed before pausing. This means that each time the parser + * is invoked, it will continue parsing until it has processed a number of bytes + * equal to or greater than the configured interval, at which point it will + * automatically pause and return 'RDB_STATUS_PAUSED' in order to allow the + * application to perform other tasks. It is also possible to pause the parser + * by the callbacks by calling RDB_pauseParser() + ****************************************************************/ +_LIBRDB_API void RDB_setPauseInterval(RdbParser *p, size_t interval); + +_LIBRDB_API void RDB_pauseParser(RdbParser *p); + +/**************************************************************** + * Error Handling + * + * If the parser, or attached built-in handlers or reader got failed, they will + * make an internal call to `RDB_reportError` to keep the error. The way to the + * app to retrieve the error is via `RDB_getErrorCode` and `RDB_getErrorMessage`. + * + * Likewise, if one of the app's callback handlers got failed, it should report + * the error to the parser by function `RDB_reportError` and also can provide + * description to the error. Either way, app's callbaack must return the error + * code. (See RdbRes enum for valid range of user-defined error-code to app to + * report). + ****************************************************************/ +_LIBRDB_API RdbRes RDB_getErrorCode(RdbParser *p); +_LIBRDB_API const char *RDB_getErrorMessage(RdbParser *p); +_LIBRDB_API void RDB_reportError(RdbParser *p, RdbRes e, const char *msg, ...); + +/**************************************************************** + * Memory management + * + * User can configure his own set of malloc/free functions via structure + * RdbMemAlloc + * + * If library is used in a multi-threaded application, then functions must be + * thread safe. In that case, library is thread-safe as well. + * + * When functions are not set or set to NULL then, they are set to internal + * routines that use the standard library functions malloc() and free() + * + * Optimizing Bulk Allocation + * + * On callback, sometimes the client application need to receive the processed + * payload wrapped in specific structure, such as, with additional header. To + * save the extra copy, the app can provide the parser its own allocator only + * for RdbBulk (and specify that bulkAllocType is of type RDB_BULK_ALLOC_EXTERN). + * In that case the parser will allocate with provided allocation function and + * copy the plain payload along with '\0' termination (without any headers or + * trailers). Note that actual allocation sometimes might be larger than than + * RDB_bulkLen + 1 (Plus one for the termination character). + * + * On the other hand, if the parser heap allocation is sufficient, and the + * client application doesn't care about the wrapping structure of RdbBulk, + * then set bulkAllocType to RDB_BULK_ALLOC_HEAP. Note that `RDB_bulkClone` in + * that case will only increment refcount. + * + * If the client app not going to use too much `RDB_bulkClone` (cloning bulks. + * See below.) then it is better to set RDB_BULK_ALLOC_STACK, such that, when + * possible allocation quickly be made on internal stack. + * + * As mentioned above, RDB_BULK_ALLOC_EXTERN can help to avoid redundant copies + * of bulks. Yet, there are some cases that the parser can avoid bulk allocation + * of any type because the data is already available in memory, either because + * data prefetched, preceding in-memory decompress chunk of data, etc. If the + * application wants to optimize those cases as well along with external + * allocator, it can configure bulkAllocType to RDB_BULK_ALLOC_EXTERN_OPT. But + * then not all the given RdbBulk to callbacks will be allocated by provided + * allocation function and the app's callback cannot make any assumption about + * RdbBulk allocation. To decide in this case if given RdbBulk is actually + * allocated by configured external allocator or only reference another section + * of memory, the callback will need to assist function `RDB_isRefBulk`. + * TODO: Mark callbacks that might return referenced bulk + ****************************************************************/ +struct RdbMemAlloc { + void *(*malloc)(size_t size); + void *(*realloc)(void *ptr, size_t size); + void (*free)(void *ptr); + + RdbBulkAllocType bulkAllocType; + + /* appBulk is relevant only if bulkAllocType equals RDB_BULK_ALLOC_EXTERN or + * RDB_BULK_ALLOC_EXTERN_OPT */ + struct { + void *(*alloc)(size_t size); + void *(*clone)(void *ptr, size_t size); + void (*free)(void *ptr); + } appBulk; +}; + +/* Memory allocation functions to be used by Reader & Handlers extensions */ +void *RDB_alloc(RdbParser *p, size_t size); +void *RDB_realloc(RdbParser *p, void *ptr, size_t size); +void RDB_free(RdbParser *p, void *ptr); + +/**************************************************************** + * RdbBulk - native c-string alike + * + * The purpose of RdbBulk is to give native c-string feeling, yet hiding whether + * it actually allocated behind on stack, heap, reference another memory, or + * externally allocated by user supplied RdbBulk allocation function. + * + * In order to process the string behind the current call-stack, function + * `RDB_bulkClone()` is the way to clone a string, within callback context only! + * If bulk allocated on stack (default of bulkAllocType) or reference another + * memory, then the function will malloc memory on heap and return a copy to the + * user. If allocated on heap, then just a refcount will be incremented. + * + * IF configured external allocator, then corresponding clone callback will be + * made, giving the opportunity to the application client to clone in its own + * way. In that mode the parser will only copy afterward the plain payload along + * with '\0' termination (It might seem like redundant to application to clone + * something that it knows to work with natively, but it is still required for + * 3rd party handlers that only familiar with the parser API). + * + * Note that the returned value of RDB_bulkClone has a distinct typedef, called + * RdbBulkCopy, in order to differentiate the functions that are allowed to apply + * on RdbBulk than the one allowed to apply on RdbBulkCopy. + ****************************************************************/ +_LIBRDB_API RdbBulkCopy RDB_bulkClone(RdbParser *p, RdbBulk b); + +_LIBRDB_API void RDB_bulkFree(RdbParser *p, RdbBulkCopy b); + +_LIBRDB_API size_t RDB_bulkLen(RdbParser *p, RdbBulk b); + +int RDB_isRefBulk(RdbParser *p, RdbBulk b); + +#ifdef __cplusplus +} +#endif + +#endif //LIBRDB_API_H diff --git a/api/librdb-ext-api.h b/api/librdb-ext-api.h new file mode 100644 index 0000000..43c64d4 --- /dev/null +++ b/api/librdb-ext-api.h @@ -0,0 +1,59 @@ +#include "librdb-api.h" +#ifndef LIBRDB_API_EXT_H +#define LIBRDB_API_EXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************** +* Enums & Typedefs +****************************************************************/ + +typedef enum { + RDBX_CONV_JSON_ENC_PLAIN, + RDBX_CONV_JSON_ENC_BASE64 +} RdbxConvJsonEnc; + +typedef enum { + RDBX_ERR_READER_FILE_GENERAL_ERROR = _RDB_ERR_EXTENSION_FIRST, + + /* rdb2json errors */ + RDBX_ERR_R2J_FAILED_OPEN_FILE, + RDBX_ERR_R2J_INVALID_STATE, + + /* HandlersFilterKey errors */ + RDBX_ERR_R2J_FAILED_COMPILING_REGEX, + +} RdbxRes; + +/**************************************************************** + * Create Reader extensions + ****************************************************************/ + +_LIBRDB_API RdbReader *RDBX_createReaderFile(RdbParser *parser, const char *filename); +_LIBRDB_API RdbReader *RDBX_createReaderSocket(int fd); + +/**************************************************************** + * Create Handlers extensions + ****************************************************************/ + +_LIBRDB_API RdbHandlers *RDBX_createHandlersRdb2Json(RdbParser *p, + RdbxConvJsonEnc encoding, + const char *filename, + RdbHandlersLevel lvl); + +/**************************************************************** + * Create Filters extensions + ****************************************************************/ + +_LIBRDB_API RdbHandlers *RDBX_createHandlersFilterKey(RdbParser *p, + const char *keyRegex, + uint32_t flags, + RdbHandlersLevel level); + +#ifdef __cplusplus +} +#endif + +#endif //LIBRDB_API_EXT_H diff --git a/bin/.gitkeep b/bin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 0000000..1ca564d --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,33 @@ +default: all + +LIB_NAME = rdb +LIB_DIR = ../lib +LIB_NAME_EXT = $(LIB_NAME)-ext + +# Artifacts: +TARGET_TEST = example1 + +######################################################################################### +SOURCES = $(notdir $(basename $(wildcard *.c))) +OBJECTS = $(patsubst %,%.o,$(SOURCES)) +TARGETS = $(basename $(SOURCES)) + +CC = gcc +STD = -std=gnu99 +STACK = -fstack-protector-all -Wstack-protector +WARNS = -Wall -Wextra -pedantic -Werror +CFLAGS = -fPIC -O3 $(STD) $(STACK) $(WARNS) +DEBUG = -g3 -DDEBUG=1 +LIBS = -L /usr/lib -L $(LIB_DIR) -l $(LIB_NAME) -l $(LIB_NAME_EXT) + +######################################### RULES ####################################### +all: $(TARGET_TEST) + @echo "Done."; + +$(TARGET_TEST): %: %.c + $(CC) $(CFLAGS) -o $@ $< $(DEBUG) $(LIBS) + +clean: + @rm -rvf $(TARGETS) ./*.o; + +.PHONY: all clean \ No newline at end of file diff --git a/examples/dumps/multiple_lists_strings.rdb b/examples/dumps/multiple_lists_strings.rdb new file mode 100644 index 0000000..52dd0d0 Binary files /dev/null and b/examples/dumps/multiple_lists_strings.rdb differ diff --git a/examples/example1.c b/examples/example1.c new file mode 100644 index 0000000..2d84bdf --- /dev/null +++ b/examples/example1.c @@ -0,0 +1,74 @@ +/* The following C file serves as an illustration of how to use the librdb + * library for transforming Redis RDB files into JSON format. If you wish to see + * the various parsing components being invoked in the background, simply set + * the environment variable ENV_VAR_DEBUG_DATA to 1. + * + * $ export LIBRDB_DEBUG_DATA=1 + * $ make example + * + */ +#include +#include +#include "../api/librdb-api.h" /* RDB library header */ +#include "../api/librdb-ext-api.h" /* RDB library extension header */ + + +void logger(RdbLogLevel l, const char *msg) { + static char *logLevelStr[] = { + [RDB_LOG_ERROR] = "| ERROR |", + [RDB_LOG_WARNNING] = "| WARN |", + [RDB_LOG_INFO] = "| INFO |", + [RDB_LOG_DBG] = "| DEBUG |", + }; + printf("%s %s\n", logLevelStr[l], msg); +} + +/******************************************************************* + * Example of RDB to Json file conversion. It also shows the usage + * of two FilterKey. + *******************************************************************/ +int main() { + RdbParser *parser; + RdbReader *reader; + RdbHandlers *handlers; + RdbHandlers *filterKey; + RdbRes err=RDB_OK; + + const char *infile = "./dumps/multiple_lists_strings.rdb"; + const char *outfile = "./dumps/multiple_lists_strings.json"; + + parser = RDB_createParserRdb(NULL); + if (!parser) { + logger(RDB_LOG_ERROR, "Failed to create parser"); + return RDB_ERR_GENERAL; + } + + RDB_setLogger(parser, logger); + + reader = RDBX_createReaderFile(parser, infile); + if (!reader) goto PARSER_ERROR; + + /* Create RDB2JSON built-in Handlers */ + handlers = RDBX_createHandlersRdb2Json(parser, RDBX_CONV_JSON_ENC_PLAIN, outfile, RDB_LEVEL_DATA); + if (!handlers) goto PARSER_ERROR; + + /* Filter keys that starts with the word `mylist` */ + filterKey = RDBX_createHandlersFilterKey(parser, "mylist.*", 0 /*flags*/, RDB_LEVEL_DATA); + if (!filterKey) goto PARSER_ERROR; + + /* Run the parser */ + RdbStatus status = RDB_parse(parser); + if (status != RDB_STATUS_OK) goto PARSER_ERROR; + printf ("Parsed successfully RDB to JSON file: %s\n", outfile); + + goto PARSER_END; + +PARSER_ERROR: + err = RDB_getErrorCode(parser); + assert(err != RDB_OK); + logger(RDB_LOG_ERROR, RDB_getErrorMessage(parser)); + +PARSER_END: + RDB_deleteParser(parser); + return err; +} \ No newline at end of file diff --git a/lib/.gitkeep b/lib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/runtests b/runtests new file mode 100755 index 0000000..f9cc670 --- /dev/null +++ b/runtests @@ -0,0 +1,33 @@ +#!/bin/bash + +VALGRIND=0 + +while [[ $# -gt 0 ]]; do + case $1 in + -v|--valgrind) + shift # past argument + VALGRIND=1 + ;; + *|-h) + echo "Usage: $(basename $0) [-v|--valgrind]" + exit 1 + ;; + esac +done + +export LD_LIBRARY_PATH="`pwd`/lib"${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} + +if [[ ${VALGRIND} -ne 0 ]]; then + valgrind \ + --track-origins=yes \ + --leak-check=full \ + --leak-resolution=high \ + --error-exitcode=1 \ + --log-file=test/log/valgrind.log \ + ./test/test_static_lib + sed -n -e '/SUMMARY:/,$p' ./test/log/valgrind.log + echo -en "\n(Entire log available at: ./test/log/valgrind.log)\n" + +else + ./test/test_lib +fi diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..42becb6 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,54 @@ +default: all + +LIB_NAME = rdb +LIB_DIR = ../lib + +# Artifacts: +TARGET_LIB = $(LIB_DIR)/lib$(LIB_NAME).so +TARGET_LIB_STATIC = $(LIB_DIR)/lib$(LIB_NAME).a + +# Source files in the working directory +SOURCES = $(notdir $(basename $(wildcard *.c))) +OBJECTS = $(patsubst %,%.o,$(SOURCES)) + +# Source files in deps/redis directory +DEPS_SOURCES = $(notdir $(basename $(wildcard deps/redis/*.c))) +DEPS_OBJECTS = $(patsubst %,deps/redis/%.o,$(DEPS_SOURCES)) + +EXTERN_RP_CONFIG= +CC = gcc +STD = -std=gnu99 +STACK = -fstack-protector-all -Wstack-protector +WARNS = -Wall -Wextra -pedantic -Werror +CFLAGS = -fPIC -O3 $(STD) $(STACK) $(WARNS) $(EXTERN_RP_CONFIG) +DEBUG = -g3 -DDEBUG=1 +LIBS = + +######################################### RULES ####################################### +all: $(TARGET_LIB) $(TARGET_LIB_STATIC) + @echo "Done."; + +$(TARGET_LIB): $(OBJECTS) $(DEPS_OBJECTS) + $(CC) -o $@ -shared ${LDFLAGS} $^ + +$(TARGET_LIB_STATIC): $(OBJECTS) $(DEPS_OBJECTS) + ar rcs $@ $^ + +# Include object file dependencies +-include $(OBJECTS:.o=.d) $(DEPS_OBJECTS:.o=.d) + +# Compile source files in the working directory to object files +%.o: %.c + $(CC) -fPIC $(CFLAGS) -c $*.c -o $*.o $(DEBUG) $(LIBS) + $(CC) -MM $(CFLAGS) $*.c > $*.d + +# Compile source files in deps/redis directory to object files +deps/redis/%.o: deps/redis/%.c + @mkdir -p deps/redis + $(CC) $(CFLAGS) -c $< -o $@ $(DEBUG) $(LIBS) + $(CC) -MM $(CFLAGS) $< > $(@:.o=.d) + +clean: + @rm -rvf $(TARGET_LIB) $(TARGET_LIB_STATIC) ./*.o ./*.d deps/redis/*.o deps/redis/*.d; + +.PHONY: all clean diff --git a/src/bulkAlloc.c b/src/bulkAlloc.c new file mode 100644 index 0000000..39e7d3a --- /dev/null +++ b/src/bulkAlloc.c @@ -0,0 +1,454 @@ +#include +#include +#include +#include "utils.h" +#include "bulkAlloc.h" + +#define STACK_SIZE 100000 +#define LEN_ALLOC_FROM_STACK_MAX_SIZE (1024*16 - 40) +#define INIT_QUEUE_SIZE 20 +#define BULK_MAGIC 0xdead + +typedef struct SerializedStack { + int size; + char *buf; + char *writePtr; + RdbMemAlloc mem; +} SerializedStack; + +/* Heap allocation (with refcount) */ +typedef struct { + unsigned short magic; + unsigned short refcount; +} BulkHeapHdr; + +struct SerializedPool { + BulkInfo *queue; + int writeIdx; + int readIdx; + int queueSize; + + RdbMemAlloc mem; + SerializedStack *stack; +}; + +/* Serialized Pool */ +static inline BulkInfo *serPoolEnqueue(SerializedPool *sp); +static inline BulkType serPoolResolveAllocType(RdbParser *p, AllocTypeRq typeRq); + +/* Heap bulk */ +static inline RdbBulk heapBulkAlloc(RdbParser *p, size_t size); +static inline RdbBulk heapBulkIncrRef(RdbBulk b); +static inline void heapBulkDecrRef(RdbParser *p, RdbBulk b); + +/* Serialized Stack */ +static RdbBulk serStackAlloc(SerializedStack *stack, unsigned short buf_size); +static inline void serStackFlush(SerializedStack *stack); +static SerializedStack * sertackInit(RdbMemAlloc *mem, int size); +static void serStackRelease(SerializedStack *stack); + +/*** LIB API functions ***/ + +_LIBRDB_API void RDB_bulkFree(RdbParser *p, RdbBulkCopy b) { + switch (p->mem.bulkAllocType) { + case RDB_BULK_ALLOC_STACK: + /* fall through - Note that even when bulkAllocType is set to RDB_BULK_ALLOC_STACK, + * calling to RDB_bulkClone() will return allocation on heap + */ + case RDB_BULK_ALLOC_HEAP: + heapBulkDecrRef(p, (RdbBulk) b); + break; + case RDB_BULK_ALLOC_EXTERN: + case RDB_BULK_ALLOC_EXTERN_OPT: + p->mem.appBulk.free( (RdbBulk) b); + break; + default: + RDB_reportError(p, RDB_ERR_INVALID_BULK_ALLOC_TYPE, + "RDB_bulkFree(): Invalid bulk allocation type: %d", p->mem.bulkAllocType); + break; + } +} + +/*** Serialized Pool ***/ + +SerializedPool *serPoolInit(RdbMemAlloc *mem) { + + SerializedPool *sp = (SerializedPool *) mem->malloc(sizeof(SerializedPool)); + sp->queue = (BulkInfo *) mem->malloc(INIT_QUEUE_SIZE * sizeof(BulkInfo)); + sp->readIdx = sp->writeIdx = 0; + sp->queueSize = INIT_QUEUE_SIZE; + sp->stack = sertackInit(mem, STACK_SIZE); + sp->mem = *mem; + return sp; +} + +void serPoolRelease(RdbParser *p) { + SerializedPool *sp = p->cache; + if (!sp) return; + + serPoolFlush(p); + RDB_free(p, sp->queue); + serStackRelease(sp->stack); + RDB_free(p, sp); + p->cache = NULL; +} + +static void serPoolAllocNew(RdbParser *p, size_t len, BulkType type, char *refBuf, BulkInfo *binfo) { + size_t lenIncNewline = len + 1; + binfo->len = len; + binfo->written = 0; + + switch(type) { + case BULK_TYPE_STACK: + if ((lenIncNewline < LEN_ALLOC_FROM_STACK_MAX_SIZE) && + (binfo->ref = serStackAlloc(p->cache->stack, lenIncNewline)) != NULL) { + binfo->bulkType = BULK_TYPE_STACK; + break; + } + + /* fall through - `len` too big or stack is full. Alloc from heap instead */ + case BULK_TYPE_HEAP: + binfo->ref = heapBulkAlloc(p, lenIncNewline); + binfo->bulkType = BULK_TYPE_HEAP; + break; + + case BULK_TYPE_EXTERN: + binfo->ref = p->cache->mem.appBulk.alloc(lenIncNewline); + binfo->bulkType = BULK_TYPE_EXTERN; + break; + + case BULK_TYPE_REF: + assert (refBuf != NULL); + binfo->ref = refBuf; + binfo->bulkType = BULK_TYPE_REF; + break; + + default: + RDB_reportError(p, RDB_ERR_SP_INVALID_ALLOCATION_TYPE, + "serPoolAllocNew() Serialized pool received invalid allocation type request: %d", type); + assert(0); + } +} + +/* Allocate memory, either new buffer or retrieve from queue. If requested to allocate + * application bulk (RQ_ALLOC_APP_BULK) then lookup what bulk allocation type is + * configured by the application (can be either stack, heap or external). Otherwise it + * is just an internal allocation of the parser, either from stack or heap. + * + * Note that for most cases internal allocations of the parser will be on stack. The + * few cases that it will allocate it on heap is when it needs to preserve data + * across states. + */ +BulkInfo *serPoolAlloc(RdbParser *p, size_t len, AllocTypeRq typeRq, RdbBulk refBuf) { + BulkInfo *binfo; + SerializedPool *sp = p->cache; + + /* if no cached buffers in queue (i.e. first time to read this data) + * then allocate new buffer and fill it from reader */ + if (sp->readIdx == sp->writeIdx) { + binfo = serPoolEnqueue(sp); + BulkType type = serPoolResolveAllocType(p, typeRq); + serPoolAllocNew(p, len, type, refBuf, binfo); + } else { + binfo = &(sp->queue[sp->readIdx]); + + /* assert allocation request (after rollback) has exact same length as before */ + if (len != sp->queue[sp->readIdx].len) + assert (len == sp->queue[sp->readIdx].len); + } + + + ++sp->readIdx; + return binfo; +} + +void serPoolFlush(RdbParser *p) { + SerializedPool *sp = p->cache; + for (int i = 0 ; i < sp->writeIdx ; ++i) { + /* release all bulks that are not allocated in stack */ + switch(sp->queue[i].bulkType) { + case BULK_TYPE_REF: + break; + case BULK_TYPE_STACK: + break; + case BULK_TYPE_HEAP: + heapBulkDecrRef(p, sp->queue[i].ref); + break; + case BULK_TYPE_EXTERN: + sp->mem.appBulk.free(sp->queue[i].ref); + break; + default: + RDB_reportError(p, RDB_ERR_INVALID_BULK_ALLOC_TYPE, + "serPoolFlush(): Invalid bulk allocation type: %d", sp->queue[i].bulkType); + break; + } + } + sp->readIdx = sp->writeIdx = 0; + serStackFlush(sp->stack); +} + +void serPoolRollback(RdbParser *p) { + SerializedPool *sp = p->cache; + sp->readIdx = 0; +} + +void serPoolPrintDbg(RdbParser *p) { + SerializedPool *sp = p->cache; + printf("*********************************************************\n"); + printf("Serialized Pool Info:\n"); + printf(" queue size: %d\n", sp->queueSize); + printf(" queue address: %p\n", (void *) sp->queue); + printf(" queue read index: %d\n", sp->readIdx); + printf(" queue write index: %d\n", sp->writeIdx); + printf(" stack start address: %p\n", sp->stack->buf); + printf(" stack write address: %p\n", sp->stack->writePtr); + + printf("Serialized Pool - Queue items: \n"); + for ( int i = 0; i != sp->writeIdx ; ++i) + printf(" - [allocType=%d] [written=%lu] %p: \"0x%X 0x%X ...\":\"%s\" (len=%lu) \n", + sp->queue[i].bulkType, + sp->queue[i].written, + sp->queue[i].ref, + ((unsigned char *)sp->queue[i].ref)[0], + ((unsigned char *)sp->queue[i].ref)[1], + (unsigned char *)sp->queue[i].ref, sp->queue[i].len); + printf("\n*********************************************************\n"); +} + +RdbBulkCopy serPoolCloneItem(RdbParser *p, BulkInfo *binfo) { + size_t lenIncNewline = binfo->len + 1; + switch(binfo->bulkType) { + case BULK_TYPE_HEAP: { + /* if buffer allocated on heap, just incref counter */ + return (RdbBulkCopy) heapBulkIncrRef(binfo->ref); + } + case BULK_TYPE_EXTERN: + /* use external clone() */ + return (RdbBulkCopy) p->mem.appBulk.clone(binfo->ref, lenIncNewline); + + /* referenced bulk or allocated on stack. We have to malloc and copy */ + case BULK_TYPE_STACK: + case BULK_TYPE_REF: { + RdbBulkCopy bulkcopy; + + /* need to use configured bulk allocator */ + switch (p->mem.bulkAllocType) { + case RDB_BULK_ALLOC_STACK: + case RDB_BULK_ALLOC_HEAP: + bulkcopy = heapBulkAlloc(p, lenIncNewline); + return memcpy(bulkcopy, binfo->ref, lenIncNewline); + + case RDB_BULK_ALLOC_EXTERN: + case RDB_BULK_ALLOC_EXTERN_OPT: + bulkcopy = p->mem.appBulk.alloc(lenIncNewline); + return memcpy(bulkcopy, binfo->ref, lenIncNewline); + + default: + RDB_reportError(p, RDB_ERR_INVALID_BULK_ALLOC_TYPE, + "serPoolCloneItem(): Invalid bulk allocation type: %d", p->mem.bulkAllocType); + return NULL; + } + } + + default: + RDB_reportError(p, RDB_ERR_INVALID_BULK_ALLOC_TYPE, + "serPoolCloneItem() Invalid bulk allocation type: %d", binfo->bulkType); + return NULL; + } +} + +int serPoolIsNewNextAllocDbg(RdbParser *p) { + SerializedPool *sp = p->cache; + return (sp->writeIdx == sp->readIdx) ? 1 : 0; +} + +static inline BulkInfo *serPoolEnqueue(SerializedPool *sp) { + sp->writeIdx += 1; + if (unlikely(sp->writeIdx == sp->queueSize)) { + sp->queueSize *= 2; + sp->queue = realloc(sp->queue, sp->queueSize * sizeof(BulkInfo)); + } + return &(sp->queue[sp->writeIdx-1]); +} + +static inline BulkType serPoolResolveAllocType(RdbParser *p, AllocTypeRq typeRq) { + + static const BulkType rqAlloc2spAllocType[RQ_ALLOC_MAX][RDB_BULK_ALLOC_MAX] = { + + /* parser request alloc for internal use. Better try alloc from stack than heap */ + [RQ_ALLOC] = { + BULK_TYPE_STACK, /* RDB_BULK_ALLOC_STACK */ + BULK_TYPE_STACK, /* RDB_BULK_ALLOC_HEAP */ + BULK_TYPE_STACK, /* RDB_BULK_ALLOC_EXTERN */ + BULK_TYPE_STACK, /* RDB_BULK_ALLOC_EXTERN_OPT */ + }, + + /* For internal use, parser request to ref another memory (in order to rdbLoad() it + * with data from RDB source and be resilient in case of rollback flow) */ + [RQ_ALLOC_REF] = { + BULK_TYPE_REF, /* RDB_BULK_ALLOC_STACK */ + BULK_TYPE_REF, /* RDB_BULK_ALLOC_HEAP */ + BULK_TYPE_REF, /* RDB_BULK_ALLOC_EXTERN */ + BULK_TYPE_REF, /* RDB_BULK_ALLOC_EXTERN_OPT */ + }, + + /* parser requests alloc RdbBulk that will be given to app callbacks */ + [RQ_ALLOC_APP_BULK] = { + BULK_TYPE_STACK, /* RDB_BULK_ALLOC_STACK - App configured stack. Try use internal stack alloc */ + BULK_TYPE_HEAP, /* RDB_BULK_ALLOC_HEAP - App configured heap. Use heap allocation */ + BULK_TYPE_EXTERN, /* RDB_BULK_ALLOC_EXTERN - App configured external allocator */ + BULK_TYPE_EXTERN, /* RDB_BULK_ALLOC_EXTERN_OPT */ + }, + + + /* parser requests to alloc RdbBulk that only reference to another memory */ + [RQ_ALLOC_APP_BULK_REF] = { + /* If an application configured for heap or stack allocation in an effort to enhance + * its performance, we can safely return a reference-bulk. This is because + * we do not copy any data in either case, and the application cannot differentiate + * since memory allocation function was configured internally. + */ + BULK_TYPE_REF, /* RDB_BULK_ALLOC_STACK */ + BULK_TYPE_REF, /* RDB_BULK_ALLOC_HEAP */ + + /* if app configure specific external allocator for bulks, then parser must obey, + * even at the cost of another copy */ + BULK_TYPE_EXTERN, /* RDB_BULK_ALLOC_EXTERN */ + + /* if app configured RDB_BULK_ALLOC_EXTERN_OPT, then let's just return reference + * bulk when possible. In this case the application callbacks cannot make any + * assumption about the allocated memory layout of RdbBulk. It can assist function + * RDB_isRefBulk to resolve whether given bulk was allocated by its external + * allocator or optimized with reference bulk. + */ + BULK_TYPE_REF, /* RDB_BULK_ALLOC_EXTERN_OPT */ + }, + }; + + return rqAlloc2spAllocType[typeRq][p->mem.bulkAllocType]; +} + +/*** Serialized Stack ***/ + +static SerializedStack * sertackInit(RdbMemAlloc *mem, int size) { + SerializedStack *stack = (SerializedStack *) mem->malloc(sizeof(SerializedStack)); + stack->buf = mem->malloc(size); + stack->writePtr = stack->buf; + stack->size = size; + stack->mem = *mem; + return stack; +} + +static void serStackRelease(SerializedStack *stack) { + stack->mem.free(stack->buf); + stack->mem.free(stack); +} + +static RdbBulk serStackAlloc(SerializedStack *stack, unsigned short buf_size) { + int written = stack->writePtr - stack->buf; + + if (unlikely( buf_size > stack->size - written)) { + return NULL; + } + + void *ptr = stack->writePtr; + stack->writePtr += buf_size; + return ptr; +} + +static inline void serStackFlush(SerializedStack *stack) { + stack->writePtr = stack->buf; +} + +/*** Heap Bulk ***/ + +static inline RdbBulk heapBulkAlloc(RdbParser *p, size_t size) { + + BulkHeapHdr *header = (BulkHeapHdr *)RDB_alloc(p, sizeof(BulkHeapHdr) + size); + header->magic = BULK_MAGIC; + header->refcount = 1; + return (RdbBulk) (header + 1); +} + +static inline void heapBulkDecrRef(RdbParser *p, RdbBulk b) { + BulkHeapHdr *header = (BulkHeapHdr *)b - 1; + assert(header->magic == BULK_MAGIC); + if (--header->refcount == 0) { + p->mem.free(header); + } +} + +static inline RdbBulk heapBulkIncrRef(RdbBulk b) { + BulkHeapHdr *header = (BulkHeapHdr *)b - 1; + assert(header->magic == BULK_MAGIC); + header->refcount++; + return b; +} + +/*** unmanaged allocations ***/ + +static inline BulkType resolveAllocTypeUnmanaged(RdbParser *p, AllocUnmngTypeRq rq) { + static const BulkType rqAlloc2spAllocType[UNMNG_RQ_ALLOC_MAX][RDB_BULK_ALLOC_MAX] = { + + /* parser request alloc RdbBulk for internal use. Only alloc from heap + * (Stack is flushed on each state transition) */ + [UNMNG_RQ_ALLOC] = { + BULK_TYPE_HEAP, /* RDB_BULK_ALLOC_STACK */ + BULK_TYPE_HEAP, /* RDB_BULK_ALLOC_HEAP */ + BULK_TYPE_HEAP, /* RDB_BULK_ALLOC_EXTERN */ + BULK_TYPE_HEAP, /* RDB_BULK_ALLOC_EXTERN_OPT */ + }, + + /* parser requests alloc RdbBulk that will be given to app callbacks */ + [UNMNG_RQ_ALLOC_APP_BULK] = { + BULK_TYPE_HEAP, /* RDB_BULK_ALLOC_STACK */ + BULK_TYPE_HEAP, /* RDB_BULK_ALLOC_HEAP */ + BULK_TYPE_EXTERN, /* RDB_BULK_ALLOC_EXTERN */ + BULK_TYPE_EXTERN, /* RDB_BULK_ALLOC_EXTERN_OPT */ + }, + + /* parser requests to alloc RdbBulk that only ref another memory */ + [UNMNG_RQ_ALLOC_APP_BULK_REF] = { + BULK_TYPE_REF, /* RDB_BULK_ALLOC_STACK */ + BULK_TYPE_REF, /* RDB_BULK_ALLOC_HEAP */ + + /* if app configure specific external allocator for bulks, then parser + * must obey, even at the cost of another copy */ + BULK_TYPE_EXTERN, /* RDB_BULK_ALLOC_EXTERN */ + + BULK_TYPE_REF, /* RDB_BULK_ALLOC_EXTERN_OPT */ + }, + + }; + return rqAlloc2spAllocType[rq][p->mem.bulkAllocType]; +} + +void allocUnmanagedBulk(RdbParser *p, size_t len, AllocUnmngTypeRq rq, char *refBuf, BulkInfo *bi) { + BulkType type = resolveAllocTypeUnmanaged(p, rq); + serPoolAllocNew(p, len, type, refBuf, bi); +} + +void freeUnmanagedBulk(RdbParser *p, BulkInfo *binfo) { + + if (unlikely(binfo->ref == NULL)) + return; + + switch(binfo->bulkType) { + case BULK_TYPE_REF: + /* nothing to do */ + break; + case BULK_TYPE_HEAP: + heapBulkDecrRef(p, binfo->ref); + break; + case BULK_TYPE_EXTERN: + p->mem.appBulk.free(binfo->ref); + break; + case BULK_TYPE_STACK: + /* fall through */ + default: + RDB_reportError(p, RDB_ERR_INVALID_BULK_ALLOC_TYPE, + "serPoolFlush(): Invalid bulk allocation type: %d", binfo->bulkType); + break; + } + binfo->ref = NULL; +} \ No newline at end of file diff --git a/src/bulkAlloc.h b/src/bulkAlloc.h new file mode 100644 index 0000000..0dbaa85 --- /dev/null +++ b/src/bulkAlloc.h @@ -0,0 +1,85 @@ +/* + * This API propose two ways to allocate and release bulks of memory: + * + * 1) Unmanaged RdbBulk allocation + * + * Unmanaged bulk allocation is a method of allocating memory where the parser + * manages the allocation and deallocation of memory rather than relying on a data + * structure like SerializedPool (which gets flushed on each state transition). + * This method is useful when the application has RdbBulk allocations that needs + * to live behind current state of the parser. + * + * + * 2) Serialized-Pool RdbBulk allocation (managed) + * + * This data structure is useful in the context of RDBParser that support asynchronous + * execution (but not only). In such cases, the parser might receive only partial data and + * needs to preserve its last valid state and return. SerializedPool helps preserve the + * partial data already read from the RDB source, which cannot be re-read such as in + * the case of streaming. The data can then be "replayed" later once more data becomes + * available to complete the parsing-element state. + * + * The SerializedPool support 3 commands: + * + * a) Allocate - Allocates memory per caller request and store a reference + * to the allocation in a sequential queue. + * b) Rollback - The "Rollback" command rewinds the queue and allows the exact same + * sequence of allocation requests to be "replayed" to the caller. + * However, instead of creating new allocations, the allocator returns + * the next item in the queue. + * c) Flush - Clean the entire queue and deletes corresponding referenced buffers. + * + * The SerializedPool utilizes three distinct types of allocators: + * a) Stack allocator + * b) Heap allocator + * c) External allocator. + * + * The Stack Allocator (SerializedStack) is specifically designed to work in tandem with + * SerializedPool and supports the Allocate, Rollback, and Flush commands. When the + * SerializedPool receives small allocation requests and the application has not + * restricted allocation to a specific type, it prefers to allocate from the stack. If + * the parser fails to reach a new state, SerializedStack will be rolled back in order to + * replay. If the parser reaches a new state, then the stack will be flushed. + * + * The Heap Allocator (HeapBulk) allocates memory from the heap with refcount support. + * + * The External Allocator is not mandatory and can be provided by the application client + * to allocate only the buffers that will be passed to the application's callbacks. These + * buffers are referred to as RQ_ALLOC_APP_BULK within the SerializedPool API. + * + * In addition, serialized pool supports Reference allocator of a Bulk. It expects to + * receive pre-allocated memory and record it as a referenced bulk. The first use case for + * it is when there is allocated memory that is already initialized with data and the + * parser just want to optimize and pass it as RdbBulk to callbacks. Another use case is + * when memory was allocated, but it is not loaded with data yet by rdbLoad functions, then + * by registration to SerializedPool it will be able to load it safely with rdbLoad functions + * without worry from rollback flows. + * + */ + +#ifndef BULK_ALLOC_H +#define BULK_ALLOC_H + +#include "parser.h" + +/* creation and destroy */ +SerializedPool *serPoolInit(RdbMemAlloc *mem); +void serPoolRelease(RdbParser *p); + +/* allocation, flush & rollback */ +BulkInfo *serPoolAlloc(RdbParser *p, size_t len, AllocTypeRq typeRq, RdbBulk refBuf); +void serPoolFlush(RdbParser *p); +void serPoolRollback(RdbParser *p); + +/* cloning RdbBulk */ +RdbBulkCopy serPoolCloneItem(RdbParser *p, BulkInfo *binfo); + +/* debug */ +void serPoolPrintDbg(RdbParser *p); +int serPoolIsNewNextAllocDbg(RdbParser *p); + +/* Unmanaged */ +void allocUnmanagedBulk(RdbParser *p, size_t len, AllocUnmngTypeRq rq, char *refBuf, BulkInfo *bi); +void freeUnmanagedBulk(RdbParser *p, BulkInfo *binfo); + +#endif /*BULK_ALLOC_H*/ \ No newline at end of file diff --git a/src/defines.h b/src/defines.h new file mode 100644 index 0000000..fd8470c --- /dev/null +++ b/src/defines.h @@ -0,0 +1,99 @@ +/* This file should include only RDB related defines. Might be read in the + * future from Redis repo */ + +#define RDB_VERSION 11 + +/* Map object types to RDB object types. Macros starting with OBJ_ are for + * memory storage and may change. Instead RDB types must be fixed because + * we store them on disk. */ +#define RDB_TYPE_STRING 0 +#define RDB_TYPE_LIST 1 +#define RDB_TYPE_SET 2 +#define RDB_TYPE_ZSET 3 +#define RDB_TYPE_HASH 4 +#define RDB_TYPE_ZSET_2 5 /* ZSET version 2 with doubles stored in binary. */ +#define RDB_TYPE_MODULE_PRE_GA 6 /* Used in 4.0 release candidates */ +#define RDB_TYPE_MODULE_2 7 /* Module value with annotations for parsing without + the generating module being loaded. */ +/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */ + +/* Object types for encoded objects. */ +#define RDB_TYPE_HASH_ZIPMAP 9 +#define RDB_TYPE_LIST_ZIPLIST 10 +#define RDB_TYPE_SET_INTSET 11 +#define RDB_TYPE_ZSET_ZIPLIST 12 +#define RDB_TYPE_HASH_ZIPLIST 13 +#define RDB_TYPE_LIST_QUICKLIST 14 +#define RDB_TYPE_STREAM_LISTPACKS 15 +#define RDB_TYPE_HASH_LISTPACK 16 +#define RDB_TYPE_ZSET_LISTPACK 17 +#define RDB_TYPE_LIST_QUICKLIST_2 18 +#define RDB_TYPE_STREAM_LISTPACKS_2 19 +#define RDB_TYPE_SET_LISTPACK 20 +#define RDB_TYPE_STREAM_LISTPACKS_3 21 +/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */ + +/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */ +#define RDB_OPCODE_FUNCTION2 245 /* function library data */ +#define RDB_OPCODE_FUNCTION 246 /* old function library data for 7.0 rc1 and rc2 */ +#define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */ +#define RDB_OPCODE_IDLE 248 /* LRU idle time. */ +#define RDB_OPCODE_FREQ 249 /* LFU frequency. */ +#define RDB_OPCODE_AUX 250 /* RDB aux field. */ +#define RDB_OPCODE_RESIZEDB 251 /* Hash table resize hint. */ +#define RDB_OPCODE_EXPIRETIME_MS 252 /* Expire time in milliseconds. */ +#define RDB_OPCODE_EXPIRETIME 253 /* Old expire time in seconds. */ +#define RDB_OPCODE_SELECTDB 254 /* DB number of the following keys. */ +#define RDB_OPCODE_EOF 255 /* End of the RDB file. */ + +/* Garantia V1002 RDB opcodes -- used to read older version RDB files. */ +/*#define REDIS_RDB_2_OPCODE_GD_DICT 249*/ +#define REDIS_RDB_2_OPCODE_GOPTIONS 250 +#define REDIS_RDB_2_OPCODE_GCAS 251 +#define REDIS_RDB_2_OPCODE_GFLAGS 252 + +/* Garantia V1006 (current) opcodes */ +/*#define RDB_OPCODE_GD_DICT 100*/ +#define RDB_OPCODE_GCAS 101 +#define RDB_OPCODE_GFLAGS 102 + +/* Defines related to the dump file format. To store 32 bits lengths for short + * keys requires a lot of space, so we check the most significant 2 bits of + * the first byte to interpreter the length: + * + * 00|XXXXXX => if the two MSB are 00 the len is the 6 bits of this byte + * 01|XXXXXX XXXXXXXX => 01, the len is 14 bits, 6 bits + 8 bits of next byte + * 10|000000 [32 bit integer] => A full 32 bit len in net byte order will follow + * 10|000001 [64 bit integer] => A full 64 bit len in net byte order will follow + * 11|OBKIND this means: specially encoded object will follow. The six bits + * number specify the kind of object that follows. + * See the RDB_ENC_* defines. + * + * Lengths up to 63 are stored using a single byte, most DB keys, and may + * values, will fit inside. */ +#define RDB_6BITLEN 0 +#define RDB_14BITLEN 1 +#define RDB_32BITLEN 0x80 +#define RDB_64BITLEN 0x81 +#define RDB_ENCVAL 3 +#define RDB_LENERR UINT64_MAX + +/* When a length of a string object stored on disk has the first two bits + * set, the remaining six bits specify a special encoding for the object + * accordingly to the following defines: */ +#define RDB_ENC_INT8 0 /* 8 bit signed integer */ +#define RDB_ENC_INT16 1 /* 16 bit signed integer */ +#define RDB_ENC_INT32 2 /* 32 bit signed integer */ +#define RDB_ENC_LZF 3 /* string compressed with FASTLZ */ +/*#define RDB_ENC_GD 4 string is a gdcompressed entry */ + +/* rdbLoad...() functions flags. */ +#define RDB_LOAD_NONE 0 +#define RDB_LOAD_ENC (1<<0) +#define RDB_LOAD_PLAIN (1<<1) +#define RDB_LOAD_SDS (1<<2) + + +/* quicklist node container formats */ +#define QUICKLIST_NODE_CONTAINER_PLAIN 1 +#define QUICKLIST_NODE_CONTAINER_PACKED 2 diff --git a/src/deps/redis/Make_file b/src/deps/redis/Make_file new file mode 100644 index 0000000..e69de29 diff --git a/src/deps/redis/crc64.c b/src/deps/redis/crc64.c new file mode 100644 index 0000000..73e0391 --- /dev/null +++ b/src/deps/redis/crc64.c @@ -0,0 +1,161 @@ +/* Copyright (c) 2014, Matt Stancliff + * Copyright (c) 2020, Amazon Web Services + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ + +#include "crc64.h" +#include "crcspeed.h" +static uint64_t crc64_table[8][256] = {{0}}; + +#define POLY UINT64_C(0xad93d23594c935a9) +/******************** BEGIN GENERATED PYCRC FUNCTIONS ********************/ +/** + * Generated on Sun Dec 21 14:14:07 2014, + * by pycrc v0.8.2, https://www.tty1.net/pycrc/ + * + * LICENSE ON GENERATED CODE: + * ========================== + * As of version 0.6, pycrc is released under the terms of the MIT licence. + * The code generated by pycrc is not considered a substantial portion of the + * software, therefore the author of pycrc will not claim any copyright on + * the generated code. + * ========================== + * + * CRC configuration: + * Width = 64 + * Poly = 0xad93d23594c935a9 + * XorIn = 0xffffffffffffffff + * ReflectIn = True + * XorOut = 0x0000000000000000 + * ReflectOut = True + * Algorithm = bit-by-bit-fast + * + * Modifications after generation (by matt): + * - included finalize step in-line with update for single-call generation + * - re-worked some inner variable architectures + * - adjusted function parameters to match expected prototypes. + *****************************************************************************/ + +/** + * Reflect all bits of a \a data word of \a data_len bytes. + * + * \param data The data word to be reflected. + * \param data_len The width of \a data expressed in number of bits. + * \return The reflected data. + *****************************************************************************/ +static inline uint_fast64_t crc_reflect(uint_fast64_t data, size_t data_len) { + uint_fast64_t ret = data & 0x01; + + for (size_t i = 1; i < data_len; i++) { + data >>= 1; + ret = (ret << 1) | (data & 0x01); + } + + return ret; +} + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + ******************************************************************************/ +uint64_t _crc64(uint_fast64_t crc, const void *in_data, const uint64_t len) { + const uint8_t *data = in_data; + unsigned long long bit; + + for (uint64_t offset = 0; offset < len; offset++) { + uint8_t c = data[offset]; + for (uint_fast8_t i = 0x01; i & 0xff; i <<= 1) { + bit = crc & 0x8000000000000000; + if (c & i) { + bit = !bit; + } + + crc <<= 1; + if (bit) { + crc ^= POLY; + } + } + + crc &= 0xffffffffffffffff; + } + + crc = crc & 0xffffffffffffffff; + return crc_reflect(crc, 64) ^ 0x0000000000000000; +} + +/******************** END GENERATED PYCRC FUNCTIONS ********************/ + +/* Initializes the 16KB lookup tables. */ +void crc64_init(void) { + crcspeed64native_init(_crc64, crc64_table); +} + +/* Compute crc64 */ +uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) { + return crcspeed64native(crc64_table, crc, (void *) s, l); +} + +/* Test main */ +#ifdef REDIS_TEST +#include + +#define UNUSED(x) (void)(x) +int crc64Test(int argc, char *argv[], int flags) { + UNUSED(argc); + UNUSED(argv); + UNUSED(flags); + crc64_init(); + printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n", + (uint64_t)_crc64(0, "123456789", 9)); + printf("[64speed]: e9c6d914c4b8d9ca == %016" PRIx64 "\n", + (uint64_t)crc64(0, (unsigned char*)"123456789", 9)); + char li[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed " + "do eiusmod tempor incididunt ut labore et dolore magna " + "aliqua. Ut enim ad minim veniam, quis nostrud exercitation " + "ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis " + "aute irure dolor in reprehenderit in voluptate velit esse " + "cillum dolore eu fugiat nulla pariatur. Excepteur sint " + "occaecat cupidatat non proident, sunt in culpa qui officia " + "deserunt mollit anim id est laborum."; + printf("[calcula]: c7794709e69683b3 == %016" PRIx64 "\n", + (uint64_t)_crc64(0, li, sizeof(li))); + printf("[64speed]: c7794709e69683b3 == %016" PRIx64 "\n", + (uint64_t)crc64(0, (unsigned char*)li, sizeof(li))); + return 0; +} + +#endif + +#ifdef REDIS_TEST_MAIN +int main(int argc, char *argv[]) { + return crc64Test(argc, argv); +} + +#endif diff --git a/src/deps/redis/crc64.h b/src/deps/redis/crc64.h new file mode 100644 index 0000000..e0fccd9 --- /dev/null +++ b/src/deps/redis/crc64.h @@ -0,0 +1,13 @@ +#ifndef CRC64_H +#define CRC64_H + +#include + +void crc64_init(void); +uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); + +#ifdef REDIS_TEST +int crc64Test(int argc, char *argv[], int flags); +#endif + +#endif diff --git a/src/deps/redis/crcspeed.c b/src/deps/redis/crcspeed.c new file mode 100644 index 0000000..9682d8e --- /dev/null +++ b/src/deps/redis/crcspeed.c @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2013 Mark Adler + * Originally by: crc64.c Version 1.4 16 Dec 2013 Mark Adler + * Modifications by Matt Stancliff : + * - removed CRC64-specific behavior + * - added generation of lookup tables by parameters + * - removed inversion of CRC input/result + * - removed automatic initialization in favor of explicit initialization + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler + madler@alumni.caltech.edu + */ + +#include "crcspeed.h" + +/* Fill in a CRC constants table. */ +void crcspeed64little_init(crcfn64 crcfn, uint64_t table[8][256]) { + uint64_t crc; + + /* generate CRCs for all single byte sequences */ + for (int n = 0; n < 256; n++) { + unsigned char v = n; + table[0][n] = crcfn(0, &v, 1); + } + + /* generate nested CRC table for future slice-by-8 lookup */ + for (int n = 0; n < 256; n++) { + crc = table[0][n]; + for (int k = 1; k < 8; k++) { + crc = table[0][crc & 0xff] ^ (crc >> 8); + table[k][n] = crc; + } + } +} + +void crcspeed16little_init(crcfn16 crcfn, uint16_t table[8][256]) { + uint16_t crc; + + /* generate CRCs for all single byte sequences */ + for (int n = 0; n < 256; n++) { + table[0][n] = crcfn(0, &n, 1); + } + + /* generate nested CRC table for future slice-by-8 lookup */ + for (int n = 0; n < 256; n++) { + crc = table[0][n]; + for (int k = 1; k < 8; k++) { + crc = table[0][(crc >> 8) & 0xff] ^ (crc << 8); + table[k][n] = crc; + } + } +} + +/* Reverse the bytes in a 64-bit word. */ +static inline uint64_t rev8(uint64_t a) { +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(a); +#else + uint64_t m; + + m = UINT64_C(0xff00ff00ff00ff); + a = ((a >> 8) & m) | (a & m) << 8; + m = UINT64_C(0xffff0000ffff); + a = ((a >> 16) & m) | (a & m) << 16; + return a >> 32 | a << 32; +#endif +} + +/* This function is called once to initialize the CRC table for use on a + big-endian architecture. */ +void crcspeed64big_init(crcfn64 fn, uint64_t big_table[8][256]) { + /* Create the little endian table then reverse all the entries. */ + crcspeed64little_init(fn, big_table); + for (int k = 0; k < 8; k++) { + for (int n = 0; n < 256; n++) { + big_table[k][n] = rev8(big_table[k][n]); + } + } +} + +void crcspeed16big_init(crcfn16 fn, uint16_t big_table[8][256]) { + /* Create the little endian table then reverse all the entries. */ + crcspeed16little_init(fn, big_table); + for (int k = 0; k < 8; k++) { + for (int n = 0; n < 256; n++) { + big_table[k][n] = rev8(big_table[k][n]); + } + } +} + +/* Calculate a non-inverted CRC multiple bytes at a time on a little-endian + * architecture. If you need inverted CRC, invert *before* calling and invert + * *after* calling. + * 64 bit crc = process 8 bytes at once; + */ +uint64_t crcspeed64little(uint64_t little_table[8][256], uint64_t crc, + void *buf, size_t len) { + unsigned char *next = buf; + + /* process individual bytes until we reach an 8-byte aligned pointer */ + while (len && ((uintptr_t)next & 7) != 0) { + crc = little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8); + len--; + } + + /* fast middle processing, 8 bytes (aligned!) per loop */ + while (len >= 8) { + crc ^= *(uint64_t *)next; + crc = little_table[7][crc & 0xff] ^ + little_table[6][(crc >> 8) & 0xff] ^ + little_table[5][(crc >> 16) & 0xff] ^ + little_table[4][(crc >> 24) & 0xff] ^ + little_table[3][(crc >> 32) & 0xff] ^ + little_table[2][(crc >> 40) & 0xff] ^ + little_table[1][(crc >> 48) & 0xff] ^ + little_table[0][crc >> 56]; + next += 8; + len -= 8; + } + + /* process remaining bytes (can't be larger than 8) */ + while (len) { + crc = little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8); + len--; + } + + return crc; +} + +uint16_t crcspeed16little(uint16_t little_table[8][256], uint16_t crc, + void *buf, size_t len) { + unsigned char *next = buf; + + /* process individual bytes until we reach an 8-byte aligned pointer */ + while (len && ((uintptr_t)next & 7) != 0) { + crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8); + len--; + } + + /* fast middle processing, 8 bytes (aligned!) per loop */ + while (len >= 8) { + uint64_t n = *(uint64_t *)next; + crc = little_table[7][(n & 0xff) ^ ((crc >> 8) & 0xff)] ^ + little_table[6][((n >> 8) & 0xff) ^ (crc & 0xff)] ^ + little_table[5][(n >> 16) & 0xff] ^ + little_table[4][(n >> 24) & 0xff] ^ + little_table[3][(n >> 32) & 0xff] ^ + little_table[2][(n >> 40) & 0xff] ^ + little_table[1][(n >> 48) & 0xff] ^ + little_table[0][n >> 56]; + next += 8; + len -= 8; + } + + /* process remaining bytes (can't be larger than 8) */ + while (len) { + crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8); + len--; + } + + return crc; +} + +/* Calculate a non-inverted CRC eight bytes at a time on a big-endian + * architecture. + */ +uint64_t crcspeed64big(uint64_t big_table[8][256], uint64_t crc, void *buf, + size_t len) { + unsigned char *next = buf; + + crc = rev8(crc); + while (len && ((uintptr_t)next & 7) != 0) { + crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8); + len--; + } + + while (len >= 8) { + crc ^= *(uint64_t *)next; + crc = big_table[0][crc & 0xff] ^ + big_table[1][(crc >> 8) & 0xff] ^ + big_table[2][(crc >> 16) & 0xff] ^ + big_table[3][(crc >> 24) & 0xff] ^ + big_table[4][(crc >> 32) & 0xff] ^ + big_table[5][(crc >> 40) & 0xff] ^ + big_table[6][(crc >> 48) & 0xff] ^ + big_table[7][crc >> 56]; + next += 8; + len -= 8; + } + + while (len) { + crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8); + len--; + } + + return rev8(crc); +} + +/* WARNING: Completely untested on big endian architecture. Possibly broken. */ +uint16_t crcspeed16big(uint16_t big_table[8][256], uint16_t crc_in, void *buf, + size_t len) { + unsigned char *next = buf; + uint64_t crc = crc_in; + + crc = rev8(crc); + while (len && ((uintptr_t)next & 7) != 0) { + crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8); + len--; + } + + while (len >= 8) { + uint64_t n = *(uint64_t *)next; + crc = big_table[0][(n & 0xff) ^ ((crc >> (56 - 8)) & 0xff)] ^ + big_table[1][((n >> 8) & 0xff) ^ (crc & 0xff)] ^ + big_table[2][(n >> 16) & 0xff] ^ + big_table[3][(n >> 24) & 0xff] ^ + big_table[4][(n >> 32) & 0xff] ^ + big_table[5][(n >> 40) & 0xff] ^ + big_table[6][(n >> 48) & 0xff] ^ + big_table[7][n >> 56]; + next += 8; + len -= 8; + } + + while (len) { + crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8); + len--; + } + + return rev8(crc); +} + +/* Return the CRC of buf[0..len-1] with initial crc, processing eight bytes + at a time using passed-in lookup table. + This selects one of two routines depending on the endianness of + the architecture. */ +uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf, + size_t len) { + uint64_t n = 1; + + return *(char *)&n ? crcspeed64little(table, crc, buf, len) + : crcspeed64big(table, crc, buf, len); +} + +uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf, + size_t len) { + uint64_t n = 1; + + return *(char *)&n ? crcspeed16little(table, crc, buf, len) + : crcspeed16big(table, crc, buf, len); +} + +/* Initialize CRC lookup table in architecture-dependent manner. */ +void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]) { + uint64_t n = 1; + + *(char *)&n ? crcspeed64little_init(fn, table) + : crcspeed64big_init(fn, table); +} + +void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]) { + uint64_t n = 1; + + *(char *)&n ? crcspeed16little_init(fn, table) + : crcspeed16big_init(fn, table); +} diff --git a/src/deps/redis/crcspeed.h b/src/deps/redis/crcspeed.h new file mode 100644 index 0000000..d7ee95e --- /dev/null +++ b/src/deps/redis/crcspeed.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2014, Matt Stancliff + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ + +#ifndef CRCSPEED_H +#define CRCSPEED_H + +#include +#include + +typedef uint64_t (*crcfn64)(uint64_t, const void *, const uint64_t); +typedef uint16_t (*crcfn16)(uint16_t, const void *, const uint64_t); + +/* CRC-64 */ +void crcspeed64little_init(crcfn64 fn, uint64_t table[8][256]); +void crcspeed64big_init(crcfn64 fn, uint64_t table[8][256]); +void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]); + +uint64_t crcspeed64little(uint64_t table[8][256], uint64_t crc, void *buf, + size_t len); +uint64_t crcspeed64big(uint64_t table[8][256], uint64_t crc, void *buf, + size_t len); +uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf, + size_t len); + +/* CRC-16 */ +void crcspeed16little_init(crcfn16 fn, uint16_t table[8][256]); +void crcspeed16big_init(crcfn16 fn, uint16_t table[8][256]); +void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]); + +uint16_t crcspeed16little(uint16_t table[8][256], uint16_t crc, void *buf, + size_t len); +uint16_t crcspeed16big(uint16_t table[8][256], uint16_t crc, void *buf, + size_t len); +uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf, + size_t len); +#endif diff --git a/src/deps/redis/endianconv.c b/src/deps/redis/endianconv.c new file mode 100644 index 0000000..247c329 --- /dev/null +++ b/src/deps/redis/endianconv.c @@ -0,0 +1,102 @@ +/* endinconv.c -- Endian conversions utilities. + * + * This functions are never called directly, but always using the macros + * defined into endianconv.h, this way we define everything is a non-operation + * if the arch is already little endian. + * + * Redis tries to encode everything as little endian (but a few things that need + * to be backward compatible are still in big endian) because most of the + * production environments are little endian, and we have a lot of conversions + * in a few places because ziplists, intsets, zipmaps, need to be endian-neutral + * even in memory, since they are serialized on RDB files directly with a single + * write(2) without other additional steps. + * + * ---------------------------------------------------------------------------- + * + * Copyright (c) 2011-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#include + +/* Toggle the 16 bit unsigned integer pointed by *p from little endian to + * big endian */ +void memrev16(void *p) { + unsigned char *x = p, t; + + t = x[0]; + x[0] = x[1]; + x[1] = t; +} + +/* Toggle the 32 bit unsigned integer pointed by *p from little endian to + * big endian */ +void memrev32(void *p) { + unsigned char *x = p, t; + + t = x[0]; + x[0] = x[3]; + x[3] = t; + t = x[1]; + x[1] = x[2]; + x[2] = t; +} + +/* Toggle the 64 bit unsigned integer pointed by *p from little endian to + * big endian */ +void memrev64(void *p) { + unsigned char *x = p, t; + + t = x[0]; + x[0] = x[7]; + x[7] = t; + t = x[1]; + x[1] = x[6]; + x[6] = t; + t = x[2]; + x[2] = x[5]; + x[5] = t; + t = x[3]; + x[3] = x[4]; + x[4] = t; +} + +uint16_t intrev16(uint16_t v) { + memrev16(&v); + return v; +} + +uint32_t intrev32(uint32_t v) { + memrev32(&v); + return v; +} + +uint64_t intrev64(uint64_t v) { + memrev64(&v); + return v; +} diff --git a/src/deps/redis/endianconv.h b/src/deps/redis/endianconv.h new file mode 100644 index 0000000..bbb7d2f --- /dev/null +++ b/src/deps/redis/endianconv.h @@ -0,0 +1,73 @@ +/* See endianconv.c top comments for more information + * + * ---------------------------------------------------------------------------- + * + * Copyright (c) 2011-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __ENDIANCONV_H +#define __ENDIANCONV_H + +#include + +void memrev16(void *p); +void memrev32(void *p); +void memrev64(void *p); +uint16_t intrev16(uint16_t v); +uint32_t intrev32(uint32_t v); +uint64_t intrev64(uint64_t v); + +/* variants of the function doing the actual conversion only if the target + * host is big endian */ +#if (BYTE_ORDER == LITTLE_ENDIAN) +#define memrev16ifbe(p) ((void)(0)) +#define memrev32ifbe(p) ((void)(0)) +#define memrev64ifbe(p) ((void)(0)) +#define intrev16ifbe(v) (v) +#define intrev32ifbe(v) (v) +#define intrev64ifbe(v) (v) +#else +#define memrev16ifbe(p) memrev16(p) +#define memrev32ifbe(p) memrev32(p) +#define memrev64ifbe(p) memrev64(p) +#define intrev16ifbe(v) intrev16(v) +#define intrev32ifbe(v) intrev32(v) +#define intrev64ifbe(v) intrev64(v) +#endif + +/* The functions htonu64() and ntohu64() convert the specified value to + * network byte ordering and back. In big endian systems they are no-ops. */ +#if (BYTE_ORDER == BIG_ENDIAN) +#define htonu64(v) (v) +#define ntohu64(v) (v) +#else +#define htonu64(v) intrev64(v) +#define ntohu64(v) intrev64(v) +#endif + +#endif diff --git a/src/deps/redis/listpack.c b/src/deps/redis/listpack.c new file mode 100644 index 0000000..83dd6d1 --- /dev/null +++ b/src/deps/redis/listpack.c @@ -0,0 +1,1551 @@ +/* Listpack -- A lists of strings serialization format + * + * This file implements the specification you can find at: + * + * https://github.com/antirez/listpack + * + * Copyright (c) 2017, Salvatore Sanfilippo + * Copyright (c) 2020, Redis Labs, Inc + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "listpack.h" +//#include "listpack_malloc.h" + +//#include "redisassert.h" +#include "../../utils.h" +//#include "util.h" + + +#ifndef LISTPACK_ALLOC_H +#define LISTPACK_ALLOC_H +//#include "zmalloc.h" +#define lp_malloc malloc +#define lp_realloc realloc +#define lp_free free +#define lp_malloc_size malloc_usable_size + +#define UNUSED(x) (void)(x) + +size_t malloc_usable_size(void *ptr) { + UNUSED(ptr); + return 0; +} +#endif + + + + +#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, Salvatore Sanfilippo + * + * 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. */ + intenc[0] = v; + *enclen = 1; + } else if (v >= -4096 && v <= 4095) { + /* 13 bit integer. */ + if (v < 0) v = ((int64_t)1<<13)+v; + intenc[0] = (v>>8)|LP_ENCODING_13BIT_INT; + intenc[1] = v&0xff; + *enclen = 2; + } else if (v >= -32768 && v <= 32767) { + /* 16 bit integer. */ + if (v < 0) v = ((int64_t)1<<16)+v; + intenc[0] = LP_ENCODING_16BIT_INT; + intenc[1] = v&0xff; + intenc[2] = v>>8; + *enclen = 3; + } else if (v >= -8388608 && v <= 8388607) { + /* 24 bit integer. */ + if (v < 0) v = ((int64_t)1<<24)+v; + intenc[0] = LP_ENCODING_24BIT_INT; + intenc[1] = v&0xff; + intenc[2] = (v>>8)&0xff; + intenc[3] = v>>16; + *enclen = 4; + } else if (v >= -2147483648 && v <= 2147483647) { + /* 32 bit integer. */ + if (v < 0) v = ((int64_t)1<<32)+v; + intenc[0] = LP_ENCODING_32BIT_INT; + intenc[1] = v&0xff; + intenc[2] = (v>>8)&0xff; + intenc[3] = (v>>16)&0xff; + intenc[4] = v>>24; + *enclen = 5; + } else { + /* 64 bit integer. */ + uint64_t uv = v; + 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; + *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; + } +} + +/* 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. */ +static inline uint64_t lpDecodeBacklen(unsigned char *p) { + uint64_t val = 0; + uint64_t shift = 0; + do { + val |= (uint64_t)(p[0] & 127) << shift; + if (!(p[0] & 128)) break; + shift += 7; + p--; + if (shift > 28) return UINT64_MAX; + } while(1); + return val; +} + +/* 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(unsigned char *p) { + if (LP_ENCODING_IS_7BIT_UINT(p[0])) return 1; + if (LP_ENCODING_IS_6BIT_STR(p[0])) return 1; + if (LP_ENCODING_IS_13BIT_INT(p[0])) return 1; + if (LP_ENCODING_IS_16BIT_INT(p[0])) return 1; + if (LP_ENCODING_IS_24BIT_INT(p[0])) return 1; + if (LP_ENCODING_IS_32BIT_INT(p[0])) return 1; + if (LP_ENCODING_IS_64BIT_INT(p[0])) return 1; + if (LP_ENCODING_IS_12BIT_STR(p[0])) return 2; + if (LP_ENCODING_IS_32BIT_STR(p[0])) return 5; + if (p[0] == 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. */ +unsigned char *lpSkip(unsigned char *p) { + unsigned long entrylen = lpCurrentEncodedSizeUnsafe(p); + entrylen += lpEncodeBacklen(NULL,entrylen); + p += entrylen; + 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 += lpEncodeBacklen(NULL,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 + , 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 + lpEncodeBacklen(NULL, *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 + lpEncodeBacklen(NULL, *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 + lpEncodeBacklen(NULL, *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; + } +} + +unsigned char *lpGet(unsigned char *p, int64_t *count, unsigned char *intbuf) { + return lpGetWithSize(p, count, intbuf, NULL); +} + +/* 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; +} + +/* 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) { + int skipcnt = 0; + unsigned char vencoding = 0; + unsigned char *value; + int64_t ll, vll; + uint64_t entry_size = 123456789; /* initialized to avoid warning. */ + uint32_t lp_bytes = lpBytes(lp); + + assert(p); + 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 (slen == ll && memcmp(value, s, slen) == 0) { + return p; + } + } 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 (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 (slen >= 32 || slen == 0 || !lpStringToInt64((const char*)s, slen, &vll)) { + vencoding = UCHAR_MAX; + } else { + 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 (vencoding != UCHAR_MAX && ll == vll) { + return p; + } + } + + /* Reset skip count */ + skipcnt = skip; + p += entry_size; + } else { + /* Skip entry */ + skipcnt--; + + /* Move to next entry, avoid use `lpNext` due to `ASSERT_INTEGRITY` 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; +} + +/* 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 += lpEncodeBacklen(NULL,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. */ + long lendiff = (enclen+backlen_size)-replaced_len; + memmove(dst+replaced_len+lendiff, + 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 { + lpEncodeString(dst,elestr,size); + } + 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; +} + +/* 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); +} + +/* 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; +} + +/* 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 = 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) { + free(*second); + *second = NULL; + *first = target; + } else { + free(*first); + *first = NULL; + *second = target; + } + + return target; +} + +/* Return the total number of bytes the listpack is composed of. */ +size_t lpBytes(unsigned char *lp) { + return lpGetTotalBytes(lp); +} + +/* 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. + * 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); + 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 = lpEncodeBacklen(NULL,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) { + 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 { + /* 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. */ + int64_t sval; + 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 into 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. */ +void lpRandomPair(unsigned char *lp, unsigned long total_count, listpackEntry *key, listpackEntry *val) { + unsigned char *p; + + /* Avoid div by zero on corrupt listpack */ + assert(total_count); + + /* Generate even numbers, because listpack saved K-V pair */ + int r = (rand() % total_count) * 2; + 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 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. */ +void lpRandomPairs(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals) { + unsigned char *p, *key, *value; + unsigned int klen = 0, vlen = 0; + long long klval = 0, vlval = 0; + + /* 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 = malloc(sizeof(rand_pick)*count); + unsigned int total_size = lpLength(lp)/2; + + /* 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++) { + picks[i].index = (rand() % total_size) * 2; /* Generate even indexes */ + /* 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 += 2; + p = lpNext(lp, p); + } + + 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. + * 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) { + unsigned char *p, *key; + unsigned int klen = 0; + long long klval = 0; + unsigned int total_size = lpLength(lp)/2; + unsigned int index = 0; + if (count > total_size) + count = total_size; + + /* 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 in the dict, this way, we could make every + * member be equally picked.*/ + p = lpFirst(lp); + unsigned int picked = 0, remaining = count; + while (picked < count && p) { + double randomDouble = ((double)rand()) / RAND_MAX; + double threshold = ((double)remaining) / (total_size - index); + if (randomDouble <= threshold) { + key = lpGetValue(p, &klen, &klval); + lpSaveValue(key, klen, klval, &keys[picked]); + assert((p = lpNext(lp, p))); + if (vals) { + key = lpGetValue(p, &klen, &klval); + lpSaveValue(key, klen, klval, &vals[picked]); + } + remaining--; + picked++; + } else { + assert((p = lpNext(lp, p))); + } + p = lpNext(lp, p); + index++; + } + return picked; +} + +/* 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); + uint32_t encoded_size = lpCurrentEncodedSizeUnsafe(p); + unsigned long back_len = lpEncodeBacklen(NULL, 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"); +} diff --git a/src/deps/redis/listpack.h b/src/deps/redis/listpack.h new file mode 100644 index 0000000..c0f1b2c --- /dev/null +++ b/src/deps/redis/listpack.h @@ -0,0 +1,67 @@ + +#ifndef __LISTPACK_H +#define __LISTPACK_H + +#include +#include + +#define LP_INTBUF_SIZE 21 /* 20 digits of -2^63 + 1 null term = 21. */ + +/* lpInsert() where argument possible values: */ +#define LP_BEFORE 0 +#define LP_AFTER 1 +#define LP_REPLACE 2 + +/* Each entry in the listpack is either a string or an integer. */ +typedef struct { + /* When string is used, it is provided with the length (slen). */ + unsigned char *sval; + uint32_t slen; + /* When integer is used, 'sval' is NULL, and lval holds the value. */ + long long lval; +} listpackEntry; + +unsigned char *lpNew(size_t capacity); +void lpFree(unsigned char *lp); +unsigned char* lpShrinkToFit(unsigned char *lp); +unsigned char *lpInsertString(unsigned char *lp, unsigned char *s, uint32_t slen, + unsigned char *p, int where, unsigned char **newp); +unsigned char *lpInsertInteger(unsigned char *lp, long long lval, + unsigned char *p, int where, unsigned char **newp); +unsigned char *lpPrepend(unsigned char *lp, unsigned char *s, uint32_t slen); +unsigned char *lpPrependInteger(unsigned char *lp, long long lval); +unsigned char *lpAppend(unsigned char *lp, unsigned char *s, uint32_t slen); +unsigned char *lpAppendInteger(unsigned char *lp, long long lval); +unsigned char *lpReplace(unsigned char *lp, unsigned char **p, unsigned char *s, uint32_t slen); +unsigned char *lpReplaceInteger(unsigned char *lp, unsigned char **p, long long lval); +unsigned char *lpDelete(unsigned char *lp, unsigned char *p, unsigned char **newp); +unsigned char *lpDeleteRangeWithEntry(unsigned char *lp, unsigned char **p, unsigned long num); +unsigned char *lpDeleteRange(unsigned char *lp, long index, unsigned long num); +unsigned char *lpMerge(unsigned char **first, unsigned char **second); +unsigned long lpLength(unsigned char *lp); +unsigned char *lpGet(unsigned char *p, int64_t *count, unsigned char *intbuf); +unsigned char *lpGetValue(unsigned char *p, unsigned int *slen, long long *lval); +unsigned char *lpFind(unsigned char *lp, unsigned char *p, unsigned char *s, uint32_t slen, unsigned int skip); +unsigned char *lpFirst(unsigned char *lp); +unsigned char *lpLast(unsigned char *lp); +unsigned char *lpNext(unsigned char *lp, unsigned char *p); +unsigned char *lpPrev(unsigned char *lp, unsigned char *p); +size_t lpBytes(unsigned char *lp); +unsigned char *lpSeek(unsigned char *lp, long index); +typedef int (*listpackValidateEntryCB)(unsigned char *p, unsigned int head_count, void *userdata); +int lpValidateIntegrity(unsigned char *lp, size_t size, int deep, + listpackValidateEntryCB entry_cb, void *cb_userdata); +unsigned char *lpValidateFirst(unsigned char *lp); +int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes); +unsigned int lpCompare(unsigned char *p, unsigned char *s, uint32_t slen); +void lpRandomPair(unsigned char *lp, unsigned long total_count, listpackEntry *key, listpackEntry *val); +void lpRandomPairs(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals); +unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals); +int lpSafeToAdd(unsigned char* lp, size_t add); +void lpRepr(unsigned char *lp); + +#ifdef REDIS_TEST +int listpackTest(int argc, char *argv[], int flags); +#endif + +#endif diff --git a/src/deps/redis/lzf.h b/src/deps/redis/lzf.h new file mode 100644 index 0000000..ffd7124 --- /dev/null +++ b/src/deps/redis/lzf.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2000-2008 Marc Alexander Lehmann + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +#ifndef LZF_H +#define LZF_H + +/*********************************************************************** +** +** lzf -- an extremely fast/free compression/decompression-method +** http://liblzf.plan9.de/ +** +** This algorithm is believed to be patent-free. +** +***********************************************************************/ + +#define LZF_VERSION 0x0105 /* 1.5, API version */ + +/* + * Compress in_len bytes stored at the memory block starting at + * in_data and write the result to out_data, up to a maximum length + * of out_len bytes. + * + * If the output buffer is not large enough or any error occurs return 0, + * otherwise return the number of bytes used, which might be considerably + * more than in_len (but less than 104% of the original size), so it + * makes sense to always use out_len == in_len - 1), to ensure _some_ + * compression, and store the data uncompressed otherwise (with a flag, of + * course. + * + * lzf_compress might use different algorithms on different systems and + * even different runs, thus might result in different compressed strings + * depending on the phase of the moon or similar factors. However, all + * these strings are architecture-independent and will result in the + * original data when decompressed using lzf_decompress. + * + * The buffers must not be overlapping. + * + * If the option LZF_STATE_ARG is enabled, an extra argument must be + * supplied which is not reflected in this header file. Refer to lzfP.h + * and lzf_c.c. + * + */ +size_t +lzf_compress (const void *const in_data, size_t in_len, + void *out_data, size_t out_len); + +/* + * Decompress data compressed with some version of the lzf_compress + * function and stored at location in_data and length in_len. The result + * will be stored at out_data up to a maximum of out_len characters. + * + * If the output buffer is not large enough to hold the decompressed + * data, a 0 is returned and errno is set to E2BIG. Otherwise the number + * of decompressed bytes (i.e. the original length of the data) is + * returned. + * + * If an error in the compressed data is detected, a zero is returned and + * errno is set to EINVAL. + * + * This function is very fast, about as fast as a copying loop. + */ +size_t +lzf_decompress (const void *const in_data, size_t in_len, + void *out_data, size_t out_len); + +#endif + diff --git a/src/deps/redis/lzfP.h b/src/deps/redis/lzfP.h new file mode 100644 index 0000000..d2e3a19 --- /dev/null +++ b/src/deps/redis/lzfP.h @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2000-2007 Marc Alexander Lehmann + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +#ifndef LZFP_h +#define LZFP_h + +#define STANDALONE 1 /* at the moment, this is ok. */ + +#ifndef STANDALONE +# include "lzf.h" +#endif + +/* + * Size of hashtable is (1 << HLOG) * sizeof (char *) + * decompression is independent of the hash table size + * the difference between 15 and 14 is very small + * for small blocks (and 14 is usually a bit faster). + * For a low-memory/faster configuration, use HLOG == 13; + * For best compression, use 15 or 16 (or more, up to 22). + */ +#ifndef HLOG +# define HLOG 16 +#endif + +/* + * Sacrifice very little compression quality in favour of compression speed. + * This gives almost the same compression as the default code, and is + * (very roughly) 15% faster. This is the preferred mode of operation. + */ +#ifndef VERY_FAST +# define VERY_FAST 1 +#endif + +/* + * Sacrifice some more compression quality in favour of compression speed. + * (roughly 1-2% worse compression for large blocks and + * 9-10% for small, redundant, blocks and >>20% better speed in both cases) + * In short: when in need for speed, enable this for binary data, + * possibly disable this for text data. + */ +#ifndef ULTRA_FAST +# define ULTRA_FAST 0 +#endif + +/* + * Unconditionally aligning does not cost very much, so do it if unsure + */ +#ifndef STRICT_ALIGN +# if !(defined(__i386) || defined (__amd64)) +# define STRICT_ALIGN 1 +# else +# define STRICT_ALIGN 0 +# endif +#endif + +/* + * You may choose to pre-set the hash table (might be faster on some + * modern cpus and large (>>64k) blocks, and also makes compression + * deterministic/repeatable when the configuration otherwise is the same). + */ +#ifndef INIT_HTAB +# define INIT_HTAB 0 +#endif + +/* + * Avoid assigning values to errno variable? for some embedding purposes + * (linux kernel for example), this is necessary. NOTE: this breaks + * the documentation in lzf.h. Avoiding errno has no speed impact. + */ +#ifndef AVOID_ERRNO +# define AVOID_ERRNO 0 +#endif + +/* + * Whether to pass the LZF_STATE variable as argument, or allocate it + * on the stack. For small-stack environments, define this to 1. + * NOTE: this breaks the prototype in lzf.h. + */ +#ifndef LZF_STATE_ARG +# define LZF_STATE_ARG 0 +#endif + +/* + * Whether to add extra checks for input validity in lzf_decompress + * and return EINVAL if the input stream has been corrupted. This + * only shields against overflowing the input buffer and will not + * detect most corrupted streams. + * This check is not normally noticeable on modern hardware + * (<1% slowdown), but might slow down older cpus considerably. + */ +#ifndef CHECK_INPUT +# define CHECK_INPUT 1 +#endif + +/* + * Whether to store pointers or offsets inside the hash table. On + * 64 bit architectures, pointers take up twice as much space, + * and might also be slower. Default is to autodetect. + * Notice: Don't set this value to 1, it will result in 'LZF_HSLOT' + * not being able to store offset above UINT32_MAX in 64bit. */ +#define LZF_USE_OFFSETS 0 + +/*****************************************************************************/ +/* nothing should be changed below */ + +#ifdef __cplusplus +# include +# include +using namespace std; +#else +# include +# include +#endif + +#ifndef LZF_USE_OFFSETS +# if defined (WIN32) +# define LZF_USE_OFFSETS defined(_M_X64) +# else +# if __cplusplus > 199711L +# include +# else +# include +# endif +# define LZF_USE_OFFSETS (UINTPTR_MAX > 0xffffffffU) +# endif +#endif + +typedef unsigned char u8; + +#if LZF_USE_OFFSETS +# define LZF_HSLOT_BIAS ((const u8 *)in_data) + typedef unsigned int LZF_HSLOT; +#else +# define LZF_HSLOT_BIAS 0 +typedef const u8 *LZF_HSLOT; +#endif + +typedef LZF_HSLOT LZF_STATE[1 << (HLOG)]; + +#if !STRICT_ALIGN +/* for unaligned accesses we need a 16 bit datatype. */ +# if USHRT_MAX == 65535 +typedef unsigned short u16; +# elif UINT_MAX == 65535 +typedef unsigned int u16; +# else +# undef STRICT_ALIGN +# define STRICT_ALIGN 1 +# endif +#endif + +#if ULTRA_FAST +# undef VERY_FAST +#endif + +#endif + diff --git a/src/deps/redis/lzf_c.c b/src/deps/redis/lzf_c.c new file mode 100644 index 0000000..2d1cfe7 --- /dev/null +++ b/src/deps/redis/lzf_c.c @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2000-2010 Marc Alexander Lehmann + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +#include "lzfP.h" + +#define HSIZE (1 << (HLOG)) + +/* + * don't play with this unless you benchmark! + * the data format is not dependent on the hash function. + * the hash function might seem strange, just believe me, + * it works ;) + */ +#ifndef FRST +# define FRST(p) (((p[0]) << 8) | p[1]) +# define NEXT(v,p) (((v) << 8) | p[2]) +# if ULTRA_FAST +# define IDX(h) ((( h >> (3*8 - HLOG)) - h ) & (HSIZE - 1)) +# elif VERY_FAST +# define IDX(h) ((( h >> (3*8 - HLOG)) - h*5) & (HSIZE - 1)) +# else +# define IDX(h) ((((h ^ (h << 5)) >> (3*8 - HLOG)) - h*5) & (HSIZE - 1)) +# endif +#endif +/* + * IDX works because it is very similar to a multiplicative hash, e.g. + * ((h * 57321 >> (3*8 - HLOG)) & (HSIZE - 1)) + * the latter is also quite fast on newer CPUs, and compresses similarly. + * + * the next one is also quite good, albeit slow ;) + * (int)(cos(h & 0xffffff) * 1e6) + */ + +#if 0 +/* original lzv-like hash function, much worse and thus slower */ +# define FRST(p) (p[0] << 5) ^ p[1] +# define NEXT(v,p) ((v) << 5) ^ p[2] +# define IDX(h) ((h) & (HSIZE - 1)) +#endif + +#define MAX_LIT (1 << 5) +#define MAX_OFF (1 << 13) +#define MAX_REF ((1 << 8) + (1 << 3)) + +#if __GNUC__ >= 3 +# define expect(expr,value) __builtin_expect ((expr),(value)) +# define inline inline +#else +# define expect(expr,value) (expr) +# define inline static +#endif + +#define expect_false(expr) expect ((expr) != 0, 0) +#define expect_true(expr) expect ((expr) != 0, 1) + +#if defined(__has_attribute) +# if __has_attribute(no_sanitize) +# define NO_SANITIZE(sanitizer) __attribute__((no_sanitize(sanitizer))) +# endif +#endif + +#if !defined(NO_SANITIZE) +# define NO_SANITIZE(sanitizer) +#endif + +/* + * compressed format + * + * 000LLLLL ; literal, L+1=1..33 octets + * LLLooooo oooooooo ; backref L+1=1..7 octets, o+1=1..4096 offset + * 111ooooo LLLLLLLL oooooooo ; backref L+8 octets, o+1=1..4096 offset + * + */ +NO_SANITIZE("alignment") +size_t +lzf_compress (const void *const in_data, size_t in_len, + void *out_data, size_t out_len +#if LZF_STATE_ARG + , LZF_STATE htab +#endif +) +{ +#if !LZF_STATE_ARG + LZF_STATE htab; +#endif + const u8 *ip = (const u8 *)in_data; + u8 *op = (u8 *)out_data; + const u8 *in_end = ip + in_len; + u8 *out_end = op + out_len; + const u8 *ref; + + /* off requires a type wide enough to hold a general pointer difference. + * ISO C doesn't have that (size_t might not be enough and ptrdiff_t only + * works for differences within a single object). We also assume that no + * no bit pattern traps. Since the only platform that is both non-POSIX + * and fails to support both assumptions is windows 64 bit, we make a + * special workaround for it. + */ +#if defined (WIN32) && defined (_M_X64) + unsigned _int64 off; /* workaround for missing POSIX compliance */ +#else + size_t off; +#endif + unsigned int hval; + int lit; + + if (!in_len || !out_len) + return 0; + +#if INIT_HTAB + memset (htab, 0, sizeof (htab)); +#endif + + lit = 0; op++; /* start run */ + + hval = FRST (ip); + while (ip < in_end - 2) + { + LZF_HSLOT *hslot; + + hval = NEXT (hval, ip); + hslot = htab + IDX (hval); + ref = *hslot ? (*hslot + LZF_HSLOT_BIAS) : NULL; /* avoid applying zero offset to null pointer */ + *hslot = ip - LZF_HSLOT_BIAS; + + if (1 + #if INIT_HTAB + && ref < ip /* the next test will actually take care of this, but this is faster */ + #endif + && (off = ip - ref - 1) < MAX_OFF + && ref > (u8 *)in_data + && ref[2] == ip[2] + #if STRICT_ALIGN + && ((ref[1] << 8) | ref[0]) == ((ip[1] << 8) | ip[0]) + #else + && *(u16 *)ref == *(u16 *)ip +#endif + ) + { + /* match found at *ref++ */ + unsigned int len = 2; + size_t maxlen = in_end - ip - len; + maxlen = maxlen > MAX_REF ? MAX_REF : maxlen; + + if (expect_false (op + 3 + 1 >= out_end)) /* first a faster conservative test */ + if (op - !lit + 3 + 1 >= out_end) /* second the exact but rare test */ + return 0; + + op [- lit - 1] = lit - 1; /* stop run */ + op -= !lit; /* undo run if length is zero */ + + for (;;) + { + if (expect_true (maxlen > 16)) + { + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + len++; if (ref [len] != ip [len]) break; + } + + do + len++; + while (len < maxlen && ref[len] == ip[len]); + + break; + } + + len -= 2; /* len is now #octets - 1 */ + ip++; + + if (len < 7) + { + *op++ = (off >> 8) + (len << 5); + } + else + { + *op++ = (off >> 8) + ( 7 << 5); + *op++ = len - 7; + } + + *op++ = off; + + lit = 0; op++; /* start run */ + + ip += len + 1; + + if (expect_false (ip >= in_end - 2)) + break; + +#if ULTRA_FAST || VERY_FAST + --ip; +# if VERY_FAST && !ULTRA_FAST + --ip; +# endif + hval = FRST (ip); + + hval = NEXT (hval, ip); + htab[IDX (hval)] = ip - LZF_HSLOT_BIAS; + ip++; + +# if VERY_FAST && !ULTRA_FAST + hval = NEXT (hval, ip); + htab[IDX (hval)] = ip - LZF_HSLOT_BIAS; + ip++; +# endif +#else + ip -= len + 1; + + do + { + hval = NEXT (hval, ip); + htab[IDX (hval)] = ip - LZF_HSLOT_BIAS; + ip++; + } + while (len--); +#endif + } + else + { + /* one more literal byte we must copy */ + if (expect_false (op >= out_end)) + return 0; + + lit++; *op++ = *ip++; + + if (expect_false (lit == MAX_LIT)) + { + op [- lit - 1] = lit - 1; /* stop run */ + lit = 0; op++; /* start run */ + } + } + } + + if (op + 3 > out_end) /* at most 3 bytes can be missing here */ + return 0; + + while (ip < in_end) + { + lit++; *op++ = *ip++; + + if (expect_false (lit == MAX_LIT)) + { + op [- lit - 1] = lit - 1; /* stop run */ + lit = 0; op++; /* start run */ + } + } + + op [- lit - 1] = lit - 1; /* end run */ + op -= !lit; /* undo run if length is zero */ + + return op - (u8 *)out_data; +} + diff --git a/src/deps/redis/lzf_d.c b/src/deps/redis/lzf_d.c new file mode 100644 index 0000000..fa4131c --- /dev/null +++ b/src/deps/redis/lzf_d.c @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2000-2010 Marc Alexander Lehmann + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +#include "lzfP.h" + +#if AVOID_ERRNO +# define SET_ERRNO(n) +#else +# include +# define SET_ERRNO(n) errno = (n) +#endif + +#if USE_REP_MOVSB /* small win on amd, big loss on intel */ +#if (__i386 || __amd64) && __GNUC__ >= 3 +# define lzf_movsb(dst, src, len) \ + asm ("rep movsb" \ + : "=D" (dst), "=S" (src), "=c" (len) \ + : "0" (dst), "1" (src), "2" (len)); +#endif +#endif + +#if defined(__GNUC__) && __GNUC__ >= 7 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif +size_t +lzf_decompress (const void *const in_data, size_t in_len, + void *out_data, size_t out_len) +{ + u8 const *ip = (const u8 *)in_data; + u8 *op = (u8 *)out_data; + u8 const *const in_end = ip + in_len; + u8 *const out_end = op + out_len; + + while (ip < in_end) + { + unsigned int ctrl; + ctrl = *ip++; + + if (ctrl < (1 << 5)) /* literal run */ + { + ctrl++; + + if (op + ctrl > out_end) + { + SET_ERRNO (E2BIG); + return 0; + } + +#if CHECK_INPUT + if (ip + ctrl > in_end) + { + SET_ERRNO (EINVAL); + return 0; + } +#endif + +#ifdef lzf_movsb + lzf_movsb (op, ip, ctrl); +#else + switch (ctrl) + { + case 32: *op++ = *ip++; case 31: *op++ = *ip++; case 30: *op++ = *ip++; case 29: *op++ = *ip++; + case 28: *op++ = *ip++; case 27: *op++ = *ip++; case 26: *op++ = *ip++; case 25: *op++ = *ip++; + case 24: *op++ = *ip++; case 23: *op++ = *ip++; case 22: *op++ = *ip++; case 21: *op++ = *ip++; + case 20: *op++ = *ip++; case 19: *op++ = *ip++; case 18: *op++ = *ip++; case 17: *op++ = *ip++; + case 16: *op++ = *ip++; case 15: *op++ = *ip++; case 14: *op++ = *ip++; case 13: *op++ = *ip++; + case 12: *op++ = *ip++; case 11: *op++ = *ip++; case 10: *op++ = *ip++; case 9: *op++ = *ip++; + case 8: *op++ = *ip++; case 7: *op++ = *ip++; case 6: *op++ = *ip++; case 5: *op++ = *ip++; + case 4: *op++ = *ip++; case 3: *op++ = *ip++; case 2: *op++ = *ip++; case 1: *op++ = *ip++; + } +#endif + } + else /* back reference */ + { + unsigned int len = ctrl >> 5; + + u8 *ref = op - ((ctrl & 0x1f) << 8) - 1; + +#if CHECK_INPUT + if (ip >= in_end) + { + SET_ERRNO (EINVAL); + return 0; + } +#endif + if (len == 7) + { + len += *ip++; +#if CHECK_INPUT + if (ip >= in_end) + { + SET_ERRNO (EINVAL); + return 0; + } +#endif + } + + ref -= *ip++; + + if (op + len + 2 > out_end) + { + SET_ERRNO (E2BIG); + return 0; + } + + if (ref < (u8 *)out_data) + { + SET_ERRNO (EINVAL); + return 0; + } + +#ifdef lzf_movsb + len += 2; + lzf_movsb (op, ref, len); +#else + switch (len) + { + default: + len += 2; + + if (op >= ref + len) + { + /* disjunct areas */ + memcpy (op, ref, len); + op += len; + } + else + { + /* overlapping, use octte by octte copying */ + do + *op++ = *ref++; + while (--len); + } + + break; + + case 9: *op++ = *ref++; /* fall-thru */ + case 8: *op++ = *ref++; /* fall-thru */ + case 7: *op++ = *ref++; /* fall-thru */ + case 6: *op++ = *ref++; /* fall-thru */ + case 5: *op++ = *ref++; /* fall-thru */ + case 4: *op++ = *ref++; /* fall-thru */ + case 3: *op++ = *ref++; /* fall-thru */ + case 2: *op++ = *ref++; /* fall-thru */ + case 1: *op++ = *ref++; /* fall-thru */ + case 0: *op++ = *ref++; /* two octets more */ + *op++ = *ref++; /* fall-thru */ + } +#endif + } + } + + return op - (u8 *)out_data; +} +#if defined(__GNUC__) && __GNUC__ >= 5 +#pragma GCC diagnostic pop +#endif diff --git a/src/deps/redis/ziplist.c b/src/deps/redis/ziplist.c new file mode 100644 index 0000000..4ec4f36 --- /dev/null +++ b/src/deps/redis/ziplist.c @@ -0,0 +1,1691 @@ +void f(){} +#if 0 +/* The ziplist is a specially encoded dually linked list that is designed + * to be very memory efficient. It stores both strings and integer values, + * where integers are encoded as actual integers instead of a series of + * characters. It allows push and pop operations on either side of the list + * in O(1) time. However, because every operation requires a reallocation of + * the memory used by the ziplist, the actual complexity is related to the + * amount of memory used by the ziplist. + * + * ---------------------------------------------------------------------------- + * + * ZIPLIST OVERALL LAYOUT + * ====================== + * + * The general layout of the ziplist is as follows: + * + * ... + * + * NOTE: all fields are stored in little endian, if not specified otherwise. + * + * is an unsigned integer to hold the number of bytes that + * the ziplist occupies, including the four bytes of the zlbytes field itself. + * This value needs to be stored to be able to resize the entire structure + * without the need to traverse it first. + * + * is the offset to the last entry in the list. This allows + * a pop operation on the far side of the list without the need for full + * traversal. + * + * is the number of entries. When there are more than + * 2^16-2 entries, this value is set to 2^16-1 and we need to traverse the + * entire list to know how many items it holds. + * + * is a special entry representing the end of the ziplist. + * Is encoded as a single byte equal to 255. No other normal entry starts + * with a byte set to the value of 255. + * + * ZIPLIST ENTRIES + * =============== + * + * Every entry in the ziplist is prefixed by metadata that contains two pieces + * of information. First, the length of the previous entry is stored to be + * able to traverse the list from back to front. Second, the entry encoding is + * provided. It represents the entry type, integer or string, and in the case + * of strings it also represents the length of the string payload. + * So a complete entry is stored like this: + * + * + * + * Sometimes the encoding represents the entry itself, like for small integers + * as we'll see later. In such a case the part is missing, and we + * could have just: + * + * + * + * The length of the previous entry, , is encoded in the following way: + * If this length is smaller than 254 bytes, it will only consume a single + * byte representing the length as an unsigned 8 bit integer. When the length + * is greater than or equal to 254, it will consume 5 bytes. The first byte is + * set to 254 (FE) to indicate a larger value is following. The remaining 4 + * bytes take the length of the previous entry as value. + * + * So practically an entry is encoded in the following way: + * + * + * + * Or alternatively if the previous entry length is greater than 253 bytes + * the following encoding is used: + * + * 0xFE <4 bytes unsigned little endian prevlen> + * + * The encoding field of the entry depends on the content of the + * entry. When the entry is a string, the first 2 bits of the encoding first + * byte will hold the type of encoding used to store the length of the string, + * followed by the actual length of the string. When the entry is an integer + * the first 2 bits are both set to 1. The following 2 bits are used to specify + * what kind of integer will be stored after this header. An overview of the + * different types and encodings is as follows. The first byte is always enough + * to determine the kind of entry. + * + * |00pppppp| - 1 byte + * String value with length less than or equal to 63 bytes (6 bits). + * "pppppp" represents the unsigned 6 bit length. + * |01pppppp|qqqqqqqq| - 2 bytes + * String value with length less than or equal to 16383 bytes (14 bits). + * IMPORTANT: The 14 bit number is stored in big endian. + * |10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes + * String value with length greater than or equal to 16384 bytes. + * Only the 4 bytes following the first byte represents the length + * up to 2^32-1. The 6 lower bits of the first byte are not used and + * are set to zero. + * IMPORTANT: The 32 bit number is stored in big endian. + * |11000000| - 3 bytes + * Integer encoded as int16_t (2 bytes). + * |11010000| - 5 bytes + * Integer encoded as int32_t (4 bytes). + * |11100000| - 9 bytes + * Integer encoded as int64_t (8 bytes). + * |11110000| - 4 bytes + * Integer encoded as 24 bit signed (3 bytes). + * |11111110| - 2 bytes + * Integer encoded as 8 bit signed (1 byte). + * |1111xxxx| - (with xxxx between 0001 and 1101) immediate 4 bit integer. + * Unsigned integer from 0 to 12. The encoded value is actually from + * 1 to 13 because 0000 and 1111 can not be used, so 1 should be + * subtracted from the encoded 4 bit value to obtain the right value. + * |11111111| - End of ziplist special entry. + * + * Like for the ziplist header, all the integers are represented in little + * endian byte order, even when this code is compiled in big endian systems. + * + * EXAMPLES OF ACTUAL ZIPLISTS + * =========================== + * + * The following is a ziplist containing the two elements representing + * the strings "2" and "5". It is composed of 15 bytes, that we visually + * split into sections: + * + * [0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff] + * | | | | | | + * zlbytes zltail entries "2" "5" end + * + * The first 4 bytes represent the number 15, that is the number of bytes + * the whole ziplist is composed of. The second 4 bytes are the offset + * at which the last ziplist entry is found, that is 12, in fact the + * last entry, that is "5", is at offset 12 inside the ziplist. + * The next 16 bit integer represents the number of elements inside the + * ziplist, its value is 2 since there are just two elements inside. + * Finally "00 f3" is the first entry representing the number 2. It is + * composed of the previous entry length, which is zero because this is + * our first entry, and the byte F3 which corresponds to the encoding + * |1111xxxx| with xxxx between 0001 and 1101. We need to remove the "F" + * higher order bits 1111, and subtract 1 from the "3", so the entry value + * is "2". The next entry has a prevlen of 02, since the first entry is + * composed of exactly two bytes. The entry itself, F6, is encoded exactly + * like the first entry, and 6-1 = 5, so the value of the entry is 5. + * Finally the special entry FF signals the end of the ziplist. + * + * Adding another element to the above string with the value "Hello World" + * allows us to show how the ziplist encodes small strings. We'll just show + * the hex dump of the entry itself. Imagine the bytes as following the + * entry that stores "5" in the ziplist above: + * + * [02] [0b] [48 65 6c 6c 6f 20 57 6f 72 6c 64] + * + * The first byte, 02, is the length of the previous entry. The next + * byte represents the encoding in the pattern |00pppppp| that means + * that the entry is a string of length , so 0B means that + * an 11 bytes string follows. From the third byte (48) to the last (64) + * there are just the ASCII characters for "Hello World". + * + * ---------------------------------------------------------------------------- + * + * Copyright (c) 2009-2012, Pieter Noordhuis + * Copyright (c) 2009-2017, Salvatore Sanfilippo + * Copyright (c) 2020, Redis Labs, Inc + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +//#include "zmalloc.h" +#include "rpUtils.h" +#include "ziplist.h" +//#include "config.h" +#include "rpEndianConv.h" +//#include "redisassert.h" + +#define ZIP_END 255 /* Special "end of ziplist" entry. */ +#define ZIP_BIG_PREVLEN 254 /* ZIP_BIG_PREVLEN - 1 is the max number of bytes of + the previous entry, for the "prevlen" field prefixing + each entry, to be represented with just a single byte. + Otherwise it is represented as FE AA BB CC DD, where + AA BB CC DD are a 4 bytes unsigned integer + representing the previous entry len. */ + +/* Different encoding/length possibilities */ +#define ZIP_STR_MASK 0xc0 +#define ZIP_INT_MASK 0x30 +#define ZIP_STR_06B (0 << 6) +#define ZIP_STR_14B (1 << 6) +#define ZIP_STR_32B (2 << 6) +#define ZIP_INT_16B (0xc0 | 0<<4) +#define ZIP_INT_32B (0xc0 | 1<<4) +#define ZIP_INT_64B (0xc0 | 2<<4) +#define ZIP_INT_24B (0xc0 | 3<<4) +#define ZIP_INT_8B 0xfe + +/* 4 bit integer immediate encoding |1111xxxx| with xxxx between + * 0001 and 1101. */ +#define ZIP_INT_IMM_MASK 0x0f /* Mask to extract the 4 bits value. To add + one is needed to reconstruct the value. */ +#define ZIP_INT_IMM_MIN 0xf1 /* 11110001 */ +#define ZIP_INT_IMM_MAX 0xfd /* 11111101 */ + +#define INT24_MAX 0x7fffff +#define INT24_MIN (-INT24_MAX - 1) + +/* Macro to determine if the entry is a string. String entries never start + * with "11" as most significant bits of the first byte. */ +#define ZIP_IS_STR(enc) (((enc) & ZIP_STR_MASK) < ZIP_STR_MASK) + +/* Utility macros.*/ + +/* Return total bytes a ziplist is composed of. */ +#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl))) + +/* Return the offset of the last item inside the ziplist. */ +#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t)))) + +/* Return the length of a ziplist, or UINT16_MAX if the length cannot be + * determined without scanning the whole ziplist. */ +#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2))) + +/* The size of a ziplist header: two 32 bit integers for the total + * bytes count and last item offset. One 16 bit integer for the number + * of items field. */ +#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t)) + +/* Size of the "end of ziplist" entry. Just one byte. */ +#define ZIPLIST_END_SIZE (sizeof(uint8_t)) + +/* Return the pointer to the first entry of a ziplist. */ +#define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE) + +/* Return the pointer to the last entry of a ziplist, using the + * last entry offset inside the ziplist header. */ +#define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) + +/* Return the pointer to the last byte of a ziplist, which is, the + * end of ziplist FF entry. */ +#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-ZIPLIST_END_SIZE) + +/* Increment the number of items field in the ziplist header. Note that this + * macro should never overflow the unsigned 16 bit integer, since entries are + * always pushed one at a time. When UINT16_MAX is reached we want the count + * to stay there to signal that a full scan is needed to get the number of + * items inside the ziplist. */ +#define ZIPLIST_INCR_LENGTH(zl,incr) { \ + if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX) \ + ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \ +} + +/* Don't let ziplists grow over 1GB in any case, don't wanna risk overflow in + * zlbytes */ +#define ZIPLIST_MAX_SAFETY_SIZE (1<<30) +int ziplistSafeToAdd(unsigned char* zl, size_t add) { + size_t len = zl? ziplistBlobLen(zl): 0; + if (len + add > ZIPLIST_MAX_SAFETY_SIZE) + return 0; + return 1; +} + + +/* We use this function to receive information about a ziplist entry. + * Note that this is not how the data is actually encoded, is just what we + * get filled by a function in order to operate more easily. */ +typedef struct zlentry { + unsigned int prevrawlensize; /* Bytes used to encode the previous entry len*/ + unsigned int prevrawlen; /* Previous entry len. */ + unsigned int lensize; /* Bytes used to encode this entry type/len. + For example strings have a 1, 2 or 5 bytes + header. Integers always use a single byte.*/ + unsigned int len; /* Bytes used to represent the actual entry. + For strings this is just the string length + while for integers it is 1, 2, 3, 4, 8 or + 0 (for 4 bit immediate) depending on the + number range. */ + unsigned int headersize; /* prevrawlensize + lensize. */ + unsigned char encoding; /* Set to ZIP_STR_* or ZIP_INT_* depending on + the entry encoding. However for 4 bits + immediate integers this can assume a range + of values and must be range-checked. */ + unsigned char *p; /* Pointer to the very start of the entry, that + is, this points to prev-entry-len field. */ +} zlentry; + +#define ZIPLIST_ENTRY_ZERO(zle) { \ + (zle)->prevrawlensize = (zle)->prevrawlen = 0; \ + (zle)->lensize = (zle)->len = (zle)->headersize = 0; \ + (zle)->encoding = 0; \ + (zle)->p = NULL; \ +} + +/* Extract the encoding from the byte pointed by 'ptr' and set it into + * 'encoding' field of the zlentry structure. */ +#define ZIP_ENTRY_ENCODING(ptr, encoding) do { \ + (encoding) = ((ptr)[0]); \ + if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \ +} while(0) + +#define ZIP_ENCODING_SIZE_INVALID 0xff +/* Return the number of bytes required to encode the entry type + length. + * On error, return ZIP_ENCODING_SIZE_INVALID */ +static inline unsigned int zipEncodingLenSize(unsigned char encoding) { + if (encoding == ZIP_INT_16B || encoding == ZIP_INT_32B || + encoding == ZIP_INT_24B || encoding == ZIP_INT_64B || + encoding == ZIP_INT_8B) + return 1; + if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX) + return 1; + if (encoding == ZIP_STR_06B) + return 1; + if (encoding == ZIP_STR_14B) + return 2; + if (encoding == ZIP_STR_32B) + return 5; + return ZIP_ENCODING_SIZE_INVALID; +} + +#define ZIP_ASSERT_ENCODING(encoding) do { \ + assert(zipEncodingLenSize(encoding) != ZIP_ENCODING_SIZE_INVALID); \ +} while (0) + +/* Return bytes needed to store integer encoded by 'encoding' */ +static inline unsigned int zipIntSize(unsigned char encoding) { + switch(encoding) { + case ZIP_INT_8B: return 1; + case ZIP_INT_16B: return 2; + case ZIP_INT_24B: return 3; + case ZIP_INT_32B: return 4; + case ZIP_INT_64B: return 8; + } + if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX) + return 0; /* 4 bit immediate */ + /* bad encoding, covered by a previous call to ZIP_ASSERT_ENCODING */ + assert(0); //redis_unreachable(); + return 0; +} + +/* Write the encoding header of the entry in 'p'. If p is NULL it just returns + * the amount of bytes required to encode such a length. Arguments: + * + * 'encoding' is the encoding we are using for the entry. It could be + * ZIP_INT_* or ZIP_STR_* or between ZIP_INT_IMM_MIN and ZIP_INT_IMM_MAX + * for single-byte small immediate integers. + * + * 'rawlen' is only used for ZIP_STR_* encodings and is the length of the + * string that this entry represents. + * + * The function returns the number of bytes used by the encoding/length + * header stored in 'p'. */ +unsigned int zipStoreEntryEncoding(unsigned char *p, unsigned char encoding, unsigned int rawlen) { + unsigned char len = 1, buf[5]; + + if (ZIP_IS_STR(encoding)) { + /* Although encoding is given it may not be set for strings, + * so we determine it here using the raw length. */ + if (rawlen <= 0x3f) { + if (!p) return len; + buf[0] = ZIP_STR_06B | rawlen; + } else if (rawlen <= 0x3fff) { + len += 1; + if (!p) return len; + buf[0] = ZIP_STR_14B | ((rawlen >> 8) & 0x3f); + buf[1] = rawlen & 0xff; + } else { + len += 4; + if (!p) return len; + buf[0] = ZIP_STR_32B; + buf[1] = (rawlen >> 24) & 0xff; + buf[2] = (rawlen >> 16) & 0xff; + buf[3] = (rawlen >> 8) & 0xff; + buf[4] = rawlen & 0xff; + } + } else { + /* Implies integer encoding, so length is always 1. */ + if (!p) return len; + buf[0] = encoding; + } + + /* Store this length at p. */ + memcpy(p,buf,len); + return len; +} + +/* Decode the entry encoding type and data length (string length for strings, + * number of bytes used for the integer for integer entries) encoded in 'ptr'. + * The 'encoding' variable is input, extracted by the caller, the 'lensize' + * variable will hold the number of bytes required to encode the entry + * length, and the 'len' variable will hold the entry length. + * On invalid encoding error, lensize is set to 0. */ +#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do { \ + if ((encoding) < ZIP_STR_MASK) { \ + if ((encoding) == ZIP_STR_06B) { \ + (lensize) = 1; \ + (len) = (ptr)[0] & 0x3f; \ + } else if ((encoding) == ZIP_STR_14B) { \ + (lensize) = 2; \ + (len) = (((ptr)[0] & 0x3f) << 8) | (ptr)[1]; \ + } else if ((encoding) == ZIP_STR_32B) { \ + (lensize) = 5; \ + (len) = ((uint32_t)(ptr)[1] << 24) | \ + ((uint32_t)(ptr)[2] << 16) | \ + ((uint32_t)(ptr)[3] << 8) | \ + ((uint32_t)(ptr)[4]); \ + } else { \ + (lensize) = 0; /* bad encoding, should be covered by a previous */ \ + (len) = 0; /* ZIP_ASSERT_ENCODING / zipEncodingLenSize, or */ \ + /* match the lensize after this macro with 0. */ \ + } \ + } else { \ + (lensize) = 1; \ + if ((encoding) == ZIP_INT_8B) (len) = 1; \ + else if ((encoding) == ZIP_INT_16B) (len) = 2; \ + else if ((encoding) == ZIP_INT_24B) (len) = 3; \ + else if ((encoding) == ZIP_INT_32B) (len) = 4; \ + else if ((encoding) == ZIP_INT_64B) (len) = 8; \ + else if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX) \ + (len) = 0; /* 4 bit immediate */ \ + else \ + (lensize) = (len) = 0; /* bad encoding */ \ + } \ +} while(0) + +/* Encode the length of the previous entry and write it to "p". This only + * uses the larger encoding (required in __ziplistCascadeUpdate). */ +int zipStorePrevEntryLengthLarge(unsigned char *p, unsigned int len) { + uint32_t u32; + if (p != NULL) { + p[0] = ZIP_BIG_PREVLEN; + u32 = len; + memcpy(p+1,&u32,sizeof(u32)); + memrev32ifbe(p+1); + } + return 1 + sizeof(uint32_t); +} + +/* Encode the length of the previous entry and write it to "p". Return the + * number of bytes needed to encode this length if "p" is NULL. */ +unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) { + if (p == NULL) { + return (len < ZIP_BIG_PREVLEN) ? 1 : sizeof(uint32_t) + 1; + } else { + if (len < ZIP_BIG_PREVLEN) { + p[0] = len; + return 1; + } else { + return zipStorePrevEntryLengthLarge(p,len); + } + } +} + +/* Return the number of bytes used to encode the length of the previous + * entry. The length is returned by setting the var 'prevlensize'. */ +#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do { \ + if ((ptr)[0] < ZIP_BIG_PREVLEN) { \ + (prevlensize) = 1; \ + } else { \ + (prevlensize) = 5; \ + } \ +} while(0) + +/* Return the length of the previous element, and the number of bytes that + * are used in order to encode the previous element length. + * 'ptr' must point to the prevlen prefix of an entry (that encodes the + * length of the previous entry in order to navigate the elements backward). + * The length of the previous entry is stored in 'prevlen', the number of + * bytes needed to encode the previous entry length are stored in + * 'prevlensize'. */ +#define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do { \ + ZIP_DECODE_PREVLENSIZE(ptr, prevlensize); \ + if ((prevlensize) == 1) { \ + (prevlen) = (ptr)[0]; \ + } else { /* prevlensize == 5 */ \ + (prevlen) = ((ptr)[4] << 24) | \ + ((ptr)[3] << 16) | \ + ((ptr)[2] << 8) | \ + ((ptr)[1]); \ + } \ +} while(0) + +/* Given a pointer 'p' to the prevlen info that prefixes an entry, this + * function returns the difference in number of bytes needed to encode + * the prevlen if the previous entry changes of size. + * + * So if A is the number of bytes used right now to encode the 'prevlen' + * field. + * + * And B is the number of bytes that are needed in order to encode the + * 'prevlen' if the previous element will be updated to one of size 'len'. + * + * Then the function returns B - A + * + * So the function returns a positive number if more space is needed, + * a negative number if less space is needed, or zero if the same space + * is needed. */ +int zipPrevLenByteDiff(unsigned char *p, unsigned int len) { + unsigned int prevlensize; + ZIP_DECODE_PREVLENSIZE(p, prevlensize); + return zipStorePrevEntryLength(NULL, len) - prevlensize; +} + +/* Check if string pointed to by 'entry' can be encoded as an integer. + * Stores the integer value in 'v' and its encoding in 'encoding'. */ +int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) { + long long value; + + if (entrylen >= 32 || entrylen == 0) return 0; + if (string2ll((char*)entry,entrylen,&value)) { + /* Great, the string can be encoded. Check what's the smallest + * of our encoding types that can hold this value. */ + if (value >= 0 && value <= 12) { + *encoding = ZIP_INT_IMM_MIN+value; + } else if (value >= INT8_MIN && value <= INT8_MAX) { + *encoding = ZIP_INT_8B; + } else if (value >= INT16_MIN && value <= INT16_MAX) { + *encoding = ZIP_INT_16B; + } else if (value >= INT24_MIN && value <= INT24_MAX) { + *encoding = ZIP_INT_24B; + } else if (value >= INT32_MIN && value <= INT32_MAX) { + *encoding = ZIP_INT_32B; + } else { + *encoding = ZIP_INT_64B; + } + *v = value; + return 1; + } + return 0; +} + +/* Store integer 'value' at 'p', encoded as 'encoding' */ +void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encoding) { + int16_t i16; + int32_t i32; + int64_t i64; + if (encoding == ZIP_INT_8B) { + ((int8_t*)p)[0] = (int8_t)value; + } else if (encoding == ZIP_INT_16B) { + i16 = value; + memcpy(p,&i16,sizeof(i16)); + memrev16ifbe(p); + } else if (encoding == ZIP_INT_24B) { + i32 = ((uint64_t)value)<<8; + memrev32ifbe(&i32); + memcpy(p,((uint8_t*)&i32)+1,sizeof(i32)-sizeof(uint8_t)); + } else if (encoding == ZIP_INT_32B) { + i32 = value; + memcpy(p,&i32,sizeof(i32)); + memrev32ifbe(p); + } else if (encoding == ZIP_INT_64B) { + i64 = value; + memcpy(p,&i64,sizeof(i64)); + memrev64ifbe(p); + } else if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX) { + /* Nothing to do, the value is stored in the encoding itself. */ + } else { + assert(NULL); + } +} + +/* Read integer encoded as 'encoding' from 'p' */ +int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) { + int16_t i16; + int32_t i32; + int64_t i64, ret = 0; + if (encoding == ZIP_INT_8B) { + ret = ((int8_t*)p)[0]; + } else if (encoding == ZIP_INT_16B) { + memcpy(&i16,p,sizeof(i16)); + memrev16ifbe(&i16); + ret = i16; + } else if (encoding == ZIP_INT_32B) { + memcpy(&i32,p,sizeof(i32)); + memrev32ifbe(&i32); + ret = i32; + } else if (encoding == ZIP_INT_24B) { + i32 = 0; + memcpy(((uint8_t*)&i32)+1,p,sizeof(i32)-sizeof(uint8_t)); + memrev32ifbe(&i32); + ret = i32>>8; + } else if (encoding == ZIP_INT_64B) { + memcpy(&i64,p,sizeof(i64)); + memrev64ifbe(&i64); + ret = i64; + } else if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX) { + ret = (encoding & ZIP_INT_IMM_MASK)-1; + } else { + assert(NULL); + } + return ret; +} + +/* Fills a struct with all information about an entry. + * This function is the "unsafe" alternative to the one below. + * Generally, all function that return a pointer to an element in the ziplist + * will assert that this element is valid, so it can be freely used. + * Generally functions such ziplistGet assume the input pointer is already + * validated (since it's the return value of another function). */ +static inline void zipEntry(unsigned char *p, zlentry *e) { + ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen); + ZIP_ENTRY_ENCODING(p + e->prevrawlensize, e->encoding); + ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len); + assert(e->lensize != 0); /* check that encoding was valid. */ + e->headersize = e->prevrawlensize + e->lensize; + e->p = p; +} + +/* Fills a struct with all information about an entry. + * This function is safe to use on untrusted pointers, it'll make sure not to + * try to access memory outside the ziplist payload. + * Returns 1 if the entry is valid, and 0 otherwise. */ +static inline int zipEntrySafe(unsigned char* zl, size_t zlbytes, unsigned char *p, zlentry *e, int validate_prevlen) { + unsigned char *zlfirst = zl + ZIPLIST_HEADER_SIZE; + unsigned char *zllast = zl + zlbytes - ZIPLIST_END_SIZE; +#define OUT_OF_RANGE(p) (unlikely((p) < zlfirst || (p) > zllast)) + + /* If there's no possibility for the header to reach outside the ziplist, + * take the fast path. (max lensize and prevrawlensize are both 5 bytes) */ + if (p >= zlfirst && p + 10 < zllast) { + ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen); + ZIP_ENTRY_ENCODING(p + e->prevrawlensize, e->encoding); + ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len); + e->headersize = e->prevrawlensize + e->lensize; + e->p = p; + /* We didn't call ZIP_ASSERT_ENCODING, so we check lensize was set to 0. */ + if (unlikely(e->lensize == 0)) + return 0; + /* Make sure the entry doesn't reach outside the edge of the ziplist */ + if (OUT_OF_RANGE(p + e->headersize + e->len)) + return 0; + /* Make sure prevlen doesn't reach outside the edge of the ziplist */ + if (validate_prevlen && OUT_OF_RANGE(p - e->prevrawlen)) + return 0; + return 1; + } + + /* Make sure the pointer doesn't reach outside the edge of the ziplist */ + if (OUT_OF_RANGE(p)) + return 0; + + /* Make sure the encoded prevlen header doesn't reach outside the allocation */ + ZIP_DECODE_PREVLENSIZE(p, e->prevrawlensize); + if (OUT_OF_RANGE(p + e->prevrawlensize)) + return 0; + + /* Make sure encoded entry header is valid. */ + ZIP_ENTRY_ENCODING(p + e->prevrawlensize, e->encoding); + e->lensize = zipEncodingLenSize(e->encoding); + if (unlikely(e->lensize == ZIP_ENCODING_SIZE_INVALID)) + return 0; + + /* Make sure the encoded entry header doesn't reach outside the allocation */ + if (OUT_OF_RANGE(p + e->prevrawlensize + e->lensize)) + return 0; + + /* Decode the prevlen and entry len headers. */ + ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen); + ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len); + e->headersize = e->prevrawlensize + e->lensize; + + /* Make sure the entry doesn't reach outside the edge of the ziplist */ + if (OUT_OF_RANGE(p + e->headersize + e->len)) + return 0; + + /* Make sure prevlen doesn't reach outside the edge of the ziplist */ + if (validate_prevlen && OUT_OF_RANGE(p - e->prevrawlen)) + return 0; + + e->p = p; + return 1; +#undef OUT_OF_RANGE +} + +/* Return the total number of bytes used by the entry pointed to by 'p'. */ +static inline unsigned int zipRawEntryLengthSafe(unsigned char* zl, size_t zlbytes, unsigned char *p) { + zlentry e; + assert(zipEntrySafe(zl, zlbytes, p, &e, 0)); + return e.headersize + e.len; +} + +/* Return the total number of bytes used by the entry pointed to by 'p'. */ +static inline unsigned int zipRawEntryLength(unsigned char *p) { + zlentry e; + zipEntry(p, &e); + return e.headersize + e.len; +} + +/* Validate that the entry doesn't reach outside the ziplist allocation. */ +static inline void zipAssertValidEntry(unsigned char* zl, size_t zlbytes, unsigned char *p) { + zlentry e; + assert(zipEntrySafe(zl, zlbytes, p, &e, 1)); +} + +/* Create a new empty ziplist. */ +unsigned char *ziplistNew(void) { + unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE; + unsigned char *zl = zmalloc(bytes); + ZIPLIST_BYTES(zl) = intrev32ifbe(bytes); + ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE); + ZIPLIST_LENGTH(zl) = 0; + zl[bytes-1] = ZIP_END; + return zl; +} + +/* Resize the ziplist. */ +unsigned char *ziplistResize(unsigned char *zl, size_t len) { + assert(len < UINT32_MAX); + zl = zrealloc(zl,len); + ZIPLIST_BYTES(zl) = intrev32ifbe(len); + zl[len-1] = ZIP_END; + return zl; +} + +/* When an entry is inserted, we need to set the prevlen field of the next + * entry to equal the length of the inserted entry. It can occur that this + * length cannot be encoded in 1 byte and the next entry needs to be grow + * a bit larger to hold the 5-byte encoded prevlen. This can be done for free, + * because this only happens when an entry is already being inserted (which + * causes a realloc and memmove). However, encoding the prevlen may require + * that this entry is grown as well. This effect may cascade throughout + * the ziplist when there are consecutive entries with a size close to + * ZIP_BIG_PREVLEN, so we need to check that the prevlen can be encoded in + * every consecutive entry. + * + * Note that this effect can also happen in reverse, where the bytes required + * to encode the prevlen field can shrink. This effect is deliberately ignored, + * because it can cause a "flapping" effect where a chain prevlen fields is + * first grown and then shrunk again after consecutive inserts. Rather, the + * field is allowed to stay larger than necessary, because a large prevlen + * field implies the ziplist is holding large entries anyway. + * + * The pointer "p" points to the first entry that does NOT need to be + * updated, i.e. consecutive fields MAY need an update. */ +unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) { + zlentry cur; + size_t prevlen, prevlensize, prevoffset; /* Informat of the last changed entry. */ + size_t firstentrylen; /* Used to handle insert at head. */ + size_t rawlen, curlen = intrev32ifbe(ZIPLIST_BYTES(zl)); + size_t extra = 0, cnt = 0, offset; + size_t delta = 4; /* Extra bytes needed to update a entry's prevlen (5-1). */ + unsigned char *tail = zl + intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)); + + /* Empty ziplist */ + if (p[0] == ZIP_END) return zl; + + zipEntry(p, &cur); /* no need for "safe" variant since the input pointer was validated by the function that returned it. */ + firstentrylen = prevlen = cur.headersize + cur.len; + prevlensize = zipStorePrevEntryLength(NULL, prevlen); + prevoffset = p - zl; + p += prevlen; + + /* Iterate ziplist to find out how many extra bytes do we need to update it. */ + while (p[0] != ZIP_END) { + assert(zipEntrySafe(zl, curlen, p, &cur, 0)); + + /* Abort when "prevlen" has not changed. */ + if (cur.prevrawlen == prevlen) break; + + /* Abort when entry's "prevlensize" is big enough. */ + if (cur.prevrawlensize >= prevlensize) { + if (cur.prevrawlensize == prevlensize) { + zipStorePrevEntryLength(p, prevlen); + } else { + /* This would result in shrinking, which we want to avoid. + * So, set "prevlen" in the available bytes. */ + zipStorePrevEntryLengthLarge(p, prevlen); + } + break; + } + + /* cur.prevrawlen means cur is the former head entry. */ + assert(cur.prevrawlen == 0 || cur.prevrawlen + delta == prevlen); + + /* Update prev entry's info and advance the cursor. */ + rawlen = cur.headersize + cur.len; + prevlen = rawlen + delta; + prevlensize = zipStorePrevEntryLength(NULL, prevlen); + prevoffset = p - zl; + p += rawlen; + extra += delta; + cnt++; + } + + /* Extra bytes is zero all update has been done(or no need to update). */ + if (extra == 0) return zl; + + /* Update tail offset after loop. */ + if (tail == zl + prevoffset) { + /* When the last entry we need to update is also the tail, update tail offset + * unless this is the only entry that was updated (so the tail offset didn't change). */ + if (extra - delta != 0) { + ZIPLIST_TAIL_OFFSET(zl) = + intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra-delta); + } + } else { + /* Update the tail offset in cases where the last entry we updated is not the tail. */ + ZIPLIST_TAIL_OFFSET(zl) = + intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra); + } + + /* Now "p" points at the first unchanged byte in original ziplist, + * move data after that to new ziplist. */ + offset = p - zl; + zl = ziplistResize(zl, curlen + extra); + p = zl + offset; + memmove(p + extra, p, curlen - offset - 1); + p += extra; + + /* Iterate all entries that need to be updated tail to head. */ + while (cnt) { + zipEntry(zl + prevoffset, &cur); /* no need for "safe" variant since we already iterated on all these entries above. */ + rawlen = cur.headersize + cur.len; + /* Move entry to tail and reset prevlen. */ + memmove(p - (rawlen - cur.prevrawlensize), + zl + prevoffset + cur.prevrawlensize, + rawlen - cur.prevrawlensize); + p -= (rawlen + delta); + if (cur.prevrawlen == 0) { + /* "cur" is the previous head entry, update its prevlen with firstentrylen. */ + zipStorePrevEntryLength(p, firstentrylen); + } else { + /* An entry's prevlen can only increment 4 bytes. */ + zipStorePrevEntryLength(p, cur.prevrawlen+delta); + } + /* Forward to previous entry. */ + prevoffset -= cur.prevrawlen; + cnt--; + } + return zl; +} + +/* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */ +unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) { + unsigned int i, totlen, deleted = 0; + size_t offset; + int nextdiff = 0; + zlentry first, tail; + size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl)); + + zipEntry(p, &first); /* no need for "safe" variant since the input pointer was validated by the function that returned it. */ + for (i = 0; p[0] != ZIP_END && i < num; i++) { + p += zipRawEntryLengthSafe(zl, zlbytes, p); + deleted++; + } + + assert(p >= first.p); + totlen = p-first.p; /* Bytes taken by the element(s) to delete. */ + if (totlen > 0) { + uint32_t set_tail; + if (p[0] != ZIP_END) { + /* Storing `prevrawlen` in this entry may increase or decrease the + * number of bytes required compare to the current `prevrawlen`. + * There always is room to store this, because it was previously + * stored by an entry that is now being deleted. */ + nextdiff = zipPrevLenByteDiff(p,first.prevrawlen); + + /* Note that there is always space when p jumps backward: if + * the new previous entry is large, one of the deleted elements + * had a 5 bytes prevlen header, so there is for sure at least + * 5 bytes free and we need just 4. */ + p -= nextdiff; + assert(p >= first.p && p= first.p. we know totlen >= 0, + * so we know that p > first.p and this is guaranteed not to reach + * beyond the allocation, even if the entries lens are corrupted. */ + size_t bytes_to_move = zlbytes-(p-zl)-1; + memmove(first.p,p,bytes_to_move); + } else { + /* The entire tail was deleted. No need to move memory. */ + set_tail = (first.p-zl)-first.prevrawlen; + } + + /* Resize the ziplist */ + offset = first.p-zl; + zlbytes -= totlen - nextdiff; + zl = ziplistResize(zl, zlbytes); + p = zl+offset; + + /* Update record count */ + ZIPLIST_INCR_LENGTH(zl,-deleted); + + /* Set the tail offset computed above */ + assert(set_tail <= zlbytes - ZIPLIST_END_SIZE); + ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(set_tail); + + /* When nextdiff != 0, the raw length of the next entry has changed, so + * we need to cascade the update throughout the ziplist */ + if (nextdiff != 0) + zl = __ziplistCascadeUpdate(zl,p); + } + return zl; +} + +/* Insert item at "p". */ +unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { + size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, newlen; + unsigned int prevlensize, prevlen = 0; + size_t offset; + int nextdiff = 0; + unsigned char encoding = 0; + long long value = 123456789; /* initialized to avoid warning. Using a value + that is easy to see if for some reason + we use it uninitialized. */ + zlentry tail; + + /* Find out prevlen for the entry that is inserted. */ + if (p[0] != ZIP_END) { + ZIP_DECODE_PREVLEN(p, prevlensize, prevlen); + } else { + unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl); + if (ptail[0] != ZIP_END) { + prevlen = zipRawEntryLengthSafe(zl, curlen, ptail); + } + } + + /* See if the entry can be encoded */ + if (zipTryEncoding(s,slen,&value,&encoding)) { + /* 'encoding' is set to the appropriate integer encoding */ + reqlen = zipIntSize(encoding); + } else { + /* 'encoding' is untouched, however zipStoreEntryEncoding will use the + * string length to figure out how to encode it. */ + reqlen = slen; + } + /* We need space for both the length of the previous entry and + * the length of the payload. */ + reqlen += zipStorePrevEntryLength(NULL,prevlen); + reqlen += zipStoreEntryEncoding(NULL,encoding,slen); + + /* When the insert position is not equal to the tail, we need to + * make sure that the next entry can hold this entry's length in + * its prevlen field. */ + int forcelarge = 0; + nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0; + if (nextdiff == -4 && reqlen < 4) { + nextdiff = 0; + forcelarge = 1; + } + + /* Store offset because a realloc may change the address of zl. */ + offset = p-zl; + newlen = curlen+reqlen+nextdiff; + zl = ziplistResize(zl,newlen); + p = zl+offset; + + /* Apply memory move when necessary and update tail offset. */ + if (p[0] != ZIP_END) { + /* Subtract one because of the ZIP_END bytes */ + memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff); + + /* Encode this entry's raw length in the next entry. */ + if (forcelarge) + zipStorePrevEntryLengthLarge(p+reqlen,reqlen); + else + zipStorePrevEntryLength(p+reqlen,reqlen); + + /* Update offset for tail */ + ZIPLIST_TAIL_OFFSET(zl) = + intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen); + + /* When the tail contains more than one entry, we need to take + * "nextdiff" in account as well. Otherwise, a change in the + * size of prevlen doesn't have an effect on the *tail* offset. */ + assert(zipEntrySafe(zl, newlen, p+reqlen, &tail, 1)); + if (p[reqlen+tail.headersize+tail.len] != ZIP_END) { + ZIPLIST_TAIL_OFFSET(zl) = + intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff); + } + } else { + /* This element will be the new tail. */ + ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl); + } + + /* When nextdiff != 0, the raw length of the next entry has changed, so + * we need to cascade the update throughout the ziplist */ + if (nextdiff != 0) { + offset = p-zl; + zl = __ziplistCascadeUpdate(zl,p+reqlen); + p = zl+offset; + } + + /* Write the entry */ + p += zipStorePrevEntryLength(p,prevlen); + p += zipStoreEntryEncoding(p,encoding,slen); + if (ZIP_IS_STR(encoding)) { + memcpy(p,s,slen); + } else { + zipSaveInteger(p,value,encoding); + } + ZIPLIST_INCR_LENGTH(zl,1); + return zl; +} + +/* Merge ziplists 'first' and 'second' by appending 'second' to 'first'. + * + * NOTE: The larger ziplist is reallocated to contain the new merged ziplist. + * 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 ziplist is the contents of 'first' followed by 'second'. + * + * On failure: returns NULL if the merge is impossible. + * On success: returns the merged ziplist (which is expanded version of either + * 'first' or 'second', also frees the other unused input ziplist, and sets the + * input ziplist argument equal to newly reallocated ziplist return value. */ +unsigned char *ziplistMerge(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 = intrev32ifbe(ZIPLIST_BYTES(*first)); + size_t first_len = intrev16ifbe(ZIPLIST_LENGTH(*first)); + + size_t second_bytes = intrev32ifbe(ZIPLIST_BYTES(*second)); + size_t second_len = intrev16ifbe(ZIPLIST_LENGTH(*second)); + + int append; + unsigned char *source, *target; + size_t target_bytes, source_bytes; + /* Pick the largest ziplist so we can resize easily in-place. + * We must also track if we are now appending or prepending to + * the target ziplist. */ + if (first_len >= second_len) { + /* 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) */ + size_t zlbytes = first_bytes + second_bytes - + ZIPLIST_HEADER_SIZE - ZIPLIST_END_SIZE; + size_t zllength = first_len + second_len; + + /* Combined zl length should be limited within UINT16_MAX */ + zllength = zllength < UINT16_MAX ? zllength : UINT16_MAX; + + /* larger values can't be stored into ZIPLIST_BYTES */ + assert(zlbytes < UINT32_MAX); + + /* Save offset positions before we start ripping memory apart. */ + size_t first_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*first)); + size_t second_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*second)); + + /* Extend target to new zlbytes then append or prepend source. */ + target = zrealloc(target, zlbytes); + if (append) { + /* append == appending to target */ + /* Copy source after target (copying over original [END]): + * [TARGET - END, SOURCE - HEADER] */ + memcpy(target + target_bytes - ZIPLIST_END_SIZE, + source + ZIPLIST_HEADER_SIZE, + source_bytes - ZIPLIST_HEADER_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 - ZIPLIST_END_SIZE, + target + ZIPLIST_HEADER_SIZE, + target_bytes - ZIPLIST_HEADER_SIZE); + memcpy(target, source, source_bytes - ZIPLIST_END_SIZE); + } + + /* Update header metadata. */ + ZIPLIST_BYTES(target) = intrev32ifbe(zlbytes); + ZIPLIST_LENGTH(target) = intrev16ifbe(zllength); + /* New tail offset is: + * + N bytes of first ziplist + * - 1 byte for [END] of first ziplist + * + M bytes for the offset of the original tail of the second ziplist + * - J bytes for HEADER because second_offset keeps no header. */ + ZIPLIST_TAIL_OFFSET(target) = intrev32ifbe( + (first_bytes - ZIPLIST_END_SIZE) + + (second_offset - ZIPLIST_HEADER_SIZE)); + + /* __ziplistCascadeUpdate just fixes the prev length values until it finds a + * correct prev length value (then it assumes the rest of the list is okay). + * We tell CascadeUpdate to start at the first ziplist's tail element to fix + * the merge seam. */ + target = __ziplistCascadeUpdate(target, target+first_offset); + + /* Now free and NULL out what we didn't realloc */ + if (append) { + zfree(*second); + *second = NULL; + *first = target; + } else { + zfree(*first); + *first = NULL; + *second = target; + } + return target; +} + +unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) { + unsigned char *p; + p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl); + return __ziplistInsert(zl,p,s,slen); +} + +/* Returns an offset to use for iterating with ziplistNext. When the given + * index is negative, the list is traversed back to front. When the list + * doesn't contain an element at the provided index, NULL is returned. */ +unsigned char *ziplistIndex(unsigned char *zl, int index) { + unsigned char *p; + unsigned int prevlensize, prevlen = 0; + size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl)); + if (index < 0) { + index = (-index)-1; + p = ZIPLIST_ENTRY_TAIL(zl); + if (p[0] != ZIP_END) { + /* No need for "safe" check: when going backwards, we know the header + * we're parsing is in the range, we just need to assert (below) that + * the size we take doesn't cause p to go outside the allocation. */ + ZIP_DECODE_PREVLENSIZE(p, prevlensize); + assert(p + prevlensize < zl + zlbytes - ZIPLIST_END_SIZE); + ZIP_DECODE_PREVLEN(p, prevlensize, prevlen); + while (prevlen > 0 && index--) { + p -= prevlen; + assert(p >= zl + ZIPLIST_HEADER_SIZE && p < zl + zlbytes - ZIPLIST_END_SIZE); + ZIP_DECODE_PREVLEN(p, prevlensize, prevlen); + } + } + } else { + p = ZIPLIST_ENTRY_HEAD(zl); + while (index--) { + /* Use the "safe" length: When we go forward, we need to be careful + * not to decode an entry header if it's past the ziplist allocation. */ + p += zipRawEntryLengthSafe(zl, zlbytes, p); + if (p[0] == ZIP_END) + break; + } + } + if (p[0] == ZIP_END || index > 0) + return NULL; + zipAssertValidEntry(zl, zlbytes, p); + return p; +} + +/* Return pointer to next entry in ziplist. + * + * zl is the pointer to the ziplist + * p is the pointer to the current element + * + * The element after 'p' is returned, otherwise NULL if we are at the end. */ +unsigned char *ziplistNext(unsigned char *zl, unsigned char *p) { + ((void) zl); + size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl)); + + /* "p" could be equal to ZIP_END, caused by ziplistDelete, + * and we should return NULL. Otherwise, we should return NULL + * when the *next* element is ZIP_END (there is no next entry). */ + if (p[0] == ZIP_END) { + return NULL; + } + + p += zipRawEntryLength(p); + if (p[0] == ZIP_END) { + return NULL; + } + + zipAssertValidEntry(zl, zlbytes, p); + return p; +} + +/* Return pointer to previous entry in ziplist. */ +unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p) { + unsigned int prevlensize, prevlen = 0; + + /* Iterating backwards from ZIP_END should return the tail. When "p" is + * equal to the first element of the list, we're already at the head, + * and should return NULL. */ + if (p[0] == ZIP_END) { + p = ZIPLIST_ENTRY_TAIL(zl); + return (p[0] == ZIP_END) ? NULL : p; + } else if (p == ZIPLIST_ENTRY_HEAD(zl)) { + return NULL; + } else { + ZIP_DECODE_PREVLEN(p, prevlensize, prevlen); + assert(prevlen > 0); + p-=prevlen; + size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl)); + zipAssertValidEntry(zl, zlbytes, p); + return p; + } +} + +/* Get entry pointed to by 'p' and store in either '*sstr' or 'sval' depending + * on the encoding of the entry. '*sstr' is always set to NULL to be able + * to find out whether the string pointer or the integer value was set. + * Return 0 if 'p' points to the end of the ziplist, 1 otherwise. */ +unsigned int ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *slen, long long *sval) { + zlentry entry; + if (p == NULL || p[0] == ZIP_END) return 0; + if (sstr) *sstr = NULL; + + zipEntry(p, &entry); /* no need for "safe" variant since the input pointer was validated by the function that returned it. */ + if (ZIP_IS_STR(entry.encoding)) { + if (sstr) { + *slen = entry.len; + *sstr = p+entry.headersize; + } + } else { + if (sval) { + *sval = zipLoadInteger(p+entry.headersize,entry.encoding); + } + } + return 1; +} + +/* Insert an entry at "p". */ +unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { + return __ziplistInsert(zl,p,s,slen); +} + +/* Delete a single entry from the ziplist, pointed to by *p. + * Also update *p in place, to be able to iterate over the + * ziplist, while deleting entries. */ +unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p) { + size_t offset = *p-zl; + zl = __ziplistDelete(zl,*p,1); + + /* Store pointer to current element in p, because ziplistDelete will + * do a realloc which might result in a different "zl"-pointer. + * When the delete direction is back to front, we might delete the last + * entry and end up with "p" pointing to ZIP_END, so check this. */ + *p = zl+offset; + return zl; +} + +/* Delete a range of entries from the ziplist. */ +unsigned char *ziplistDeleteRange(unsigned char *zl, int index, unsigned int num) { + unsigned char *p = ziplistIndex(zl,index); + return (p == NULL) ? zl : __ziplistDelete(zl,p,num); +} + +/* Replaces the entry at p. This is equivalent to a delete and an insert, + * but avoids some overhead when replacing a value of the same size. */ +unsigned char *ziplistReplace(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { + + /* get metadata of the current entry */ + zlentry entry; + zipEntry(p, &entry); + + /* compute length of entry to store, excluding prevlen */ + unsigned int reqlen; + unsigned char encoding = 0; + long long value = 123456789; /* initialized to avoid warning. */ + if (zipTryEncoding(s,slen,&value,&encoding)) { + reqlen = zipIntSize(encoding); /* encoding is set */ + } else { + reqlen = slen; /* encoding == 0 */ + } + reqlen += zipStoreEntryEncoding(NULL,encoding,slen); + + if (reqlen == entry.lensize + entry.len) { + /* Simply overwrite the element. */ + p += entry.prevrawlensize; + p += zipStoreEntryEncoding(p,encoding,slen); + if (ZIP_IS_STR(encoding)) { + memcpy(p,s,slen); + } else { + zipSaveInteger(p,value,encoding); + } + } else { + /* Fallback. */ + zl = ziplistDelete(zl,&p); + zl = ziplistInsert(zl,p,s,slen); + } + return zl; +} + +/* Compare entry pointer to by 'p' with 'sstr' of length 'slen'. */ +/* Return 1 if equal. */ +unsigned int ziplistCompare(unsigned char *p, unsigned char *sstr, unsigned int slen) { + zlentry entry; + unsigned char sencoding; + long long zval, sval; + if (p[0] == ZIP_END) return 0; + + zipEntry(p, &entry); /* no need for "safe" variant since the input pointer was validated by the function that returned it. */ + if (ZIP_IS_STR(entry.encoding)) { + /* Raw compare */ + if (entry.len == slen) { + return memcmp(p+entry.headersize,sstr,slen) == 0; + } else { + return 0; + } + } else { + /* Try to compare encoded values. Don't compare encoding because + * different implementations may encoded integers differently. */ + if (zipTryEncoding(sstr,slen,&sval,&sencoding)) { + zval = zipLoadInteger(p+entry.headersize,entry.encoding); + return zval == sval; + } + } + return 0; +} + +/* 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 *ziplistFind(unsigned char *zl, unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) { + int skipcnt = 0; + unsigned char vencoding = 0; + long long vll = 0; + size_t zlbytes = ziplistBlobLen(zl); + + while (p[0] != ZIP_END) { + struct zlentry e; + unsigned char *q; + + assert(zipEntrySafe(zl, zlbytes, p, &e, 1)); + q = p + e.prevrawlensize + e.lensize; + + if (skipcnt == 0) { + /* Compare current entry with specified entry */ + if (ZIP_IS_STR(e.encoding)) { + if (e.len == vlen && memcmp(q, vstr, vlen) == 0) { + return p; + } + } 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 (vencoding == 0) { + if (!zipTryEncoding(vstr, vlen, &vll, &vencoding)) { + /* If the entry can't be encoded we set it to + * UCHAR_MAX so that we don't retry again the next + * time. */ + vencoding = UCHAR_MAX; + } + /* Must be non-zero by now */ + assert(vencoding); + } + + /* 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 (vencoding != UCHAR_MAX) { + long long ll = zipLoadInteger(q, e.encoding); + if (ll == vll) { + return p; + } + } + } + + /* Reset skip count */ + skipcnt = skip; + } else { + /* Skip entry */ + skipcnt--; + } + + /* Move to next entry */ + p = q + e.len; + } + + return NULL; +} + +/* Return length of ziplist. */ +unsigned int ziplistLen(unsigned char *zl) { + unsigned int len = 0; + if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX) { + len = intrev16ifbe(ZIPLIST_LENGTH(zl)); + } else { + unsigned char *p = zl+ZIPLIST_HEADER_SIZE; + size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl)); + while (*p != ZIP_END) { + p += zipRawEntryLengthSafe(zl, zlbytes, p); + len++; + } + + /* Re-store length if small enough */ + if (len < UINT16_MAX) ZIPLIST_LENGTH(zl) = intrev16ifbe(len); + } + return len; +} + +/* Return ziplist blob size in bytes. */ +size_t ziplistBlobLen(unsigned char *zl) { + return intrev32ifbe(ZIPLIST_BYTES(zl)); +} + +void ziplistRepr(unsigned char *zl) { + unsigned char *p; + int index = 0; + zlentry entry; + size_t zlbytes = ziplistBlobLen(zl); + + printf( + "{total bytes %u} " + "{num entries %u}\n" + "{tail offset %u}\n", + intrev32ifbe(ZIPLIST_BYTES(zl)), + intrev16ifbe(ZIPLIST_LENGTH(zl)), + intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))); + p = ZIPLIST_ENTRY_HEAD(zl); + while(*p != ZIP_END) { + assert(zipEntrySafe(zl, zlbytes, p, &entry, 1)); + printf( + "{\n" + "\taddr 0x%08lx,\n" + "\tindex %2d,\n" + "\toffset %5lu,\n" + "\thdr+entry len: %5u,\n" + "\thdr len%2u,\n" + "\tprevrawlen: %5u,\n" + "\tprevrawlensize: %2u,\n" + "\tpayload %5u\n", + (long unsigned)p, + index, + (unsigned long) (p-zl), + entry.headersize+entry.len, + entry.headersize, + entry.prevrawlen, + entry.prevrawlensize, + entry.len); + printf("\tbytes: "); + for (unsigned int i = 0; i < entry.headersize+entry.len; i++) { + printf("%02x|",p[i]); + } + printf("\n"); + p += entry.headersize; + if (ZIP_IS_STR(entry.encoding)) { + printf("\t[str]"); + if (entry.len > 40) { + if (fwrite(p,40,1,stdout) == 0) perror("fwrite"); + printf("..."); + } else { + if (entry.len && + fwrite(p,entry.len,1,stdout) == 0) perror("fwrite"); + } + } else { + printf("\t[int]%lld", (long long) zipLoadInteger(p,entry.encoding)); + } + printf("\n}\n"); + p += entry.len; + index++; + } + printf("{end}\n\n"); +} + +/* 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 ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep, + ziplistValidateEntryCB entry_cb, void *cb_userdata) { + /* check that we can actually read the header. (and ZIP_END) */ + if (size < ZIPLIST_HEADER_SIZE + ZIPLIST_END_SIZE) + return 0; + + /* check that the encoded size in the header must match the allocated size. */ + size_t bytes = intrev32ifbe(ZIPLIST_BYTES(zl)); + if (bytes != size) + return 0; + + /* the last byte must be the terminator. */ + if (zl[size - ZIPLIST_END_SIZE] != ZIP_END) + return 0; + + /* make sure the tail offset isn't reaching outside the allocation. */ + if (intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)) > size - ZIPLIST_END_SIZE) + return 0; + + if (!deep) + return 1; + + unsigned int count = 0; + unsigned int header_count = intrev16ifbe(ZIPLIST_LENGTH(zl)); + unsigned char *p = ZIPLIST_ENTRY_HEAD(zl); + unsigned char *prev = NULL; + size_t prev_raw_size = 0; + while(*p != ZIP_END) { + struct zlentry e; + /* Decode the entry headers and fail if invalid or reaches outside the allocation */ + if (!zipEntrySafe(zl, size, p, &e, 1)) + return 0; + + /* Make sure the record stating the prev entry size is correct. */ + if (e.prevrawlen != prev_raw_size) + return 0; + + /* Optionally let the caller validate the entry too. */ + if (entry_cb && !entry_cb(p, header_count, cb_userdata)) + return 0; + + /* Move to the next entry */ + prev_raw_size = e.headersize + e.len; + prev = p; + p += e.headersize + e.len; + count++; + } + + /* Make sure 'p' really does point to the end of the ziplist. */ + if (p != zl + bytes - ZIPLIST_END_SIZE) + return 0; + + /* Make sure the entry really do point to the start of the last entry. */ + if (prev != NULL && prev != ZIPLIST_ENTRY_TAIL(zl)) + return 0; + + /* Check that the count in the header is correct */ + if (header_count != UINT16_MAX && count != header_count) + return 0; + + return 1; +} + +/* Randomly select a pair of key and value. + * total_count is a pre-computed length/2 of the ziplist (to avoid calls to ziplistLen) + * 'key' and 'val' are used to store the result key value pair. + * 'val' can be NULL if the value is not needed. */ +void ziplistRandomPair(unsigned char *zl, unsigned long total_count, ziplistEntry *key, ziplistEntry *val) { + int ret; + unsigned char *p; + + /* Avoid div by zero on corrupt ziplist */ + assert(total_count); + + /* Generate even numbers, because ziplist saved K-V pair */ + int r = (rand() % total_count) * 2; + p = ziplistIndex(zl, r); + ret = ziplistGet(p, &key->sval, &key->slen, &key->lval); + assert(ret != 0); + + if (!val) + return; + p = ziplistNext(zl, p); + ret = ziplistGet(p, &val->sval, &val->slen, &val->lval); + assert(ret != 0); +} + +/* int compare for qsort */ +int uintCompare(const void *a, const void *b) { + return (*(unsigned int *) a - *(unsigned int *) b); +} + +/* Helper method to store a string into from val or lval into dest */ +static inline void ziplistSaveValue(unsigned char *val, unsigned int len, long long lval, ziplistEntry *dest) { + dest->sval = val; + dest->slen = len; + dest->lval = lval; +} + +/* 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. */ +void ziplistRandomPairs(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals) { + unsigned char *p, *key, *value; + unsigned int klen = 0, vlen = 0; + long long klval = 0, vlval = 0; + + /* 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 = zmalloc(sizeof(rand_pick)*count); + unsigned int total_size = ziplistLen(zl)/2; + + /* Avoid div by zero on corrupt ziplist */ + assert(total_size); + + /* create a pool of random indexes (some may be duplicate). */ + for (unsigned int i = 0; i < count; i++) { + picks[i].index = (rand() % total_size) * 2; /* Generate even indexes */ + /* 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 ziplist into a output array respecting the original order. */ + unsigned int zipindex = picks[0].index, pickindex = 0; + p = ziplistIndex(zl, zipindex); + while (ziplistGet(p, &key, &klen, &klval) && pickindex < count) { + p = ziplistNext(zl, p); + assert(ziplistGet(p, &value, &vlen, &vlval)); + while (pickindex < count && zipindex == picks[pickindex].index) { + int storeorder = picks[pickindex].order; + ziplistSaveValue(key, klen, klval, &keys[storeorder]); + if (vals) + ziplistSaveValue(value, vlen, vlval, &vals[storeorder]); + pickindex++; + } + zipindex += 2; + p = ziplistNext(zl, p); + } + + zfree(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. + * The return value is the number of items picked which can be lower than the + * requested count if the ziplist doesn't hold enough pairs. */ +unsigned int ziplistRandomPairsUnique(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals) { + unsigned char *p, *key; + unsigned int klen = 0; + long long klval = 0; + unsigned int total_size = ziplistLen(zl)/2; + unsigned int index = 0; + if (count > total_size) + count = total_size; + + /* 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 in the dict, this way, we could make every + * member be equally picked.*/ + p = ziplistIndex(zl, 0); + unsigned int picked = 0, remaining = count; + while (picked < count && p) { + double randomDouble = ((double)rand()) / RAND_MAX; + double threshold = ((double)remaining) / (total_size - index); + if (randomDouble <= threshold) { + assert(ziplistGet(p, &key, &klen, &klval)); + ziplistSaveValue(key, klen, klval, &keys[picked]); + p = ziplistNext(zl, p); + assert(p); + if (vals) { + assert(ziplistGet(p, &key, &klen, &klval)); + ziplistSaveValue(key, klen, klval, &vals[picked]); + } + remaining--; + picked++; + } else { + p = ziplistNext(zl, p); + assert(p); + } + p = ziplistNext(zl, p); + index++; + } + return picked; +} +#endif diff --git a/src/deps/redis/ziplist.h b/src/deps/redis/ziplist.h new file mode 100644 index 0000000..ff855fd --- /dev/null +++ b/src/deps/redis/ziplist.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2009-2012, Pieter Noordhuis + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _ZIPLIST_H +#define _ZIPLIST_H + +#define ZIPLIST_HEAD 0 +#define ZIPLIST_TAIL 1 + +/* Each entry in the ziplist is either a string or an integer. */ +typedef struct { + /* When string is used, it is provided with the length (slen). */ + unsigned char *sval; + unsigned int slen; + /* When integer is used, 'sval' is NULL, and lval holds the value. */ + long long lval; +} ziplistEntry; + +unsigned char *ziplistNew(void); +unsigned char *ziplistMerge(unsigned char **first, unsigned char **second); +unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where); +unsigned char *ziplistIndex(unsigned char *zl, int index); +unsigned char *ziplistNext(unsigned char *zl, unsigned char *p); +unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p); +unsigned int ziplistGet(unsigned char *p, unsigned char **sval, unsigned int *slen, long long *lval); +unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen); +unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p); +unsigned char *ziplistDeleteRange(unsigned char *zl, int index, unsigned int num); +unsigned char *ziplistReplace(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen); +unsigned int ziplistCompare(unsigned char *p, unsigned char *s, unsigned int slen); +unsigned char *ziplistFind(unsigned char *zl, unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip); +unsigned int ziplistLen(unsigned char *zl); +size_t ziplistBlobLen(unsigned char *zl); +void ziplistRepr(unsigned char *zl); +typedef int (*ziplistValidateEntryCB)(unsigned char* p, unsigned int head_count, void* userdata); +int ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep, + ziplistValidateEntryCB entry_cb, void *cb_userdata); +void ziplistRandomPair(unsigned char *zl, unsigned long total_count, ziplistEntry *key, ziplistEntry *val); +void ziplistRandomPairs(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals); +unsigned int ziplistRandomPairsUnique(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals); +int ziplistSafeToAdd(unsigned char* zl, size_t add); + + +#endif /* _ZIPLIST_H */ diff --git a/src/ext/Makefile b/src/ext/Makefile new file mode 100644 index 0000000..5c5f3c7 --- /dev/null +++ b/src/ext/Makefile @@ -0,0 +1,41 @@ +default: all + +LIB_NAME = rdb +LIB_NAME_EXT = $(LIB_NAME)-ext +LIB_DIR = ../../lib + +TARGET_LIB_STATIC = $(LIB_DIR)/lib$(LIB_NAME).a +# Artifacts: +TARGET_LIB_EXT = $(LIB_DIR)/lib$(LIB_NAME_EXT).so +TARGET_LIB_STATIC_EXT = $(LIB_DIR)/lib$(LIB_NAME_EXT).a + +######################################################################################### +SOURCES = $(notdir $(basename $(wildcard *.c))) +OBJECTS = $(patsubst %,%.o,$(SOURCES)) + +CC = gcc +STD = -std=gnu99 +STACK = -fstack-protector-all -Wstack-protector +WARNS = -Wall -Wextra -pedantic -Werror +CFLAGS = -fPIC -O3 $(STD) $(STACK) $(WARNS) $(EXTERN_RP_CONFIG) +DEBUG = -g3 -DDEBUG=1 +LIBS = -L $(LIB_DIR) -l $(LIB_NAME) + +######################################### RULES ####################################### +all: $(TARGET_LIB_EXT) $(TARGET_LIB_STATIC_EXT) + @echo "Done."; + +$(TARGET_LIB_EXT): $(OBJECTS) + $(CC) -o $@ -shared ${LDFLAGS} $^ $(LIBS) + +$(TARGET_LIB_STATIC_EXT): $(OBJECTS) + ar rcs $@ $^ + +-include $(OBJECTS:.o=.d) + +%.o: %.c + $(CC) $(CFLAGS) -c $*.c -o $*.o $(DEBUG) + $(CC) -MM $(CFLAGS) $*.c > $*.d + +clean: + @rm -rvf $(TARGET_LIB_EXT) $(TARGET_LIB_STATIC_EXT) ./*.o ./*.d; diff --git a/src/ext/common.h b/src/ext/common.h new file mode 100644 index 0000000..ff4697d --- /dev/null +++ b/src/ext/common.h @@ -0,0 +1,21 @@ +#ifndef RDBX_COMMON_H +#define RDBX_COMMON_H + +#include +#include + +/* Extension lib must rely only on API (and not core parser headers) */ +#include "../../api/librdb-api.h" +#include "../../api/librdb-ext-api.h" + +#define UNUSED(...) unused( (void *) NULL, ##__VA_ARGS__); +inline void unused(void *dummy, ...) { (void)(dummy);} + +typedef union CallbacksUnion { + struct { HANDLERS_COMMON_CALLBACKS } common; + RdbHandlersRawCallbacks rawCb; + RdbHandlersStructCallbacks structCb; + RdbHandlersDataCallbacks dataCb; +} CallbacksUnion; + +#endif /*define RDBX_COMMON_H*/ diff --git a/src/ext/handlersFilterKey.c b/src/ext/handlersFilterKey.c new file mode 100644 index 0000000..7dc7f0e --- /dev/null +++ b/src/ext/handlersFilterKey.c @@ -0,0 +1,116 @@ +#include +#include +#include +#include +#include "common.h" + +typedef struct FilterKeyCtx { + regex_t regex_compiled; + int regexInitialized; + int regex_cflags; + int filteroutKey; + RdbRes cbReturnValue; +} FilterKeyCtx; + +static void deleteFilterKeyCtx(RdbParser *p, void *data) { + FilterKeyCtx *ctx = (FilterKeyCtx *) data; + if (ctx->regexInitialized) { + regfree(&ctx->regex_compiled); + } + + RDB_free(p, ctx); +} + +/*** Handling common ***/ + +static RdbRes filterHandlingNewKey(RdbParser *p, void *userData, RdbBulk key, RdbKeyInfo *info) { + UNUSED(p, info); + FilterKeyCtx *ctx = (FilterKeyCtx *) userData; + ctx->cbReturnValue = (regexec(&ctx->regex_compiled, key, 0, NULL, 0)) ? RDB_OK_DONT_PROPAGATE : RDB_OK; + return ctx->cbReturnValue; +} + +static RdbRes filterHandlingEndKey(RdbParser *p, void *userData) { + UNUSED(p); + return ((FilterKeyCtx *) userData)->cbReturnValue; +} + +/*** Handling data ***/ + +static RdbRes filterHandlingString(RdbParser *p, void *userData, RdbBulk value) { + UNUSED(p, value); + return ((FilterKeyCtx *) userData)->cbReturnValue; +} + +static RdbRes filterHandlingList(RdbParser *p, void *userData, RdbBulk str) { + UNUSED(p, str); + return ((FilterKeyCtx *) userData)->cbReturnValue; +} + +/*** Handling struct ***/ + +static RdbRes filterHandlingQListNode(RdbParser *p, void *userData, RdbBulk listNode) { + UNUSED(p, listNode); + return ((FilterKeyCtx *) userData)->cbReturnValue; +} + +/*** Handling raw ***/ + +static RdbRes filterHandlingFrag(RdbParser *p, void *userData, RdbBulk frag) { + UNUSED(p, frag); + return ((FilterKeyCtx *) userData)->cbReturnValue; +} + +static RdbRes filterHandlingRawBegin(RdbParser *p, void *userData, size_t size) { + UNUSED(p, size); + return ((FilterKeyCtx *) userData)->cbReturnValue; +} + +static RdbRes filterHandlingRawEnd(RdbParser *p, void *userData) { + UNUSED(p); + return ((FilterKeyCtx *) userData)->cbReturnValue; +} + +RdbHandlers *RDBX_createHandlersFilterKey(RdbParser *p, + const char *keyRegex, + uint32_t flags, + RdbHandlersLevel lvl) { + UNUSED(flags); + + CallbacksUnion callbacks; + memset (&callbacks, 0, sizeof(callbacks)); + + FilterKeyCtx *ctx = RDB_alloc(p, sizeof(FilterKeyCtx)); + ctx->regexInitialized = 0; + + // compile the regular expression + if (regcomp(&ctx->regex_compiled, keyRegex, REG_EXTENDED) != 0) { + RDB_reportError(p, (RdbRes) RDBX_ERR_R2J_FAILED_COMPILING_REGEX, + "FilterKey: Error compiling regular expression"); + deleteFilterKeyCtx(p, ctx); + return NULL; + } else { + ctx->regexInitialized = 1; + } + + callbacks.common.handleNewKey = filterHandlingNewKey; + callbacks.common.handleEndKey = filterHandlingEndKey; + if (lvl == RDB_LEVEL_DATA) { + callbacks.dataCb.handleStringValue = filterHandlingString; + callbacks.dataCb.handleListElement = filterHandlingList; + return RDB_createHandlersData(p, &callbacks.dataCb, ctx, deleteFilterKeyCtx); + } + + if (lvl == RDB_LEVEL_STRUCT) { + callbacks.structCb.handleStringValue = filterHandlingString; + callbacks.structCb.handlerQListNode = filterHandlingQListNode; + callbacks.structCb.handlerPlainNode = filterHandlingList; + return RDB_createHandlersStruct(p, &callbacks.structCb, ctx, deleteFilterKeyCtx); + } + + /* else (lvl == RDB_LEVEL_RAW) */ + callbacks.rawCb.handleFrag = filterHandlingFrag; + callbacks.rawCb.handleBegin = filterHandlingRawBegin; + callbacks.rawCb.handleEnd = filterHandlingRawEnd; + return RDB_createHandlersRaw(p, &callbacks.rawCb, ctx, deleteFilterKeyCtx); +} diff --git a/src/ext/handlersRdb2Json.c b/src/ext/handlersRdb2Json.c new file mode 100644 index 0000000..ef2a997 --- /dev/null +++ b/src/ext/handlersRdb2Json.c @@ -0,0 +1,341 @@ +#include +#include +#include +#include +#include "common.h" + +struct Rdb2JsonCtx; + +typedef void (*EncodingFunc)(struct Rdb2JsonCtx *ctx, char *p, size_t len); + +typedef enum +{ + R2J_IDLE = 0, + R2J_IN_DB, + R2J_IN_KEY, + + /* Possible states in R2J_IN_KEY */ + R2J_IN_LIST, + R2J_IN_SET, + R2J_IN_STRING, + R2J_IN_HASH, + R2J_IN_ZSET +} Rdb2JsonState; + +typedef struct Rdb2JsonConfig { + char *filename; + RdbxConvJsonEnc encoding; +} Rdb2JsonConfig; + +typedef struct Rdb2JsonCtx { + Rdb2JsonConfig conf; + Rdb2JsonState state; + FILE *outfile; + EncodingFunc encfunc; + + struct { + RdbBulkCopy key; + RdbKeyInfo info; + } keyCtx; + + unsigned int count_keys; + unsigned int count_db; +} Rdb2JsonCtx; + +#define ouput_fprintf(ctx, ...) fprintf(ctx->outfile, ##__VA_ARGS__); + +static void outputPlainEscaping(Rdb2JsonCtx *ctx, char *p, size_t len) { + while(len--) { + switch(*p) { + case '\\': + case '"': + ouput_fprintf(ctx, "\\%c", *p); break; + case '\n': ouput_fprintf(ctx, "\\n"); break; + case '\f': ouput_fprintf(ctx, "\\f"); break; + case '\r': ouput_fprintf(ctx, "\\r"); break; + case '\t': ouput_fprintf(ctx, "\\t"); break; + case '\b': ouput_fprintf(ctx, "\\b"); break; + default: + // todo: formalize rdb2json supported outputs + //ouput_fprintf(ctx, (*p >= 0 && *p <= 0x1f) ? "\\u%04x" : "%c",*p); + ouput_fprintf(ctx, (isprint(*p)) ? "%c" : "\\x%02x", (unsigned char)*p); + } + p++; + } +} + +static void outputQuotedEscaping(Rdb2JsonCtx *ctx, char *data, size_t len) { + ouput_fprintf(ctx, "\""); + ctx->encfunc(ctx, data, len); + ouput_fprintf(ctx, "\""); +} + +static void deleteRdb2JsonCtx(RdbParser *p, void *data) { + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) data; + if (ctx->outfile) fclose(ctx->outfile); + RDB_free(p, ctx->conf.filename); + RDB_free(p, ctx); +} + +static Rdb2JsonCtx *initRdb2JsonCtx(RdbParser *p, RdbxConvJsonEnc encoding, const char *filename) { + FILE *f; + + if (!(f = fopen(filename, "w"))) { + RDB_reportError(p, (RdbRes) RDBX_ERR_R2J_FAILED_OPEN_FILE, + "HandlersRdb2Json: Failed to open file"); + return NULL; + } + + /* init Rdb2Json context */ + Rdb2JsonCtx *ctx = RDB_alloc(p, sizeof(Rdb2JsonCtx)); + ctx->conf.filename = RDB_alloc(p, strlen(filename)+1); + strcpy(ctx->conf.filename, filename); + ctx->outfile = f; + ctx->state = R2J_IDLE; + ctx->count_keys = 0; + ctx->conf.encoding = encoding; + switch(encoding) { + case RDBX_CONV_JSON_ENC_PLAIN: ctx->encfunc = outputPlainEscaping; break; + case RDBX_CONV_JSON_ENC_BASE64: /* todo: support base64 */ + default: assert(0); break; + } + + return ctx; +} + +/*** Handling common ***/ + +static RdbRes handlingAuxField(RdbParser *p, void *userData, RdbBulk auxkey, RdbBulk auxval) { + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) userData; + UNUSED(p); + + /* output json part */ + outputQuotedEscaping(ctx, auxkey, RDB_bulkLen(p, auxkey)); + ouput_fprintf(ctx, ":"); + outputQuotedEscaping(ctx, auxval, RDB_bulkLen(p, auxval)); + ouput_fprintf(ctx, ",\n"); + + return RDB_OK; +} + +static RdbRes handlingEndKey(RdbParser *p, void *userData) { + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) userData; + + /* output json part */ + switch(ctx->state) { + case R2J_IN_LIST: + case R2J_IN_SET: + ouput_fprintf(ctx, "]"); + break; + case R2J_IN_HASH: + case R2J_IN_ZSET: + ouput_fprintf(ctx, "}"); + break; + case R2J_IN_KEY: + case R2J_IN_STRING: + break; /* do nothing */ + default: + RDB_reportError(p, (RdbRes) RDBX_ERR_R2J_INVALID_STATE, + "handlingEndKey(): Invalid state value: %d", ctx->state); + return (RdbRes) RDBX_ERR_R2J_INVALID_STATE; + } + + RDB_bulkFree(p, ctx->keyCtx.key); + + /* update new state */ + ctx->state = R2J_IN_DB; + + return RDB_OK; +} + +static RdbRes handlingNewKey(RdbParser *p, void *userData, RdbBulk key, RdbKeyInfo *info) { + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) userData; + + if (ctx->state != R2J_IN_DB) { + RDB_reportError(p, (RdbRes) RDBX_ERR_R2J_INVALID_STATE, + "handlingNewKey(): Invalid state value: %d", ctx->state); + return (RdbRes) RDBX_ERR_R2J_INVALID_STATE; + } + ctx->keyCtx.key = RDB_bulkClone(p, key); + ctx->keyCtx.info = *info; + + /* update new state */ + ctx->state = R2J_IN_KEY; + + /* output json part */ + ouput_fprintf(ctx, "%s ", (++ctx->count_keys == 1) ? "" : ",\n"); + outputQuotedEscaping(ctx, key, RDB_bulkLen(p, key)); + ouput_fprintf(ctx, ":"); + + return RDB_OK; +} + +static RdbRes handlingNewDb(RdbParser *p, void *userData, int db) { + UNUSED(db); + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) userData; + + if (ctx->state == R2J_IDLE) { + ouput_fprintf(ctx, "[{\n"); + } else if (ctx->state == R2J_IN_DB) { + /* output json part */ + ouput_fprintf(ctx, "},{\n"); + } else { + RDB_reportError(p, (RdbRes) RDBX_ERR_R2J_INVALID_STATE, + "handlingNewDb(): Invalid state value: %d", ctx->state); + return (RdbRes) RDBX_ERR_R2J_INVALID_STATE; + } + + /* update new state */ + ctx->state = R2J_IN_DB; + ++ctx->count_db; + ctx->count_keys = 0; + return RDB_OK; +} + +static RdbRes handlingEndRdb(RdbParser *p, void *userData) { + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) userData; + + if (ctx->state != R2J_IN_DB) { + RDB_reportError(p, (RdbRes) RDBX_ERR_R2J_INVALID_STATE, + "handlingEndRdb(): Invalid state value: %d", ctx->state); + return (RdbRes) RDBX_ERR_R2J_INVALID_STATE; + } + + /* output json part */ + ouput_fprintf(ctx, "\n}]\n"); + + /* update new state */ + ctx->state = R2J_IDLE; + + return RDB_OK; +} + +/*** Handling data ***/ + +static RdbRes handlingString(RdbParser *p, void *userData, RdbBulk value) { + UNUSED(p); + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) userData; + + /* output json part */ + outputQuotedEscaping(ctx, value, RDB_bulkLen(p, value)); + + return RDB_OK; +} + +static RdbRes handlingList(RdbParser *p, void *userData, RdbBulk str) { + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) userData; + + if (ctx->state == R2J_IN_KEY) { + + /* output json part */ + ouput_fprintf(ctx, "["); + outputQuotedEscaping(ctx, str, RDB_bulkLen(p, str)); + + /* update new state */ + ctx->state = R2J_IN_LIST; + + } else if (ctx->state == R2J_IN_LIST) { + + /* output json part */ + ouput_fprintf(ctx, ","); + outputQuotedEscaping(ctx, str, RDB_bulkLen(p, str)); + + /* state unchanged */ + + } else { + RDB_reportError(p, (RdbRes) RDBX_ERR_R2J_INVALID_STATE, + "handlingList(): Invalid state value: %d", ctx->state); + return (RdbRes) RDBX_ERR_R2J_INVALID_STATE; + } + + return RDB_OK; +} + +/*** Handling struct ***/ + +static RdbRes handlingQListNode(RdbParser *p, void *userData, RdbBulk listNode) { + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) userData; + + if (ctx->state == R2J_IN_KEY) { + + /* output json part */ + ouput_fprintf(ctx, "["); + outputQuotedEscaping(ctx, listNode, RDB_bulkLen(p, listNode)); + + /* update new state */ + ctx->state = R2J_IN_LIST; + + } else if (ctx->state == R2J_IN_LIST) { + + /* output json part */ + ouput_fprintf(ctx, ","); + outputQuotedEscaping(ctx, listNode, RDB_bulkLen(p, listNode)); + + /* state unchanged */ + + } else { + RDB_reportError(p, (RdbRes) RDBX_ERR_R2J_INVALID_STATE, + "handlingList(): Invalid state value: %d", ctx->state); + return (RdbRes) RDBX_ERR_R2J_INVALID_STATE; + } + + return RDB_OK; +} + +/*** Handling raw ***/ + +static RdbRes handlingFrag(RdbParser *p, void *userData, RdbBulk frag) { + UNUSED(p); + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) userData; + /* output json part */ + ctx->encfunc(ctx, frag, RDB_bulkLen(p, frag)); + return RDB_OK; +} + +static RdbRes handlingRawBegin(RdbParser *p, void *userData, size_t size) { + UNUSED(p); + UNUSED(size); + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) userData; + ouput_fprintf(ctx, "\""); + return RDB_OK; +} + +static RdbRes handlingRawEnd(RdbParser *p, void *userData) { + UNUSED(p); + Rdb2JsonCtx *ctx = (Rdb2JsonCtx *) userData; + ouput_fprintf(ctx, "\""); + return RDB_OK; +} + +RdbHandlers *RDBX_createHandlersRdb2Json(RdbParser *p, RdbxConvJsonEnc encoding, const char *filename, RdbHandlersLevel lvl) { + Rdb2JsonCtx *ctx = initRdb2JsonCtx(p, encoding, filename); + if (ctx == NULL) return NULL; + + CallbacksUnion callbacks; + memset (&callbacks, 0, sizeof(callbacks)); + + callbacks.common.handleAuxField = handlingAuxField; + callbacks.common.handleNewKey = handlingNewKey; + callbacks.common.handleEndKey = handlingEndKey; + callbacks.common.handleNewDb = handlingNewDb; + callbacks.common.handleEndRdb = handlingEndRdb; + + if (lvl == RDB_LEVEL_DATA) { + callbacks.dataCb.handleStringValue = handlingString; + callbacks.dataCb.handleListElement = handlingList; + return RDB_createHandlersData(p, &callbacks.dataCb, ctx, deleteRdb2JsonCtx); + } + + if (lvl == RDB_LEVEL_STRUCT) { + callbacks.structCb.handleStringValue = handlingString; + callbacks.structCb.handlerQListNode = handlingQListNode; + callbacks.structCb.handlerPlainNode = handlingList; + return RDB_createHandlersStruct(p, &callbacks.structCb, ctx, deleteRdb2JsonCtx); + } + + /* else (lvl == RDB_LEVEL_RAW) */ + callbacks.rawCb.handleFrag = handlingFrag; + callbacks.rawCb.handleBegin = handlingRawBegin; + callbacks.rawCb.handleEnd = handlingRawEnd; + return RDB_createHandlersRaw(p, &callbacks.rawCb, ctx, deleteRdb2JsonCtx); +} diff --git a/src/ext/readerFile.c b/src/ext/readerFile.c new file mode 100644 index 0000000..968d837 --- /dev/null +++ b/src/ext/readerFile.c @@ -0,0 +1,58 @@ +#include +#include +#include "common.h" + +typedef struct RdbReaderFile { + char *filename; + FILE *file; +} RdbReaderFile; + +static void deleteReaderFile(RdbParser *p, void *rdata) { + if (!rdata) return; + + RdbReaderFile *readerData = (RdbReaderFile *) rdata; + if (readerData->filename) RDB_free(p, readerData->filename); + if(readerData->file) fclose(readerData->file); + RDB_free(p, readerData); +} + +static RdbStatus readFile(RdbParser *p, void *data, void *buf, size_t len) { + UNUSED(p); + + RdbReaderFile *readerFile = (RdbReaderFile *) data; + size_t newLen = fread(buf, sizeof(char), len, readerFile->file); + if (ferror( readerFile->file) != 0) { + RDB_reportError(p, RDB_ERR_FAILED_READ_RDB_FILE, NULL); + return RDB_STATUS_ERROR; + } + + if (newLen != len) { + RDB_reportError(p, RDB_ERR_FAILED_PARTIAL_READ_RDB_FILE, NULL); + return RDB_STATUS_ERROR; + } + + return RDB_STATUS_OK; +} + +RdbReader *RDBX_createReaderFile(RdbParser *p, const char *filename) { + FILE *f; + + if (!(f = fopen(filename, "rb"))) { + RDB_reportError(p, RDB_ERR_FAILED_OPEN_RDB_FILE, + "Failed to open RDB file: %s", filename); + return NULL; + } + + RDB_log(p, RDB_LOG_INFO, "RDBX_createReaderFile: Initialized with file %s", filename); + + RdbReaderFile *readerFileData = (RdbReaderFile *) RDB_alloc(p, sizeof(RdbReaderFile)); + readerFileData->file = f; + readerFileData->filename = RDB_alloc(p, strlen(filename)+1); + strcpy(readerFileData->filename, filename); + return RDB_createReaderRdb(p, readFile, readerFileData, deleteReaderFile); +} + +RdbReader *RDBX_createReaderSocket(int fd) { + UNUSED(fd); + return NULL; +} \ No newline at end of file diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..f9f68a1 --- /dev/null +++ b/src/parser.c @@ -0,0 +1,1276 @@ +/* + * It is recommended to read the "Parser implementation notes" section + * in the README.md file as an introduction to this file implementation. + */ + +#include +#include +#include +#include +#include "deps/redis/crc64.h" +#include "bulkAlloc.h" +#include "parser.h" +#include "defines.h" +#include "deps/redis/endianconv.h" +#include "utils.h" +#include "deps/redis/listpack.h" +#include "deps/redis/lzf.h" + +#define DONE_FILL_BULK SIZE_MAX + +struct ParsingElementInfo peiStructAndData[PE_MAX] = { + [PE_RDB_HEADER] = {elementRdbHeader, "elementRdbHeader", "Start parsing RDB header"}, + [PE_NEXT_RDB_TYPE] = {elementNextRdbType, "elementNextRdbType", "Parsing next RDB type"}, + [PE_AUX_FIELD] = {elementAuxField, "elementAuxField", "Parsing auxiliary field" }, + [PE_SELECT_DB] = {elementSelectDb, "elementSelectDb", "Parsing select-db"}, + [PE_RESIZE_DB] = {elementResizeDb, "elementResizeDb", "Parsing resize-db"}, + [PE_EXPIRETIME] = {elementExpireTime, "elementExpireTime", "Parsing expire-time"}, + [PE_EXPIRETIMEMSEC] = {elementExpireTimeMsec, "elementExpireTimeMsec", "Parsing expire-time-msec"}, + [PE_NEW_KEY] = {elementNewKey, "elementNewKey", "Parsing new key-value"}, + [PE_END_KEY] = {elementEndKey, "elementEndKey", "Parsing end key"}, + [PE_STRING] = {elementString, "elementString", "Parsing string"}, + [PE_LIST] = {elementList, "elementList", "Parsing list"}, + [PE_END_OF_FILE] = {elementEndOfFile, "elementEndOfFile", "End parsing RDB file"}, +}; + +/*** Environemnt Variables ***/ +const char *ENV_VAR_SIM_WAIT_MORE_DATA = "LIBRDB_SIM_WAIT_MORE_DATA"; /* simulate RDB_STATUS_WAIT_MORE_DATA by RDB reader */ +const char *ENV_VAR_DEBUG_DATA = "LIBRDB_DEBUG_DATA"; /* to print parsing-elements and theirs states */ + +/*** various static functions (declaration) ***/ + +/*** cache ***/ +static inline void rollbackCache(RdbParser *p); + +/*** release blocks on termination ***/ +static void releaseReader(RdbParser *p); +static void releaseHandlers(RdbParser *p, RdbHandlers *h); + +/*** parsing flow ***/ +static RdbStatus finalizeConfig(RdbParser *p, int isParseFromBuff); +static RdbStatus parserMainLoop(RdbParser *p); + +/*** misc ***/ +static RdbHandlers *createHandlersCommon(RdbParser *p, void *userData, RdbFreeFunc f, RdbHandlersLevel level); +static void loggerCbDefault(RdbLogLevel l, const char *msg); +static inline RdbStatus updateStateAfterParse(RdbParser *p, RdbStatus status); +static void printParserState(RdbParser *p); + +/*** RDB Reader function ***/ +static RdbStatus readRdbFromReader(RdbParser *p, size_t len, AllocTypeRq type, char *refBuf, BulkInfo **binfo); +static RdbStatus readRdbFromBuff(RdbParser *p, size_t len, AllocTypeRq type, char *refBuf, BulkInfo **binfo); +static RdbStatus readRdbWaitMoreDataDbg(RdbParser *p, size_t len, AllocTypeRq type, char *refBuf, BulkInfo **binfo); + +/*** LIB API functions ***/ + +_LIBRDB_API RdbParser *RDB_createParserRdb(RdbMemAlloc *memAlloc) { + + /* init default memory allocation */ + RdbMemAlloc mem = { + .malloc=malloc, + .realloc=realloc, + .free=free, + .bulkAllocType=RDB_BULK_ALLOC_STACK + }; + + if (memAlloc) mem = *memAlloc; + + RdbParser *p = mem.malloc(sizeof(RdbParser)); + memset(p, 0, sizeof(RdbParser) ); + + p->state = RDB_STATECONFIGURING; + + p->callSubElm.callerElm = PE_MAX; + p->callSubElm.bulkResult.ref = NULL; + + p->mem = mem; + p->reader = NULL; + p->cache = NULL; + p->appCbCtx.numBulks = 0; + p->loggerCb = loggerCbDefault; + p->logLevel = RDB_LOG_DBG; + p->maxRawLen = SIZE_MAX; + p->errorCode = RDB_OK; + p->handlers[RDB_LEVEL_RAW] = NULL; + p->handlers[RDB_LEVEL_STRUCT] = NULL; + p->handlers[RDB_LEVEL_DATA] = NULL; + p->numHandlers[RDB_LEVEL_RAW] = 0; + p->numHandlers[RDB_LEVEL_STRUCT] = 0; + p->numHandlers[RDB_LEVEL_DATA] = 0; + p->totalHandlers = 0; + p->firstHandlers = NULL; + + p->elmCtx.state = 0; + p->parsingElement = PE_RDB_HEADER; + + p->elmCtx.key.info.expiretime = -1; + p->elmCtx.key.info.lru_idle = -1; + p->elmCtx.key.info.lfu_freq = -1; + + p->currOpcode = UINT32_MAX; + p->deepIntegCheck = 1; + p->ignoreChecksum = 0; + + /*** RDB_parseBuff related data ***/ + p->isParseFromBuff = 0; + p->bytesRead = 0; + p->parsebuffCtx.start = NULL; + p->parsebuffCtx.size = 0; + p->parsebuffCtx.at = NULL; + p->parsebuffCtx.end = NULL; + + + p->pauseInterval = 0; + p->bytesToNextPause = SIZE_MAX; + + p->checksum = 0; + + return p; +} + +_LIBRDB_API void RDB_deleteParser(RdbParser *p) { + SerializedPool *sp = p->cache; + + freeUnmanagedBulk(p, &p->callSubElm.bulkResult); + + parserRawRelease(p); + + /* release reader */ + releaseReader(p); + + /* release all handlers */ + releaseHandlers(p, p->firstHandlers); + + if (sp) serPoolRelease(p); + p->mem.free(p); +} + +_LIBRDB_API RdbStatus RDB_parse(RdbParser *p) { + if (p->state == RDB_STATECONFIGURING) + IF_NOT_OK_RETURN(finalizeConfig(p, 0)); + + /* nothing special to do after pause */ + if (p->state == RDB_STATEPAUSED) + p->state = RDB_STATERUNNING; + + return parserMainLoop(p); +} + +_LIBRDB_API RdbStatus RDB_parseBuff(RdbParser *p, unsigned char *buff, size_t size, int isEOF) { + + if (p->state == RDB_STATECONFIGURING) + IF_NOT_OK_RETURN(finalizeConfig(p, 1)); + + if (p->state != RDB_STATEPAUSED) + { + /* track buffer consumption in parser context */ + p->parsebuffCtx.start = buff; + p->parsebuffCtx.size = size; + p->parsebuffCtx.at = buff; + p->parsebuffCtx.end = buff + size; + } else { + /* after pause verify that given buff is exactly as before */ + if (buff != p->parsebuffCtx.start || p->parsebuffCtx.size != size) { + RDB_reportError(p, RDB_ERR_PARSEBUF_AFTER_PAUSE_NOT_SAME_BUFF, + "RDB_parseBuff(): Expected to continue parse same buffer that was parsed before the pause."); + return RDB_STATUS_ERROR; + } + + p->state = RDB_STATERUNNING; + } + + RdbStatus status = parserMainLoop(p); + + if (isEOF) { + if (status == RDB_STATUS_WAIT_MORE_DATA) { + RDB_reportError(p, RDB_ERR_EXP_EOF_BUT_PARSER_WAIT_MORE_DATA, + "RDB_parseBuff(): Expected EOF but parser return RDB_STATUS_WAIT_MORE_DATA"); + return RDB_STATUS_ERROR; + } + } + + return status; +} + +_LIBRDB_API RdbReader *RDB_createReaderRdb(RdbParser *p, RdbReaderFunc r, void *readerData, RdbFreeFunc freeReaderData) { + assert(p->state == RDB_STATECONFIGURING); + + /* if previously allocated reader, then release it first */ + releaseReader(p); + + /* alloc & register new reader in parser */ + p->reader = (RdbReader *) RDB_alloc(p, sizeof(RdbReader)); + memset(p->reader, 0, sizeof(RdbReader)); + p->reader->parser = p; + p->reader->readFunc = r; + p->reader->readerData = readerData; + p->reader->destructor = freeReaderData; + return p->reader; +} + +_LIBRDB_API size_t RDB_bulkLen(RdbParser *p, RdbBulk b) { + for (int i = 0 ; i < p->appCbCtx.numBulks ; ++i) { + if (likely(p->appCbCtx.bulks[i]->ref == b)) + return p->appCbCtx.bulks[i]->len; + } + + RDB_reportError(p, RDB_ERR_INVALID_BULK_LENGTH_REQUEST, + "Invalid RDB_bulkLen() request. Couldn't find application-bulk with address: %p", b); + + return 0; +} + +/* if app configured RDB_BULK_ALLOC_EXTERN_OPT, then let's just return reference + * bulk when possible. In this case the application callbacks cannot make any + * assumption about the allocated memory layout of RdbBulk. It can assist function + * RDB_isRefBulk to resolve whether given bulk was allocated by its external + * allocator or optimized with reference bulk. + */ +_LIBRDB_API int RDB_isRefBulk(RdbParser *p, RdbBulk b) { + for (int i = 0 ; i < p->appCbCtx.numBulks ; ++i) { + if (likely(p->appCbCtx.bulks[i]->ref == b)) + return p->appCbCtx.bulks[i]->bulkType == BULK_TYPE_REF; + } + + RDB_reportError(p, RDB_ERR_INVALID_IS_REF_BULK, + "Invalid RDB_isRefBulk() request. Couldn't find application-bulk with address: %p", b); + return 0; +} + +_LIBRDB_API RdbBulkCopy RDB_bulkClone(RdbParser *p, RdbBulk b) { + + for (int i = 0 ; i < p->appCbCtx.numBulks ; ++i) { + if (likely(p->appCbCtx.bulks[i]->ref == b)) + return serPoolCloneItem(p, p->appCbCtx.bulks[i]); + } + + RDB_reportError(p, RDB_ERR_INVALID_BULK_CLONE_REQUEST, + "Invalid RDB_bulkClone() request. Couldn't find application-bulk with address: %p", b); + + return NULL; +} + +_LIBRDB_API void RDB_setPauseInterval(RdbParser *p, size_t interval) { + p->pauseInterval = interval; +} + +_LIBRDB_API void RDB_pauseParser(RdbParser *p) { + p->bytesToNextPause = p->bytesRead; +} + +_LIBRDB_API void RDB_setLogger(RdbParser *p, RdbLoggerCB f) { + p->loggerCb = f; +} + +_LIBRDB_API void RDB_IgnoreChecksum(RdbParser *p) { + p->ignoreChecksum = 1; +} + +_LIBRDB_API void RDB_setMaxRawLenHandling(RdbParser *p, size_t size) { + p->maxRawLen = size; +} + +_LIBRDB_API void RDB_log(RdbParser *p, RdbLogLevel lvl, const char *format, ...) { + if (lvl <= p->logLevel) + { + va_list args; + va_start (args, format); + char buffer[1024] = {0}; + vsnprintf(buffer, sizeof(buffer), format, args); + p->loggerCb(lvl, buffer); + va_end(args); + return; + } +} + +_LIBRDB_API void RDB_setLogLevel(RdbParser *p, RdbLogLevel l) { + p->logLevel = l; +} + +_LIBRDB_API void RDB_setDeepIntegCheck(RdbParser *p, int deep) { + p->deepIntegCheck = !!deep; +} + +_LIBRDB_API size_t RDB_getBytesProcessed(RdbParser *p) { + return p->bytesRead; +} + +_LIBRDB_API RdbState RDB_getState(RdbParser *p) { + return p->state; +} + +_LIBRDB_API RdbRes RDB_getErrorCode(RdbParser *p) { + return p->errorCode; +} + +_LIBRDB_API void RDB_reportError(RdbParser *p, RdbRes e, const char *msg, ...) { + int nchars = 0; + p->errorCode = e; + + if (msg == NULL) { + p->errorMsg[0] = '\0'; + return; + } + + /* RDB_OK & RDB_OK_DONT_PROPAGATE - not a real errors to report */ + assert (e != RDB_OK && e != RDB_OK_DONT_PROPAGATE); + + if (p->state == RDB_STATERUNNING) { + nchars = snprintf(p->errorMsg, MAX_ERROR_MSG, "[%s::State=%d] ", + p->pei[p->parsingElement].funcname, + p->elmCtx.state); + } + + va_list args; + va_start(args, msg); + vsnprintf(p->errorMsg + nchars, MAX_ERROR_MSG - nchars, msg, args); + va_end(args); + + RDB_log(p, RDB_LOG_ERROR, p->errorMsg); +} + +_LIBRDB_API const char *RDB_getErrorMessage(RdbParser *p) { + return p->errorMsg; +} + +_LIBRDB_API void *RDB_alloc(RdbParser *p, size_t size) { + return p->mem.malloc((size)); +} + +_LIBRDB_API void *RDB_realloc(RdbParser *p, void *ptr, size_t size) { + return p->mem.realloc(ptr, size); +} + +_LIBRDB_API void RDB_free(RdbParser *p, void *ptr) { + p->mem.free(ptr); +} + +_LIBRDB_API RdbHandlers *RDB_createHandlersRaw(RdbParser *p, + RdbHandlersRawCallbacks *callbacks, + void *userData, + RdbFreeFunc freeUserData) { + RdbHandlers *hndl = createHandlersCommon(p, userData, freeUserData, RDB_LEVEL_RAW); + hndl->h.rdbRaw = *callbacks; + return hndl; +} + +_LIBRDB_API RdbHandlers *RDB_createHandlersStruct(RdbParser *p, + RdbHandlersStructCallbacks *callbacks, + void *userData, + RdbFreeFunc freeUserData) { + RdbHandlers *hndl = createHandlersCommon(p, userData, freeUserData, RDB_LEVEL_STRUCT); + hndl->h.rdbStruct = *callbacks; + return hndl; +} + +_LIBRDB_API RdbHandlers *RDB_createHandlersData(RdbParser *p, + RdbHandlersDataCallbacks *callbacks, + void *userData, + RdbFreeFunc freeUserData) { + RdbHandlers *hndl = createHandlersCommon(p, userData, freeUserData, RDB_LEVEL_DATA); + hndl->h.rdbData = *callbacks; + return hndl; +} + +/*** various functions ***/ + +static const char *getStatusString(RdbStatus status) { + switch ((int) status) { + case RDB_STATUS_OK: return "RDB_STATUS_OK"; + case RDB_STATUS_WAIT_MORE_DATA: return "RDB_STATUS_WAIT_MORE_DATA"; + case RDB_STATUS_PAUSED: return "RDB_STATUS_PAUSED"; + case RDB_STATUS_ERROR: return "RDB_STATUS_ERROR"; + case RDB_STATUS_ENDED: return "(RDB_STATUS_ENDED)"; /* internal state. (Not part of API) */ + default: assert(0); + } +} + +static inline RdbStatus updateStateAfterParse(RdbParser *p, RdbStatus status) { + /* update parser internal state according to returned status */ + switch ( (int)status) { + case RDB_STATUS_PAUSED: + rollbackCache(p); + p->state = RDB_STATEPAUSED; + return RDB_STATUS_PAUSED; + + case RDB_STATUS_WAIT_MORE_DATA: + rollbackCache(p); + p->state = RDB_STATERUNNING; + return RDB_STATUS_WAIT_MORE_DATA; + + case RDB_STATUS_ERROR: + printParserState(p); + p->state = RDB_STATEERROR; + return RDB_STATUS_ERROR; + + case RDB_STATUS_ENDED: + /* STATUS_ENDED is an internal value that is not exposed to the caller. + * It saves us a condition in main loop. */ + + /* fall-thru */ + case RDB_STATUS_OK: + p->state = RDB_STATEENDED; + RDB_log(p, RDB_LOG_INFO, "Parser done"); + return RDB_STATUS_OK; + + default: + + RDB_reportError(p, RDB_ERR_PARSER_RETURNED_INVALID_LIBRDB_STATUS, + "updateStateAfterParse() Parser returned invalid status: %d", status); + return RDB_STATUS_ERROR; + } +} + +static RdbStatus parserMainLoop(RdbParser *p) { + RdbStatus status; + assert(p->state == RDB_STATERUNNING); + + p->bytesToNextPause = (p->pauseInterval == 0) ? SIZE_MAX : p->bytesRead + p->pauseInterval ; + + if (unlikely(p->debugData)) { + while (1) { + printf(">>> Parsing element %s [State=%d] => ", + p->pei[p->parsingElement].funcname, p->elmCtx.state); + status = p->pei[p->parsingElement].func(p); + printf("status=%s\n", getStatusString(status)); + if (status != RDB_STATUS_OK) break; + } + } else { + /* If this loop become too much performance intensive, then we can optimize + * certain transitions by avoiding passing through the main loop. It can be + * done by flushing the cache with function serPoolFlush(), and then make + * direct call to next state */ + while ((status = p->pei[p->parsingElement].func(p)) == RDB_STATUS_OK); + } + return updateStateAfterParse(p, status); +} + +static inline void rollbackCache(RdbParser *p) { + serPoolRollback(p); +} + +static inline RdbStatus nextParsingElementKeyValue(RdbParser *p, + ParsingElementType peKey, + ParsingElementType peValue) { + p->elmCtx.key.valueType = peValue; + return nextParsingElement(p, peKey); +} + +static RdbRes handleNewKeyPrintDbg(RdbParser *p, void *userData, RdbBulk key, RdbKeyInfo *info) { + UNUSED(p,userData,info); + printf("Key=%s, ", key); + return RDB_OK; +} + +static void chainHandlersAcrossLevels(RdbParser *p) { + RdbHandlers *prev=NULL, *next; + + for (int lvl = 0; lvl < RDB_LEVEL_MAX ; ++lvl) { + + if (p->numHandlers[lvl] == 0) + continue; + + p->totalHandlers += p->numHandlers[lvl]; + + if (prev == NULL) { + /* keep pointer to first handler in the chain */ + prev = p->firstHandlers = p->handlers[lvl]; + + if(p->handlers[lvl]->next == NULL) + continue; + + next = p->handlers[lvl]->next; + } else { + next = p->handlers[lvl]; + } + + /* found next handler in the chain. Connect it. And update before for next level iteration */ + prev->next = next; + while (next->next != NULL) next=next->next; + prev = next; + } +} + +static RdbStatus finalizeConfig(RdbParser *p, int isParseFromBuff) { + static int is_crc_init = 0; + assert(p->state == RDB_STATECONFIGURING); + + RDB_log(p, RDB_LOG_INFO, "Finalizing parser configuration"); + + if (!is_crc_init) { + crc64_init(); + is_crc_init = 1; + } + + if ((p->debugData = getEnvVar(ENV_VAR_DEBUG_DATA, 0)) != 0) { + RdbHandlersDataCallbacks cb = {.handleNewKey = handleNewKeyPrintDbg}; + RDB_createHandlersData(p, &cb, NULL, NULL); + } + + p->isParseFromBuff = isParseFromBuff; + + if (isParseFromBuff) { + assert (p->reader == NULL); + p->readRdbFunc = readRdbFromBuff; + } + else { + assert (p->reader != NULL); + if (getEnvVar(ENV_VAR_SIM_WAIT_MORE_DATA, 0)) + p->readRdbFunc = readRdbWaitMoreDataDbg; + else + p->readRdbFunc = readRdbFromReader; + } + + p->cache = serPoolInit(&p->mem); + + chainHandlersAcrossLevels(p); + + if (p->numHandlers[RDB_LEVEL_RAW]) { + memcpy(p->pei, peiRaw, PE_MAX * sizeof(ParsingElementInfo)); + parserRawInit(p); + } else { + memcpy(p->pei, peiStructAndData, PE_MAX * sizeof(ParsingElementInfo)); + } + + p->state = RDB_STATERUNNING; + RDB_log(p, RDB_LOG_INFO, "Start processing RDB source"); + return RDB_STATUS_OK; +} + +static void printParserState(RdbParser *p) { + printf ("Parser error code:%d\n", RDB_getErrorCode(p)); + printf ("Parser element func name: %s\n", p->pei[p->parsingElement].funcname); + printf ("Parser element func description: %s\n", p->pei[p->parsingElement].funcname); + printf ("Parser element state:%d\n", p->elmCtx.state); + serPoolPrintDbg(p); +} + +static void loggerCbDefault(RdbLogLevel l, const char *msg) { + static char *logLevelStr[] = { + [RDB_LOG_ERROR] = ":: ERROR ::", + [RDB_LOG_WARNNING] = ":: WARN ::", + [RDB_LOG_INFO] = ":: INFO ::", + [RDB_LOG_DBG] = ":: DEBUG ::", + }; + printf("%s %s\n", logLevelStr[l], msg); +} + +static void releaseReader(RdbParser *p) { + RdbReader *r = p->reader; + if (!r) return; + if (r->destructor) r->destructor(p, r->readerData); + RDB_free(p, r); +} + +static void releaseHandlers(RdbParser *p, RdbHandlers *h) { + while (h) { + RdbHandlers *next = h->next; + if (h->destructor && h->userData) h->destructor(p, h->userData); + RDB_free(p, h); + h = next; + } +} + +RdbStatus allocFromCache(RdbParser *p, + size_t len, + AllocTypeRq type, + char *refBuf, + BulkInfo **binfo) +{ + + /* pool adds termination of '\0' */ + *binfo = serPoolAlloc(p, len, type, refBuf); + + if (unlikely( (*binfo)->ref == NULL)) { + RDB_reportError(p, RDB_ERR_NO_MEMORY, + "allocFromCache() failed allocating %llu bytes (allocation type=%d)", + (unsigned long long)len, + type); + + return RDB_STATUS_ERROR; + } + + ((unsigned char *) (*binfo)->ref)[len] = '\0'; + return RDB_STATUS_OK; +} + +static inline RdbStatus unpackList(RdbParser *p, unsigned char *lp) { + char tmp = 'x'; + unsigned char *eptr; + unsigned int vlen; + long long vll; + + eptr = lpFirst( lp); + while (eptr) { + char *item = (char *)lpGetValue(eptr, &vlen, &vll); + BulkInfo *binfo; + if (item) { + /* The callback function expects a native string that is terminated + * with '\0'. However, the string we have is packed without + * termination. To avoid allocating a new string, we can follow these + * steps: + * 1. Save the last character that comes at the end of the referenced + * string in a temporary char (tmp). + * 2. allocFromCache(RQ_ALLOC_APP_BULK_REF) will: + * - Set last character '\0' to terminate the string. + * - Mark the string as a referenced bulk allocation (placement-new alloc) + * 3. invoke CALL_HANDLERS_CB that will: + * - Supply the bulk to callbacks + * - Finalize by restoring original char (from tmp) */ + tmp = item[vlen]; + IF_NOT_OK_RETURN(allocFromCache(p, vlen, RQ_ALLOC_APP_BULK_REF, item, &binfo)); + + /* if requested ref another memory but forced to allocate a new buffer, + * (since configured RDB_BULK_ALLOC_EXTERN) then copy data to the new buffer */ + if (binfo->bulkType != BULK_TYPE_REF) + memcpy(binfo->ref, item, vlen); + + } else { + int buflen = 32; + IF_NOT_OK_RETURN(allocFromCache(p, buflen, RQ_ALLOC_APP_BULK, NULL, &binfo)); + vlen = ll2string(binfo->ref, buflen, vll); + binfo->len = vlen; /* update len */ + } + + registerAppBulkForNextCb(p, binfo); + CALL_HANDLERS_CB(p, + item[vlen] = tmp, /* <<< finalize: restore modified char */ + RDB_LEVEL_DATA, + rdbData.handleListElement, + binfo->ref); + eptr = lpNext( lp, eptr); + } + return RDB_STATUS_OK; +} + +static RdbHandlers *createHandlersCommon(RdbParser *p, + void *userData, + RdbFreeFunc f, + RdbHandlersLevel level) { + assert(p->state == RDB_STATECONFIGURING); + /* alloc & register empty handlers in parser */ + RdbHandlers *h = (RdbHandlers *) RDB_alloc(p, sizeof(RdbHandlers)); + memset(h, 0, sizeof(RdbHandlers)); + h->userData = userData; + h->destructor = f; + h->level = level; + h->parser = p; + h->next = p->handlers[level]; + + p->handlers[level] = h; + p->numHandlers[level] += 1; + return h; +} + +/*** sub-element parsing ***/ + +RdbStatus subElementCall(RdbParser *p, ParsingElementType next, int returnState) { + + assert(p->callSubElm.callerElm == PE_MAX); /* prev sub-element flow ended */ + + /* release bulk from previous flow of subElement */ + freeUnmanagedBulk(p, &p->callSubElm.bulkResult); + + p->callSubElm.callerElm = p->parsingElement; + p->callSubElm.stateToReturn = returnState; + return nextParsingElement(p, next); +} + +RdbStatus subElementReturn(RdbParser *p, BulkInfo *bulkResult) { + p->callSubElm.bulkResult = *bulkResult; + return nextParsingElementState(p, p->callSubElm.callerElm, p->callSubElm.stateToReturn); +} + +void subElementCallEnd(RdbParser *p, RdbBulk *bulkResult, size_t *len) { + *bulkResult = p->callSubElm.bulkResult.ref; + *len = p->callSubElm.bulkResult.len; + p->callSubElm.callerElm = PE_MAX; /* mark as done */ +} + +/*** Parsing Elements ***/ + +RdbStatus elementRdbHeader(RdbParser *p) { + BulkInfo *binfo; + + /* read REDIS signature and RDB version */ + IF_NOT_OK_RETURN(rdbLoad(p, 9, RQ_ALLOC, NULL, &binfo)); + + /*** ENTER SAFE STATE ***/ + + if (memcmp(binfo->ref, "REDIS", 5) != 0) { + RDB_reportError(p, RDB_ERR_WRONG_FILE_SIGNATURE, + "Wrong signature trying to load DB from file"); + return RDB_STATUS_ERROR; + } + + /* read rdb version */ + p->rdbversion = atoi(((char *) binfo->ref) + 5); + if (p->rdbversion < 1 || p->rdbversion > RDB_VERSION) { + RDB_reportError(p, RDB_ERR_WRONG_FILE_VERSION, + "Can't handle RDB format version: %d", p->rdbversion); + return RDB_STATUS_ERROR; + } + + RDB_log(p, RDB_LOG_INFO, "rdbversion=%d", p->rdbversion); + + return nextParsingElement(p, PE_NEXT_RDB_TYPE); +} + +RdbStatus elementAuxField(RdbParser *p) { + BulkInfo *binfoAuxKey, *binfoAuxVal; + + IF_NOT_OK_RETURN(rdbLoadString(p, RQ_ALLOC_APP_BULK, NULL, &binfoAuxKey)); + IF_NOT_OK_RETURN(rdbLoadString(p, RQ_ALLOC_APP_BULK, NULL, &binfoAuxVal)); + + /*** ENTER SAFE STATE ***/ + + registerAppBulkForNextCb(p, binfoAuxKey); + registerAppBulkForNextCb(p, binfoAuxVal); + CALL_COMMON_HANDLERS_CB(p, handleAuxField, binfoAuxKey->ref, binfoAuxVal->ref); + + return nextParsingElement(p, PE_NEXT_RDB_TYPE); +} + +RdbStatus elementSelectDb(RdbParser *p) { + uint64_t dbid; + + IF_NOT_OK_RETURN(rdbLoadLen(p, NULL, &dbid, NULL, NULL)); + + /*** ENTER SAFE STATE ***/ + + CALL_COMMON_HANDLERS_CB(p, handleNewDb, ((int) dbid)); + return nextParsingElement(p, PE_NEXT_RDB_TYPE); +} + +RdbStatus elementResizeDb(RdbParser *p) { + /* RESIZEDB: Hint about the size of the keys in the currently + * selected data base, in order to avoid useless rehashing. */ + uint64_t db_size, expires_size; + + IF_NOT_OK_RETURN(rdbLoadLen(p, NULL, &db_size, NULL, NULL)); + IF_NOT_OK_RETURN(rdbLoadLen(p, NULL, &expires_size, NULL, NULL)); + + /*** ENTER SAFE STATE ***/ + + CALL_COMMON_HANDLERS_CB(p, handleDbSize, db_size, expires_size); + + return nextParsingElement(p, PE_NEXT_RDB_TYPE); +} + +RdbStatus elementNewKey(RdbParser *p) { + BulkInfo *binfoKey; + + IF_NOT_OK_RETURN(rdbLoadString(p, RQ_ALLOC_APP_BULK, NULL, &binfoKey)); + + /*** ENTER SAFE STATE ***/ + + registerAppBulkForNextCb(p, binfoKey); + CALL_COMMON_HANDLERS_CB(p, handleNewKey, binfoKey->ref, &p->elmCtx.key.info); + + /* reset values for next key */ + p->elmCtx.key.info.expiretime = -1; + p->elmCtx.key.info.lru_idle = -1; + p->elmCtx.key.info.lfu_freq = -1; + + /* Read value */ + return nextParsingElement(p, p->elmCtx.key.valueType); +} + +RdbStatus elementExpireTime(RdbParser *p) { + BulkInfo *binfo; + + IF_NOT_OK_RETURN(rdbLoad(p, 4, RQ_ALLOC, NULL, &binfo)); + + /*** ENTER SAFE STATE ***/ + + p->elmCtx.key.info.expiretime = ((time_t) *((int32_t *) binfo->ref)) * 1000; + return nextParsingElement(p, PE_NEXT_RDB_TYPE); +} + +/* This function loads a time from the RDB file. It gets the version of the + * RDB because, unfortunately, before Redis 5 (RDB version 9), the function + * failed to convert data to/from little endian, so RDB files with keys having + * expires could not be shared between big endian and little endian systems + * (because the expire time will be totally wrong). The fix for this is just + * to call memrev64ifbe(), however if we fix this for all the RDB versions, + * this call will introduce an incompatibility for big endian systems: + * after upgrading to Redis version 5 they will no longer be able to load their + * own old RDB files. Because of that, we instead fix the function only for new + * RDB versions, and load older RDB versions as we used to do in the past, + * allowing big endian systems to load their own old RDB files. */ +RdbStatus elementExpireTimeMsec(RdbParser *p) { + BulkInfo *binfo; + + IF_NOT_OK_RETURN(rdbLoad(p, 8, RQ_ALLOC, NULL, &binfo)); + + /*** ENTER SAFE STATE ***/ + + if (p->rdbversion >= 9) /* Check the top comment of this function. */ + memrev64ifbe(((int64_t *) pRead)); /* Convert in big endian if the system is BE. */ + + p->elmCtx.key.info.expiretime = (long long) binfo->ref; + return nextParsingElement(p, PE_NEXT_RDB_TYPE); +} + +RdbStatus elementNextRdbType(RdbParser *p) { + BulkInfo *biType; + + /* Load a "type" in RDB format, that is a one byte unsigned integer */ + IF_NOT_OK_RETURN(rdbLoad(p, 1, RQ_ALLOC, NULL, &biType)); + + /*** ENTER SAFE STATE ***/ + + p->currOpcode = *((unsigned char *)biType->ref); + switch (p->currOpcode) { + case RDB_OPCODE_EXPIRETIME: return nextParsingElement(p, PE_EXPIRETIME); + case RDB_OPCODE_EXPIRETIME_MS: return nextParsingElement(p, PE_EXPIRETIMEMSEC); + case RDB_OPCODE_AUX: return nextParsingElement(p, PE_AUX_FIELD); + case RDB_OPCODE_SELECTDB: return nextParsingElement(p, PE_SELECT_DB); + case RDB_OPCODE_RESIZEDB: return nextParsingElement(p, PE_RESIZE_DB); + case RDB_TYPE_STRING: return nextParsingElementKeyValue(p, PE_NEW_KEY, PE_STRING); + case RDB_TYPE_LIST_QUICKLIST: return nextParsingElementKeyValue(p, PE_NEW_KEY, PE_LIST); + case RDB_TYPE_LIST_QUICKLIST_2: return nextParsingElementKeyValue(p, PE_NEW_KEY, PE_LIST); + case RDB_OPCODE_EOF: return nextParsingElement(p, PE_END_OF_FILE); + + case RDB_OPCODE_FREQ: + case RDB_OPCODE_IDLE: + case RDB_OPCODE_GFLAGS: + case RDB_OPCODE_GCAS: + case RDB_OPCODE_MODULE_AUX: + case RDB_OPCODE_FUNCTION: + case RDB_OPCODE_FUNCTION2: + case RDB_TYPE_LIST: + case RDB_TYPE_SET: + case RDB_TYPE_ZSET: + case RDB_TYPE_HASH: + case RDB_TYPE_ZSET_2: + case RDB_TYPE_MODULE_2: + case RDB_TYPE_HASH_ZIPMAP: + case RDB_TYPE_LIST_ZIPLIST: + case RDB_TYPE_SET_INTSET: + case RDB_TYPE_ZSET_ZIPLIST: + case RDB_TYPE_HASH_ZIPLIST: + case RDB_TYPE_STREAM_LISTPACKS: + case RDB_TYPE_HASH_LISTPACK: + case RDB_TYPE_ZSET_LISTPACK: + case RDB_TYPE_STREAM_LISTPACKS_2: + case RDB_TYPE_SET_LISTPACK: + case RDB_TYPE_STREAM_LISTPACKS_3: + RDB_reportError(p, RDB_ERR_NOT_SUPPORTED_RDB_ENCODING_TYPE, + "Not supported RDB encoding type: %d", p->currOpcode); + return RDB_STATUS_ERROR; + + default: + RDB_reportError(p, RDB_ERR_UNKNOWN_RDB_ENCODING_TYPE, "Unknown RDB encoding type"); + return RDB_STATUS_ERROR; + } +} + +RdbStatus elementEndKey(RdbParser *p) { + /*** ENTER SAFE STATE ***/ + CALL_COMMON_HANDLERS_CB(p, handleEndKey); + return nextParsingElement(p, PE_NEXT_RDB_TYPE); +} + +RdbStatus elementString(RdbParser *p) { + BulkInfo *binfoStr; + + IF_NOT_OK_RETURN(rdbLoadString(p, RQ_ALLOC_APP_BULK, NULL, &binfoStr)); + + /*** ENTER SAFE STATE ***/ + + registerAppBulkForNextCb(p, binfoStr); + CALL_HANDLERS_CB(p, NOP, RDB_LEVEL_DATA, rdbData.handleStringValue, binfoStr->ref); + + registerAppBulkForNextCb(p, binfoStr); + CALL_HANDLERS_CB(p, NOP, RDB_LEVEL_STRUCT, rdbData.handleStringValue, binfoStr->ref); + + return nextParsingElement(p, PE_END_KEY); +} + +RdbStatus elementList(RdbParser *p) { + ElementCtx *ctx = &p->elmCtx; + enum LIST_STATES { + ST_LIST_HEADER=0, /* Retrieve number of nodes */ + ST_LIST_NEXT_NODE /* Process next node and callback to app (Iterative) */ + } ; + + switch (ctx->state) { + case ST_LIST_HEADER: + IF_NOT_OK_RETURN(rdbLoadLen(p, NULL, &(ctx->list.numNodes), NULL, NULL)); + + /*** ENTER SAFE STATE ***/ + + updateElementState(p, ST_LIST_NEXT_NODE); /* fall-thru */ + + case ST_LIST_NEXT_NODE: { + uint64_t container; + BulkInfo *binfoNode; + + /* is end of list */ + if (ctx->list.numNodes == 0) + return nextParsingElement(p, PE_END_KEY); + + IF_NOT_OK_RETURN(rdbLoadLen(p, NULL, &container, NULL, NULL)); + + if (container != QUICKLIST_NODE_CONTAINER_PACKED && + container != QUICKLIST_NODE_CONTAINER_PLAIN) { + RDB_reportError(p, RDB_ERR_QUICK_LIST_INTEG_CHECK, "elementList(1): Quicklist integrity check failed"); + return RDB_STATUS_ERROR; + } + + IF_NOT_OK_RETURN(rdbLoadString(p, RQ_ALLOC_APP_BULK, NULL, &binfoNode)); + + /* ****************************** ENTER SAFE STATE ********************************* + * STARTING FROM THIS POINT, UP-TO END OF STATE, WON'T BE ANY MORE READS FROM RDB, * + * SO IT IS SAFE NOW TO CALL HANDLERS CALLBACKS WITHOUT THE RISK OF ROLLBACK DUE * + * TO `RDB_STATUS_WAIT_MORE_DATA` (WE CAN ADD LOCK VERIFICATION BY NEED). * + ***********************************************************************************/ + + if (container == QUICKLIST_NODE_CONTAINER_PLAIN) { + registerAppBulkForNextCb(p, binfoNode); + CALL_HANDLERS_CB(p, NOP, RDB_LEVEL_DATA, rdbData.handleListElement, binfoNode->ref); + + registerAppBulkForNextCb(p, binfoNode); + CALL_HANDLERS_CB(p, NOP, RDB_LEVEL_STRUCT, rdbStruct.handlerPlainNode, binfoNode->ref); + return RDB_STATUS_OK; + } + + unsigned char *lp = (unsigned char *) binfoNode->ref; + + if (p->currOpcode == RDB_TYPE_LIST_QUICKLIST_2) { + if (!lpValidateIntegrity(lp, binfoNode->len, p->deepIntegCheck, NULL, NULL)) { + RDB_reportError(p, RDB_ERR_QUICK_LIST_INTEG_CHECK, + "elementList(2): Quicklist integrity check failed"); + return RDB_STATUS_ERROR; + } + } else { + /* TODO: elementList() - ziplistValidateIntegrity */ + assert(0); + } + + /* Silently skip empty listpack */ + if (lpLength(lp) == 0) return RDB_STATUS_OK; + + registerAppBulkForNextCb(p, binfoNode); + CALL_HANDLERS_CB(p, NOP, RDB_LEVEL_STRUCT, rdbStruct.handlerQListNode, binfoNode->ref); + + /* unpackList makes multiple callbacks. all data in ctx.lp */ + /* TODO: unpack only if cb handleListElement is registered */ + IF_NOT_OK_RETURN(unpackList(p, lp)); + + /* Update context (context update must being made only from safe state. For sure won't be rollback) */ + --ctx->list.numNodes; + + return updateElementState(p, ST_LIST_NEXT_NODE); + } + + default: + RDB_reportError(p, RDB_ERR_QUICK_LIST_INVALID_STATE, + "elementList() : invalid parsing element state"); + return RDB_STATUS_ERROR; + } +} + +RdbStatus elementEndOfFile(RdbParser *p) { + /* Verify the checksum if RDB version is >= 5 */ + if (p->rdbversion >= 5) { + BulkInfo *bulkInfo; + uint64_t cksum; + uint64_t evaluated = p->checksum; + + + IF_NOT_OK_RETURN(rdbLoad(p, 8, RQ_ALLOC, NULL, &bulkInfo)); + cksum = *((uint64_t *) bulkInfo->ref); + + if (!p->ignoreChecksum) { + memrev64ifbe(&cksum); + if (cksum == 0) { + RDB_log(p, RDB_LOG_WARNNING, "RDB file was saved with checksum disabled: no check performed."); + } else if (cksum != evaluated) { + RDB_reportError(p, RDB_ERR_CHECKSUM_FAILURE, "Wrong RDB checksum checksum=%lx, evaluated=%lx", + (unsigned long long) cksum, + (unsigned long long) p->checksum); + return RDB_STATUS_ERROR; + } + } + } + + CALL_COMMON_HANDLERS_CB(p, handleEndRdb); + return RDB_STATUS_ENDED; /* THE END */ +} + +/*** Loaders from RDB ***/ + +RdbStatus rdbLoadInteger(RdbParser *p, int enctype, AllocTypeRq type, char *refBuf, BulkInfo **binfo) { + long long val; + + if (enctype == RDB_ENC_INT8) { + IF_NOT_OK_RETURN(rdbLoad(p, 1, RQ_ALLOC, NULL, binfo)); + val = ((unsigned char *) (*binfo)->ref)[0]; + } else if (enctype == RDB_ENC_INT16) { + uint16_t v; + IF_NOT_OK_RETURN(rdbLoad(p, 2, RQ_ALLOC, NULL, binfo)); + v = ((uint32_t) ((unsigned char *) (*binfo)->ref)[0])| + ((uint32_t)((unsigned char *) (*binfo)->ref)[1]<<8); + val = (int16_t)v; + } else if (enctype == RDB_ENC_INT32) { + uint32_t v; + IF_NOT_OK_RETURN(rdbLoad(p, 4, RQ_ALLOC, NULL, binfo)); + v = ((uint32_t)((unsigned char *) (*binfo)->ref)[0])| + ((uint32_t)((unsigned char *) (*binfo)->ref)[1]<<8)| + ((uint32_t)((unsigned char *) (*binfo)->ref)[2]<<16)| + ((uint32_t)((unsigned char *) (*binfo)->ref)[3]<<24); + val = (int32_t)v; + } else { + RDB_log(p, RDB_LOG_ERROR, "Unknown RDB integer encoding type %d", enctype); + RDB_reportError(p, RDB_ERR_INVALID_INT_ENCODING, NULL); + return RDB_STATUS_ERROR; + } + + char buf[LONG_STR_SIZE]; + int len = ll2string(buf,sizeof(buf),val); + + IF_NOT_OK_RETURN(allocFromCache(p, len, type, refBuf, binfo)); + memcpy((*binfo)->ref, buf, len); + return RDB_STATUS_OK; +} + +/* Load an encoded length. Read length is set to '*lenptr'. If instead the + * loaded length describes a special encoding that follows, then '*isencoded' + * is set to 1 and the encoding format is stored at '*lenptr'. + * + * outbuff is optional, in case you want the raw encoded data too, and should + * have room for at least 9 bytes for worse case. + * outbufflen should be pre-zeroed by the caller. + * + * The function returns -1 on error, 0 on success. */ +RdbStatus rdbLoadLen(RdbParser *p, int *isencoded, uint64_t *lenptr, unsigned char* outbuff, int *outbufflen) { + unsigned char buf[2]; + BulkInfo *binfo; + int type; + + if (isencoded) *isencoded = 0; + + /* Load a "type" in RDB format, that is a one byte unsigned integer */ + IF_NOT_OK_RETURN(rdbLoad(p, 1, RQ_ALLOC, NULL, &binfo)); + buf[0] = *((unsigned char *) binfo->ref); + + if (outbuff) outbuff[0] = buf[0], (*outbufflen)++; + type = (buf[0]&0xC0)>>6; + if (type == RDB_ENCVAL) { + /* Read a 6 bit encoding type. */ + if (isencoded) *isencoded = 1; + *lenptr = buf[0]&0x3F; + } else if (type == RDB_6BITLEN) { + /* Read a 6 bit len. */ + *lenptr = buf[0]&0x3F; + } else if (type == RDB_14BITLEN) { + /* Read a 14 bit len. */ + IF_NOT_OK_RETURN(rdbLoad(p, 1, RQ_ALLOC, NULL, &binfo)); + buf[1] = *((unsigned char *) binfo->ref); + if (outbuff) outbuff[1] = buf[1], (*outbufflen)++; + *lenptr = ((buf[0]&0x3F)<<8)|buf[1]; + } else if (buf[0] == RDB_32BITLEN) { + /* Read a 32 bit len. */ + IF_NOT_OK_RETURN(rdbLoad(p, 4, RQ_ALLOC, NULL, &binfo)); + if (outbuff) memcpy(outbuff+1, binfo->ref, 4), (*outbufflen)+=4; + *lenptr = ntohl( (*((uint32_t *)binfo->ref))); + } else if (buf[0] == RDB_64BITLEN) { + /* Read a 64 bit len. */ + IF_NOT_OK_RETURN(rdbLoad(p, 8, RQ_ALLOC, NULL, &binfo)); + if (outbuff) memcpy(outbuff+1, &binfo->ref, 8), (*outbufflen)+=8; + *lenptr = ntohu64(*((uint64_t *)binfo->ref)); + } else { + RDB_reportError(p, RDB_ERR_INVALID_LEN_ENCODING, "Unknown length encoding %d in rdbLoadLen()",type); + return RDB_STATUS_ERROR; + } + return RDB_STATUS_OK; +} + +RdbStatus rdbLoadLzfString(RdbParser *p, AllocTypeRq type, char *refBuf, BulkInfo **binfo) { + BulkInfo *binfoComp; + uint64_t len, clen; + + IF_NOT_OK_RETURN(rdbLoadLen(p, NULL, &clen, NULL, NULL)); + IF_NOT_OK_RETURN(rdbLoadLen(p, NULL, &len, NULL, NULL)); + + /* Load the compressed representation */ + IF_NOT_OK_RETURN(rdbLoad(p, clen, RQ_ALLOC, NULL, &binfoComp)); + + /* Allocate our target according to the uncompressed size. */ + IF_NOT_OK_RETURN(allocFromCache(p, len, type, refBuf, binfo)); + + /* and uncompress it to target */ + if (lzf_decompress(binfoComp->ref, clen, (*binfo)->ref, len) != len) { + RDB_reportError(p, RDB_ERR_STRING_INVALID_LZF_COMPRESSED, + "rdbLoadLzfString(): Invalid LZF compressed string"); + return RDB_STATUS_ERROR; + } + + return RDB_STATUS_OK; +} + +/* eq. to redis fork: rdbGenericLoadStringObject() */ +RdbStatus rdbLoadString(RdbParser *p, AllocTypeRq type, char *refBuf, BulkInfo **binfo) { + int isencoded; + uint64_t len; + + IF_NOT_OK_RETURN(rdbLoadLen(p, &isencoded, &len, NULL, NULL)); + + if (isencoded) { + switch(len) { + case RDB_ENC_INT8: + case RDB_ENC_INT16: + case RDB_ENC_INT32: + return rdbLoadInteger(p, len, type, refBuf, binfo); + + case RDB_ENC_LZF: + return rdbLoadLzfString(p, type, refBuf, binfo); + default: + RDB_reportError(p, RDB_ERR_STRING_UNKNOWN_ENCODING_TYPE, + "rdbLoadString(): Unknown RDB string encoding type: %llu",len); + return RDB_STATUS_ERROR; + } + } + + return rdbLoad(p, len, type, refBuf, binfo); +} + +/*** RDB Reader functions ***/ + +/* simulate WAIT_MORE_DATA for each new read from RDB reader */ +static RdbStatus readRdbWaitMoreDataDbg(RdbParser *p, size_t len, AllocTypeRq type, char *refBuf, BulkInfo **binfo) { + if (serPoolIsNewNextAllocDbg(p)) { + static uint64_t waitMoreDataCounterDbg = 0; + if (++waitMoreDataCounterDbg % 2) { + return RDB_STATUS_WAIT_MORE_DATA; + } + } + + return readRdbFromReader(p, len, type, refBuf, binfo); +} + +static RdbStatus readRdbFromReader(RdbParser *p, size_t len, AllocTypeRq type, char *refBuf, BulkInfo **binfo) { + RdbStatus res; + + IF_NOT_OK_RETURN(allocFromCache(p, len, type, refBuf, binfo)); + + /* if either first time to read, or filled only partially last time */ + if (likely(len > (*binfo)->written)) { + size_t bytesToFill = len - (*binfo)->written; + p->bytesRead += bytesToFill; /* likely */ + + /* if needed to read less due to pause interval */ + if (unlikely(p->bytesRead > p->bytesToNextPause)) { + /* correct the values */ + size_t overflow = p->bytesRead - p->bytesToNextPause; + bytesToFill -= overflow; + p->bytesRead -= overflow; + + res = p->reader->readFunc(p, p->reader->readerData, + ((char *) (*binfo)->ref) + (*binfo)->written, bytesToFill); + (*binfo)->written += bytesToFill; + if (res != RDB_STATUS_OK) goto not_ok; + return RDB_STATUS_PAUSED; + } else { /* fill up entire item */ + + res = p->reader->readFunc(p, p->reader->readerData, + ((char *) (*binfo)->ref) + (*binfo)->written, bytesToFill); + if (unlikely(res != RDB_STATUS_OK)) { + (*binfo)->written += bytesToFill; + goto not_ok; + } + + /* done read entirely. Eval crc of entire read */ + (*binfo)->written = DONE_FILL_BULK; + p->checksum = crc64(p->checksum, (unsigned char *) (*binfo)->ref, len); + return res; + } + } else { + if (likely(len == (*binfo)->written)) { + /* Got last time WAIT_MORE_DATA. assumed async read filled it up */ + + /* After WAIT_MORE_DATA we cannot eval crc. Update it now. */ + p->checksum = crc64(p->checksum, (unsigned char *) (*binfo)->ref, len); + (*binfo)->written = DONE_FILL_BULK; + } + return RDB_STATUS_OK; + } + +not_ok: + if (res == RDB_STATUS_ERROR) { + /* verify reader reported an error. Otherwise set such one */ + if (p->errorCode == RDB_OK) { + p->errorCode = RDB_ERR_FAILED_READ_RDB_FILE; + RDB_log(p, RDB_LOG_WARNNING, "Reader returned error indication but didn't RDB_reportError()"); + } + } else { + /* reader can return only ok, wait-more-data or error */ + assert(res == RDB_STATUS_WAIT_MORE_DATA); + } + return res; +} + +static RdbStatus readRdbFromBuff(RdbParser *p, size_t len, AllocTypeRq type, char *refBuf, BulkInfo **binfo) { + size_t toFillBeforePause, leftInBuff, leftToFillItem; + + IF_NOT_OK_RETURN(allocFromCache(p, len, type, refBuf, binfo)); + + /* if spItem was already filled (due to parser rollback). Nothing to do. */ + if (unlikely((*binfo)->written >= len)) + return RDB_STATUS_OK; + + leftInBuff = p->parsebuffCtx.end - p->parsebuffCtx.at; + leftToFillItem = len - (*binfo)->written; + + if (likely(leftToFillItem <= leftInBuff)) { /* enough to fill item */ + p->bytesRead += leftToFillItem; + + /* if needed to read less due to pause interval */ + if (unlikely(p->bytesRead > p->bytesToNextPause)) { + size_t overflow = p->bytesRead - p->bytesToNextPause; + p->bytesRead -= overflow; + toFillBeforePause = leftToFillItem - overflow; + + memcpy(((char *) (*binfo)->ref) + (*binfo)->written, + p->parsebuffCtx.at, + toFillBeforePause); + p->parsebuffCtx.at += toFillBeforePause; + (*binfo)->written += toFillBeforePause; + return RDB_STATUS_PAUSED; + } else { + memcpy(((char *) (*binfo)->ref) + (*binfo)->written, + p->parsebuffCtx.at, + leftToFillItem); + p->checksum = crc64(p->checksum, (*binfo)->ref, len); + p->parsebuffCtx.at += leftToFillItem; + + (*binfo)->written = DONE_FILL_BULK; + return RDB_STATUS_OK; + } + } else { /* not enough to fill item */ + p->bytesRead += leftInBuff; + + /* if needed to read less due to pause interval */ + if (unlikely(p->bytesRead > p->bytesToNextPause)) { + size_t overflow = p->bytesRead - p->bytesToNextPause; + p->bytesRead -= overflow; + toFillBeforePause = leftInBuff - overflow; + + memcpy(((char *) (*binfo)->ref) + (*binfo)->written, + p->parsebuffCtx.at, + toFillBeforePause); + p->parsebuffCtx.at += toFillBeforePause; + (*binfo)->written += toFillBeforePause; + return RDB_STATUS_PAUSED; + } else { + memcpy(((char *) (*binfo)->ref) + (*binfo)->written, + p->parsebuffCtx.at, + leftInBuff); + p->parsebuffCtx.at += leftInBuff; + (*binfo)->written += leftInBuff; + return RDB_STATUS_WAIT_MORE_DATA; + } + } +} diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 0000000..e6ebdce --- /dev/null +++ b/src/parser.h @@ -0,0 +1,374 @@ +#ifndef LIBRDB_PARSER_H +#define LIBRDB_PARSER_H + +#include +#include +#include "../api/librdb-api.h" + +#define MAX_ERROR_MSG 1024 +#define MAX_APP_BULKS 2 +#define NOP /*no-op*/ +#define IF_NOT_OK_RETURN(cmd) do {RdbStatus s; s = cmd; if (unlikely(s!=RDB_STATUS_OK)) return s;} while (0) + +/* parser internal status value. Not exposed at to the caller. + * Saves us another stopping condition in main loop. */ +#define RDB_STATUS_ENDED 999 + +#define RAW_AGG_FIRST_STATIC_BUFF_LEN (1024*32) + +#define UNUSED(...) unused( (void *) NULL, ##__VA_ARGS__); +inline void unused(void *dummy, ...) { (void)(dummy);} + +#define CALL_COMMON_HANDLERS_CB(p, callback, ...) \ + do { \ + for (RdbHandlers *h = p->firstHandlers; h ; h = h->next) { \ + if (h->h.common.callback) { \ + p->errorCode = h->h.common.callback(p, h->userData, ##__VA_ARGS__); \ + if (unlikely(p->errorCode != RDB_OK)) { \ + if (p->errorCode == RDB_OK_DONT_PROPAGATE) { \ + p->errorCode = RDB_OK; \ + /* skip all handlers until next level */ \ + RdbHandlersLevel currLevel = h->level; \ + while ( (h->next) && (h->next->level == currLevel) ) h = h->next; \ + continue; \ + } \ + return RDB_STATUS_ERROR; \ + } \ + } \ + } \ + p->appCbCtx.numBulks = 0; \ + } while (0) + +#define CALL_HANDLERS_CB(p, finalize_cmd, lvl,level_and_callback, ...) \ + do { \ + RdbHandlers *h = p->handlers[lvl]; \ + for (int ii = 0 ; ii < p->numHandlers[lvl] ; ++ii) { \ + if (h->h.level_and_callback) { \ + p->errorCode = h->h.level_and_callback(p, h->userData, ##__VA_ARGS__);\ + if (unlikely(p->errorCode != RDB_OK)) { \ + if (p->errorCode == RDB_OK_DONT_PROPAGATE) { \ + p->errorCode = RDB_OK; \ + break; /* Don't propagate to next handler */ \ + } \ + finalize_cmd; \ + return RDB_STATUS_ERROR; \ + } \ + } \ + h = h->next; \ + } \ + p->appCbCtx.numBulks = 0; \ + finalize_cmd; \ + } while (0) + +typedef enum BulkType { + BULK_TYPE_STACK, /* from SerializedStack */ + BULK_TYPE_HEAP, /* from heap bulk */ + BULK_TYPE_EXTERN, /* from external allocator */ + BULK_TYPE_REF, /* Reference another memory bulk */ + BULK_TYPE_MAX +} BulkType; + +typedef struct BulkInfo { + BulkType bulkType; + void *ref; + size_t len; /* allocation size, not including '\0' at the end */ + size_t written; +} BulkInfo; + +/* Allocation requests from the parser to SerializedPools */ +typedef enum { + /* Allocate for internal use of the parser */ + RQ_ALLOC, + RQ_ALLOC_REF, /*placement-new*/ + /* Allocate RdbBulk in order to pass it to app callbacks */ + RQ_ALLOC_APP_BULK, + RQ_ALLOC_APP_BULK_REF, + RQ_ALLOC_MAX +} AllocTypeRq; + +/* Unmanaged allocation requests from the parser */ +typedef enum { + /* Allocate for internal use of the parser */ + UNMNG_RQ_ALLOC, + /* Allocate RdbBulk in order to pass it to app callbacks */ + UNMNG_RQ_ALLOC_APP_BULK, + UNMNG_RQ_ALLOC_APP_BULK_REF, + UNMNG_RQ_ALLOC_MAX +} AllocUnmngTypeRq; + +typedef enum ParsingElementType { + PE_RDB_HEADER, + PE_NEXT_RDB_TYPE, + PE_AUX_FIELD, + PE_SELECT_DB, + PE_RESIZE_DB, + PE_EXPIRETIME, + PE_EXPIRETIMEMSEC, + PE_NEW_KEY, + PE_END_KEY, + PE_STRING, + PE_LIST, + PE_END_OF_FILE, + PE_MAX +} ParsingElementType; + +typedef struct SerializedPool SerializedPool; /* fwd decl */ + +typedef RdbStatus (*ParsingElementFunc) (RdbParser *p); +typedef RdbStatus (*ReadRdbFunc)(RdbParser *p, size_t len, AllocTypeRq type, char *refBuf, BulkInfo **out); + +typedef struct ParsingElementInfo { + ParsingElementFunc func; + const char *funcname; + const char *description; +} ParsingElementInfo; + +extern ParsingElementInfo peiRaw[PE_MAX]; + +typedef struct { + uint64_t numNodes; +} ElementListCtx; + +typedef struct { + RdbKeyInfo info; + ParsingElementType valueType; +} ElementKeyCtx; + +typedef struct { + unsigned char **buff; + uint64_t len, clen, uclen, encoding; + int isencoded; +} ElementRawStringCtx; + +typedef struct { + uint64_t numNodes; + uint64_t container; +} ElementRawListCtx; + +typedef struct ElementCtx { + ElementKeyCtx key; + ElementListCtx list; + + /* raw elements context */ + ElementRawStringCtx rawString; + ElementRawListCtx rawList; + + int state; /* parsing-element state */ + +} ElementCtx; + +/* The parser can handle one level of nested parsing-elements (PE), whereby a PE + * may be called by another PE and control is returned to the caller once the + * parsing of sub-element is complete. Currently, this functionality is only + * utilized by the raw list PE, which calls upon the raw string PE to parse + * individual string elements. */ +typedef struct ParsingSubElement { + + /* let callee knows which element and state to callback once done */ + ParsingElementType callerElm; + int stateToReturn; + + BulkInfo bulkResult; /* unmanaged bulk (Callee alloc -> caller release) */ +} ParsingSubElement; + +typedef struct AppCallbackCtx { + /* The array bulks[] keep RdbBulks metadata out of band and available by need + * for operations by next callback, such as bulk length, or how to clone, + * etc. Note that keeping bulks metadata out-of-band (i.e. not sequentially + * in memory with a preceding header) is essential in case when bulkAllocType + * is set to external since external allocations expect to receive only bare + * data. It is also essential when referenced another memory in order to + * avoid yet another allocation. */ + BulkInfo *bulks[MAX_APP_BULKS]; + int numBulks; /* reset to 0 after each HANDLERS_CB */ +} AppCallbackCtx; + +typedef struct RawContext { + + /* aggType - The parser encounters difficulty determining the size of + * certain types during their parsing because they lack a preceding header + * that would indicate their size. This creates a problem when implementing + * callback handlers for executing RESP2 RESTORE commands on a live server, + * which requires the type's size to be sent at the beginning. To resolve + * this issue, the parser gathers all payload data for these types and only + * provides it to callback handlers once it reaches the end of the type and + * knows its total size. However, for types like strings, whose sizes are + * already known at the beginning, the parser will not aggregate the entire + * payload if it is large enough, but stream it to handleFrag callback. */ + enum { + AGG_TYPE_UNINIT, + AGG_TYPE_ENTIRE_DATA, + AGG_TYPE_PARTIALLY + } aggType; + + char staticBulk[RAW_AGG_FIRST_STATIC_BUFF_LEN]; + BulkInfo *bulkArray; /* exp-growth */ + + char *at; + int curBulkIndex; + size_t totalSize; +} RawContext; + +struct RdbParser { + + RdbState state; /* internal state */ + int currOpcode; /* current RDB opcode */ + ParsingElementInfo pei[PE_MAX]; + ParsingElementType parsingElement; /* current PE (parsing element) */ + ParsingSubElement callSubElm; + + /*** parser handlers ***/ + /* Maintain a chain of Handlers to each level. Each Handlers embed in it pointer to `next` handlers */ + RdbHandlers *handlers[RDB_LEVEL_MAX]; + int numHandlers[RDB_LEVEL_MAX]; + int totalHandlers; + RdbHandlers *firstHandlers; + + /*** configuration ***/ + RdbMemAlloc mem; + int deepIntegCheck; + int ignoreChecksum; + RdbLoggerCB loggerCb; + RdbLogLevel logLevel; + size_t maxRawLen; + + /*** context ***/ + ElementCtx elmCtx; /* parsing-element context */ + AppCallbackCtx appCbCtx; /* Trace bulks that will be given to next app cb. Cleared after each cb */ + RawContext rawCtx; + + /*** caching ***/ + SerializedPool *cache; /* Cleared after each parsing-element state change */ + + /*** error reporting ***/ + RdbRes errorCode; + char errorMsg[MAX_ERROR_MSG]; + + /*** read RDB from reader VS read RDB from buffer ***/ + + int isParseFromBuff; /* bool */ + size_t bytesRead; + RdbReader *reader; + struct { + unsigned char *start; + unsigned char *end; + unsigned char *at; + size_t size; + } parsebuffCtx; + + /* points to: readRdbFromBuff / readRdbFromReader / readRdbWaitMoreDataDbg */ + ReadRdbFunc readRdbFunc; + + /*** pause interval ***/ + /* pauseInterval - At least number of bytes to process before pausing. App can configure + * it via RDB_setPauseInterval(). In addition, if handler's callback wishes to suspend + * immediately because some condition has met, then it need to call RDB_pauseParser(). */ + size_t pauseInterval; + size_t bytesToNextPause; + + /*** misc ***/ + int rdbversion; /* keep aside RDB version */ + uint64_t checksum; + int debugData; /* if envvar LIBRDB_DEBUG_DATA=1 then print state machine transitions */ +}; + +/* reader base struct */ +struct RdbReader { + void *readerData; + RdbReaderFunc readFunc; + RdbParser *parser; + RdbFreeFunc destructor; /* destructor for "derived structs" of RdbReader */ +}; + +typedef struct HandlersCommonCallbacks { + HANDLERS_COMMON_CALLBACKS +} HandlersCommonCallbacks; + +/* RDB Handlers base struct */ +struct RdbHandlers { + void *userData; + + RdbHandlers *next; /* next handlers in the parser's stack (at level: level) */ + RdbHandlersLevel level; /* handlers registered at what level of the parser (bluk/struct/data) */ + RdbFreeFunc destructor; /* destructor for derived structs of this struct */ + RdbParser *parser; + + union { + HandlersCommonCallbacks common; + RdbHandlersRawCallbacks rdbRaw; + RdbHandlersStructCallbacks rdbStruct; + RdbHandlersDataCallbacks rdbData; + } h; +}; + +/*** inline functions ***/ + +/* before each handler's callback with RdbBulk, need to register its corresponding + * BulkInfo (See comment at struct AppCallbackCtx) */ +inline void registerAppBulkForNextCb(RdbParser *p, BulkInfo *binfo) { + p->appCbCtx.bulks[p->appCbCtx.numBulks++] = binfo; +} + +extern void serPoolFlush(RdbParser *p); /* avoid cyclic headers inclusion */ + +inline RdbStatus updateElementState(RdbParser *p, int newState) { + serPoolFlush(p); + p->elmCtx.state = newState; + return RDB_STATUS_OK; +} + +inline RdbStatus nextParsingElementState(RdbParser *p, ParsingElementType next, int st) { + serPoolFlush(p); + p->elmCtx.state = st; + p->parsingElement = next; + return RDB_STATUS_OK; +} + +inline RdbStatus nextParsingElement(RdbParser *p, ParsingElementType next) { + serPoolFlush(p); + p->elmCtx.state = 0; + p->parsingElement = next; + return RDB_STATUS_OK; +} + +RdbStatus allocFromCache(RdbParser *p, size_t len, AllocTypeRq type, char *refBuf, BulkInfo **binfo); + +/*** sub-element parsing ***/ +RdbStatus subElementCall(RdbParser *p, ParsingElementType next, int returnState); +RdbStatus subElementReturn(RdbParser *p, BulkInfo *bulkResult); +void subElementCallEnd(RdbParser *p, RdbBulk *bulkResult, size_t *len); + +/*** Loaders from RDB ***/ +RdbStatus rdbLoadLen(RdbParser *p, int *isencoded, uint64_t *lenptr, unsigned char* outbuff, int *outbufflen); +RdbStatus rdbLoadInteger(RdbParser *p, int enctype, AllocTypeRq type, char *refBuf, BulkInfo **out); +RdbStatus rdbLoadString(RdbParser *p, AllocTypeRq type, char *refBuf, BulkInfo **out); +RdbStatus rdbLoadLzfString(RdbParser *p, AllocTypeRq type, char *refBuf, BulkInfo **out); +inline RdbStatus rdbLoad(RdbParser *p, size_t len, AllocTypeRq type, char *refBuf, BulkInfo **out) { + return p->readRdbFunc(p, len, type, refBuf, out); +} + +/*** raw data parsing ***/ +void parserRawInit(RdbParser *p); +void parserRawRelease(RdbParser *p); + +/*** Common Parsing Elements ***/ +RdbStatus elementNewKey(RdbParser *p); +RdbStatus elementEndKey(RdbParser *p); +RdbStatus elementEndOfFile(RdbParser *p); +RdbStatus elementRdbHeader(RdbParser *p); +RdbStatus elementNextRdbType(RdbParser *p); +RdbStatus elementAuxField(RdbParser *p); +RdbStatus elementSelectDb(RdbParser *p); +RdbStatus elementResizeDb(RdbParser *p); +RdbStatus elementExpireTime(RdbParser *p); +RdbStatus elementExpireTimeMsec(RdbParser *p); +/*** Struct/Data Parsing Elements ***/ +RdbStatus elementString(RdbParser *p); +RdbStatus elementList(RdbParser *p); +/*** Raw Parsing Elements ***/ +RdbStatus elementRawNewKey(RdbParser *p); +RdbStatus elementRawEndKey(RdbParser *p); +RdbStatus elementRawList(RdbParser *p); +RdbStatus elementRawString(RdbParser *p); + +#endif /*LIBRDB_PARSER_H*/ \ No newline at end of file diff --git a/src/parserRaw.c b/src/parserRaw.c new file mode 100644 index 0000000..a1aa555 --- /dev/null +++ b/src/parserRaw.c @@ -0,0 +1,471 @@ +#include +#include +#include "deps/redis/lzf.h" +/*#include "deps/redis/crc64.h"*/ +#include "bulkAlloc.h" +#include "parser.h" +#include "defines.h" +#include "deps/redis/endianconv.h" +#include "utils.h" +#include "deps/redis/listpack.h" + +#define MAX_STRING_WRITE_CHUNK (1024*63) +#define DATA_SIZE_UNKNOWN_AHEAD 0 +#define RAW_AGG_MAX_NUM_BULKS 96 +#define RAW_AGG_FIRST_EXTERN_BUFF_LEN (1024-1) /* minus one for additional '\0' */ + +struct ParsingElementInfo peiRaw[PE_MAX] = { + /* reuse of default elements */ + [PE_RDB_HEADER] = {elementRdbHeader, "elementRdbHeader", "Start parsing RDB header"}, + [PE_NEXT_RDB_TYPE] = {elementNextRdbType, "elementNextRdbType", "Parsing next RDB type"}, + [PE_AUX_FIELD] = {elementAuxField, "elementAuxField", "Parsing auxiliary field" }, + [PE_SELECT_DB] = {elementSelectDb, "elementSelectDb", "Parsing select-db"}, + [PE_RESIZE_DB] = {elementResizeDb, "elementResizeDb", "Parsing resize-db"}, + [PE_EXPIRETIME] = {elementExpireTime, "elementExpireTime", "Parsing expire-time"}, + [PE_EXPIRETIMEMSEC] = {elementExpireTimeMsec, "elementExpireTimeMsec", "Parsing expire-time-msec"}, + [PE_END_OF_FILE] = {elementEndOfFile, "elementEndOfFile", "End parsing RDB file"}, + + /* special raw elements parsing */ + [PE_NEW_KEY] = {elementRawNewKey, "elementRawNewKey", "Parsing new raw key-value"}, + [PE_END_KEY] = {elementRawEndKey, "elementRawEndKey", "Parsing raw end key"}, + [PE_STRING] = {elementRawString, "elementRawString", "Parsing raw string"}, + [PE_LIST] = {elementRawList, "elementRawList", "Parsing raw list"}, +}; + +static inline RdbStatus cbHandleBegin(RdbParser *p, size_t size); +static inline RdbStatus cbHandleFrag(RdbParser *p, BulkInfo *binfo); +static inline RdbStatus cbHandleEnd(RdbParser *p); + +/* Aggregator of bulks for raw data until read entire key */ +static inline void aggFlushBulks(RdbParser *p); +static inline void aggAllocFirstBulk(RdbParser *p); +static RdbStatus aggMakeRoom(RdbParser *p, size_t numBytesRq); +static RdbStatus aggUpdateWrittenCbFrag(RdbParser *p, size_t bytesWritten); + +/*** init & release ***/ + +void parserRawInit(RdbParser *p) { + + RawContext *ctx = &p->rawCtx; + ctx->bulkArray = (BulkInfo *) RDB_alloc(p, RAW_AGG_MAX_NUM_BULKS * sizeof(struct BulkInfo)); +} + +void parserRawRelease(RdbParser *p) { + RawContext *ctx = &p->rawCtx; + + if (ctx->bulkArray) { + for (int i = 0; i <= ctx->curBulkIndex ; ++i) + freeUnmanagedBulk(p, ctx->bulkArray + i); + RDB_free(p, ctx->bulkArray); + } +} + +/*** Parsing Elements ***/ + +RdbStatus elementRawNewKey(RdbParser *p) { + + /* call base implementation of new-key handling. Read key. */ + IF_NOT_OK_RETURN(elementNewKey(p)); + + /*** ENTER SAFE STATE ***/ + + p->rawCtx.aggType = AGG_TYPE_UNINIT; + + aggAllocFirstBulk(p); + + /* write type of 1 byte. No need to call aggMakeRoom(). First bulk is empty. */ + p->rawCtx.at[0] = p->currOpcode; + + return aggUpdateWrittenCbFrag(p, 1); +} + +RdbStatus elementRawEndKey(RdbParser *p) { + /*** ENTER SAFE STATE (no rdb read) ***/ + + RawContext *ctx = &p->rawCtx; + + /* if aggregated entire type then only now parser knows to report totalSize */ + if (ctx->aggType == AGG_TYPE_ENTIRE_DATA) { + CALL_HANDLERS_CB(p, NOP, RDB_LEVEL_RAW, rdbRaw.handleBegin, ctx->totalSize); + } + + /* report leftover to cb handlers */ + for(int j = 0 ; j <= ctx->curBulkIndex ; ++j) + cbHandleFrag(p, ctx->bulkArray + j); + + aggFlushBulks(p); + + IF_NOT_OK_RETURN(cbHandleEnd(p)); + + /* now call base implementation of end-key handling */ + return elementEndKey(p); +} + +RdbStatus elementRawList(RdbParser *p) { + + enum RAW_LIST_STATES { + ST_RAW_LIST_HEADER=0, /* Retrieve number of nodes */ + ST_RAW_LIST_NEXT_NODE_CALL_STR, /* Process next node. Call PE_STRING as sub-element */ + ST_RAW_LIST_NEXT_NODE_STR_RETURN, /* integ check of the returned string from PE_STRING */ + } ; + + ElementRawListCtx *listCtx = &p->elmCtx.rawList; + RawContext *rawCtx = &p->rawCtx; + + switch (p->elmCtx.state) { + + case ST_RAW_LIST_HEADER: { + int headerLen = 0; + + aggMakeRoom(p, 10); /* worse case 9 bytes for len */ + + IF_NOT_OK_RETURN(rdbLoadLen(p, NULL, &listCtx->numNodes, + (unsigned char *) rawCtx->at, &headerLen)); + + /*** ENTER SAFE STATE ***/ + + IF_NOT_OK_RETURN(cbHandleBegin(p, DATA_SIZE_UNKNOWN_AHEAD)); + + IF_NOT_OK_RETURN(aggUpdateWrittenCbFrag(p, headerLen)); + + } + + updateElementState(p, ST_RAW_LIST_NEXT_NODE_CALL_STR); /* fall-thru */ + + case ST_RAW_LIST_NEXT_NODE_CALL_STR: { + listCtx->container = QUICKLIST_NODE_CONTAINER_PACKED; + + if (listCtx->numNodes == 0) + return nextParsingElement(p, PE_END_KEY); /* done */ + + if (p->currOpcode == RDB_TYPE_LIST_QUICKLIST_2) { + int headerLen = 0; + aggMakeRoom(p, 10); /* 9 bytes for len */ + IF_NOT_OK_RETURN(rdbLoadLen(p, NULL, &listCtx->container, + (unsigned char *) rawCtx->at, &headerLen)); + + /*** ENTER SAFE STATE ***/ + + IF_NOT_OK_RETURN(aggUpdateWrittenCbFrag(p, headerLen)); + } + + /* call raw string as subelement */ + return subElementCall(p, PE_STRING, ST_RAW_LIST_NEXT_NODE_STR_RETURN); + } + + case ST_RAW_LIST_NEXT_NODE_STR_RETURN: { + + /*** ENTER SAFE STATE (no rdb read)***/ + + int ret; + size_t len; + unsigned char *encodedNode; + + /* return from sub-element string parsing */ + subElementCallEnd(p, (char **) &encodedNode, &len); + + if (listCtx->container != QUICKLIST_NODE_CONTAINER_PLAIN) { + if (p->currOpcode == RDB_TYPE_LIST_QUICKLIST_2) + ret = lpValidateIntegrity(encodedNode, len, p->deepIntegCheck, + NULL, NULL); + else + ret = 0; + /* TODO: ret = ziplistValidateIntegrity((unsigned char *) encoded, sdslen(encoded), p->deepIntegCheck, NULL, NULL); */ + + if (!ret) { + RDB_reportError(p, RDB_ERR_QUICK_LIST_INTEG_CHECK, + "elementRawList(1): Quicklist integrity check failed"); + return RDB_STATUS_ERROR; + } + } + + if (--listCtx->numNodes == 0) + return nextParsingElement(p, PE_END_KEY); /* done */ + + return updateElementState(p, ST_RAW_LIST_NEXT_NODE_CALL_STR); + } + + default: + RDB_reportError(p, RDB_ERR_QUICK_LIST_INVALID_STATE, + "elementRawList() : invalid parsing element state"); + return RDB_STATUS_ERROR; + } +} + +/* either element or sub-element */ +RdbStatus elementRawString(RdbParser *p) { + + enum RAW_STRING_STATES { + ST_RAW_STRING_PASS_HEADER, + ST_RAW_STRING_PASS_CHUNKS, /* no need to aggregate entire type. Stream it! */ + ST_RAW_STRING_PASS_AND_REPLY_CALLER, /* called on behalf another flow */ + } ; + + ElementRawStringCtx *strCtx = &p->elmCtx.rawString; + RawContext *rawCtx = &p->rawCtx; + + switch (p->elmCtx.state) { + + case ST_RAW_STRING_PASS_HEADER: { + int headerlen = 0; + + aggMakeRoom(p, 16 * 3); /* worse case, 1 byte type + (9 bytes for len) * 3 */ + + IF_NOT_OK_RETURN(rdbLoadLen(p, &strCtx->isencoded, &strCtx->len, + (unsigned char *) rawCtx->at + headerlen, &headerlen)); + + if (strCtx->isencoded) { + strCtx->encoding = strCtx->len; + switch(strCtx->encoding) { + case RDB_ENC_INT8: strCtx->len = 1; break; + case RDB_ENC_INT16: strCtx->len = 2; break; + case RDB_ENC_INT32: strCtx->len = 4; break; + case RDB_ENC_LZF: + IF_NOT_OK_RETURN(rdbLoadLen(p, &strCtx->isencoded, &strCtx->len, + (unsigned char *) rawCtx->at + headerlen, &headerlen)); + IF_NOT_OK_RETURN(rdbLoadLen(p, &strCtx->isencoded, &strCtx->uclen, + (unsigned char *) rawCtx->at + headerlen, &headerlen)); + break; + default: + RDB_reportError(p, RDB_ERR_STRING_UNKNOWN_ENCODING_TYPE, + "elementRawString(): Unknown RDB string encoding type: %llu", strCtx->len); + return RDB_STATUS_ERROR; + } + } + + /*** ENTER SAFE STATE ***/ + + IF_NOT_OK_RETURN(cbHandleBegin(p, 1 + headerlen + strCtx->len)); /* type + hdr + string */ + + IF_NOT_OK_RETURN(aggUpdateWrittenCbFrag(p, headerlen)); + + if (p->callSubElm.callerElm != PE_MAX) + return updateElementState(p, ST_RAW_STRING_PASS_AND_REPLY_CALLER); + } + + updateElementState(p, ST_RAW_STRING_PASS_CHUNKS); /* fall-thru */ + + case ST_RAW_STRING_PASS_CHUNKS: { + + while(1) { + BulkInfo *binfo; + + size_t bulkLen = strCtx->len > MAX_STRING_WRITE_CHUNK ? MAX_STRING_WRITE_CHUNK : strCtx->len; + + /* Load ctx->bulkArray with data. Assist BULK_TYPE_REF to be + * resilient from wait-more-data (rollback) flow */ + + + /* Populate ctx->bulkArray with data and ensure that flow can handle + * wait-more-data (rollback) in the middle by assisting BULK_TYPE_REF */ + IF_NOT_OK_RETURN(aggMakeRoom(p, bulkLen)); + IF_NOT_OK_RETURN(rdbLoad(p, bulkLen, RQ_ALLOC_REF, rawCtx->at, &binfo)); + + /*** ENTER SAFE STATE ***/ + + /* now safe to update ctx and be ready for another iteration */ + IF_NOT_OK_RETURN(aggUpdateWrittenCbFrag(p, bulkLen)); + + /* update context for next iteration */ + strCtx->len -= bulkLen; + + if (!(strCtx->len)) /* stop condition */ + return nextParsingElement(p, PE_END_KEY); + + updateElementState(p, ST_RAW_STRING_PASS_CHUNKS); + } + } + + case ST_RAW_STRING_PASS_AND_REPLY_CALLER: { + BulkInfo *binfoEnc; + BulkInfo binfoDec; + + /* Populate ctx->bulkArray with data and ensure that flow can handle + * wait-more-data (rollback) in the middle by assisting BULK_TYPE_REF */ + IF_NOT_OK_RETURN(aggMakeRoom(p, strCtx->len)); + IF_NOT_OK_RETURN(rdbLoad(p, strCtx->len, RQ_ALLOC_REF, rawCtx->at, &binfoEnc)); + char *encoded = rawCtx->at; + + /*** ENTER SAFE STATE ***/ + + IF_NOT_OK_RETURN(aggUpdateWrittenCbFrag(p, strCtx->len)); + + if (!(strCtx->isencoded)) { + return subElementReturn(p, binfoEnc /* no decoding required */); + } + + if (strCtx->encoding <= RDB_ENC_INT32) { + char buf[LONG_STR_SIZE]; + long val = 0; + if (strCtx->encoding == RDB_ENC_INT8) + val = (int8_t) (encoded[0]); + else if (strCtx->encoding == RDB_ENC_INT16) + val = (int16_t) (encoded[0] | (encoded[1] << 8)); + else if (strCtx->encoding == RDB_ENC_INT32) + val = (int32_t) (encoded[0] | (encoded[1] << 8) | (encoded[2] << 16) | (encoded[3] << 24)); + + int strLen = ll2string(buf, sizeof(buf), val); + + allocUnmanagedBulk(p, strLen, UNMNG_RQ_ALLOC, NULL, &binfoDec); + memcpy(binfoDec.ref, buf, strLen); + return subElementReturn(p, &binfoDec); + } + + if (strCtx->encoding == RDB_ENC_LZF) { + allocUnmanagedBulk(p, strCtx->uclen, UNMNG_RQ_ALLOC, NULL, &binfoDec); + + if (lzf_decompress(binfoEnc->ref, strCtx->len, binfoDec.ref, strCtx->uclen) != strCtx->uclen) { + RDB_reportError(p, RDB_ERR_STRING_INVALID_LZF_COMPRESSED, + "elementRawString(): Invalid LZF compressed string"); + return RDB_STATUS_ERROR; + } + return subElementReturn(p, &binfoDec); + } + + RDB_reportError(p, RDB_ERR_STRING_UNKNOWN_ENCODING_TYPE, + "elementRawString(): Unknown RDB string encoding type: %llu", strCtx->encoding); + return RDB_STATUS_ERROR; + } + + default: { + RDB_reportError(p, RDB_ERR_STRING_INVALID_STATE, + "elementRawString() : invalid parsing element state"); + return RDB_STATUS_ERROR; + } + } +} + +/*** various functions ***/ + +static inline RdbStatus cbHandleFrag(RdbParser *p, BulkInfo *binfo) { + + if (likely(binfo->written)) { + /* Update current buffer len to be actual usage */ + binfo->len = binfo->written; + ((char *)binfo->ref)[binfo->written] = '\0'; + + registerAppBulkForNextCb(p, binfo); + CALL_HANDLERS_CB(p, NOP, RDB_LEVEL_RAW, rdbRaw.handleFrag, binfo->ref); + } + + return RDB_STATUS_OK; +} + +static inline RdbStatus cbHandleBegin(RdbParser *p, size_t size) { + + /* if aggType already initialized, then it is sub-element call. Ignore. */ + if (p->rawCtx.aggType != AGG_TYPE_UNINIT) + return RDB_STATUS_OK; + + if (size == DATA_SIZE_UNKNOWN_AHEAD) { + p->rawCtx.aggType = AGG_TYPE_ENTIRE_DATA; + + /* TODO: add configuration to avoid aggregation, in case app doens't need + * to know ahead the size of payload, for example, when it only stores + * the data to a file */ + } else { + /* we know the total size of type. No need to aggregate it entirely */ + p->rawCtx.aggType = AGG_TYPE_PARTIALLY; + CALL_HANDLERS_CB(p, NOP, RDB_LEVEL_RAW, rdbRaw.handleBegin, size); + } + return RDB_STATUS_OK; +} + +static inline RdbStatus cbHandleEnd(RdbParser *p) { + CALL_HANDLERS_CB(p, NOP, RDB_LEVEL_RAW, rdbRaw.handleEnd); + return RDB_STATUS_OK; +} + +/*** raw aggregator of data ***/ + +static RdbStatus aggUpdateWrittenCbFrag(RdbParser *p, size_t bytesWritten) { + RawContext *ctx = &p->rawCtx; + + p->rawCtx.at += bytesWritten; + p->rawCtx.totalSize += bytesWritten; + ctx->bulkArray[ctx->curBulkIndex].written += bytesWritten; + + /* if filled-up at least first buffer, only then it worth to forward */ + if (unlikely(ctx->curBulkIndex > 0)) { + /* If not required to agg entire type data then pass what aggregated so far */ + if (ctx->aggType == AGG_TYPE_PARTIALLY) { + for (int i = 0 ; i <= ctx->curBulkIndex ; ++i) { + IF_NOT_OK_RETURN(cbHandleFrag(p, ctx->bulkArray + i)); + } + + aggFlushBulks(p); + aggAllocFirstBulk(p); + } + } + return RDB_STATUS_OK; +} + +static RdbStatus aggMakeRoom(RdbParser *p, size_t numBytesRq) { + RawContext *ctx = &p->rawCtx; + BulkInfo *currBuff = ctx->bulkArray + ctx->curBulkIndex; + size_t freeRoomLeft = currBuff->len - currBuff->written; + + /* fill-up current buffer before attempting to allocate new one */ + if (likely(freeRoomLeft >= numBytesRq)) + return RDB_STATUS_OK; + + if (unlikely(p->maxRawLen < ctx->totalSize + numBytesRq)) { + RDB_reportError(p, RDB_ERR_MAX_RAW_LEN_EXCEEDED_FOR_KEY, "Maximum raw length exceeded for key (len=%lu)", + ctx->totalSize + numBytesRq); + return RDB_STATUS_ERROR; + } + + /* determine next buffer size to allocate. Factor x2 up-to 1mb, x1.5 upto + * 256mb, or x1.2 above it. With 96 entries for bulkArray, it is sufficient + * for at least 100TB */ + size_t len = (currBuff->len > numBytesRq) ? currBuff->len : numBytesRq; + float factor = likely(len < (1<<20)) ? 2 : (len < (1<<28)) ? 1.5 : 1.2; + size_t nextBufSize = (size_t) len * factor; + + ++(ctx->curBulkIndex); + ++currBuff; + + allocUnmanagedBulk(p, nextBufSize, UNMNG_RQ_ALLOC_APP_BULK, NULL, currBuff); + ctx->at = ctx->bulkArray[ctx->curBulkIndex].ref; + return RDB_STATUS_OK; +} + +static inline void aggFlushBulks(RdbParser *p) { + RawContext *ctx = &p->rawCtx; + + /* skip first static buffer */ + for (int i = 0; i <= ctx->curBulkIndex ; ++i) + freeUnmanagedBulk(p, ctx->bulkArray + i); +} + +static inline void aggAllocFirstBulk(RdbParser *p) { + RawContext *ctx = &p->rawCtx; + + /* Allocate first bulk in bulkArray */ + if (p->mem.bulkAllocType == RDB_BULK_ALLOC_EXTERN) { + /* If app configured explicitly to allocate RdbBulks by external allocation + * function then it will be a waste to use "internal buffer" and then copy + * it to "external buffer" (in order to pass RdbBulk to callbacks). Better + * to allocate from start "external buffer". */ + allocUnmanagedBulk(p, + RAW_AGG_FIRST_EXTERN_BUFF_LEN, + UNMNG_RQ_ALLOC_APP_BULK, + NULL, + ctx->bulkArray); + } else { + /* If not configured explicitly to allocate RdbBulks by external allocation, + * then only reference staticBulk for the first buffer. Will be bigger + * buffer than the case that RDB_BULK_ALLOC_EXTERN is configured, because + * the buffer is static and not really allocated each time. '-1' is because + * the '\0' termination which is not counted. */ + allocUnmanagedBulk(p, + RAW_AGG_FIRST_STATIC_BUFF_LEN - 1, + UNMNG_RQ_ALLOC_APP_BULK_REF, + ctx->staticBulk, + ctx->bulkArray); + } + + ctx->at = ctx->bulkArray[0].ref; + ctx->curBulkIndex = 0; + ctx->totalSize = 0; +} \ No newline at end of file diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..c85a08c --- /dev/null +++ b/src/utils.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include "utils.h" + +/* Return the number of digits of 'v' when converted to string in radix 10. + * See ll2string() for more information. */ +uint32_t digits10(uint64_t v) { + if (v < 10) return 1; + if (v < 100) return 2; + if (v < 1000) return 3; + if (v < 1000000000000UL) { + if (v < 100000000UL) { + if (v < 1000000) { + if (v < 10000) return 4; + return 5 + (v >= 100000); + } + return 7 + (v >= 10000000UL); + } + if (v < 10000000000UL) { + return 9 + (v >= 1000000000UL); + } + return 11 + (v >= 100000000000UL); + } + return 12 + digits10(v / 1000000000000UL); +} + +/* Convert a unsigned long long into a string. Returns the number of + * characters needed to represent the number. + * If the buffer is not big enough to store the string, 0 is returned. + * + * Based on the following article (that apparently does not provide a + * novel approach but only publicizes an already used technique): + * + * https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920 */ +int ull2string(char *dst, size_t dstlen, unsigned long long value) { + static const char digits[201] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + + /* Check length. */ + uint32_t length = digits10(value); + if (length >= dstlen) goto err;; + + /* Null term. */ + uint32_t next = length - 1; + dst[next + 1] = '\0'; + while (value >= 100) { + int const i = (value % 100) * 2; + value /= 100; + dst[next] = digits[i + 1]; + dst[next - 1] = digits[i]; + next -= 2; + } + + /* Handle last 1-2 digits. */ + if (value < 10) { + dst[next] = '0' + (uint32_t) value; + } else { + int i = (uint32_t) value * 2; + dst[next] = digits[i + 1]; + dst[next - 1] = digits[i]; + } + return length; + err: + /* force add Null termination */ + if (dstlen > 0) + dst[0] = '\0'; + return 0; +} + +/* Convert a long long into a string. Returns the number of + * characters needed to represent the number. + * If the buffer is not big enough to store the string, 0 is returned. */ +int ll2string(char *dst, size_t dstlen, long long svalue) { + unsigned long long value; + int negative = 0; + + /* The ull2string function with 64bit unsigned integers for simplicity, so + * we convert the number here and remember if it is negative. */ + if (svalue < 0) { + if (svalue != LLONG_MIN) { + value = -svalue; + } else { + value = ((unsigned long long) LLONG_MAX)+1; + } + if (dstlen < 2) + goto err; + negative = 1; + dst[0] = '-'; + dst++; + dstlen--; + } else { + value = svalue; + } + + /* Converts the unsigned long long value to string*/ + int length = ull2string(dst, dstlen, value); + if (length == 0) return 0; + return length + negative; + + err: + /* force add Null termination */ + if (dstlen > 0) + dst[0] = '\0'; + return 0; +} + +unsigned int getEnvVar(const char* varName, unsigned int defaultVal) { + const char* envValueStr = getenv(varName); + if (envValueStr == NULL) { + return defaultVal; + } + + errno = 0; + char* endptr = NULL; + unsigned long longValue = strtoul(envValueStr, &endptr, 10); + if (errno != 0 || endptr == envValueStr || *endptr != '\0' || longValue > UINT_MAX) { + errno = 0; + return defaultVal; + } + + return (unsigned int) longValue; +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..b19fed5 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,22 @@ +#ifndef LIBRDB_UTILS_H +#define LIBRDB_UTILS_H + +#ifdef __GNUC__ +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#endif + +/* Bytes needed for long -> str + '\0' */ +#define LONG_STR_SIZE 21 + +int ll2string(char *s, size_t len, long long value); +int ull2string(char *s, size_t len, unsigned long long value); + +unsigned int getEnvVar(const char* varName, unsigned int defaultVal); + + + +#endif /*LIBRDB_UTILS_H*/ diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..0bc2439 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,44 @@ +default: all + +LIB_NAME = rdb +LIB_DIR = ../lib +LIB_NAME_EXT = $(LIB_NAME)-ext + +# Artifacts: +TARGET_TEST = test_lib +TARGET_TEST_STATIC = test_static_lib + +######################################################################################### +SOURCES = $(notdir $(basename $(wildcard *.c))) +OBJECTS = $(patsubst %,%.o,$(SOURCES)) + +CC = gcc +STD = -std=gnu99 +STACK = -fstack-protector-all -Wstack-protector +WARNS = -Wall -Wextra -pedantic -Werror -Wno-unused-function + +CFLAGS = -fPIC -O3 $(STD) $(STACK) $(WARNS) +DEBUG = -g3 -DDEBUG=1 +LIBS = -l cmocka -L /usr/lib -L $(LIB_DIR) -l $(LIB_NAME) -l $(LIB_NAME_EXT) +LIBS_STATIC = -l cmocka -L /usr/lib -L $(LIB_DIR) -l:lib$(LIB_NAME_EXT).a + +######################################### RULES ####################################### +all: $(TARGET_TEST) $(TARGET_TEST_STATIC) + @echo "Done."; + +$(TARGET_TEST): $(OBJECTS) + $(CC) $(OBJECTS) -o $@ $(DEBUG) $(CFLAGS) $(LIBS) + +$(TARGET_TEST_STATIC): $(OBJECTS) + $(CC) $(OBJECTS) -o $@ $(DEBUG) $(CFLAGS) $(LIBS) $(LIBS_STATIC) + +-include $(OBJECTS:.o=.d) + +%.o: %.c + $(CC) -c $*.c -o $*.o $(DEBUG) $(CFLAGS) + $(CC) -MM $(CFLAGS) $*.c > $*.d + +clean: + @rm -rvf $(TARGET_TEST) $(TARGET_TEST_STATIC) ./*.o ./*.d ./log/*.log; + +.PHONY: all clean \ No newline at end of file diff --git a/test/dumps/multiple_dbs.rdb b/test/dumps/multiple_dbs.rdb new file mode 100644 index 0000000..28160e8 Binary files /dev/null and b/test/dumps/multiple_dbs.rdb differ diff --git a/test/dumps/multiple_lists_strings.rdb b/test/dumps/multiple_lists_strings.rdb new file mode 100644 index 0000000..52dd0d0 Binary files /dev/null and b/test/dumps/multiple_lists_strings.rdb differ diff --git a/test/dumps/single_key.rdb b/test/dumps/single_key.rdb new file mode 100644 index 0000000..8c5870a Binary files /dev/null and b/test/dumps/single_key.rdb differ diff --git a/test/dumps/single_list.rdb b/test/dumps/single_list.rdb new file mode 100644 index 0000000..ae0b647 Binary files /dev/null and b/test/dumps/single_list.rdb differ diff --git a/test/dumps/string_lzf.rdb b/test/dumps/string_lzf.rdb new file mode 100644 index 0000000..82c868d Binary files /dev/null and b/test/dumps/string_lzf.rdb differ diff --git a/test/log/.gitkeep b/test/log/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/test_bulk_ops.c b/test/test_bulk_ops.c new file mode 100644 index 0000000..1d43ff5 --- /dev/null +++ b/test/test_bulk_ops.c @@ -0,0 +1,183 @@ +#include +#include "test_common.h" + +RdbMemAlloc mem = {xmalloc, xrealloc, xfree, + RDB_BULK_ALLOC_MAX, /* << change each iteration */ + { xmalloc, xclone, xfree } +}; + +void loggerCb(RdbLogLevel l, const char *msg) { + UNUSED(l, msg); + /* mask simulated errors */ +} + +void reportErrorCb(RdbRes errorID, const char *errorMsg) { + UNUSED(errorID, errorMsg); +} + +void testBulkOps(RdbParser *p, RdbBulk b) { + + /*** test clone, and free ***/ + + RdbBulkCopy bcopy = RDB_bulkClone(p, b); + + if (RDB_isRefBulk(p, b)) { + assert_ptr_not_equal(b, bcopy); + assert_int_not_equal(mem.bulkAllocType, RDB_BULK_ALLOC_EXTERN); + } else if (mem.bulkAllocType == RDB_BULK_ALLOC_HEAP) + assert_ptr_equal(b, bcopy); /* heap clone uses refcount */ + else if (mem.bulkAllocType == RDB_BULK_ALLOC_EXTERN) + assert_ptr_not_equal(b, bcopy); /* xclone imp creates a new copy */ + + assert_string_equal(b, bcopy); + RDB_bulkFree(p, bcopy); + + /* Try clone non exist bulk */ + RdbBulk nonExistBulk = "NON EXIST BULK"; + assert_null(RDB_bulkClone(p, nonExistBulk)); + RdbRes err = RDB_getErrorCode(p); + assert_int_equal(err, RDB_ERR_INVALID_BULK_CLONE_REQUEST); + + /*** test bulk len ***/ + + assert_true(0 != RDB_bulkLen(p, b)); + assert_memory_equal(b, b, RDB_bulkLen(p, b)); +} + +RdbRes handle_aux_field(RdbParser *p, void *userData, RdbBulk auxkey, RdbBulk auxval) { + UNUSED(userData); + testBulkOps(p, auxkey); + testBulkOps(p, auxval); + return RDB_OK; +} + +RdbRes handle_new_key(RdbParser *p, void *userData, RdbBulk key, RdbKeyInfo *info) { + UNUSED(userData, info); + testBulkOps(p, key); + return RDB_OK; +} + +RdbRes handle_raw_begin(RdbParser *p, void *userData, size_t size) { + UNUSED(p); + /* init rawObjSizeLeft with declared size. Verified by handle_raw_end */ + size_t *rawObjSizeLeft = (size_t *)userData; + *rawObjSizeLeft = size; + return RDB_OK; +} + +RdbRes handle_raw_frag(RdbParser *p, void *userData, RdbBulk frag) { + size_t *rawObjSizeLeft = (size_t *)userData; + + *rawObjSizeLeft -= RDB_bulkLen(p, frag); + testBulkOps(p, frag); + return RDB_OK; +} + +RdbRes handle_raw_end(RdbParser *p, void *userData) { + UNUSED(p); + size_t *rawObjSizeLeft = (size_t *)userData; + + /* Verify reported size by handle_raw_begin == total received bulks */ + assert_int_equal(*rawObjSizeLeft, 0); + return RDB_OK; +} + +RdbRes handle_string_value(RdbParser *p, void *userData, RdbBulk str) { + UNUSED(userData); + testBulkOps(p, str); + return RDB_OK; +} + +RdbRes handle_list_element(RdbParser *p, void *userData, RdbBulk b) { + UNUSED(userData); + testBulkOps(p, b); + return RDB_OK; +} + +static void test_raw_handlers_callbacks_bulk_ops (void **state) { + UNUSED(state); + RdbStatus status; + size_t rawObjSizeLeft; + void *user_data =&rawObjSizeLeft; + + for (mem.bulkAllocType = 0 ; mem.bulkAllocType < RDB_BULK_ALLOC_MAX ; ++mem.bulkAllocType) { + + RdbHandlersRawCallbacks callbacks = { + .handleAuxField = handle_aux_field, + .handleNewKey = handle_new_key, + .handleBegin = handle_raw_begin, + .handleFrag = handle_raw_frag, + .handleEnd = handle_raw_end, + }; + + RdbParser *parser = RDB_createParserRdb(&mem); + RDB_setLogger(parser, loggerCb); + assert_non_null(RDBX_createReaderFile(parser, PATH_DUMP_FOLDER("multiple_lists_strings.rdb"))); + assert_non_null(RDB_createHandlersRaw(parser, &callbacks, user_data, NULL)); + while ((status = RDB_parse(parser)) == RDB_STATUS_WAIT_MORE_DATA); + assert_int_equal( status, RDB_STATUS_OK); + RDB_deleteParser(parser); + } +} + +static void test_struct_handlers_callbacks_bulk_ops (void **state) { + UNUSED(state); + RdbStatus status; + size_t rawObjSizeLeft; + void *user_data =&rawObjSizeLeft; + + for (mem.bulkAllocType = 0 ; mem.bulkAllocType < RDB_BULK_ALLOC_MAX ; ++mem.bulkAllocType) { + + RdbHandlersStructCallbacks callbacks = { + .handleAuxField = handle_aux_field, + .handleNewKey = handle_new_key, + .handleStringValue = handle_string_value, + .handlerPlainNode = handle_list_element, + .handlerQListNode = handle_list_element, + + }; + + RdbParser *parser = RDB_createParserRdb(&mem); + RDB_setLogger(parser, loggerCb); + assert_non_null(RDBX_createReaderFile(parser, PATH_DUMP_FOLDER("multiple_lists_strings.rdb"))); + assert_non_null(RDB_createHandlersStruct(parser, &callbacks, user_data, NULL)); + while ((status = RDB_parse(parser)) == RDB_STATUS_WAIT_MORE_DATA); + assert_int_equal( status, RDB_STATUS_OK); + RDB_deleteParser(parser); + } +} + +static void test_data_handlers_callbacks_bulk_ops (void **state) { + UNUSED(state); + RdbStatus status; + size_t rawObjSizeLeft; + void *user_data =&rawObjSizeLeft; + + for (mem.bulkAllocType = 0 ; mem.bulkAllocType < RDB_BULK_ALLOC_MAX ; ++mem.bulkAllocType) { + + RdbHandlersDataCallbacks callbacks = { + .handleAuxField = handle_aux_field, + .handleNewKey = handle_new_key, + .handleStringValue = handle_string_value, + .handleListElement = handle_list_element, + }; + + RdbParser *parser = RDB_createParserRdb(&mem); + RDB_setLogger(parser, loggerCb); + assert_non_null(RDBX_createReaderFile(parser, PATH_DUMP_FOLDER("multiple_lists_strings.rdb"))); + assert_non_null(RDB_createHandlersData(parser, &callbacks, user_data, NULL)); + while ((status = RDB_parse(parser)) == RDB_STATUS_WAIT_MORE_DATA); + assert_int_equal( status, RDB_STATUS_OK); + RDB_deleteParser(parser); + } +} + +/*************************** group_rdb_to_json *******************************/ +int group_bulk_ops(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_raw_handlers_callbacks_bulk_ops), + cmocka_unit_test(test_struct_handlers_callbacks_bulk_ops), + cmocka_unit_test(test_data_handlers_callbacks_bulk_ops), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/test_common.c b/test/test_common.c new file mode 100644 index 0000000..289d8b5 --- /dev/null +++ b/test/test_common.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include "test_common.h" + +char* sanitizeJson(char* str) { + int i, j; + int len = strlen(str); + char* output = str; + + for (i = 0, j = 0; i < len; i++) { + if ((str[i] != ' ')&&(str[i] != '\n')) { + output[j++] = str[i]; + } + } + output[j] = '\0'; + return output; +} + +void assert_json_file(const char *filename, char *expJson) { + FILE* fp; + char* str; + size_t size; + + assert_non_null(fp = fopen(filename, "r")); + + fseek(fp, 0L, SEEK_END); + size = ftell(fp); + fseek (fp, 0, SEEK_SET); + assert_non_null(str = (char*) malloc(size + 1)); + + size_t readBytes = fread(str, 1, size, fp); + assert_int_equal(readBytes, size); + + str[size] = '\0'; + fclose(fp); + + assert_string_equal( sanitizeJson(str) , sanitizeJson(expJson)); + free(str); +} + +void readFileToBuff(const char* filename, unsigned char** buffer, size_t* length) { + long file_size = 0; + FILE* file = fopen(filename, "rb"); + assert_non_null(file); + assert_int_equal(fseek(file, 0, SEEK_END), 0); + file_size = ftell(file); + assert_int_not_equal(file_size, -1); + assert_int_equal(fseek(file, 0, SEEK_SET), 0); + *buffer = (unsigned char*)malloc(file_size); + assert_int_equal(fread(*buffer, 1, file_size, file), file_size); + *length = file_size; + fclose(file); +} + +/* Test different use cases to convert given rdb file to json: + * 1. RDB_parse - parse with RDB reader + * 2. RDB_parse - set pause-interval to 1 byte + * 3. RDB_parseBuff - parse buffer. Use buffer of size 1 char + * 4. RDB_parseBuff - parse a single buffer. set pause-interval to 1 byte + * + * All those tests will be wrapped with a loop that will test it each time with a different + * bulk allocation type (bulkAllocType) this includes allocating from stack, heap, external, + * or optimized-external allocation mode. + */ +void testVariousCases(const char *rdbfile, + const char *jsonfile, + char *expJson, + RdbHandlersLevel parseLevel) +{ + + for (int type = 0 ; type <= RDB_BULK_ALLOC_MAX ; ++type) { + unsigned char *buffer; + size_t bufLen; + RdbStatus status; + RdbMemAlloc memAlloc = {xmalloc, xrealloc, xfree, type, {xmalloc, xclone, xfree}}; + RdbMemAlloc *pMemAlloc = (type != RDB_BULK_ALLOC_MAX) ? &memAlloc : NULL; + + /* read file to buffer for testing RDB_parseBuff() */ + readFileToBuff(rdbfile, &buffer, &bufLen); + + /*** 1. RDB_parse - parse with RDB reader ***/ + remove(jsonfile); + RdbParser *parser = RDB_createParserRdb(pMemAlloc); + RDB_setLogLevel(parser, RDB_LOG_ERROR); + assert_non_null(RDBX_createReaderFile(parser, rdbfile)); + assert_non_null(RDBX_createHandlersRdb2Json(parser, RDBX_CONV_JSON_ENC_PLAIN, jsonfile, parseLevel)); + while ((status = RDB_parse(parser)) == RDB_STATUS_WAIT_MORE_DATA); + assert_int_equal(status, RDB_STATUS_OK); + RDB_deleteParser(parser); + assert_json_file(jsonfile, expJson); + + /*** 2. RDB_parse - set pause-interval to 1 byte ***/ + int looseCounterAssert = 0; + long countPauses = 0; + size_t lastBytes = 0; + remove(jsonfile); + parser = RDB_createParserRdb(pMemAlloc); + RDB_setLogLevel(parser, RDB_LOG_ERROR); + assert_non_null(RDBX_createReaderFile(parser, rdbfile)); + assert_non_null(RDBX_createHandlersRdb2Json(parser, RDBX_CONV_JSON_ENC_PLAIN, jsonfile, parseLevel)); + RDB_setPauseInterval(parser, 1 /*bytes*/); + while (1) { + status = RDB_parse(parser); + if (status == RDB_STATUS_WAIT_MORE_DATA) { + looseCounterAssert = 1; + continue; + } + if (status == RDB_STATUS_PAUSED) { + ++countPauses; + continue; + } + assert_int_equal(status, RDB_STATUS_OK); + break; + } + /* If recorded WAIT_MORE_DATA, it will mess a little our countPauses evaluation. + * When parser reach WAIT_MORE_DATA together with STATUS_PAUSED, then it + * will prefer to return WAIT_MORE_DATA */ + if (looseCounterAssert) + assert_true(countPauses > (((long) bufLen) / 2)); + else + assert_int_equal(countPauses + 1, bufLen); + RDB_deleteParser(parser); + assert_json_file(jsonfile, expJson); + + /*** 3. RDB_parseBuff - parse buffer. Use buffer of size 1 char ***/ + remove(jsonfile); + parser = RDB_createParserRdb(pMemAlloc); + RDB_setLogLevel(parser, RDB_LOG_ERROR); + assert_non_null(RDBX_createHandlersRdb2Json(parser, RDBX_CONV_JSON_ENC_PLAIN, jsonfile, parseLevel)); + parseBuffOneCharEachTime(parser, buffer, bufLen, 1); + RDB_deleteParser(parser); + assert_json_file(jsonfile, expJson); + + /*** 4. RDB_parseBuff - parse a single buffer. set pause-interval to 1 byte ***/ + countPauses = 0; + remove(jsonfile); + parser = RDB_createParserRdb(pMemAlloc); + RDB_setLogLevel(parser, RDB_LOG_ERROR); + assert_non_null(RDBX_createHandlersRdb2Json(parser, RDBX_CONV_JSON_ENC_PLAIN, jsonfile, parseLevel)); + RDB_setPauseInterval(parser, 1 /*bytes*/); + while (1) { + status = RDB_parseBuff(parser, buffer, bufLen, 1); + assert_true (lastBytes < RDB_getBytesProcessed(parser)); + lastBytes = RDB_getBytesProcessed(parser); + if (status == RDB_STATUS_PAUSED) { + ++countPauses; + continue; + } + assert_int_equal(status, RDB_STATUS_OK); + break; + } + assert_int_equal(countPauses + 1, bufLen); + RDB_deleteParser(parser); + assert_json_file(jsonfile, expJson); + + free(buffer); + } +} + +void parseBuffOneCharEachTime(RdbParser *p, unsigned char *buff, size_t size, int isEOF) { + for (size_t i = 0 ; i < size-1 ; ++i) + assert_int_equal(RDB_parseBuff(p, buff + i, 1, 0), RDB_STATUS_WAIT_MORE_DATA); + + if (!isEOF) + assert_int_equal(RDB_parseBuff(p, buff + size - 1, 1, 1), RDB_STATUS_WAIT_MORE_DATA); + else + assert_int_equal(RDB_parseBuff(p, buff + size - 1, 1, 0), RDB_STATUS_OK); +} + +/*** simulate external malloc ***/ + +#define MAGIC_VALUE 0xDEADBEE + +void *xmalloc(size_t size) { + void *ptr = malloc(size + sizeof(int)); + assert_non_null(ptr); + *(int *)ptr = MAGIC_VALUE; + return (char *)ptr + sizeof(int); +} + +void *xclone(void *str, size_t len) { + void *ptr = xmalloc(len); + memcpy(ptr, str, len); + return ptr; +} + +void xfree(void *ptr) { + int *header = (int *)((char *)ptr - sizeof(int)); + assert_int_equal(*header, MAGIC_VALUE); + free(header); +} + +void *xrealloc(void *ptr, size_t size) { + if (ptr == NULL) + return xmalloc(size); + + int *header = (int *)((char *)ptr - sizeof(int)); + assert_int_equal(*header, MAGIC_VALUE); + + void *new_ptr = realloc(header, size + sizeof(int)); + assert_non_null(new_ptr); + *(int *)new_ptr = MAGIC_VALUE; + return (char *)new_ptr + sizeof(int); +} \ No newline at end of file diff --git a/test/test_common.h b/test/test_common.h new file mode 100644 index 0000000..3e60f72 --- /dev/null +++ b/test/test_common.h @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include + +#include "../api/librdb-api.h" /* RDB library header */ +#include "../api/librdb-ext-api.h" /* RDB library extension header */ + +#define UNUSED(...) unused( (void *) NULL, ##__VA_ARGS__); +inline void unused(void *dummy, ...) { (void)(dummy);} + +#define QUOTE(...) #__VA_ARGS__ + +#define PATH_DUMP_FOLDER(file) "./test/dumps/"file +#define PATH_TMP_FOLDER(file) "./test/tmp/"file + +void assert_json_file(const char *filename, char *expJson); +void parseBuffOneCharEachTime(RdbParser *p, unsigned char *buff, size_t size, int isEOF); +void readFileToBuff(const char* filename, unsigned char** buffer, size_t* length); + +void testVariousCases(const char *rdbfile, + const char *jsonfile, + char *expJson, + RdbHandlersLevel parseLevel); + +extern int group_main(void); +extern int group_rdb_to_json(void); +extern int group_mem_management(void); +extern int group_pause(void); +extern int group_bulk_ops(void); + +/* simulate external malloc */ +void *xmalloc(size_t size); +void *xclone(void *str, size_t len); +void xfree(void *ptr); +void *xrealloc(void *ptr, size_t size); diff --git a/test/test_main.c b/test/test_main.c new file mode 100644 index 0000000..5640b3c --- /dev/null +++ b/test/test_main.c @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include "test_common.h" + +static void test_createReader_missingFile(void **state) { + UNUSED(state); + + RdbParser *parser = RDB_createParserRdb(NULL); + RdbReader *reader = RDBX_createReaderFile(parser, "./test/dumps/non_exist_file.rdb"); + + /* verify didn't get back reader instance */ + assert_null(reader); + + /* verify returned error code */ + RdbRes err = RDB_getErrorCode(parser); + assert_int_equal(err, RDB_ERR_FAILED_OPEN_RDB_FILE); + + /* verify returned error string */ + assert_string_equal(RDB_getErrorMessage(parser), "Failed to open RDB file: ./test/dumps/non_exist_file.rdb"); + + RDB_deleteParser(parser); +} + +static void test_createHandlersRdb2Json_and_2_FilterKey(void **state) { + UNUSED(state); + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1677580558", + "used-mem":"937464", + "aof-base":"0", + [{ + "mylist1":["v1"], + "mylist3":["v3","v2","v1"], + "mylist2":["v2","v1"] + }] + + ); + RdbStatus status; + RdbParser *parser = RDB_createParserRdb(NULL); + RDB_setLogLevel(parser, RDB_LOG_ERROR); + const char *rdbfile = PATH_DUMP_FOLDER("multiple_lists_strings.rdb"); + const char *jsonfile = PATH_TMP_FOLDER("multiple_lists_strings.json"); + + assert_non_null(RDBX_createReaderFile(parser, rdbfile)); + assert_non_null(RDBX_createHandlersRdb2Json(parser, + RDBX_CONV_JSON_ENC_PLAIN, + jsonfile, + RDB_LEVEL_DATA)); + + assert_non_null(RDBX_createHandlersFilterKey(parser, ".*i.*", 0, RDB_LEVEL_DATA)); + assert_non_null(RDBX_createHandlersFilterKey(parser, "mylist.*", 0, RDB_LEVEL_DATA)); + + + while ((status = RDB_parse(parser)) == RDB_STATUS_WAIT_MORE_DATA); + assert_int_equal( status, RDB_STATUS_OK); + + RDB_deleteParser(parser); + assert_json_file(jsonfile, expJson); +} + +static void printResPicture(int result) { + if (result) + printf(" x_x\n" + " /|\\\n" + " / \\\n" + "Tests got failed!\n\n"); + else + printf(" \\o/\n" + " |\n" + " / \\\n" + "All tests passed!!!\n\n"); + +} + +#define RUN_TEST_GROUP(grp) \ + if ((runGroupPrefix == NULL) || (strncmp(runGroupPrefix, #grp, strlen(runGroupPrefix)) == 0)) { \ + printf ("\n--- Test Group: %s ---\n", #grp); \ + result |= grp(); \ + } + +/*************************** group_main *******************************/ +int group_main(void) { + /* Insert here your test functions */ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_createReader_missingFile), + cmocka_unit_test(test_createHandlersRdb2Json_and_2_FilterKey), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} + +/*************************** MAIN *******************************/ +int main(int argc, char *argv[]) { + struct timeval st, et; + char *runGroupPrefix = NULL; + int result = 0; + + /* Parse command-line arguments */ + for (int i = 1; i < argc; i++) { + if ((strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--run-group") == 0) && i+1 < argc) { + runGroupPrefix = argv[++i]; + } else { + printf("Invalid argument: %s\n", argv[i]); + exit(EXIT_FAILURE); + } + } + + gettimeofday(&st,NULL); + + printf ("\n*************** START TESTING *******************\n"); + setenv("LIBRDB_SIM_WAIT_MORE_DATA", "0", 1); + RUN_TEST_GROUP(group_main); + RUN_TEST_GROUP(group_rdb_to_json); + RUN_TEST_GROUP(group_mem_management); + RUN_TEST_GROUP(group_bulk_ops); + RUN_TEST_GROUP(group_pause); + + printf ("\n*************** SIMULATING WAIT_MORE_DATA *******************\n"); + setenv("LIBRDB_SIM_WAIT_MORE_DATA", "1", 1); + RUN_TEST_GROUP(group_main); + RUN_TEST_GROUP(group_rdb_to_json); + RUN_TEST_GROUP(group_mem_management); + RUN_TEST_GROUP(group_bulk_ops); + printf ("\n*************** END TESTING *******************\n"); + + gettimeofday(&et,NULL); + + int elapsed = (et.tv_sec - st.tv_sec)*1000 + (et.tv_usec - st.tv_usec)/1000; + printf("Total time: %d milliseconds\n",elapsed); + + printResPicture(result); + return result; +} diff --git a/test/test_malloc.c b/test/test_malloc.c new file mode 100644 index 0000000..8c890f4 --- /dev/null +++ b/test/test_malloc.c @@ -0,0 +1,94 @@ +#include +#include +#include "test_common.h" + +enum { + CNT_CLONE_BULK, + CNT_MALLOC_BULK, + CNT_FREE_BULK, + CNT_MALLOC_WRAPPER, + CNT_REALLOC_WRAPPER, + CNT_FREE_WRAPPER, + CNT_MAX +}; + +int counters[CNT_MAX]; + +void *myCloneBulk(void *str, size_t len) {++counters[CNT_CLONE_BULK]; return xclone(str, len); } +void *myMallocBulk(size_t size) { ++counters[CNT_MALLOC_BULK]; return xmalloc(size); } +void myFreeBulk(void *ptr) { ++counters[CNT_FREE_BULK]; xfree(ptr); } + +void *myMalloc (size_t size) { ++counters[CNT_MALLOC_WRAPPER]; return xmalloc(size); } +void *myRealloc (void *ptr, size_t size) { ++counters[CNT_REALLOC_WRAPPER]; return xrealloc(ptr, size); } +void myFree (void *ptr) { ++counters[CNT_FREE_WRAPPER]; xfree(ptr); } + +static void test_extern_alloc(void **state) { + UNUSED(state); + RdbStatus status; + + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1672087814", + "used-mem":"972952", + "repl-stream-db":"0", + "repl-id":"67ebe8f627f436e2630eef8661a697fa33563a8f", + "repl-offset":"162341903", + "aof-base":"0", + [ + {"xxx":"111"} + ] + ); + + for (int bulkAllocType = 0 ; bulkAllocType < RDB_BULK_ALLOC_MAX ; ++bulkAllocType) { + memset(counters, 0, sizeof(counters)); + + RdbMemAlloc mem = {myMalloc, myRealloc, myFree, + bulkAllocType, /* << change each iteration */ + { myMallocBulk, myCloneBulk, myFreeBulk } + }; + + RdbParser *parser = RDB_createParserRdb(&mem); + RDB_setLogLevel(parser, RDB_LOG_ERROR); + + assert_non_null(RDBX_createReaderFile(parser, PATH_DUMP_FOLDER("single_key.rdb"))); + assert_non_null(RDBX_createHandlersRdb2Json(parser, + RDBX_CONV_JSON_ENC_PLAIN, + PATH_TMP_FOLDER("single_key.json"), + RDB_LEVEL_DATA)); + while ((status = RDB_parse(parser)) == RDB_STATUS_WAIT_MORE_DATA); + assert_int_equal(status, RDB_STATUS_OK); + RDB_deleteParser(parser); + assert_json_file(PATH_TMP_FOLDER("single_key.json"), expJson); + + switch (bulkAllocType) { + case RDB_BULK_ALLOC_STACK: + assert_int_equal(0, counters[CNT_MALLOC_BULK]); + assert_int_equal(0, counters[CNT_CLONE_BULK]); + break; + case RDB_BULK_ALLOC_HEAP: + assert_int_equal(0, counters[CNT_MALLOC_BULK]); + assert_int_equal(0, counters[CNT_CLONE_BULK]); + break; + case RDB_BULK_ALLOC_EXTERN: + case RDB_BULK_ALLOC_EXTERN_OPT: + /* Exactly 8 pairs of auxiliary fields and 1 pair of key-value should be created + * by provided "external" Bulk allocator. 18 calls in total */ + assert_int_equal(18, counters[CNT_MALLOC_BULK]); + /* Exactly 1 key need to be cloned externally by rdb2json */ + assert_int_equal(1, counters[CNT_CLONE_BULK]); + break; + } + + assert_int_not_equal(0, counters[CNT_MALLOC_WRAPPER]); + assert_int_not_equal(0, counters[CNT_FREE_WRAPPER]); + } +} + +/*************************** group_rdb_to_json *******************************/ +int group_mem_management(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_extern_alloc), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/test_pause.c b/test/test_pause.c new file mode 100644 index 0000000..e75ee03 --- /dev/null +++ b/test/test_pause.c @@ -0,0 +1,40 @@ +#include +#include "test_common.h" + +int aux_fields_counter; +RdbRes handle_pause_aux_field_pause(RdbParser *p, void *userData, RdbBulk auxkey, RdbBulk auxval) { + UNUSED(userData, auxkey, auxval); + ++aux_fields_counter; + RDB_pauseParser(p); + return RDB_OK; +} + +static void test_pause_by_handlers_callback(void **state) { + UNUSED(state); + RdbStatus status; + int pause_counter = 0; + void *user_data = NULL; + aux_fields_counter = 0; + + RdbHandlersRawCallbacks cb = { .handleAuxField = handle_pause_aux_field_pause }; + RdbParser *parser = RDB_createParserRdb(NULL); + RDB_setLogLevel(parser, RDB_LOG_ERROR); + assert_non_null(RDBX_createReaderFile(parser, "./test/dumps/single_list.rdb")); + assert_non_null(RDB_createHandlersRaw(parser, &cb, user_data, NULL)); + + while ((status = RDB_parse(parser)) != RDB_STATUS_OK) { + if (status == RDB_STATUS_PAUSED) ++pause_counter; + } + assert_int_equal( status, RDB_STATUS_OK); + assert_int_equal( pause_counter, aux_fields_counter); + + RDB_deleteParser(parser); +} + +/*************************** group_rdb_to_json *******************************/ +int group_pause(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_pause_by_handlers_callback), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/test_rdb_to_json.c b/test/test_rdb_to_json.c new file mode 100644 index 0000000..cdcb9ab --- /dev/null +++ b/test/test_rdb_to_json.c @@ -0,0 +1,245 @@ +#include +#include "test_common.h" + +static void test_r2j_single_list_data(void **state) { + UNUSED(state); + + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1677071222", + "used-mem":"982208", + "repl-stream-db":"0", + "repl-id":"f42ea6b158e5926941d08bde6b9ecb6ae88dcd45", + "repl-offset":"162395634", + "aof-base":"0", + [ + {"mylist":["val3", "val2", "val1"]} + ] + ); + + testVariousCases(PATH_DUMP_FOLDER("single_list.rdb"), + PATH_TMP_FOLDER("single_list.json"), + expJson, + RDB_LEVEL_DATA); +} + +static void test_r2j_single_list_struct(void **state) { + UNUSED(state); + + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1677071222", + "used-mem":"982208", + "repl-stream-db":"0", + "repl-id":"f42ea6b158e5926941d08bde6b9ecb6ae88dcd45", + "repl-offset":"162395634", + "aof-base":"0", + [ + {"mylist":["\x19\x00\x00\x00\x03\x00\x84val3\x05\x84val2\x05\x84val1\x05\xff"]} + ] + ); + + testVariousCases(PATH_DUMP_FOLDER("single_list.rdb"), + PATH_TMP_FOLDER("single_list.json"), + expJson, + RDB_LEVEL_STRUCT); +} + +static void test_r2j_single_list_raw (void **state) { + UNUSED(state); + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1677071222", + "used-mem":"982208", + "repl-stream-db":"0", + "repl-id":"f42ea6b158e5926941d08bde6b9ecb6ae88dcd45", + "repl-offset":"162395634", + "aof-base":"0", + [ + {"mylist":"\x12\x01\x02\x19\x19\x00\x00\x00\x03\x00\x84val3\x05\x84val2\x05\x84val1\x05\xff"} + ] + ); + testVariousCases(PATH_DUMP_FOLDER("single_list.rdb"), + PATH_TMP_FOLDER("single_list.json"), + expJson, + RDB_LEVEL_RAW); +} + +static void test_r2j_multiple_lists_and_strings_data (void **state) { + UNUSED(state); + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1677580558", + "used-mem":"937464", + "aof-base":"0", + [{ + "string2":"Hithere!", + "mylist1":["v1"], + "mylist3":["v3","v2","v1"], + "lzf_compressed":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "string1":"blaa", + "mylist2":["v2","v1"] + }] + ); + + testVariousCases(PATH_DUMP_FOLDER("multiple_lists_strings.rdb"), + PATH_TMP_FOLDER("multiple_lists_strings.json"), + expJson, + RDB_LEVEL_DATA); +} + +static void test_r2j_multiple_lists_and_strings_struct (void **state) { + UNUSED(state); + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1677580558", + "used-mem":"937464", + "aof-base":"0", + [{ + "string2":"Hithere!", + "mylist1":["\x0b\x00\x00\x00\x01\x00\x82v1\x03\xff"], + "mylist3":["\x13\x00\x00\x00\x03\x00\x82v3\x03\x82v2\x03\x82v1\x03\xff"], + "lzf_compressed":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "string1":"blaa", + "mylist2":["\x0f\x00\x00\x00\x02\x00\x82v2\x03\x82v1\x03\xff"] + }] + ); + + testVariousCases(PATH_DUMP_FOLDER("multiple_lists_strings.rdb"), + PATH_TMP_FOLDER("multiple_lists_strings.json"), + expJson, + RDB_LEVEL_STRUCT); +} + +static void test_r2j_multiple_lists_and_strings_raw (void **state) { + UNUSED(state); + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1677580558", + "used-mem":"937464", + "aof-base":"0", + [{ + "string2":"\x00\tHithere!", + "mylist1":"\x12\x01\x02\x0b\x0b\x00\x00\x00\x01\x00\x82v1\x03\xff", + "mylist3":"\x12\x01\x02\x13\x13\x00\x00\x00\x03\x00\x82v3\x03\x82v2\x03\x82v1\x03\xff", + "lzf_compressed":"\x00\xc3\t@v\x01cc\xe0i\x00\x01cc", + "string1":"\x00\x04blaa", + "mylist2":"\x12\x01\x02\x0f\x0f\x00\x00\x00\x02\x00\x82v2\x03\x82v1\x03\xff" + }] + ); + + testVariousCases(PATH_DUMP_FOLDER("multiple_lists_strings.rdb"), + PATH_TMP_FOLDER("multiple_lists_strings.json"), + expJson, + RDB_LEVEL_RAW); +} + +static void test_r2j_single_string_data(void **state) { + UNUSED(state); + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1672087814", + "used-mem":"972952", + "repl-stream-db":"0", + "repl-id":"67ebe8f627f436e2630eef8661a697fa33563a8f", + "repl-offset":"162341903", + "aof-base":"0", + [ + {"xxx":"111"} + ] + ); + testVariousCases(PATH_DUMP_FOLDER("single_key.rdb"), + PATH_TMP_FOLDER("single_key.json"), + expJson, + RDB_LEVEL_DATA); +} + +static void test_r2j_single_string_struct (void **state) { + UNUSED(state); + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1672087814", + "used-mem":"972952", + "repl-stream-db":"0", + "repl-id":"67ebe8f627f436e2630eef8661a697fa33563a8f", + "repl-offset":"162341903", + "aof-base":"0", + [ + {"xxx":"111"} + ] + ); + testVariousCases(PATH_DUMP_FOLDER("single_key.rdb"), + PATH_TMP_FOLDER("single_key.json"), + expJson, + RDB_LEVEL_STRUCT); +} + +static void test_r2j_single_string_raw (void **state) { + UNUSED(state); + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1672087814", + "used-mem":"972952", + "repl-stream-db":"0", + "repl-id":"67ebe8f627f436e2630eef8661a697fa33563a8f", + "repl-offset":"162341903", + "aof-base":"0", + [ + {"xxx":"\x00\xc0o"} + ] + ); + testVariousCases(PATH_DUMP_FOLDER("single_key.rdb"), + PATH_TMP_FOLDER("single_key.json"), + expJson, + RDB_LEVEL_RAW); +} + +static void test_r2j_multiple_dbs (void **state) { + UNUSED(state); + char expJson[] = QUOTE( + "redis-ver":"255.255.255", + "redis-bits":"64", + "ctime":"1683103535", + "used-mem":"967040", + "aof-base":"0", + [ + {"x":"0"}, + {"y":"1"}, + {"z":"2"} + ] + ); + + testVariousCases(PATH_DUMP_FOLDER("multiple_dbs.rdb"), + PATH_TMP_FOLDER("multiple_dbs.json"), + expJson, + RDB_LEVEL_DATA); +} + +/*************************** group_rdb_to_json *******************************/ +int group_rdb_to_json(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_r2j_single_list_data), + cmocka_unit_test(test_r2j_single_list_struct), + cmocka_unit_test(test_r2j_single_list_raw), + + cmocka_unit_test(test_r2j_single_string_data), + cmocka_unit_test(test_r2j_single_string_struct), + cmocka_unit_test(test_r2j_single_string_raw), + + cmocka_unit_test(test_r2j_multiple_lists_and_strings_data), + cmocka_unit_test(test_r2j_multiple_lists_and_strings_struct), + cmocka_unit_test(test_r2j_multiple_lists_and_strings_raw), + + cmocka_unit_test(test_r2j_multiple_dbs), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/tmp/.gitkeep b/test/tmp/.gitkeep new file mode 100644 index 0000000..e69de29