From b8140ec8b6916fa1b822173c8846c6d9f1e48152 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 3 Apr 2018 07:08:57 -0400 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0hiredis=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/hiredis/.gitignore | 7 + lib/hiredis/.travis.yml | 45 + lib/hiredis/CHANGELOG.md | 149 +++ lib/hiredis/COPYING | 29 + lib/hiredis/README.md | 411 ++++++++ lib/hiredis/adapters/ae.h | 127 +++ lib/hiredis/adapters/glib.h | 153 +++ lib/hiredis/adapters/ivykis.h | 81 ++ lib/hiredis/adapters/libev.h | 147 +++ lib/hiredis/adapters/libevent.h | 108 ++ lib/hiredis/adapters/libuv.h | 122 +++ lib/hiredis/adapters/macosx.h | 114 ++ lib/hiredis/adapters/qt.h | 135 +++ lib/hiredis/appveyor.yml | 23 + lib/hiredis/async.c | 690 ++++++++++++ lib/hiredis/async.h | 129 +++ lib/hiredis/dict.c | 338 ++++++ lib/hiredis/dict.h | 126 +++ lib/hiredis/examples/example-ae.c | 62 ++ lib/hiredis/examples/example-glib.c | 73 ++ lib/hiredis/examples/example-ivykis.c | 58 ++ lib/hiredis/examples/example-libev.c | 52 + lib/hiredis/examples/example-libevent.c | 53 + lib/hiredis/examples/example-libuv.c | 53 + lib/hiredis/examples/example-macosx.c | 66 ++ lib/hiredis/examples/example-qt.cpp | 46 + lib/hiredis/examples/example-qt.h | 32 + lib/hiredis/examples/example.c | 78 ++ lib/hiredis/fmacros.h | 38 + lib/hiredis/hiredis.c | 1024 ++++++++++++++++++ lib/hiredis/hiredis.h | 223 ++++ lib/hiredis/net.c | 477 +++++++++ lib/hiredis/net.h | 53 + lib/hiredis/read.c | 525 ++++++++++ lib/hiredis/read.h | 111 ++ lib/hiredis/sds.c | 1272 +++++++++++++++++++++++ lib/hiredis/sds.h | 273 +++++ lib/hiredis/sdsalloc.h | 42 + lib/hiredis/test.c | 823 +++++++++++++++ lib/hiredis/test2 | Bin 0 -> 200032 bytes lib/hiredis/test2.c | 20 + lib/hiredis/win32.h | 42 + 42 files changed, 8430 insertions(+) create mode 100755 lib/hiredis/.gitignore create mode 100755 lib/hiredis/.travis.yml create mode 100755 lib/hiredis/CHANGELOG.md create mode 100755 lib/hiredis/COPYING create mode 100755 lib/hiredis/README.md create mode 100755 lib/hiredis/adapters/ae.h create mode 100755 lib/hiredis/adapters/glib.h create mode 100755 lib/hiredis/adapters/ivykis.h create mode 100755 lib/hiredis/adapters/libev.h create mode 100755 lib/hiredis/adapters/libevent.h create mode 100755 lib/hiredis/adapters/libuv.h create mode 100755 lib/hiredis/adapters/macosx.h create mode 100755 lib/hiredis/adapters/qt.h create mode 100755 lib/hiredis/appveyor.yml create mode 100755 lib/hiredis/async.c create mode 100755 lib/hiredis/async.h create mode 100755 lib/hiredis/dict.c create mode 100755 lib/hiredis/dict.h create mode 100755 lib/hiredis/examples/example-ae.c create mode 100755 lib/hiredis/examples/example-glib.c create mode 100755 lib/hiredis/examples/example-ivykis.c create mode 100755 lib/hiredis/examples/example-libev.c create mode 100755 lib/hiredis/examples/example-libevent.c create mode 100755 lib/hiredis/examples/example-libuv.c create mode 100755 lib/hiredis/examples/example-macosx.c create mode 100755 lib/hiredis/examples/example-qt.cpp create mode 100755 lib/hiredis/examples/example-qt.h create mode 100755 lib/hiredis/examples/example.c create mode 100755 lib/hiredis/fmacros.h create mode 100755 lib/hiredis/hiredis.c create mode 100755 lib/hiredis/hiredis.h create mode 100755 lib/hiredis/net.c create mode 100755 lib/hiredis/net.h create mode 100755 lib/hiredis/read.c create mode 100755 lib/hiredis/read.h create mode 100755 lib/hiredis/sds.c create mode 100755 lib/hiredis/sds.h create mode 100755 lib/hiredis/sdsalloc.h create mode 100755 lib/hiredis/test.c create mode 100755 lib/hiredis/test2 create mode 100755 lib/hiredis/test2.c create mode 100755 lib/hiredis/win32.h diff --git a/lib/hiredis/.gitignore b/lib/hiredis/.gitignore new file mode 100755 index 0000000..c44b5c5 --- /dev/null +++ b/lib/hiredis/.gitignore @@ -0,0 +1,7 @@ +/hiredis-test +/examples/hiredis-example* +/*.o +/*.so +/*.dylib +/*.a +/*.pc diff --git a/lib/hiredis/.travis.yml b/lib/hiredis/.travis.yml new file mode 100755 index 0000000..faf2ce6 --- /dev/null +++ b/lib/hiredis/.travis.yml @@ -0,0 +1,45 @@ +language: c +sudo: false +compiler: + - gcc + - clang + +os: + - linux + - osx + +branches: + only: + - staging + - trying + - master + +before_script: + - if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi + +addons: + apt: + packages: + - libc6-dbg + - libc6-dev + - libc6:i386 + - libc6-dev-i386 + - libc6-dbg:i386 + - gcc-multilib + - valgrind + +env: + - CFLAGS="-Werror" + - PRE="valgrind --track-origins=yes --leak-check=full" + - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror" + - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" + +matrix: + exclude: + - os: osx + env: PRE="valgrind --track-origins=yes --leak-check=full" + + - os: osx + env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" + +script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example diff --git a/lib/hiredis/CHANGELOG.md b/lib/hiredis/CHANGELOG.md new file mode 100755 index 0000000..f40ec53 --- /dev/null +++ b/lib/hiredis/CHANGELOG.md @@ -0,0 +1,149 @@ +### 1.0.0 (unreleased) + +**Fixes**: + +* Catch a buffer overflow when formatting the error message +* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 +* Fix warnings, when compiled with -Wshadow +* Make hiredis compile in Cygwin on Windows, now CI-tested + +**BREAKING CHANGES**: + +* Change `redisReply.len` to `size_t`, as it denotes the the size of a string + +User code should compare this to `size_t` values as well. +If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before. + +* Remove backwards compatibility macro's + +This removes the following old function aliases, use the new name now: + +| Old | New | +| --------------------------- | ---------------------- | +| redisReplyReaderCreate | redisReaderCreate | +| redisReplyReaderCreate | redisReaderCreate | +| redisReplyReaderFree | redisReaderFree | +| redisReplyReaderFeed | redisReaderFeed | +| redisReplyReaderGetReply | redisReaderGetReply | +| redisReplyReaderSetPrivdata | redisReaderSetPrivdata | +| redisReplyReaderGetObject | redisReaderGetObject | +| redisReplyReaderGetError | redisReaderGetError | + +* The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS` + +Previously it broke some builds for people that had `DEBUG` set to some arbitrary value, +due to debugging other software. +By renaming we avoid unintentional name clashes. + +Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again. + +### 0.13.3 (2015-09-16) + +* Revert "Clear `REDIS_CONNECTED` flag when connection is closed". +* Make tests pass on FreeBSD (Thanks, Giacomo Olgeni) + + +If the `REDIS_CONNECTED` flag is cleared, +the async onDisconnect callback function will never be called. +This causes problems as the disconnect is never reported back to the user. + +### 0.13.2 (2015-08-25) + +* Prevent crash on pending replies in async code (Thanks, @switch-st) +* Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs) +* Add MacOS X addapter (Thanks, @dizzus) +* Add Qt adapter (Thanks, Pietro Cerutti) +* Add Ivykis adapter (Thanks, Gergely Nagy) + +All adapters are provided as is and are only tested where possible. + +### 0.13.1 (2015-05-03) + +This is a bug fix release. +The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code. +Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects. +Other non-C99 code can now use hiredis as usual again. +Sorry for the inconvenience. + +* Fix memory leak in async reply handling (Salvatore Sanfilippo) +* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa) + +### 0.13.0 (2015-04-16) + +This release adds a minimal Windows compatibility layer. +The parser, standalone since v0.12.0, can now be compiled on Windows +(and thus used in other client libraries as well) + +* Windows compatibility layer for parser code (tzickel) +* Properly escape data printed to PKGCONF file (Dan Skorupski) +* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff) +* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra) + +### 0.12.1 (2015-01-26) + +* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location +* Fix `make test` as 32 bit build on 64 bit platform + +### 0.12.0 (2015-01-22) + +* Add optional KeepAlive support + +* Try again on EINTR errors + +* Add libuv adapter + +* Add IPv6 support + +* Remove possibility of multiple close on same fd + +* Add ability to bind source address on connect + +* Add redisConnectFd() and redisFreeKeepFd() + +* Fix getaddrinfo() memory leak + +* Free string if it is unused (fixes memory leak) + +* Improve redisAppendCommandArgv performance 2.5x + +* Add support for SO_REUSEADDR + +* Fix redisvFormatCommand format parsing + +* Add GLib 2.0 adapter + +* Refactor reading code into read.c + +* Fix errno error buffers to not clobber errors + +* Generate pkgconf during build + +* Silence _BSD_SOURCE warnings + +* Improve digit counting for multibulk creation + + +### 0.11.0 + +* Increase the maximum multi-bulk reply depth to 7. + +* Increase the read buffer size from 2k to 16k. + +* Use poll(2) instead of select(2) to support large fds (>= 1024). + +### 0.10.1 + +* Makefile overhaul. Important to check out if you override one or more + variables using environment variables or via arguments to the "make" tool. + +* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements + being created by the default reply object functions. + +* Issue #43: Don't crash in an asynchronous context when Redis returns an error + reply after the connection has been made (this happens when the maximum + number of connections is reached). + +### 0.10.0 + +* See commit log. + diff --git a/lib/hiredis/COPYING b/lib/hiredis/COPYING new file mode 100755 index 0000000..a5fc973 --- /dev/null +++ b/lib/hiredis/COPYING @@ -0,0 +1,29 @@ +Copyright (c) 2009-2011, Salvatore Sanfilippo +Copyright (c) 2010-2011, Pieter Noordhuis + +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. diff --git a/lib/hiredis/README.md b/lib/hiredis/README.md new file mode 100755 index 0000000..01223ea --- /dev/null +++ b/lib/hiredis/README.md @@ -0,0 +1,411 @@ +[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) + +**This Readme reflects the latest changed in the master branch. See [v0.13.3](https://github.com/redis/hiredis/tree/v0.13.3) for the Readme and documentation for the latest release.** + +# HIREDIS + +Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. + +It is minimalistic because it just adds minimal support for the protocol, but +at the same time it uses a high level printf-alike API in order to make it +much higher level than otherwise suggested by its minimal code base and the +lack of explicit bindings for every Redis command. + +Apart from supporting sending commands and receiving replies, it comes with +a reply parser that is decoupled from the I/O layer. It +is a stream parser designed for easy reusability, which can for instance be used +in higher level language bindings for efficient reply parsing. + +Hiredis only supports the binary-safe Redis protocol, so you can use it with any +Redis version >= 1.2.0. + +The library comes with multiple APIs. There is the +*synchronous API*, the *asynchronous API* and the *reply parsing API*. + +## Upgrading to `1.0.0` + +Version 1.0.0 marks a stable release of hiredis. +It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory. +It also bundles the updated `sds` library, to sync up with upstream and Redis. +For most applications a recompile against the new hiredis should be enough. +For code changes see the [Changelog](CHANGELOG.md). + +## Upgrading from `<0.9.0` + +Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing +code using hiredis should not be a big pain. The key thing to keep in mind when +upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to +the stateless 0.0.1 that only has a file descriptor to work with. + +## Synchronous API + +To consume the synchronous API, there are only a few function calls that need to be introduced: + +```c +redisContext *redisConnect(const char *ip, int port); +void *redisCommand(redisContext *c, const char *format, ...); +void freeReplyObject(void *reply); +``` + +### Connecting + +The function `redisConnect` is used to create a so-called `redisContext`. The +context is where Hiredis holds state for a connection. The `redisContext` +struct has an integer `err` field that is non-zero when the connection is in +an error state. The field `errstr` will contain a string with a description of +the error. More information on errors can be found in the **Errors** section. +After trying to connect to Redis using `redisConnect` you should +check the `err` field to see if establishing the connection was successful: +```c +redisContext *c = redisConnect("127.0.0.1", 6379); +if (c == NULL || c->err) { + if (c) { + printf("Error: %s\n", c->errstr); + // handle error + } else { + printf("Can't allocate redis context\n"); + } +} +``` + +*Note: A `redisContext` is not thread-safe.* + +### Sending commands + +There are several ways to issue commands to Redis. The first that will be introduced is +`redisCommand`. This function takes a format similar to printf. In the simplest form, +it is used like this: +```c +reply = redisCommand(context, "SET foo bar"); +``` + +The specifier `%s` interpolates a string in the command, and uses `strlen` to +determine the length of the string: +```c +reply = redisCommand(context, "SET foo %s", value); +``` +When you need to pass binary safe strings in a command, the `%b` specifier can be +used. Together with a pointer to the string, it requires a `size_t` length argument +of the string: +```c +reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); +``` +Internally, Hiredis splits the command in different arguments and will +convert it to the protocol used to communicate with Redis. +One or more spaces separates arguments, so you can use the specifiers +anywhere in an argument: +```c +reply = redisCommand(context, "SET key:%s %s", myid, value); +``` + +### Using replies + +The return value of `redisCommand` holds a reply when the command was +successfully executed. When an error occurs, the return value is `NULL` and +the `err` field in the context will be set (see section on **Errors**). +Once an error is returned the context cannot be reused and you should set up +a new connection. + +The standard replies that `redisCommand` are of the type `redisReply`. The +`type` field in the `redisReply` should be used to test what kind of reply +was received: + +* **`REDIS_REPLY_STATUS`**: + * The command replied with a status reply. The status string can be accessed using `reply->str`. + The length of this string can be accessed using `reply->len`. + +* **`REDIS_REPLY_ERROR`**: + * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. + +* **`REDIS_REPLY_INTEGER`**: + * The command replied with an integer. The integer value can be accessed using the + `reply->integer` field of type `long long`. + +* **`REDIS_REPLY_NIL`**: + * The command replied with a **nil** object. There is no data to access. + +* **`REDIS_REPLY_STRING`**: + * A bulk (string) reply. The value of the reply can be accessed using `reply->str`. + The length of this string can be accessed using `reply->len`. + +* **`REDIS_REPLY_ARRAY`**: + * A multi bulk reply. The number of elements in the multi bulk reply is stored in + `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well + and can be accessed via `reply->element[..index..]`. + Redis may reply with nested arrays but this is fully supported. + +Replies should be freed using the `freeReplyObject()` function. +Note that this function will take care of freeing sub-reply objects +contained in arrays and nested arrays, so there is no need for the user to +free the sub replies (it is actually harmful and will corrupt the memory). + +**Important:** the current version of hiredis (0.10.0) frees replies when the +asynchronous API is used. This means you should not call `freeReplyObject` when +you use this API. The reply is cleaned up by hiredis _after_ the callback +returns. This behavior will probably change in future releases, so make sure to +keep an eye on the changelog when upgrading (see issue #39). + +### Cleaning up + +To disconnect and free the context the following function can be used: +```c +void redisFree(redisContext *c); +``` +This function immediately closes the socket and then frees the allocations done in +creating the context. + +### Sending commands (cont'd) + +Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. +It has the following prototype: +```c +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +``` +It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the +arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will +use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments +need to be binary safe, the entire array of lengths `argvlen` should be provided. + +The return value has the same semantic as `redisCommand`. + +### Pipelining + +To explain how Hiredis supports pipelining in a blocking connection, there needs to be +understanding of the internal execution flow. + +When any of the functions in the `redisCommand` family is called, Hiredis first formats the +command according to the Redis protocol. The formatted command is then put in the output buffer +of the context. This output buffer is dynamic, so it can hold any number of commands. +After the command is put in the output buffer, `redisGetReply` is called. This function has the +following two execution paths: + +1. The input buffer is non-empty: + * Try to parse a single reply from the input buffer and return it + * If no reply could be parsed, continue at *2* +2. The input buffer is empty: + * Write the **entire** output buffer to the socket + * Read from the socket until a single reply could be parsed + +The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply +is expected on the socket. To pipeline commands, the only things that needs to be done is +filling up the output buffer. For this cause, two commands can be used that are identical +to the `redisCommand` family, apart from not returning a reply: +```c +void redisAppendCommand(redisContext *c, const char *format, ...); +void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +``` +After calling either function one or more times, `redisGetReply` can be used to receive the +subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where +the latter means an error occurred while reading a reply. Just as with the other commands, +the `err` field in the context can be used to find out what the cause of this error is. + +The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and +a single call to `read(2)`): +```c +redisReply *reply; +redisAppendCommand(context,"SET foo bar"); +redisAppendCommand(context,"GET foo"); +redisGetReply(context,&reply); // reply for SET +freeReplyObject(reply); +redisGetReply(context,&reply); // reply for GET +freeReplyObject(reply); +``` +This API can also be used to implement a blocking subscriber: +```c +reply = redisCommand(context,"SUBSCRIBE foo"); +freeReplyObject(reply); +while(redisGetReply(context,&reply) == REDIS_OK) { + // consume message + freeReplyObject(reply); +} +``` +### Errors + +When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is +returned. The `err` field inside the context will be non-zero and set to one of the +following constants: + +* **`REDIS_ERR_IO`**: + There was an I/O error while creating the connection, trying to write + to the socket or read from the socket. If you included `errno.h` in your + application, you can use the global `errno` variable to find out what is + wrong. + +* **`REDIS_ERR_EOF`**: + The server closed the connection which resulted in an empty read. + +* **`REDIS_ERR_PROTOCOL`**: + There was an error while parsing the protocol. + +* **`REDIS_ERR_OTHER`**: + Any other error. Currently, it is only used when a specified hostname to connect + to cannot be resolved. + +In every case, the `errstr` field in the context will be set to hold a string representation +of the error. + +## Asynchronous API + +Hiredis comes with an asynchronous API that works easily with any event library. +Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) +and [libevent](http://monkey.org/~provos/libevent/). + +### Connecting + +The function `redisAsyncConnect` can be used to establish a non-blocking connection to +Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field +should be checked after creation to see if there were errors creating the connection. +Because the connection that will be created is non-blocking, the kernel is not able to +instantly return if the specified host and port is able to accept a connection. + +*Note: A `redisAsyncContext` is not thread-safe.* + +```c +redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); +if (c->err) { + printf("Error: %s\n", c->errstr); + // handle error +} +``` + +The asynchronous context can hold a disconnect callback function that is called when the +connection is disconnected (either because of an error or per user request). This function should +have the following prototype: +```c +void(const redisAsyncContext *c, int status); +``` +On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the +user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` +field in the context can be accessed to find out the cause of the error. + +The context object is always freed after the disconnect callback fired. When a reconnect is needed, +the disconnect callback is a good point to do so. + +Setting the disconnect callback can only be done once per context. For subsequent calls it will +return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: +```c +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +``` +### Sending commands and their callbacks + +In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. +Therefore, unlike the synchronous API, there is only a single way to send commands. +Because commands are sent to Redis asynchronously, issuing a command requires a callback function +that is called when the reply is received. Reply callbacks should have the following prototype: +```c +void(redisAsyncContext *c, void *reply, void *privdata); +``` +The `privdata` argument can be used to curry arbitrary data to the callback from the point where +the command is initially queued for execution. + +The functions that can be used to issue commands in an asynchronous context are: +```c +int redisAsyncCommand( + redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, + const char *format, ...); +int redisAsyncCommandArgv( + redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, + int argc, const char **argv, const size_t *argvlen); +``` +Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command +was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection +is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is +returned on calls to the `redisAsyncCommand` family. + +If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback +for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only +valid for the duration of the callback. + +All pending callbacks are called with a `NULL` reply when the context encountered an error. + +### Disconnecting + +An asynchronous connection can be terminated using: +```c +void redisAsyncDisconnect(redisAsyncContext *ac); +``` +When this function is called, the connection is **not** immediately terminated. Instead, new +commands are no longer accepted and the connection is only terminated when all pending commands +have been written to the socket, their respective replies have been read and their respective +callbacks have been executed. After this, the disconnection callback is executed with the +`REDIS_OK` status and the context object is freed. + +### Hooking it up to event library *X* + +There are a few hooks that need to be set on the context object after it is created. +See the `adapters/` directory for bindings to *libev* and *libevent*. + +## Reply parsing API + +Hiredis comes with a reply parsing API that makes it easy for writing higher +level language bindings. + +The reply parsing API consists of the following functions: +```c +redisReader *redisReaderCreate(void); +void redisReaderFree(redisReader *reader); +int redisReaderFeed(redisReader *reader, const char *buf, size_t len); +int redisReaderGetReply(redisReader *reader, void **reply); +``` +The same set of functions are used internally by hiredis when creating a +normal Redis context, the above API just exposes it to the user for a direct +usage. + +### Usage + +The function `redisReaderCreate` creates a `redisReader` structure that holds a +buffer with unparsed data and state for the protocol parser. + +Incoming data -- most likely from a socket -- can be placed in the internal +buffer of the `redisReader` using `redisReaderFeed`. This function will make a +copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed +when `redisReaderGetReply` is called. This function returns an integer status +and a reply object (as described above) via `void **reply`. The returned status +can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went +wrong (either a protocol error, or an out of memory error). + +The parser limits the level of nesting for multi bulk payloads to 7. If the +multi bulk nesting level is higher than this, the parser returns an error. + +### Customizing replies + +The function `redisReaderGetReply` creates `redisReply` and makes the function +argument `reply` point to the created `redisReply` variable. For instance, if +the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` +will hold the status as a vanilla C string. However, the functions that are +responsible for creating instances of the `redisReply` can be customized by +setting the `fn` field on the `redisReader` struct. This should be done +immediately after creating the `redisReader`. + +For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) +uses customized reply object functions to create Ruby objects. + +### Reader max buffer + +Both when using the Reader API directly or when using it indirectly via a +normal Redis context, the redisReader structure uses a buffer in order to +accumulate data from the server. +Usually this buffer is destroyed when it is empty and is larger than 16 +KiB in order to avoid wasting memory in unused buffers + +However when working with very big payloads destroying the buffer may slow +down performances considerably, so it is possible to modify the max size of +an idle buffer changing the value of the `maxbuf` field of the reader structure +to the desired value. The special value of 0 means that there is no maximum +value for an idle buffer, so the buffer will never get freed. + +For instance if you have a normal Redis context you can set the maximum idle +buffer to zero (unlimited) just with: +```c +context->reader->maxbuf = 0; +``` +This should be done only in order to maximize performances when working with +large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again +as soon as possible in order to prevent allocation of useless memory. + +## AUTHORS + +Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and +Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. +Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and +Jan-Erik Rediger (janerik at fnordig dot com) diff --git a/lib/hiredis/adapters/ae.h b/lib/hiredis/adapters/ae.h new file mode 100755 index 0000000..5c551c2 --- /dev/null +++ b/lib/hiredis/adapters/ae.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 __HIREDIS_AE_H__ +#define __HIREDIS_AE_H__ +#include +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisAeEvents { + redisAsyncContext *context; + aeEventLoop *loop; + int fd; + int reading, writing; +} redisAeEvents; + +static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { + ((void)el); ((void)fd); ((void)mask); + + redisAeEvents *e = (redisAeEvents*)privdata; + redisAsyncHandleRead(e->context); +} + +static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { + ((void)el); ((void)fd); ((void)mask); + + redisAeEvents *e = (redisAeEvents*)privdata; + redisAsyncHandleWrite(e->context); +} + +static void redisAeAddRead(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (!e->reading) { + e->reading = 1; + aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); + } +} + +static void redisAeDelRead(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (e->reading) { + e->reading = 0; + aeDeleteFileEvent(loop,e->fd,AE_READABLE); + } +} + +static void redisAeAddWrite(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (!e->writing) { + e->writing = 1; + aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e); + } +} + +static void redisAeDelWrite(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (e->writing) { + e->writing = 0; + aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); + } +} + +static void redisAeCleanup(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + redisAeDelRead(privdata); + redisAeDelWrite(privdata); + free(e); +} + +static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisAeEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisAeEvents*)malloc(sizeof(*e)); + e->context = ac; + e->loop = loop; + e->fd = c->fd; + e->reading = e->writing = 0; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisAeAddRead; + ac->ev.delRead = redisAeDelRead; + ac->ev.addWrite = redisAeAddWrite; + ac->ev.delWrite = redisAeDelWrite; + ac->ev.cleanup = redisAeCleanup; + ac->ev.data = e; + + return REDIS_OK; +} +#endif diff --git a/lib/hiredis/adapters/glib.h b/lib/hiredis/adapters/glib.h new file mode 100755 index 0000000..e0a6411 --- /dev/null +++ b/lib/hiredis/adapters/glib.h @@ -0,0 +1,153 @@ +#ifndef __HIREDIS_GLIB_H__ +#define __HIREDIS_GLIB_H__ + +#include + +#include "../hiredis.h" +#include "../async.h" + +typedef struct +{ + GSource source; + redisAsyncContext *ac; + GPollFD poll_fd; +} RedisSource; + +static void +redis_source_add_read (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events |= G_IO_IN; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_del_read (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events &= ~G_IO_IN; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_add_write (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events |= G_IO_OUT; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_del_write (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events &= ~G_IO_OUT; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_cleanup (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + + g_return_if_fail(source); + + redis_source_del_read(source); + redis_source_del_write(source); + /* + * It is not our responsibility to remove ourself from the + * current main loop. However, we will remove the GPollFD. + */ + if (source->poll_fd.fd >= 0) { + g_source_remove_poll((GSource *)data, &source->poll_fd); + source->poll_fd.fd = -1; + } +} + +static gboolean +redis_source_prepare (GSource *source, + gint *timeout_) +{ + RedisSource *redis = (RedisSource *)source; + *timeout_ = -1; + return !!(redis->poll_fd.events & redis->poll_fd.revents); +} + +static gboolean +redis_source_check (GSource *source) +{ + RedisSource *redis = (RedisSource *)source; + return !!(redis->poll_fd.events & redis->poll_fd.revents); +} + +static gboolean +redis_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + RedisSource *redis = (RedisSource *)source; + + if ((redis->poll_fd.revents & G_IO_OUT)) { + redisAsyncHandleWrite(redis->ac); + redis->poll_fd.revents &= ~G_IO_OUT; + } + + if ((redis->poll_fd.revents & G_IO_IN)) { + redisAsyncHandleRead(redis->ac); + redis->poll_fd.revents &= ~G_IO_IN; + } + + if (callback) { + return callback(user_data); + } + + return TRUE; +} + +static void +redis_source_finalize (GSource *source) +{ + RedisSource *redis = (RedisSource *)source; + + if (redis->poll_fd.fd >= 0) { + g_source_remove_poll(source, &redis->poll_fd); + redis->poll_fd.fd = -1; + } +} + +static GSource * +redis_source_new (redisAsyncContext *ac) +{ + static GSourceFuncs source_funcs = { + .prepare = redis_source_prepare, + .check = redis_source_check, + .dispatch = redis_source_dispatch, + .finalize = redis_source_finalize, + }; + redisContext *c = &ac->c; + RedisSource *source; + + g_return_val_if_fail(ac != NULL, NULL); + + source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); + source->ac = ac; + source->poll_fd.fd = c->fd; + source->poll_fd.events = 0; + source->poll_fd.revents = 0; + g_source_add_poll((GSource *)source, &source->poll_fd); + + ac->ev.addRead = redis_source_add_read; + ac->ev.delRead = redis_source_del_read; + ac->ev.addWrite = redis_source_add_write; + ac->ev.delWrite = redis_source_del_write; + ac->ev.cleanup = redis_source_cleanup; + ac->ev.data = source; + + return (GSource *)source; +} + +#endif /* __HIREDIS_GLIB_H__ */ diff --git a/lib/hiredis/adapters/ivykis.h b/lib/hiredis/adapters/ivykis.h new file mode 100755 index 0000000..6a12a86 --- /dev/null +++ b/lib/hiredis/adapters/ivykis.h @@ -0,0 +1,81 @@ +#ifndef __HIREDIS_IVYKIS_H__ +#define __HIREDIS_IVYKIS_H__ +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisIvykisEvents { + redisAsyncContext *context; + struct iv_fd fd; +} redisIvykisEvents; + +static void redisIvykisReadEvent(void *arg) { + redisAsyncContext *context = (redisAsyncContext *)arg; + redisAsyncHandleRead(context); +} + +static void redisIvykisWriteEvent(void *arg) { + redisAsyncContext *context = (redisAsyncContext *)arg; + redisAsyncHandleWrite(context); +} + +static void redisIvykisAddRead(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent); +} + +static void redisIvykisDelRead(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_in(&e->fd, NULL); +} + +static void redisIvykisAddWrite(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent); +} + +static void redisIvykisDelWrite(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_out(&e->fd, NULL); +} + +static void redisIvykisCleanup(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + + iv_fd_unregister(&e->fd); + free(e); +} + +static int redisIvykisAttach(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisIvykisEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisIvykisEvents*)malloc(sizeof(*e)); + e->context = ac; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisIvykisAddRead; + ac->ev.delRead = redisIvykisDelRead; + ac->ev.addWrite = redisIvykisAddWrite; + ac->ev.delWrite = redisIvykisDelWrite; + ac->ev.cleanup = redisIvykisCleanup; + ac->ev.data = e; + + /* Initialize and install read/write events */ + IV_FD_INIT(&e->fd); + e->fd.fd = c->fd; + e->fd.handler_in = redisIvykisReadEvent; + e->fd.handler_out = redisIvykisWriteEvent; + e->fd.handler_err = NULL; + e->fd.cookie = e->context; + + iv_fd_register(&e->fd); + + return REDIS_OK; +} +#endif diff --git a/lib/hiredis/adapters/libev.h b/lib/hiredis/adapters/libev.h new file mode 100755 index 0000000..2bf8d52 --- /dev/null +++ b/lib/hiredis/adapters/libev.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 __HIREDIS_LIBEV_H__ +#define __HIREDIS_LIBEV_H__ +#include +#include +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisLibevEvents { + redisAsyncContext *context; + struct ev_loop *loop; + int reading, writing; + ev_io rev, wev; +} redisLibevEvents; + +static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { +#if EV_MULTIPLICITY + ((void)loop); +#endif + ((void)revents); + + redisLibevEvents *e = (redisLibevEvents*)watcher->data; + redisAsyncHandleRead(e->context); +} + +static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { +#if EV_MULTIPLICITY + ((void)loop); +#endif + ((void)revents); + + redisLibevEvents *e = (redisLibevEvents*)watcher->data; + redisAsyncHandleWrite(e->context); +} + +static void redisLibevAddRead(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (!e->reading) { + e->reading = 1; + ev_io_start(EV_A_ &e->rev); + } +} + +static void redisLibevDelRead(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (e->reading) { + e->reading = 0; + ev_io_stop(EV_A_ &e->rev); + } +} + +static void redisLibevAddWrite(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (!e->writing) { + e->writing = 1; + ev_io_start(EV_A_ &e->wev); + } +} + +static void redisLibevDelWrite(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (e->writing) { + e->writing = 0; + ev_io_stop(EV_A_ &e->wev); + } +} + +static void redisLibevCleanup(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + redisLibevDelRead(privdata); + redisLibevDelWrite(privdata); + free(e); +} + +static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisLibevEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisLibevEvents*)malloc(sizeof(*e)); + e->context = ac; +#if EV_MULTIPLICITY + e->loop = loop; +#else + e->loop = NULL; +#endif + e->reading = e->writing = 0; + e->rev.data = e; + e->wev.data = e; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibevAddRead; + ac->ev.delRead = redisLibevDelRead; + ac->ev.addWrite = redisLibevAddWrite; + ac->ev.delWrite = redisLibevDelWrite; + ac->ev.cleanup = redisLibevCleanup; + ac->ev.data = e; + + /* Initialize read/write events */ + ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ); + ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE); + return REDIS_OK; +} + +#endif diff --git a/lib/hiredis/adapters/libevent.h b/lib/hiredis/adapters/libevent.h new file mode 100755 index 0000000..7d2bef1 --- /dev/null +++ b/lib/hiredis/adapters/libevent.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 __HIREDIS_LIBEVENT_H__ +#define __HIREDIS_LIBEVENT_H__ +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisLibeventEvents { + redisAsyncContext *context; + struct event *rev, *wev; +} redisLibeventEvents; + +static void redisLibeventReadEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + redisAsyncHandleRead(e->context); +} + +static void redisLibeventWriteEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + redisAsyncHandleWrite(e->context); +} + +static void redisLibeventAddRead(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_add(e->rev,NULL); +} + +static void redisLibeventDelRead(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(e->rev); +} + +static void redisLibeventAddWrite(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_add(e->wev,NULL); +} + +static void redisLibeventDelWrite(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(e->wev); +} + +static void redisLibeventCleanup(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_free(e->rev); + event_free(e->wev); + free(e); +} + +static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { + redisContext *c = &(ac->c); + redisLibeventEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisLibeventEvents*)malloc(sizeof(*e)); + e->context = ac; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibeventAddRead; + ac->ev.delRead = redisLibeventDelRead; + ac->ev.addWrite = redisLibeventAddWrite; + ac->ev.delWrite = redisLibeventDelWrite; + ac->ev.cleanup = redisLibeventCleanup; + ac->ev.data = e; + + /* Initialize and install read/write events */ + e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e); + e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e); + event_add(e->rev, NULL); + event_add(e->wev, NULL); + return REDIS_OK; +} +#endif diff --git a/lib/hiredis/adapters/libuv.h b/lib/hiredis/adapters/libuv.h new file mode 100755 index 0000000..ff08c25 --- /dev/null +++ b/lib/hiredis/adapters/libuv.h @@ -0,0 +1,122 @@ +#ifndef __HIREDIS_LIBUV_H__ +#define __HIREDIS_LIBUV_H__ +#include +#include +#include "../hiredis.h" +#include "../async.h" +#include + +typedef struct redisLibuvEvents { + redisAsyncContext* context; + uv_poll_t handle; + int events; +} redisLibuvEvents; + + +static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + if (status != 0) { + return; + } + + if (p->context != NULL && (events & UV_READABLE)) { + redisAsyncHandleRead(p->context); + } + if (p->context != NULL && (events & UV_WRITABLE)) { + redisAsyncHandleWrite(p->context); + } +} + + +static void redisLibuvAddRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_READABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_READABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void redisLibuvAddWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_WRITABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_WRITABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void on_close(uv_handle_t* handle) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + free(p); +} + + +static void redisLibuvCleanup(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->context = NULL; // indicate that context might no longer exist + uv_close((uv_handle_t*)&p->handle, on_close); +} + + +static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { + redisContext *c = &(ac->c); + + if (ac->ev.data != NULL) { + return REDIS_ERR; + } + + ac->ev.addRead = redisLibuvAddRead; + ac->ev.delRead = redisLibuvDelRead; + ac->ev.addWrite = redisLibuvAddWrite; + ac->ev.delWrite = redisLibuvDelWrite; + ac->ev.cleanup = redisLibuvCleanup; + + redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p)); + + if (!p) { + return REDIS_ERR; + } + + memset(p, 0, sizeof(*p)); + + if (uv_poll_init(loop, &p->handle, c->fd) != 0) { + return REDIS_ERR; + } + + ac->ev.data = p; + p->handle.data = p; + p->context = ac; + + return REDIS_OK; +} +#endif diff --git a/lib/hiredis/adapters/macosx.h b/lib/hiredis/adapters/macosx.h new file mode 100755 index 0000000..72121f6 --- /dev/null +++ b/lib/hiredis/adapters/macosx.h @@ -0,0 +1,114 @@ +// +// Created by Дмитрий Бахвалов on 13.07.15. +// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. +// + +#ifndef __HIREDIS_MACOSX_H__ +#define __HIREDIS_MACOSX_H__ + +#include + +#include "../hiredis.h" +#include "../async.h" + +typedef struct { + redisAsyncContext *context; + CFSocketRef socketRef; + CFRunLoopSourceRef sourceRef; +} RedisRunLoop; + +static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) { + if( redisRunLoop != NULL ) { + if( redisRunLoop->sourceRef != NULL ) { + CFRunLoopSourceInvalidate(redisRunLoop->sourceRef); + CFRelease(redisRunLoop->sourceRef); + } + if( redisRunLoop->socketRef != NULL ) { + CFSocketInvalidate(redisRunLoop->socketRef); + CFRelease(redisRunLoop->socketRef); + } + free(redisRunLoop); + } + return REDIS_ERR; +} + +static void redisMacOSAddRead(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); +} + +static void redisMacOSDelRead(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); +} + +static void redisMacOSAddWrite(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); +} + +static void redisMacOSDelWrite(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); +} + +static void redisMacOSCleanup(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + freeRedisRunLoop(redisRunLoop); +} + +static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) { + redisAsyncContext* context = (redisAsyncContext*) info; + + switch (callbackType) { + case kCFSocketReadCallBack: + redisAsyncHandleRead(context); + break; + + case kCFSocketWriteCallBack: + redisAsyncHandleWrite(context); + break; + + default: + break; + } +} + +static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) { + redisContext *redisCtx = &(redisAsyncCtx->c); + + /* Nothing should be attached when something is already attached */ + if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR; + + RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop)); + if( !redisRunLoop ) return REDIS_ERR; + + /* Setup redis stuff */ + redisRunLoop->context = redisAsyncCtx; + + redisAsyncCtx->ev.addRead = redisMacOSAddRead; + redisAsyncCtx->ev.delRead = redisMacOSDelRead; + redisAsyncCtx->ev.addWrite = redisMacOSAddWrite; + redisAsyncCtx->ev.delWrite = redisMacOSDelWrite; + redisAsyncCtx->ev.cleanup = redisMacOSCleanup; + redisAsyncCtx->ev.data = redisRunLoop; + + /* Initialize and install read/write events */ + CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL }; + + redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd, + kCFSocketReadCallBack | kCFSocketWriteCallBack, + redisMacOSAsyncCallback, + &socketCtx); + if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop); + + redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0); + if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop); + + CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode); + + return REDIS_OK; +} + +#endif + diff --git a/lib/hiredis/adapters/qt.h b/lib/hiredis/adapters/qt.h new file mode 100755 index 0000000..5cc02e6 --- /dev/null +++ b/lib/hiredis/adapters/qt.h @@ -0,0 +1,135 @@ +/*- + * Copyright (C) 2014 Pietro Cerutti + * + * Redistribution and use in source and binary forms, with or without + * modification, 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 AUTHOR 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 AUTHOR 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 __HIREDIS_QT_H__ +#define __HIREDIS_QT_H__ +#include +#include "../async.h" + +static void RedisQtAddRead(void *); +static void RedisQtDelRead(void *); +static void RedisQtAddWrite(void *); +static void RedisQtDelWrite(void *); +static void RedisQtCleanup(void *); + +class RedisQtAdapter : public QObject { + + Q_OBJECT + + friend + void RedisQtAddRead(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->addRead(); + } + + friend + void RedisQtDelRead(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->delRead(); + } + + friend + void RedisQtAddWrite(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->addWrite(); + } + + friend + void RedisQtDelWrite(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->delWrite(); + } + + friend + void RedisQtCleanup(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->cleanup(); + } + + public: + RedisQtAdapter(QObject * parent = 0) + : QObject(parent), m_ctx(0), m_read(0), m_write(0) { } + + ~RedisQtAdapter() { + if (m_ctx != 0) { + m_ctx->ev.data = NULL; + } + } + + int setContext(redisAsyncContext * ac) { + if (ac->ev.data != NULL) { + return REDIS_ERR; + } + m_ctx = ac; + m_ctx->ev.data = this; + m_ctx->ev.addRead = RedisQtAddRead; + m_ctx->ev.delRead = RedisQtDelRead; + m_ctx->ev.addWrite = RedisQtAddWrite; + m_ctx->ev.delWrite = RedisQtDelWrite; + m_ctx->ev.cleanup = RedisQtCleanup; + return REDIS_OK; + } + + private: + void addRead() { + if (m_read) return; + m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0); + connect(m_read, SIGNAL(activated(int)), this, SLOT(read())); + } + + void delRead() { + if (!m_read) return; + delete m_read; + m_read = 0; + } + + void addWrite() { + if (m_write) return; + m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0); + connect(m_write, SIGNAL(activated(int)), this, SLOT(write())); + } + + void delWrite() { + if (!m_write) return; + delete m_write; + m_write = 0; + } + + void cleanup() { + delRead(); + delWrite(); + } + + private slots: + void read() { redisAsyncHandleRead(m_ctx); } + void write() { redisAsyncHandleWrite(m_ctx); } + + private: + redisAsyncContext * m_ctx; + QSocketNotifier * m_read; + QSocketNotifier * m_write; +}; + +#endif /* !__HIREDIS_QT_H__ */ diff --git a/lib/hiredis/appveyor.yml b/lib/hiredis/appveyor.yml new file mode 100755 index 0000000..819efbd --- /dev/null +++ b/lib/hiredis/appveyor.yml @@ -0,0 +1,23 @@ +# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin) +environment: + matrix: + - CYG_BASH: C:\cygwin64\bin\bash + CC: gcc + - CYG_BASH: C:\cygwin\bin\bash + CC: gcc + TARGET: 32bit + TARGET_VARS: 32bit-vars + +clone_depth: 1 + +# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail +init: + - git config --global core.autocrlf input + +# Install needed build dependencies +install: + - '%CYG_BASH% -lc "cygcheck -dc cygwin"' + +build_script: + - 'echo building...' + - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0 + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 "fmacros.h" +#include +#include +#include +#include +#include +#include +#include "async.h" +#include "net.h" +#include "dict.c" +#include "sds.h" + +#define _EL_ADD_READ(ctx) do { \ + if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_READ(ctx) do { \ + if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ + } while(0) +#define _EL_ADD_WRITE(ctx) do { \ + if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_WRITE(ctx) do { \ + if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ + } while(0) +#define _EL_CLEANUP(ctx) do { \ + if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ + } while(0); + +/* Forward declaration of function in hiredis.c */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); + +/* Functions managing dictionary of callbacks for pub/sub. */ +static unsigned int callbackHash(const void *key) { + return dictGenHashFunction((const unsigned char *)key, + sdslen((const sds)key)); +} + +static void *callbackValDup(void *privdata, const void *src) { + ((void) privdata); + redisCallback *dup = malloc(sizeof(*dup)); + memcpy(dup,src,sizeof(*dup)); + return dup; +} + +static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { + int l1, l2; + ((void) privdata); + + l1 = sdslen((const sds)key1); + l2 = sdslen((const sds)key2); + if (l1 != l2) return 0; + return memcmp(key1,key2,l1) == 0; +} + +static void callbackKeyDestructor(void *privdata, void *key) { + ((void) privdata); + sdsfree((sds)key); +} + +static void callbackValDestructor(void *privdata, void *val) { + ((void) privdata); + free(val); +} + +static dictType callbackDict = { + callbackHash, + NULL, + callbackValDup, + callbackKeyCompare, + callbackKeyDestructor, + callbackValDestructor +}; + +static redisAsyncContext *redisAsyncInitialize(redisContext *c) { + redisAsyncContext *ac; + + ac = realloc(c,sizeof(redisAsyncContext)); + if (ac == NULL) + return NULL; + + c = &(ac->c); + + /* The regular connect functions will always set the flag REDIS_CONNECTED. + * For the async API, we want to wait until the first write event is + * received up before setting this flag, so reset it here. */ + c->flags &= ~REDIS_CONNECTED; + + ac->err = 0; + ac->errstr = NULL; + ac->data = NULL; + + ac->ev.data = NULL; + ac->ev.addRead = NULL; + ac->ev.delRead = NULL; + ac->ev.addWrite = NULL; + ac->ev.delWrite = NULL; + ac->ev.cleanup = NULL; + + ac->onConnect = NULL; + ac->onDisconnect = NULL; + + ac->replies.head = NULL; + ac->replies.tail = NULL; + ac->sub.invalid.head = NULL; + ac->sub.invalid.tail = NULL; + ac->sub.channels = dictCreate(&callbackDict,NULL); + ac->sub.patterns = dictCreate(&callbackDict,NULL); + return ac; +} + +/* We want the error field to be accessible directly instead of requiring + * an indirection to the redisContext struct. */ +static void __redisAsyncCopyError(redisAsyncContext *ac) { + if (!ac) + return; + + redisContext *c = &(ac->c); + ac->err = c->err; + ac->errstr = c->errstr; +} + +redisAsyncContext *redisAsyncConnect(const char *ip, int port) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectNonBlock(ip,port); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectUnix(const char *path) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectUnixNonBlock(path); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + + __redisAsyncCopyError(ac); + return ac; +} + +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { + if (ac->onConnect == NULL) { + ac->onConnect = fn; + + /* The common way to detect an established connection is to wait for + * the first write event to be fired. This assumes the related event + * library functions are already set. */ + _EL_ADD_WRITE(ac); + return REDIS_OK; + } + return REDIS_ERR; +} + +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { + if (ac->onDisconnect == NULL) { + ac->onDisconnect = fn; + return REDIS_OK; + } + return REDIS_ERR; +} + +/* Helper functions to push/shift callbacks */ +static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { + redisCallback *cb; + + /* Copy callback from stack to heap */ + cb = malloc(sizeof(*cb)); + if (cb == NULL) + return REDIS_ERR_OOM; + + if (source != NULL) { + memcpy(cb,source,sizeof(*cb)); + cb->next = NULL; + } + + /* Store callback in list */ + if (list->head == NULL) + list->head = cb; + if (list->tail != NULL) + list->tail->next = cb; + list->tail = cb; + return REDIS_OK; +} + +static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { + redisCallback *cb = list->head; + if (cb != NULL) { + list->head = cb->next; + if (cb == list->tail) + list->tail = NULL; + + /* Copy callback from heap to stack */ + if (target != NULL) + memcpy(target,cb,sizeof(*cb)); + free(cb); + return REDIS_OK; + } + return REDIS_ERR; +} + +static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { + redisContext *c = &(ac->c); + if (cb->fn != NULL) { + c->flags |= REDIS_IN_CALLBACK; + cb->fn(ac,reply,cb->privdata); + c->flags &= ~REDIS_IN_CALLBACK; + } +} + +/* Helper function to free the context. */ +static void __redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb; + dictIterator *it; + dictEntry *de; + + /* Execute pending callbacks with NULL reply. */ + while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Execute callbacks for invalid commands */ + while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Run subscription callbacks callbacks with NULL reply */ + it = dictGetIterator(ac->sub.channels); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.channels); + + it = dictGetIterator(ac->sub.patterns); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.patterns); + + /* Signal event lib to clean up */ + _EL_CLEANUP(ac); + + /* Execute disconnect callback. When redisAsyncFree() initiated destroying + * this context, the status will always be REDIS_OK. */ + if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { + if (c->flags & REDIS_FREEING) { + ac->onDisconnect(ac,REDIS_OK); + } else { + ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); + } + } + + /* Cleanup self */ + redisFree(c); +} + +/* Free the async context. When this function is called from a callback, + * control needs to be returned to redisProcessCallbacks() before actual + * free'ing. To do so, a flag is set on the context which is picked up by + * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ +void redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_FREEING; + if (!(c->flags & REDIS_IN_CALLBACK)) + __redisAsyncFree(ac); +} + +/* Helper function to make the disconnect happen and clean up. */ +static void __redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + /* Make sure error is accessible if there is any */ + __redisAsyncCopyError(ac); + + if (ac->err == 0) { + /* For clean disconnects, there should be no pending callbacks. */ + int ret = __redisShiftCallback(&ac->replies,NULL); + assert(ret == REDIS_ERR); + } else { + /* Disconnection is caused by an error, make sure that pending + * callbacks cannot call new commands. */ + c->flags |= REDIS_DISCONNECTING; + } + + /* For non-clean disconnects, __redisAsyncFree() will execute pending + * callbacks with a NULL-reply. */ + __redisAsyncFree(ac); +} + +/* Tries to do a clean disconnect from Redis, meaning it stops new commands + * from being issued, but tries to flush the output buffer and execute + * callbacks for all remaining replies. When this function is called from a + * callback, there might be more replies and we can safely defer disconnecting + * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately + * when there are no pending callbacks. */ +void redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_DISCONNECTING; + if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) + __redisAsyncDisconnect(ac); +} + +static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { + redisContext *c = &(ac->c); + dict *callbacks; + dictEntry *de; + int pvariant; + char *stype; + sds sname; + + /* Custom reply functions are not supported for pub/sub. This will fail + * very hard when they are used... */ + if (reply->type == REDIS_REPLY_ARRAY) { + assert(reply->elements >= 2); + assert(reply->element[0]->type == REDIS_REPLY_STRING); + stype = reply->element[0]->str; + pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; + + if (pvariant) + callbacks = ac->sub.patterns; + else + callbacks = ac->sub.channels; + + /* Locate the right callback */ + assert(reply->element[1]->type == REDIS_REPLY_STRING); + sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); + de = dictFind(callbacks,sname); + if (de != NULL) { + memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); + + /* If this is an unsubscribe message, remove it. */ + if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { + dictDelete(callbacks,sname); + + /* If this was the last unsubscribe message, revert to + * non-subscribe mode. */ + assert(reply->element[2]->type == REDIS_REPLY_INTEGER); + if (reply->element[2]->integer == 0) + c->flags &= ~REDIS_SUBSCRIBED; + } + } + sdsfree(sname); + } else { + /* Shift callback for invalid commands. */ + __redisShiftCallback(&ac->sub.invalid,dstcb); + } + return REDIS_OK; +} + +void redisProcessCallbacks(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb = {NULL, NULL, NULL}; + void *reply = NULL; + int status; + + while((status = redisGetReply(c,&reply)) == REDIS_OK) { + if (reply == NULL) { + /* When the connection is being disconnected and there are + * no more replies, this is the cue to really disconnect. */ + if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0 + && ac->replies.head == NULL) { + __redisAsyncDisconnect(ac); + return; + } + + /* If monitor mode, repush callback */ + if(c->flags & REDIS_MONITORING) { + __redisPushCallback(&ac->replies,&cb); + } + + /* When the connection is not being disconnected, simply stop + * trying to get replies and wait for the next loop tick. */ + break; + } + + /* Even if the context is subscribed, pending regular callbacks will + * get a reply before pub/sub messages arrive. */ + if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { + /* + * A spontaneous reply in a not-subscribed context can be the error + * reply that is sent when a new connection exceeds the maximum + * number of allowed connections on the server side. + * + * This is seen as an error instead of a regular reply because the + * server closes the connection after sending it. + * + * To prevent the error from being overwritten by an EOF error the + * connection is closed here. See issue #43. + * + * Another possibility is that the server is loading its dataset. + * In this case we also want to close the connection, and have the + * user wait until the server is ready to take our request. + */ + if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { + c->err = REDIS_ERR_OTHER; + snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); + c->reader->fn->freeObject(reply); + __redisAsyncDisconnect(ac); + return; + } + /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ + assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); + if(c->flags & REDIS_SUBSCRIBED) + __redisGetSubscribeCallback(ac,reply,&cb); + } + + if (cb.fn != NULL) { + __redisRunCallback(ac,&cb,reply); + c->reader->fn->freeObject(reply); + + /* Proceed with free'ing when redisAsyncFree() was called. */ + if (c->flags & REDIS_FREEING) { + __redisAsyncFree(ac); + return; + } + } else { + /* No callback for this reply. This can either be a NULL callback, + * or there were no callbacks to begin with. Either way, don't + * abort with an error, but simply ignore it because the client + * doesn't know what the server will spit out over the wire. */ + c->reader->fn->freeObject(reply); + } + } + + /* Disconnect when there was an error reading the reply */ + if (status != REDIS_OK) + __redisAsyncDisconnect(ac); +} + +/* Internal helper function to detect socket status the first time a read or + * write event fires. When connecting was not successful, the connect callback + * is called with a REDIS_ERR status and the context is free'd. */ +static int __redisAsyncHandleConnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (redisCheckSocketError(c) == REDIS_ERR) { + /* Try again later when connect(2) is still in progress. */ + if (errno == EINPROGRESS) + return REDIS_OK; + + if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); + __redisAsyncDisconnect(ac); + return REDIS_ERR; + } + + /* Mark context as connected. */ + c->flags |= REDIS_CONNECTED; + if (ac->onConnect) ac->onConnect(ac,REDIS_OK); + return REDIS_OK; +} + +/* This function should be called when the socket is readable. + * It processes all replies that can be read and executes their callbacks. + */ +void redisAsyncHandleRead(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferRead(c) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Always re-schedule reads */ + _EL_ADD_READ(ac); + redisProcessCallbacks(ac); + } +} + +void redisAsyncHandleWrite(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + int done = 0; + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferWrite(c,&done) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Continue writing when not done, stop writing otherwise */ + if (!done) + _EL_ADD_WRITE(ac); + else + _EL_DEL_WRITE(ac); + + /* Always schedule reads after writes */ + _EL_ADD_READ(ac); + } +} + +/* Sets a pointer to the first argument and its length starting at p. Returns + * the number of bytes to skip to get to the following argument. */ +static const char *nextArgument(const char *start, const char **str, size_t *len) { + const char *p = start; + if (p[0] != '$') { + p = strchr(p,'$'); + if (p == NULL) return NULL; + } + + *len = (int)strtol(p+1,NULL,10); + p = strchr(p,'\r'); + assert(p); + *str = p+2; + return p+2+(*len)+2; +} + +/* Helper function for the redisAsyncCommand* family of functions. Writes a + * formatted command to the output buffer and registers the provided callback + * function with the context. */ +static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { + redisContext *c = &(ac->c); + redisCallback cb; + int pvariant, hasnext; + const char *cstr, *astr; + size_t clen, alen; + const char *p; + sds sname; + int ret; + + /* Don't accept new commands when the connection is about to be closed. */ + if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; + + /* Setup callback */ + cb.fn = fn; + cb.privdata = privdata; + + /* Find out which command will be appended. */ + p = nextArgument(cmd,&cstr,&clen); + assert(p != NULL); + hasnext = (p[0] == '$'); + pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; + cstr += pvariant; + clen -= pvariant; + + if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { + c->flags |= REDIS_SUBSCRIBED; + + /* Add every channel/pattern to the list of subscription callbacks. */ + while ((p = nextArgument(p,&astr,&alen)) != NULL) { + sname = sdsnewlen(astr,alen); + if (pvariant) + ret = dictReplace(ac->sub.patterns,sname,&cb); + else + ret = dictReplace(ac->sub.channels,sname,&cb); + + if (ret == 0) sdsfree(sname); + } + } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { + /* It is only useful to call (P)UNSUBSCRIBE when the context is + * subscribed to one or more channels or patterns. */ + if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; + + /* (P)UNSUBSCRIBE does not have its own response: every channel or + * pattern that is unsubscribed will receive a message. This means we + * should not append a callback function for this command. */ + } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { + /* Set monitor flag and push callback */ + c->flags |= REDIS_MONITORING; + __redisPushCallback(&ac->replies,&cb); + } else { + if (c->flags & REDIS_SUBSCRIBED) + /* This will likely result in an error reply, but it needs to be + * received and passed to the callback. */ + __redisPushCallback(&ac->sub.invalid,&cb); + else + __redisPushCallback(&ac->replies,&cb); + } + + __redisAppendCommand(c,cmd,len); + + /* Always schedule a write when the write buffer is non-empty */ + _EL_ADD_WRITE(ac); + + return REDIS_OK; +} + +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { + char *cmd; + int len; + int status; + len = redisvFormatCommand(&cmd,format,ap); + + /* We don't want to pass -1 or -2 to future functions as a length. */ + if (len < 0) + return REDIS_ERR; + + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + free(cmd); + return status; +} + +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { + va_list ap; + int status; + va_start(ap,format); + status = redisvAsyncCommand(ac,fn,privdata,format,ap); + va_end(ap); + return status; +} + +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { + sds cmd; + int len; + int status; + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); + if (len < 0) + return REDIS_ERR; + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + sdsfree(cmd); + return status; +} + +int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { + int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + return status; +} diff --git a/lib/hiredis/async.h b/lib/hiredis/async.h new file mode 100755 index 0000000..59cbf46 --- /dev/null +++ b/lib/hiredis/async.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 __HIREDIS_ASYNC_H +#define __HIREDIS_ASYNC_H +#include "hiredis.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ +struct dict; /* dictionary header is included in async.c */ + +/* Reply callback prototype and container */ +typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); +typedef struct redisCallback { + struct redisCallback *next; /* simple singly linked list */ + redisCallbackFn *fn; + void *privdata; +} redisCallback; + +/* List of callbacks for either regular replies or pub/sub */ +typedef struct redisCallbackList { + redisCallback *head, *tail; +} redisCallbackList; + +/* Connection callback prototypes */ +typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); +typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); + +/* Context for an async connection to Redis */ +typedef struct redisAsyncContext { + /* Hold the regular context, so it can be realloc'ed. */ + redisContext c; + + /* Setup error flags so they can be used directly. */ + int err; + char *errstr; + + /* Not used by hiredis */ + void *data; + + /* Event library data and hooks */ + struct { + void *data; + + /* Hooks that are called when the library expects to start + * reading/writing. These functions should be idempotent. */ + void (*addRead)(void *privdata); + void (*delRead)(void *privdata); + void (*addWrite)(void *privdata); + void (*delWrite)(void *privdata); + void (*cleanup)(void *privdata); + } ev; + + /* Called when either the connection is terminated due to an error or per + * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ + redisDisconnectCallback *onDisconnect; + + /* Called when the first write event was received. */ + redisConnectCallback *onConnect; + + /* Regular command callbacks */ + redisCallbackList replies; + + /* Subscription callbacks */ + struct { + redisCallbackList invalid; + struct dict *channels; + struct dict *patterns; + } sub; +} redisAsyncContext; + +/* Functions that proxy to hiredis */ +redisAsyncContext *redisAsyncConnect(const char *ip, int port); +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr); +redisAsyncContext *redisAsyncConnectUnix(const char *path); +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +void redisAsyncDisconnect(redisAsyncContext *ac); +void redisAsyncFree(redisAsyncContext *ac); + +/* Handle read/write events */ +void redisAsyncHandleRead(redisAsyncContext *ac); +void redisAsyncHandleWrite(redisAsyncContext *ac); + +/* Command functions for an async context. Write the command to the + * output buffer and register the provided callback. */ +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); +int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/hiredis/dict.c b/lib/hiredis/dict.c new file mode 100755 index 0000000..e17a625 --- /dev/null +++ b/lib/hiredis/dict.c @@ -0,0 +1,338 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, 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 "fmacros.h" +#include +#include +#include +#include "dict.h" + +/* -------------------------- private prototypes ---------------------------- */ + +static int _dictExpandIfNeeded(dict *ht); +static unsigned long _dictNextPower(unsigned long size); +static int _dictKeyIndex(dict *ht, const void *key); +static int _dictInit(dict *ht, dictType *type, void *privDataPtr); + +/* -------------------------- hash functions -------------------------------- */ + +/* Generic hash function (a popular one from Bernstein). + * I tested a few and this was the best. */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { + unsigned int hash = 5381; + + while (len--) + hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ + return hash; +} + +/* ----------------------------- API implementation ------------------------- */ + +/* Reset an hashtable already initialized with ht_init(). + * NOTE: This function should only called by ht_destroy(). */ +static void _dictReset(dict *ht) { + ht->table = NULL; + ht->size = 0; + ht->sizemask = 0; + ht->used = 0; +} + +/* Create a new hash table */ +static dict *dictCreate(dictType *type, void *privDataPtr) { + dict *ht = malloc(sizeof(*ht)); + _dictInit(ht,type,privDataPtr); + return ht; +} + +/* Initialize the hash table */ +static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { + _dictReset(ht); + ht->type = type; + ht->privdata = privDataPtr; + return DICT_OK; +} + +/* Expand or create the hashtable */ +static int dictExpand(dict *ht, unsigned long size) { + dict n; /* the new hashtable */ + unsigned long realsize = _dictNextPower(size), i; + + /* the size is invalid if it is smaller than the number of + * elements already inside the hashtable */ + if (ht->used > size) + return DICT_ERR; + + _dictInit(&n, ht->type, ht->privdata); + n.size = realsize; + n.sizemask = realsize-1; + n.table = calloc(realsize,sizeof(dictEntry*)); + + /* Copy all the elements from the old to the new table: + * note that if the old hash table is empty ht->size is zero, + * so dictExpand just creates an hash table. */ + n.used = ht->used; + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if (ht->table[i] == NULL) continue; + + /* For each hash entry on this slot... */ + he = ht->table[i]; + while(he) { + unsigned int h; + + nextHe = he->next; + /* Get the new element index */ + h = dictHashKey(ht, he->key) & n.sizemask; + he->next = n.table[h]; + n.table[h] = he; + ht->used--; + /* Pass to the next element */ + he = nextHe; + } + } + assert(ht->used == 0); + free(ht->table); + + /* Remap the new hashtable in the old */ + *ht = n; + return DICT_OK; +} + +/* Add an element to the target hash table */ +static int dictAdd(dict *ht, void *key, void *val) { + int index; + dictEntry *entry; + + /* Get the index of the new element, or -1 if + * the element already exists. */ + if ((index = _dictKeyIndex(ht, key)) == -1) + return DICT_ERR; + + /* Allocates the memory and stores key */ + entry = malloc(sizeof(*entry)); + entry->next = ht->table[index]; + ht->table[index] = entry; + + /* Set the hash entry fields. */ + dictSetHashKey(ht, entry, key); + dictSetHashVal(ht, entry, val); + ht->used++; + return DICT_OK; +} + +/* Add an element, discarding the old if the key already exists. + * Return 1 if the key was added from scratch, 0 if there was already an + * element with such key and dictReplace() just performed a value update + * operation. */ +static int dictReplace(dict *ht, void *key, void *val) { + dictEntry *entry, auxentry; + + /* Try to add the element. If the key + * does not exists dictAdd will succeed. */ + if (dictAdd(ht, key, val) == DICT_OK) + return 1; + /* It already exists, get the entry */ + entry = dictFind(ht, key); + /* Free the old value and set the new one */ + /* Set the new value and free the old one. Note that it is important + * to do that in this order, as the value may just be exactly the same + * as the previous one. In this context, think to reference counting, + * you want to increment (set), and then decrement (free), and not the + * reverse. */ + auxentry = *entry; + dictSetHashVal(ht, entry, val); + dictFreeEntryVal(ht, &auxentry); + return 0; +} + +/* Search and remove an element */ +static int dictDelete(dict *ht, const void *key) { + unsigned int h; + dictEntry *de, *prevde; + + if (ht->size == 0) + return DICT_ERR; + h = dictHashKey(ht, key) & ht->sizemask; + de = ht->table[h]; + + prevde = NULL; + while(de) { + if (dictCompareHashKeys(ht,key,de->key)) { + /* Unlink the element from the list */ + if (prevde) + prevde->next = de->next; + else + ht->table[h] = de->next; + + dictFreeEntryKey(ht,de); + dictFreeEntryVal(ht,de); + free(de); + ht->used--; + return DICT_OK; + } + prevde = de; + de = de->next; + } + return DICT_ERR; /* not found */ +} + +/* Destroy an entire hash table */ +static int _dictClear(dict *ht) { + unsigned long i; + + /* Free all the elements */ + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if ((he = ht->table[i]) == NULL) continue; + while(he) { + nextHe = he->next; + dictFreeEntryKey(ht, he); + dictFreeEntryVal(ht, he); + free(he); + ht->used--; + he = nextHe; + } + } + /* Free the table and the allocated cache structure */ + free(ht->table); + /* Re-initialize the table */ + _dictReset(ht); + return DICT_OK; /* never fails */ +} + +/* Clear & Release the hash table */ +static void dictRelease(dict *ht) { + _dictClear(ht); + free(ht); +} + +static dictEntry *dictFind(dict *ht, const void *key) { + dictEntry *he; + unsigned int h; + + if (ht->size == 0) return NULL; + h = dictHashKey(ht, key) & ht->sizemask; + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return he; + he = he->next; + } + return NULL; +} + +static dictIterator *dictGetIterator(dict *ht) { + dictIterator *iter = malloc(sizeof(*iter)); + + iter->ht = ht; + iter->index = -1; + iter->entry = NULL; + iter->nextEntry = NULL; + return iter; +} + +static dictEntry *dictNext(dictIterator *iter) { + while (1) { + if (iter->entry == NULL) { + iter->index++; + if (iter->index >= + (signed)iter->ht->size) break; + iter->entry = iter->ht->table[iter->index]; + } else { + iter->entry = iter->nextEntry; + } + if (iter->entry) { + /* We need to save the 'next' here, the iterator user + * may delete the entry we are returning. */ + iter->nextEntry = iter->entry->next; + return iter->entry; + } + } + return NULL; +} + +static void dictReleaseIterator(dictIterator *iter) { + free(iter); +} + +/* ------------------------- private functions ------------------------------ */ + +/* Expand the hash table if needed */ +static int _dictExpandIfNeeded(dict *ht) { + /* If the hash table is empty expand it to the initial size, + * if the table is "full" dobule its size. */ + if (ht->size == 0) + return dictExpand(ht, DICT_HT_INITIAL_SIZE); + if (ht->used == ht->size) + return dictExpand(ht, ht->size*2); + return DICT_OK; +} + +/* Our hash table capability is a power of two */ +static unsigned long _dictNextPower(unsigned long size) { + unsigned long i = DICT_HT_INITIAL_SIZE; + + if (size >= LONG_MAX) return LONG_MAX; + while(1) { + if (i >= size) + return i; + i *= 2; + } +} + +/* Returns the index of a free slot that can be populated with + * an hash entry for the given 'key'. + * If the key already exists, -1 is returned. */ +static int _dictKeyIndex(dict *ht, const void *key) { + unsigned int h; + dictEntry *he; + + /* Expand the hashtable if needed */ + if (_dictExpandIfNeeded(ht) == DICT_ERR) + return -1; + /* Compute the key hash value */ + h = dictHashKey(ht, key) & ht->sizemask; + /* Search if this slot does not already contain the given key */ + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return -1; + he = he->next; + } + return h; +} + diff --git a/lib/hiredis/dict.h b/lib/hiredis/dict.h new file mode 100755 index 0000000..95fcd28 --- /dev/null +++ b/lib/hiredis/dict.h @@ -0,0 +1,126 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, 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 __DICT_H +#define __DICT_H + +#define DICT_OK 0 +#define DICT_ERR 1 + +/* Unused arguments generate annoying warnings... */ +#define DICT_NOTUSED(V) ((void) V) + +typedef struct dictEntry { + void *key; + void *val; + struct dictEntry *next; +} dictEntry; + +typedef struct dictType { + unsigned int (*hashFunction)(const void *key); + void *(*keyDup)(void *privdata, const void *key); + void *(*valDup)(void *privdata, const void *obj); + int (*keyCompare)(void *privdata, const void *key1, const void *key2); + void (*keyDestructor)(void *privdata, void *key); + void (*valDestructor)(void *privdata, void *obj); +} dictType; + +typedef struct dict { + dictEntry **table; + dictType *type; + unsigned long size; + unsigned long sizemask; + unsigned long used; + void *privdata; +} dict; + +typedef struct dictIterator { + dict *ht; + int index; + dictEntry *entry, *nextEntry; +} dictIterator; + +/* This is the initial size of every hash table */ +#define DICT_HT_INITIAL_SIZE 4 + +/* ------------------------------- Macros ------------------------------------*/ +#define dictFreeEntryVal(ht, entry) \ + if ((ht)->type->valDestructor) \ + (ht)->type->valDestructor((ht)->privdata, (entry)->val) + +#define dictSetHashVal(ht, entry, _val_) do { \ + if ((ht)->type->valDup) \ + entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ + else \ + entry->val = (_val_); \ +} while(0) + +#define dictFreeEntryKey(ht, entry) \ + if ((ht)->type->keyDestructor) \ + (ht)->type->keyDestructor((ht)->privdata, (entry)->key) + +#define dictSetHashKey(ht, entry, _key_) do { \ + if ((ht)->type->keyDup) \ + entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ + else \ + entry->key = (_key_); \ +} while(0) + +#define dictCompareHashKeys(ht, key1, key2) \ + (((ht)->type->keyCompare) ? \ + (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ + (key1) == (key2)) + +#define dictHashKey(ht, key) (ht)->type->hashFunction(key) + +#define dictGetEntryKey(he) ((he)->key) +#define dictGetEntryVal(he) ((he)->val) +#define dictSlots(ht) ((ht)->size) +#define dictSize(ht) ((ht)->used) + +/* API */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len); +static dict *dictCreate(dictType *type, void *privDataPtr); +static int dictExpand(dict *ht, unsigned long size); +static int dictAdd(dict *ht, void *key, void *val); +static int dictReplace(dict *ht, void *key, void *val); +static int dictDelete(dict *ht, const void *key); +static void dictRelease(dict *ht); +static dictEntry * dictFind(dict *ht, const void *key); +static dictIterator *dictGetIterator(dict *ht); +static dictEntry *dictNext(dictIterator *iter); +static void dictReleaseIterator(dictIterator *iter); + +#endif /* __DICT_H */ diff --git a/lib/hiredis/examples/example-ae.c b/lib/hiredis/examples/example-ae.c new file mode 100755 index 0000000..8efa730 --- /dev/null +++ b/lib/hiredis/examples/example-ae.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +#include +#include +#include + +/* Put event loop in the global scope, so it can be explicitly stopped */ +static aeEventLoop *loop; + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + aeStop(loop); + return; + } + + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + aeStop(loop); + return; + } + + printf("Disconnected...\n"); + aeStop(loop); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + loop = aeCreateEventLoop(64); + redisAeAttach(loop, c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + aeMain(loop); + return 0; +} + diff --git a/lib/hiredis/examples/example-glib.c b/lib/hiredis/examples/example-glib.c new file mode 100755 index 0000000..d6e10f8 --- /dev/null +++ b/lib/hiredis/examples/example-glib.c @@ -0,0 +1,73 @@ +#include + +#include +#include +#include + +static GMainLoop *mainloop; + +static void +connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, + int status) +{ + if (status != REDIS_OK) { + g_printerr("Failed to connect: %s\n", ac->errstr); + g_main_loop_quit(mainloop); + } else { + g_printerr("Connected...\n"); + } +} + +static void +disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, + int status) +{ + if (status != REDIS_OK) { + g_error("Failed to disconnect: %s", ac->errstr); + } else { + g_printerr("Disconnected...\n"); + g_main_loop_quit(mainloop); + } +} + +static void +command_cb(redisAsyncContext *ac, + gpointer r, + gpointer user_data G_GNUC_UNUSED) +{ + redisReply *reply = r; + + if (reply) { + g_print("REPLY: %s\n", reply->str); + } + + redisAsyncDisconnect(ac); +} + +gint +main (gint argc G_GNUC_UNUSED, + gchar *argv[] G_GNUC_UNUSED) +{ + redisAsyncContext *ac; + GMainContext *context = NULL; + GSource *source; + + ac = redisAsyncConnect("127.0.0.1", 6379); + if (ac->err) { + g_printerr("%s\n", ac->errstr); + exit(EXIT_FAILURE); + } + + source = redis_source_new(ac); + mainloop = g_main_loop_new(context, FALSE); + g_source_attach(source, context); + + redisAsyncSetConnectCallback(ac, connect_cb); + redisAsyncSetDisconnectCallback(ac, disconnect_cb); + redisAsyncCommand(ac, command_cb, NULL, "SET key 1234"); + redisAsyncCommand(ac, command_cb, NULL, "GET key"); + + g_main_loop_run(mainloop); + + return EXIT_SUCCESS; +} diff --git a/lib/hiredis/examples/example-ivykis.c b/lib/hiredis/examples/example-ivykis.c new file mode 100755 index 0000000..67affce --- /dev/null +++ b/lib/hiredis/examples/example-ivykis.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + iv_init(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisIvykisAttach(c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + + iv_main(); + + iv_deinit(); + + return 0; +} diff --git a/lib/hiredis/examples/example-libev.c b/lib/hiredis/examples/example-libev.c new file mode 100755 index 0000000..cc8b166 --- /dev/null +++ b/lib/hiredis/examples/example-libev.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibevAttach(EV_DEFAULT_ c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + ev_loop(EV_DEFAULT_ 0); + return 0; +} diff --git a/lib/hiredis/examples/example-libevent.c b/lib/hiredis/examples/example-libevent.c new file mode 100755 index 0000000..d333c22 --- /dev/null +++ b/lib/hiredis/examples/example-libevent.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + struct event_base *base = event_base_new(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibeventAttach(c,base); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + event_base_dispatch(base); + return 0; +} diff --git a/lib/hiredis/examples/example-libuv.c b/lib/hiredis/examples/example-libuv.c new file mode 100755 index 0000000..a5462d4 --- /dev/null +++ b/lib/hiredis/examples/example-libuv.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + uv_loop_t* loop = uv_default_loop(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibuvAttach(c,loop); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + uv_run(loop, UV_RUN_DEFAULT); + return 0; +} diff --git a/lib/hiredis/examples/example-macosx.c b/lib/hiredis/examples/example-macosx.c new file mode 100755 index 0000000..bc84ed5 --- /dev/null +++ b/lib/hiredis/examples/example-macosx.c @@ -0,0 +1,66 @@ +// +// Created by Дмитрий Бахвалов on 13.07.15. +// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. +// + +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + CFRunLoopStop(CFRunLoopGetCurrent()); + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + CFRunLoopRef loop = CFRunLoopGetCurrent(); + if( !loop ) { + printf("Error: Cannot get current run loop\n"); + return 1; + } + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisMacOSAttach(c, loop); + + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + + CFRunLoopRun(); + + return 0; +} + diff --git a/lib/hiredis/examples/example-qt.cpp b/lib/hiredis/examples/example-qt.cpp new file mode 100755 index 0000000..f524c3f --- /dev/null +++ b/lib/hiredis/examples/example-qt.cpp @@ -0,0 +1,46 @@ +#include +using namespace std; + +#include +#include + +#include "example-qt.h" + +void getCallback(redisAsyncContext *, void * r, void * privdata) { + + redisReply * reply = static_cast(r); + ExampleQt * ex = static_cast(privdata); + if (reply == nullptr || ex == nullptr) return; + + cout << "key: " << reply->str << endl; + + ex->finish(); +} + +void ExampleQt::run() { + + m_ctx = redisAsyncConnect("localhost", 6379); + + if (m_ctx->err) { + cerr << "Error: " << m_ctx->errstr << endl; + redisAsyncFree(m_ctx); + emit finished(); + } + + m_adapter.setContext(m_ctx); + + redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value); + redisAsyncCommand(m_ctx, getCallback, this, "GET key"); +} + +int main (int argc, char **argv) { + + QCoreApplication app(argc, argv); + + ExampleQt example(argv[argc-1]); + + QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit())); + QTimer::singleShot(0, &example, SLOT(run())); + + return app.exec(); +} diff --git a/lib/hiredis/examples/example-qt.h b/lib/hiredis/examples/example-qt.h new file mode 100755 index 0000000..374f476 --- /dev/null +++ b/lib/hiredis/examples/example-qt.h @@ -0,0 +1,32 @@ +#ifndef __HIREDIS_EXAMPLE_QT_H +#define __HIREDIS_EXAMPLE_QT_H + +#include + +class ExampleQt : public QObject { + + Q_OBJECT + + public: + ExampleQt(const char * value, QObject * parent = 0) + : QObject(parent), m_value(value) {} + + signals: + void finished(); + + public slots: + void run(); + + private: + void finish() { emit finished(); } + + private: + const char * m_value; + redisAsyncContext * m_ctx; + RedisQtAdapter m_adapter; + + friend + void getCallback(redisAsyncContext *, void *, void *); +}; + +#endif /* !__HIREDIS_EXAMPLE_QT_H */ diff --git a/lib/hiredis/examples/example.c b/lib/hiredis/examples/example.c new file mode 100755 index 0000000..4d494c5 --- /dev/null +++ b/lib/hiredis/examples/example.c @@ -0,0 +1,78 @@ +#include +#include +#include + +#include + +int main(int argc, char **argv) { + unsigned int j; + redisContext *c; + redisReply *reply; + const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; + int port = (argc > 2) ? atoi(argv[2]) : 6379; + + struct timeval timeout = { 1, 500000 }; // 1.5 seconds + c = redisConnectWithTimeout(hostname, port, timeout); + if (c == NULL || c->err) { + if (c) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + } else { + printf("Connection error: can't allocate redis context\n"); + } + exit(1); + } + + /* PING server */ + reply = redisCommand(c,"PING"); + printf("PING: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key */ + reply = redisCommand(c,"SET %s %s", "foo", "hello world"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key using binary safe API */ + reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); + printf("SET (binary API): %s\n", reply->str); + freeReplyObject(reply); + + /* Try a GET and two INCR */ + reply = redisCommand(c,"GET foo"); + printf("GET foo: %s\n", reply->str); + freeReplyObject(reply); + + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + /* again ... */ + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + + /* Create a list of numbers, from 0 to 9 */ + reply = redisCommand(c,"DEL mylist"); + freeReplyObject(reply); + for (j = 0; j < 10; j++) { + char buf[64]; + + snprintf(buf,64,"%u",j); + reply = redisCommand(c,"LPUSH mylist element-%s", buf); + freeReplyObject(reply); + } + + /* Let's check what we have inside the list */ + reply = redisCommand(c,"LRANGE mylist 0 -1"); + if (reply->type == REDIS_REPLY_ARRAY) { + for (j = 0; j < reply->elements; j++) { + printf("%u) %s\n", j, reply->element[j]->str); + } + } + freeReplyObject(reply); + + /* Disconnects and frees the context */ + redisFree(c); + + return 0; +} diff --git a/lib/hiredis/fmacros.h b/lib/hiredis/fmacros.h new file mode 100755 index 0000000..4cdbc13 --- /dev/null +++ b/lib/hiredis/fmacros.h @@ -0,0 +1,38 @@ +#ifndef __HIREDIS_FMACRO_H +#define __HIREDIS_FMACRO_H + +#if defined(__linux__) +#define _BSD_SOURCE +#define _DEFAULT_SOURCE +#endif + +#if defined(__CYGWIN__) +#include +#endif + +#if defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__) +#define _XOPEN_SOURCE 600 +#elif defined(__APPLE__) && defined(__MACH__) +#define _XOPEN_SOURCE +#elif defined(__FreeBSD__) +// intentionally left blank, don't define _XOPEN_SOURCE +#elif defined(AIX) +// intentionally left blank, don't define _XOPEN_SOURCE +#else +#define _XOPEN_SOURCE +#endif + +#if defined(__sun__) +#define _POSIX_C_SOURCE 200112L +#endif + +#if defined(__APPLE__) && defined(__MACH__) +#define _OSX +#endif + +#ifndef AIX +# define _XOPEN_SOURCE_EXTENDED 1 +# define _ALL_SOURCE +#endif + +#endif diff --git a/lib/hiredis/hiredis.c b/lib/hiredis/hiredis.c new file mode 100755 index 0000000..b344962 --- /dev/null +++ b/lib/hiredis/hiredis.c @@ -0,0 +1,1024 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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 "fmacros.h" +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "net.h" +#include "sds.h" + +static redisReply *createReplyObject(int type); +static void *createStringObject(const redisReadTask *task, char *str, size_t len); +static void *createArrayObject(const redisReadTask *task, int elements); +static void *createIntegerObject(const redisReadTask *task, long long value); +static void *createNilObject(const redisReadTask *task); + +/* Default set of functions to build the reply. Keep in mind that such a + * function returning NULL is interpreted as OOM. */ +static redisReplyObjectFunctions defaultFunctions = { + createStringObject, + createArrayObject, + createIntegerObject, + createNilObject, + freeReplyObject +}; + +/* Create a reply object */ +static redisReply *createReplyObject(int type) { + redisReply *r = calloc(1,sizeof(*r)); + + if (r == NULL) + return NULL; + + r->type = type; + return r; +} + +/* Free a reply object */ +void freeReplyObject(void *reply) { + redisReply *r = reply; + size_t j; + + if (r == NULL) + return; + + switch(r->type) { + case REDIS_REPLY_INTEGER: + break; /* Nothing to free */ + case REDIS_REPLY_ARRAY: + if (r->element != NULL) { + for (j = 0; j < r->elements; j++) + if (r->element[j] != NULL) + freeReplyObject(r->element[j]); + free(r->element); + } + break; + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_STRING: + if (r->str != NULL) + free(r->str); + break; + } + free(r); +} + +static void *createStringObject(const redisReadTask *task, char *str, size_t len) { + redisReply *r, *parent; + char *buf; + + r = createReplyObject(task->type); + if (r == NULL) + return NULL; + + buf = malloc(len+1); + if (buf == NULL) { + freeReplyObject(r); + return NULL; + } + + assert(task->type == REDIS_REPLY_ERROR || + task->type == REDIS_REPLY_STATUS || + task->type == REDIS_REPLY_STRING); + + /* Copy string value */ + memcpy(buf,str,len); + buf[len] = '\0'; + r->str = buf; + r->len = len; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createArrayObject(const redisReadTask *task, int elements) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_ARRAY); + if (r == NULL) + return NULL; + + if (elements > 0) { + r->element = calloc(elements,sizeof(redisReply*)); + if (r->element == NULL) { + freeReplyObject(r); + return NULL; + } + } + + r->elements = elements; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createIntegerObject(const redisReadTask *task, long long value) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_INTEGER); + if (r == NULL) + return NULL; + + r->integer = value; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createNilObject(const redisReadTask *task) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_NIL); + if (r == NULL) + return NULL; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +/* Return the number of digits of 'v' when converted to string in radix 10. + * Implementation borrowed from link in redis/src/util.c:string2ll(). */ +static uint32_t countDigits(uint64_t v) { + uint32_t result = 1; + for (;;) { + if (v < 10) return result; + if (v < 100) return result + 1; + if (v < 1000) return result + 2; + if (v < 10000) return result + 3; + v /= 10000U; + result += 4; + } +} + +/* Helper that calculates the bulk length given a certain string length. */ +static size_t bulklen(size_t len) { + return 1+countDigits(len)+2+len+2; +} + +int redisvFormatCommand(char **target, const char *format, va_list ap) { + const char *c = format; + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + sds curarg, newarg; /* current argument */ + int touched = 0; /* was the current argument touched? */ + char **curargv = NULL, **newargv = NULL; + int argc = 0; + int totlen = 0; + int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ + int j; + + /* Abort if there is not target to set */ + if (target == NULL) + return -1; + + /* Build the command string accordingly to protocol */ + curarg = sdsempty(); + if (curarg == NULL) + return -1; + + while(*c != '\0') { + if (*c != '%' || c[1] == '\0') { + if (*c == ' ') { + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto memory_err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + + /* curarg is put in argv so it can be overwritten. */ + curarg = sdsempty(); + if (curarg == NULL) goto memory_err; + touched = 0; + } + } else { + newarg = sdscatlen(curarg,c,1); + if (newarg == NULL) goto memory_err; + curarg = newarg; + touched = 1; + } + } else { + char *arg; + size_t size; + + /* Set newarg so it can be checked even if it is not touched. */ + newarg = curarg; + + switch(c[1]) { + case 's': + arg = va_arg(ap,char*); + size = strlen(arg); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case 'b': + arg = va_arg(ap,char*); + size = va_arg(ap,size_t); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case '%': + newarg = sdscat(curarg,"%"); + break; + default: + /* Try to detect printf format */ + { + static const char intfmts[] = "diouxX"; + static const char flags[] = "#0-+ "; + char _format[16]; + const char *_p = c+1; + size_t _l = 0; + va_list _cpy; + + /* Flags */ + while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; + + /* Field width */ + while (*_p != '\0' && isdigit(*_p)) _p++; + + /* Precision */ + if (*_p == '.') { + _p++; + while (*_p != '\0' && isdigit(*_p)) _p++; + } + + /* Copy va_list before consuming with va_arg */ + va_copy(_cpy,ap); + + /* Integer conversion (without modifiers) */ + if (strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); + goto fmt_valid; + } + + /* Double conversion (without modifiers) */ + if (strchr("eEfFgGaA",*_p) != NULL) { + va_arg(ap,double); + goto fmt_valid; + } + + /* Size: char */ + if (_p[0] == 'h' && _p[1] == 'h') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* char gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: short */ + if (_p[0] == 'h') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* short gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long long */ + if (_p[0] == 'l' && _p[1] == 'l') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long long); + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long */ + if (_p[0] == 'l') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long); + goto fmt_valid; + } + goto fmt_invalid; + } + + fmt_invalid: + va_end(_cpy); + goto format_err; + + fmt_valid: + _l = (_p+1)-c; + if (_l < sizeof(_format)-2) { + memcpy(_format,c,_l); + _format[_l] = '\0'; + newarg = sdscatvprintf(curarg,_format,_cpy); + + /* Update current position (note: outer blocks + * increment c twice so compensate here) */ + c = _p-1; + } + + va_end(_cpy); + break; + } + } + + if (newarg == NULL) goto memory_err; + curarg = newarg; + + touched = 1; + c++; + } + c++; + } + + /* Add the last argument if needed */ + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto memory_err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + } else { + sdsfree(curarg); + } + + /* Clear curarg because it was put in curargv or was free'd. */ + curarg = NULL; + + /* Add bytes needed to hold multi bulk count */ + totlen += 1+countDigits(argc)+2; + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) goto memory_err; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); + memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); + pos += sdslen(curargv[j]); + sdsfree(curargv[j]); + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + free(curargv); + *target = cmd; + return totlen; + +format_err: + error_type = -2; + goto cleanup; + +memory_err: + error_type = -1; + goto cleanup; + +cleanup: + if (curargv) { + while(argc--) + sdsfree(curargv[argc]); + free(curargv); + } + + sdsfree(curarg); + + /* No need to check cmd since it is the last statement that can fail, + * but do it anyway to be as defensive as possible. */ + if (cmd != NULL) + free(cmd); + + return error_type; +} + +/* Format a command according to the Redis protocol. This function + * takes a format similar to printf: + * + * %s represents a C null terminated string you want to interpolate + * %b represents a binary safe string + * + * When using %b you need to provide both the pointer to the string + * and the length in bytes as a size_t. Examples: + * + * len = redisFormatCommand(target, "GET %s", mykey); + * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); + */ +int redisFormatCommand(char **target, const char *format, ...) { + va_list ap; + int len; + va_start(ap,format); + len = redisvFormatCommand(target,format,ap); + va_end(ap); + + /* The API says "-1" means bad result, but we now also return "-2" in some + * cases. Force the return value to always be -1. */ + if (len < 0) + len = -1; + + return len; +} + +/* Format a command according to the Redis protocol using an sds string and + * sdscatfmt for the processing of arguments. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, + const size_t *argvlen) +{ + sds cmd; + unsigned long long totlen; + int j; + size_t len; + + /* Abort on a NULL target */ + if (target == NULL) + return -1; + + /* Calculate our total size */ + totlen = 1+countDigits(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Use an SDS string for command construction */ + cmd = sdsempty(); + if (cmd == NULL) + return -1; + + /* We already know how much storage we need */ + cmd = sdsMakeRoomFor(cmd, totlen); + if (cmd == NULL) + return -1; + + /* Construct command */ + cmd = sdscatfmt(cmd, "*%i\r\n", argc); + for (j=0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + cmd = sdscatfmt(cmd, "$%u\r\n", len); + cmd = sdscatlen(cmd, argv[j], len); + cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); + } + + assert(sdslen(cmd)==totlen); + + *target = cmd; + return totlen; +} + +void redisFreeSdsCommand(sds cmd) { + sdsfree(cmd); +} + +/* Format a command according to the Redis protocol. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + size_t len; + int totlen, j; + + /* Abort on a NULL target */ + if (target == NULL) + return -1; + + /* Calculate number of bytes needed for the command */ + totlen = 1+countDigits(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) + return -1; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + pos += sprintf(cmd+pos,"$%zu\r\n",len); + memcpy(cmd+pos,argv[j],len); + pos += len; + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + *target = cmd; + return totlen; +} + +void redisFreeCommand(char *cmd) { + free(cmd); +} + +void __redisSetError(redisContext *c, int type, const char *str) { + size_t len; + + c->err = type; + if (str != NULL) { + len = strlen(str); + len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); + memcpy(c->errstr,str,len); + c->errstr[len] = '\0'; + } else { + /* Only REDIS_ERR_IO may lack a description! */ + assert(type == REDIS_ERR_IO); + __redis_strerror_r(errno, c->errstr, sizeof(c->errstr)); + } +} + +redisReader *redisReaderCreate(void) { + return redisReaderCreateWithFunctions(&defaultFunctions); +} + +static redisContext *redisContextInit(void) { + redisContext *c; + + c = calloc(1,sizeof(redisContext)); + if (c == NULL) + return NULL; + + c->err = 0; + c->errstr[0] = '\0'; + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + c->tcp.host = NULL; + c->tcp.source_addr = NULL; + c->unix_sock.path = NULL; + c->timeout = NULL; + + if (c->obuf == NULL || c->reader == NULL) { + redisFree(c); + return NULL; + } + + return c; +} + +void redisFree(redisContext *c) { + if (c == NULL) + return; + if (c->fd > 0) + close(c->fd); + if (c->obuf != NULL) + sdsfree(c->obuf); + if (c->reader != NULL) + redisReaderFree(c->reader); + if (c->tcp.host) + free(c->tcp.host); + if (c->tcp.source_addr) + free(c->tcp.source_addr); + if (c->unix_sock.path) + free(c->unix_sock.path); + if (c->timeout) + free(c->timeout); + free(c); +} + +int redisFreeKeepFd(redisContext *c) { + int fd = c->fd; + c->fd = -1; + redisFree(c); + return fd; +} + +int redisReconnect(redisContext *c) { + c->err = 0; + memset(c->errstr, '\0', strlen(c->errstr)); + + if (c->fd > 0) { + close(c->fd); + } + + sdsfree(c->obuf); + redisReaderFree(c->reader); + + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + + if (c->connection_type == REDIS_CONN_TCP) { + return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, + c->timeout, c->tcp.source_addr); + } else if (c->connection_type == REDIS_CONN_UNIX) { + return redisContextConnectUnix(c, c->unix_sock.path, c->timeout); + } else { + /* Something bad happened here and shouldn't have. There isn't + enough information in the context to reconnect. */ + __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); + } + + return REDIS_ERR; +} + +/* Connect to a Redis instance. On error the field error in the returned + * context will be set to the return value of the error function. + * When no set of reply functions is given, the default set will be used. */ +redisContext *redisConnect(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,&tv); + return c; +} + +redisContext *redisConnectNonBlock(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectBindNonBlock(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags &= ~REDIS_BLOCK; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags &= ~REDIS_BLOCK; + c->flags |= REDIS_REUSEADDR; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + +redisContext *redisConnectUnix(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,&tv); + return c; +} + +redisContext *redisConnectUnixNonBlock(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +redisContext *redisConnectFd(int fd) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->fd = fd; + c->flags |= REDIS_BLOCK | REDIS_CONNECTED; + return c; +} + +/* Set read/write timeout on a blocking socket. */ +int redisSetTimeout(redisContext *c, const struct timeval tv) { + if (c->flags & REDIS_BLOCK) + return redisContextSetTimeout(c,tv); + return REDIS_ERR; +} + +/* Enable connection KeepAlive. */ +int redisEnableKeepAlive(redisContext *c) { + if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) + return REDIS_ERR; + return REDIS_OK; +} + +/* Use this function to handle a read event on the descriptor. It will try + * and read some bytes from the socket and feed them to the reply parser. + * + * After this function is called, you may use redisContextReadReply to + * see if there is a reply available. */ +int redisBufferRead(redisContext *c) { + char buf[1024*16]; + int nread; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + nread = read(c->fd,buf,sizeof(buf)); + if (nread == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nread == 0) { + __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); + return REDIS_ERR; + } else { + if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + } + return REDIS_OK; +} + +/* Write the output buffer to the socket. + * + * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was + * successfully written to the socket. When the buffer is empty after the + * write operation, "done" is set to 1 (if given). + * + * Returns REDIS_ERR if an error occurred trying to write and sets + * c->errstr to hold the appropriate error string. + */ +int redisBufferWrite(redisContext *c, int *done) { + int nwritten; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + if (sdslen(c->obuf) > 0) { + nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); + if (nwritten == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nwritten > 0) { + if (nwritten == (signed)sdslen(c->obuf)) { + sdsfree(c->obuf); + c->obuf = sdsempty(); + } else { + sdsrange(c->obuf,nwritten,-1); + } + } + } + if (done != NULL) *done = (sdslen(c->obuf) == 0); + return REDIS_OK; +} + +/* Internal helper function to try and get a reply from the reader, + * or set an error in the context otherwise. */ +int redisGetReplyFromReader(redisContext *c, void **reply) { + if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisGetReply(redisContext *c, void **reply) { + int wdone = 0; + void *aux = NULL; + + /* Try to read pending replies */ + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + + /* For the blocking context, flush output buffer and read reply */ + if (aux == NULL && c->flags & REDIS_BLOCK) { + /* Write until done */ + do { + if (redisBufferWrite(c,&wdone) == REDIS_ERR) + return REDIS_ERR; + } while (!wdone); + + /* Read until there is a reply */ + do { + if (redisBufferRead(c) == REDIS_ERR) + return REDIS_ERR; + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + } while (aux == NULL); + } + + /* Set reply object */ + if (reply != NULL) *reply = aux; + return REDIS_OK; +} + + +/* Helper function for the redisAppendCommand* family of functions. + * + * Write a formatted command to the output buffer. When this family + * is used, you need to call redisGetReply yourself to retrieve + * the reply (or replies in pub/sub). + */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { + sds newbuf; + + newbuf = sdscatlen(c->obuf,cmd,len); + if (newbuf == NULL) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + c->obuf = newbuf; + return REDIS_OK; +} + +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) { + + if (__redisAppendCommand(c, cmd, len) != REDIS_OK) { + return REDIS_ERR; + } + + return REDIS_OK; +} + +int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { + char *cmd; + int len; + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } else if (len == -2) { + __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + free(cmd); + return REDIS_ERR; + } + + free(cmd); + return REDIS_OK; +} + +int redisAppendCommand(redisContext *c, const char *format, ...) { + va_list ap; + int ret; + + va_start(ap,format); + ret = redisvAppendCommand(c,format,ap); + va_end(ap); + return ret; +} + +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + sds cmd; + int len; + + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + sdsfree(cmd); + return REDIS_ERR; + } + + sdsfree(cmd); + return REDIS_OK; +} + +/* Helper function for the redisCommand* family of functions. + * + * Write a formatted command to the output buffer. If the given context is + * blocking, immediately read the reply into the "reply" pointer. When the + * context is non-blocking, the "reply" pointer will not be used and the + * command is simply appended to the write buffer. + * + * Returns the reply when a reply was successfully retrieved. Returns NULL + * otherwise. When NULL is returned in a blocking context, the error field + * in the context will be set. + */ +static void *__redisBlockForReply(redisContext *c) { + void *reply; + + if (c->flags & REDIS_BLOCK) { + if (redisGetReply(c,&reply) != REDIS_OK) + return NULL; + return reply; + } + return NULL; +} + +void *redisvCommand(redisContext *c, const char *format, va_list ap) { + if (redisvAppendCommand(c,format,ap) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} + +void *redisCommand(redisContext *c, const char *format, ...) { + va_list ap; + va_start(ap,format); + void *reply = redisvCommand(c,format,ap); + va_end(ap); + return reply; +} + +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} diff --git a/lib/hiredis/hiredis.h b/lib/hiredis/hiredis.h new file mode 100755 index 0000000..77d5797 --- /dev/null +++ b/lib/hiredis/hiredis.h @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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 __HIREDIS_H +#define __HIREDIS_H +#include "read.h" +#include /* for va_list */ +#include /* for struct timeval */ +#include /* uintXX_t, etc */ +#include "sds.h" /* for sds */ + +#define HIREDIS_MAJOR 0 +#define HIREDIS_MINOR 13 +#define HIREDIS_PATCH 3 +#define HIREDIS_SONAME 0.13 + +/* Connection type can be blocking or non-blocking and is set in the + * least significant bit of the flags field in redisContext. */ +#define REDIS_BLOCK 0x1 + +/* Connection may be disconnected before being free'd. The second bit + * in the flags field is set when the context is connected. */ +#define REDIS_CONNECTED 0x2 + +/* The async API might try to disconnect cleanly and flush the output + * buffer and read all subsequent replies before disconnecting. + * This flag means no new commands can come in and the connection + * should be terminated once all replies have been read. */ +#define REDIS_DISCONNECTING 0x4 + +/* Flag specific to the async API which means that the context should be clean + * up as soon as possible. */ +#define REDIS_FREEING 0x8 + +/* Flag that is set when an async callback is executed. */ +#define REDIS_IN_CALLBACK 0x10 + +/* Flag that is set when the async context has one or more subscriptions. */ +#define REDIS_SUBSCRIBED 0x20 + +/* Flag that is set when monitor mode is active */ +#define REDIS_MONITORING 0x40 + +/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ +#define REDIS_REUSEADDR 0x80 + +#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ + +/* number of times we retry to connect in the case of EADDRNOTAVAIL and + * SO_REUSEADDR is being used. */ +#define REDIS_CONNECT_RETRIES 10 + +/* strerror_r has two completely different prototypes and behaviors + * depending on system issues, so we need to operate on the error buffer + * differently depending on which strerror_r we're using. */ +#ifndef _GNU_SOURCE +/* "regular" POSIX strerror_r that does the right thing. */ +#define __redis_strerror_r(errno, buf, len) \ + do { \ + strerror_r((errno), (buf), (len)); \ + } while (0) +#else +/* "bad" GNU strerror_r we need to clean up after. */ +#define __redis_strerror_r(errno, buf, len) \ + do { \ + char *err_str = strerror_r((errno), (buf), (len)); \ + /* If return value _isn't_ the start of the buffer we passed in, \ + * then GNU strerror_r returned an internal static buffer and we \ + * need to copy the result into our private buffer. */ \ + if (err_str != (buf)) { \ + strncpy((buf), err_str, ((len) - 1)); \ + (buf)[(len)-1] = '\0'; \ + } \ + } while (0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is the reply object returned by redisCommand() */ +typedef struct redisReply { + int type; /* REDIS_REPLY_* */ + long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ + size_t len; /* Length of string */ + char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ + size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ + struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ +} redisReply; + +redisReader *redisReaderCreate(void); + +/* Function to free the reply objects hiredis returns by default. */ +void freeReplyObject(void *reply); + +/* Functions to format a command according to the protocol. */ +int redisvFormatCommand(char **target, const char *format, va_list ap); +int redisFormatCommand(char **target, const char *format, ...); +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); +int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); +void redisFreeCommand(char *cmd); +void redisFreeSdsCommand(sds cmd); + +enum redisConnectionType { + REDIS_CONN_TCP, + REDIS_CONN_UNIX +}; + +/* Context for a connection to Redis */ +typedef struct redisContext { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + int fd; + int flags; + char *obuf; /* Write buffer */ + redisReader *reader; /* Protocol reader */ + + enum redisConnectionType connection_type; + struct timeval *timeout; + + struct { + char *host; + char *source_addr; + int port; + } tcp; + + struct { + char *path; + } unix_sock; + +} redisContext; + +redisContext *redisConnect(const char *ip, int port); +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); +redisContext *redisConnectNonBlock(const char *ip, int port); +redisContext *redisConnectBindNonBlock(const char *ip, int port, + const char *source_addr); +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr); +redisContext *redisConnectUnix(const char *path); +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); +redisContext *redisConnectUnixNonBlock(const char *path); +redisContext *redisConnectFd(int fd); + +/** + * Reconnect the given context using the saved information. + * + * This re-uses the exact same connect options as in the initial connection. + * host, ip (or path), timeout and bind address are reused, + * flags are used unmodified from the existing context. + * + * Returns REDIS_OK on successful connect or REDIS_ERR otherwise. + */ +int redisReconnect(redisContext *c); + +int redisSetTimeout(redisContext *c, const struct timeval tv); +int redisEnableKeepAlive(redisContext *c); +void redisFree(redisContext *c); +int redisFreeKeepFd(redisContext *c); +int redisBufferRead(redisContext *c); +int redisBufferWrite(redisContext *c, int *done); + +/* In a blocking context, this function first checks if there are unconsumed + * replies to return and returns one if so. Otherwise, it flushes the output + * buffer to the socket and reads until it has a reply. In a non-blocking + * context, it will return unconsumed replies until there are no more. */ +int redisGetReply(redisContext *c, void **reply); +int redisGetReplyFromReader(redisContext *c, void **reply); + +/* Write a formatted command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len); + +/* Write a command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisvAppendCommand(redisContext *c, const char *format, va_list ap); +int redisAppendCommand(redisContext *c, const char *format, ...); +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +/* Issue a command to Redis. In a blocking context, it is identical to calling + * redisAppendCommand, followed by redisGetReply. The function will return + * NULL if there was an error in performing the request, otherwise it will + * return the reply. In a non-blocking context, it is identical to calling + * only redisAppendCommand and will always return NULL. */ +void *redisvCommand(redisContext *c, const char *format, va_list ap); +void *redisCommand(redisContext *c, const char *format, ...); +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/hiredis/net.c b/lib/hiredis/net.c new file mode 100755 index 0000000..33d9a82 --- /dev/null +++ b/lib/hiredis/net.c @@ -0,0 +1,477 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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 "fmacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "net.h" +#include "sds.h" + +/* Defined in hiredis.c */ +void __redisSetError(redisContext *c, int type, const char *str); + +static void redisContextCloseFd(redisContext *c) { + if (c && c->fd >= 0) { + close(c->fd); + c->fd = -1; + } +} + +static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { + int errorno = errno; /* snprintf() may change errno */ + char buf[128] = { 0 }; + size_t len = 0; + + if (prefix != NULL) + len = snprintf(buf,sizeof(buf),"%s: ",prefix); + __redis_strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len); + __redisSetError(c,type,buf); +} + +static int redisSetReuseAddr(redisContext *c) { + int on = 1; + if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +static int redisCreateSocket(redisContext *c, int type) { + int s; + if ((s = socket(type, SOCK_STREAM, 0)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + c->fd = s; + if (type == AF_INET) { + if (redisSetReuseAddr(c) == REDIS_ERR) { + return REDIS_ERR; + } + } + return REDIS_OK; +} + +static int redisSetBlocking(redisContext *c, int blocking) { + int flags; + + /* Set the socket nonblocking. + * Note that fcntl(2) for F_GETFL and F_SETFL can't be + * interrupted by a signal. */ + if ((flags = fcntl(c->fd, F_GETFL)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + + if (blocking) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if (fcntl(c->fd, F_SETFL, flags) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisKeepAlive(redisContext *c, int interval) { + int val = 1; + int fd = c->fd; + + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval; + +#ifdef _OSX + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#else +#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval/3; + if (val == 0) val = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = 3; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#endif +#endif + + return REDIS_OK; +} + +static int redisSetTcpNoDelay(redisContext *c) { + int yes = 1; + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) + +static int redisContextTimeoutMsec(redisContext *c, long *result) +{ + const struct timeval *timeout = c->timeout; + long msec = -1; + + /* Only use timeout when not NULL. */ + if (timeout != NULL) { + if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { + *result = msec; + return REDIS_ERR; + } + + msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); + + if (msec < 0 || msec > INT_MAX) { + msec = INT_MAX; + } + } + + *result = msec; + return REDIS_OK; +} + +static int redisContextWaitReady(redisContext *c, long msec) { + struct pollfd wfd[1]; + + wfd[0].fd = c->fd; + wfd[0].events = POLLOUT; + + if (errno == EINPROGRESS) { + int res; + + if ((res = poll(wfd, 1, msec)) == -1) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); + redisContextCloseFd(c); + return REDIS_ERR; + } else if (res == 0) { + errno = ETIMEDOUT; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + + if (redisCheckSocketError(c) != REDIS_OK) + return REDIS_ERR; + + return REDIS_OK; + } + + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; +} + +int redisCheckSocketError(redisContext *c) { + int err = 0; + socklen_t errlen = sizeof(err); + + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); + return REDIS_ERR; + } + + if (err) { + errno = err; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + + return REDIS_OK; +} + +int redisContextSetTimeout(redisContext *c, const struct timeval tv) { + if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); + return REDIS_ERR; + } + if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); + return REDIS_ERR; + } + return REDIS_OK; +} + +static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr) { + int s, rv, n; + char _port[6]; /* strlen("65535"); */ + struct addrinfo hints, *servinfo, *bservinfo, *p, *b; + int blocking = (c->flags & REDIS_BLOCK); + int reuseaddr = (c->flags & REDIS_REUSEADDR); + int reuses = 0; + long timeout_msec = -1; + + servinfo = NULL; + c->connection_type = REDIS_CONN_TCP; + c->tcp.port = port; + + /* We need to take possession of the passed parameters + * to make them reusable for a reconnect. + * We also carefully check we don't free data we already own, + * as in the case of the reconnect method. + * + * This is a bit ugly, but atleast it works and doesn't leak memory. + **/ + if (c->tcp.host != addr) { + if (c->tcp.host) + free(c->tcp.host); + + c->tcp.host = strdup(addr); + } + + if (timeout) { + if (c->timeout != timeout) { + if (c->timeout == NULL) + c->timeout = malloc(sizeof(struct timeval)); + + memcpy(c->timeout, timeout, sizeof(struct timeval)); + } + } else { + if (c->timeout) + free(c->timeout); + c->timeout = NULL; + } + + if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { + __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified"); + goto error; + } + + if (source_addr == NULL) { + free(c->tcp.source_addr); + c->tcp.source_addr = NULL; + } else if (c->tcp.source_addr != source_addr) { + free(c->tcp.source_addr); + c->tcp.source_addr = strdup(source_addr); + } + + snprintf(_port, 6, "%d", port); + memset(&hints,0,sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + /* Try with IPv6 if no IPv4 address was found. We do it in this order since + * in a Redis client you can't afford to test if you have IPv6 connectivity + * as this would add latency to every connect. Otherwise a more sensible + * route could be: Use IPv6 if both addresses are available and there is IPv6 + * connectivity. */ + if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { + hints.ai_family = AF_INET6; + if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { + __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); + return REDIS_ERR; + } + } + for (p = servinfo; p != NULL; p = p->ai_next) { +addrretry: + if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) + continue; + + c->fd = s; + if (redisSetBlocking(c,0) != REDIS_OK) + goto error; + if (c->tcp.source_addr) { + int bound = 0; + /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ + if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + + if (reuseaddr) { + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, + sizeof(n)) < 0) { + freeaddrinfo(bservinfo); + goto error; + } + } + + for (b = bservinfo; b != NULL; b = b->ai_next) { + if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { + bound = 1; + break; + } + } + freeaddrinfo(bservinfo); + if (!bound) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + } + if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { + if (errno == EHOSTUNREACH) { + redisContextCloseFd(c); + continue; + } else if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else if (errno == EADDRNOTAVAIL && reuseaddr) { + if (++reuses >= REDIS_CONNECT_RETRIES) { + goto error; + } else { + redisContextCloseFd(c); + goto addrretry; + } + } else { + if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) + goto error; + } + } + if (blocking && redisSetBlocking(c,1) != REDIS_OK) + goto error; + if (redisSetTcpNoDelay(c) != REDIS_OK) + goto error; + + c->flags |= REDIS_CONNECTED; + rv = REDIS_OK; + goto end; + } + if (p == NULL) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + +error: + rv = REDIS_ERR; +end: + freeaddrinfo(servinfo); + return rv; // Need to return REDIS_OK if alright +} + +int redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout) { + return _redisContextConnectTcp(c, addr, port, timeout, NULL); +} + +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr) { + return _redisContextConnectTcp(c, addr, port, timeout, source_addr); +} + +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { + int blocking = (c->flags & REDIS_BLOCK); + struct sockaddr_un sa; + long timeout_msec = -1; + + if (redisCreateSocket(c,AF_LOCAL) < 0) + return REDIS_ERR; + if (redisSetBlocking(c,0) != REDIS_OK) + return REDIS_ERR; + + c->connection_type = REDIS_CONN_UNIX; + if (c->unix_sock.path != path) + c->unix_sock.path = strdup(path); + + if (timeout) { + if (c->timeout != timeout) { + if (c->timeout == NULL) + c->timeout = malloc(sizeof(struct timeval)); + + memcpy(c->timeout, timeout, sizeof(struct timeval)); + } + } else { + if (c->timeout) + free(c->timeout); + c->timeout = NULL; + } + + if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) + return REDIS_ERR; + + sa.sun_family = AF_LOCAL; + strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); + if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { + if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else { + if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) + return REDIS_ERR; + } + } + + /* Reset socket to be blocking after connect(2). */ + if (blocking && redisSetBlocking(c,1) != REDIS_OK) + return REDIS_ERR; + + c->flags |= REDIS_CONNECTED; + return REDIS_OK; +} diff --git a/lib/hiredis/net.h b/lib/hiredis/net.h new file mode 100755 index 0000000..fc8ddd3 --- /dev/null +++ b/lib/hiredis/net.h @@ -0,0 +1,53 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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 __NET_H +#define __NET_H + +#include "hiredis.h" + +#if defined(__sun) || defined(AIX) +#define AF_LOCAL AF_UNIX +#endif + +int redisCheckSocketError(redisContext *c); +int redisContextSetTimeout(redisContext *c, const struct timeval tv); +int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr); +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); +int redisKeepAlive(redisContext *c, int interval); + +#endif diff --git a/lib/hiredis/read.c b/lib/hiredis/read.c new file mode 100755 index 0000000..061bbda --- /dev/null +++ b/lib/hiredis/read.c @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 "fmacros.h" +#include +#include +#ifndef _MSC_VER +#include +#endif +#include +#include +#include + +#include "read.h" +#include "sds.h" + +static void __redisReaderSetError(redisReader *r, int type, const char *str) { + size_t len; + + if (r->reply != NULL && r->fn && r->fn->freeObject) { + r->fn->freeObject(r->reply); + r->reply = NULL; + } + + /* Clear input buffer on errors. */ + if (r->buf != NULL) { + sdsfree(r->buf); + r->buf = NULL; + r->pos = r->len = 0; + } + + /* Reset task stack. */ + r->ridx = -1; + + /* Set error. */ + r->err = type; + len = strlen(str); + len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); + memcpy(r->errstr,str,len); + r->errstr[len] = '\0'; +} + +static size_t chrtos(char *buf, size_t size, char byte) { + size_t len = 0; + + switch(byte) { + case '\\': + case '"': + len = snprintf(buf,size,"\"\\%c\"",byte); + break; + case '\n': len = snprintf(buf,size,"\"\\n\""); break; + case '\r': len = snprintf(buf,size,"\"\\r\""); break; + case '\t': len = snprintf(buf,size,"\"\\t\""); break; + case '\a': len = snprintf(buf,size,"\"\\a\""); break; + case '\b': len = snprintf(buf,size,"\"\\b\""); break; + default: + if (isprint(byte)) + len = snprintf(buf,size,"\"%c\"",byte); + else + len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); + break; + } + + return len; +} + +static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { + char cbuf[8], sbuf[128]; + + chrtos(cbuf,sizeof(cbuf),byte); + snprintf(sbuf,sizeof(sbuf), + "Protocol error, got %s as reply type byte", cbuf); + __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); +} + +static void __redisReaderSetErrorOOM(redisReader *r) { + __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); +} + +static char *readBytes(redisReader *r, unsigned int bytes) { + char *p; + if (r->len-r->pos >= bytes) { + p = r->buf+r->pos; + r->pos += bytes; + return p; + } + return NULL; +} + +/* Find pointer to \r\n. */ +static char *seekNewline(char *s, size_t len) { + int pos = 0; + int _len = len-1; + + /* Position should be < len-1 because the character at "pos" should be + * followed by a \n. Note that strchr cannot be used because it doesn't + * allow to search a limited length and the buffer that is being searched + * might not have a trailing NULL character. */ + while (pos < _len) { + while(pos < _len && s[pos] != '\r') pos++; + if (pos==_len) { + /* Not found. */ + return NULL; + } else { + if (s[pos+1] == '\n') { + /* Found. */ + return s+pos; + } else { + /* Continue searching. */ + pos++; + } + } + } + return NULL; +} + +/* Read a long long value starting at *s, under the assumption that it will be + * terminated by \r\n. Ambiguously returns -1 for unexpected input. */ +static long long readLongLong(char *s) { + long long v = 0; + int dec, mult = 1; + char c; + + if (*s == '-') { + mult = -1; + s++; + } else if (*s == '+') { + mult = 1; + s++; + } + + while ((c = *(s++)) != '\r') { + dec = c - '0'; + if (dec >= 0 && dec < 10) { + v *= 10; + v += dec; + } else { + /* Should not happen... */ + return -1; + } + } + + return mult*v; +} + +static char *readLine(redisReader *r, int *_len) { + char *p, *s; + int len; + + p = r->buf+r->pos; + s = seekNewline(p,(r->len-r->pos)); + if (s != NULL) { + len = s-(r->buf+r->pos); + r->pos += len+2; /* skip \r\n */ + if (_len) *_len = len; + return p; + } + return NULL; +} + +static void moveToNextTask(redisReader *r) { + redisReadTask *cur, *prv; + while (r->ridx >= 0) { + /* Return a.s.a.p. when the stack is now empty. */ + if (r->ridx == 0) { + r->ridx--; + return; + } + + cur = &(r->rstack[r->ridx]); + prv = &(r->rstack[r->ridx-1]); + assert(prv->type == REDIS_REPLY_ARRAY); + if (cur->idx == prv->elements-1) { + r->ridx--; + } else { + /* Reset the type because the next item can be anything */ + assert(cur->idx < prv->elements); + cur->type = -1; + cur->elements = -1; + cur->idx++; + return; + } + } +} + +static int processLineItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + int len; + + if ((p = readLine(r,&len)) != NULL) { + if (cur->type == REDIS_REPLY_INTEGER) { + if (r->fn && r->fn->createInteger) + obj = r->fn->createInteger(cur,readLongLong(p)); + else + obj = (void*)REDIS_REPLY_INTEGER; + } else { + /* Type will be error or status. */ + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,p,len); + else + obj = (void*)(size_t)(cur->type); + } + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj = NULL; + char *p, *s; + long len; + unsigned long bytelen; + int success = 0; + + p = r->buf+r->pos; + s = seekNewline(p,r->len-r->pos); + if (s != NULL) { + p = r->buf+r->pos; + bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ + len = readLongLong(p); + + if (len < 0) { + /* The nil object can always be created. */ + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + success = 1; + } else { + /* Only continue when the buffer contains the entire bulk item. */ + bytelen += len+2; /* include \r\n */ + if (r->pos+bytelen <= r->len) { + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,s+2,len); + else + obj = (void*)REDIS_REPLY_STRING; + success = 1; + } + } + + /* Proceed when obj was created. */ + if (success) { + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->pos += bytelen; + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + } + + return REDIS_ERR; +} + +static int processMultiBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + long elements; + int root = 0; + + /* Set error for nested multi bulks with depth > 7 */ + if (r->ridx == 8) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "No support for nested multi bulk replies with depth > 7"); + return REDIS_ERR; + } + + if ((p = readLine(r,NULL)) != NULL) { + elements = readLongLong(p); + root = (r->ridx == 0); + + if (elements == -1) { + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + moveToNextTask(r); + } else { + if (r->fn && r->fn->createArray) + obj = r->fn->createArray(cur,elements); + else + obj = (void*)REDIS_REPLY_ARRAY; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Modify task stack when there are more than 0 elements. */ + if (elements > 0) { + cur->elements = elements; + cur->obj = obj; + r->ridx++; + r->rstack[r->ridx].type = -1; + r->rstack[r->ridx].elements = -1; + r->rstack[r->ridx].idx = 0; + r->rstack[r->ridx].obj = NULL; + r->rstack[r->ridx].parent = cur; + r->rstack[r->ridx].privdata = r->privdata; + } else { + moveToNextTask(r); + } + } + + /* Set reply if this is the root object. */ + if (root) r->reply = obj; + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + char *p; + + /* check if we need to read type */ + if (cur->type < 0) { + if ((p = readBytes(r,1)) != NULL) { + switch (p[0]) { + case '-': + cur->type = REDIS_REPLY_ERROR; + break; + case '+': + cur->type = REDIS_REPLY_STATUS; + break; + case ':': + cur->type = REDIS_REPLY_INTEGER; + break; + case '$': + cur->type = REDIS_REPLY_STRING; + break; + case '*': + cur->type = REDIS_REPLY_ARRAY; + break; + default: + __redisReaderSetErrorProtocolByte(r,*p); + return REDIS_ERR; + } + } else { + /* could not consume 1 byte */ + return REDIS_ERR; + } + } + + /* process typed item */ + switch(cur->type) { + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_INTEGER: + return processLineItem(r); + case REDIS_REPLY_STRING: + return processBulkItem(r); + case REDIS_REPLY_ARRAY: + return processMultiBulkItem(r); + default: + assert(NULL); + return REDIS_ERR; /* Avoid warning. */ + } +} + +redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { + redisReader *r; + + r = calloc(1,sizeof(redisReader)); + if (r == NULL) + return NULL; + + r->err = 0; + r->errstr[0] = '\0'; + r->fn = fn; + r->buf = sdsempty(); + r->maxbuf = REDIS_READER_MAX_BUF; + if (r->buf == NULL) { + free(r); + return NULL; + } + + r->ridx = -1; + return r; +} + +void redisReaderFree(redisReader *r) { + if (r->reply != NULL && r->fn && r->fn->freeObject) + r->fn->freeObject(r->reply); + if (r->buf != NULL) + sdsfree(r->buf); + free(r); +} + +int redisReaderFeed(redisReader *r, const char *buf, size_t len) { + sds newbuf; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* Copy the provided buffer. */ + if (buf != NULL && len >= 1) { + /* Destroy internal buffer when it is empty and is quite large. */ + if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { + sdsfree(r->buf); + r->buf = sdsempty(); + r->pos = 0; + + /* r->buf should not be NULL since we just free'd a larger one. */ + assert(r->buf != NULL); + } + + newbuf = sdscatlen(r->buf,buf,len); + if (newbuf == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->buf = newbuf; + r->len = sdslen(r->buf); + } + + return REDIS_OK; +} + +int redisReaderGetReply(redisReader *r, void **reply) { + /* Default target pointer to NULL. */ + if (reply != NULL) + *reply = NULL; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* When the buffer is empty, there will never be a reply. */ + if (r->len == 0) + return REDIS_OK; + + /* Set first item to process when the stack is empty. */ + if (r->ridx == -1) { + r->rstack[0].type = -1; + r->rstack[0].elements = -1; + r->rstack[0].idx = -1; + r->rstack[0].obj = NULL; + r->rstack[0].parent = NULL; + r->rstack[0].privdata = r->privdata; + r->ridx = 0; + } + + /* Process items in reply. */ + while (r->ridx >= 0) + if (processItem(r) != REDIS_OK) + break; + + /* Return ASAP when an error occurred. */ + if (r->err) + return REDIS_ERR; + + /* Discard part of the buffer when we've consumed at least 1k, to avoid + * doing unnecessary calls to memmove() in sds.c. */ + if (r->pos >= 1024) { + sdsrange(r->buf,r->pos,-1); + r->pos = 0; + r->len = sdslen(r->buf); + } + + /* Emit a reply when there is one. */ + if (r->ridx == -1) { + if (reply != NULL) + *reply = r->reply; + r->reply = NULL; + } + return REDIS_OK; +} diff --git a/lib/hiredis/read.h b/lib/hiredis/read.h new file mode 100755 index 0000000..2988aa4 --- /dev/null +++ b/lib/hiredis/read.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 __HIREDIS_READ_H +#define __HIREDIS_READ_H +#include /* for size_t */ + +#define REDIS_ERR -1 +#define REDIS_OK 0 + +/* When an error occurs, the err flag in a context is set to hold the type of + * error that occurred. REDIS_ERR_IO means there was an I/O error and you + * should use the "errno" variable to find out what is wrong. + * For other values, the "errstr" field will hold a description. */ +#define REDIS_ERR_IO 1 /* Error in read or write */ +#define REDIS_ERR_EOF 3 /* End of file */ +#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ +#define REDIS_ERR_OOM 5 /* Out of memory */ +#define REDIS_ERR_OTHER 2 /* Everything else... */ + +#define REDIS_REPLY_STRING 1 +#define REDIS_REPLY_ARRAY 2 +#define REDIS_REPLY_INTEGER 3 +#define REDIS_REPLY_NIL 4 +#define REDIS_REPLY_STATUS 5 +#define REDIS_REPLY_ERROR 6 + +#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct redisReadTask { + int type; + int elements; /* number of elements in multibulk container */ + int idx; /* index in parent (array) object */ + void *obj; /* holds user-generated value for a read task */ + struct redisReadTask *parent; /* parent task */ + void *privdata; /* user-settable arbitrary field */ +} redisReadTask; + +typedef struct redisReplyObjectFunctions { + void *(*createString)(const redisReadTask*, char*, size_t); + void *(*createArray)(const redisReadTask*, int); + void *(*createInteger)(const redisReadTask*, long long); + void *(*createNil)(const redisReadTask*); + void (*freeObject)(void*); +} redisReplyObjectFunctions; + +typedef struct redisReader { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + + char *buf; /* Read buffer */ + size_t pos; /* Buffer cursor */ + size_t len; /* Buffer length */ + size_t maxbuf; /* Max length of unused buffer */ + + redisReadTask rstack[9]; + int ridx; /* Index of current read task */ + void *reply; /* Temporary reply pointer */ + + redisReplyObjectFunctions *fn; + void *privdata; +} redisReader; + +/* Public API for the protocol parser. */ +redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn); +void redisReaderFree(redisReader *r); +int redisReaderFeed(redisReader *r, const char *buf, size_t len); +int redisReaderGetReply(redisReader *r, void **reply); + +#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) +#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply) +#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/hiredis/sds.c b/lib/hiredis/sds.c new file mode 100755 index 0000000..923ffd8 --- /dev/null +++ b/lib/hiredis/sds.c @@ -0,0 +1,1272 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, 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 "sds.h" +#include "sdsalloc.h" + +static inline int sdsHdrSize(char type) { + switch(type&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return sizeof(struct sdshdr5); + case SDS_TYPE_8: + return sizeof(struct sdshdr8); + case SDS_TYPE_16: + return sizeof(struct sdshdr16); + case SDS_TYPE_32: + return sizeof(struct sdshdr32); + case SDS_TYPE_64: + return sizeof(struct sdshdr64); + } + return 0; +} + +static inline char sdsReqType(size_t string_size) { + if (string_size < 32) + return SDS_TYPE_5; + if (string_size < 0xff) + return SDS_TYPE_8; + if (string_size < 0xffff) + return SDS_TYPE_16; + if (string_size < 0xffffffff) + return SDS_TYPE_32; + return SDS_TYPE_64; +} + +/* Create a new sds string with the content specified by the 'init' pointer + * and 'initlen'. + * If NULL is used for 'init' the string is initialized with zero bytes. + * + * The string is always null-termined (all the sds strings are, always) so + * even if you create an sds string with: + * + * mystring = sdsnewlen("abc",3); + * + * You can print the string with printf() as there is an implicit \0 at the + * end of the string. However the string is binary safe and can contain + * \0 characters in the middle, as the length is stored in the sds header. */ +sds sdsnewlen(const void *init, size_t initlen) { + void *sh; + sds s; + char type = sdsReqType(initlen); + /* Empty strings are usually created in order to append. Use type 8 + * since type 5 is not good at this. */ + if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; + int hdrlen = sdsHdrSize(type); + unsigned char *fp; /* flags pointer. */ + + sh = s_malloc(hdrlen+initlen+1); + if (sh == NULL) return NULL; + if (!init) + memset(sh, 0, hdrlen+initlen+1); + s = (char*)sh+hdrlen; + fp = ((unsigned char*)s)-1; + switch(type) { + case SDS_TYPE_5: { + *fp = type | (initlen << SDS_TYPE_BITS); + break; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + } + if (initlen && init) + memcpy(s, init, initlen); + s[initlen] = '\0'; + return s; +} + +/* Create an empty (zero length) sds string. Even in this case the string + * always has an implicit null term. */ +sds sdsempty(void) { + return sdsnewlen("",0); +} + +/* Create a new sds string starting from a null terminated C string. */ +sds sdsnew(const char *init) { + size_t initlen = (init == NULL) ? 0 : strlen(init); + return sdsnewlen(init, initlen); +} + +/* Duplicate an sds string. */ +sds sdsdup(const sds s) { + return sdsnewlen(s, sdslen(s)); +} + +/* Free an sds string. No operation is performed if 's' is NULL. */ +void sdsfree(sds s) { + if (s == NULL) return; + s_free((char*)s-sdsHdrSize(s[-1])); +} + +/* Set the sds string length to the length as obtained with strlen(), so + * considering as content only up to the first null term character. + * + * This function is useful when the sds string is hacked manually in some + * way, like in the following example: + * + * s = sdsnew("foobar"); + * s[2] = '\0'; + * sdsupdatelen(s); + * printf("%d\n", sdslen(s)); + * + * The output will be "2", but if we comment out the call to sdsupdatelen() + * the output will be "6" as the string was modified but the logical length + * remains 6 bytes. */ +void sdsupdatelen(sds s) { + int reallen = strlen(s); + sdssetlen(s, reallen); +} + +/* Modify an sds string in-place to make it empty (zero length). + * However all the existing buffer is not discarded but set as free space + * so that next append operations will not require allocations up to the + * number of bytes previously available. */ +void sdsclear(sds s) { + sdssetlen(s, 0); + s[0] = '\0'; +} + +/* Enlarge the free space at the end of the sds string so that the caller + * is sure that after calling this function can overwrite up to addlen + * bytes after the end of the string, plus one more byte for nul term. + * + * Note: this does not change the *length* of the sds string as returned + * by sdslen(), but only the free buffer space we have. */ +sds sdsMakeRoomFor(sds s, size_t addlen) { + void *sh, *newsh; + size_t avail = sdsavail(s); + size_t len, newlen; + char type, oldtype = s[-1] & SDS_TYPE_MASK; + int hdrlen; + + /* Return ASAP if there is enough space left. */ + if (avail >= addlen) return s; + + len = sdslen(s); + sh = (char*)s-sdsHdrSize(oldtype); + newlen = (len+addlen); + if (newlen < SDS_MAX_PREALLOC) + newlen *= 2; + else + newlen += SDS_MAX_PREALLOC; + + type = sdsReqType(newlen); + + /* Don't use type 5: the user is appending to the string and type 5 is + * not able to remember empty space, so sdsMakeRoomFor() must be called + * at every appending operation. */ + if (type == SDS_TYPE_5) type = SDS_TYPE_8; + + hdrlen = sdsHdrSize(type); + if (oldtype==type) { + newsh = s_realloc(sh, hdrlen+newlen+1); + if (newsh == NULL) return NULL; + s = (char*)newsh+hdrlen; + } else { + /* Since the header size changes, need to move the string forward, + * and can't use realloc */ + newsh = s_malloc(hdrlen+newlen+1); + if (newsh == NULL) return NULL; + memcpy((char*)newsh+hdrlen, s, len+1); + s_free(sh); + s = (char*)newsh+hdrlen; + s[-1] = type; + sdssetlen(s, len); + } + sdssetalloc(s, newlen); + return s; +} + +/* Reallocate the sds string so that it has no free space at the end. The + * contained string remains not altered, but next concatenation operations + * will require a reallocation. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdsRemoveFreeSpace(sds s) { + void *sh, *newsh; + char type, oldtype = s[-1] & SDS_TYPE_MASK; + int hdrlen; + size_t len = sdslen(s); + sh = (char*)s-sdsHdrSize(oldtype); + + type = sdsReqType(len); + hdrlen = sdsHdrSize(type); + if (oldtype==type) { + newsh = s_realloc(sh, hdrlen+len+1); + if (newsh == NULL) return NULL; + s = (char*)newsh+hdrlen; + } else { + newsh = s_malloc(hdrlen+len+1); + if (newsh == NULL) return NULL; + memcpy((char*)newsh+hdrlen, s, len+1); + s_free(sh); + s = (char*)newsh+hdrlen; + s[-1] = type; + sdssetlen(s, len); + } + sdssetalloc(s, len); + return s; +} + +/* Return the total size of the allocation of the specifed sds string, + * including: + * 1) The sds header before the pointer. + * 2) The string. + * 3) The free buffer at the end if any. + * 4) The implicit null term. + */ +size_t sdsAllocSize(sds s) { + size_t alloc = sdsalloc(s); + return sdsHdrSize(s[-1])+alloc+1; +} + +/* Return the pointer of the actual SDS allocation (normally SDS strings + * are referenced by the start of the string buffer). */ +void *sdsAllocPtr(sds s) { + return (void*) (s-sdsHdrSize(s[-1])); +} + +/* Increment the sds length and decrements the left free space at the + * end of the string according to 'incr'. Also set the null term + * in the new end of the string. + * + * This function is used in order to fix the string length after the + * user calls sdsMakeRoomFor(), writes something after the end of + * the current string, and finally needs to set the new length. + * + * Note: it is possible to use a negative increment in order to + * right-trim the string. + * + * Usage example: + * + * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the + * following schema, to cat bytes coming from the kernel to the end of an + * sds string without copying into an intermediate buffer: + * + * oldlen = sdslen(s); + * s = sdsMakeRoomFor(s, BUFFER_SIZE); + * nread = read(fd, s+oldlen, BUFFER_SIZE); + * ... check for nread <= 0 and handle it ... + * sdsIncrLen(s, nread); + */ +void sdsIncrLen(sds s, int incr) { + unsigned char flags = s[-1]; + size_t len; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { + unsigned char *fp = ((unsigned char*)s)-1; + unsigned char oldlen = SDS_TYPE_5_LEN(flags); + assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); + *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); + len = oldlen+incr; + break; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); + len = (sh->len += incr); + break; + } + default: len = 0; /* Just to avoid compilation warnings. */ + } + s[len] = '\0'; +} + +/* Grow the sds to have the specified length. Bytes that were not part of + * the original length of the sds will be set to zero. + * + * if the specified length is smaller than the current length, no operation + * is performed. */ +sds sdsgrowzero(sds s, size_t len) { + size_t curlen = sdslen(s); + + if (len <= curlen) return s; + s = sdsMakeRoomFor(s,len-curlen); + if (s == NULL) return NULL; + + /* Make sure added region doesn't contain garbage */ + memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ + sdssetlen(s, len); + return s; +} + +/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the + * end of the specified sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatlen(sds s, const void *t, size_t len) { + size_t curlen = sdslen(s); + + s = sdsMakeRoomFor(s,len); + if (s == NULL) return NULL; + memcpy(s+curlen, t, len); + sdssetlen(s, curlen+len); + s[curlen+len] = '\0'; + return s; +} + +/* Append the specified null termianted C string to the sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscat(sds s, const char *t) { + return sdscatlen(s, t, strlen(t)); +} + +/* Append the specified sds 't' to the existing sds 's'. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatsds(sds s, const sds t) { + return sdscatlen(s, t, sdslen(t)); +} + +/* Destructively modify the sds string 's' to hold the specified binary + * safe string pointed by 't' of length 'len' bytes. */ +sds sdscpylen(sds s, const char *t, size_t len) { + if (sdsalloc(s) < len) { + s = sdsMakeRoomFor(s,len-sdslen(s)); + if (s == NULL) return NULL; + } + memcpy(s, t, len); + s[len] = '\0'; + sdssetlen(s, len); + return s; +} + +/* Like sdscpylen() but 't' must be a null-termined string so that the length + * of the string is obtained with strlen(). */ +sds sdscpy(sds s, const char *t) { + return sdscpylen(s, t, strlen(t)); +} + +/* Helper for sdscatlonglong() doing the actual number -> string + * conversion. 's' must point to a string with room for at least + * SDS_LLSTR_SIZE bytes. + * + * The function returns the length of the null-terminated string + * representation stored at 's'. */ +#define SDS_LLSTR_SIZE 21 +int sdsll2str(char *s, long long value) { + char *p, aux; + unsigned long long v; + size_t l; + + /* Generate the string representation, this method produces + * an reversed string. */ + v = (value < 0) ? -value : value; + p = s; + do { + *p++ = '0'+(v%10); + v /= 10; + } while(v); + if (value < 0) *p++ = '-'; + + /* Compute length and add null term. */ + l = p-s; + *p = '\0'; + + /* Reverse the string. */ + p--; + while(s < p) { + aux = *s; + *s = *p; + *p = aux; + s++; + p--; + } + return l; +} + +/* Identical sdsll2str(), but for unsigned long long type. */ +int sdsull2str(char *s, unsigned long long v) { + char *p, aux; + size_t l; + + /* Generate the string representation, this method produces + * an reversed string. */ + p = s; + do { + *p++ = '0'+(v%10); + v /= 10; + } while(v); + + /* Compute length and add null term. */ + l = p-s; + *p = '\0'; + + /* Reverse the string. */ + p--; + while(s < p) { + aux = *s; + *s = *p; + *p = aux; + s++; + p--; + } + return l; +} + +/* Create an sds string from a long long value. It is much faster than: + * + * sdscatprintf(sdsempty(),"%lld\n", value); + */ +sds sdsfromlonglong(long long value) { + char buf[SDS_LLSTR_SIZE]; + int len = sdsll2str(buf,value); + + return sdsnewlen(buf,len); +} + +/* Like sdscatprintf() but gets va_list instead of being variadic. */ +sds sdscatvprintf(sds s, const char *fmt, va_list ap) { + va_list cpy; + char staticbuf[1024], *buf = staticbuf, *t; + size_t buflen = strlen(fmt)*2; + + /* We try to start using a static buffer for speed. + * If not possible we revert to heap allocation. */ + if (buflen > sizeof(staticbuf)) { + buf = s_malloc(buflen); + if (buf == NULL) return NULL; + } else { + buflen = sizeof(staticbuf); + } + + /* Try with buffers two times bigger every time we fail to + * fit the string in the current buffer size. */ + while(1) { + buf[buflen-2] = '\0'; + va_copy(cpy,ap); + vsnprintf(buf, buflen, fmt, cpy); + va_end(cpy); + if (buf[buflen-2] != '\0') { + if (buf != staticbuf) s_free(buf); + buflen *= 2; + buf = s_malloc(buflen); + if (buf == NULL) return NULL; + continue; + } + break; + } + + /* Finally concat the obtained string to the SDS string and return it. */ + t = sdscat(s, buf); + if (buf != staticbuf) s_free(buf); + return t; +} + +/* Append to the sds string 's' a string obtained using printf-alike format + * specifier. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsnew("Sum is: "); + * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). + * + * Often you need to create a string from scratch with the printf-alike + * format. When this is the need, just use sdsempty() as the target string: + * + * s = sdscatprintf(sdsempty(), "... your format ...", args); + */ +sds sdscatprintf(sds s, const char *fmt, ...) { + va_list ap; + char *t; + va_start(ap, fmt); + t = sdscatvprintf(s,fmt,ap); + va_end(ap); + return t; +} + +/* This function is similar to sdscatprintf, but much faster as it does + * not rely on sprintf() family functions implemented by the libc that + * are often very slow. Moreover directly handling the sds string as + * new data is concatenated provides a performance improvement. + * + * However this function only handles an incompatible subset of printf-alike + * format specifiers: + * + * %s - C String + * %S - SDS string + * %i - signed int + * %I - 64 bit signed integer (long long, int64_t) + * %u - unsigned int + * %U - 64 bit unsigned integer (unsigned long long, uint64_t) + * %% - Verbatim "%" character. + */ +sds sdscatfmt(sds s, char const *fmt, ...) { + const char *f = fmt; + int i; + va_list ap; + + va_start(ap,fmt); + i = sdslen(s); /* Position of the next byte to write to dest str. */ + while(*f) { + char next, *str; + size_t l; + long long num; + unsigned long long unum; + + /* Make sure there is always space for at least 1 char. */ + if (sdsavail(s)==0) { + s = sdsMakeRoomFor(s,1); + } + + switch(*f) { + case '%': + next = *(f+1); + f++; + switch(next) { + case 's': + case 'S': + str = va_arg(ap,char*); + l = (next == 's') ? strlen(str) : sdslen(str); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); + } + memcpy(s+i,str,l); + sdsinclen(s,l); + i += l; + break; + case 'i': + case 'I': + if (next == 'i') + num = va_arg(ap,int); + else + num = va_arg(ap,long long); + { + char buf[SDS_LLSTR_SIZE]; + l = sdsll2str(buf,num); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); + } + memcpy(s+i,buf,l); + sdsinclen(s,l); + i += l; + } + break; + case 'u': + case 'U': + if (next == 'u') + unum = va_arg(ap,unsigned int); + else + unum = va_arg(ap,unsigned long long); + { + char buf[SDS_LLSTR_SIZE]; + l = sdsull2str(buf,unum); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); + } + memcpy(s+i,buf,l); + sdsinclen(s,l); + i += l; + } + break; + default: /* Handle %% and generally %. */ + s[i++] = next; + sdsinclen(s,1); + break; + } + break; + default: + s[i++] = *f; + sdsinclen(s,1); + break; + } + f++; + } + va_end(ap); + + /* Add null-term */ + s[i] = '\0'; + return s; +} + +/* Remove the part of the string from left and from right composed just of + * contiguous characters found in 'cset', that is a null terminted C string. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); + * s = sdstrim(s,"Aa. :"); + * printf("%s\n", s); + * + * Output will be just "Hello World". + */ +sds sdstrim(sds s, const char *cset) { + char *start, *end, *sp, *ep; + size_t len; + + sp = start = s; + ep = end = s+sdslen(s)-1; + while(sp <= end && strchr(cset, *sp)) sp++; + while(ep > sp && strchr(cset, *ep)) ep--; + len = (sp > ep) ? 0 : ((ep-sp)+1); + if (s != sp) memmove(s, sp, len); + s[len] = '\0'; + sdssetlen(s,len); + return s; +} + +/* Turn the string into a smaller (or equal) string containing only the + * substring specified by the 'start' and 'end' indexes. + * + * start and end can be negative, where -1 means the last character of the + * string, -2 the penultimate character, and so forth. + * + * The interval is inclusive, so the start and end characters will be part + * of the resulting string. + * + * The string is modified in-place. + * + * Example: + * + * s = sdsnew("Hello World"); + * sdsrange(s,1,-1); => "ello World" + */ +void sdsrange(sds s, int start, int end) { + size_t newlen, len = sdslen(s); + + if (len == 0) return; + if (start < 0) { + start = len+start; + if (start < 0) start = 0; + } + if (end < 0) { + end = len+end; + if (end < 0) end = 0; + } + newlen = (start > end) ? 0 : (end-start)+1; + if (newlen != 0) { + if (start >= (signed)len) { + newlen = 0; + } else if (end >= (signed)len) { + end = len-1; + newlen = (start > end) ? 0 : (end-start)+1; + } + } else { + start = 0; + } + if (start && newlen) memmove(s, s+start, newlen); + s[newlen] = 0; + sdssetlen(s,newlen); +} + +/* Apply tolower() to every character of the sds string 's'. */ +void sdstolower(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = tolower(s[j]); +} + +/* Apply toupper() to every character of the sds string 's'. */ +void sdstoupper(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = toupper(s[j]); +} + +/* Compare two sds strings s1 and s2 with memcmp(). + * + * Return value: + * + * positive if s1 > s2. + * negative if s1 < s2. + * 0 if s1 and s2 are exactly the same binary string. + * + * If two strings share exactly the same prefix, but one of the two has + * additional characters, the longer string is considered to be greater than + * the smaller one. */ +int sdscmp(const sds s1, const sds s2) { + size_t l1, l2, minlen; + int cmp; + + l1 = sdslen(s1); + l2 = sdslen(s2); + minlen = (l1 < l2) ? l1 : l2; + cmp = memcmp(s1,s2,minlen); + if (cmp == 0) return l1-l2; + return cmp; +} + +/* Split 's' with separator in 'sep'. An array + * of sds strings is returned. *count will be set + * by reference to the number of tokens returned. + * + * On out of memory, zero length string, zero length + * separator, NULL is returned. + * + * Note that 'sep' is able to split a string using + * a multi-character separator. For example + * sdssplit("foo_-_bar","_-_"); will return two + * elements "foo" and "bar". + * + * This version of the function is binary-safe but + * requires length arguments. sdssplit() is just the + * same function but for zero-terminated strings. + */ +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { + int elements = 0, slots = 5, start = 0, j; + sds *tokens; + + if (seplen < 1 || len < 0) return NULL; + + tokens = s_malloc(sizeof(sds)*slots); + if (tokens == NULL) return NULL; + + if (len == 0) { + *count = 0; + return tokens; + } + for (j = 0; j < (len-(seplen-1)); j++) { + /* make sure there is room for the next element and the final one */ + if (slots < elements+2) { + sds *newtokens; + + slots *= 2; + newtokens = s_realloc(tokens,sizeof(sds)*slots); + if (newtokens == NULL) goto cleanup; + tokens = newtokens; + } + /* search the separator */ + if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { + tokens[elements] = sdsnewlen(s+start,j-start); + if (tokens[elements] == NULL) goto cleanup; + elements++; + start = j+seplen; + j = j+seplen-1; /* skip the separator */ + } + } + /* Add the final element. We are sure there is room in the tokens array. */ + tokens[elements] = sdsnewlen(s+start,len-start); + if (tokens[elements] == NULL) goto cleanup; + elements++; + *count = elements; + return tokens; + +cleanup: + { + int i; + for (i = 0; i < elements; i++) sdsfree(tokens[i]); + s_free(tokens); + *count = 0; + return NULL; + } +} + +/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ +void sdsfreesplitres(sds *tokens, int count) { + if (!tokens) return; + while(count--) + sdsfree(tokens[count]); + s_free(tokens); +} + +/* Append to the sds string "s" an escaped string representation where + * all the non-printable characters (tested with isprint()) are turned into + * escapes in the form "\n\r\a...." or "\x". + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatrepr(sds s, const char *p, size_t len) { + s = sdscatlen(s,"\"",1); + while(len--) { + switch(*p) { + case '\\': + case '"': + s = sdscatprintf(s,"\\%c",*p); + break; + case '\n': s = sdscatlen(s,"\\n",2); break; + case '\r': s = sdscatlen(s,"\\r",2); break; + case '\t': s = sdscatlen(s,"\\t",2); break; + case '\a': s = sdscatlen(s,"\\a",2); break; + case '\b': s = sdscatlen(s,"\\b",2); break; + default: + if (isprint(*p)) + s = sdscatprintf(s,"%c",*p); + else + s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); + break; + } + p++; + } + return sdscatlen(s,"\"",1); +} + +/* Helper function for sdssplitargs() that returns non zero if 'c' + * is a valid hex digit. */ +int is_hex_digit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +/* Helper function for sdssplitargs() that converts a hex digit into an + * integer from 0 to 15 */ +int hex_digit_to_int(char c) { + switch(c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: return 0; + } +} + +/* Split a line into arguments, where every argument can be in the + * following programming-language REPL-alike form: + * + * foo bar "newline are supported\n" and "\xff\x00otherstuff" + * + * The number of arguments is stored into *argc, and an array + * of sds is returned. + * + * The caller should free the resulting array of sds strings with + * sdsfreesplitres(). + * + * Note that sdscatrepr() is able to convert back a string into + * a quoted string in the same format sdssplitargs() is able to parse. + * + * The function returns the allocated tokens on success, even when the + * input string is empty, or NULL if the input contains unbalanced + * quotes or closed quotes followed by non space characters + * as in: "foo"bar or "foo' + */ +sds *sdssplitargs(const char *line, int *argc) { + const char *p = line; + char *current = NULL; + char **vector = NULL; + + *argc = 0; + while(1) { + /* skip blanks */ + while(*p && isspace(*p)) p++; + if (*p) { + /* get a token */ + int inq=0; /* set to 1 if we are in "quotes" */ + int insq=0; /* set to 1 if we are in 'single quotes' */ + int done=0; + + if (current == NULL) current = sdsempty(); + while(!done) { + if (inq) { + if (*p == '\\' && *(p+1) == 'x' && + is_hex_digit(*(p+2)) && + is_hex_digit(*(p+3))) + { + unsigned char byte; + + byte = (hex_digit_to_int(*(p+2))*16)+ + hex_digit_to_int(*(p+3)); + current = sdscatlen(current,(char*)&byte,1); + p += 3; + } else if (*p == '\\' && *(p+1)) { + char c; + + p++; + switch(*p) { + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'b': c = '\b'; break; + case 'a': c = '\a'; break; + default: c = *p; break; + } + current = sdscatlen(current,&c,1); + } else if (*p == '"') { + /* closing quote must be followed by a space or + * nothing at all. */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else if (insq) { + if (*p == '\\' && *(p+1) == '\'') { + p++; + current = sdscatlen(current,"'",1); + } else if (*p == '\'') { + /* closing quote must be followed by a space or + * nothing at all. */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else { + switch(*p) { + case ' ': + case '\n': + case '\r': + case '\t': + case '\0': + done=1; + break; + case '"': + inq=1; + break; + case '\'': + insq=1; + break; + default: + current = sdscatlen(current,p,1); + break; + } + } + if (*p) p++; + } + /* add the token to the vector */ + vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); + vector[*argc] = current; + (*argc)++; + current = NULL; + } else { + /* Even on empty input string return something not NULL. */ + if (vector == NULL) vector = s_malloc(sizeof(void*)); + return vector; + } + } + +err: + while((*argc)--) + sdsfree(vector[*argc]); + s_free(vector); + if (current) sdsfree(current); + *argc = 0; + return NULL; +} + +/* Modify the string substituting all the occurrences of the set of + * characters specified in the 'from' string to the corresponding character + * in the 'to' array. + * + * For instance: sdsmapchars(mystring, "ho", "01", 2) + * will have the effect of turning the string "hello" into "0ell1". + * + * The function returns the sds string pointer, that is always the same + * as the input pointer since no resize is needed. */ +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { + size_t j, i, l = sdslen(s); + + for (j = 0; j < l; j++) { + for (i = 0; i < setlen; i++) { + if (s[j] == from[i]) { + s[j] = to[i]; + break; + } + } + } + return s; +} + +/* Join an array of C strings using the specified separator (also a C string). + * Returns the result as an sds string. */ +sds sdsjoin(char **argv, int argc, char *sep) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscat(join, argv[j]); + if (j != argc-1) join = sdscat(join,sep); + } + return join; +} + +/* Like sdsjoin, but joins an array of SDS strings. */ +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscatsds(join, argv[j]); + if (j != argc-1) join = sdscatlen(join,sep,seplen); + } + return join; +} + +/* Wrappers to the allocators used by SDS. Note that SDS will actually + * just use the macros defined into sdsalloc.h in order to avoid to pay + * the overhead of function calls. Here we define these wrappers only for + * the programs SDS is linked to, if they want to touch the SDS internals + * even if they use a different allocator. */ +void *sds_malloc(size_t size) { return s_malloc(size); } +void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); } +void sds_free(void *ptr) { s_free(ptr); } + +#if defined(SDS_TEST_MAIN) +#include +#include "testhelp.h" +#include "limits.h" + +#define UNUSED(x) (void)(x) +int sdsTest(void) { + { + sds x = sdsnew("foo"), y; + + test_cond("Create a string and obtain the length", + sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) + + sdsfree(x); + x = sdsnewlen("foo",2); + test_cond("Create a string with specified length", + sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) + + x = sdscat(x,"bar"); + test_cond("Strings concatenation", + sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); + + x = sdscpy(x,"a"); + test_cond("sdscpy() against an originally longer string", + sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) + + x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); + test_cond("sdscpy() against an originally shorter string", + sdslen(x) == 33 && + memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) + + sdsfree(x); + x = sdscatprintf(sdsempty(),"%d",123); + test_cond("sdscatprintf() seems working in the base case", + sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) + + sdsfree(x); + x = sdsnew("--"); + x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); + test_cond("sdscatfmt() seems working in the base case", + sdslen(x) == 60 && + memcmp(x,"--Hello Hi! World -9223372036854775808," + "9223372036854775807--",60) == 0) + printf("[%s]\n",x); + + sdsfree(x); + x = sdsnew("--"); + x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); + test_cond("sdscatfmt() seems working with unsigned numbers", + sdslen(x) == 35 && + memcmp(x,"--4294967295,18446744073709551615--",35) == 0) + + sdsfree(x); + x = sdsnew(" x "); + sdstrim(x," x"); + test_cond("sdstrim() works when all chars match", + sdslen(x) == 0) + + sdsfree(x); + x = sdsnew(" x "); + sdstrim(x," "); + test_cond("sdstrim() works when a single char remains", + sdslen(x) == 1 && x[0] == 'x') + + sdsfree(x); + x = sdsnew("xxciaoyyy"); + sdstrim(x,"xy"); + test_cond("sdstrim() correctly trims characters", + sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) + + y = sdsdup(x); + sdsrange(y,1,1); + test_cond("sdsrange(...,1,1)", + sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,-1); + test_cond("sdsrange(...,1,-1)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,-2,-1); + test_cond("sdsrange(...,-2,-1)", + sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,2,1); + test_cond("sdsrange(...,2,1)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,100); + test_cond("sdsrange(...,1,100)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,100,100); + test_cond("sdsrange(...,100,100)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("foo"); + y = sdsnew("foa"); + test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("bar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("aar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) + + sdsfree(y); + sdsfree(x); + x = sdsnewlen("\a\n\0foo\r",7); + y = sdscatrepr(sdsempty(),x,sdslen(x)); + test_cond("sdscatrepr(...data...)", + memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) + + { + unsigned int oldfree; + char *p; + int step = 10, j, i; + + sdsfree(x); + sdsfree(y); + x = sdsnew("0"); + test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0); + + /* Run the test a few times in order to hit the first two + * SDS header types. */ + for (i = 0; i < 10; i++) { + int oldlen = sdslen(x); + x = sdsMakeRoomFor(x,step); + int type = x[-1]&SDS_TYPE_MASK; + + test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen); + if (type != SDS_TYPE_5) { + test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step); + oldfree = sdsavail(x); + } + p = x+oldlen; + for (j = 0; j < step; j++) { + p[j] = 'A'+j; + } + sdsIncrLen(x,step); + } + test_cond("sdsMakeRoomFor() content", + memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); + test_cond("sdsMakeRoomFor() final length",sdslen(x)==101); + + sdsfree(x); + } + } + test_report() + return 0; +} +#endif + +#ifdef SDS_TEST_MAIN +int main(void) { + return sdsTest(); +} +#endif diff --git a/lib/hiredis/sds.h b/lib/hiredis/sds.h new file mode 100755 index 0000000..13be75a --- /dev/null +++ b/lib/hiredis/sds.h @@ -0,0 +1,273 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, 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. + */ + +#ifndef __SDS_H +#define __SDS_H + +#define SDS_MAX_PREALLOC (1024*1024) + +#include +#include +#include + +typedef char *sds; + +/* Note: sdshdr5 is never used, we just access the flags byte directly. + * However is here to document the layout of type 5 SDS strings. */ +struct __attribute__ ((__packed__)) sdshdr5 { + unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr8 { + uint8_t len; /* used */ + uint8_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr16 { + uint16_t len; /* used */ + uint16_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr32 { + uint32_t len; /* used */ + uint32_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr64 { + uint64_t len; /* used */ + uint64_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; + +#define SDS_TYPE_5 0 +#define SDS_TYPE_8 1 +#define SDS_TYPE_16 2 +#define SDS_TYPE_32 3 +#define SDS_TYPE_64 4 +#define SDS_TYPE_MASK 7 +#define SDS_TYPE_BITS 3 +#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); +#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) +#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) + +static inline size_t sdslen(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->len; + case SDS_TYPE_16: + return SDS_HDR(16,s)->len; + case SDS_TYPE_32: + return SDS_HDR(32,s)->len; + case SDS_TYPE_64: + return SDS_HDR(64,s)->len; + } + return 0; +} + +static inline size_t sdsavail(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { + return 0; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + return sh->alloc - sh->len; + } + } + return 0; +} + +static inline void sdssetlen(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len = newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len = newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len = newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len = newlen; + break; + } +} + +static inline void sdsinclen(sds s, size_t inc) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len += inc; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len += inc; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len += inc; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len += inc; + break; + } +} + +/* sdsalloc() = sdsavail() + sdslen() */ +static inline size_t sdsalloc(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->alloc; + case SDS_TYPE_16: + return SDS_HDR(16,s)->alloc; + case SDS_TYPE_32: + return SDS_HDR(32,s)->alloc; + case SDS_TYPE_64: + return SDS_HDR(64,s)->alloc; + } + return 0; +} + +static inline void sdssetalloc(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + /* Nothing to do, this type has no total allocation info. */ + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->alloc = newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->alloc = newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->alloc = newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->alloc = newlen; + break; + } +} + +sds sdsnewlen(const void *init, size_t initlen); +sds sdsnew(const char *init); +sds sdsempty(void); +sds sdsdup(const sds s); +void sdsfree(sds s); +sds sdsgrowzero(sds s, size_t len); +sds sdscatlen(sds s, const void *t, size_t len); +sds sdscat(sds s, const char *t); +sds sdscatsds(sds s, const sds t); +sds sdscpylen(sds s, const char *t, size_t len); +sds sdscpy(sds s, const char *t); + +sds sdscatvprintf(sds s, const char *fmt, va_list ap); +#ifdef __GNUC__ +sds sdscatprintf(sds s, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +#else +sds sdscatprintf(sds s, const char *fmt, ...); +#endif + +sds sdscatfmt(sds s, char const *fmt, ...); +sds sdstrim(sds s, const char *cset); +void sdsrange(sds s, int start, int end); +void sdsupdatelen(sds s); +void sdsclear(sds s); +int sdscmp(const sds s1, const sds s2); +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); +void sdsfreesplitres(sds *tokens, int count); +void sdstolower(sds s); +void sdstoupper(sds s); +sds sdsfromlonglong(long long value); +sds sdscatrepr(sds s, const char *p, size_t len); +sds *sdssplitargs(const char *line, int *argc); +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); +sds sdsjoin(char **argv, int argc, char *sep); +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); + +/* Low level functions exposed to the user API */ +sds sdsMakeRoomFor(sds s, size_t addlen); +void sdsIncrLen(sds s, int incr); +sds sdsRemoveFreeSpace(sds s); +size_t sdsAllocSize(sds s); +void *sdsAllocPtr(sds s); + +/* Export the allocator used by SDS to the program using SDS. + * Sometimes the program SDS is linked to, may use a different set of + * allocators, but may want to allocate or free things that SDS will + * respectively free or allocate. */ +void *sds_malloc(size_t size); +void *sds_realloc(void *ptr, size_t size); +void sds_free(void *ptr); + +#ifdef REDIS_TEST +int sdsTest(int argc, char *argv[]); +#endif + +#endif diff --git a/lib/hiredis/sdsalloc.h b/lib/hiredis/sdsalloc.h new file mode 100755 index 0000000..f43023c --- /dev/null +++ b/lib/hiredis/sdsalloc.h @@ -0,0 +1,42 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, 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. + */ + +/* SDS allocator selection. + * + * This file is used in order to change the SDS allocator at compile time. + * Just define the following defines to what you want to use. Also add + * the include of your alternate allocator if needed (not needed in order + * to use the default libc allocator). */ + +#define s_malloc malloc +#define s_realloc realloc +#define s_free free diff --git a/lib/hiredis/test.c b/lib/hiredis/test.c new file mode 100755 index 0000000..a23d606 --- /dev/null +++ b/lib/hiredis/test.c @@ -0,0 +1,823 @@ +#include "fmacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "net.h" + +enum connection_type { + CONN_TCP, + CONN_UNIX, + CONN_FD +}; + +struct config { + enum connection_type type; + + struct { + const char *host; + int port; + struct timeval timeout; + } tcp; + + struct { + const char *path; + } unix_sock; +}; + +/* The following lines make up our testing "framework" :) */ +static int tests = 0, fails = 0; +#define test(_s) { printf("#%02d ", ++tests); printf(_s); } +#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} + +static long long usec(void) { + struct timeval tv; + gettimeofday(&tv,NULL); + return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; +} + +/* The assert() calls below have side effects, so we need assert() + * even if we are compiling without asserts (-DNDEBUG). */ +#ifdef NDEBUG +#undef assert +#define assert(e) (void)(e) +#endif + +static redisContext *select_database(redisContext *c) { + redisReply *reply; + + /* Switch to DB 9 for testing, now that we know we can chat. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Make sure the DB is emtpy */ + reply = redisCommand(c,"DBSIZE"); + assert(reply != NULL); + if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { + /* Awesome, DB 9 is empty and we can continue. */ + freeReplyObject(reply); + } else { + printf("Database #9 is not empty, test can not continue\n"); + exit(1); + } + + return c; +} + +static int disconnect(redisContext *c, int keep_fd) { + redisReply *reply; + + /* Make sure we're on DB 9. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + reply = redisCommand(c,"FLUSHDB"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Free the context as well, but keep the fd if requested. */ + if (keep_fd) + return redisFreeKeepFd(c); + redisFree(c); + return -1; +} + +static redisContext *connect(struct config config) { + redisContext *c = NULL; + + if (config.type == CONN_TCP) { + c = redisConnect(config.tcp.host, config.tcp.port); + } else if (config.type == CONN_UNIX) { + c = redisConnectUnix(config.unix_sock.path); + } else if (config.type == CONN_FD) { + /* Create a dummy connection just to get an fd to inherit */ + redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path); + if (dummy_ctx) { + int fd = disconnect(dummy_ctx, 1); + printf("Connecting to inherited fd %d\n", fd); + c = redisConnectFd(fd); + } + } else { + assert(NULL); + } + + if (c == NULL) { + printf("Connection error: can't allocate redis context\n"); + exit(1); + } else if (c->err) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + exit(1); + } + + return select_database(c); +} + +static void test_format_commands(void) { + char *cmd; + int len; + + test("Format command without interpolation: "); + len = redisFormatCommand(&cmd,"SET foo bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%s string interpolation: "); + len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%s and an empty string: "); + len = redisFormatCommand(&cmd,"SET %s %s","foo",""); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(0+2)); + free(cmd); + + test("Format command with an empty string in between proper interpolations: "); + len = redisFormatCommand(&cmd,"SET %s %s","","foo"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && + len == 4+4+(3+2)+4+(0+2)+4+(3+2)); + free(cmd); + + test("Format command with %%b string interpolation: "); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%b and an empty string: "); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(0+2)); + free(cmd); + + test("Format command with literal %%: "); + len = redisFormatCommand(&cmd,"SET %% %%"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 && + len == 4+4+(3+2)+4+(1+2)+4+(1+2)); + free(cmd); + + /* Vararg width depends on the type. These tests make sure that the + * width is correctly determined using the format and subsequent varargs + * can correctly be interpolated. */ +#define INTEGER_WIDTH_TEST(fmt, type) do { \ + type value = 123; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + +#define FLOAT_WIDTH_TEST(type) do { \ + type value = 123.0; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + + INTEGER_WIDTH_TEST("d", int); + INTEGER_WIDTH_TEST("hhd", char); + INTEGER_WIDTH_TEST("hd", short); + INTEGER_WIDTH_TEST("ld", long); + INTEGER_WIDTH_TEST("lld", long long); + INTEGER_WIDTH_TEST("u", unsigned int); + INTEGER_WIDTH_TEST("hhu", unsigned char); + INTEGER_WIDTH_TEST("hu", unsigned short); + INTEGER_WIDTH_TEST("lu", unsigned long); + INTEGER_WIDTH_TEST("llu", unsigned long long); + FLOAT_WIDTH_TEST(float); + FLOAT_WIDTH_TEST(double); + + test("Format command with invalid printf format: "); + len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); + test_cond(len == -1); + + const char *argv[3]; + argv[0] = "SET"; + argv[1] = "foo\0xxx"; + argv[2] = "bar"; + size_t lens[3] = { 3, 7, 3 }; + int argc = 3; + + test("Format command by passing argc/argv without lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,NULL); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command by passing argc/argv with lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,lens); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(7+2)+4+(3+2)); + free(cmd); + + sds sds_cmd; + + sds_cmd = sdsempty(); + test("Format command into sds by passing argc/argv without lengths: "); + len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL); + test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + sdsfree(sds_cmd); + + sds_cmd = sdsempty(); + test("Format command into sds by passing argc/argv with lengths: "); + len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens); + test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(7+2)+4+(3+2)); + sdsfree(sds_cmd); +} + +static void test_append_formatted_commands(struct config config) { + redisContext *c; + redisReply *reply; + char *cmd; + int len; + + c = connect(config); + + test("Append format command: "); + + len = redisFormatCommand(&cmd, "SET foo bar"); + + test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK); + + assert(redisGetReply(c, (void*)&reply) == REDIS_OK); + + free(cmd); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_reply_reader(void) { + redisReader *reader; + void *reply; + int ret; + int i; + + test("Error handling in reply parser: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); + + /* when the reply already contains multiple items, they must be free'd + * on an error. valgrind will bark when this doesn't happen. */ + test("Memory cleanup in reply parser: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*2\r\n",4); + redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); + + test("Set error on nested multi bulks with depth > 7: "); + reader = redisReaderCreate(); + + for (i = 0; i < 9; i++) { + redisReaderFeed(reader,(char*)"*1\r\n",4); + } + + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strncasecmp(reader->errstr,"No support for",14) == 0); + redisReaderFree(reader); + + test("Works with NULL functions for reply: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r\n",5); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); + redisReaderFree(reader); + + test("Works when a single newline (\\r\\n) covers two calls to feed: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r",4); + ret = redisReaderGetReply(reader,&reply); + assert(ret == REDIS_OK && reply == NULL); + redisReaderFeed(reader,(char*)"\n",1); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); + redisReaderFree(reader); + + test("Don't reset state after protocol error: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"x",1); + ret = redisReaderGetReply(reader,&reply); + assert(ret == REDIS_ERR); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && reply == NULL); + redisReaderFree(reader); + + /* Regression test for issue #45 on GitHub. */ + test("Don't do empty allocation for empty multi bulk: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*0\r\n",4); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && + ((redisReply*)reply)->elements == 0); + freeReplyObject(reply); + redisReaderFree(reader); +} + +static void test_free_null(void) { + void *redisCtx = NULL; + void *reply = NULL; + + test("Don't fail when redisFree is passed a NULL value: "); + redisFree(redisCtx); + test_cond(redisCtx == NULL); + + test("Don't fail when freeReplyObject is passed a NULL value: "); + freeReplyObject(reply); + test_cond(reply == NULL); +} + +static void test_blocking_connection_errors(void) { + redisContext *c; + + test("Returns error when host cannot be resolved: "); + c = redisConnect((char*)"idontexist.test", 6379); + test_cond(c->err == REDIS_ERR_OTHER && + (strcmp(c->errstr,"Name or service not known") == 0 || + strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 || + strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || + strcmp(c->errstr,"No address associated with hostname") == 0 || + strcmp(c->errstr,"Temporary failure in name resolution") == 0 || + strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 || + strcmp(c->errstr,"no address associated with name") == 0)); + redisFree(c); + + test("Returns error when the port is not open: "); + c = redisConnect((char*)"localhost", 1); + test_cond(c->err == REDIS_ERR_IO && + strcmp(c->errstr,"Connection refused") == 0); + redisFree(c); + + test("Returns error when the unix_sock socket path doesn't accept connections: "); + c = redisConnectUnix((char*)"/tmp/idontexist.sock"); + test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ + redisFree(c); +} + +static void test_blocking_connection(struct config config) { + redisContext *c; + redisReply *reply; + + c = connect(config); + + test("Is able to deliver commands: "); + reply = redisCommand(c,"PING"); + test_cond(reply->type == REDIS_REPLY_STATUS && + strcasecmp(reply->str,"pong") == 0) + freeReplyObject(reply); + + test("Is a able to send commands verbatim: "); + reply = redisCommand(c,"SET foo bar"); + test_cond (reply->type == REDIS_REPLY_STATUS && + strcasecmp(reply->str,"ok") == 0) + freeReplyObject(reply); + + test("%%s String interpolation works: "); + reply = redisCommand(c,"SET %s %s","foo","hello world"); + freeReplyObject(reply); + reply = redisCommand(c,"GET foo"); + test_cond(reply->type == REDIS_REPLY_STRING && + strcmp(reply->str,"hello world") == 0); + freeReplyObject(reply); + + test("%%b String interpolation works: "); + reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); + freeReplyObject(reply); + reply = redisCommand(c,"GET foo"); + test_cond(reply->type == REDIS_REPLY_STRING && + memcmp(reply->str,"hello\x00world",11) == 0) + + test("Binary reply length is correct: "); + test_cond(reply->len == 11) + freeReplyObject(reply); + + test("Can parse nil replies: "); + reply = redisCommand(c,"GET nokey"); + test_cond(reply->type == REDIS_REPLY_NIL) + freeReplyObject(reply); + + /* test 7 */ + test("Can parse integer replies: "); + reply = redisCommand(c,"INCR mycounter"); + test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) + freeReplyObject(reply); + + test("Can parse multi bulk replies: "); + freeReplyObject(redisCommand(c,"LPUSH mylist foo")); + freeReplyObject(redisCommand(c,"LPUSH mylist bar")); + reply = redisCommand(c,"LRANGE mylist 0 -1"); + test_cond(reply->type == REDIS_REPLY_ARRAY && + reply->elements == 2 && + !memcmp(reply->element[0]->str,"bar",3) && + !memcmp(reply->element[1]->str,"foo",3)) + freeReplyObject(reply); + + /* m/e with multi bulk reply *before* other reply. + * specifically test ordering of reply items to parse. */ + test("Can handle nested multi bulk replies: "); + freeReplyObject(redisCommand(c,"MULTI")); + freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); + freeReplyObject(redisCommand(c,"PING")); + reply = (redisCommand(c,"EXEC")); + test_cond(reply->type == REDIS_REPLY_ARRAY && + reply->elements == 2 && + reply->element[0]->type == REDIS_REPLY_ARRAY && + reply->element[0]->elements == 2 && + !memcmp(reply->element[0]->element[0]->str,"bar",3) && + !memcmp(reply->element[0]->element[1]->str,"foo",3) && + reply->element[1]->type == REDIS_REPLY_STATUS && + strcasecmp(reply->element[1]->str,"pong") == 0); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_blocking_connection_timeouts(struct config config) { + redisContext *c; + redisReply *reply; + ssize_t s; + const char *cmd = "DEBUG SLEEP 3\r\n"; + struct timeval tv; + + c = connect(config); + test("Successfully completes a command when the timeout is not exceeded: "); + reply = redisCommand(c,"SET foo fast"); + freeReplyObject(reply); + tv.tv_sec = 0; + tv.tv_usec = 10000; + redisSetTimeout(c, tv); + reply = redisCommand(c, "GET foo"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0); + freeReplyObject(reply); + disconnect(c, 0); + + c = connect(config); + test("Does not return a reply when the command times out: "); + s = write(c->fd, cmd, strlen(cmd)); + tv.tv_sec = 0; + tv.tv_usec = 10000; + redisSetTimeout(c, tv); + reply = redisCommand(c, "GET foo"); + test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); + freeReplyObject(reply); + + test("Reconnect properly reconnects after a timeout: "); + redisReconnect(c); + reply = redisCommand(c, "PING"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + freeReplyObject(reply); + + test("Reconnect properly uses owned parameters: "); + config.tcp.host = "foo"; + config.unix_sock.path = "foo"; + redisReconnect(c); + reply = redisCommand(c, "PING"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_blocking_io_errors(struct config config) { + redisContext *c; + redisReply *reply; + void *_reply; + int major, minor; + + /* Connect to target given by config. */ + c = connect(config); + { + /* Find out Redis version to determine the path for the next test */ + const char *field = "redis_version:"; + char *p, *eptr; + + reply = redisCommand(c,"INFO"); + p = strstr(reply->str,field); + major = strtol(p+strlen(field),&eptr,10); + p = eptr+1; /* char next to the first "." */ + minor = strtol(p,&eptr,10); + freeReplyObject(reply); + } + + test("Returns I/O error when the connection is lost: "); + reply = redisCommand(c,"QUIT"); + if (major > 2 || (major == 2 && minor > 0)) { + /* > 2.0 returns OK on QUIT and read() should be issued once more + * to know the descriptor is at EOF. */ + test_cond(strcasecmp(reply->str,"OK") == 0 && + redisGetReply(c,&_reply) == REDIS_ERR); + freeReplyObject(reply); + } else { + test_cond(reply == NULL); + } + + /* On 2.0, QUIT will cause the connection to be closed immediately and + * the read(2) for the reply on QUIT will set the error to EOF. + * On >2.0, QUIT will return with OK and another read(2) needed to be + * issued to find out the socket was closed by the server. In both + * conditions, the error will be set to EOF. */ + assert(c->err == REDIS_ERR_EOF && + strcmp(c->errstr,"Server closed the connection") == 0); + redisFree(c); + + c = connect(config); + test("Returns I/O error on socket timeout: "); + struct timeval tv = { 0, 1000 }; + assert(redisSetTimeout(c,tv) == REDIS_OK); + test_cond(redisGetReply(c,&_reply) == REDIS_ERR && + c->err == REDIS_ERR_IO && errno == EAGAIN); + redisFree(c); +} + +static void test_invalid_timeout_errors(struct config config) { + redisContext *c; + + test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = 0; + config.tcp.timeout.tv_usec = 10000001; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); + redisFree(c); + + test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; + config.tcp.timeout.tv_usec = 0; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); + redisFree(c); +} + +static void test_throughput(struct config config) { + redisContext *c = connect(config); + redisReply **replies; + int i, num; + long long t1, t2; + + test("Throughput:\n"); + for (i = 0; i < 500; i++) + freeReplyObject(redisCommand(c,"LPUSH mylist foo")); + + num = 1000; + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c,"PING"); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c,"LRANGE mylist 0 499"); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); + assert(replies[i] != NULL && replies[i]->elements == 500); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); + + num = 10000; + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"PING"); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"LRANGE mylist 0 499"); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); + assert(replies[i] != NULL && replies[i]->elements == 500); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + disconnect(c, 0); +} + +// static long __test_callback_flags = 0; +// static void __test_callback(redisContext *c, void *privdata) { +// ((void)c); +// /* Shift to detect execution order */ +// __test_callback_flags <<= 8; +// __test_callback_flags |= (long)privdata; +// } +// +// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { +// ((void)c); +// /* Shift to detect execution order */ +// __test_callback_flags <<= 8; +// __test_callback_flags |= (long)privdata; +// if (reply) freeReplyObject(reply); +// } +// +// static redisContext *__connect_nonblock() { +// /* Reset callback flags */ +// __test_callback_flags = 0; +// return redisConnectNonBlock("127.0.0.1", port, NULL); +// } +// +// static void test_nonblocking_connection() { +// redisContext *c; +// int wdone = 0; +// +// test("Calls command callback when command is issued: "); +// c = __connect_nonblock(); +// redisSetCommandCallback(c,__test_callback,(void*)1); +// redisCommand(c,"PING"); +// test_cond(__test_callback_flags == 1); +// redisFree(c); +// +// test("Calls disconnect callback on redisDisconnect: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)2); +// redisDisconnect(c); +// test_cond(__test_callback_flags == 2); +// redisFree(c); +// +// test("Calls disconnect callback and free callback on redisFree: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)2); +// redisSetFreeCallback(c,__test_callback,(void*)4); +// redisFree(c); +// test_cond(__test_callback_flags == ((2 << 8) | 4)); +// +// test("redisBufferWrite against empty write buffer: "); +// c = __connect_nonblock(); +// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); +// redisFree(c); +// +// test("redisBufferWrite against not yet connected fd: "); +// c = __connect_nonblock(); +// redisCommand(c,"PING"); +// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && +// strncmp(c->error,"write:",6) == 0); +// redisFree(c); +// +// test("redisBufferWrite against closed fd: "); +// c = __connect_nonblock(); +// redisCommand(c,"PING"); +// redisDisconnect(c); +// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && +// strncmp(c->error,"write:",6) == 0); +// redisFree(c); +// +// test("Process callbacks in the right sequence: "); +// c = __connect_nonblock(); +// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); +// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); +// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); +// +// /* Write output buffer */ +// wdone = 0; +// while(!wdone) { +// usleep(500); +// redisBufferWrite(c,&wdone); +// } +// +// /* Read until at least one callback is executed (the 3 replies will +// * arrive in a single packet, causing all callbacks to be executed in +// * a single pass). */ +// while(__test_callback_flags == 0) { +// assert(redisBufferRead(c) == REDIS_OK); +// redisProcessCallbacks(c); +// } +// test_cond(__test_callback_flags == 0x010203); +// redisFree(c); +// +// test("redisDisconnect executes pending callbacks with NULL reply: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)1); +// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); +// redisDisconnect(c); +// test_cond(__test_callback_flags == 0x0201); +// redisFree(c); +// } + +int main(int argc, char **argv) { + struct config cfg = { + .tcp = { + .host = "127.0.0.1", + .port = 6379 + }, + .unix_sock = { + .path = "/tmp/redis.sock" + } + }; + int throughput = 1; + int test_inherit_fd = 1; + + /* Ignore broken pipe signal (for I/O error tests). */ + signal(SIGPIPE, SIG_IGN); + + /* Parse command line options. */ + argv++; argc--; + while (argc) { + if (argc >= 2 && !strcmp(argv[0],"-h")) { + argv++; argc--; + cfg.tcp.host = argv[0]; + } else if (argc >= 2 && !strcmp(argv[0],"-p")) { + argv++; argc--; + cfg.tcp.port = atoi(argv[0]); + } else if (argc >= 2 && !strcmp(argv[0],"-s")) { + argv++; argc--; + cfg.unix_sock.path = argv[0]; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { + throughput = 0; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { + test_inherit_fd = 0; + } else { + fprintf(stderr, "Invalid argument: %s\n", argv[0]); + exit(1); + } + argv++; argc--; + } + + test_format_commands(); + test_reply_reader(); + test_blocking_connection_errors(); + test_free_null(); + + printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); + cfg.type = CONN_TCP; + test_blocking_connection(cfg); + test_blocking_connection_timeouts(cfg); + test_blocking_io_errors(cfg); + test_invalid_timeout_errors(cfg); + test_append_formatted_commands(cfg); + if (throughput) test_throughput(cfg); + + printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path); + cfg.type = CONN_UNIX; + test_blocking_connection(cfg); + test_blocking_connection_timeouts(cfg); + test_blocking_io_errors(cfg); + if (throughput) test_throughput(cfg); + + if (test_inherit_fd) { + printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path); + cfg.type = CONN_FD; + test_blocking_connection(cfg); + } + + + if (fails) { + printf("*** %d TESTS FAILED ***\n", fails); + return 1; + } + + printf("ALL TESTS PASSED\n"); + return 0; +} diff --git a/lib/hiredis/test2 b/lib/hiredis/test2 new file mode 100755 index 0000000000000000000000000000000000000000..5cb2c76eac1a872f7c98219c5272ad814bc2b019 GIT binary patch literal 200032 zcmd443w%_?6+eDASzzUn8x=4p)>Rix3P_@$i3ZJ*4cvtdA`ig=A}k3+LlTn>0YwPz zF3EMh7~izo7F*k5wUt^87zGj@Nwhu!Dn(I2MYz`mgD(;c$^O1)=HA_#NZS5Bem?)- ze|0i<&di)SbLN~gXCC*mCdV@|&SDXaU%YUmK&j1%9FnDH*sbyUlOSXX>B2eqJ3?>> zeF5!*kfrDLjN5h8Mr_l|5jHDAdK?GzjEC#xjM$)%Vrs9qU()kD$A|^N&Hz-8XjT8l zl8Ei>!H75N-P;uHCeJ7Gs9w8XuU)Ta#D{gljM(TO^^L+zy?;Gp7xL(5#8j4k)4udA zrsqwMv*b@=hfbdn8})8MJ)-k}{prIiOxN4%jW0<${YI>JR~S=Ru;|*cV+t3KE-WZ2 zUom<``n9949eY(-@l{u|eiMEwJ89}HMv}otJ5ogRU}>E=y_8Sy1^#Q`v7ZHhRxjnB>jl1}7kFwf zbUJ&1FYE=rs2BWS_5y#Q7x-U$fnV4QJhvBkc`xuoy}+;R1^!4c@SA(#!;`&~|6MQe zxxK(&0e(C3dj7Toa5lMmvln+ zV1>mMG1!WQ3-Sw!78VxVlPBbt=7A)g^aTr6lq`wzWkIQs?=A8%a+Vb@&qEW$PvkEI z7nUt7C=x143w#`hFv;VdkiFpQtJ20sAFsJ8_I&kK<6>}OfqQ1|g2j2Ic}oh)e0ila zbF&MJi}GeJTvSM{ELm1uq_?tw7d>l1L5RaFLI2TgLH}9xn5WM6m;8h7aHDRVj*Vp0 z0!$D!!I%dhd-w@VpM8Wv014(BR&Wl@R26!<{6b2{3w}L)enG(?qV6F=f}i0>81w(; zb2! z44!O)>#h)kr<&k~Ped8%Cb-!@$}+*JZR01I;KtZc-ZT>&f)M@9F~R#sp@MLS2~M`l z_$@WT2O2=cB_=ooEBaeuf+s|wf>3FKizaxr37%+z*O=hvn&2BuaI-z$XoBk^$ZBme z!F7>h@XaQ;E}9I!)dau5)NZ{Ao@9bIo8ZGt@D>yNA`|?832v+vDEqJpezB>%YJ$^T zX8cZ?;FlOc#O)@y-2@kOJtEn;)C9Mg;O6--(FAvx$|srNBTR6+2~NI{@k=(rlMNu^ zR1-YK1Wz}?e`$hC{^K?&)aT1*?1B`m_r*n8B>!&PE+I1RTYw@XzQ{)WQ~Cl*m$3BJlqMC??qKO(Qkqmlo5s?YQJPG! zmc`N+QkqmmOJ(WvC{3!N*;%?jrAZ~UM3#=DG^v6nu=I}`k-mh|Cp)SC-%{F6>BB7j z6{RnwbPG#=PH7T#t)8VnqBNO)Z8J;nqcn-Uwvnap|GizvuR)sqBxR877X(Qe_tgCq zjb{;rv+f<_tII%A{#D{70ajSNL`XZ~miOK+$)8L9BPXZL^eBBUyoc%y`r}Gg{U{1X zhV(@n-;0;H80Z-gFiTSU{2H;8(Jskf`i4RDKl&Y#5dA^(BZp&6%-=;)A4`Lxg;Y-& z_bY}br3>|?hOT%?J}EV*H%XR8X>XS=2?(Fj5e_7T2FX3~RgOOwc!csXl7D;!Du6Q3 zGf)bRuLI5MC+M##f$fcbG^8z5y%Jp#zS{+q=noa2ujQizJwb~Tw}&AdAoE%^uRuAwuAlwZ58@uvrq+rB%@2nY; z+(8y8)X(1@=Nq}^ZhyO_JV6TGn?(72)O8&|1^Xn)hvv@LRv=~YR|-u^rcwM%3eC2s zosfe2eCJ7_OcnLYvnBr?iDNTggl;&-G8|b^zWUWZXmk04u~yDQiToOME?6yv zX4o07PmSy7ZVSH^^&~HH(yfj zv$*7BHN}86_*OcVoDOQYuhZXYDW53GUwPzHlKigvJz8(HCId-!k~`#bfTMRMQb&hM zp{zuIdR)2N?Vag>4bMuJW;=RD_p=k2?o4MC<2<7QWY3}t*9qPpcENxhhL zoAg3Hly*>E4RH(aM)_I1{FS`?7z}M)C6zz%BeB|~54?*QF4jeLJn#`!4zMVx3H~xL zV+e*wq3MbK>*Fwn-YKAQI%u@MIhQD#!duz~azNQ0qU=>_$(M{=r+^Y8{|QiFe`2G0Y^{2J#h=S+;;tQqnT ztTomyiK4oQH%UX67Cg}eUtbVB-VQIvV1X1$3=h`%K=q8=i`?S&-KSg=EVgphS$XCvG0+78;r3Dr zt#a7EbBe_|f_QE}cPJ-Ok|!nRh}*9~W7nWDmweP!z1Wec?rdX?iUHbcbB1yRfBP7* zYCag_ueUkn96`Gk8FdI4N6Z3;)Dmbr7%c{_gF6HEfaBXK+r*&YA!t|-Y?%-7VQLI| zY%XP~CD;4qWM$NsC=N;fT6OR)!#s-t6>22b&cM*lOKz82{D=ExTE(jVpae1!_EBcN z6$J>c%qQpR1i;FQkTXHXuDwT^$0m{b*q z=KS@EV68P*$p@ALxhZGd{xgFrp7B6TQ^94A{CTdjI^uyr{DQE6Zad9j@}UWhE=Q`M z&c6nJMKMgYAy1(hZIb`Tb3Dp~IC0&3#9aT6UUA)g>h;Gg`A7tz-L(!FJdb z40lpSSPXtZgF&p4ynCFDR;!@?>RVD2mm}FD!)e*QR8|egz`0NR0J$ES6;ihT&AeJh*8OO`NW?WF`eFJph{%(M^PSyz1D`le1WvSQZLid;-txP;8 z{59lBnb<#klo643Q1gM7%2kfUrM?`B3dO)}ki>1k64B=150!WJ(JqCVQ=EOZVJz;a zor`!Y6i5g^y_FWS;m5bavhOMr%EEJ)oyU>RS7fQj2rsy7p{m_ULG zX1DN@)GuZtBzSg7j3}q#azwOmK|wde_D`Van)y#s6~nMS%@bUuPofN0=^chjNA>w@ zs&CZV&l6NjZV=aPqa@k4W8L%__RZ1{12o)48Yz(_QwsaVYa7U;BqMe^F^H_!EIi09 z^(qWkeavw62W`x7y#^Pqm95P=dkV$iXXqa#;xG@yky{}H06slh%=vWXNzSLh-Bi$+ zn&W{HQ**9-l+Dgt!NaWp37}NytK9-B6z4fK=o8taE&Ls0sdkL~Z-~e<7Yo7;8aDMw zjvpqDQ#a`{Wj>jpf*vy;UvksG(RwGg((xXq3HcqsGy(?q$et^#o>vy z1c|zvM1zRZj>GR`(xDZA940Q>0zA3>IX0Wtf$2YOpSB8B5xtBE)}pcqbSUEO_zi219{{@o_2jvv zk`tQlkzI}~51u67bz+LkF-=VIIPMTrW;#m5l&2k)C@!Wv>!@Mz3yzH}e%Y~^#jiW+ z5lc$IahS!lWCFfK35sQ>Ax#7r@xWBdMoyCYYZ&f2j7$WYDeaWj?k0kabvQh3F;IsQ zH#y?+dy;oQ-0>vx1jKa(WRjWJF^irIHM@}~xN_c17?@zVUyW)f91y+Y6A-Rw#YcF8 zSKNSvQwDch+-;wcK1Dr{Jx*n5zu+mi{H{~}o>m1mas3uhu1t*NVAmuzuPfF)k>pQIFyiKAIhvh2ytP{rU9Fkf9X4AsfI?jk%gTf=wJ_6EVeBA7Dn zFH0$_$t8cDRHnj4)|SlP6?#Fg92H8oJ93#u+>r^FQV(?RdBJ zIoikF(*3o!koG7(P_2x3ehJ#cydi~dpnj;wzalMcg`+=6L@xE~=tL{FrR;ks>X zmzZYVZLCd^o|wt)Lr<)D2N`!|&KaQOxXvd4tRk!>;({a-86-aaaUPw=dxEJvR7k5g$1{oFHTg~d7rC0m| z(>vttja5C$-^a8-fXY z+b<5wqk-;>PsAYYU&2KlsH|X1gbOqb8tqu@!Rnh@R>r-wn0#<74d4)~laP;&(1ynM z;3;{xi5_L#V<_WRT+m>$=!SleffG-gXck?)-Pnrp$X}^(K;p*QqusI#NfdHgK0zU; zGCxjTry>syH1-QU8Swmay(b!7eS|?+WsVm5m;W_mo@1aGWWJUcWK$la0)9|3^#k&q zY?5!-L$G_ey5Jbod9Lr|({O$7uHUvE`h5=5Z$m!epK7n8ChK#qd@NTfM0b=C8@yca z-JzSvhN<5lMHz1gmiRO!TUxp5Cs4ma6|BqOvMzthx_o<_`Y32~dmGu}BBK-Q3!}s1 z(BbFUl2XaGbrUy|Kk*^bdDGaa#+_P3i(VQzSf3E`;WE{s4@&kqghERP z25vQI!$8UI&!CqPlxg@|^u)AzC!T0>X6kOy6HMd3hn`aot3VzdC0L!0^aR-qZ2_@P z8FUJg0>UH^26Ny=RF?)hQxm{7vYs*h*Dm@p5;56>=+-nGB>B4XWPg`M48Dbas&Ob7 zO2^1Uw#%$@=d%q{EfY{?kL7>U{_lJw>(&rk{OxNbWr|f2C++mN+sZE3WGycX)|cPh z=!&-@m+e;4JPMCXQtpLB9c!258CDEt$TtL!zH{59l+!d1ro3ZmXwc%nlm67bZY7$b zwXv`%u6l;m_h)&hxWzZ5D<4(6!W)czqFDmN3Z!R{aYkDOHi1LCAy44Y6$=@MiV@XK zA`TrtLc63f{(yZcRdzvhjMwKKvXH+=!?cAT+7ZB0FtTQ)6nc&PG1yP+-lyYq41f($ zz!FpjfIKu_;?G}zh|p_WqkzXbpg9T%aKNT0po{=G4$$tzLySIkE(ngbUVeW#$kuzt z`N@pX?}^5yD*8(zqS9syHqWEyD@w7|IF4u{fr1@uRf!(#jtzvDTV$??DK^DjA=}Mw3|`f@S~L>58uH`ZpSbR>R2cbNg10; zu`zDg7wja%T||dFx3{1i-_>72J>sVpz9vAi6@k4zwMC5e<$H#Y4KnqQ`Xn{^i6r{r zk;tMS9;r|C!z1;9e!eS#-*=fmRE>ls?G1Qv%*8vT-uo*QnbmArXvl#W;ZUgoH-L#^j`CwNK zr++_eQutVJ^s8;3MM*aE`;G)+1(%%wFi8HN&F?=Fzq`!*9zYcLFZJ2&tm*%^cv@QaF@n9n4O{3y-kgL8YR5cL^$2vFj)T}F#R0DyV)7KHb4u>L+UtA zIS7K}#O%yfME5cCe0Mf}OEI#|{Qd_np%`R;KszZvL+W*Y>*{Lod(Es~_`Mv>8T>Zu zDHD%he-h>LohB~FnaI83XOep^fNRn)%W zj=1G7>jY$aaLRW3$5{TgV-nuuy9yMS>Qgngn<>{mj#?;Rfh`|~O+L(hdTK{K{~#uP zoCyx_4fMBLeFwt{Ox{m1-L?4sm9~#!FCczb>vy(Bu)0uf_3pp9+wpe?s zaXeCy4%aRX7T$a`!srgoZgXaQAO?*{fh#!pd^`I2GwUzB{j-+9DrUuOE z3}CBsI3*v+uMxN0+L2FtpLCQFUR#563d&|D2etdOWq>k%r9tfOX%)oB>Twd>vnP=6 zdM!S!KJW(e!=KVPrPWv7(@Dj{@9OpQ{r7Y*z;?ax9N4qG1yChkH`p*Npe2Iar z@Dm|pVxR&K+CT9prVrwl*&W&hfOJ1ktexGdT^&p7=PY#9OCRS}?R=Kt#xDWPV76Ewd{%?b@D6n0Vag)< zk;RaIi4zevpwBQVi^-4l|9=1e4G6TcYhTlye>Y-3g!tz+vn~VW!MclMtgC``rrsH> zbD`tJSCzK)INy%M_~x75gE`GOpJB||k#Dr0s9wWc8Dj`4?w{ZYjaaQgCec3bOhNk5 zB$UX+PoznXqT~1fp1&y&wVwPLj=8JJJow-*w6I3#GMPU64Mp zY8(DKU#H1)8?3$%9x4S-;c90s+_^0Au{~L0?FSyOqsFbQ8g60pvHH~epq7pd;!weD z*_~C{87BrlLFLsW@ftYYvXJh|n0j#m29=EN0~h&6l>lW-KUl$ScI=Q%p* zPZ!IyNWp`?^K)U75+wN}m>4=1yB-bVBz`!XB|JUnJL*Y95SCv@Mm{2CoXj0|#OXg` z!!G4HNDVvD_JMzoRl5P1tX;)QqzcYz&=Rh}eYMv~4Y$#nUK@%+PDUyIbTEqI+36zu zj(p1KV1GMVPMmf#ir3_%9nriDxo!`X@t&LM=9hLq2+FU)CPy$rO5?Ms8~1@i+EBzU z%VOlxJSEmvMC6mq7pRyA%a94-=YGKbI8Tfm4pp?Vvw*0)hALXMD_P}A&@5!te;toe zI;Brags)TU7t0s7RCH);8hA-D=i9IlGON zPrwy~>g>qeUDes0ES)cBcc2{n37xcxVIb*NhLvuiaBk$+G){^Ljs58Okn_7~QX8>> zgUGHlO=^t+vV_J-o!!c()uU*>JgGz9pEAxT*que1K#2hT6bf=ZA3`p!yAppZe~kBC zO(#>q0;D_`2HcQs12;S=-%1(#E0Spjj4qs=x6|biaL$DR49)I9ITtIp{D8-D3cUB= z5|b^{x(2-*Bw;oXYg>qO9{DpVWiQ)!k(9yLO3L5q^(hEB7%oWS#IK~aom_M80u@fC zw6Br+I@y}gmEMkt?P7}<7>I0D#5gfsZA414>~br&TJ#C|NDH!HgKg?P??UP;Bk`{4 zd$ILM?`t7t^)1I=Fo4b(UDaisPNwEJAjOa7kjBQX4U(T#eOH$jVG;-aSN>=zpCZ=Y z+zCet=X&p?yy=m@ki7fAB5~b^D5cTqT+AMeTW;=v57vpI;rAdH9=QyMZFjY4e`6RS zuej`Y0h-N8JH^fg*`}W|Xd0A^p9^wnGvvW3H#(sbxXO46jMn(I7?KNIF=X8xKpiLh z^%A%Sp18!?yCP2im;JP1q|fMAysrp|U31rhmE|IvQ9YKmjnx4?QR;t#l}o+Z}415JE@*(Ia> z(1JE9m@9w1CIJ)2ebZ3fYpr&tG)n%COFYWG7IEE4;`G`MCb#QIgeU=KitDaG4|T)6 zOI+8&+Pe3=ZP@h1tYpRC8dv&JF|Zp?>V$Xnfjfs5XqG*=0mz)tRy7GZ9=T23{1!>_ znK)N<9wd1(u!-xB5UR>En3fhHjVFw&MD57)A!qY(xsmpU%~!gNb7q=|vVE2l2KL+!Hq}&_F6qNc9%p|d)8<=fs_1k!~>|s)v zNmSzCo6IrP?MpPZd`c$B{Q^(8rW%}!dfIgS(dkm|wQgey|2k?4`dL>N^$hc=e4u)M zXLU}OHiA(GxzB`_CLtjo;QOWGLyw>uM)bX@Y(yK~au>H9OxhT|WNyMFVBYn=X7f14@TurpHHiEBHB1KQzIV$Q{|iv?T@@3n9zS4t}E+ahBk_Uvw$!B z5=kQBy%d8C)+8fpXlMw6Md50ZfbwBsr?kR-(5dV{Sm9^UI{3!nauk{?p*BJasW)m!u}Kp z7prHXB&MB|of+SXetkiI+C#O+J>{lo{PSqGZaRvox4miNAKbstq*k$dF*QXKocENJ zvQr{745SQtiKCgx(Y#M+<`A0m2o0_G{aw&II#W-*1qNRiY5+2S+=_YMQ^$_b#!vIps~XuF!oqpcuBsxOHZSIWY6?Se9jqQKDPZTO{MaFU* zwy^QZ#Y8$z_ zXXA~qE4R$Q@V06fARmTC2m>L z5&ms7?W{je&drhV1H8X*m6(NTw2ovF8udVR|5mY{Ahm+89sZcwfp66s-WQ`EE=fmt zXDkhGt~2}+t!l0Sck*k9F`bf(KC7G=m%Tnm0?ao*5)f zG&sk15wSn1Rh!DwexgSEa{Bn|`)zp)g87T~7q!{3eA-{sCLx`+kF95jzV~Rmmborb z+b>qJJMYS%k8^so5Br}1U*iQZg-pEssg%+Pxlq6RP9MMqG4LB2b@^SaL1}1Tr7p`2 zjn*Vklch`bJs8KwqPY3$gz@)=1#Qcx6WWm#t>NcrJ=28-m)}HYjkgwl6z#Ej0`M!z zTyeOz0vt#Gk)ru1Wxtg1o>;{fmmudfTZ+oJ=`Kq%KWAz90_B^lZ)1Z(FKEfPwesyz=mGemdUAcl~s{yN#cG(k-8M*S-A~|49w) zH-TAF+BZ^4L)coAOD{7t=PFK%yX|w_t&Eox>m!n-9=ZO8RO~`Jt0i!EZ~1t z!8m85(=ytvOoLBX?`*_Kq_j&J|1P^k_q#-~_G-!BWRd(|SzrVs)syi~;=fCe@|hES ziV=hMJ@OlPv(%%wjrKigKi5+4Non*q^7ijW`}@oHd*o&`%v(FX{8Gan)m|Xhrt$VP z-rg-}?_Hxkh#%(vcFr>%45Q(0o1<^S5Dh=oZ}CQU2bE;SOeI>NZ4S_#TP&>?jbXl6u#N?a?Wf@1JC2s7xz7W3NO z)I-yCE;B#=f3Uwtq4!VuD}i{6cJ1+MHUa@hJ?rjF#~rjhUrM*AOXwE$ z3c4+qMME?V+wJMtvj$UK4hafxY$VWiRyrY229-O>9MN@FzHF`Wc$c){8X7TP47`WB z`a)nirfRY$;<~@W-=zh?PKvQ0xQ8b3P#2r;;l zgC<%OX-nnlQDW8Gz|7Cn-ouUZZ=~()qbzKH{TngJ?k%!x)PqR?=rOd0QW!WUb^rxr zJYyA^?(f2Te7{G&{EffSCdK0-Io@7K*M0*K*3UPxxhw})>%2$A;An`!ghuP-5a`{E zk;c^mwnL)NcbL%5Xs0m@l*W9ACF~hZV`-Aj%wmt_%~$?LC@EbZp^(f!$N7OL;8yw3uTFC;0|U6nX7xSp_Y$%f}Rqa&pS zn_1v*a3c|7%x{o)#wajbtv><5k#`aAb;bv(P}W3Rj4ZM03MK*LpSr0#X|Z`v8T^3_ zEUumrVa^Vp-z9%`t3{g%GI=`RpWR{6Zj7cYvpX%?80rPrM-QAmbTL={0aq8u=6;&UjR6v(7{J}R{_U7ge-t{s(_6M+k*nk4kMG1rdQ;z`ZHXFYC zj85XBHj6FRD$zM1l-)5hyG_ex5Gr4ex02Guz>N?d|7i%89a|rAvlI^v@;BI^U+H+! zK}$zAYd5oimwyy%U{?MU>)Fb$M>buShF;zcyXtdfWtMqD!^8lsw=rN-Vh6A<0fLpm z1veVw!Vh4bp($-n%ex=|w^xrv$3G@4frY3jvi%HFPuf%<^_)lO4E4-t5UajLui+Sv zlw@p$!D*s9Rr%Y9L(x}7D^ftfU&nI-*jrj&E2o02vn9JN=AV(UYd3*=`Iadi;ctW~ zo#Eq>WlC%KC`L@N_UHSm;RCo5J_R>&4f-_~M1=Z{*VCe~RKVnBc3r#~+Tye{!ERA+ zncTQTQ(8@hUJiM%6K+%AdkqLJO_=NTN}dqF(ftTXeg~>TMX@PLU4E1<(xV8H zIMW0~I|ebS$+VP@=CV@OnQ{`c(hR6BB-==im^PMmusm9HrPXVHLgbc@!`hOu71G|& z@&RGea67o)U3x;PbR=r0lZ3;#Eg^Z))ofJ%mu_e;Vxvg=E;Ndr6k`<0Y3??Pj}gEa z#iK4_0re|UeF}V_p>$FA4I6YR#`EhNqT`teL&V3k#$}lchdoQku^iIvmUcI0*{FQ6 zVf{Qd-Za3pFUrRFO}4(a>}~igzIsX|{2IX-pNSsK;>72X8eS~~ElBEo&cVn<@q^!k zyYc%Md|bg+TDaEetf!4#?r)j-Pw&mqsT#COV%^WuehUy*3pr^KEghGA*dV0*WDq28 zY!u$4l~T`9ApVQ%UL@{g6zEnBjRL)HV;Y5uMQ0s>VYqM6a|GZw5v|I&Ro9vL zwrIsD8}(;MZcY3=i2K&v`C0KF`8hMn&&ei!-gs7iCIi}opZh=xH@sxOZv{(x6!-nZvI2-}fk`^9YGWK;ml z_&h1renh?0_G0L;-dg~~NbO=Wg>~Knw2$;Zc^doWEcq;wj7GK@5#ty08XNqd^{YH> zt+>!F<2+~l_t#OM-;lfmACfGmBe-G^`_76pl+^~#`&#+#J=ReDEsb;nD~QrJ!F@>W z)KhzL0<(S_GCcC0w1bUUyJ9{tma}y9_zY_%cG$-H=MH7HhJP2MkLq-Wm8iU6p2_Oa z?tz%;{9v!C>ZhjQk5it*7OZTwigfh+HH%%~#W>Ar4SyQLj}z889pSfQ_^ao1hPTJ^ zGfs-@Z$N7qEvqwAzEvjReU}4xsX`f*BnFr|1rOr&V3K+-J`aHD=_8M&QJq*z+^*N? z)(xlAuUax1#fMH}7*bARpBHG@{W8u*)2>+szM?VYH`jKTx)?p!Hw&IvJqG7{Y8o0* zX0hd;;yd5M+rhi>BE1m`KQ%P?0own|Xe8*{ z-KQ$1?VsrR&)Mlq_+RK-`_KPR=@V;jkH}xi2e8$Rsg|AH(K#NBj}P|(&5IzCXYeu8 zT7h|U$ek=VCWf3JnO53~T#n1It&z{q zk)(`IXmd%N_(9lj^snM_bbjsF?t)f>@_29h1f4zI3gM+^905*=%CWJZz~n*-z5!Pk zpPIqvNN9bUF0MZSmSa!6n$Fg6#RtE2OrC^7Y9Wj7nyE^R?(8NATLVL2);H2jl%y&!;Gs5iZ9IXvfrdUnB-aJ3za%*RgKlo`D<& zv&VwF;CHSvYBw00@+pj<`r|>?kQ?_beNV93pifR_A9KL|KEBWqzY0wA?!k@KT*cbN zYLWCLsXI{BgZA*%uzI(QrU7qh8-Wb;~El%_t{5>;1(sSo$&5* zgGcZluNZh7gXOjy_b8+9zY15OZe;VHv$D%32KLhkKeU+!x3a6B7^HCpIxotxcYf7Z zfCosxPv#$e>6_vr-w37qyX5bD3s-N!<7Hxi))jb~$Io~1G)N5onj=dzkP!-XF%Wt( zaMsdM*4_P z)Z?tS#^0KjR*&?!3K7>2wvSVIT3e8)hQYo3IUoZo^L6q$aqR_~F|& z^M$~{^2;TECj89+ov%wFD>fi@ryIOx*LmQ-sBYa9NIr46j@xmy z<3qO4&V`&crX8#ycccYvRG!9|uYoD8Jbk4Y>tbvT8lQTN#}!DvI!DovTT&`@PU1nR%ZKs@#;`k-&fDDL7gyv141c*B$MTfwalxb z)7FO1t(F!hF2r6nT_=}RPHRC$QrsQ1mm1SvE&i{p|2SV=0R3%u$%p*MaK|ZPtr&xk zo-P~74TsyCxz%H7ir~1LkEh$xf_-Npry3vB*lEelc&~iOce`AnIc>SPs){|eS(txUDqf0Cgf_01DF4Pl(h`2v6T-^pRVN%gR67`c z5tL!6vk4pESODP~xZb|pqTS2PHBa;JWA4Xmo0!pH)YQ-i@T*R&&U7hx_-uhk{t`D| zEir!M4gF7G?V8kUz@&^%d;{3`Fq>2dlC<=8XM9|K&|nknDL?;smv1A$WYRt|eT&7W zUd9``wp&9rQT*{7e*(Lfg*SVf)Mw~rK)s0r7!s^__y;v%OQZUs-L(m;<`}&{Ngw>P zc{<_e(c4pDPK=k}Y^n_?;Gtl{%Xb}@c6z;5k~i5=OuZBLhgmLrvk33ZZiHY6S$Jpm zq9%VLN`Sj;wa<>ujr{{`0^z;8+tGOV3-fsu&igv>t_J<{E#$gfAcg$Y3N670;M<{g zdZ3R#hhAf+9qfJWYNCKH{fa=xq$1W|HYp{A#sp7@!7PRd_Z{gY`0OQ{b@ad*WZ3-d zMQA|`{uw>Miy<2+BCq`kJkZX8zNdKz)s~mjPTC$kWm?Pe{y5IMM`CLyqoJ&jy(}on zV;$<(@8N}QH~ugNVYO`Vs0Ik>Cxyo1Q=~H;wsN{eb2GFA7Q^{VipKv6QQg~UK)rzV ztCA{U|38opm?6LFQ=c;IzEL`Iu~v%6Z}R(3VpTkf0^_wn#aDz+u&=y;eFldwGjW){ z)yg(J9D;u-VrNu%LT@y6qBP78P@i%i~IWD3_YQ-CQQG1W>cH$0MG>!L~ zu$2=A8m`ac7O5lrO4=K-b^oL;Sb|BNu9SN0hI?SAnZ)4U+Q><5TBebv`vWi)lW?t= z$~g=;BieLcWcWXjv$e-*p@L&}Uq6X^LB=a@EBXWOXD=YZ{kaTd;tD-Ps^RpX#!brW z24IFz#{UUxM>xlIh{%0P!g5k-!rcf=){w#Koj~sPehA0?DQD=x#!W~P*b8j_(|yIN z{UUA6yKyAvp(8m~t|`FE(Ktr^K~(pjwz(CKLJT~g0KB2?G<5DzE$M+1yJD6uXIo27 z{JITD=LG9lPfDw2e3CZ6v|t=6Gy4vbclrh^({NFs-r~%tUvu1<(SixpyR&jtUrT>6 z@Bzjf8%UJ(M${fTLkXZdn12pyjT)uP*Ki2Ms-xf%X9)XZ&D!0>&gctx`gvw<=m{c8 zr!+UTj!<~yz0_UoM0s%SLBn?xU)TAw`k}MaJyfSVqhZa7jJ;wY4_uB>J_GcCa%48M ztpLpH(RNvbZd_3dq@j*Z2flTQBR-aew+qF=vNrmT)+oEC#mqa_#KllU=G z{+3gIf*A{WuSV|!ay`U6I7kXj!#B3~iNTxt@qPub!icf&`x*QLqsWXqLqnZS>x|i$ zsvH4xAido*a52!32aNrQgTDS?E}JCecy|+f0!f0lm{9;5!D!0(MpWj2Q*^}=7k?NB zD~U_^Jnkmqq|S6LqXVL3If>&;G0;xW#PVC=AJ2)y*G)4=V~&LkWsY<**R|Y6pF>4~ zxXeLf;7O3>S5wnY;j6OCFVx&97_}#W11eUH1yEAP*20&EI^d~9+&&?aIjHD_T#en*UBqVyY@8me!@8BuSd^h+{!y@^t@82pqJ=%Mo4pTbCjC;FF*q-H z?G~gDM#6>7)xrFG(l|-QRl|V}-4t}u$db$D3~eWckU|3;E^Ijth9=2f;`)e{Mn3Z? zx02Ze& zf3SZcx372>leQT69WXf+TmbZ9f*cI>9K7FOryID(P{r*%8LHz3ZfvbH^ztcEU1fFz z7s7+@V5FQ%Nf)*)+%Uq*;97UvL3hI!FmR9UCmimS?{GM-46g+c-@N@IK71ASi>`-l zy9KuGDxeSD1KT$CeB2~V4*7n8&xlWoxNs^Iq9Ah>6#|jJw*ktrjx|%9uwQ+@dVKb^Xts?;a@JJ2`{qRUufqo`q z>ul-^0@3nEq5iNa&g!Xv)zIKI zx`tZy4?1XnI|Wt-_qpVEwn8E?3)5!_>D@1S{{(x(lJ`xTHuUQ^jj zv>p|s)+Hbf%|(Ly`#Ewm{}|x2G-_-V^<9t@18t0Z(jV>E;jV&@XmYhEqMwNqo zX7Ff$L(Vp1{*{^1vDHFw-*=!ihp=yP;wzotTxa;-`bRq7l|s(eFuDxXFa|>}B0>6$ z{o@v76k64%pFrgn>Jz4bRJXF4eaX%(PqkuA5%LXa$5PCOFWKS$U6z&xH6E9IM9vHcez30M5pVi7WBvT>MiN=!!0>*ax{1eSo-U45MiLN~G%EKwVWtPksjR zckKKhh|d>``%wE^Z?vDyj6211CsGH-miU(1ahYObI#uY43h2o7*Wu3%*h4>e;$Q+t z&;d#~wB=yGycchd4xsNF+Pbs_1o&2?4@QOtIGMOX+VF`(+ZinzdAKi@h&=T{bQv5O z!B{cw$|S~-$%yKnf|XJ`AB_qjyAe~v&f}P-VhfZ0i65u@LmgZ~{dO|WAW7mf z5Rypmfx!-Hm=`YvE_`f44AcYUWH^vfN0S$M!-9N>KBYcTJ^cusG_NSf@pRp-vu4e5 zHgsOn_7yWX(OO1@l&?fr3l5(L*3sar+j-UNqvh%dv0j*{frS#(4ZPG~ypoJ z3&zgot{U|QWQNk6+nMc5cq|gpKLN6n4sg40v=aUoo$KH|Wi^o%RWT|N?Z595##wD5GJr`6Y0BKl5x9KpY!%%mc|i{ zFUq@EZFLfI846b@S;qlm)_4I}r=x<1`~gYg!J8=T#Mn zLt%JYxSzo~q%J{&#s?@@Uc8NZZ1Bps?PaWI)F~jg$xjqeu`qT!sEd%-Rk(}5@!`6G z`Z9`v`S>u_eB64#S9`!qgVNdpU`4-a|M`U41r-a{XNiyR(dlXX zWUj2TZFKz}8$+BE8s)%0ut6iqIt=E!hq56A-(7fO!$8c>MbC-(=hRi8F^qtE@FB(v zV{p~EI43c9Ay(7=_h^u7G?SrbG!l%*_PE=YB!W_QOq46uk z%~}k}CRE86Tk8oOBAG3mcY)*vnH%l-OJzRNL_lK6%{-bqnqw1mY~FauUV z!f|F6W5*rnCoB~{UGH3bv=V6ihw0RJIK8+cQ3oKNcz=hUk}<-eSp!4mw$Oc6^#PzG zN!}#}MBs2@NSOXLv2U*7)c!ez6`*@Rfyco$IN`j+&h4N;sC)sM15G(YvlH=o z<|IrOE?^gtt$Gl-`0Uo*{JR;jA!1+#n(?=L z#o(PNL39KbQr~dYkIPW!h!1_h+T%S%`_=ysY5{!G@KO1CAS;13C_uCxK8X3k!KKK!i{#B-q;C~whk78<`c#s zdP_@r+EIxT>MoF?{vGvjS*8llAfz6IV1tVp1F|pm>W%0)7Jh8@A@e}zU7SSyqc+jO z3`SNAR&PdeqkD$k3oXO96>)e)Yhm?9)?Snbk++-5+YPTHxq`@HYY<=P_3=yj7r~i; zg~awH=Ve+wlPh%wP+RMAV~M!u{|)lq!!O}#2w$T!s}Kr`Z4K7q3%Vy0cNGqeMT zf1mMz7~1ynWZ?oWnj7ou9l&oq`dB^M5bw0pN!Bh1( zt5%j+SAu%bF{tl8s)dm{O4onjIsEuZA3+hE^mqeHB9{sck27Cuv64VmZ!$C0_ zd9v)O{0e*2)>$Mg=(nDl#Nmh-`G{v%nV7(KWNp&g*LuYTUC{R;^*zu3R`N%tAj_K}C{mVe+*=Gx;Y zL5M5#7t&6)QL73VAG~hoKVku|@Bts_gB&D&$^$ z;$K44i!k`xD1~ol*zhkqaMy#K=Z7wlw#GbD`P0k9AbtN0*nBTx$ALs- zIyR|DVgG;%ymiBrS5p-}D0Ih?XI37A0f$w@EH9|k9xoBRgDlFa0GHm9nDmmIm2f%x zGacwCL-G+Z`K6i52Mi>_-0+fP8wUvJGNf3&|Mv!0f{~T!Y+1vPz`Y8_O6^@TP8XsftW;QkNJC3> zyJV~cuq$i>)K+-!H{W-7q`&S1j@`x8q+lRz_2d+dWoHBmAp6^>(l1Ig-=eXQ67UWEyzQBF6Fe9&Yd0wgATUcC{x7hAm znrDwuC>WbUboXPW;4WIeu&`h;2rXT<&}T36l@=7iH2M~n-8K5g?#QR-O!M4EP24=) zzG{`d_aZZ9!p_b3Z)K*tr%n<|7MA7}`Oe;wbNY1WZ93E99lgm4bRdkDwj)_sid7Q%3ZfpFV4!s~w$VLd`Nywm3q=OfHV z$U!Iq>~6#tBP1Y1kY0~)J)YNKqi_Yn-3YfJR3WtgB@#J+pdu_qXhs;mDH0ilkcr?y zI0IM>;!1=Pgst#KHzGE7z+0NP(3dxZjmFK3?u2lea|MCtJ4;I!t~^_|p6@R55I?Ig?4fq5ekiiCNk!aScaZ=o=6kuYz?h}5fB2v^41I@{C9zky$W zk3HcBncrI z!Tu7;BYgidWaJf$AHwT+K7wGYg=s;krT};|f(vt(17R!zz4x~pp#;x&AWTEZLa0O- z^(yco3`ck!X)9t0VFkicgk*#{2uBdUN3hlb7vlF3TJXFPVJku^LJh)kgyAso0}(#P z^BIJ#J0g({2%9M04!npT#M#VlgqIP%NBUXBwuVULB;o@I^$5vGe~f+EW`vg!en9#Z z;ztmw5Y{3rL3j{h@$N`uCV~sWfiMqY!yfb#aVkP0f`sr^6L2CNM>vD~4|0Hz{(li{J<7K4n+dhay z&O;c6u&um6C7gA(AR_x9_VPc9tR=>l!%5)S&5ApM|L z?jmog2QvrzB^MxNztNs*zv2peap7XjA0rtk(|*m>DP*E~iA-||`;Fu6$>l|51xt!B zeH0Y=Qj$kgrIZvw$2XoAFIzhL#)XB2#op2M1l&}UmHY{2Q-NMko9X>e_`6sCS*;2G zOMjBf!GUYXF7T!N4{aGmqYQ%`UkpR%EiWB?W5MDT_Dp+8>GIJx<`w2G!^Br60KBL? z-+t+M`_x$;k8l|*;$^~R><_numodI<-Va?ts7F|VP=io%40>NQ22SF$)E%xS> zmAQR+%NYG^K9~+(b{VrUEU%Ip;dI51j_ zS3wF$?SziSNiPa@hx1um^p*w3mWas%$~Mj>dmekkMp(^ffwbo zv7Jif`N(%-v zbMrm0*N6`wo`XFr+r6;eNT0U~wwB$fp7FI;-!e`-)md?C~Pbam{uT8LR|eA@FCuSxCF8EIP5>-rHD5p zp7R83KVsXHkgh(mK%D+-=mBEeZ!oVQ-ikQchWw{c4sr6+m~RmuK>PyY zcEt6FlYWc&4zcZbpaVv;dL!ly#Lb8wM7-e{wlf% z=P_RlMEme%W+GkzUu7xcO^8Wz-f*747{7C~cQN0<#2V7qet_cZ=>*FRQBq{MZ3HArA&V=N3eOw8t z{=RXm`zItj6YR)CnF$FtC&X1qLZGdD2kgjvst;QIajW`Mb~2I<pgIv$L7CD_*y9sWMk5>n$PfC@m+TTX>*fZ###2T|7! z8}=b|kLo@|b=SvFNU$r`2?@y$_HibpuIuYeNcZ=1C2X|BJNoudNY7441zwc-X`em@ zzRlRt_%+coDdDgs?%@QmUYLo{j=Zt!a5M`0C`|c}ZB0&?7JqR(nnLqGX)11Yf^Aph zTW`PfE`9hM{fDf)AV(rFm+VL5ycn=efPG7C{kMMI5kD&KtZlgl5dO`;HBX7*&jM^W zU~>SYyPjOm3^|&M@yTK1Gj%{|bgWzhV&f)300^%Qv^my8-|{;=SLfMTd`)qf-(?COPJV67=(;Sp_z%RgIKKqfKECehSu!{+T(VqcH zAvp;Eo(}kNg2(s*JEhxwm30bh9WNf2mZsGtRAQMdx z_kiK39ssVbz;y%RVmfp?v%S9pe~53-BL4vL$xajBrUGuquu(e_$}L8@p6w70dJW>hf5Ace@gT|x@Bx=v5XX2Ln>*0GxE(zP4F-(ltr>O4 z!f%Az!+u25$AF~*Mm(nZ)gN~Y>pPWi*UK-XJp2eY(1s5}qWski!flEju-5^*onVYd z7(*L+j=!z}lh`=%=bR{R@?BNbxl_kOyju#G3;yf3x{Us0Zi9?Q=eBA0VFm!8$83{> zF(F(p16MnI-=_ZsS61A2XGJ!N^NltgR>41hSf`cvFc>f!eC5k@*jU8LfH?pgMQtuf zNQ`$|5^PdJB1!6m0hniOIRoxue;4DgYk(KrpfSAz^@hWrW~aZsj45e_*&WD6KdJ81 zsGInwNaPp)NgnEZR7Sr@=E(p59(9xPOyj2eEV&7GcS6!6rXp<21^60gP|lEpIVe6D zF!KHBUpqI|oeVn{HrdZyJ_9l5M~cF;2biBBAo@H9drNec0JjJ0h5ts^29%qLau1>o z{b*cX0IUVDLI%_>i|1vPrtDw*&w4C%)U{gnA3a=d4mmLoZY!Y{0{je?%g+y^a0< zVeh@8t17y<;hA%DlUs6=8xlh3X+RQ6LP8*f7JBFjH5M?4G^Hs;R7w;9R_e4BXmf4COIn^%=OI}$?(Rdys# zJ?oU2**Gg4A%;L0Ci7)p^Ub;U=+RAJi;5rep>bGJJO0*y_TB%1mip`jZ3V6YPi-8R zl*>8PU{PU6&>0_9$Y}6=3_h-N-~UhjMfAM7Mtiy+yKbN{`T_iU;9B%oTx*U8tVVEN zhFO)Jmo0e;0skznE%qn)%8*?RTCSHrWb4shmtejgeO)pkq0HG4NGKT7`04}~+l>30 zA!FOa=m!AvVA_L7yBBHFw*#-nZ$HxZAT1OFq#*_yt{`U0Q10KZy>N|!Yp)TFjRPNedEMR@yt4sE>DLzK2k#=H}v>2=jET41qXcjq-O9-=$(~vd< zbW51l(0_7X*wDYB(X(F|0@}@>9cXFSIOoV%$e680-hSjA&)92_1FJl@BFt>imIpDp zv+%HB1xPo8@5jC9YiP)^>arnbhbJ7?o^i*HS?Nz8L6zb66=W7*4I;O3ES5Kp#Y(vO zF2v%H#?@u#@CDGP&1*-G{z;+G%g*63sk3v{ap>JJhsqfoHO<3SHrwGm@E!v16}EiK z+c3`^16~J{t47Qtg3fA_G}ygfM*l>4k3wF-8>n;4Wukm^tk|hY*yDJ*^CI@|(+XH- z;+Shahu^`yjmG+6@@s8l&+T9$+(^Gsa$Q;u-lyMfSOa0TorttONONP7Z~gO`@Soj@9J|xy#J$e9WhVf(bJxX+4E$3o@LL= z?0KC%Z?flG?fHIte%783+ViLO{IfmBVr=G-YR?_*xu-o3v**e7Jjko@cdI)L5%!rg#i~Q{KTE zmp}a+Lo~>Gxhczu(2SFH(Choq1miD4dkLYf9m_DjPf~f_>c{_G@h`>~R{)+ECvGkp zu{Xwxd-f{UcM9`)X%eGjxf<`8N;oF2n6y6czfm4eTqBLqzTqGM-3Xo>?_`whtR~%> zMPPEa!pkw|3s_9>VgcKtuZr0~FwPh2qjcYg;17G-eFfP6Ih9G@4p8}DM(^XzxR)f# z^BhjlxijPkxY;l^)8SQltZy$eg2#a^uD5_KpHD6ADt|5#e7{gbpZ^P#=KGa!*gqPD_>K@x z^MAzL-w3DsuZ0f2-wEgV%TSE(55fih9fbcRT;kW1^B3W=#O@lf(nQJ=r^9fc7NjEa zF(mmML8=mY=i3*fci>c=IEp;6ns>YBB=%&1K22>GCHA9M@q#Q#tS6G7uRzZ7M1EM) z*GvxqQj>TE6-d@=ft;6kItxwF_e0v6#P5;hOViH+S)VAk$usm8q}3+=NCR^96F@d6 z{!S!MkorUi-uHDDWLu)2NEbnNCx(f1(~R2(6IT(*7v1(G9#7r6Yu>zgS~+*X`TiW< zc?q8bumgakV*KN;8ABh$mjg}!P^B;$LHE>uZ zzX_?nO}-c4gfw3rz3A8IgM8N$RQ?uJZ43SB^H=i!R=P8cC}oVq`WSLajCuOSDirIZ zDw6uHo0_ar_~FYy4z25>nI5>D?(J*fo2gkpft`Cn7#zoV3r^<09V|Y$0<{^uUBGdu zq~J~g7ohz3R@5n}3jc;fd~@aLi&OAKaK*P~iti;b>lx!x; zzrDcH7%eFGF%%K&V+le;nTAM=iuLiC4as2%CWpHv!86j$k@6@QiKpPy@_drANFhj1z$ zP*>q4bYYsG%`&`{>FNI8QA~Il#pIxRI^Z1Zqec!32%kwXeFi`?28P%1W=p~qsBo2V zJ!(*^jI{98=TeK-l)9NEDF2z%;u^v}e|Jn~!q-alsSIEE`b{L>f<7?3MTnJe62u({ zU(yN!B>ux2j4h1+@TDgHXPu(LmofQ#@kzLr@OuAV2n%ndoLc`ughBXnO4;n^b4B4R zdm^{qzZK%cSEVC;Tk4V-i&XeJ8nN4d3&r0+-S@%h3OFLx_Z%cS)F*r+^~{_LF5Q!{ z?)OyRm5yAcvfe{M`VSAAN|~MEl}iqi>+(=?_6+6|;?dzc_zyTWUP?U6;04IvMf?pY zf_#prp8@Z9UQnq@E4V!pcxb$25c!?1KwdiNbD9$5UXL7Sq?v|JL~Z{VKx`*>j=wZeE{*l1pl+}Q~F1-dUtOkN?m47 zuzELiqt!o()q|m6ho5QHk1{n}0kBKg>5G*9$>W9bo{ig9bz?`Qe-U}QBCnU#jDn~P zC{SiK3eevOO|>nv!P6^X1k3zJ%DgX9<~LF%_jhHp$ZtF(oB>oEd%BeqaqM@VVds33Uev;mOVH`=;uU~%G!mf){h8M z!D_|PP>a-_9(_a#dIBn#T6{!2`YUi#i;qTXvFjN#mni+1(X%4&O)Jv4!Pa6SZ@BAQ z49#9^%Pqzh36fhxd;u%#7O5J7m^39TinMJQFsXCd`vMt#;jtKu>#qCmu7 zX4Dtamx<`^YuIhniu+YoJqpsN3jd;>M%m{hIuF9QM4e9+ftkT^IM&tc&f1KLQp zZ&A7WG~stP4(U35>2js#Ntier%Zs;Ik>w!&u2GX;fobdyHg^$n6Y-10rn3^ct*!Os z?b;|8ym^H-_hnp78a_rbd%eOOM)AqUB@|7UoNQ`Tv_Dz2N3_xO!9us2my4SywE125hFijooVa~NEqUS#1LxjH^LDcKzE=s*-1=Z5WBR$`t_)hO5owNKB zCieaem|1xehU^|J7hO7qo#@GgaA7zPV_OLmh2aNawuJjFLj@TodM3CG%Mb`9OcaJ! zfx(AKmiX;rrA`MWJKL4`U+MUds^ZgZ?lrBHxH*i>nz15;4j^Pe>}b; z@{>jWAo80<;#i@vzESvJ$VETh&eVbvHnq@BoY{lZMcv-R)b6uknNAlnGj&QvY9CIW zjt)>+ej1r9x%Gue!_n59P({-~$tp?~sV~C0dAlr8Gbqx~V)Ov?)*IMo#fzvKOO+ZC zb&nMlFQQ^lYW9+4B>xH|bZXyA-QpW0Lnx=x`l!Rf^gwqb?dFKkDOg_3#?$}=^hhm; zdO%$zW%L^Wr@wA_$`~z5SMYgmQO|r(bgZn{rOt(xsBRaNkhQQ{02NdIxItsl$mHbiYdr`>_mgz8inxCDc z(4>U0D9TZyxWrOW(Syitr06)CDTJ6Mt;~nChmg`p}zZBgj&3v`N z%&)A>!|X3!b=E>)31Knw8qgOzQD&Zq>_%p`jW#nk+RPmmv1VRo(E)#tLT3eJj@Jd9 zbrlCFmDQ8Y_F2&}-KzI!#N?ZsTa%v^lYWAV#^h(k$9w#KcyOP4rrHB&0w>bl>9ED(dq=hVU-YZg&?WxuHJix3I?T664lGIb+-| zG43aq6w2o_Y2eot!Zl&6V%KljgcrY~9MvRhsKou~nnxvLbJ4ur6RV zl3w5zCmRAv2#a;qpf7G|DQMmAO|2`inPOdEi->hQEMl#@%A)^ho%BT^Sfba6uHnwc zs?Q_Fjlj6bX~P=P{!!}I*%D2HZkI!KhR8Ccm)P{_NWU|Zjv*MOln%F?F>R4WXxf0S7U9s~hf*k&Q+zex6`R2ZnHiOl_m$1yge?-rx)39z-?Qiv zYoW^8hS`H&A?6>>GpS&ZL)X!fG=GJde-%2)GR{#}3i-=G^I~l&q;z#s1NX3dp5v5R zfjc4sIY*$tIU+Cy-B9TqOC;Vn%Oc{9i!5>hU2|PDvNIZaED9O$Y!uSnST24leZywR zg-ia)?3yY>2~GbpLMsJTS(7;V8|`rUrNA=9M+~4h`}VcTS3ofs?T;*|;AxWaG`+8* zKLjB!Ci;rhA}%Ha9tA-KT)_f79psSm zxx-Lg!GMA{nVy6kv+QFy-bn>6*Xqrj0@(Clv`(|^)6C(r&xMHmGP#&c7Co*(T$FK{ zSEopct6)W$TwpdAqdP+Ho(JghRLPISr9j!KE_NNTA#}@+P|NQ*w85y^YXA)EYIWxm z0J^P{57JK9390yQ-V!GEt^&4FE&+OF0o-R93doS|tRzDb($8s!_q$M{OTblK{Vx&r zCGtv$i?GYhQcLlL9gQBmVy%Z5_%^h0=jtnCva~Nf+n3%^6>qmBJ3#WHAwe&J-oWn} zd$Hl>l@*qTiob~>{nd~L7b_!ZnaboOeib+?Igcm}bTZ8MLvHDGi?Ayy-5-S%e`paF zKN>E8hD~L1f+7l({uw2+cxX<8Z7U3CQ`=@*5(>Y~ko<#fc@Esl;~9`jOF1E8JUr1m zf>e3}-5pGr8tT}#09L%TfK=sSY!1AOvFUg4@)S9|=7h-YhpI9y!0&Li=%#eE0Iwxu z=hDyu_+~x*p4>(&$S?w)i?jeAQcMeYPZNF@a!d;_95*muTEKgnE#U4-sk=*0k16(4$9g6XL)TKr}iTE){;*k!c z^_MyW(I`QB4(hLji4vq=N^MHSe{LBn$ndpZNCrmyxu`&i_(lXK;twOQgbX5Vv#CE> zeJF)tJa$u>$El|@E>~>N0|ygjF~JOvrCZhV(L||I4PogY+*= zy5BRmAt*52QpJh3SQ0f)mui${3B=FBCZz2w>8Bv=Dx{SnqA)7GQqsX(*}$v|Rq1?7 zBW>yRD5Ur^i%@~fq7{(YV5PGfO8y*G!fm!>rUy{@JgCu&>0)`)lJkDBG?6y4G@@D4 zHsUdD#ElBBpU7>bQ&0p0Mp{ZsZ>gDTA<}zJbjf5-6a?lM_>efjafciYK@R_<@LQlOA3I> zu<`~AdZ@cnBhQRM2?yzq-wT0`Wta+j5*q)=tT>==lDJndpy?!ji#mKnv<~S8RK};biGI`%Xn!Aw-y!oTymhEs=ma!szo(Cz`V*LRnodTlXJhT^ zvXf8&EhPvZfy}a#<|*Au`lbs9LNfQ+>(*{etS-JL6@}yY4iUZ$tw4HtSGFqMX|ol) zT%3T(f=m0hOY3UKPJ!)xwsjc=^2u@$IP_w}=@Q*xi8u_lgTd_Ep*u%3%6Q6V2T^K& zlP#BVl`$J@R>xww##RH%m}>I<9#;{Sw;e-?tEUtn$x_9=tmssT-V!AmO?9wU64x*3 zMgW`r{ShKnJiw;2lX=;uL*KmPqUceez2roW8poMSVyY2W>%jNl7IwfgBfD|w!W-q8 zE_^5Isf4uBg?Aey%+iH#wiR%Qg3i}7AW6FL)WP6jqIBU807J=KqCRRFDm*Ot3YUKU z5YRJC=!Xp@y$n(BVYt`e`z7Qr3AG7?>z3y$sB*P~V98AdkajxLW_@mhj?m zFfdU{NGT_cm~gLU;C@bxfxLz~UJbZX zcUQ%iSdxrhXiJ79X5;6pV%@Rwi>Ul$4Dk@*=ErP2j9Uh!Q>^c$M7JV1@hLInh$>Y) z%es$K*U2W`w=6Piz1Ze*W_PaT;ye+--E`LKx|O7N8q&xNbA`-W{ho=D1~JW60^93L z5p`VK>TE8R{lw-jgR%#0I0Ipx=*r0iJGhSwyf~(5T zu}Iy^$2#;W%){dMUOuNWVGRd^Cz+ut`521c{R9Qv2%?Eku#N7{_F@Ra1?*i+o4|*j z3>dk5B^xFtoqZ57n;p#!BI4VKh#M$k;tP^>CTJGz z&thrQMq`E|*YC6TBL6X>;@4U9bDJ?niP=9h9Sq$T1 zh+ucKOsnELo6c6qMJTpEb7{E@gg@9^#_r{Y3zuUT8*QWyh`L^Le9I(!UFB7&gXJI| zhrybcWsyibD8Izy{#{N@V>zad=ozV_X3E1Vn7oyBG_t0dk@BE9p?>JI;c};TBrEV# zcxD8`U=(X$_Bb;;8{HK-r&OR*-eLLKQw(@83ONyC;3czE^i5R8fIp&;^P;E*w6pG_ ztm~pON;}vLIzm;xhGw7#P#w(yb)qZ;4H$(ihHDu!ROLIc&XH%|JU@VFJRTC8{3M&B zJnS_Es{~3-VMBGF{$^X}q3b?^ah;YRYZnZxS`DE0di)Dj+JNeAYXZ4S-?M_auz^23AZm{41WIYI-J3R@Y#dwtW5`f+Z@oyP0X-S)H;k97ZTW6DvhQE#c z5~hjae*w$8{htk|4j(i%oTcjt$FpobIj3oMRTxE8HOIfazy5docZmJ<(e|^$bhoQ) zkJz7707H%Q09DNa^lpcL9{>~kgUt=k0i5)OvvK_-_IH^G`r%VJ>|2qw7>0J*4WMKafJXtm3842r z0C!Es_tVMlg)ux_LtjIQ$CFu45Z2d0cp_OL_1va5#R zUujp%NVW5-EYkR zT9T4t2T93u06^ZUmaM^ABo48O6JY~tjZa`f1wNKlh7=xs%n2&2g0`K8AuIMc02Khv zA#fsqdja(3%iO*Oa01z@EqhDLKAp_usW{&9EF(a1bpUdj(pP;7EAp0DDGfTaITec= zA939Xx(Z9jOBQPes|k-*!WupmS8oY!j9BwX4-|g^vYwzG8v(RC0l@VDrU1y@1^|z5 z_U0?>YLRdP#blKjetXHS`cepazjk?(Pqbot1ITGgUv)3sQ1vMO<@K?m{tu=V-)%ML zven5bj&ZVaP7@=G_ggBu{%lLN81eqTP3O2@`dbu|*XEezn>%jLM_fB)M^LFlmQ+S| zI6&9&sjE(pAZsDbeFnhG1YQ9Uhta;%djQG+Tt4vUd^_z07{y=g>3ZsyxDMbisc>pnOsV*D-kaJBlO+C}ccyjvtFtP=u!b3Ca95A3AjnWb@}7o%%Gg_;bFV+8ihToDWWV2br(n7f^(zCgEa)ztnM4 zH^YAZoKL2XKo)<_p=o^dn!n_Sbafd-48t$wR!wl)9JrUi*wggXTX5ztbjsBAP@ccg zO!Xc*-x$>j1=#&{V__us*EQV;xQnHs@nL@*!e*D5_YcBmmznoZ!e*D5_b}4 z3sMny3`t%`kgC8UIM^GbcSF7E0C$;rV>REOGAF>BLtdYzwu=JXW#)|+WJ#c&NP=F3 zoaF)TGV?am!+_KTxXa9&%n?ML7vL^4Z;E~t($)mNN0K*9?+3Czz+GnE41EsLY6IM5 z=FQdXfNTzMmzg(Dkoo|3nRz=4vMs<}X5KD>><)y9*j;7;K7Z=X7v1&*xXaAj9Zyk3 zcA3TIWC96tmsw~0;``tqe<9N`m|bRZYB`c*msy+|169l}GXtAlW^rmAlFcqN1BX=t zcbR$ZE;DZ(y(qiPyw?*nyUe^>=uflD%)9kuBy*S9YNW;bt_2p)Friv` zsmUslZVYpmnYWK-dYHS+ynPLPGemg%2^{OY3P=(+nE7_Fcrn&62 zY%V)3o6Ani=4Pj*Ylo5?jyqP?jtEKjVl^X&rguvu={91gt_;<4a}v4 z-Q7oIjxo%A$=tB}==KQn04}-MlFJCYyN}51Gt7g?+_3xT^9b|M7aEv{5q5VUkvVLb z%gLOiTE2^ZO_#@1%tjUtv=%vZ9p72lY7w8*D34*B9>MV0dXgn;f$p)5T`o{KPM+gr&gS4KWgF#$Mp%fCe;R4IjbQy)<<>QUPr-sBDmTzLO(fhc@yWVo)<%SYqhI-o)rEKaHD!& z3}e4^x~pnuk+=>xRqZ0w{{ohQz?MT)z1|{2GN9_tD8#5rB`LP;7S>ZM5m`Ue@IPoy z@b4ig)(TI-nEcjoY1RlD)k?gnCuT2M7(pttj+iVuR*$D{t)+4+!InPP#WH5#mMw11 znY@nQ;|kO*_dqK!wvOrKAOKc_EQblXL16Cy!_hX2%HHH*RoB8|9`g1pobgB>dXIL8 z4FxOvj#FHZ-sEB4r@+$@3O9S0+YZLI-%gEh@z4<-xTt+UhQ_TP*2nPKn45DqsP@Hl z<^${hMW>061e}iNCq0aSKh(w39|oF!pHUt>r-XR264QsTz?s0WCOvQ>V0 zRN1#4Tio-g2p%l)Ks1TpGo3czFa~D)W+~65XG*Ozh^UNDZ2k9vP8lazy3t)=&~l5g zyW@z$E)UwO{IyY3oYojiRqN}mVB7Ivl?IK2Cbu2SXgXV}bCE5t4b{V;>!?R7IR0#L ze9@^$Y&)4%yI2@1EJHgXC&S&AVX-jG6NY!lP~~BNQH#!qhrBxuXI-}%(Z%Lx@^1k{ zE5vV=#Bb6(V6uo`C3~LE1D=166+zE-hGg5@qoUy2D@3|=ZT|03q~nAXG!vuTU#Rj& zR~r_JTkO(|ZA4RWdKAsoE=`lN)<)6XYiO__#yUnJH%4W?gEZ@M3bi~GTMy%gc-oVG(YWW$qqDXtZ(1K`qW1`G~QA8r;_yz(m zo_pgPp<9NanjYmTu&y1i%z=IsV%x35KjgCW`g0!e?S`6xws1azc%Efr5Vx+eNaHcP z8AY@d5sPr0&4aTz9=h&$EPF9pT1tGp1d%2_T1q%>Tfkfng(m+Ha5EH|{IrFxN-d?g zbTF@PFL$Y034bZ^n^?ZVd!7#bakdYGC!Ar%BZIvJti2fATC|-BM|YP;L&`kGb`b2G z4xc3_fN%}P<_h7kC8&{0Fl*t#{)Qbo8|n)DpBUP>9^H+3$Pk@PW>+i9&Ok8uf+(;; zMJQAkx3pnKq^d%J9E}EvRVz?j#zZTHj?bKK5$kw9n4$7_T4%F|SOfu72rr?@Z)+7D zhV&avdgONUDwPRkV(@$i!VZscY{MfQ`CJR(5#J5h#g<{ERo5_pA5FtPkeR>9YD0Ry zw-5z4DIj6vy>Q9m29{T%Sb7?kko#aJ+!sr&f-6-6M`kk{Yb=D8aUwG@C~~bHs_m7m zqCXu5tHKf&4|y*q&bqEF%-HHqffzK@iwj+}`BIMf$o)&6o$W9WIRIi84dzyNoIcLm$gfO9sbN=rVi; zk{l)q!{9T?knyl3o`f1yEuUwT{K~05o_R`bGACGgL@Z<;d+HCs+d=x?uSHkRbqv&h zIR^b}EjxNmIbH;F`)BCDZ-xA95EVU!#11E&gz>{9s*D>fTQyaYiP0>Zav%-7Upr#< z%V5r7f>;rAmXy%)Ogi!tXU}n_#0JIhPGVg!kBUD9a-;aC5ycIZc{UXP&9b|S^XnHz z@y~^P8i>`g%XV5s@n>yHJr)1dD1JAXbC@8Ce+~>sORkw==TBLcQGBH~ zXI;0anNfV7D1O^H=$)ju*(aKui)Pts<^nv`Z*bpAXxfvt_q+tzR^Y}^gG*qzg%nV$ z=bcd~L0p=?3YsxdG+O}-k#t-Ewu@M;s>$#l=Vmf2K>FC#0Jy#GY5+OR%{>d)OTc=Z z4}6z z@etSUELyzX;e2-3x$uB^Xoti3Ec5Z{L3qgEFC!lx19H(e=P^5i-bGX*`gZZ(QQ+dU zORO3dEl|=fXLYjmS>_s8&P4H96)@|w@lyz5HbaJco&98>&+Y?>_$>Q;a57hX_5rYB z`b^P_^wVW)g+Ko{Tk^T^)5nm)o(Z*{pKS|Z`X3GHnPY6a^{dR1xfM9#T&_;)!Q$LK zW26lAAg1FybX`qr<6M*fJalL^Yd*Ht4;JS>whm82r(;X+5J6AB0N%nFGE`#m(+e>H z=kuS#9Il&qHvYP*^KR7Dc!+H8AZGL^=Ta1M<3*s8r!YoI4e1loaUC>T{QdDIkhoy! ze5J=Yg%DD^0laUr%NXlWwR?bfX*o%hRcSUPE=h$zc|c>Ha{-7v8xO52+z1!uTEZ~v zV1e^!bGZISq#uNo946-O0LF!sUY`J{wB*(qY7*#kKShSjuzKAG=toNev*h4Tz1)Y9 zv4WQMdIM0{)>qh)Tt||(k#QqQdOQOt-I63(lH3=N_$;%rY?%u9Ok2zdOQ6qiy1~d! zossun&~?fL5WWoen3_`N_C!t*a(WB|u+7RrOv#Zu2#Kd6H+M9E%K+fW?`Ww`ZHMNA zV%!LGW!ECY7MgTS@vx}@!=lVXFq$90a%1KjR>0OA!;CSNBmO=vMRr~U&UR41)X>Vt zHFVPiQ9;#%NUwILp-!ZR-hgU3Oq3c5*4i3+(UQA0^b09iLz6+Fq=s&uXe7Ed)QZ*2 z8ahWvSVMa(N!U=Tek5TH)ssYO=zt|@R71}&8_kF`9Z>4M0$XgKY)`(jwYed7RNI!OsthL1)t45@0 z3>+&Z$s-Erb>DQq=feXM$B zf^k)jYLc*t+(i;;A{9-fNL?Rfwymi=t4SFZKiPQ)B1+jr#D~9wFo!wf!|t1{5Apdf zITFmt`0yO$_F*mI$UkiBpOO75M6zi}e}jdfe78upkJcCoqS^=gD)vYDU)vIH07OzZUo*_M%T`Ebj7QSFfMm7Luf_j16Cl#V5m2{ zIok5hqW(#9&YM2kl+^He&CIr@M3etQq{N>zCsNL}$mMYUlP2LN9d{Ewnau82rdnZHJrYyyr;W4vu9bCTstaO4Jpq?F}sG4k_sHNK?Wkk%B(51>GUS)`HpCeuvn8062@< zCAt1BZc)3WsCwd1>rPSYDPSykcckDA@hEt!xOtB)7(IdWL~npZ;AJixK59Pc5?Y~KZxb>_Qr@a`I{h`^>LZzD#^NA z?WpLo@rqa1>JQVceamj6rbv`7_E%s^^~2i|`U!}Tn| zC4Tl;k!AGnS$YuUN&Hma4*xPy{Ch1h8$+p9*BUac($|rJm#{TEZ45mRAcwgUL(T5M zAkItJTFVzUs+)K^1GzZz|FBvxp6-D}6Gkf2EzE=#r?uFJ!c4hxMRiu9Tf3Wm>nw3l z%`hWfzAyz)*L|C19AxsZMtR01v&020ceyT^-Qbd1D`Za+1kA<*n${(6gE)tY;*y?s zvM6a*KC6A0E?KB|k%2Dp?Y1tN4IqcP;*#ru8JD*=8lcG{0vc2NWEftON#V4ABim;IgqAg`{9#(UDjmp8<*W@nMd(pRz*R%0ou!wrsRmIdt7J@Moh7Y%8&anMm6L zhGD9NPR(FLX6pu__`W4iwdDQzR$Odn?Fzz<91C5RdbktmG7er3vvZh`%W?2EU^vRY zr&>R8PP@BIQq*$@QXaaFZD=60l5GFOc%=v9=jV0EJns>_rQ<2`3VI9BAy2WB|Amn_ z_-Xbn0)E4sSQ7rqW-?To#1Sewj0^EElmWuACjk^kF6?Ccj75*K&p?{=8Mguh{RS8o z7)TJ+nF#tn9s+&o^++yeUOmz}5u5oKNu-advGj+Kr~Nts7L~G(kzsom=yI4N{Y&6+ z+rJzhM0TtRnUm>XW+4|x;WLoWU8OwAK%s+r2!+0l^iHqf-^C!zVNUKRz(P-uFk_nK z;A}g8xkcDJF9k2VC=UMa!s2SsxO#KizQNM0LfGb5-Q1f~NK<8N==dk44*RhRfK}599LTUaMIdBlT%+Pmv{(v%r@8P0GX9sZ`RT+M`w|oqXU9EDEy>6@+XoC` zv(Yj!&YlMgLMu*I8M8EQIJ)X1^J|l7uJ+CPqq7P$uDXo7ID`%a#B6*JcNsHpu}-#; zC#i_phB{7!l`R|-RyBgp zYU}IjZE=N`PuE>GLB(r*<`_ zZ{n4~sQotRT{^ZFef96Ol|kROAfRwgfp3+6^484w-OXk}DJ!G%%C z@F=R**FMCCD=+ym}tS;YVy%1PmIoD+8G z)(1F_usNw6&{u}z*__Rt0Y25`ElZPTX#vy^kKM};QI(zLL0 zQel~twSv>9|Byxohk}?h{sI5;d{VS-gd7wsZq5Ht4 zNfu^U6Loi$IoXzMFI*gXK*L_ErJ z*py3JHMSqV7F&xVr8R3S=?oz(P1af+XkbDnidU$t&^!Km{8KQfI zXlyQduko<=Ai*^hIBxEO834&?Xs;!01#8jS!de_6CEj3q(y>2Nr*l0V9Vz162A$4r z5OH3Eh;K^1Y3mHcI_cIZgj zvst|;AZiaTyB`)XxuJk5qJHHeXwP5ZQC;coun0V)E8Ptrf!pj%L&pVudH%tE-pPaxe-MdHkNki41Bi@?0Tt@tnBL5l4yAiskYJA4pRy%>99M+?aB zAjMV?#CMRYaSG+1pz+^4o&W#dcaRe0J4kWgz2%_e;&+hZzGtB-oQs-^`(-OYUzp!P ziu;vtI6NBa#vLJ?7XAqT<9;KY9_DwD;(jNb6E5=t{)2Eqcn9G>373R5<@`msEQvc* z;;={wq&#Ul43E=-R3tryq&P>AswD1EiHp%;oT`(!LnSU&rvjOi#78;fe0mV57bS6r zN?g1kOOon|BMLnSU*uK;ph5_hP?rRX-0wkGL&B*mraEJg-$hIWzP>Jgz$nGTWP>JiNbHVdq z5^oE~<%@26lDI=9uDdJt;y0o}dc1AmkIQUMR8kc zZ5ToH0T>a?uuX!EYD=&&l~pOJgWS|)Z23Z>e5NR_51xSo%nc-QeGPmwq{j6VSiVi< z+rcvB+eE(G1vFbn1YCeZ{jI2oZyf$@1!angy_yUs;-9}YQxf?Bp`5^gIG7t|FCQo) zhYIu2!oWbnG0pjHqQD@L)STZY3Jk6%Nto}V2n-=>TKHb11cov_9j4t4(eiDgz%YWb zdo&cQo z0Tb#C6*uF^w}}E{+X8G^i`HM@1S;0T6V3TFdZ1ED3cp92#xpslIqyCNCQ{Ay&G~k* zz$9v0+nm?$fysn7hr8f^U<%>-FuzR{IF4Tp+SZESCJG!+nPI0D-$)TSp&m$@(|Q(| zr``ghbf=X#e;PCJZ6XONa!a7C#&_4iMnb`TFzXW}$hV0CCmklpZw&12QiEo^q-y_|l679E!&WSMh-rK-jO4u>XeaLJa>+~hFS0z?MM|^9DZ%{TR57^(p zTt+y?Fb^cN&y+lf%zl-~NBi-up`RkmL*Hy*9!A(_n1_=&Y?#Z*oTS>VM-pEA$V#d` z75{Kt7B**HcXA?DAhAo~JxypWW^d{8Vv+as9NqP10+4YK|Gu$2hsbk{z7`J1{0!+290U&&v%dqj_DukPlIP!G z_9x?CGX7<^$G??G%6JWcE2_3FM9t7&BCI=pfb=XTWPc9mJ&(L(J{Ck-Ow2wH z*j`}S7X#?|K7dUC<`K9Nz%2l*Us^9VvTDGlN9kC)n0`47k}M{QZ$^IrfW8SIB6;qX z=Ab^?a6W8KO83&U$o)9-+WZH=JphIf*a2V;fSjf*+0P^AV&vqNSz&E3b%M41&M-pH zfxs@fnC0$?t_fCEBqT0Yg;)a3EXu|+%URnrs581iz2@@_-yT|XwSE{upV=4byHQ3K z6SIc`i~kTn1%OKloB*KjN2s_{0Cbp-f7#3NZ!?mz*8unrfDBe+rWIb$9>N`6O5v=> zjUNMGJw5~=i@8#F?*fy$8~q8Dk|_LL`eIBVD59fY1Q8|*nIN8C2x9T{O~TcU_5IpH zV^ux5VztzD`%h686Qr(h0VZ|b?K1$Z>r)7@uCE8cBllAv%dH8yCQa$3ItJG!nMaZL zIS4a<1`z)_s;Vhvb^ukCj~rNX8NBkIm2`+YU8M(N8k~6p(x-qVi;3CWfE@*veGh=! zzJPD#OAF5d%V*niCG1Xzg!i7K8f;prUjks$nhYR|nbNc_0oKs8o&;9FI{D115zc^+ zdpoi^q$++RGZUpM+Wr>+o6%MPJo4EkjIxzcM&9o$a0nm#=$c|_ zfOAnqW#mEb&GW5xnXvZ){eA+<8-VmzSa44OXMGFH6>^J50`qKqa8)s%3B{u>tMaD< za0S#-zzi;IX0AYT#$f;#0N768bO4>dgCJ66Uk+@|_o&B-%T3`!P>rd&~{ekEP_ zQk1GlEsp0hLf!|Kz5?{!gVr1}7ou1MK_liF4a~S-$W1wgy`or}l22)JdT$iu$tf#Zoc88nLwO zN>dIZm30M+lFuh5vIaG0Nl^sJxo zV`BDfV5@w_jZXi7-S3m23_vGF|{kZ15?g;Q+yvMp5V*H*0Ye&W#@ax&O ztn}6(e@@J2`hI%r*s!QMt=_}s;4IM0F(Iq%Q0@F|Yi9CtJ4!;9n9FjUq zwSjmJ=p&Gvx7G@_eM8>e)>UhK)_3q{g8>-jJ{FGJO2TLms_6o5IP;y5UjhS|M3J0 z9F@1p=F&HLuUdrVtNwe?6X4m0K`j0eFb;s+g(A0X#!|qGB()67z^BWa#XyGlDlDkDo&Nq^IX{-x$J*2Ue?o!Gi*`rsesp_E9 zD5yvTsll6w91(RnWf%gXM|h1eRJ8q#CJYskzfT|E(LYVa3KTuvU% zxgRA6Hbe?ro{Nm3@-FbRM26^hHbFJGoQ2O9R+&`5t@Fi*fMiq)tI`Wdp@xrV;#|#< zofR;blxlcKrdDeX*G#Pze%X00kKzxzHi-P`cY<_S8m>z9bgm`fjvLv^shnp2Geo-d zau!rBuYZuNa*V@kJ_j9r+%c=hn|It^6BRoy9Y5KaXf+{$}#U)a?EPHW7gXp z^Zq5rtgt)gRZAZ8swKy)#mB6sYHOxJOw3jv!1DzxCU~)cZK?1Ef_wqfH8?|Fg@Xl6 z!Z8z|l=E00f<^e}d5aP0`w;ozn0AK%`#%pL&*Oq!yPbJ2cKto!5|{S6AU0CVuBffy}{lfj}7J^HxU0Q z{;pyOY9|pPvEr-oa!?CD5{q04CCykn-IP zISb<2@(p1A_n}5yd&6`snBvlzY?+>LnNGtqPxWAqV@eDc>U;%Vfyc~;wZ86DC5A6i z@DxS!JwHI%mmHJ!5@$^JNPHy zdBG*1JU=)XlN5^+0K1|7(84i2nm<sJ3!K=aE0`ksJaQGCa^3G3iw1B+x6RaTEJPk4n;(Ix7 zfj#vgij42=Fhiy8CsHa%Oe(iJ>m_|)-4`Z+%!C!G2Vk^UkqEj0y| z7(c+F9_gw4@@0IP^FE5<=g22PX}k#on0bF$=)39YmgW6rq3@-9d4E~x7jm2TmxX>M zY~EiMIzrgIzby2dl;G#xq0sMy&HKwje-JkBFAM!i*u1|i^cUf>)b3Crq%@K8RNfs5 zX+bJdA45{e5u_@05p)d2=xd-}bt>-;g<|y$K<1?K?oi05sqLav-W>|X3$i4&o=Ae8 ziJaxBygL+XrWx-wsk}QBN|x)G^HO)BD(?=3 zy6Z)Oqn}aW-?zamTUWY8E~*pKGG(0VId>D!?4$ zzn^et!m-LHcaxF^>;b)>6N{uW!@x;!(m=v7t#~&nX%JywE6#6|2D4Jae%?(=8ba1I z|Gh{_8p`x^xPryUP;}BTf^pB{pHJ>4C6%{FEA;bjQqqY20F|G2lafYOv3vCMZc@^y zGXRJEyqlCXny)%b^S?#$V>Sa$SFKq}3BWe;kCEJlHI8y$K{>HLmLNowX^6zASl!Q4}Dj`eY-5<`7bZls=Bd~YcB8L_Z} z3-AvIcVF?)bvvO5ziPNk6S@G`#^yV`yENwlpAu);$iEx;SO19xT7F9zJCi7{{a;wl zTs$3DP?|Fk>^)!EkuPc1cWKVS&Ovt0gmhf}+j6TrBJUoZjLVMPc}Vvh1uz3Z9{_pG z?R+M%(}8ul89>HGN{)f%r1S|t!YN%JLYD6OH2$k@y9rOuZ%WzuUr2u#athuC;K5ey z?w`5x_GM1Ek|w-S!6`M?#}EL5T%N%tyvQc z0kcfLEVJfhp}?h?cuxwyGY5ANBl{q?rWC7nW zmP@O#15xGD@aqZC_UFm3KqVG`1uSB5XIh-7wJKU+rA>pP``~<9Lp$+}?HZWl_q=hX zGDp#+mXt2}%95@W(iLHG%pz5^#nR9*iBRqD&MAD%lF-el%>91l@x20t)_^#^yj#WkxHruiB!_l0P>hCmGl{~UgWDq%Ka{J zmd6qg2aw0C64t21@lacUI0L2Xv-kj&)TpqrMo%R_Yn1%?O*y1?HzB8S?LLH*!jX-9 z-J8BXWRt=ujE6qElzuM!#HKGbniQp3oGyO}Dp_ZjMkR~0S@Vl2sOZHDc*66Mlz=XQ zb-xnn>(@)N+JN+In|`JPX5>oToTiGdvov*}86-3loik1D0#*2e&Akn|g`ZeNn(1E_ zncEuc!dCV&>Pnr8ez(dpcXQL+;%2rH-vg=8RhO}gyv)!4hoEs5U!|a`oZX>>z27$J9aFKJ*nBV8@Q))dz2$c4E!Pv5&wcL@`n|yWbg+ed z3DMAp%b73)r-|?qPL(~(3#fbG6>mT2DropBDiPmXV}BdA%)W?*6hjSf5)FT2iK5|6 zQeu(U)zI+IBQ6@=EE?VdyieNyt`YmSzOPUpzvy##l~KeSXU@8AOiQDWU-WsO<%vFi z(WhUmtB>K2iF5S{h(7NV7mo#neuYn~epoXzv-Np?U|a|^=sQ3=VUgEho{fiB_Lr@s zAx$Mb8-H8bpG*8YLG$7T-&kAKe@&9$ytMIbtXb9nyl8y=s(x4$Owi0dKY*f6cCjCT znPD$c%+3u0-%8%F;5&(LL`~=O=9Lca03OkT^gMHifB zTsQDgHZE>K)3FPcK3)WE3TZWH)>=b>K1+@b5YS1M~rW zg9zD@2C~a-X>3-7*I495@)Qj_*NA@;*yrG7yDvaY;spUKV-b{i-O0*WRdk6Z`-n_X zm+c>|u<$yY{teQLHdx6-I#{CBNMCZkDdRV!PmD0LB#x@OP>T7S#Z{q$FTBK(Q;Zr| z1Ebyqld*kM#HgaXZD}0cu8Ee)uT2y^X>&PPajk_4%h<=_q*p6+))=+KD@hg+ugD0q z7OL~ujT*wjdaGnR!UPh~V{?R=3m}iVGQ#WyCL>H%B1f3PS?PGVyao`C0p_Ii6n!Pd za4N7CWHQQZ2aw;CQbwHp$SL}z#xS$5UJYjBc{&fnV;k#!W-^JxeR&y{*%BqmJ|?x)~Nm#kScX z7uzldkjGrHXBV&`^vgcWH_sNe6syL<=t;7SodD!FC2JU2-JBDx0DabYir`#HL_7^( zBxUUe&`6fcAtUQDkl2xR&nlTWU4xXum#lgWjG`DDjSRC83W@mt?~|w$OUg-9cS~vq zEO!!BoNZ~`*`sW@xW~4H1@znNGZJDN~Z_lgE4DNDGake#W0D!Goq5+=*wA$mT|VtILx@X zRIhGJtll+Z>5fvHSeBMisXn z_0Hocoi+b4q>J6YWNUX10QpTh#O^7`X>9k|NGZI~N|&17V-ePT3JUqhnn(YI+t^nb z#Z=J&%f~P%v3xcRTC(r)%~r*SEX_ZKLGjJjh3qrw+n($x5hmm5HPca|`yFQcC{CV= z_-Z;HoTJ?w(N`5VuaRUWL~SiXqUS9U_=?JHI*t7;T4^cS@A<=CDo_J%OfR~?GH~EX zPZK5aCk@!6yy4Ng) zrIwQ8MB(uk5hIJ&C^Y9YKzBo zBO+KA!p}BSR4A|rC!hs}QkSXXF*cc+MVO6NW;tXkXl41stA;u;Xas*wp(*Q(LAV5w zL)Z0&fMJ*c{>cmYyAiv?mtczY=OJWsW*}|lJj(?YYPZ za!6Y_A32S$KW;-x;U!kO=_3Z;3>mlBq^YJng;#cdgA&b^d$*@S5%64U+sO^?HGEU1 zf`5TdPiv#SsX;r18G>dZ*ECfcl{__yjBQtDIYXmKRQb!8Yw7>8ZZ%_J1gi4C ztjdw{-tn)PF={U7mS6t>P>tS6`u$907 z*fX&PrC%bM2XBNLSeA{+OIz$1I%6hg*{?}}rP>vU7OOvxmlKgMeQON? z#>>qB@<=5y@*J?nHTD%!>{zwP%A90G<0IF2WsvE!!!4}b9t0@28bE$i4k=>|avGO$ zD^iMXveFq9yv_KJQGp8Zd){`tkD~i5A4B8=%V$HxFWvN+*pqB&+-};eOUP)wg2u=S zhfCDXHSf&=m}iEt359Q25i*1o#aqk^$4vwklx3B;QNoA8ipN_THgOumd4aU&wJS{t zoaOP*pB{1j_&(jo2Ik-mk>baW0P>r1h#yBFr?DUBA*FD;l}>*YKV*?(7!DQjJQv4p zmnh6w`K%M{muX5ZrF~)rh}nfJEGF$Jp-(LOe^A@|5fyQmpX>XiE%3Q#wuSo`%9U#n zLi#?YjgrgP2e=wJN`m{rX;dTD$5X|0OqBF?5pcT|uw5F{Sq$(U65vDmsU=-Q*u6QO z8)-qDl(GfYkn36KKjsa#AffKi5-n)ibi9e_TrxWwM~`E3I*-WJ$SXSO96&hOD|Bz~ z)|p$Sx;Gm>`xRc?6kQNSJgpJ2jPN}g5kDD4eDU9jiyIMt9!1Qr`kK(88X_708q_HC zO8BB{hR%h}EW&G_qD+gh=D)RK?1hsmEQn_76~$IOJc=#S0x>G8iW^xuv*|58A>GvO zY^hyYQI?ERTo~7LNTVvwC4H69pKIx>gkGK|kqmBAKTfE}e?#iAggF2)bjC`}FT@hT zI3bykOp?L+JDWeaLD*mtR6Z{GT=*V^Ef;vvD^o|xrS?!>X^)hrc82Xns+ur~anV=D zWthVHQka^^rdnY=@F(eT;5#31VeSZIy@W6+;5%X2)(fTzuz!`T_}*o?4uDqw{4&JK zT((Oq+ny^3uwPsPAdghihOY+JxDEdYDMjyCX>7xHKo)9Wu6oVN_J5nyQRBf555drk zZ!M9uFPQODrZvLb8XZd0+>*{HrKAV`^``%uX6wUZO%7P4cN$HA)*MrqShgG_jV-$l zDTPO@TJ&jAN86pS%ijh4A_3QQB7m6pd-g=S{1VGY_q=NPY?q%ZUH+-6xZKjXJzrBN zq4>+JF6{T(LsX=xv-O(pyvnn2|0-4J)~@t>lfHD!7vY*0PA>DH!@;lRt@44hN#iwk z+v2B7Cmlu%@|Sx06!&qRg!A>J#}Rn^wYYMM`)RkpDSFBxB=MIvL|1Lac{6^k6ro8e zs6GBtFP!2&ztj3rJ;mMm+xm7r147bh9|Vd)P@;o`lk1 zpWp~AEO;6f*>n;y_os=!OlSTAL1R}T`Evhi7V zDi8Y|)Kv$dtSgIlY^(nt^4dnPlBfu%BwS`PGa-Q_ zV;~ozkYF<5o>$NQnSZTHOzt-97 zxA)B1gXMj{_xYahdH$p1{MI^auf5jVYp=b}Idd83%UATT?8_dU*f%uJ7d)vyjr2ge z6@2pP%%NURxvo1*Jbrw7>h;}W+LNrgfY_txUuZNLgkFd)v?IqY#}tw~+$mc(%pdBp z)+}0HtvIb)xz1?u)2!JvF>PnGr-4;${h3w~J9`#;R~Akl2Ja^HCxVKa?-GG?s4-;% zyEM^H0~(eht?-r$j$n3iwhDSD;Z8VLwQ%YFk{Wbf0?4z@Y>#u!%r2Dt zHThn_V@sa^>Mtkg0}g&lQyk}l$L3ZscPNFkx-i7Wr1?M{3wo|2cXJb9}Zr zeD<{dVLqIWM5pq|kZn7YG>6W@36`3`t$9RH9nVI|KSxjHiFzs(p)wZh@JVGn*E&Ik zkO?kcT(8bIHoG}3+nSsN zEXlHk%r4E=Ni{IL9O*(fLo$V|rD|x;>$9`0I;w{B6p?CSc4?u6Da&Ed?1<+wf^{T$ z*dbwehqd=Q!gdwt0)ibDDndV0g#VpgRqn>^;H-@fd38u`d7=kMs6m5Jg9h`)>d@R$ zF~n}H4$~W}!?CeCB6nkTWLAF^R^Dgv1fp@7K;+Qus&urjO2?Qi$l4;^s<%kT%D_nt z878AF}*3ss6NQtzs=8B{EEyK(+LcRUCx6RdmOAb@e%eCPAL4yO-iolr0x1>BTb9CI08iJj>cVo@WOvXU|1D zE*Qz1C@wBiX6gBL%0-R z?o&LEvNeP*135BBqlO%lTSJbu=5f&&mZgWt@)>+<`oJeHWf`3xh zto40A85QOfU13f|g&7r`tfN_ycd>;?v@ux3$6_AS)s>dv3Lu5%G!z4#zo%!F8gn^C z^FPZhzcTo{2Yo3pfpyZ%&$fqVKN~@pQo*UN2hzD%2~PB^AkkDg4?7*2;@D2{<;Wy# zs4lP@s>xY)_z|3flEnT^HO0o=k6dVL0FMlBBP5%6x?Y^yQcwSBR)%h;$WZ!~qEtNa ziJbjI&b}jS8^L{MT~=|Qv#drw8@1I3bt{%#f6B7Yv$t`}(Do*rs+7zrI+Q)-ISl`D zW_B(s6spK!_*X%pZ_~~9NazL{m2mj81L;#8^x_gm|Bq>JYaO$&Y2KK|`SZstn{a08 z%+p6NY-(;_HoCF7eau;7=-c`hw%65%d^sUpxp__Lwj7Zh!j*4Z-WCciXlkvmYigsP z(4`lB`%vKmAEfK*>Kh1?b?N1c>zPe=?$S+*>NRR_ZfZ-{5vCh>jrHl4#c7SniKZ3} z>3nT#BVp1}G_SpBVS02^^SPqk2rX|*(c`{_q>Uu0i`M#DR!cAGtPh!$u4`(R4q&e@ zLM@xQH)_{h&jR!!VKRa+Sw^3@Y-(l`2-mi?)whb0`RtQs(~Y#q6?6}eUy9yRJS1_O zI&j;Xs%gceRqNI@n?jL3k(<`4{@u)I_6Dj!K59?js1EiT)SPYVlnPZ|q)w<%bBgAi zbFLaysAiX_lZz@R7N7RP_R<;U(<@6WpC>h8wRm%6#DT@5D}CaiLNzurDy(|ChN+#Q z3OW{TP@fREv8zP&h8n2VVS$T z$krzHbZE1BxU1sID{oSl7pgyyvY&LNS3jWEg-g`Ooh7T;>VlF2b<{RB#7nD7ONimA zqju0CQn1xO>m6FqLqK^0nYp##p*`MQHJZ%3v&-w=^(VEc0@~E=;qKMmyAzB1MAWv9 z1`5u`ND1LrJ1PjT4M$E4d%GqUFCf+jo$E`9_v3J6WLWJ8?O4~Qwsd&|y>>OutH?Bd zPpv4q>N|}j_-Tj7_X2l>ineE-efpuyv(JrgoOq!+b>j^3ul8D!_^{I}*}0QsJ`6Pw zxxUNmA>!W1R`o<@Z-yGx{1vU94L6M2LZhcebvzk~ z6o$7HPb*a`iY`zqD!zGj`Bjzb$Zl%Ap(8RT?49qm-uTGZZoKgk68?E)Yt`z=se^to z=PtD*GA^vg7Zc6fz1-bex;hS&m%71{6W`zYM=obPM6pu50p-+8X;?d(y< zc-z!B={JJ*@6Jpt9;;Stt5MH%R;VrE9`(J>bS9&IPbbO9M?2GZ->Gg1uP3F~cdi$m zk4Cl;{aA;$je6V@USIB??Iz;;;q~5e6IXl3tx-$2daJju`Q%--e4BbY?3F~y4_!Um z+kQz#?Z4v)wY*35->TY+)Vm!eJeCW8Ph&Z;aoUCITlCvWZ*_RxG}2z$4+CduxIn$; zOaA#T4Eh5RZ`e7vR<7H$&MTf+>}86*P49XW&YwWWyt%5d(zi9D)+w)-+DE9lS5&Z` z#yJu>FZ`V?^~ITNX*Ga-`F+TvTu^s(d4)vLPqELripcG&cC@x$PmCR*hK$-k_WiCS zednEb`SE>>$U2JVu%Zov{`uAu>RTN}xw-iUCG595Xbx@<_;X*tABxAXBNZ3lO7pBK zGU8CLo_v};rg7qV7tLv@nOLb-G@O~)aFy>%lULy%@9WsiF1;6tTtqHSEZ#LuR``JG@6jkX{FG+Hq_p~OR}on=uCa7Wjat#&zG>5@2dRpu+jpxU z_!Y5;Dq^AfWoVhKo8Y}+$OJ56K}A3plhr8O{hpcNNrvZZA`2G%A@}G zzWO^!{o5pZH;Mj<)PJpOv)WBH^J`scNBc*teXLsBwL^VCO>UzmJE=)GHTgF+>Gqp6 z<@oSPD81m7;?;B1wN&cVE^2iHwfZNu+C!7-TAE6GI!m06y2I)A=hWw&JLuy@Me0gw zaZPw56>l!P6@C?GzznD9jnwotwX<`p`f9jHebQN|ZVr2e>K$skfqHuNRR{54CuUT6~8(8MJ1}C#COQG&lCk_dIHH z#!Rn9E%u5$n&`{ARsY_1>nq1)>Nlx_dhULY=FSZ({Pp1TzL62(b>8_rWeO_%icB;5 zv*rE-r*i5~rBC`sP71HhOjHMwgz8@5mC&5~uy4_`RN5Dr6%gow9X73NP_JMobD)Rmi-=MBuqPO!k^)B$ns3X%})w@4YLuqU) z8&z$3QDvn%q9L<=j`~rTS8}r&PQ`JHnp4uK&RWkBOS&5y8`Y%^YE18-zmJ(Xtk@s$ zLi*dMfOl4Xn9=9hb(AEN!lfRs@Mm$Rm2M?;2Iu8uPRn9mil=h&O=R9h%sZRW4Bj@K zl^(RqdzTVt7;mc*73vVOg7`?ynfn!{_-3Cycz-tdwr!RcY|eImw} zu#(xlK{_eTa1QU%Ms_odv2>v?kYIsR3yGQJ{mrP7RRq6DMQe>3NGo4h{mS1yP@VR1 zks8i3@%k>0RlFQZ6S2uZ_i~8s_<9%5kdH&W?ftmZtDs$8Cfx8=e0AG>bq{pWDtTP5dXk!i)e>HbzuwiO(nXOG zgNd;^?4_SwL(E2h=eVcSOW#b?i=oI_v>l_3_GBu7y6vAkH*X?Syb^U-BpUVxli2S& zdm<+sNosBhr)jU_6~6Y|bKU~=SU7zP**l0N{?*Atb#<8S{fz43A@pH%t ztatZr5~}WPQ$Gt=ta)%E$zI4o{d;F*Y`BuBpNDodvo%i>^K^RkL1egY(9I3D7N~)d;aoENs^;!Z>X6Ocwy$by*h1G^jFq~CcWQkL z8VbBYV@9iEdbGsy9&gM&8a8&nOS_KZ+sN|2b@Y}-M(}`K7b&_#Gl=*~q-2XXh*o^! z+(JuFB`pcN|Dw+7{p(-5U!(e+cWTG9JgRms!ip2+&jiS3os8b|+wPH*jy&_3+%^MBL9lleja z3gEd8o*O@m@GN|xgG2p@&wtV9f6vFS_}4<;^)1VEZZ7pN8D96t`?(G_`bUw?v|cXo zQ|$s)3>q5$A9+m+DMUFwp9)273V1hk7&AsMlS4)^Z#z(zvu8D|1zX_ z48wwog4-@Eo;E2{d~WGQrDtE#6rSRJi0Hzr9^B*&_ZsFt@iZ-E zYH5WU>}O_}GG%o!jU4S1=R8{KjhUnRH$3a>Ybc;u-n~m5Ni$@iw^gnk=Bj}`-en!$ zBnre$tGouj4(nBslR1c)RqH81NAg75rn=X8V|50_XrISBi-BcN>M52h~y~!glBCj~*DL+~=tDwq@@V|_N}jHF z`z`LJ7Q6i=;-x;Di@5?*%bQk)OCzJWP<_;=Bzfr@Z>RxViG9z?5~@@qs8aQ!QvKRj za^K3$Qm3fN^L=`+x{xZYZ?s2ubXIuVxR~D6M;^s0&jDH$4klI zR`?6$&AhE9@*^#Gn&HlS=RKGIZx`gG#3(C$PTA@Wbd-aE#j{JPG~fDb?{=aqRC7sXr0{Unf3s>YQOhePtK~)N(86h3 zu2ij?y(QkCA|nUWPG>2thF-DSe=|jC#~|OJtp$IYSxlS%26bqIcUbLQVurt>UZdN3 z19%ONsBchV(6$!wxq_dnk-e+euJv(G0qs;5u2*9!Xk(DB%&5b9Zn;K{q)i8r>bX#b z_w2;>cD1pqLXC<{4XY!H)Z&fg`>u)p%|0p(`l^2at=CMX*m)0ek6+BC+ zeX?uU9_j5}b$0izKki=RlNAN4E3a&1s7kswm3m(k|40pC8zX8OtBu^L_AgO~^SU~; zK}}hI=d8IGRn9J*J*RF?`Shp#cH2Wdy}~N(XNejh)OvMTH)(l%8CT|~_v3xd6U%t= zY~3$%DkoOt0V;09-Moy`(SMP?bV>9f-Ik8fw=w~Reasxg4ddk0kKqq?^-k$;Acm92$>c8Gw#cR~B=)N1>!14xHE`8z+|7u6o zr`7AUx%^fnt^Q4SLyx8z+eVe=dah==R7pbk~1*$;aN9hkePG&0b@>x+=194c)AcoEBCi ziqzZ^bx~22%0ZF3pQRg~@`|7ErTi&++@A>YI z7CX<|tnP}Wy}@miCa%97w;h!Z(Z8bNbP};<~!)iC)-1Yu|9%g@+?W z;Wec7YPzZHEugI=)l=f1SExQEg|smt;=Ru9+$4W!sW(Vn6{cO>`<>ploO4_0-1gaY zuaH)C3MX%bhWR@ay1N)z{OzuNIPI_19^v=5IY|Bs(;^3%%f=bt&m7<&BIME~@2 zK)*)fu_YNzB}zZdw2v^E+cN($|DmDa+Pb<{dL~Fzd$WEvXsRE%-^9GWd`=#G63;yN zBut&x%_9+lxI0CVHShyUhB;3G#Z!KF1TmGI$P3C!9yI`e`Yh;p&agh0zV<@XUF4e|`FbIy@BoZUa6|Xl3Om{#vl4Vzug5qLy}7ep4-N z$jqh+_AA;3Z98{j8(sBLck5O2R_|(cTPNL^_Ij=uTYfG**YXOV_pYOw=dU_mcfqsX zq;GnwKjFpbX0qt9j%~Xt)hFR?boZa`m3K9$k3t(a&<%aBg6ExtJR-qR<9{m z-|g63dF7a8o#%U*T@(iD^P3S`0_s;kr_QWUB@LD8w~$b)BRgJQ{p=VXzstHSRSW6- zUC8V4W~wrRf9mkIR$h5p=Q55d>rzkB^Uc!wEjO+4E{%*FGDbbs>22hJyxYr=hs(WT zJg8*Qo7Cf=PX~RXHg?b>fJh-7({!w8SKlG6u$wX3U>< z(ZKw(V~0O5#~+%!i*Dj6QfVeQ769Fjw6{k10vl z=6UCzGG0yhO0*rmRb=)*=uz5@bW=wg*S@(@t)+#25hcgfv?yL1{|TjBP8cpVs6B?F|j}t!4D?gwUeeW&Fvwwx$*JDLFe?izb_! zmd&Q`B~H-$BwE_jp@v226n$*6NmMS$>EOcF`g$-@)8dV-wTl;^>)N{d){E-XW%OOg z%c-<1r8iP638kjZN{vsAOHomwH$J37i(8wP)YYbI{S$4qOX^en%{oh4o6_|m{;FjD z%w7X^GHc$Y>;^aYPp0P8w$+DH3#tBP_cxi!P`aMYzp%Y|e!8ipxlNzGu%&fTZ8~1p zCe}`ZF@B%EviW}7Nv*B5%R{O83u>F1L#dP>#&~^tYHMptE3w;KQ}bIEwl8Y-Yhg3_ zKsC6%b$)${OE2}+M&I&m>fHQLYg@XOOiYnmOPX5g(~MMmm!-ID6VHD`NmFx*zSg;o z>b~C>N7Q$7QElsG^{pX}8V7-T;_u$3_mI>#5}Sfep=+jy&^JtzXQWN1f&1M4~cvVRGi=#I#v6Qz_lwq{WLV;@MGYZ*FU9Y_6{xMloQI z{5;~p)WHquI8D=rU|ftZOKMa6Mi||W0*5pzn>uA$JT-aN%$cd=%xM*55WOvEK4q2` zhoPy(3)|@@lxr8Z*N4(AY4)5?@i>z^{B|1t2rq?`-!;SRS{`I#WW{p+;^n^Nik9Yj zejY9$MJa+&Ikt!^!S9@C%C0J3Gm#SNg_viW8TsrehX=E-qYybj30rb4YWDY0Yp^3g?A&p~bbW^<-wF zNvW)?PfU*bS;;k(d!q8Ii`e{y^|j6Iiz)ql_b%XEr(xA~Q_pTrchfo7^O-Hpz8p1L zTw6CzAI9s$Y5H)wKAaIswQ;3SrP?^1TAJ(HXd=yTX>U$X;ZdRluWPGcv^c#y)Uu?$ zwP9h)(iDvr{j0Cd>5|t4)2$vkzw#jWQj1#X-F^OvB{@Y!a(ap6M^>4Y;^DWEAZD*T z+58JAfzdn5l+}|rJfyV?7cZy{)i11H#E~aoT5Fpd>qC_C^uL|+(p9>peg1;_x~#M^ z)z0Ks&Xk(O>qKikl?7g#{Gc@RjPxt)xHJ6{8=tqZwwdcW)e+68UD({dNXNqDR=jO- z?R*K4W?ooNmRvM5Ic)OTXAe88aD3soVdKY+J8Rt7@n;Mhy(rz@TwmN+PfJYGd?IUG z=PxLxw^fcl^Yme(8|nYXx_QG!&pK_`Xev;}^GU<#hUm1(?6^r5Jq+3(`UL_rQd}S@ zQoOE`)m&7`Y+dwtuWp8&b3SWVpwpI?v-ZWZ{I)Hi-#Fmx%2^XwHx0cMZD~o5kqeH{f)*@E z;t!_|rMfMweo4#DGA{faN#Dm|oE_S&)5eGBLadgSd0TyWx%J(mqQO2$I?~FpBwagi zVLhrIM?9Z1S%GE~67(Ixe)4mkBxG6OC5+Z4tcaY1{$5FkUdJnYLQQSvo=fZN7f)K) zw4~lo*we;S=zYzWpK53#oh@8=>)O(s4D2Yanc^>fDui;1N&<&W7j_+zoS8$x;=)EP zXVhL+U)IvHh|-RCD^!npfkb8H71uX_%RU=HRg;Pmk0z}r{-t85nTv3mSAMgqljHLj zw1(y{Y@u0CD5BT+7EYAbG^eRwf~7uk0jO`Kt=v@q z`hzDu4@!OgWiu&l>26Voe^rLAqFUzHx3x_t#5vDIy+e8;Ki<)@rNQk$lWK2`@nW^^Bv}m+!R$^9aMj~o(blMDq zr%a}u7KBJ9m1;lJzdD;>xKn3No@8aFT{I)g_rdt8j5iHvTNW>mk{@*aLyLxAqPhG~ z!J=h>ouDmMx|K7?^uS9VdZTrsl{YDDI8v76n|W5mXH70m#S>*yCs8Qtm(b3oP2cyT zq&kx?Bq?KB7SH1=N6NhVR=(zGYi~wKxJdJQN>`2Z{abJib;jt)v&b1=f7z57)U|Iv z5{Ngulu1G5(Q1uqUC3X+G;T{^XTIlOZ=#Dv)1}F{a6!3qe%`BB?O<6Q0cUqUE4%=Nown? zY}usQBC();{$)Crx&lDkGzt$79mQCTD z7Z~P@==8Xa8!7i~nmm2h)QYK-H8JI)GMXJ!+S!8aX~`E-=yuY@ti`{91lF|ZG)w@B zB3U%QwwY3=mi9G%C-JE<@trR3sp8spp(jt|8_4vNgK`Xql7OEWRvhr}!s-*EZvoGM z+V3Q-BOHnYdcx9z`n3J)cA7w^*$opJ+e9O`VJu(6*giBY%h#~XY4~gLc&80U(5SI; z>W4-o3HTFX~pq?<+mIJ~*EeHxG zl14z77!D->t+h^UkCUE?Opnn%;@be@V>IX^9KtYAUoc%6MTt2A>ny89VA`@e1zu%Y zHwj#8SwRbCOLVOTG1r2aYeCGlAm&;Sb1jGkEx1=K*lNZ(c8}QmJ!5wa1NE6@0V2*S z)YcQeTY_ze%IWZar^7P0!!mUEyy>tE@D*W2LYH>|!-#OG%r&7b=rf|d()gS_QAIgzT2fWI3R}Of!u(au*)>%Pov_|{Msl$v&6tKat z;!t#hWdW`+EXQ>I!fJkpBI)k9n^2FpjKx8&XiL%MkETj$*|&3^r~e6 zzHV5K>6Z-E{Vb6qMIG>P)2bZsXkls7L5;G4)@Y5O{&J%}0eGZoRRsuztUi$eWz|>h zP5qTdeGG7@QC|%Rg{(f7M}2ir{k5X@8%BK;@FT;DL+wb@V;t~g!*T+@fxax5Uq6&1 z1s<@(=q?AGB`h6yP*qk?E3<#0{<}tf0&u!%RRsuzI`E*Z`YNkfBdGtWQ6B@GY1CH( zLZJ>kD68HH{8mx>no%DGJkh9+Ls8PQ04oj43H(m_tpa}aF}jn0*Be$j;0?mkfd};s zE2x#(zfk{$bVJ)oz^|EBRe(^a0}sloud<3YI!JUJYji~cn++=tMYmWM;C~sG6PdAM z@)Tow67aW%RSw9%`M@t78BiZvL4Df(h5GZ1NCNO}!>R&=LLC`UR=pFMD$%jf=!ybv zF|0Tgy=z&39~hPsnJefkiutupjx<>Tk2I~y0go1zjtr<#R!}Rmf1&;+qdoyR)U>Js zghCw|P*%MYnT)7?$f%D3{@bwPP&Cr?7zaGru$;*B&^IaZ>zi_<$N&ztx&e*OMf?8q(ikHVL0Smg+57NLB+3^29#hdRAfXw9QtAYK)w)&}Yuhh~X_cwCsI67b_} zIS_lS93XvPy8rUK20>rc>%T}J;PKgVAj+*A;Cx|et3h>Vx7NDtUpFub1m=8+K{!+c zh(K@$i43SB<5>+Lf};gN{mcj^01+MP1Q5}og$WR`At^w_hNJ)y8Il4-WJn6IJ1a=; zeMH1#lOzmXqym&BWvNCCngcB9N#hasX)05L?=2_S}uqyQr(GgE*VB9a0;-bw)?2qXpAoi$qS zg9pUpAQ_qn{qh{}F=n`;fSBEM0}rt|Nut3mfe{W81AIP94EP-r$SB|r!%6}oPNWPF zzHy(yNlIdNa%xm4j+|{B0=(L=;@1nbKE~nCXk%3raG7DnI|Ra2x)=MNuwXDP2doh81%& z)v2}AqwdfC#Z>#VI7(%EV;6|O39}zF*Y#N(Iz9u|H^nbs9*Ku=`^=y{J~KE`e5SQV zqhje6wsB4&VNeIMbGt}_z`TuuG0e;h>PPV#iop%Vh6_a3R3hOFG!hF+;To|bGMC{P zvD}&O+=+z+A4^4!gvMonhZw7(fUu6z7;u!80_2|*<`>-#?qdpG#w17-5DxiraOtmB z4iH}Ya`3aSsc=z1xbDjZooCOqS@tg`6@6sde^FZQ+jN0NFecA-B2S8=yZdZ+fUg)< z>`j5zs4tttd(5;>0@`W)Kb(p|J2`YP^hIN66c7f}HUQAt9z{T59V-J?cB}zCVJZ58 zpA3jWRu1s+Y&j6atsEeN;p+G3HhQ!ecE63Qkih0m5bK1Q0Hh6yPD^fK~?z zVW75v2m`eRL=bdaP&On98w`yQl4oUfsnh}@4pblk5d>Br%YZ^0NDvS~(1M`gAqfJ) zJuL_d?vNlLe9?lStY@*kd4>_Dy&@poq1`DUd|~xvc|1$y^Q20ox5LxFUV3B%+ z|4&A$`V)Z|1CkmkGw~EtQli1wlavmL{fjK?XB;d)Sl|(s1z+JP?dRRhDTgtzB+stD zO6oDX<;@w!3)#&)bc96Cw~4z!bXhx-ZDR6fV|vwYf&VtFC?GnaB3&)^ z7~p+|m2$(7biPV~+=rl}yJw6qNxsT4^AY*(K@Ni-IyEYKR ztsLM<*>WIGvvPn4hOaLe4z?vYZu#;Avl*3_3OXPP6IHgGrFRrik^)4^X4TTyjjj1= zshv6+x!oDv-!h7#sMA(=6m|NaR_b#=Tc?%40?XwRg$!z|i8*z)92KT$!u=4-ox zp1JK3Xt%+rN&zk%pxd*vi43S83zv2!fZKBfLH*GR0>U`0@jqJ^gLVJtqlGEa04Mde z*2OZQs`oR3fE8Z0AgHejmox&_(yvnRi>-@gKwT|d5(He6BM54f6$IRxBM9nb`sECM zkw(DJg{8X&HPb|}8W5wP1wmmTNFyLdK?{PyK#-t;^zFv{(t=^3`pc070WZiA1cf0b zL9NIBz0k>0lWpI(*#@}9uwsB1R7y3#3K?};vH^c)SSi3?8&(W(WPvt}bODYsti)u2 zryg?A0*Zy5UvpmFd@myErXMRdBNF0>jI|2O%fm$1J0PBMFHQ+db^ziSfF-tRr@*T%>jr_hTGm|xAGE9=3*2T|fEZM={}G8g;!ER(c+pR9 z`7e?JJi@SQ01--(3P!M6CUK*g%u&E!nGzmb%ewK zv52r3@Bp)*!~sVbRumAWij<*Rb(skm2mG_JSU)h*gIWS22UrYvjY*X_;O&ML1$@!4 z;()IiRumA^l)4P&*sGFT51Z_XKe_jW+F_;ck=*)`%`reE)_*(4uyBx_fEU{w%ac5L zhW0Hfv=}?mVz)B=9N)RP2?$9`rM6WAQ2EGt+%`0{6(tgBUKkC?!mKrQ4x zgKG@)f;sqQwjM54&<_0lP7w5C!TyT^1_-O^`Vz1`yD5mPZBsyGGQD*H5LxH<3IZOJ z9AJ^G4Za+z7Alm_bZb-%Y7`2NF9!nYOPv5BlYKc5*z1xUAkv&d0EmsQ-xLIfiR1vY zM+_Jv=eGrc{Q(anFbbP52m%v~IsrUF64;lUAOvO|$pK;l`f?yDP4?FSBKv*0eaWeT zGWN)s{OQvoQb-Sy0z`UH(DNiko}tb&s{1y8`#4tlh9%Dk{iDfWBJ zw8X@ts~12_J6{fj4PzA`!s*L_z$~K;C*VlgCi-&01;J-xpKye%13nYPQNpA#1sq{m zF?Rr~^32seo$jbs<(aE{I^ChJa_4Gwp1HcG(;ffneVnU%I`bt(p1HcG)6JA>%vJ16 z{OGyqQ=Mn7?&)-Msw&T1-P8HsNwgPb{U<{{?*#E1Vbc3OKJV1;_W%pKPG9~_S(`?i zi;HqV7d?a*wo+bKpXS3=nL{elG@;#kMj;w0@`Cdmj-zFbX zWAlJ9IqBI9;&_jQjKzSljaQ+5|IUDbAuJfoG+dJ+3s@$21(&bgQ?mLzV^*Ok;A@5z z3--3h*<06OYm5>{D+!XX)+A{SAX1PPN2Cc{&$B-u7HY{rb16~-h>AkRG&sqrVQ@Z^ zRM4A)Wzt#G9g(<*JSIoFuk^G)OkHA~AhYsxvvZ7MkS{ayBGx7lQ-&J;N?0hi#0nY- z3%(}Z@=g`_hsLTXAgrSR16o6)F!UTTl$E)bMq%knV|)~_Lsv0{MRv&4X{@i7?xsci`ef4BqJAt$rC zB%rHJU>Aoj=ZJ%WO=3S9iZwes3+NPBJ<_IMs1v8a!Ul*!h>3+9CKhh1AQOp&WWSTR8O$YQRO z&Jbbk&xoc0yg6Oh#~U`n8+Jkl+!t&=FAIF#vapI^5>k!9T8VvCuG-+vD)($~S_Dnx zxedd9Zmfs`!a#b53ZS($3R}N#3ku+)rtq}($$;2m7ApB;rVBX`N{x59=F)SDo3KxbP0U#EXV$Yc;sIKl9bl+#4 z0YrCfCHi~ZiUB^A6&m1YtQg=gv&4XZZN&iL5O)fX2HOY%!pVFQ#3KC-yOP}_5Elki ztFZL_*+@mP@w>$A31bTc;tGKp;#%-iD-|3=SkNN6d5QzyYWo9(byT|mt)WpEiUpdL z!P2nSx8$?&d2=Pl}Uo%8{P^1I`L-9&LpI6T;%>lF1CH zd#s>nF^4Awy(~xCX9C7es~SKk)Q>$uS@ktWuYf1z_y;-CI0DWv>dOJ4P(PanWz{<$ zemPTgOfkBmfENp^Z)nxw1;Y=%O31sdq?lw5{}QCE2;fnMRR*i(T2`CDD-A1ZlnU4; z$6h(+d3^qXBjRWgiO?{kB?&m*u;PGc8CDW-g0OUYfLdY%+=3z_js&PqD+skXLjBQ3Bmr1sSml6F$m$ar zP*%M&ju(lJgwYiPtPqxj56YHXNx+U=Ntlk~;d1&MIYvUKIe|VkAd@H1SBOZD9BByy z{DWat0lq6N9cWM|nT&N<9~kvXKs3x_)RQB$&(F(|dF&gS7gVbHs5d+Uxy5ZNsv`NuM!lxuEKD$l?T8>zb;Ek_zSz(&K011=Dj9ym}x zwSqx!pnjYYNdTf%eh1x#v3w0(pT|p2rKZQ|l>+auta}7*wk$x{6$!0zHXA<_@hx)9 zZ+ku|*HB2IUy3{+)aMzmOHR-sUL;3q1$eh%#R2aTmL4xqZ(G6OctQQ;MkE1{eF*DG4UE>YV^kfw_6qjBZFQ*aM3NwSP&B(J_s3QaVCJ zh=qR8NuFFdR9aa@b6rJqp=iDFZ!X{ldA-x6bcBjMsd++c|ITSk&Q$E)dsPIlyC2LDOo$X@*q}_#?xr2HYtu73%27d?G|26KUt8Z*X#gxWRb} zmpZa|z+J}kDBuCc^BTY-3@Zva!?0=q%Y?;!0zYBJ0G~IkDBxHVu^K=GljZ{8Bom$* zKtztD01+{IrxqaMMpA%Cv3&CwF+^6e46u#s(h%Z0$@slA1U$t$35bx;9Sp#TcoYdO z4ILo(A6YzLSW9WvX8Nz3>h8Gwe(dPgG5p(tm_+bYycF`f1HFKk7wu@S(JnN*C!{9{ZM<1l}N2{wI5L-74p3?4mcJc`33BnT~;fN+e> z0ETBI1_;Mk4A?s9?8f0Fyx?ZQaD$$d0>We#1OARNzY6BVzWlaU?VGL6RpdKzQSnD} zaghZ4Tv++5@RM@(CEE}X*EjhahU9wncyqa%1UxzGQZ|_Zb*T}IHVd>DrO~H^^^TSL zy};d;wMXE=#@gs$fnzP}bb-?>3$R*Pbj@}#FfMkpu6uF0$YQ`>v)TZA3@f=#;C~rb z6!2lgN&-G2EY=T z1H^Dr<^$sXhzhOh%7FNpH0AqLv2jB)!Ct`+cD+GvQoUgAXC(nYFsyRGzZzB&aHp_z z+Jfq}f_v-nVIz_NeA2LDfG~!heRO49?RbW2I(bKK{0=@|vna~|k23c|YXC=Q-AGMl zKuxuRfcFWDmXd3`K8G{+7Z8abQtOY?K#YZ6k?R;tsAbV74Q_TD6)_4HL`G+7nz?*9DXTBYIu7W2!w&2{s1Bj+;+4$4Ud_( zRCxH=8)^#I4>Gw@4tSW!sU_hI2qYXeEtB)LrfCfz;zUw_aGvcjGfV(tW_>>+1N@Qo z{o1ZwLVPMm+QgTAH2TIxLW1Y;`e1w^1p3dWZhsVcx3h81=5B!!gut&uXRgKQ!7fN+Z{ z3m_s-Qo;G~xsxN&;4fC?*=oR|N5rChGi~uO-PdpANJ|Q0Wyt@U%G}adiyk0t1GY%9xX!-!2X;^W@ zk!LXQslqD=wNoY!b=I1o+`%NbF6>Ph;Y~PU{D{S&g5#lKoM3QzMNViM;>F~uL^H(V;q!o#~2#ch^GHM#&8#Qk-Pig#W|wi zwkk)fd*l?2G2nf|;&20Rv|@nY%Mt^A%!&a%ktGKFGb;v&F~~Q@4<8-!Ut8r!P6NJT zSQUV82uqJKsKbxZI_S{^;6!0*K~O8LAmGn)1VQyPPE-I66qeQqYP1#9HrT)LY?pB( z0r-iq3g|+*W?6@>udTg&Jdlgqv6pE&pKt=&C6jmg(tP414#QJJF~Cc+ z#DLqZ7~sk*v7kpoaWtj~F?#SZZ z88C|52CYIn$SRV_1ci1Ur8zT(>LXTOFs2XE*l4W5)CR-Mw zUujvci%zKjlZ$s5%l5@Z49VlR)!yz1g%otV_ z@L%?4AS3eomHV0Bv5W%Fkd=a}QJKKmh7|>T->{ql1)m=?J|_TM#6ev;L0x6sP5{Dv zEePrrD+pL84roD8Eyk?`APm-mI$ZYeSz;>;rA;j07sibmKp5_SBP$4)PEEhW-uJ`> zzbS~pGNQ!n%78f0FgwB-5OR#%YOD=Igzz#CxXOe&ico(jEEWU)#EJnPBst4sF)3)}rm#f;F>ow~ky~NK z0B;c%i`^xO`c0FtQ9z_Oiy?`BVZ{Jn6&8yDzhT7ykwGjLOrayiz3FCEDtk-dZp#A1 z>O{}cwswL*ROs6lHi&yK8qdp47JH@`yUGBuiuu|=AS$FSXzO*N6+xup5EM~jGe#|s z2yA@T*cf;Fjt3Q^)jg(F6cCE|tAc`>m={#_Q&E9-^akS=XUkG2;cAy733WI^SIEh) zTA`(m5Kbbnq-mui^q$y+(V`)FsWWJJP$L(B>vKUTknodDcE{aBc7`9VjyA2LfKbGK1vTaKb)u+1 zyS&3cSmKCZI?p@&_~H7AB1}T=@Z%(AY2M+l7n^R9qd)vF3h}BOX+{E~MC47ApfXAm zs6x9unbQ<7fiefJUN#2A0N)f=z5t^cIwOzWWP%VB+GGL;zJ6IUVejy^*`3C*M?6IE zP!|O)sESxYO?g7=DvCkTM6<7p0nRl0y0$J{j=*#Zt=pdyZCvW0b>3)02;G`!g8@6f zB-%&FcC*-QJIes=wsU0{ww?d7Vt}|ph=f*l0Uu(vm}P*tYG5(oldKpZt|V9t_*^Rn zh%GORVXynH6$8YUm&Jm42KPFQOL5%2fW|zD5NyS>C)g}sb!wMh&>Ib?Tb=F|4}Ks= z5^@UNfYsaMei{pssG`sojQwI^j+* zi(L|MjIj9rko&uAZo@>rh6$%|;%7u3Iz;zOkpf&N#?V&g_NBpzfOg+E?Gk`DNjrLz z9w1cZzq#e=ib2;6MpqSJwl4QGk@!u$=fti)p*2q~&4Aiwtc?PGVOS{xO{dYYz)HhP z0bV97-2 z3D{~_3Bap_l|SFw^s)nTAycm=qR$rBFBn!n#}*G;6ZR&oQcE zL6J)&8gH1WBmw^*EI+^?KDKguQ}w(NNdTf%{;As`4Z2LjB;abpssOy+uqpw6Zdet7 zZyQz&@LgdkdY3{51T4$5?|fP0KQyYG0K1l#xt7OZ`5ji#{Q@5|tQa6-mgf%5SdpG! zS|tIahE)zY+pv;=6~f9tAEAD-X_x?3yrnOV3NX!<4vmsAQaNmL%`)$3b4zt$^m-}D*@#dEIpq<{nZKr?#vMc^)D+3`0pG+PzRVmmIEFr zEZsGzp;izOgP{dMonQnLfOfRX_cmHPC3R?)0={W{hyx-H++!>Q>W@|s5OL6gpgy;P zfQSPK}%PrFNU}IbIVSzuktYFxN%Bhi7WQjoJ zEKR=agoOkp)?LCvAH)jko+%=jBDA@ENmwt-ky!ul2Y+vVl^G|ZfY#MA*VQt(iqXtd zw-GZa#>*YO56ld^SfA)N57{@bw%)P;XhmU{G9t6Z!m2 zf@$i0x)G*aX{##{T5)hdUwAyOumNxGC9&r25c3UwhPqN zMlcF^uVKXj-!QBs;BLc;1OC&nqJU>wp8(G_EN7j8^^HbR0uYway#YX2Lcsz2hiO<1 z2us-QL;J5CZ?eiUIy8OAPoKD+c(BEHU6$tQa7I z#GS&UvrRTt0U}!YKG<-LXoYK8q5q@lu-wfOXP4?Kio-msDDEn9c6&iZeCsl(iFr`9 zzc|di0fG^Xe78N(4zhV~d+zM7>(r?O^pVO2-~_{p1D|@5c3J||}qI&>^-|wIgpaNP4oEiZK5H|`T zOr2tkh`V;hUENNO^brMCce$&(9J=o{-IW7AAS_N0VEj@ZO#wjs6cCF6|IBIwL?l@Z z)*oe}S`LU{<;%)tq7^n%jG+~SLM%Xx$X>@MxXQZf<4_NS^W}^<)E}%M;KwjExoqHOM%Z034C!Tp|Oi z*a`w-Y_vvDHC7N1W1|H@ecuWKBIH^S)azCdFna*pjqT55s{C7yw7CMrl;QOr_yh@7 zU!FeWhGLA)H!+L>Vjgj8;6=HuF;w_zNa`C9lZ#se-=EtWxvhg!dXk);B1ish&Hcal;>Zjbhzea_)QU`<9Q%>% zfG_BEWM;FlU^7X-C9L1ekv~xCktk>wNTNZbkZQ;!|2Yf@WRm~===8l)h%kzxFAM^q zhH$VaWbnNg_4`|HWW@0>AxT_--#900@s??)r&%LcoG-AHk=vm*ln3GUl@+RzLJ z8A0-eL7+xJIM@@EVLDLbl^GBt%y3XmIAdsLl+YMwUl;^(-|vZpgFWpGEZx+2X$I8? z<;HJ(_RtKJ)k%-OFbIqygo8an8Hy7%ZpnZ^zW9wN4b5QGk&V7E2!s~G!Jc+{nsb<` zvB~iQue7XStKCkknA^&|ZY|Jy9+4w$D*$&IRyE+T{j@agRmThLGOQ%vy@pi{_?%&t z1HNKdNx&V3RSh^myrLcf#~4-;aH?Td19lizIp9@>l>}U8Sk-{{8df>rX2VJXK4Vzb zfG-$U67W64styj<`Em;O(wC41Mew7^@0o#!-YyYBTM1OkZ!Ac{g3FBs&b<(~RUEB~OshEHBw_K& z1zc*y0OMI=z*SZZur^B!c%c;og!SBMFzmO8vp+FT$KB|~kv=wh&UNP=Q83i>UyecA zWmy=cnI`GVXA5k%te}yu?kZPzm8-kT)m;_T4Urb<-v3x*PzX+C)fQwoGLj}g{6t0) z3x1*(V!=;@nOMkEBq_0i76iNFPiTH7aYcX33#!WZNzP$PYrj;ER8Ln4{JUiVp3p~2 z(=|5WWrh_8Tw_>Cz*`L~4!Fs%l7NpJRvb|EMUP3q;|!|`aH?U&0lN(=3AoX);($Ll ztR&!e!>R%tXeNX+<=yxt5K6>RU%`O7#Y8m$h`4D%PzVZDCqM*63xfK$iCO{>hHF7k z)(vO>7#1Bz8C}ssB@SauJfd!hoYK8n+qhQ)c3|1YB0 zDoVMEQc#3!pik_%S&(x5ao#Y8@ttdi&CHLhEDOe5VZxV0nt#W#g7f$cQDAjPktJ4l z)YTnzbvxrQTXZ8C{S95v0Q7dV2>biXpisLw0N+T+eY-C#!cdTq`%YeTbGr@Pv!Wd1 zMdjyx$v2y=&ZKh{MO{TvSCJE?|Fx_{o*~I7aorN*6!Jpx{$i6OH3;A%mIa+Rn2A_( ztH66LD>!Vf?zpQv?&^-ax}Eji)otdWwcAWMYj?`k?Myht0%=c?giaJZVxcG@?!-bj z=z~}Y4~BwR!7#bq#og}WZg+9FySUq(6S-h_d%`&@7znReXP6MRG#FBqC&pENgvT@owUBe8-V5-ZprvHnwcyh9E4$Gl*N%nSC& zykM8i3-@R+;n{>gmSF9cI;eE@3wYx0~cJ;B{?SlpPiUMmv!nGg)3wD_95`Zv@OSo%Q!nG;^ ztNI&56MzU+zV+Y=>H8*QLlW>d!-@mmVOUAPZwO0|cQEzV%2`O~x$%T1r2=TQ0ADk# zYQWcprMm;w-`WLujA2y+jy0?#-~z*PZvVlq3yl#8z?Tgx4hT!wE_$b-2w3&bz6iU4 z2aT>MOh3#R6?N@&)|_)i!7D~lITXQh+6B6boQ>gnQ2>kRmpt6do~dG6N{%GtUiP3f z2$PWeQOGN$yR~v8A@`G)axOGh0Ob#23nyu@gP-U(&pQ_WXRUfJ)%|`VN6JmW4=&TE z=&mr}zlEi96V$kNBM4X|EG-D?pe05Su+p%SfXA*hQX1I5uC)^>9GOT+tOAUF2XDe@&Qd9xR>r{(n9UoN&=l4zjR zo4sh$hTFeLsI19!)#dv1-^5!=$2@aloH%*4odCB9{Gnx`Ouz}&C39kmJ5ByzSz&Pp z(}fzMj=XFqll4QRs7w#*O&Po?GLyaxut8Y-d1ByS3h!Y0-m~!|+Cs;lHbUkWO2q*t zEMQb*2pPN5XRT)JsTP<;W%j+Yu;pN1!YiZdW%#J>0~#3=q+G z8wCf$l?aYau|yV;rps{z8=)oEffBHjO_ZZS-}79@2gjXnAYtJ9no%B``F&tGSPt&P zSLOw6Ps+f&B*(o>E}Z^c#0G{gm)i-pMB|(%Ysp?zVdC5(vp%1y;27*hRnVCVv2Od9 zSc!A6b|C%8aB#7~Icy~IndrLV>VX?CqJrg#8qQ(v52O#v1nmj>Sso|`*-5cMYFxg- z4z{DxgIq#0Vg-A`1sj&*XRHy;81m$2wdYCD~T4EE-QI&TjbwCDMC zkf3CqD>{g*6c;!sz}>>5Nq3{bwU!0=J;RCt+7j$s0i{LSic|!n`G`3Gn2pQJ0{>!J zfW5+^&%$?QKw#ceNQ2X!eg@F}z9ft-`r#-5zi;|X0Ge*-@!_};d3sF*d(D??zYxXf zh{F7)u>NlPbOH!d5gq=@reJR>)b9%?ZO{$+$m(u~bVun%zjQ8yjiF=1QBq1@GKIPZ zTLKj9eO~N?qd#7Bu9xG$&@!j`;M@yFrqz*wK@()v92jzCg6m_xzOaqQ{v@BCpbuw? zUElf=`*6#po9{72l?6LCc!8vTrC>BnYbz|Z76^uo2i7g2A9(P4h zi0GhPBy_ry!aB03O>Nc@(nTH4y>!$y&w`&2DqPo%r z1A-cH1Tn-AGe9sHbuv7~*Jc12Vdc^DLU56$nJH#kXSzGxJ&5?4-RO!K6{ATo(aabz z%WgCoOg>|>c}yO=(X3`CqKTSS$5l7PM?#E=WH-@0x9r_Sr1 z`>3j0)wj@@c&!x^!0EWJ3T(I1c2B|9TUI7kVvyzhf!4NnJacq2tC=O_mIC;#@nMSBz}Z9L}W zwhsmvr>r&UFohL0)I3sVmsdf6% z$=`nn9UcqpMo&FF##TKX-|U9_gZ@nNfX`|ihX_yhhhrnj4}s-f~jk7fx zr@wUm9e=sCm2uvH;NeBi$XYGhVazQ5M=Yhj1C&8Raty+Zy%*JW zj4@4-xBWp=*GIa_ch@)pWVO#qk(>mUL@=t4g$02e$)Nu=j+~W?w8#%o&tC%%XJ&7O z?7tjBhsOfD(X$GVaabRY?;`r?Hh-iv-3szc0gL1VV0k;`u<-3bPLHVAQwS#!`Y|&; z={>%!7Ls&>QwWQJ*B0&7800{nh!JLZx~;B*Gdh><`dP3Z^y5fz;@Iv_9FZ}~8Zn7x zM%O{bo>l0XZbK35Mg-njh2aF1j>Nz_F5X?m3~DVRQ%0#*Cq>FyZnP8qJozB#!PZcsf?Y)4>{^?jkx4$+8YZSUL(}=^%uqV-S`OL0CxU~bstMXe_O-2 zrs8><6=aD?leZ-p?J!2U6=dmUf9$Uz`BsG>OF<`I!^)nGj9ZH=u^q%UQ}ASkAWP4H zD}p57s1Rf+7#VAr!ILmZt!tKo@vJ! z(r<+1NHcv#j`?drZH+yqYAD1b&FmOm(np78eq&CPl3Wg6 z-N)v;(r=w+lAH{zV01=mj*j%mN&(}bQ&T6pq%!8; zW6m;vKn?@jjZxc;QQM7C+mQ>fXK_;GiiYeh$S(7aKSZ0RqEXm`M2fRNUzCX1l<>8P z?NrouineR~hEI{4?sv-KHKb1t#7NExiIF}v5F=R^5+gk?5F^Ptn*9w~$yfZxuql$P zcic9Jb)B|*tOv@S5y_{r_K=JNk}rT|-(+Ix4}l=b|EdsV=?ee7s%DZqd{*RMi=plH zLu(=V2e8a@Y|`WXC|XFK1>^QbB9;P|EqZ7~T{cEt-nusGvN6tOV_(!|q*qTy*z5f8 zQ_SnZfJKt|mA8<$k79x8_1;3Bv3(TF^ez{cx}w{Vz&<~>t$(c%uA=* zlHqAnaWAKSS_LIPR*1eLNJGtN+t^=g@nBmZ6n!I+2U)w=pL9(_8#DxM!RC22U>!!r zXGRP)MGZC4&=`MMG?6?4EVExK>Ei=2k|%`3NY4nwNX`n0k)9Wbkz5cGBfT&XBgsZF zvFRSd%yABp>0~64qD)!z4d8WI#dz0Y~8^NnU>DMHu`U9ewWR1^iB{>Z&I}hsW zY=qf-V4mcl&sszByI@&cEIknjYV%j)&?kM76v?motY#W|EMSrRiO-4*=jwM^Z)XD~ zGbHag#vYPuQIh>&+0a>9e5^01L#O#!zWElCH~Z!ejAMgf}4AI2V*vHbh13{86hvI1LV=0Iua`)E&|4fbC z7STL+brcqBzSpn$de(7*-zbs$DcZgOae9jtEO4Vv8MSoCN_cWC{K>CmGhMP;qDqoA zj9m|59IdFUSx6a$u+Ig?EthLO>7ZIoSh_1lwNlg?>*p#(lKx{RuOlISnyse0#j zYbbJep=D$0aW7oarR+7UqgoG&(Cq4X`sG!VpJ~oXDQvAu-Y#M+^Bnd=~u)dCVkZ_2F^N0?kQ> zjv9-3_2)leg!zc;CR!#YL7XP4{IsS>P5{ev;iKPq>%>Ug()BV3#)Y6|VnWa&(wQfG z(&jGWat>V4WlT?9q(tI+rGw@i6ErKyu{`AcnpQVqRIw#aF}f>YRIxEmG5WQqsA5x` zVpAklQN^`!ifbc^+#mL>qiyZwl7r+yu#b|Q4 z4Z{R~(}Px$v&OO7>q&kXEL&}so;=DI)S009nZEfJlK1)>C^VD&H8y*(S4%7%<;T%X zlAf&~OD%yQ$$O8q5m`Z&YW#qkNwN-BaO7$NvORSCvl6*Z>v&KG_SRD~08LnBh<3t;%i8XWmHd{lI@tEkE`dDi4^S*}U5y-F= zWQifk+($ATU2^YaNTZy!B-v+Vdl`~Ut8u#4Px4qlx->}!ZUtFlX2l{&b{H$j(mteK z1W7XgR*)rDQUpnc>l>YM{t-RralZ$pNZ#ykgw)LFI{lh8pNx<`>W9|cfF?)IT4?O%3t9p!S4|l`|i>@o7~802Z9|d zyD!Z?v5!UWS~6CyB81JVFuRb}oe^wilHCaGkwQ2^LKxp0jo}E1!SHnul-E&^ufrfe zDGrWI(?3Ylr%X{KnFAxHT^kiLre>tsZOvsU-7{3?;MIWK*OO$G&HUHer_TZ+s4FF`B=bzP+=~3-RuR%UCZAqd zMe>vE=7RGXNxG7seD4`WKS!b;c}(rZV=_dF*)qoET{g&J3JC<0`CW4z={6Wn+@VGc;YsRQL`A zOp&-birr@?5+R;ArExxuaZZszT!O^aSIbC!>yqxywQP(_UVhMJOnpC&X!`xA>iS4i zZM3sI(!j%z=dSZ|-%7I4zm;7>a!tjpEKC0$2c3WTLEG z#+RP>HC48&U`eE7RHhH<5)xTC!si6`dIo+AV`uPO&rlR2}*HOH#uj_M2NHfP*aRP zw4o+RnvZGMY8arwxULkHiC&p#nV7|JEh4|2%0))blt?^LU##i9kd2F3%SiWsCTUq( z%f`gM4zB1jru*k1@-aWEx<1lW8||DKsj-Xha4j&dHc^>qR8K1SDnL-pxkpic9H{lDApYsVlYuyf-7S{Hn#txNWQlKCJSp?M*>_rUJQF{W6qK zLH_wU8GdEudGLR$+G(L`7wzvF*wy`l^>7fkuE$F7bvtYOTy%UI^8C=>&(MEf1ilW` z`qR)a>wwPzt8qhh6Yy@J)}IG?-Oq0Z|EJVLyRaWmMBcQ$&qMDEz<&Tf24ozxOFR5} zpSH(#d;smH+mYj}Tjgm~LVS;5oUP^y6ceq-7NGXu3AtY2O~7NSt>l})bC0v-?*X3# z9tXQK6YPHj`oq9aB-nWx{NE|NI^O%?r*4uxGW`ULYk*pQ8RRwsyMeC*Upn4u%%5!W zAdq$s*WUjBp}igNu^!$A)|_a|Gk`OJrvY_5XMuknuo3u=z+vEvz?Xrq0kysV1V8s) zdt@i@HsB|LcLKHkec(R`{1)(oQ>|nQ*bHn1t_Ny+UEps4UIly=So1!sF#|Xgcp6aK zn-Bg+fJ=b)1BZb7fKLMd4XEwC4E`&?H-K*e{|MZSYwGR58-X_gZwG4o_k#ar;8%d} zd%u;e2QC3F1+D;UdoAE^0B!<43fvET75F;vO`x{dJk4^h06NocxeB-u*bXcJ%fMdX zwZNUg+kl@1J^%ai$HUjR&%q@?(zp;*Q9prhFy%Xiz zfV+U)L!6s;^A?xaulGSt+aCb`3E(i0pC>qg@(Ck?qx zKo{5xydJm%coVRI>(9RdUI*L`><9AtRQu-_R+9W{`H|v@$ix5tG(LW1gtme-H$l<)eT)A+F?7> z&LI5ML7#Ca*=4=8eSYk%1NR9Fab3I>xDeNoX94#B?*|S5`RzSze;e!$0%`YJ*na}J zA4osv!tS-ek^BxrPW$KA;OJl5W86vl{08Hjh+luWjdAl!Y0D4?Ka;i*Wqu<~$EUx; z#;>9=FSL8Y2zFU7ZT|rD>TqAN49HJfM&xn44)-4^+z0S`om!r91<3b9p3eoeJmm%; zUx57WKrK(X1CW0haxW#wQ*IWH*8%xuQ*EDbH1gx}^|-G&57-YJ0`k78CgFaEUr|re z)BfvVryaNlI1F5X`zQVYKg{HA8;7>BJe+e zzXTotz6o@20X7C$4V=UaGZnx{_LaD^4fl0_2kgiF;#0tO+%M%5?x*%bub}j%;6C$% zKy7D#!u=KPJqNqg{|neD;6AuN;ePn%h~s(SD?rAx2X+nsbw0L$zYX};K&{6(zKVK$ zFQGoizuS690S~e~#qys5z5>+oQ12?}^(#Fs{~5?V0el)rzgoTu_vN1l4gh(dt>q`- z_`8880+#@*(NB`}UC8YM(%&%3+TR_pcNef9_y}+#@_u(h{{IMm74E0U0qcPqasPTJ zQ2YNL0(>v94mcOM0C*;F8E^$q`x{AL%MZZLOTc>EKlcK){8aF@+_m8E z1P&$WFTj0r8*mTspA+t%e+qt8jg4d(a5K|@-0rLL>90Kx}!~fs%Pa$4@ z#DyR27`)U*GP%p*8F`Cm0r~ZbTTtfS8|U;`Ic~7djjFgSUG-)5I5)cBZW7!{g4;&$ zyCPOP`2SZb-g?Bzc)4XNx9HRHa9ic;5XYy1{Qd*?uIIil+`5K4ROzjJxt|iZ5(y%; zl#Z8MBLA{vkMlDM%$JLJwgPoLBl+V-@H!vdep|;!{@Wk5M@APdau;pJ$$WkearFbW zJ-x*?^UZqcd}@96x2@=R{20o3^s_o3`{SokX1lY0(J!|?WIJnr)8S_(@N^*kYk3_H z{c~5{PZg|23i;If`w%a8>%0BXx~&Ht7xR=fUKuyrmHR8{@yc<+xHwL@ z_Y(Jj;$BC3oNzqzQ!hFntj{p={u1z4K<>iGJrY@8?(R4)XDvS_w_E2tF;e}RZ|<|ld~@$V+SU2yE`vJ$-RKX@6Wf*ZGwta4SI;xLT{&*` zE=rvDIDa2*Jlw}CDIRX%#CbR=9_>%>V?{fx-=7=*{w>x6=N0Ddh^>~--I-T# z9hSrMTQBaz?@72Hzjc(A=f@5CZq#1z`CblRKhpmAz6_r?4B|P#Q+!^a6v$tQb?c7+ z7Xeei?*o;(^A_y#l~O)`n3W*UAD-~16#U5q-;2=pZ^ioZ9w2`#!ym>_k3WjxI%DQY zYpDac6{vq4qy23hXE~j~ZXoSzdGh(&wPzvkjMtL3tbg~wb!EQ6&p7uWUTsCc6i+?Y zo3E?$4SB7v<@xe?lKhG9vKF`@$qbaaqsju57Xu?ZUL0%nxhaQ^NAF0%AHi?OI^8ZH z*Zs6U<+&%rNb=nLfLjxAUxGodS1JeGZh(Fm2iF~yxTAmgKa#xGuPj%|d!-9wMsAfn zwrq*z%YL}A{4vh1SpHb&p;-PA4xdvuV&uj+{&CKpPsZ^l zIzzkS_>-Ld_t!)YR5`~x2V(h?orAIb6P%~M9B1d&Trl^$HRZ7`;88@ z`wSd3k&KdM@#@@Fxo2#9xn<^%upi`I{+x zlh2Je*)k40e*yWr<1PPU3(hLY4?>=4>VW)C$mbw`1?2l7kHgOGkaw`c{`Zjo0_1xk z&lm5$1OC}JTR}WPb$$f?Jn(tK{u%f+;M1>pW5m?g0iPqsyeVS%!|*F3)B7`TJ{$g! zs_)qze~PnlZ;dB6%ZfWIgdf)~O|a8@s}*EC>%rdz{?V}01AaaD3&6i7!Txo^H|?9c z$_o0Q)dv5-)t1kl7P3-|i}Nd<--*ugUxdRD#L0hGHWIlY{vlE>Ue-3#)ul~y8 zN9X5Ja^*S7iO!Sn7XC!3@8DnB0Ga=jz#qnW!80o&wI1KfhW}p)`2!bN?gw!2V(_bH zTE-hvHh<<#bmQ0gTU#D2{mJ~@=*PUaz-8i@#`~!&p!`2&cD z<0}PzubHUjj}N@M66|~|f&W?9;g_s$!36Z>1o_7j_`|T{pnY*e;=Bp|z+4;WmFPfY zMIf%e=8g7hsf|7_zu%Le;B4G%?QF2%n0;qVp6k!D4Cd3^VNG zEwH~1{K9(6co6Zs;HR#%{69hd3h?{UE_Z`}i^oSH1o?z~-UBm+1mXET*E)Oy@>9XDn`Zexl)Rh21>o;Q|360J zGk;4wzNN$eUkLfZ&6eANgPXwbcP--~@N)@v{u=W2Ypvi_qb%-7kne|l9Zm@Ku>TPF zHRoBzbnt(WVCSa^{NE+;&E5-f{ePOrpWyVP-RHpXDGBoDLcZn{D>w%AHNUtp<49lk zUIh7pdMp1!w4=F`H|<+@z2(nAJ6;F=PK>Wk*uMq*q1je`w*}{O;Mc&8bSLlc>%y-A z9fpU6f86LP&N7_$dF)Bg4od-=H@Zxm&R<#HC5Y$O#*WmO_bKRZ&fAb5_^_3~52lVC zZ|w}Ovy4xHeO z!LNZ|j_d6nAIc%Plk!(sCVxQrn6ziLTqo~T`TQc}>oI;Wfvsld2lM`^;C~hJJ8LZC z4)A{n{?IbZm<9fi!S6+fJpp|43mns48!xg9=J}VDUt{@Px%&;}>n#7Puyf3j)=sL! zGT5(90Kag$<+FWH_4rT@LA~%ze)Rce1?<$IKhus2{x^=ZMt%TWmr#C%WpLb?6{@&# zy(58tC+ze>iSyf?(vBu?)z@45k{0jpz63kpg`IkppU1!-IN$1@gnnzbrSjSZ@@3fn zJ@_fid#7Q382v8ff1wrR`Ek@ymS2~){3h5r4*X%r<8s-V1^xi8e`seu_^FIFa1!h+ z27eI!>NW72JU$8`=t$sS2|GIx=hu;k>%p%+#TsG1+9iB5FX?`=Tlh60eO~?~Cb{toT3cjrZuf zmf7XP)T{RbE8YdI8^9mLb=pH17`K66jeJgp{AWCVg-fsS^#uOo3H;X*__Flo#aWN* zy|d}6=w{k$+nnzF7Plq8%H3LS$&@awEo6%2+BzqlPBkrUX<3(UXkE3WW%=q=>2&Sf zGtZnKdthGO`~?XI=FM^1ise$dXY*$9)$SJEj%=yy7SrYKbX!+G=axjVJ)iFA%5Tba zrQ6H-Vkw>J+3K|Ay9-@z*=?^qqpogtyij^`HkVCjip9+3X*X9cUhZrzX1d*Udrx=w z<)RWQrA4w_AzF4zM&^~h( z%uUc(I4|MAne(I>H)ndf$_+iawsJO~D>PfE*$*>W@|P0`X;kboMR7Oh;GUb^bMbXrOkZQAan&s(=@ z(aPmZDvy-P(xZiS{;JmW(q*u)?7Su?eg2Boix;g(uWo2)UfPmwS+sb?(zK~cTfuam zuI#3^bX%#%>ts@HD_>r4)8%|mp&;G2E8kXMfBuT)iCn=Va~-x` z4f$esro1HI-JQv`FDiCysis)63uhdJs?U{1Mr2dk8HpeCl+E7C1a&`Wt(`3rFibk_k=*pHw3fi4+ z+ojD?QMbI*1aG-pY_1rZm~HC})e60!ry$X|fhju}F3grY*?BrLSwAh6H3_A$!F6SP zq%)= zm_5(9kSfdAUo9%_*<8A(Bz?gPUqw>RcjYg0(fT2$tMa+Ul7UN2uWNEe-LqM^+z-3S zZL|Fn9mixXG=Z9Fy27EU35`14G3oW9GxN%#uIv`;uFK6-^h4FZQ?#( zHkKtgbGomU!R*ww$+eo?NC?l})YBm<=KLglTQuE?bY|10qPvBqu58XFE$JdDlO{=6 z;=R}Z0?iYCbBl{*5C8p=g<$Z$k}hyN%%U?xsBs~EzK24r@g*(6bxN|$@>5u!@2e0$ zKtZ6^H_JA^f9D-;jp)DEvP;F!bq=m8(d;TAYf@$-EeB9;ps#!o-QOU3uwA$`7gLrft~9Y5I4HX%$EX^-#c z5Emj~`q%Zp7W#`}kniyjw_;u0=*v@B|3x_F?`Gi{7bZUQ$M=1RMkoA-=I~iJ2o=`n zdqKpFtQa(~>}yuey}#S#|FAyaA0o285clIJ|1PE93&X@sCc&XU`X%cgA;SLoJ`(Xr z3IcWg{~r2&HE)C<@C#UvqL~^Jz`cUTKlS;36Y&FRKdNc}-%lkQXPIc;dpS0w&-bbh9H#yYqHFpd{qy~+gU}x~$&o+yPx@s$?vi6h zg!+6>>!~jrEytN1x2?_dFKVTK# z*j;Hr>rYS6FI;FPk5Tznmq%`+aa%3AJS(+=(b9O&!(op__wU&I%xV8(I-pXWor z(u+JU{G65C_g-pPiOQ1WG$rWQe#6cP`~@HFYkgDZ+(8+G2VSt;hw!4(4B@Sof9y}3 n|IPfyz9*#rx99g(f3mDMhyS$y +#include "hiredis.h" +int main() +{ + redisContext *conn = redisConnect("127.0.0.1",6379); + if(conn != NULL && conn->err) + { + printf("connection error: %s\n",conn->errstr); + return 0; + } + redisReply *reply = (redisReply*)redisCommand(conn,"set foo 1234"); + freeReplyObject(reply); + + reply = redisCommand(conn,"get foo"); + printf("%s\n",reply->str); + freeReplyObject(reply); + + redisFree(conn); + return 0; +} diff --git a/lib/hiredis/win32.h b/lib/hiredis/win32.h new file mode 100755 index 0000000..1a27c18 --- /dev/null +++ b/lib/hiredis/win32.h @@ -0,0 +1,42 @@ +#ifndef _WIN32_HELPER_INCLUDE +#define _WIN32_HELPER_INCLUDE +#ifdef _MSC_VER + +#ifndef inline +#define inline __inline +#endif + +#ifndef va_copy +#define va_copy(d,s) ((d) = (s)) +#endif + +#ifndef snprintf +#define snprintf c99_snprintf + +__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +__inline int c99_snprintf(char* str, size_t size, const char* format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = c99_vsnprintf(str, size, format, ap); + va_end(ap); + + return count; +} +#endif + +#endif +#endif \ No newline at end of file