Compare commits

...

386 Commits

Author SHA1 Message Date
Ruoyu Zhong
77bcc73ebb Makefile: correctly handle version suffixes on macOS
On macOS, the version suffix of a shared library comes before the
`.dylib` suffix, which is different from the convention on other
systems. Currently, the Makefile only overrides `DYLIB_MINOR_NAME` for
macOS, but does not handle the other similar variables, namely
`DYLIB_MAJOR_NAME`, `SSL_DYLIB_MINOR_NAME`, and `SSL_DYLIB_MAJOR_NAME`.

This commit fixes the issue by overriding all these variables for macOS.

Signed-off-by: Ruoyu Zhong <zhongruoyu@outlook.com>
2025-02-10 14:10:17 -08:00
Cristian Rodríguez
ab8c75ebcb Do not explicitly initialize openSSL in newer versions 2025-02-05 08:39:58 -08:00
David W. Dougherty
8d8703ee61 Update old link for modules-api-ref.md 2024-11-04 09:20:07 -08:00
Orion Poplawski
e0f48202aa Make test.sh use REDIS_SERVER everywhere 2024-08-27 18:45:18 -07:00
michael-grunder
329346bb64 Test for both EAGAIN and EINPROGRESS for AF_UNIX sockets.
Reading the manpage it seems like we only need to test for `EAGAIN` but
testing for both seems more prudent since this may be subtly different
on more esoteric kernels (SunOS, AIX, BSD, etc).

Fixes #1260
2024-08-21 08:56:21 -07:00
michael-grunder
0c63e3f396 Fix timing based macOS CI failure.
Signed-off-by: michael-grunder <michael.grunder@gmail.com>
2024-08-13 08:03:06 -07:00
michael-grunder
45b7820dc3 CentOS 7 is EOL
CentOS 7 reached EOL of even maintinance updates on 2024-06-30.
2024-08-06 14:14:19 -07:00
michael-grunder
643dddafab Clarify macos adapter and example license.
Fixes #1271
2024-08-06 14:11:56 -07:00
michael-grunder
19cfd60d92 Only override context read/write funcs on SSL_connect success.
Fixes #1233
2024-04-03 07:57:33 -07:00
michael-grunder
2a7b8fac96 Remove dead assignment in sdstrim.
The assignment is harmless but causes noise in static analysers
2024-04-02 14:19:35 -07:00
guoguangwu
7ab6b824c7 fix: typos
Signed-off-by: guoguangwu <guoguangwug@gmail.com>
2024-03-15 20:56:25 -07:00
Uilian Ries
398e16e7cc
[cmake] Fix cmake options position to support cmake toolchain (#1250)
Move CMake `OPTION` values below where we declare the `PROJECT` so they are scoped within it.

Signed-off-by: Uilian Ries <uilianries@gmail.com>
2024-02-14 10:24:17 -08:00
git-hulk
ab30060a6e Fix review comments 2024-01-31 12:08:52 -08:00
git-hulk
5b253d89c7 Add support of RESP3 attribute type
Currently, Redis DEBUG PROTOCOL 'attrib' command will return an
attribute type, but hiredis doesn't support it yet. So it got the
protocol type error:

```
127.0.0.1:6379>  DEBUG PROTOCOL attrib
Error: Protocol error, got "|" as reply type byte
```

After apply this PR, it should reply:

```
127.0.0.1:6379> DEBUG PROTOCOL attrib
1# "key-popularity"
1# 1) "key:123"
   2) (integer) 90
```
2024-01-31 12:08:52 -08:00
Mark Agranat
2706c3e16d Fix memory leak.
When redisLibuvAttach receives error from call to
uv_poll_init_socket there is a memory leaked ptr
of type redisLibuvEvents.
2024-01-30 10:42:32 -08:00
Yann E. MORIN
ff7a064490 CMakeList: add option to not install NuGet packaging
The NuGet hiredis.target packaging description file is of no use on
systems that are not using NuGet, like Linux systems, and the spurious
presence of that file is not "clean".

Add a cmake option to allow users to disable installation of that file.
As some people may have relied on that file to be installed, continue to
install it by default.

Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
2024-01-21 13:31:26 -08:00
michael-grunder
94d931d96a Fix macOS and FreeBSD CI runners.
* Update macOS to brew install redis@7.2
* Switch freeBSD runner to v1 and switch from running it on a macos
  runner to ubuntu.
2024-01-21 12:28:15 -08:00
Michael Grunder
869f3d0ef1
Make redisEnableKeepAlive a no-op on AF_UNIX connections. (#1215)
Fixes #1185
2023-08-18 21:07:25 -07:00
Chayim
039385bd8b
Integrating spellcheck into CI (#1218)
* Adding spellcheck testing

* words

* updating version of spellcheck action
2023-08-18 21:06:47 -07:00
michael-grunder
e07ae7d3b6 Add a panic helper for non-assert aborts.
We merged a fix for a "maybe uninitialized" warning in #1209, but after
merging there could actually have then been a double free.

The reason is that when compiling with NDEBUG our assert macro becomes a
no-op, meaning that execution would no longer stop after `assert(NULL)`.

This commit just adds a simple panic macro which will execute regardless
of whether NDEBUG is defined or not.
2023-07-25 11:16:07 -07:00
michael-grunder
bfe45d9f80 Document poll(2) logic changes.
See #1206, #1213
2023-07-25 11:15:04 -07:00
michael-grunder
af95517612 Retry poll(2) if we are intterupted.
This commit adds logic to retry our poll call when waiting for the
connection to complete, in the event that we are interrupted by a
signal.

Additionally we do some simple bookkeeping to keep track of the overall
timeout specified by the user.

Fixes #1206
2023-07-25 10:24:06 -07:00
V G
dedc6208b1 Fix a false positive warning of gcc 12+ -Werror=maybe-uninitialized 2023-07-14 08:37:31 -07:00
Romain Geissler
bff171c9fc Allow disabling the -Werror flag. 2023-07-13 11:27:05 -07:00
michael-grunder
d7a84df9ea Bump development SONAME. 2023-07-13 09:17:53 -07:00
Chayim
60e5075d4a
Version 1.2.0 (#1202)
Co-authored-by: Michael Grunder <michael.grunder@gmail.com>
2023-07-12 10:31:17 +03:00
Valentino Geron
adef139a75
Remove support in deprecated TLS versions 1.0 and 1.1 (#1205) 2023-07-12 09:20:56 +03:00
Yossi Gottlieb
d543baba67 Install major version symlink of shared objects.
This change addresses the issue discussed in #1202 and should make it
possible in the future to update minor versions without requiring
re-linking binaries.
2023-07-10 08:44:57 -07:00
windyakin
052f99ab2d Ensure functionality without _MSC_VER definition 2023-06-08 12:18:12 -07:00
Michael Grunder
ded32c7d1a
Add a test for the TCP_USER_TIMEOUT option. (#1192)
* Add a test for the TCP_USER_TIMEOUT option.
* Explicitly set errno to ENOTSUP on unsupported OS's
2023-06-01 09:09:11 -07:00
Yossi Gottlieb
5cbd1f2960
Add -Werror as a default. (#1193) 2023-06-01 19:03:24 +03:00
Yossi Gottlieb
af14456383
CI: Update homebrew Redis version. (#1191)
Fixes cross-compilation QEMU CI tests

* CI: Update homebrew Redis version.
* CI: Try apt-get update as a workaround.
2023-05-30 14:09:03 -07:00
Yossi Gottlieb
6de326e872 Fix a typo in b6a052f. 2023-05-30 14:05:28 -07:00
Viktor Söderqvist
b6a052fe09
Helper for setting TCP_USER_TIMEOUT socket option (#1188)
* Implement redisSetTcpUserTimeout to set socket option TCP_USER_TIMEOUT

* Documentation for redisSetTcpUserTimeout and some more undocumented functions

Documentation for redisReconnect() and the setters of socket options:

* redisKeepAlive()
* redisEnableKeepAliveWithInterval()
* redisSetTcpUserTimeout()
2023-05-29 13:25:34 -07:00
Ozan Tezcan
3fa9b69443
Add RedisModule adapter (#1182)
* Add RedisModule adapter
* add timer callback, add compatibility helper
2023-04-24 15:00:47 -07:00
Jacky Hu
d13c091e9f Fix wincrypt symbols conflict 2023-04-18 20:03:41 -07:00
michael-grunder
5d84c8cfdb Add a test ensuring we don't clobber connection error.
Adds a test that we don't attempt to set any socket timeout if the
connection itself failed.  This issue only seemed to occur on macOS but
it's nice to have a test for it.
2023-04-15 17:20:11 -07:00
michael-grunder
3f95fcdae4 Don't attempt to set a timeout if we are in an error state. 2023-04-14 23:02:01 -07:00
michael-grunder
aacb84b8d4 Fix typo in makefile. 2023-04-14 12:42:14 -07:00
Michael Grunder
563b062e37 Accept -nan per the RESP3 spec recommendation.
For reference:
https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md
2023-04-14 12:41:58 -07:00
Viktor Söderqvist
04c1b5b029 Fix colliding option values 2023-03-13 12:08:58 -07:00
michael-grunder
4ca8e73f66 Rework searching for openssl 2023-03-09 15:53:55 -08:00
Victor Polevoy
cd208812f9 Attempt to find the correct path for openssl.
The installation path for openssl may vary depending on the way
used for its installation and the macOS version.

This commit attempts to find the correct path for openssl to use.
2023-03-09 15:53:55 -08:00
michael-grunder
011f7093c0 Allow specifying the keepalive interval
Fixes #1155
2023-03-07 17:06:35 -08:00
autoantwort
e9243d4f70
Cmake static or shared (#1160)
* cmake: build either static or shared libs

* cmake: allow to build non-PIC static libs

* fix typo

* cmake: add ALIAS targets

* cmake: link to OpenSSL imported targets

CMake imported targets are more robust

* turn ENABLE_EXAMPLES to a CMake option

* fix typo

* install pdb files if shared

* fix hiredis_ssl-config file

* Fix more targets

* CMake knows when to enable CMAKE_POSITION_INDEPENDENT_CODE

* Restore setting of /Z7

* [ci] fix building of shared and static libs

* Apply suggestions from code review

Co-authored-by: Bjorn Svensson <bjorn.a.svensson@est.tech>

* Make it possible to change name of exported target

---------

Co-authored-by: SpaceIm <30052553+SpaceIm@users.noreply.github.com>
Co-authored-by: Bjorn Svensson <bjorn.a.svensson@est.tech>
2023-03-07 17:05:25 -08:00
Dominik Charousset
1cbd5bc762
Write a version file for the CMake package (#1165)
* Write a version file for the CMake package

> If no VERSION is given, the PROJECT_VERSION variable is used.

Since we set the project version to `${VERSION}`, we can safely skip
passing it to `write_basic_package_version_file` as well.
2023-02-24 11:42:18 -08:00
Till Krüss
6f5bae8c69 fix typo 2023-01-06 12:29:45 -08:00
Fabrice Fontaine
acd09461d3 CMakeLists.txt: respect BUILD_SHARED_LIBS
To allow building hiredis on toolchain without dynamic library support,
respect standard cmake BUILD_SHARED_LIBS:
https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html

Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
2022-12-27 11:24:55 -08:00
Michael de Lang
97fcf0fd1a Add sdevent adapter 2022-12-21 15:55:54 -08:00
michael-grunder
ccff093bcd Bump dev version for the next release cycle. 2022-11-15 21:18:54 -08:00
michael-grunder
c14775b4e4 Prepare for v1.1.0 GA 2022-11-14 20:34:30 -08:00
filipe oliveira
f0bdf8405b
Add support for nan in RESP3 double (#1133) 2022-11-14 08:55:52 -08:00
Michael Grunder
991b0b0b31
Add an example that calls redisCommandArgv (#1140)
See #1138
2022-11-12 20:18:45 -08:00
Bjorn Svensson
a36686f84f
CI updates (#1139)
* Build CMake generated makefiles
* Simplify and update checkout
* Update CI workflow 'C/C++ CI'
* Some corrections of spelling and naming.
* Remove running on branch dev since not officially available.
2022-11-10 11:20:43 -08:00
dachui
8ad4985e9d fix flag reference 2022-11-09 21:17:59 -08:00
michael-grunder
7583ebb1b2 Make freeing a NULL redisAsyncContext a no op.
This makes our behavior consistent with redisFree and freeReplyObject.
2022-11-09 18:35:59 -08:00
michael-grunder
2c53dea7f9 Update version in dev branch. 2022-11-09 18:13:54 -08:00
michael-grunder
f063370edf Prepare for v1.1.0-rc1
- Update changelog.
- Bump version/soname defines.
2022-11-06 17:39:21 -08:00
michael-grunder
2b069573a5 CI fixes in preparation of release
- Upgrade to actions/checkout@v3 as node 12 is being phased out.
- Perform a manual dnf refresh on RockyLinux during setup.
- Switch to official cygwin/cygwin-install-action
2022-11-02 14:43:59 -07:00
michael-grunder
e1e9eb40db Add author information to release-drafter template. 2022-11-02 11:25:16 -07:00
bit0fun
afc29ee1a1 Update for mingw cross compile
- Used lowercase for Mstcpip.h as mingw will not find the file
	  otherwise
2022-10-26 18:09:00 -07:00
cqm
ceb8a8815e fixed cpp build error with adapters/libhv.h 2022-10-24 11:04:47 -07:00
Bjorn Svensson
3b15a04b59
Fixup of PR734: Coverage of hiredis.c (#1124)
Improve coverage (#734)

* Remove duplicate tests

- double covered by:
  "Can parse RESP3 doubles"
- bool covered via:
  "Can parse RESP3 bool"

* Make (connect) timeout in test config general

* Set error string in Unix connect with invalid timeout

Restructure testcase since redisConnectWithTimeout() and
redisConnectUnixWithTimeout() now behaves similar.

* Use quiet flag in lcov/genhtml instead of piping to /dev/null

* Fixup of redisCommandArgv test case

* Update test case to match what it covers

Use new test case info text since the previous one seemed copy&pasted.
The sought coverage was the handling of the parent-chaining
for a double object, which the test case now focuses on.

Co-authored-by: Ariel <ashtul@gmail.com>
2022-10-14 08:11:40 -07:00
Bjorn Svensson
c245df9fb9
CMake corrections for building on Windows (#1122)
* Replace use of newer CMake builtins than in v3.0.0

Visual Studio generator will replace -D with /D.

* Only use /Z7 flag on a Visual Studio compilator

Flag is not available on a GNU compilator running on windows,
like in a mingw64 setup.
2022-09-29 10:20:49 -07:00
michael-grunder
9c338a5981 Fix PUSH handler tests for Redis >= 7.0.5
Redis updated how invalidation push messages are sent to the client
after a FLUSHDB or FLUSHALL command.

See: redis/redis#8935
2022-09-27 14:33:26 -07:00
Bjorn Svensson
6d5c3ee74d
Install on windows fixes (#1117)
* Remove attempt to install .pdb file for windows static lib

From ff57c18b (#1054) the debug information was embedded in the
windows static lib rather than creating a .pdb file.
Removing the installation step of this file in CMake when building
with buildtype Debug or RelWithDebInfo.

* Embed debug information in windows hiredis_ssl_static lib

See ff57c18b (#1054) for same change in library hiredis_static.
2022-09-27 09:56:42 -07:00
Michael Grunder
68b29e1ad5
Add timeout support to libhv adapter. (#1109)
Add timeout support to libhv adapter.

See: #904
2022-09-21 15:10:37 -07:00
Bjorn Svensson
722e3409c7
Additional include directory given by pkg-config (#1118)
* Update include example in README

* Update pkg-config installed via CMake

Gives the include directory to enable the use of:
 #include <hiredis/hiredis.h>

but keeps the existing include dir. for backwards compatibility.

Example:
> pkg-config hiredis --cflags
-D_FILE_OFFSET_BITS=64 -I/usr/local/include/hiredis -I/usr/local/include

* Update pkg-config installed via Make

Gives the include directory to enable the use of:
  #include <hiredis/hiredis.h>
but keeps the existing include path for backwards compatibility.

Example:
> pkg-config hiredis --cflags
-D_FILE_OFFSET_BITS=64 -I/usr/local/include/hiredis -I/usr/local/include
2022-09-19 10:49:55 -07:00
Björn Svensson
bd9ccb8c47 Use __attribute__ when building with clang on windows
Since clang supports __attribute__ we can avoid disabling
it and use packed sdshdr structs. This also make sure we dont
affect subsequent header files that require __attribute__.

Note:
Clang attempts to be compatible with MSVC and defines _MSC_VER
2022-09-14 16:56:41 -07:00
Vlad Turchenko
5392adc265 set default SSL certificate directory 2022-09-08 14:02:29 -07:00
michael-grunder
560e664862 Minor refactor
Protect against a NULL pointer dereference, and remove unused write
to a variable.
2022-09-08 11:18:43 -07:00
michael-grunder
d756f68a59 Add libhv example to our standard Makefile
See #904
2022-09-07 15:20:28 -07:00
hewei.it
a66916719b Add adapters/libhv 2022-09-07 11:14:45 -07:00
Björn Svensson
855b48a819 Fix pkgconfig for hiredis_ssl
Respect an overridden libdir when installing using CMake.
CMake now generates the hiredis_ssl.pc file with the correct
result in `libdir` and `Libs`.

See #767 and CMakes `CMAKE_INSTALL_LIBDIR`
2022-09-06 09:11:00 -07:00
Michael Grunder
79ae5ffc69
Fix protocol error (#1106)
Fix ProtocolError

This commit attempts to fix hiredis such that a recoverable write error
will be retried rather than throwing a hard error.

Since our read/write functions are now behind function pointers, we
specify semantically that a return value of < 0 is a hard error, 0 a
recoverable error, and > 0 a success.

Our default `redisNetRead` function was already doing something similar
so this also improves code consistency.

Resolves #961

Co-authored-by: Maksim Tuleika <maksim.tuleika@appcast.io>
2022-09-05 11:59:21 -07:00
Michael Grunder
61b5b299f0
Use a windows specific keepalive function. (#1104)
Use a windows specific keepalive function.

While it is possible to toggle `TCP_KEEPALIVE` in windows via
setsockopt, you have to use `WSAIoctl` to set the interval.

Since `WSAIoctl` can actually do all of this in one call (toggle the
option, and set the corresponding interval), just use that in Windows
and avoid the call to `setsockopt` alltogether.

Fixes: #1100
2022-09-03 12:39:57 -07:00
zhenwei pi
fce8abc1c1 Introduce .close method for redisContextFuncs
Currently, hiredis supports TCP/SSL/Unix, all of the connection types
use a single FD(int), close() is enough to close a connection. For the
further step, introduce .close method for redisContextFuncs, this
allows to close a complex connection context, for example RDMA.

Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
2022-09-02 10:25:52 -07:00
Michael Grunder
cfb6ca8811
Add REDIS_OPT_PREFER_UNSPEC (#1101)
Add REDIS_OPT_PREFER_UNSPEC

See: #1099, #1096

Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
2022-09-02 09:57:18 -07:00
michael-grunder
cc7c35ce60 Update documentation to explain redisConnectWithOptions.
Additionally document the new `REDIS_OPT_PREFER_IPV4`,
`REDIS_OPT_PREFER_IPV6` as well as the rest of our existing options.

See #1096
2022-09-01 18:40:12 -07:00
zhangtaoXT5
bc8d837b72
fix heap-buffer-overflow (#957)
Prevent incrementing passed a `\0` in our format string.

Co-authored-by: Michael Grunder <michael.grunder@gmail.com>
Co-authored-by: Kristján Valur Jónsson <sweskman@gmail.com>
2022-09-01 13:45:28 -07:00
Anton Tiurin
ca4a0e850b uvadapter: reduce number of uv_poll_start calls
Internally uv_poll_start iterates over all
attached event handlers to update event mask. It's
quite expensive operation if there many event handlers attached
to a loop.
As redisLibuvEvents.events is a copy of what libuv should see,
we can rely on it to avoid event mask updates.

Signed-off-by: Anton Tiurin <noxiouz@yandex.ru>
2022-09-01 12:37:30 -07:00
Tongliang Liao
35d398c903 Fix cmake config path on Linux.
CMake config files were installed to `/usr/local/share/hiredis`, which is not recognizable by `find_package()`.
I'm not sure why it was set that way.
Given the commit introducing it is for Windows, I keep that behavior consistent there, but fix the rest.
2022-09-01 10:43:36 -07:00
Viktor Söderqvist
10c78c6e17 Add possibility to prefer IPv6, IPv4 or unspecified 2022-09-01 10:42:36 -07:00
Björn Svensson
1abe0c8285 fuzzer: No alloc in redisFormatCommand() when fail 2022-09-01 10:35:07 -07:00
Björn Svensson
329eaf9bae Fix heap-buffer-overflow issue in redisvFormatCommad
A command with a faulty formatting string that lacks the
conversion specifier results in a ASAN heap-buffer-overflow.
This was due to that strchr() matches on null-termination,
which triggers a continuation of the string parsing.
2022-09-01 10:35:07 -07:00
michael-grunder
eaae7321c2 Polling adapter requires sockcompat.h
We need to install our socket compatibility header as the new polling
adapter needs it.
2022-09-01 00:50:23 -07:00
bugwz
0a5fa3ddee Regression test for off-by-one parsing error
See: #916
2022-08-31 09:42:10 -07:00
Pei-Hsuan Hung
9e174e8f7d Add do while(0) protection for macros
Wrapping multi-line macros in do...while(0) statement prevents
potential dangling else problem.
2022-08-30 11:41:59 -07:00
michael-grunder
4ad99c69a2 Rework asSleep to be a generic millisleep function. 2022-08-29 16:22:20 -07:00
Arseniy Simonov
75cb6c1ea6 Do store command timeout in the context for redisSetTimeout (#593) 2022-08-29 16:22:20 -07:00
Lipraxde
c57cad658d CMake: remove dict.c form hiredis_sources
Commit c6b8bd77c0 to make all functions in dict.c static. If a CMake project set warning unused functions, and include hiredis using add_subdirectory , this cause warnings / errors.
2022-08-29 14:31:51 -07:00
Kristján Valur Jónsson
8491a65a95
Add Github Actions CI workflow for hiredis: Arm, Arm64, 386, windows. (#943)
* Add docker support to test.sh
specifying a REDIS_DOCKER env var will run this as a server.
* Add initial test workflow
* Add workflow test to test 32 bit build
* Add ARM x compilation tests
* Add tests for windows platform
* Test with valgrind
2022-08-29 14:30:08 -07:00
Michael Grunder
77e4f09ea8
Merge pull request #964 from afcidk/fix-createDoubleObject
Fix potential fault at createDoubleObject
2022-08-29 12:24:57 -07:00
Michael Grunder
9219f7e7c3
Merge pull request #901 from devnexen/illumos_test_fix
Illumos test fixes, error message difference fot bad hostname test.
2022-08-29 11:35:13 -07:00
Michael Grunder
810cc6104c
Merge pull request #905 from sundb/master
Remove semicolon after do-while in _EL_CLEANUP
2022-08-29 11:30:36 -07:00
Michael Grunder
df8b74d69e
Merge pull request #1091 from redis/ssl-error-ub-fix
Fix some undefined behavior
2022-08-29 11:29:04 -07:00
jengab
0ed6cdec35 Fix some undefined behaviour
- redisSSLContextError must always be initialized at defintion,
  otherwise when SSL connect succeeds it may not be assigned to a valid error.
  Thus the memory trash remains in the variable, which may sign a misleading error.
2022-08-29 11:12:53 -07:00
Michael Grunder
507a6dcaa5
Merge pull request #1090 from Nordix/subscribe-oom-error
Copy OOM errors to redisAsyncContext when finding subscribe callback
2022-08-29 09:17:53 -07:00
Björn Svensson
b044eaa6a7 Copy error to redisAsyncContext when finding subscribe cb 2022-08-29 13:34:27 +02:00
Michael Grunder
e0200b797b
Merge pull request #1087 from redis/const-and-non-const-callback
Maintain backward compatibiliy withour onConnect callback.
2022-08-26 10:35:38 -07:00
michael-grunder
6a3e96ad21 Maintain backward compatibiliy withour onConnect callback.
In f69fac7690, our async onConnect
callback was improved to take a non-const redisAsyncContext allowing it
to be reentrant.

Unfortunately, this is a breaking change we can't make until hiredis
v2.0.0.

This commit creates a separate callback member and corresponding
function that allows us to use the new functionality, while maintaining
our existing API for legacy code.

Fixes #1086
2022-08-26 10:14:47 -07:00
Michael Grunder
e7afd998f9
Merge pull request #1079 from SukkaW/drop-macos-10.15-runner
CI: bump macos runner version
2022-08-26 10:14:31 -07:00
Michael Grunder
17c8fe0798
Merge pull request #931 from kristjanvalur/pr2
Stability: Support calling redisAsyncCommand and redisAsyncDisconnect from the onConnected callback
2022-08-19 11:29:00 -07:00
Michael Grunder
b808c0c206
Merge pull request #1083 from chayim/ck-drafter
Support for generating release notes
2022-08-17 12:22:33 -07:00
Michael Grunder
367a82bf02
Merge pull request #1085 from stanhu/ssl-improve-options-setting
Make it possible to set SSL verify mode
2022-08-15 17:19:51 -07:00
Stan Hu
71119a71d7
Make it possible to set SSL verify mode
If no SSL certificates are provided, many Redis clients default to
disabling SSL peer verification. Previously it was a bit cumbersome to
configure this because the client would either have to reimplement
`redisCreateSSLContext()` or reach into the internals to set the
OpenSSL verify mode.

We can improve the SSL API by introducing a
`redisCreateSSLContextWithOptions()` call that takes into structured
parameters for SSL initialization. This structure contains a verify
mode that is used to set the OpenSSL verify mode.

Relates to https://github.com/redis/hiredis/issues/646
2022-08-15 11:21:44 -07:00
Michael Grunder
dd7979ac10
Merge pull request #1084 from stanhu/sh-improve-ssl-docs
Improve example for SSL initialization in README.md
2022-08-10 14:43:23 -07:00
Stan Hu
c71116178b
Improve example for SSL initialization in README.md
The previous example left `ssl_error`
uninitialized. `redisCreateSSLContex` is not guaranteed to set this
when no error occurs.

Use the `REDIS_SSL_CTX_NONE` constant instead of 0 to be precise.
2022-08-10 00:38:33 -07:00
Chayim I. Kirshen
5c9b6b571e Release drafter 2022-08-10 09:40:30 +03:00
Sukka
a606ccf2a5
CI: use recommended vmactions/freebsd-vm@v0
Co-authored-by: Bjorn Svensson <bjorn.a.svensson@est.tech>
2022-08-05 17:07:18 +08:00
Michael Grunder
0865c115ba
Merge pull request #1080 from Nordix/readme-corrections
Fix README typos
2022-08-04 09:18:26 -07:00
Björn Svensson
f6cee7142c Fix README typos 2022-08-04 12:07:14 +02:00
Michael Grunder
06be7ff312
Merge pull request #1050 from smmir-cent/fix-cmake-version
fix cmake version
2022-07-31 14:54:13 -07:00
SukkaW
7dd833d544 CI: bump macos runner version 2022-07-25 13:43:19 +08:00
Kristján Valur Jónsson
f69fac7690
Drop const on redisAsyncContext in redisConnectCallback
Since the callback is now re-entrant, it can call apis such as redisAsyncDisconnect()
2022-07-08 13:52:43 +00:00
Kristján Valur Jónsson
005d7edebe
Support calling redisAsyncDisconnect from the onConnected callback, by deferring context deletion 2022-07-08 13:52:22 +00:00
Kristján Valur Jónsson
6ed060920f
Add async regression test for issue #931 2022-07-08 13:52:22 +00:00
Michael Grunder
eaa2a7ee77
Merge pull request #932 from kristjanvalur/pr3
Polling adapter and example
2022-07-07 11:43:19 -07:00
Kristján Valur Jónsson
2ccef30f3e Add regression test for issue #945 2022-07-05 11:17:30 +00:00
Kristján Valur Jónsson
4b901d44ad Initial async tests 2022-07-05 11:17:30 +00:00
Kristján Valur Jónsson
31c91408ef Polling adapter and example 2022-07-05 11:14:03 +00:00
Michael Grunder
8a15f4d657
Merge pull request #1057 from orgads/static-name
Use the same name for static and shared libraries
2022-06-26 16:49:19 -07:00
Michael Grunder
902dd047fe
Merge pull request #1054 from kristjanvalur/pr08
Embed debug information in windows static .lib file
2022-06-26 16:07:34 -07:00
Michael Grunder
c78d0926bf
Merge pull request #1074 from michael-grunder/kristjanvalur-pr4
Improved async documentation
2022-06-26 15:42:00 -07:00
michael-grunder
2b115d56cd Whitespace 2022-06-26 14:42:31 -07:00
Kristján Valur Jónsson
1343988cee Fix typos 2022-06-26 14:42:08 -07:00
Kristján Valur Jónsson
47b57aa243 Add some documentation on connect/disconnect callbacks and command callbacks 2022-06-26 14:42:02 -07:00
Michael Grunder
a890d9ce20
Merge pull request #1073 from michael-grunder/kristjanvalur-pr1
Fix async connect on Windows
2022-06-26 14:23:12 -07:00
michael-grunder
f246ee433d Whitespace, style 2022-06-26 14:14:58 -07:00
Kristján Valur Jónsson
94c1985bde Use correct type for getsockopt() 2022-06-26 13:19:40 -07:00
Kristján Valur Jónsson
5e002bc21c Support failed async connects on windows. 2022-06-26 13:19:33 -07:00
Michael Grunder
5d68ad2f48
Merge pull request #1072 from michael-grunder/fix-redis7-unit-tests
Fix tests so they work for Redis 7.0
2022-06-26 12:18:54 -07:00
michael-grunder
f4b6ed2898 Fix tests so they work for Redis 7.0
* Redis >= 7.0.0 disables the `DEBUG` command by default, which we need
  for our unit tests.

* Downgrade to Redis 6.2.x in macOS temporarily

  There is a macOS specific TLS error on large payloads when running
  against 7.x.x so temporarily run our tests against 6.2, while we
  investigate the root cause.
2022-06-26 12:03:50 -07:00
Michael Grunder
95a0c1283a
Merge pull request #1058 from orgads/win64
Fix warnings on Win64
2022-05-04 12:22:29 -07:00
Orgad Shaneh
eedb37a65d Fix warnings on Win64
read.c:399:27: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
  399 |                     obj = (void*)(long)cur->type;
      |                           ^
2022-04-24 13:12:30 +03:00
Michael Grunder
47c3ecefc0
Merge pull request #1062 from yossigo/fix-push-notification-order
Handle push notifications before or after reply.
2022-04-23 14:31:09 -07:00
Michael Grunder
e23d91c97b
Merge pull request #1061 from yossigo/update-redis-apt
Use official repository for redis package.
2022-04-23 14:21:22 -07:00
Michael Grunder
34211ad542
Merge pull request #1063 from redis/fix-windows-tests
Whitelist hiredis repo path in cygwin
2022-04-23 13:40:10 -07:00
michael-grunder
9957af7e3c Whitelist hiredis repo path in cygwin 2022-04-23 13:25:57 -07:00
Yossi Gottlieb
b455b33818 Handle push notifications before or after reply. 2022-04-22 15:35:55 +03:00
Yossi Gottlieb
aed9ce4462 Use official repository for redis package. 2022-04-22 15:20:04 +03:00
Michael Grunder
d7683f35aa
Merge pull request #1047 from Nordix/unsubscribe-handling
Unsubscribe handling in async
2022-04-05 13:00:54 -07:00
Michael Grunder
7c44a9d7ec
Merge pull request #1045 from Nordix/sds-updates
Update hiredis sds with improvements found in redis
2022-04-05 12:55:25 -07:00
Orgad Shaneh
dd4bf97836 Use the same name for static and shared libraries
On all system except MSVC, the targets are different.

Unix: libhiredis.so, libhiredis.a
MinGW: libhiredis.dll+libhiredis.dll.a, libhiredis.a
MSVC: hiredis.dll+hiredis.lib, hiredis_static.lib
2022-03-29 17:19:52 +03:00
Kristján Valur Jónsson
ff57c18b9e Embed debug information in windows static lib, rather than create a .pdb file
Using .pdb files with .lib files on windows is very inconvenient, particularly if the .lib
file is then linked as part of a different .dll.  Chances are that the original .pdb
will not be picked up or distributed along with the tooling.
2022-03-24 14:41:22 +00:00
smmir-cent
8310ad4f5c fix cmake version 2022-03-18 20:27:01 +00:00
Björn Svensson
7123b87f6d Handle any pipelined unsubscribe in async
Redis responds to an unsubscribe with one or many replies, depending
on the current subscribe state. When channels/patterns names are
provided in a command each given name will trigger a reply even if
duplicated or not subscribed to.
To know when we can return from the subscribed state we need to do
bookkeeping on pending additional unsubscribe replies, and make sure
we receive them all before switching state.
2022-02-03 09:26:01 +01:00
Björn Svensson
b6fb548fc6 Ignore pubsub replies without a channel/pattern 2022-02-02 23:43:21 +01:00
Björn Svensson
00b82683bb Handle overflows as errors instead of asserting 2022-02-02 10:44:52 +01:00
Björn Svensson
64062a1d40 Catch size_t overflows in sds.c
Equivalent changes introduced to redis sds.c via:
https://github.com/redis/redis/pull/8522
https://github.com/redis/redis/pull/9584
2022-02-01 18:34:38 +01:00
Björn Svensson
066c6de79e Use size_t/long to avoid truncation
Equivalent changes introduced to redis sds.c via:
https://github.com/redis/redis/pull/4568
2022-02-01 18:34:38 +01:00
Michael Grunder
f8de9a4bd4
Merge pull request #1046 from redis/rockylinux-ci
CentOS 8 is EOL, switch to RockyLinux
2022-02-01 09:10:30 -08:00
michael-grunder
a41c9bc8b7 CentOS 8 is EOL, switch to RockyLinux 2022-02-01 08:57:37 -08:00
Bjorn Svensson
be41ed60d7
Avoid incorrect call to the previous reply's callback (#1040)
* No reuse of the previous reply callback

When multiple replies are parsed from a socket in one read
a previously found callback might get reused when the current
reply has no known callback.

This can be triggered by the added testcase which unsubscribe to
subscribed (A,B) and a non-subscribed channel (X).
Without this correction a callback for wrong channel is called.
-  In 'unsubscribe B X A', B's callback is called when handling X.
-  Now this is not done, i.e. there is no callback called for X.

* Re-push monitor callback for each reply

MONITORING used the same callback for all replies while parsing
multiple responses. This handling was changed to avoid calling
the wrong callback in some scenarios.
Now also change monitorings repush to work with this change.

Includes an added async monitoring testcase.
2022-01-27 18:30:58 -08:00
Dietmar Scheidl
f2e8010d95 fix building on AIX and SunOS (#1031) 2022-01-24 12:12:19 -08:00
MichaelSuen
e73ab2f232
Add timeout support for libuv adapter (#1016)
Add timeout to libuv adapter

Co-authored-by: sunmingqi <sunmingqi@corp.netease.com>
Co-authored-by: sunmingqi <smq222@126.com>
Co-authored-by: Michael Grunder <michael.grunder@gmail.com>
Co-authored-by: Viktor Söderqvist <viktor@zuiderkwast.se>
2022-01-18 10:59:18 -08:00
Bjorn Svensson
f2ce5980e6
Allow sending commands after sending an unsubscribe (#1036)
* Add test of async commands after unsubscribe

Verify that commands are handled after unsubscribing from a channel.
A command is sent before the `unsubscribe` response is received,
which currently triggers an assert in async.c:567:

`redisProcessCallbacks: Assertion `(c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)' failed.`

* Handle async commands after an unsubscribe

When unsubscribing from the last channel we move from the `subscribe`
state to a normal state. These states uses different holders for the
command callback information.
By moving the callback info during the state change the callback order
can be maintained.
2022-01-18 10:09:38 -08:00
Bjorn Svensson
ff860e55db
Correction for command timeout during pubsub (#1038)
* Add test of command timeout during pubsub

A timeout of a non-subscribe command will be ignored during pubsub.
It will be handled as an idle timeout and a response is awaited for.

* Correction for command timeout during pubsub

Disconnect when a sent non-subscribe command triggers a timeout.
2022-01-11 10:19:43 -08:00
Fabrice Fontaine
24d5344934
CMakeLists.txt: allow building without a C++ compiler (#872)
Define hiredis as a C project (and use a single PROJECT statement) to
avoid the following build failure if a C++ compiler is not found:

CMake Error at CMakeLists.txt:3 (PROJECT):
  The CMAKE_CXX_COMPILER:

    /srv/storage/autobuild/run/instance-1/output-1/host/bin/arm-linux-g++

  is not a full path to an existing compiler tool.

  Tell CMake where to find the compiler by setting either the environment
  variable "CXX" or the CMake cache entry CMAKE_CXX_COMPILER to the full path
  to the compiler, or to the compiler name if it is in the PATH.

The only cpp source file is examples/example-qt.cpp which is never
compiled with cmake buildsystem. This file is compiled only with the
Makefile buildsystem so perhaps it should be removed. If it is added to
the cmake buildsystem, a call to enable_language(CXX) will have to be
added.

Fixes:
 - http://autobuild.buildroot.org/results/830ec3398cd29b9fc5cde06a225ef531d7a9d850

Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
2022-01-08 18:57:13 -08:00
Peter Tummillo
4ece9a02ed
Fix adapters/libevent.h compilation for 64-bit Windows (#937)
Where SOCKET is a 64-bit unsigned integer.
2022-01-05 10:43:51 -08:00
michael-grunder
799edfaadf Don't link with crypto libs if USE_SSL isn't set. 2021-12-24 11:13:28 -08:00
zhenwei pi
f74b08182c Makefile: move SSL options into a block and refine rules
Move SSL options into two blocks to make it easy to read:
  1, first part: SSL variables part
  1, second part: SSL building rules part
and change global rules to make it easy to maintain. For the further
step, it gets extensible to add another type.

New version of the library building rule:
	static: $(STLIBNAME) $(SSL_STLIB)
	dynamic: $(DYLIBNAME) $(SSL_DYLIB)

Compare with the orignal version:
	dynamic: $(DYLIBNAME)
	static: $(STLIBNAME)
	ifeq ($(USE_SSL),1)
	dynamic: $(SSL_DYLIBNAME)
	static: $(SSL_STLIBNAME)
	endif

If we want to add a new type(Ex, RDMA), for the new version, we can
do like this:
	########### RDMA variables start ###############
	.....
	########### RDMA variables end   ###############

	static: $(STLIBNAME) $(SSL_STLIB) $(RDMA_STLIB)
	dynamic: $(DYLIBNAME) $(SSL_DYLIB) $(RDMA_DYLIB)

	########### RDMA building rules start ###############
	.....
	########### RDMA building rules end   ###############

Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
2021-12-23 17:26:35 +08:00
Eric Deng
f347743b7d
Update CMakeLists.txt for more portability (#1005)
add alias hiredis::hiredis and hiredis::hiredis_static so when this project is FetchContent, it behave the same as find_package-ed.
e.g.
```
find_package(hiredis 1.0.2 QUIET)
if (NOT hiredis_FOUND)
    FetchContent_Declare(
            hiredis
            GIT_REPOSITORY https://github.com/redis/hiredis.git
            GIT_TAG v1.0.2
    )
    FetchContent_MakeAvailable(hiredis)
endif ()
# later
target_link_libraries(target **hiredis::hiredis**) #can always use hiredis::hiredis to ref to this project. no matter find_package or FetchContent
```
this is actually sort of best practice when making a CMake lib.
2021-12-22 10:47:03 -08:00
sundb
f2be748024
Fix integer overflow when format command larger than 4GB (#1030) 2021-12-22 10:45:52 -08:00
Bjorn Svensson
58aacdac65
Handle array response in parallell with pubsub using RESP3 (#1014)
RESP3 allows sending commands in parallell with pubsub handling
and these commands might get responded with a REDIS_REPLY_ARRAY.
This conflicts with the pubsub response handling for RESP2 and
results in a faulty state when using RESP3.

Add functionality to keep track of PUSH/RESP3 support on the connection
and only expect the message type REDIS_REPLY_PUSH as subscribe messages
when once seen.
2021-12-22 10:44:29 -08:00
Bjorn Svensson
d3384260e7
Support PING while subscribing (RESP2) (#1027)
* Handle PING during pubsub in RESP2

* Rename invalid callback list

Some commands are valid to send during a subscribe in RESP2, and
most in RESP3. Renaming the callback list from `invalid` to `replies`
to detail this fact.

* Fix review comment
2021-12-16 12:38:15 -08:00
Michael Grunder
e3a479e409
FreeBSD build fixes + CI (#1026)
* BSD linkage fix proposal

* Touch up the Makefile to fix BSD builds

Fixes #984

* Add a build in FreeBSD to GitHub Actions

Lots of people use hiredis in FreeBSD so this should reduce some pain
moving forward.

Co-authored-by: David Carlier <devnexen@gmail.com>
2021-12-16 12:36:05 -08:00
sundb
c6657ef65b
Merge branch 'redis:master' into master 2021-12-16 17:42:58 +08:00
Bjorn Svensson
da5a4ff362
Add asynchronous test for pubsub using RESP3 (#1012)
* Include `unsubscribe` as a subscribe reply in RESP3

By providing the (p)unsubscribe message via the subscribe callback,
instead of via the push callback, we get the same behavior in RESP3
as in RESP2.

* Add asynchronous test for pubsub using RESP3

The testcase will subscribe to a channel, and via a second client
a message is published to the channel. After receiving the message
the testcase will unsubscribe and disconnect.

This RESP3 testcase reuses the subscribe callback from the RESP2
testcase to make sure we have a common behavior.
2021-12-01 11:43:23 -08:00
Bjorn Svensson
b5716ee829
Valgrind returns error exit code when errors found (#1011)
By default Valgrind will return the exit code from the tested process.
Since our test can return 0 (ALL TESTS PASS) even when a leak was
found we need to tell Valgrind to return an error code.
This will fail the CI job when issues are found.
2021-11-24 23:09:23 -08:00
Michael Grunder
1aed21a8c5
Move to using make directly in Cygwin (#1020)
CMake started hanging when trying to detect the C compiler ABI in cygin,
so for now just build with make directly.
2021-11-18 13:50:09 -08:00
Björn Svensson
a83f4b8905 Correct CMake warning for libevent adapter example 2021-11-18 09:14:39 -08:00
Björn Svensson
c4333203e3 Remove unused parameter warning in libev adapter
A warning in `redisLibevTimeout(..)` is triggered when building the
libev adapter with Clang using -Wextra/-Wunused-parameter.
Works fine with gcc..
2021-11-18 09:14:39 -08:00
michael-grunder
7ad38dc4a8 Small tweaks of the async tests 2021-11-17 14:37:27 -08:00
Pei-Hsuan Hung
50cdcab49d Fix potential fault at createDoubleObject
Resolves #963.

Add additional check to `hi_malloc` for `r->str` when len equals to
SIZE_MAX.
2021-11-01 12:17:05 +08:00
Björn Svensson
4021726a69 Add asynchronous test for pubsub using RESP2
The testcase will subscribe to a channel, and via a second client
a message is published to the channel. After receiving the message
the testcase will unsubscribe and disconnect.
2021-10-27 16:46:37 +02:00
Björn Svensson
648763c36e Add build options for enabling async tests
Asynchronous testcases that requires the event library `libevent`
can be built and enabled by using the added build flags:
- ENABLE_ASYNC_TESTS when using CMake
- TEST_ASYNC when using Make

The async tests are disabled by default to avoid adding new requirements,
but the testcases are built and run in CI.
2021-10-26 09:54:44 +02:00
Bjorn Svensson
c98c6994de
Correcting the build target coverage for enabled SSL (#1009)
* Exclude includes from /usr in coverage reporting

* Correct build target `coverage` for enabled ssl

`USE_SSL=1 make coverage` will now build the test binary with the
forwarded define HIREDIS_TEST_SSL. This avoids inconsistency between
built test binary and the testrunner `test.sh`.
This enables test coverage measurements for SSL too.
2021-10-21 13:17:21 -07:00
Björn Svensson
30ff8d850e Run SSL tests in CI 2021-10-20 12:55:24 -07:00
michael-grunder
4a126e8a9c Add valgrind and CMake to tests 2021-10-14 11:40:00 -07:00
michael-grunder
b73c2d410f Add Centos8
I'm sure this can be done with a container matrix but figuring that out
is left for another day.
2021-10-12 14:14:03 -07:00
michael-grunder
e9f6473845 We should run actions on PRs 2021-10-12 13:31:02 -07:00
michael-grunder
6ad4ccf3c7 Add Cygwin build test 2021-10-12 13:06:11 -07:00
michael-grunder
783a3789c2 Add Windows tests in GitHub actions
See: #992

TODO:  MinGW/cygwin tests
2021-10-10 14:24:58 -07:00
michael-grunder
0cac8dae1b Switch to GitHub actions
Since TravisCI.org was deprecated we've been without any tests.  This
commit adds back basic tests in Ubuntu, CentOS, and MacOS.

More sophisticated tests/platforms to come in the future (e.g. 32bit
tests).

See: #992
2021-10-10 13:18:01 -07:00
michael-grunder
fa900ef76f Fix unused variable warning. 2021-10-10 11:58:19 -07:00
michael-grunder
e489846b72 Minor refactor of CVE-2021-32765 fix.
Since `hi_calloc` always passes through one of our wrapper functions,
we can perform this overflow in the wrapper, and get protection
everywhere.

Previous commit: 76a7b10005

Related vuln ID: CVE-2021-32765
[Full Details](https://github.com/redis/hiredis/security/advisories/GHSA-hfm9-39pp-55p2)
2021-10-10 11:13:23 -07:00
Tongliang Liao
51c740824b Remove extra comma from cmake var.
Or it'll be treated as part of the var name.
2021-10-09 13:19:39 -07:00
michael-grunder
632bf07183 Merge branch 'release/v1.0.2' 2021-10-07 13:25:50 -07:00
michael-grunder
b731283245 Prepare for v1.0.2 GA 2021-10-07 10:00:14 -07:00
michael-grunder
d4e6f109a0 Revert erroneous SONAME bump 2021-10-07 09:48:08 -07:00
michael-grunder
a39824a5df Merge branch 'release/v1.0.1'
Merge the v1.0.1 release branch and bump the dev version to 1.0.2-dev
2021-10-04 13:35:10 -07:00
michael-grunder
8d1bfac464 Prepare for v1.0.1 GA 2021-10-04 13:08:51 -07:00
Yossi Gottlieb
76a7b10005 Fix for integer/buffer overflow CVE-2021-32765
This fix prevents hiredis from trying to allocate more than `SIZE_MAX`
bytes, which would result in a buffer overrun.

[Full Details](https://github.com/redis/hiredis/security/advisories/GHSA-hfm9-39pp-55p2)
2021-10-04 11:56:31 -07:00
Yunier Perez
9eca1f36f4 Allow to override OPENSSL_PREFIX in Linux 2021-10-03 10:58:27 -07:00
rouzier
2d9d77518d
Don't leak memory if an invalid type is set (#906)
Co-authored-by: James Rouzier <jrouzier@inverse.ca>
2021-08-18 19:13:34 -07:00
Meir Shpilraien (Spielrein)
f5f31ff9b9
Added REDIS_NO_AUTO_FREE_REPLIES flag (#962)
When set hiredis will not automatically free replies in an async context, and the replies must be freed instead by the user.

Co-authored-by: Michael Grunder <michael.grunder@gmail.com>
2021-07-11 11:26:20 -07:00
michael-grunder
5850a8ecd2 Ensure we curry any connect error to an async context. 2021-06-17 13:37:49 -07:00
michael-grunder
b6f86f38c2 Fix README.md
Closes #929
2021-05-23 10:45:47 -07:00
Michael Grunder
667dbf5365
Merge pull request #935 from kristjanvalur/pr5
Bugfix: Ignore timeout callback from a successful connect (fixes #945)
2021-05-02 12:26:05 -07:00
Michael Grunder
9bf6c250e5
Merge pull request #939 from zmartzone/improve_pr_896_ssl_leak
improve SSL leak fix redis/hiredis#896
2021-05-02 11:49:48 -07:00
Michael Grunder
959af97609
Merge pull request #949 from plan-do-break-fix/Typo-corrections
fix(docs): corrects typos in project README
2021-05-02 11:29:52 -07:00
plan-do-break-fix
0743f57bba fix(docs): corrects typos in project README 2021-04-24 02:23:36 -05:00
Hans Zandbelt
5f4382247a improve SSL leak fix redis/hiredis#896
Free SSL object when redisSSLConnect fails but avoid doing that for
callers of redisInitiateSSL who are supposed to manager their own SSL
object.

Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
2021-04-11 18:49:38 +02:00
Kristján Valur Jónsson
e06ecf7e45 Ignore timeout callback from a successful connect 2021-04-08 09:45:49 +00:00
michael-grunder
dfa33e60b0 Change order independant push logic to not change behavior.
Since redisGetReplyFromReader is exposed in a header file, we probably
shouldn't modify how it behaves in any way.  For this reason, handle the
changed logic in an internal static helper method.
2021-04-02 09:34:09 -07:00
michael-grunder
6204182aae Handle the case where an invalidation is sent second.
RESP3 invalidation messages always seemed to be sent before the response
to a given command, but it appears this is not always the case:

In Redis 6.2.0RC1 Redis sends the invalidation after the HSET in the
following sequence:

```
hget hash field
$5
value
hset hash field value
:0
>2
$10
invalidate
*1
$4
hash
```

To account for this possibility just wrap redisGetReplyFromReader in a
loop as it is called twice in redisGetReply.
2021-04-02 09:34:09 -07:00
michael-grunder
d6a0b192b4 Merge branch 'reader-updates'
Updates and improvements to the RESP3 protocol reader.

* Fix the unset len field when creating RESP3 double objects
* Fix RESP3 double infinity parsing
* Add additional validations when parsing various reply types
* Fix the parent type assertions in certain default reply object
  creation callbacks (mostly to include PUSH as a parent).
* Additional reader test cases
* Implement RESP3 BIGNUM support
* Refactor seekNewline() to use memchr()
2021-02-25 21:53:34 -08:00
michael-grunder
410c24d2a9 Fix off-by-one error in seekNewline 2021-02-25 21:25:17 -08:00
Alex Smith
bd7488d27d read: Validate line items prior to checking for object creation callbacks 2021-02-25 21:25:17 -08:00
Alex Smith
5f9242a1f8 read: Remove obsolete comment on nested multi bulk depth limitation 2021-02-25 21:25:17 -08:00
Alex Smith
83c1450425 read: Add support for the RESP3 bignum type 2021-02-25 21:25:17 -08:00
Alex Smith
c6646cb192 read: Ensure no invalid '\r' or '\n' in simple status/error strings 2021-02-25 21:25:17 -08:00
Alex Smith
e43061156c read: Additional validation and test case for RESP3 double
This ensures that malformed RESP3 double messages that include an
invalid null byte are not parsed as valid.
2021-02-25 21:25:17 -08:00
Alex Smith
c8adea4024 redisReply: Fix parent type assertions during double, nil, bool creation
Per RESP3, push messages are able to contain exactly what array
messages can contain (that is, any other type).
2021-02-25 21:25:17 -08:00
Alex Smith
ff73f1f9e7 redisReply: Explicitly list nil and bool cases in freeReplyObject() switch. 2021-02-25 21:25:17 -08:00
Alex Smith
0f92518847 test: Add test case for RESP3 set 2021-02-25 21:25:17 -08:00
Alex Smith
33c06dd503 test: Add test case for RESP3 map 2021-02-25 21:25:17 -08:00
Alex Smith
397fe26301 read: Use memchr() in seekNewline() instead of looping over entire string 2021-02-25 21:25:17 -08:00
Alex Smith
81c48a9821 test: Add test cases for RESP3 bool 2021-02-25 21:25:17 -08:00
Alex Smith
51e693f4fd read: Add additional RESP3 bool validation
RESP3 bools should be only one of "#t\r\n" or "#f\r\n". We also allow
capital 'T' and 'F' to be lenient.
2021-02-25 21:25:17 -08:00
Alex Smith
790b4d3b4d test: Add test cases for RESP3 nil 2021-02-25 21:25:17 -08:00
Alex Smith
d8899fbc19 read: Add additional RESP3 nil validation
RESP3 nil should consist of "_\r\n" and nothing else.
2021-02-25 21:25:17 -08:00
Alex Smith
96e8ea6110 test: Add test cases for infinite and NaN doubles 2021-02-25 21:25:17 -08:00
Alex Smith
f913e9b997 read: Fix double validation and infinity parsing
The ',' protocol byte gets removed in processItem(), so it should not
be compared against in processLineItem().

strtod() allows multiple representations of infinity and NaN that are
not RESP3 compliant. Since we explicitly check for the two compliant
infinity cases, strtod() should only return finite values.
2021-02-25 21:25:17 -08:00
Alex Smith
8039c7d26c test: Add test case for doubles 2021-02-25 21:25:17 -08:00
Alex Smith
49539fd1a7 redisReply: Fix - set len in double objects 2021-02-25 21:25:17 -08:00
Michael Grunder
53a8144c81
Merge pull request #924 from cheese1/master
http -> https
2021-02-17 10:08:35 -08:00
cheese1
9390de006d
http -> https 2021-02-17 16:15:45 +01:00
Michael Grunder
7d99b56356
Merge pull request #917 from Nordix/stack-alloc-dict-iter
Stack allocate dict iterators
2021-01-26 11:20:25 -08:00
Bjorn Svensson
4bba72103c Handle OOM during async command callback registration
Unless the callback is pushed to the list it will trigger an assert
in redisProcessCallbacks() when the response arrives.
This change let the user get an early error instead,
available in the async context directly.
2021-01-26 09:57:19 +01:00
Bjorn Svensson
920128a260 Stack allocate dict iterators
Replacing the get & release functions with an initiation
function. Simplifies the code and will make sure we
run subscription callbacks in OOM scenarios.
2021-01-25 16:19:42 +01:00
sundb
fd033e983a Remove semicolon after do-while in _EL_CLEANUP 2020-12-24 11:09:10 +08:00
michael-grunder
297ecbecb7 Tiny formatting changes + suppress implicit memcpy warning 2020-12-12 11:56:35 -08:00
AdamKorcz
f746a28e71 Removed 2 typecasts 2020-12-12 19:26:04 +00:00
AdamKorcz
940a04f4dd Added fuzzer 2020-12-11 14:03:42 +00:00
David Carlier
664c415e7f Illumos test fixes, error message difference fot bad hostname test. 2020-12-07 10:47:15 +00:00
Michael Grunder
e4a200040a
Merge pull request #896 from ayeganov/bugfix/ssl_leak
Free SSL object when redisSSLConnect fails
2020-11-15 09:56:10 -08:00
Aleksandr Yeganov
aefef8987f Free SSL object when redisSSLConnect fails 2020-11-10 11:59:41 -05:00
Michael Grunder
e3f88ebcf8
Merge pull request #894 from jcohen02/fix/issue893
Updating SSL connection example in README
2020-10-27 14:20:53 -07:00
Jeremy Cohen
308ffcab8d Updating SSL connection example 2020-10-27 19:40:13 +00:00
Michael Grunder
297f6551da
Merge pull request #889 from redis/wincert
Add support for Wincert CA store in Windows
2020-10-18 14:55:42 -07:00
michael-grunder
e7dda97859 Formatting 2020-10-18 14:24:22 -07:00
Michael Grunder
f44945a0af
Merge pull request #874 from masariello/position-independent-code
Enable position-independent code, and add PDB files to packages for MSVC builds
2020-10-18 14:04:02 -07:00
Michael Grunder
74e78498cf
Merge pull request #888 from michael-grunder/nil-push-invalidation
Fix handling of NIL invalidation messages.
2020-10-18 13:25:45 -07:00
michael-grunder
b9b9f446fe Fix handling of NIL invalidation messages.
When CLIENT TRACKING is enabled, Redis will send an invalidation message
with a NIL payload to all tracking clients after a FLUSHDB is executed.

We didn't account for REDIS_REPLY_PUSH being a valid parent object to a
NIL payload, and were failing an assertion.

Additionally this commit adds a regression test for the logic.
2020-10-17 19:08:05 -07:00
Michael Grunder
acc917548d
Merge pull request #885 from gkorland/patch-1
clean a warning, remvoe empty else block
2020-10-12 15:47:14 -07:00
Guy Korland
b086f763e7
clean a warning, remvoe empty else block 2020-10-12 17:15:29 +03:00
Michael Grunder
b47fae4e70
Merge pull request #881 from timgates42/bugfix_typo_terminated
docs: Fix simple typo, termined -> terminated
2020-09-27 21:04:58 -07:00
Tim Gates
f989670e59
docs: Fix simple typo, termined -> terminated
There is a small typo in sds.c.

Should read `terminated` rather than `termined`.
2020-09-26 20:58:09 +10:00
shiyuge
773d6ea8a7
Copy error to redisAsyncContext on timeout 2020-09-23 15:46:20 +08:00
Alessio M
e35300a668 add pdb files to packages for MSVC builds 2020-09-09 18:11:05 +01:00
Alessio M
dde6916b42 Add d suffix to debug libraries so that can packaged together with optimized builds (Release, RelWithDebInfo, etc) 2020-09-09 17:27:28 +01:00
Alessio M
3b68b5018e Enable position-independent code 2020-09-09 11:55:42 +01:00
Alessio M
6693863f4c Add support for system CA certificate store on Windows 2020-09-08 20:09:39 +01:00
michael-grunder
2a5a57b90a Remove whitespace 2020-09-07 17:47:50 -07:00
Alessio M
1b40ec5096 fixed issue with unit test linking on windows with SSL 2020-09-07 23:27:45 +01:00
Alessio M
d7b1d21e80 Merge branch 'master' of github.com:redis/hiredis 2020-09-04 09:31:47 +01:00
Michael Grunder
fb0e6c0dd9
Merge pull request #870 from michael-grunder/cmake-c99
Explicitly set c99 in CMake
2020-08-31 10:29:37 -07:00
michael-grunder
13a35bdb64 Explicitly set c99 in CMake
See #869
2020-08-28 12:35:20 -07:00
Michael Grunder
bea137ca94
Merge pull request #868 from michael-grunder/fix-sockaddr-typo
Fix sockaddr typo
2020-08-20 18:38:16 -07:00
michael-grunder
bd6f86eb6b Fix sockaddr typo
Fixes #867
2020-08-20 17:58:23 -07:00
Michael Grunder
48696e7e5e
Don't use non-installed win32.h helper in examples (#863)
See: #862
2020-08-07 10:26:38 -07:00
michael-grunder
faa1c4863a Merge tag 'v1.0.0'
Release of v1.0.0

Hiredis v1.0.0 marks the first stable release of Hiredis and introduces
RESP3 support, SSL connections, allocator injection, better Windows support,
and more.

IMPORTANT:  There are breaking changes in this release meaning your code
will need to be recompiled and may need small changes.  The exact
details of the breaking changes can be found in README.md.

CHANGELOG.md has a detailed list of changes between v0.14.1 and v1.0.0.

~~~

Thank you to everyone who contriubuted to the project by submitting PRs,
reporting bugs, or helping answer people's questions.

And a special thank you to the following people who contributed at least
five lines of code to this release (sorted by lines contributed) \o/

Michael Grunder, Yossi Gottlieb, Mark Nunberg, Marcus Geelnard, Justin Brewer,
Minun Dragonation, Omri Steiner, Sangmoon Yi, Jinjiazh, Odin Hultgren Van Der Horst,
Nick Rivera, Qi Yang, kevin1018
2020-08-03 12:00:32 -07:00
michael-grunder
d5b4c69b71 Prepare for v1.0.0 GA 2020-08-03 11:18:07 -07:00
Michael Grunder
5003906d63
Define a no op assert if we detect NDEBUG (#861)
Addresses #642
2020-07-31 13:23:28 -07:00
michael-grunder
ea063b7cc8 Use development specific versions in master
Avoids issues like #860
2020-07-31 12:32:40 -07:00
Michael Grunder
04a27f4800
We can run SSL tests everywhere except mingw/Windows (#859) 2020-07-31 09:16:32 -07:00
Michael Grunder
8966a1fc2b
Remove extra whitespace (#858) 2020-07-30 16:55:48 -07:00
lijiageng
34b7f7a0ff
Keep libev's code style (#857) 2020-07-30 10:59:19 -07:00
michael-grunder
a853467542 Update notes preparing for v1.0.0-rc1 2020-07-29 13:39:28 -07:00
Michael Grunder
d8ff72387d
Move SSL management to a distinct private pointer. (#855)
We need to allow our users to use redisContext->privdata as context
for any RESP3 PUSH messages, which means we can't use it for managing
SSL connections.

Bulletpoints:

* Create a secondary redisContext member for internal use only called
  privctx and rename the redisContextFuncs->free_privdata accordingly.

* Adds a `free_privdata` function pointer so the user can tie allocated
  memory to the lifetime of a redisContext (like they can already do
  with redisAsyncContext)

* Enables SSL tests in .travis.yml
2020-07-29 11:53:03 -07:00
Michael Grunder
be32bcdc8e
Minor refactor for scheduling an async timer. (#854)
Small change to the logic introduced in #839
2020-07-26 13:03:42 -07:00
valentinogeron
38b5ae543f
add a command_timeout to redisContextOptions (#839)
Add an additional timeout so the user has a convenient way of controlling distinct connect and command timeouts
2020-07-26 12:32:27 -07:00
masariello
07c3618ffe Add static library target and cpack support 2020-07-22 20:30:41 +01:00
michael-grunder
3bb985314d Fix a static analysis false positive
Static analyzer's can't tell that hi_calloc is calloc-like, and report a
potential null pointer dereference.  This isn't possible but it's
probably smarter to make the test anyway in the event code changes.
2020-07-21 15:39:40 -07:00
Michael Grunder
18fc12c392
Move include to sockcompat.h to maintain style (#850)
See #848
2020-07-20 16:27:28 -07:00
Malizia R
43aeabbbee
fix windows compiling with mingw (#848) 2020-07-20 15:34:52 -07:00
Michael Grunder
5a3c324138
Remove erroneous tag and add license to push example (#849) 2020-07-19 21:47:28 -07:00
Michael Grunder
2e7d7cbabd
Resp3 oob push support (#841)
Proper support for RESP3 PUSH messages.

By default, PUSH messages are now intercepted and the reply memory freed.  
This means existing code should work unchanged when connecting to Redis
>= 6.0.0 even if `CLIENT TRACKING` were then enabled.

Additionally, we define two callbacks users can configure if they wish to handle
these messages in a custom way:

void redisPushFn(void *privdata, void *reply);
void redisAsyncPushFn(redisAsyncContext *ac, void *reply);

See #825
2020-07-19 18:54:42 -07:00
Michael Grunder
1864e76ea7
Some Windows quality of life improvments. (#846)
* Don't try to ignore SIGPIPE in Windows (it doesn't exist).
* Add an include to our win32.h compatibility header.
* Enable building examples on Travis in Windows.

See #831
2020-07-10 12:53:52 -07:00
Michael Grunder
ada3665279
Use _WIN32 define instead of WIN32 (#845)
It appears that _WIN32 is always defined by MSVC whereas WIN32 may not
be, depending on configuration.
2020-07-08 17:44:30 -07:00
Michael Grunder
08593db1f2
Non Linux CI fixes (#844)
* Switch to memurai for Windows tests

* Switch to macports from brew.
2020-07-08 17:06:30 -07:00
OmriSteiner
392de5d7f9
fix #785: defer TCP_NODELAY in async tcp connections (#836)
Co-authored-by: Omri Steiner <omri@insoundz.com>
2020-06-22 13:20:30 -07:00
valentinogeron
a28de70a01
timeout option in redisConnectWithOptions should be on connect only (#829)
When connecting with a timeout, we shouldn't also call `redisSetTimeout` which will implement a timeout for commands.

See related issue #722
2020-06-18 21:45:25 -07:00
Michael Grunder
6448f735d5
sdsrange overflow fix (#830)
Fix overflow bug in `sdsrange`
2020-06-07 14:38:16 -07:00
Aureus
c726723545
Use explicit pointer casting for c++ compatibility (#826) 2020-06-01 15:21:27 -07:00
michael-grunder
4152bfce7c Merge branch 'new-ssl-api' 2020-05-30 11:50:59 -07:00
michael-grunder
904bf7fe00 Tiny OOM fix 2020-05-30 11:03:45 -07:00
michael-grunder
ffd6eaebd6 Merge branch 'master' into new-ssl-api 2020-05-30 09:30:01 -07:00
Michael Grunder
e553e0f382
Document allocator injection and completeness fix in test.c (#824) 2020-05-26 10:06:28 -07:00
Michael Grunder
f5d2585043
Use unique names for allocator struct members (#823)
Using `strdup` as a struct member causes issues in older gcc
2020-05-25 12:17:43 -07:00
Yossi Gottlieb
190bca88d0 New SSL API to replace redisSecureConnection(). 2020-05-24 23:37:47 +03:00
Michael Grunder
8e0264cfd6
Allow users to replace allocator and handle OOM everywhere. (#800)
* Adds an indirection to every allocation/deallocation to allow users to 
  plug in ones of their choosing (use custom functions, jemalloc, etc).

* Gracefully handle OOM everywhere in hiredis.  This should make it possible
  for users of the library to have more flexibility in how they handle such situations.

* Changes `redisReaderTask->elements` from an `int` to a `long long` to prevent
  a possible overflow when transferring the task elements into a `redisReply`.

* Adds a configurable `max elements` member to `redisReader` that defaults to
  2^32 - 1.  This can be set to "unlimited" by setting the value to zero.
2020-05-22 09:27:49 -07:00
Michael Grunder
83bba659b9
Add logic to handle RESP3 push messages (#819)
Fixes #815
2020-05-21 11:12:18 -07:00
Muhammad Zahalqa
c8999c6602
Use standrad isxdigit instead of custom helper function. (#814)
Standard function available and on most platforms uses a lookup table and not
a check with 3 ranges.
2020-05-20 09:35:15 -07:00
Michael Grunder
5c9f49e212
Resp3 verbatim string support (#805)
Pull RESP3 verbatim string handling from Redis

Fixes #802
2020-05-19 12:56:02 -07:00
Yossi Gottlieb
243099ccd2
Fix missing SSL build/install options. (#812)
* Fix missing SSL build/install options.

* Use different build commands for non-Linux linkers.
* Add missing install targets.

Fixes #809
2020-05-19 10:13:32 -07:00
Michael Grunder
a6a824cb65
Add link to ABI tracker (#808)
Addresses #506
2020-05-16 12:15:26 -07:00
Michael Grunder
eafb085d11
Remove nested depth limitation. (#797)
* Remove nested depth limitation.

This commit removes the nested multi-bulk depth limitation of 7.
We do this by switching to pointer to pointer indirection and
growing the stack in chunks when needed.

See: #794, #421
2020-05-04 10:36:04 -07:00
Michael Grunder
994d2fd77d
Support timeouts in libev adapater (#795)
Add support for timeouts in our libev adapter.

See #795
2020-05-04 10:35:30 -07:00
Michael Grunder
2cb203c1e9
Attempt to fix compilation on Solaris (#796)
See #757
2020-04-23 14:15:59 -07:00
Michael Grunder
7e2ee7c38b
Fix pkgconfig when installing to a custom lib dir (#793)
* Respect overridden libdir in CMake

See: #767
2020-04-23 11:11:55 -07:00
Michael Grunder
b484021859
Fix USE_SSL=1 make/cmake on OSX and CMake tests (#789)
* Fix linker problems when building with SSL enabled on OSX
* Corrects `HIREDIS_SSL=ON` to `USE_SSL=ON` so we test building with
  SSL enabled on travis.
2020-04-13 09:32:32 -07:00
Nick Rivera
0184caac9d
Provides an optional cleanup callback for async data. 2020-04-09 20:23:06 -07:00
Michael Grunder
a5613f3f7f
Use correct libuv call on Windows (#784)
Explicitly call `uv_poll_init_socket` as that has slightly different semantics on Windows (and is identical to `uv_poll_init` on Linux).

http://docs.libuv.org/en/v1.x/poll.html#c.uv_poll_init_socket
2020-04-09 12:39:49 -07:00
Michael Grunder
ec08c2b94a
Added CMake package config and fixed hiredis_ssl on Windows (#783)
* Add CMake package configuration so hiredis can be more easily included in 
  other projects.

* Fixes hiredis_ssl such that it compiles and works in windows

Co-authored-by: nrivera <nrivera@blizzard.com>
Co-authored-by: Nick <heronr1@gmail.com>
2020-04-09 08:05:14 -07:00
Michael Grunder
b314c0df3d
Merge pull request #780 from yossigo/fix/cmake_ssl_so_version
CMake: Set hiredis_ssl shared object version.
2020-04-07 12:04:39 -07:00
Yossi Gottlieb
1b967bc947 CMake: Set hiredis_ssl shared object version. 2020-04-07 21:19:00 +03:00
Michael Grunder
cc9d032971
Win32 tests and timeout fix (#776)
Unit tests in Windows and a Windows timeout fix

This commit gets our unit tests compiling and running on Windows as well as removes a duplicated `timeval` -> `DWORD` conversion logic in sockcompat.c 

There are minor differences in behavior between Linux and Windows to note:

1.  In Windows, opening a non-existent hangs forever in WSAPoll whereas
    it correctly returns with a "Connection refused" error on Linux.
    For that reason, I simply skip this test in Windows.

    It may be related to this known issue:
    https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/

2.  Timeouts are handled slightly differently in Windows and Linux.  
    In Linux, we intentionally set REDIS_ERR_IO for connection
    timeouts whereas in Windows we set REDIS_ERR_TIMEOUT.  It may be
    prudent to fix this discrepancy although there are almost certainly
    users relying on the current behavior.
2020-04-02 22:41:34 -07:00
michael-grunder
ec18d790f1 Const correctness
Fixes #778
2020-04-02 11:38:04 -07:00
michael-grunder
a1405e172a Add release pattern for automatic CI 2020-03-14 15:09:37 -07:00
michael-grunder
d57067564e Actually define include guard 2020-03-13 10:57:06 -07:00
michael-grunder
139f966cc8 Bump version to 0.15.0 2020-02-28 11:04:52 -08:00
Michael Grunder
38675d23cc
Housekeeping fixes (#764)
Housekeeping

* Check for C++ (#758, #750) 
* Include `alloc.h` in `make install` and `cmake`
* Add a `.def` file for Windows (#760)
* Include allocation wrappers referenced in adapter headers
* Fix minor syntax errors and typos in README
* Fix CI in Windows by properly escaping arguments (#761)
2020-02-27 21:29:05 -08:00
Michael Grunder
3421ac3093
Merge pull request #756 from ch1aki/fix-make-install
install alloc.h
2020-01-29 01:00:14 -08:00
akichan
3ae4739799 install alloc.h
alloc.h added in # 754
But not added to INSTALL_INCLUDE_PATH
2020-01-29 17:26:34 +09:00
Michael Grunder
669ac9d0c8
Safe allocation wrappers (#754)
Create allocation wrappers with a configurable OOM handler (defaults to abort()).

See #752, #747
2020-01-28 12:13:05 -08:00
Michael Grunder
0501c623c9
Merge pull request #746 from ShooterIT/spelling
fix spelling mistakes
2020-01-01 00:43:30 -08:00
ShooterIT
386b9950f3 fix spelling mistakes 2020-01-01 14:42:10 +08:00
Michael Grunder
c96d492215
Merge pull request #741 from redis/redisgetreply-null
Free the reply in redisGetReply when passed NULL
2019-12-18 13:45:01 -08:00
michael-grunder
ac0b186aa3 Free the reply in redisGetReply when passed NULL
We currently perform a NULL check in redisGetReply and don't push the
reply back to the caller, but we don't free any reply meaning that this
will leak memory:

redisGetReply(context, NULL);

This change simply frees the reply if we were passed NULL.

Addresses #740
2019-12-12 14:40:50 -08:00
Michael Grunder
b2d1ad64d0
Merge pull request #727 from pbotros/botros/disable-tests-cmake
Adding an option to DISABLE_TESTS
2019-12-05 17:06:44 -08:00
Michael Grunder
f2ef5f0c98
Merge pull request #737 from natoscott/master
Fix dead code in sslLogCallback relating to should_log variable.
2019-11-25 17:01:12 -08:00
Nathan Scott
42697bd45a Fix dead code in sslLogCallback relating to should_log variable.
Coverity scans found that the should_log logic in sslLogCallback
is not working as expected because the variable is not correctly
initialised (the conditional code before logging always sets the
value to 1, which it already is).
2019-11-25 15:21:05 +11:00
Michael Grunder
5d0568d9ae
Merge pull request #731 from Kevin-Xi/patch-1
Fix typo in dict.c.
2019-11-19 11:11:46 -08:00
Kevin
e58a9f7d52
Fix typo in dict.c. 2019-11-20 00:00:00 +08:00
Paul Botros
f052fd1add Adding an option to DISABLE_TESTS
Useful when hiredis is used as a CMake dependency in other projects and
added via add_subdirectory(). With DISABLE_TESTS on, `make test` in the
parent project won't run hiredis tests.
2019-11-03 22:01:28 -08:00
michael-grunder
e777b0295e Use correct flag in README.md 2019-10-10 15:46:33 -07:00
Michael Grunder
42a2e679be
Merge pull request #720 from yossigo/ssl-docs
Update README with SSL support.
2019-10-10 14:54:01 -07:00
Michael Grunder
18eeeed076
Merge pull request #715 from redis/test-leaks
Fixes leaks in unit tests
2019-10-08 10:55:15 -07:00
Yossi Gottlieb
c5726ba5f7 Update README with SSL support. 2019-10-07 13:23:59 +03:00
michael-grunder
5aa7b1056b Fixes leaks in unit tests
redisFormatSdsCommandArgv takes an sds* and calls sdsempty() for us.

Addresses #714
2019-09-25 11:02:44 -07:00
Mark Nunberg
0153527444
Merge pull request #711 from yossigo/ssl-tests
SSL Tests
2019-09-16 10:43:53 -04:00
Yossi Gottlieb
d952ed877a Add SSL mode tests.
This repeats all existing tests in SSL mode, but does not yet provide
SSL-specific tests.
2019-09-16 17:30:35 +03:00
Yossi Gottlieb
a1e538092d Make SSL timeout error compatible with rest. 2019-09-16 17:30:35 +03:00
Yossi Gottlieb
d41443bd3d Fix: redisReconnect() should clear SSL context.
We should not attempt to keep the context and re-establish the
TLS connection for several reasons:

1. Maintain symmetry between redisConnect() and redisReconnect(), so in
both cases an extra step is required to initiate SSL.
2. The caller may also wish to reconfigure the SSL session and needs a
chance to do that.
3. It is not a practical thing to do on an async non-blocking connection
context.
2019-09-16 17:30:35 +03:00
Mark Nunberg
bd2c8fedf7
Merge pull request #708 from yossigo/wip/ssl-reorganization
SSL Reorganization
2019-09-16 06:05:46 -04:00
Yossi Gottlieb
dd408e8e3f Update CMakelists for hiredis/hiredis_ssl builds.
Also rename the SSL option from `HIREDIS_SSL` to `ENABLE_SSL` to conform
with CMake convnetions.
2019-09-01 13:47:39 +03:00
Yossi Gottlieb
5872d818d9 Separate hiredis and hiredis_ssl library build. 2019-09-01 13:46:27 +03:00
Yossi Gottlieb
5c85a04164 Use a const funcs in redisContext. 2019-09-01 13:46:07 +03:00
Yossi Gottlieb
44ef4de9d9 Update CMakeLists with sslio.c rename. 2019-08-29 22:21:40 +03:00
Yossi Gottlieb
df68d7d8bd Rename sslio. 2019-08-29 22:14:09 +03:00
Yossi Gottlieb
8715ba5c82 wip: SSL code reorganization, see #705. 2019-08-29 22:09:37 +03:00
Mark Nunberg
1ac8fca35d
Merge pull request #706 from yossigo/fix/msvc
Fix MSVC build.
2019-08-29 10:14:34 -04:00
Yossi Gottlieb
28759c4b81 Fix MSVC build. 2019-08-28 18:43:40 +03:00
Mark Nunberg
2020f6f329
Merge pull request #702 from yossigo/report-connect-errors
SSL: Properly report SSL_connect() errors.
2019-08-27 07:10:25 -04:00
Mark Nunberg
ff4fa45422
Merge pull request #697 from yossigo/resp3
Port RESP3 support from Redis.
2019-08-27 06:59:32 -04:00
Mark Nunberg
ce7cb7bcc4
Merge pull request #699 from yossigo/silent-ssl-trace
Silent SSL trace to stdout by default.
2019-08-27 06:58:55 -04:00
Yossi Gottlieb
aacd4ccd76 Fix typo. 2019-08-25 11:43:27 +03:00
Yossi Gottlieb
153b8f632b SSL: Properly report SSL_connect() errors. 2019-08-22 16:20:41 +03:00
Mark Nunberg
9c7c694cb7
Merge pull request #670 from jman-krafton/master
fix timeout code in windows
2019-08-13 08:50:41 -04:00
Yossi Gottlieb
6d21ffce7c Silent SSL trace to stdout by default. 2019-08-13 12:51:47 +03:00
Sangmoon Yi
8e61d5737a add recv error code for clarifying timeout 2019-08-12 10:55:08 +09:00
Sangmoon Yi
ab1762cd92 fix timeout code in windows 2019-08-12 10:54:12 +09:00
Mark Nunberg
ac49287c3d
Merge pull request #663 from mbitsnbites/mingw-support-2
Windows: MinGW fixes and Windows Travis builders
2019-08-09 07:37:28 -04:00
Marcus Geelnard
bbbafc5324 Travis: Add a Windows MSVC 2017 compilation test 2019-08-09 11:49:02 +02:00
Marcus Geelnard
687997c410 Travis: Add a MinGW cross compilation test 2019-08-09 11:49:02 +02:00
Marcus Geelnard
173f16ab55 MSVC: Fix some compiler warnings in sds.h 2019-08-09 11:49:02 +02:00
Marcus Geelnard
85fee25653 MinGW fix: Use _MSC_VER instead of _WIN32 where appropriate
Use _MSC_VER (instead of _WIN32) for things that are specific for
Visual Studio.

Also remove #include <winsock2.h> from hiredis.h, as it leaks too
many symbols and defines into the global namespace, which is
undesirable for a public interface header. Anyone who uses the
the affected parts of the hiredis API needs to include the
appropriate headers anyway in order to declare struct timeval
variables.
2019-08-09 11:49:02 +02:00
Mark Nunberg
3af99d5fd5
Merge pull request #597 from justinbrewer/createArray-size_t
Update createArray to take size_t
2019-08-09 04:03:02 -04:00
Mark Nunberg
f9bccfb7ba
Merge branch 'master' into createArray-size_t 2019-08-09 04:02:53 -04:00
Mark Nunberg
5d013039a9
Merge pull request #621 from Crunsher/master
Update README.md
2019-08-09 03:54:24 -04:00
Mark Nunberg
fe215464ca
Merge pull request #662 from dragonation/master
The setsockopt and getsockopt API diffs from BSD socket and WSA one
2019-08-09 03:52:06 -04:00
Mark Nunberg
b1fa77d023
Merge pull request #665 from ghost/musl-test-compat
test: fix errstr matching for musl libc
2019-08-09 03:48:56 -04:00
Mark Nunberg
99cdec371f
Merge pull request #671 from movebean/master
redisReaderGetReply leak memory
2019-08-09 03:44:25 -04:00
Mark Nunberg
fbb83c4a7a
Merge pull request #684 from qlyoung/remove-unnecessary-nullcheck
Remove unnecessary null check before free
2019-08-09 03:40:32 -04:00
Mark Nunberg
9414207002
Merge pull request #688 from kevin1018/patch-1
Add install adapters header files
2019-08-09 03:40:05 -04:00
Mark Nunberg
c259f9059d
Merge pull request #691 from Miniwoffer/master
Removed whitespace before newline
2019-08-09 03:39:39 -04:00
Yossi Gottlieb
91de9c975a RESP3 support changes from Redis.
This corresponds to commits d5c54f0b..bea09a7f in the redis repository.
2019-08-04 12:13:04 +03:00
Yossi Gottlieb
a7a1886b7e Initial RESP3 support [d5c54f0b]. 2019-08-04 11:55:24 +03:00
Odin Hultgren Van Der Horst
a1d4da63b8 Removed whitespace before newline
- Removed whitespace before newline
 - Removed win style newline
2019-07-22 11:06:10 +02:00
kevin1018
8249e67355
Add install adapters header files 2019-07-17 19:05:06 +08:00
Quentin Young
83d3c097ef Remove unnecessary null check before free 2019-07-03 21:36:48 +00:00
qi.yang
918e24c83b redisReaderGetReply leak memory 2019-05-30 15:03:38 +08:00
Eivind Uggedal
993af7710e test: fix errstr matching for musl libc
This makes the tests pass on musl[1] based distros like Alpine Linux.

[1]: https://www.musl-libc.org/
2019-05-14 07:29:26 +00:00
Minun Dragonation
76394f1be8 remove useless type casting 2019-05-13 23:20:05 +08:00
Minun Dragonation
f5454d509f fix bugs on socket timeout tv usec calculation 2019-05-05 21:58:34 +08:00
Minun Dragonation
4a94ce6326 fix bugs for optlen output on size not big enough for timeout events 2019-05-05 21:46:34 +08:00
Minun Dragonation
82252440de fix bugs on ref address incorrect on sockcompact with getsockopt 2019-05-05 21:39:46 +08:00
Minun Dragonation
d8f814d48b fix bugs of setsockopt diff in win compact implementation 2019-05-05 21:34:28 +08:00
Mark Nunberg
f5f855c912
Merge pull request #658 from jinjiazhang/master
Fix Compile Error On Windows (Visual Studio)
2019-04-13 09:16:46 -04:00
jinjiazhang
cdb836d5f8 Fix Compile Error On Windows (Visual Studio) 2019-04-13 10:38:34 +08:00
Jean Flach
9ff1cc7826 Update README.md
Add note about using context->data to pass user data to connect and disconnect callbacks
2018-10-26 15:57:23 +02:00
Justin Brewer
300fc013c1 Add Changelog entry about the integer parsing changes
This should have been included in
93421f9d84 but was missed.

Signed-off-by: Justin Brewer <jzb0012@auburn.edu>
2018-05-21 11:00:52 -05:00
Justin Brewer
ef4256670f Update createArray to take size_t
This makes createArray consistent with createString, which also takes
size_t. Bounds-check and unit tests are updated to allow up to
min(SIZE_MAX,LLONG_MAX).

Changelog is updated to mention this API break.

Signed-off-by: Justin Brewer <jzb0012@auburn.edu>
2018-05-21 10:49:30 -05:00
70 changed files with 7685 additions and 1253 deletions

49
.github/release-drafter-config.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name-template: '$NEXT_MAJOR_VERSION'
tag-template: 'v$NEXT_MAJOR_VERSION'
autolabeler:
- label: 'maintenance'
files:
- '*.md'
- '.github/*'
- label: 'bug'
branch:
- '/bug-.+'
- label: 'maintenance'
branch:
- '/maintenance-.+'
- label: 'feature'
branch:
- '/feature-.+'
categories:
- title: 'Breaking Changes'
labels:
- 'breakingchange'
- title: '🧪 Experimental Features'
labels:
- 'experimental'
- title: '🚀 New Features'
labels:
- 'feature'
- 'enhancement'
- title: '🐛 Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- 'BUG'
- title: '🧰 Maintenance'
label: 'maintenance'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
exclude-labels:
- 'skip-changelog'
template: |
## Changes
$CHANGES
## Contributors
We'd like to thank all the contributors who worked on this release!
$CONTRIBUTORS

29
.github/spellcheck-settings.yml vendored Normal file
View File

@ -0,0 +1,29 @@
matrix:
- name: Markdown
expect_match: false
apsell:
lang: en
d: en_US
ignore-case: true
dictionary:
wordlists:
- .github/wordlist.txt
output: wordlist.dic
pipeline:
- pyspelling.filters.markdown:
markdown_extensions:
- markdown.extensions.extra:
- pyspelling.filters.html:
comments: false
attributes:
- alt
ignores:
- ':matches(code, pre)'
- code
- pre
- blockquote
- img
sources:
- 'README.md'
- 'FAQ.md'
- 'docs/**'

99
.github/wordlist.txt vendored Normal file
View File

@ -0,0 +1,99 @@
ABI
ACLs
alloc
Allocator
allocators
antirez
api
APIs
ASYNC
asyncRedisContext
asyncronous
AUTOFREE
autoload
autoloader
autoloading
Autoloading
backend
backends
behaviour
boolean
CAS
Changelog
customizable
Customizable
CVE
dataset
de
deallocation
ElastiCache
extensibility
FPM
getaddrinfo
gmail
grunder
Grunder
hiredis
Hiredis
HIREDIS
hostname
IANA
IPv
IPV
keepalive
keyspace
keyspaces
KiB
libc
libev
libevent
localhost
Lua
michael
minimalistic
namespace
NOAUTOFREE
NOAUTOFREEREPLIES
NONBLOCK
Noordhuis
OpenSSL
Packagist
pcnoordhuis
PhpRedis
Pieter
pipelined
pipelining
pluggable
Predis
PRERELEASE
printf
PSR
PSUBSCRIBE
rb
Readme
README
rebalanced
rebalancing
redis
Redis
redisAsyncContext
redisContext
redisOptions
redisReader
reusability
REUSEADDR
runtime
Sanfilippo
SHA
sharding
SONAME
SSL
struct
stunnel
subelements
TCP
TLS
unparsed
UNSPEC
URI
variadic

141
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,141 @@
name: Build and test
on: [push, pull_request]
jobs:
ubuntu:
name: Ubuntu
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: |
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
sudo apt-get update
sudo apt-get install -y redis-server valgrind libevent-dev
- name: Build using cmake
env:
EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON
CFLAGS: -Werror
CXXFLAGS: -Werror
run: mkdir build && cd build && cmake .. && make
- name: Build using makefile
run: USE_SSL=1 TEST_ASYNC=1 make
- name: Run tests
env:
SKIPS_AS_FAILS: 1
TEST_SSL: 1
run: $GITHUB_WORKSPACE/test.sh
# - name: Run tests under valgrind
# env:
# SKIPS_AS_FAILS: 1
# TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full
# run: $GITHUB_WORKSPACE/test.sh
centos8:
name: RockyLinux 8
runs-on: ubuntu-latest
container: rockylinux:8
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: |
dnf -y upgrade --refresh
dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
dnf -y module install redis:remi-6.0
dnf -y group install "Development Tools"
dnf -y install openssl-devel cmake valgrind libevent-devel
- name: Build using cmake
env:
EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON
CFLAGS: -Werror
CXXFLAGS: -Werror
run: mkdir build && cd build && cmake .. && make
- name: Build using Makefile
run: USE_SSL=1 TEST_ASYNC=1 make
- name: Run tests
env:
SKIPS_AS_FAILS: 1
TEST_SSL: 1
run: $GITHUB_WORKSPACE/test.sh
- name: Run tests under valgrind
env:
SKIPS_AS_FAILS: 1
TEST_SSL: 1
TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full
run: $GITHUB_WORKSPACE/test.sh
freebsd:
runs-on: ubuntu-latest
name: FreeBSD
steps:
- uses: actions/checkout@v3
- name: Build in FreeBSD
uses: vmactions/freebsd-vm@v1.0.5
with:
prepare: pkg install -y gmake cmake
run: |
mkdir build && cd build && cmake .. && make && cd ..
gmake
macos:
name: macOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: |
brew install openssl redis@7.2
brew link redis@7.2 --force
- name: Build hiredis
run: USE_SSL=1 make
- name: Run tests
env:
TEST_SSL: 1
run: $GITHUB_WORKSPACE/test.sh
windows:
name: Windows
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: |
choco install -y ninja memurai-developer
- uses: ilammy/msvc-dev-cmd@v1
- name: Build hiredis
run: |
mkdir build && cd build
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON
ninja -v
- name: Run tests
run: |
./build/hiredis-test.exe
- name: Install Cygwin Action
uses: cygwin/cygwin-install-action@v2
with:
packages: make git gcc-core
- name: Build in cygwin
env:
HIREDIS_PATH: ${{ github.workspace }}
run: |
make clean && make

19
.github/workflows/release-drafter.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Release Drafter
on:
push:
# branches to consider in the event; optional, defaults to all
branches:
- master
jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
# Drafts your next Release notes as Pull Requests are merged into "master"
- uses: release-drafter/release-drafter@v5
with:
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
config-name: release-drafter-config.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

14
.github/workflows/spellcheck.yml vendored Normal file
View File

@ -0,0 +1,14 @@
name: spellcheck
on:
pull_request:
jobs:
check-spelling:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Check Spelling
uses: rojopolis/spellcheck-github-actions@0.33.1
with:
config_path: .github/spellcheck-settings.yml
task_name: Markdown

100
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,100 @@
name: C/C++ CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
full-build:
name: Build all, plus default examples, run tests against redis
runs-on: ubuntu-latest
env:
# the docker image used by the test.sh
REDIS_DOCKER: redis:alpine
steps:
- name: Install prerequisites
run: sudo apt-get update && sudo apt-get install -y libev-dev libevent-dev libglib2.0-dev libssl-dev valgrind
- uses: actions/checkout@v3
- name: Run make
run: make all examples
- name: Run unittests
run: make check
- name: Run tests with valgrind
env:
TEST_PREFIX: valgrind --error-exitcode=100
SKIPS_ARG: --skip-throughput
run: make check
build-32-bit:
name: Build and test minimal 32 bit linux
runs-on: ubuntu-latest
steps:
- name: Install prerequisites
run: sudo apt-get update && sudo apt-get install gcc-multilib
- uses: actions/checkout@v3
- name: Run make
run: make all
env:
PLATFORM_FLAGS: -m32
- name: Run unittests
env:
REDIS_DOCKER: redis:alpine
run: make check
build-arm:
name: Cross-compile and test arm linux with Qemu
runs-on: ubuntu-latest
strategy:
matrix:
include:
- name: arm
toolset: arm-linux-gnueabi
emulator: qemu-arm
- name: aarch64
toolset: aarch64-linux-gnu
emulator: qemu-aarch64
steps:
- name: Install qemu
if: matrix.emulator
run: sudo apt-get update && sudo apt-get install -y qemu-user
- name: Install platform toolset
if: matrix.toolset
run: sudo apt-get install -y gcc-${{matrix.toolset}}
- uses: actions/checkout@v3
- name: Run make
run: make all
env:
CC: ${{matrix.toolset}}-gcc
AR: ${{matrix.toolset}}-ar
- name: Run unittests
env:
REDIS_DOCKER: redis:alpine
TEST_PREFIX: ${{matrix.emulator}} -L /usr/${{matrix.toolset}}/
run: make check
build-windows:
name: Build and test on windows 64 bit Intel
runs-on: windows-latest
steps:
- uses: microsoft/setup-msbuild@v1.0.2
- uses: actions/checkout@v3
- name: Run CMake (shared lib)
run: cmake -Wno-dev CMakeLists.txt
- name: Build hiredis (shared lib)
run: MSBuild hiredis.vcxproj /p:Configuration=Debug
- name: Run CMake (static lib)
run: cmake -Wno-dev CMakeLists.txt -DBUILD_SHARED_LIBS=OFF
- name: Build hiredis (static lib)
run: MSBuild hiredis.vcxproj /p:Configuration=Debug
- name: Build hiredis-test
run: MSBuild hiredis-test.vcxproj /p:Configuration=Debug
# use memurai, redis compatible server, since it is easy to install. Can't
# install official redis containers on the windows runner
- name: Install Memurai redis server
run: choco install -y memurai-developer.install
- name: Run tests
run: Debug\hiredis-test.exe

2
.gitignore vendored
View File

@ -6,3 +6,5 @@
/*.a /*.a
/*.pc /*.pc
*.dSYM *.dSYM
tags
compile_commands.json

View File

@ -1,5 +1,4 @@
language: c language: c
sudo: false
compiler: compiler:
- gcc - gcc
- clang - clang
@ -8,8 +7,29 @@ os:
- linux - linux
- osx - osx
dist: bionic
branches:
only:
- staging
- trying
- master
- /^release\/.*$/
install:
- if [ "$TRAVIS_COMPILER" != "mingw" ]; then
wget https://github.com/redis/redis/archive/6.0.6.tar.gz;
tar -xzvf 6.0.6.tar.gz;
pushd redis-6.0.6 && BUILD_TLS=yes make && export PATH=$PWD/src:$PATH && popd;
fi;
before_script: before_script:
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi - if [ "$TRAVIS_OS_NAME" == "osx" ]; then
curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.6.2-10.13-HighSierra.pkg;
sudo installer -pkg MacPorts-2.6.2-10.13-HighSierra.pkg -target /;
export PATH=$PATH:/opt/local/bin && sudo port -v selfupdate;
sudo port -N install openssl redis;
fi;
addons: addons:
apt: apt:
@ -21,6 +41,8 @@ addons:
- libc6-dbg:i386 - libc6-dbg:i386
- gcc-multilib - gcc-multilib
- g++-multilib - g++-multilib
- libssl-dev
- libssl-dev:i386
- valgrind - valgrind
env: env:
@ -28,7 +50,7 @@ env:
- BITS="64" - BITS="64"
script: script:
- EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON"; - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON";
if [ "$TRAVIS_OS_NAME" == "osx" ]; then if [ "$TRAVIS_OS_NAME" == "osx" ]; then
if [ "$BITS" == "32" ]; then if [ "$BITS" == "32" ]; then
CFLAGS="-m32 -Werror"; CFLAGS="-m32 -Werror";
@ -52,7 +74,52 @@ script:
fi; fi;
fi; fi;
export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
- make && make clean;
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
if [ "$BITS" == "64" ]; then
OPENSSL_PREFIX="$(ls -d /usr/local/Cellar/openssl@1.1/*)" USE_SSL=1 make;
fi;
else
USE_SSL=1 make;
fi;
- mkdir build/ && cd build/ - mkdir build/ && cd build/
- cmake .. ${EXTRA_CMAKE_OPTS} - cmake .. ${EXTRA_CMAKE_OPTS}
- make VERBOSE=1 - make VERBOSE=1
- ctest -V - if [ "$BITS" == "64" ]; then
TEST_SSL=1 SKIPS_AS_FAILS=1 ctest -V;
else
SKIPS_AS_FAILS=1 ctest -V;
fi;
jobs:
include:
# Windows MinGW cross compile on Linux
- os: linux
dist: xenial
compiler: mingw
addons:
apt:
packages:
- ninja-build
- gcc-mingw-w64-x86-64
- g++-mingw-w64-x86-64
script:
- mkdir build && cd build
- CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on
- ninja -v
# Windows MSVC 2017
- os: windows
compiler: msvc
env:
- MATRIX_EVAL="CC=cl.exe && CXX=cl.exe"
before_install:
- eval "${MATRIX_EVAL}"
install:
- choco install ninja
- choco install -y memurai-developer
script:
- mkdir build && cd build
- cmd.exe //C 'C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat' amd64 '&&'
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON '&&' ninja -v
- ./hiredis-test.exe

View File

@ -1,18 +1,410 @@
### 1.0.0 (unreleased) ## [1.2.0](https://github.com/redis/hiredis/tree/v1.2.0) - (2023-06-04)
Announcing Hiredis v1.2.0 with with new adapters, and a great many bug fixes.
## 🚀 New Features
- Add sdevent adapter @Oipo (#1144)
- Allow specifying the keepalive interval @michael-grunder (#1168)
- Add RedisModule adapter @tezc (#1182)
- Helper for setting TCP_USER_TIMEOUT socket option @zuiderkwast (#1188)
## 🐛 Bug Fixes
- Fix a typo in b6a052f. @yossigo (#1190)
- Fix wincrypt symbols conflict @hudayou (#1151)
- Don't attempt to set a timeout if we are in an error state. @michael-grunder (#1180)
- Accept -nan per the RESP3 spec recommendation. @michael-grunder (#1178)
- Fix colliding option values @zuiderkwast (#1172)
- Ensure functionality without `_MSC_VER` definition @windyakin (#1194)
## 🧰 Maintenance
- Add a test for the TCP_USER_TIMEOUT option. @michael-grunder (#1192)
- Add -Werror as a default. @yossigo (#1193)
- CI: Update homebrew Redis version. @yossigo (#1191)
- Fix typo in makefile. @michael-grunder (#1179)
- Write a version file for the CMake package @Neverlord (#1165)
- CMakeLists.txt: respect BUILD_SHARED_LIBS @ffontaine (#1147)
- Cmake static or shared @autoantwort (#1160)
- fix typo @tillkruss (#1153)
- Add a test ensuring we don't clobber connection error. @michael-grunder (#1181)
- Search for openssl on macOS @michael-grunder (#1169)
## Contributors
We'd like to thank all the contributors who worked on this release!
<a href="https://github.com/neverlord"><img src="https://github.com/neverlord.png" width="32" height="32"></a>
<a href="https://github.com/Oipo"><img src="https://github.com/Oipo.png" width="32" height="32"></a>
<a href="https://github.com/autoantwort"><img src="https://github.com/autoantwort.png" width="32" height="32"></a>
<a href="https://github.com/ffontaine"><img src="https://github.com/ffontaine.png" width="32" height="32"></a>
<a href="https://github.com/hudayou"><img src="https://github.com/hudayou.png" width="32" height="32"></a>
<a href="https://github.com/michael-grunder"><img src="https://github.com/michael-grunder.png" width="32" height="32"></a>
<a href="https://github.com/postgraph"><img src="https://github.com/postgraph.png" width="32" height="32"></a>
<a href="https://github.com/tezc"><img src="https://github.com/tezc.png" width="32" height="32"></a>
<a href="https://github.com/tillkruss"><img src="https://github.com/tillkruss.png" width="32" height="32"></a>
<a href="https://github.com/vityafx"><img src="https://github.com/vityafx.png" width="32" height="32"></a>
<a href="https://github.com/windyakin"><img src="https://github.com/windyakin.png" width="32" height="32"></a>
<a href="https://github.com/yossigo"><img src="https://github.com/yossigo.png" width="32" height="32"></a>
<a href="https://github.com/zuiderkwast"><img src="https://github.com/zuiderkwast.png" width="32" height="32"></a>
## [1.1.0](https://github.com/redis/hiredis/tree/v1.1.0) - (2022-11-15)
Announcing Hiredis v1.1.0 GA with better SSL convenience, new async adapters and a great many bug fixes.
**NOTE**: Hiredis can now return `nan` in addition to `-inf` and `inf` when returning a `REDIS_REPLY_DOUBLE`.
## 🐛 Bug Fixes
- Add support for nan in RESP3 double [@filipecosta90](https://github.com/filipecosta90)
([\#1133](https://github.com/redis/hiredis/pull/1133))
## 🧰 Maintenance
- Add an example that calls redisCommandArgv [@michael-grunder](https://github.com/michael-grunder)
([\#1140](https://github.com/redis/hiredis/pull/1140))
- fix flag reference [@pata00](https://github.com/pata00) ([\#1136](https://github.com/redis/hiredis/pull/1136))
- Make freeing a NULL redisAsyncContext a no op. [@michael-grunder](https://github.com/michael-grunder)
([\#1135](https://github.com/redis/hiredis/pull/1135))
- CI updates ([@bjosv](https://github.com/redis/bjosv) ([\#1139](https://github.com/redis/hiredis/pull/1139))
## Contributors
We'd like to thank all the contributors who worked on this release!
<a href="https://github.com/bjsov"><img src="https://github.com/bjosv.png" width="32" height="32"></a>
<a href="https://github.com/filipecosta90"><img src="https://github.com/filipecosta90.png" width="32" height="32"></a>
<a href="https://github.com/michael-grunder"><img src="https://github.com/michael-grunder.png" width="32" height="32"></a>
<a href="https://github.com/pata00"><img src="https://github.com/pata00.png" width="32" height="32"></a>
## [1.1.0-rc1](https://github.com/redis/hiredis/tree/v1.1.0-rc1) - (2022-11-06)
Announcing Hiredis v1.1.0-rc1, with better SSL convenience, new async adapters, and a great many bug fixes.
## 🚀 New Features
- Add possibility to prefer IPv6, IPv4 or unspecified [@zuiderkwast](https://github.com/zuiderkwast)
([\#1096](https://github.com/redis/hiredis/pull/1096))
- Add adapters/libhv [@ithewei](https://github.com/ithewei) ([\#904](https://github.com/redis/hiredis/pull/904))
- Add timeout support to libhv adapter. [@michael-grunder](https://github.com/michael-grunder) ([\#1109](https://github.com/redis/hiredis/pull/1109))
- set default SSL verification path [@adobeturchenko](https://github.com/adobeturchenko) ([\#928](https://github.com/redis/hiredis/pull/928))
- Introduce .close method for redisContextFuncs [@pizhenwei](https://github.com/pizhenwei) ([\#1094](https://github.com/redis/hiredis/pull/1094))
- Make it possible to set SSL verify mode [@stanhu](https://github.com/stanhu) ([\#1085](https://github.com/redis/hiredis/pull/1085))
- Polling adapter and example [@kristjanvalur](https://github.com/kristjanvalur) ([\#932](https://github.com/redis/hiredis/pull/932))
- Unsubscribe handling in async [@bjosv](https://github.com/bjosv) ([\#1047](https://github.com/redis/hiredis/pull/1047))
- Add timeout support for libuv adapter [@MichaelSuen-thePointer](https://github.com/@MichaelSuenthePointer) ([\#1016](https://github.com/redis/hiredis/pull/1016))
## 🐛 Bug Fixes
- Update for MinGW cross compile [@bit0fun](https://github.com/bit0fun) ([\#1127](https://github.com/redis/hiredis/pull/1127))
- fixed CPP build error with adapters/libhv.h [@mtdxc](https://github.com/mtdxc) ([\#1125](https://github.com/redis/hiredis/pull/1125))
- Fix protocol error
[@michael-grunder](https://github.com/michael-grunder),
[@mtuleika-appcast](https://github.com/mtuleika-appcast) ([\#1106](https://github.com/redis/hiredis/pull/1106))
- Use a windows specific keepalive function. [@michael-grunder](https://github.com/michael-grunder) ([\#1104](https://github.com/redis/hiredis/pull/1104))
- Fix CMake config path on Linux. [@xkszltl](https://github.com/xkszltl) ([\#989](https://github.com/redis/hiredis/pull/989))
- Fix potential fault at createDoubleObject [@afcidk](https://github.com/afcidk) ([\#964](https://github.com/redis/hiredis/pull/964))
- Fix some undefined behavior [@jengab](https://github.com/jengab) ([\#1091](https://github.com/redis/hiredis/pull/1091))
- Copy OOM errors to redisAsyncContext when finding subscribe callback [@bjosv](https://github.com/bjosv) ([\#1090](https://github.com/redis/hiredis/pull/1090))
- Maintain backward compatibility with our onConnect callback. [@michael-grunder](https://github.com/michael-grunder) ([\#1087](https://github.com/redis/hiredis/pull/1087))
- Fix PUSH handler tests for Redis >= 7.0.5 [@michael-grunder](https://github.com/michael-grunder) ([\#1121](https://github.com/redis/hiredis/pull/1121))
- fix heap-buffer-overflow [@zhangtaoXT5](https://github.com/zhangtaoXT5) ([\#957](https://github.com/redis/hiredis/pull/957))
- Fix heap-buffer-overflow issue in redisvFormatCommad [@bjosv](https://github.com/bjosv) ([\#1097](https://github.com/redis/hiredis/pull/1097))
- Polling adapter requires sockcompat.h [@michael-grunder](https://github.com/michael-grunder) ([\#1095](https://github.com/redis/hiredis/pull/1095))
- Illumos test fixes, error message difference for bad hostname test. [@devnexen](https://github.com/devnexen) ([\#901](https://github.com/redis/hiredis/pull/901))
- Remove semicolon after do-while in \_EL\_CLEANUP [@sundb](https://github.com/sundb) ([\#905](https://github.com/redis/hiredis/pull/905))
- Stability: Support calling redisAsyncCommand and redisAsyncDisconnect from the onConnected callback [@kristjanvalur](https://github.com/kristjanvalur)
([\#931](https://github.com/redis/hiredis/pull/931))
- Fix async connect on Windows [@kristjanvalur](https://github.com/kristjanvalur) ([\#1073](https://github.com/redis/hiredis/pull/1073))
- Fix tests so they work for Redis 7.0 [@michael-grunder](https://github.com/michael-grunder) ([\#1072](https://github.com/redis/hiredis/pull/1072))
- Fix warnings on Win64 [@orgads](https://github.com/orgads) ([\#1058](https://github.com/redis/hiredis/pull/1058))
- Handle push notifications before or after reply. [@yossigo](https://github.com/yossigo) ([\#1062](https://github.com/redis/hiredis/pull/1062))
- Update hiredis sds with improvements found in redis [@bjosv](https://github.com/bjosv) ([\#1045](https://github.com/redis/hiredis/pull/1045))
- Avoid incorrect call to the previous reply's callback [@bjosv](https://github.com/bjosv) ([\#1040](https://github.com/redis/hiredis/pull/1040))
- fix building on AIX and SunOS [\#1031](https://github.com/redis/hiredis/pull/1031) ([@scddev](https://github.com/scddev))
- Allow sending commands after sending an unsubscribe [@bjosv](https://github.com/bjosv) ([\#1036](https://github.com/redis/hiredis/pull/1036))
- Correction for command timeout during pubsub [@bjosv](https://github.com/bjosv) ([\#1038](https://github.com/redis/hiredis/pull/1038))
- Fix adapters/libevent.h compilation for 64-bit Windows [@pbtummillo](https://github.com/pbtummillo) ([\#937](https://github.com/redis/hiredis/pull/937))
- Fix integer overflow when format command larger than 4GB [@sundb](https://github.com/sundb) ([\#1030](https://github.com/redis/hiredis/pull/1030))
- Handle array response during subscribe in RESP3 [@bjosv](https://github.com/bjosv) ([\#1014](https://github.com/redis/hiredis/pull/1014))
- Support PING while subscribing (RESP2) [@bjosv](https://github.com/bjosv) ([\#1027](https://github.com/redis/hiredis/pull/1027))
## 🧰 Maintenance
- CI fixes in preparation of release [@michael-grunder](https://github.com/michael-grunder) ([\#1130](https://github.com/redis/hiredis/pull/1130))
- Add do while(0) (protection for macros [@afcidk](https://github.com/afcidk) [\#959](https://github.com/redis/hiredis/pull/959))
- Fixup of PR734: Coverage of hiredis.c [@bjosv](https://github.com/bjosv) ([\#1124](https://github.com/redis/hiredis/pull/1124))
- CMake corrections for building on Windows [@bjosv](https://github.com/bjosv) ([\#1122](https://github.com/redis/hiredis/pull/1122))
- Install on windows fixes [@bjosv](https://github.com/bjosv) ([\#1117](https://github.com/redis/hiredis/pull/1117))
- Add libhv example to our standard Makefile [@michael-grunder](https://github.com/michael-grunder) ([\#1108](https://github.com/redis/hiredis/pull/1108))
- Additional include directory given by pkg-config [@bjosv](https://github.com/bjosv) ([\#1118](https://github.com/redis/hiredis/pull/1118))
- Use __attribute__ when building with Clang on Windows [@bjosv](https://github.com/bjosv) ([\#1115](https://github.com/redis/hiredis/pull/1115))
- Minor refactor [@michael-grunder](https://github.com/michael-grunder) ([\#1110](https://github.com/redis/hiredis/pull/1110))
- Fix pkgconfig result for hiredis_ssl [@bjosv](https://github.com/bjosv) ([\#1107](https://github.com/redis/hiredis/pull/1107))
- Update documentation to explain redisConnectWithOptions. [@michael-grunder](https://github.com/michael-grunder) ([\#1099](https://github.com/redis/hiredis/pull/1099))
- uvadapter: reduce number of uv_poll_start calls [@noxiouz](https://github.com/noxiouz) ([\#1098](https://github.com/redis/hiredis/pull/1098))
- Regression test for off-by-one parsing error [@bugwz](https://github.com/bugwz) ([\#1092](https://github.com/redis/hiredis/pull/1092))
- CMake: remove dict.c form hiredis_sources [@Lipraxde](https://github.com/Lipraxde) ([\#1055](https://github.com/redis/hiredis/pull/1055))
- Do store command timeout in the context for redisSetTimeout [@catterer](https://github.com/catterer) ([\#593](https://github.com/redis/hiredis/pull/593), [\#1093](https://github.com/redis/hiredis/pull/1093))
- Add GitHub Actions CI workflow for hiredis: Arm, Arm64, 386, windows. [@kristjanvalur](https://github.com/kristjanvalur) ([\#943](https://github.com/redis/hiredis/pull/943))
- CI: bump macOS runner version [@SukkaW](https://github.com/SukkaW) ([\#1079](https://github.com/redis/hiredis/pull/1079))
- Support for generating release notes [@chayim](https://github.com/chayim) ([\#1083](https://github.com/redis/hiredis/pull/1083))
- Improve example for SSL initialization in README.md [@stanhu](https://github.com/stanhu) ([\#1084](https://github.com/redis/hiredis/pull/1084))
- Fix README typos [@bjosv](https://github.com/bjosv) ([\#1080](https://github.com/redis/hiredis/pull/1080))
- fix cmake version [@smmir-cent](https://github.com/@smmircent) ([\#1050](https://github.com/redis/hiredis/pull/1050))
- Use the same name for static and shared libraries [@orgads](https://github.com/orgads) ([\#1057](https://github.com/redis/hiredis/pull/1057))
- Embed debug information in windows static .lib file [@kristjanvalur](https://github.com/kristjanvalur) ([\#1054](https://github.com/redis/hiredis/pull/1054))
- Improved async documentation [@kristjanvalur](https://github.com/kristjanvalur) ([\#1074](https://github.com/redis/hiredis/pull/1074))
- Use official repository for redis package. [@yossigo](https://github.com/yossigo) ([\#1061](https://github.com/redis/hiredis/pull/1061))
- Whitelist hiredis repo path in cygwin [@michael-grunder](https://github.com/michael-grunder) ([\#1063](https://github.com/redis/hiredis/pull/1063))
- CentOS 8 is EOL, switch to RockyLinux [@michael-grunder](https://github.com/michael-grunder) ([\#1046](https://github.com/redis/hiredis/pull/1046))
- CMakeLists.txt: allow building without a C++ compiler [@ffontaine](https://github.com/ffontaine) ([\#872](https://github.com/redis/hiredis/pull/872))
- Makefile: move SSL options into a block and refine rules [@pizhenwei](https://github.com/pizhenwei) ([\#997](https://github.com/redis/hiredis/pull/997))
- Update CMakeLists.txt for more portability [@EricDeng1001](https://github.com/EricDeng1001) ([\#1005](https://github.com/redis/hiredis/pull/1005))
- FreeBSD build fixes + CI [@michael-grunder](https://github.com/michael-grunder) ([\#1026](https://github.com/redis/hiredis/pull/1026))
- Add asynchronous test for pubsub using RESP3 [@bjosv](https://github.com/bjosv) ([\#1012](https://github.com/redis/hiredis/pull/1012))
- Trigger CI failure when Valgrind issues are found [@bjosv](https://github.com/bjosv) ([\#1011](https://github.com/redis/hiredis/pull/1011))
- Move to using make directly in Cygwin [@michael-grunder](https://github.com/michael-grunder) ([\#1020](https://github.com/redis/hiredis/pull/1020))
- Add asynchronous API tests [@bjosv](https://github.com/bjosv) ([\#1010](https://github.com/redis/hiredis/pull/1010))
- Correcting the build target `coverage` for enabled SSL [@bjosv](https://github.com/bjosv) ([\#1009](https://github.com/redis/hiredis/pull/1009))
- GH Actions: Run SSL tests during CI [@bjosv](https://github.com/bjosv) ([\#1008](https://github.com/redis/hiredis/pull/1008))
- GH: Actions - Add valgrind and CMake [@michael-grunder](https://github.com/michael-grunder) ([\#1004](https://github.com/redis/hiredis/pull/1004))
- Add Centos8 tests in GH Actions [@michael-grunder](https://github.com/michael-grunder) ([\#1001](https://github.com/redis/hiredis/pull/1001))
- We should run actions on PRs [@michael-grunder](https://github.com/michael-grunder) (([\#1000](https://github.com/redis/hiredis/pull/1000))
- Add Cygwin test in GitHub actions [@michael-grunder](https://github.com/michael-grunder) ([\#999](https://github.com/redis/hiredis/pull/999))
- Add Windows tests in GitHub actions [@michael-grunder](https://github.com/michael-grunder) ([\#996](https://github.com/redis/hiredis/pull/996))
- Switch to GitHub actions [@michael-grunder](https://github.com/michael-grunder) ([\#995](https://github.com/redis/hiredis/pull/995))
- Minor refactor of CVE-2021-32765 fix. [@michael-grunder](https://github.com/michael-grunder) ([\#993](https://github.com/redis/hiredis/pull/993))
- Remove extra comma from CMake var. [@xkszltl](https://github.com/xkszltl) ([\#988](https://github.com/redis/hiredis/pull/988))
- Add REDIS\_OPT\_PREFER\_UNSPEC [@michael-grunder](https://github.com/michael-grunder) ([\#1101](https://github.com/redis/hiredis/pull/1101))
## Contributors
We'd like to thank all the contributors who worked on this release!
<a href="https://github.com/EricDeng1001"><img src="https://github.com/EricDeng1001.png" width="32" height="32"></a>
<a href="https://github.com/Lipraxde"><img src="https://github.com/Lipraxde.png" width="32" height="32"></a>
<a href="https://github.com/MichaelSuen-thePointer"><img src="https://github.com/MichaelSuen-thePointer.png" width="32" height="32"></a>
<a href="https://github.com/SukkaW"><img src="https://github.com/SukkaW.png" width="32" height="32"></a>
<a href="https://github.com/adobeturchenko"><img src="https://github.com/adobeturchenko.png" width="32" height="32"></a>
<a href="https://github.com/afcidk"><img src="https://github.com/afcidk.png" width="32" height="32"></a>
<a href="https://github.com/bit0fun"><img src="https://github.com/bit0fun.png" width="32" height="32"></a>
<a href="https://github.com/bjosv"><img src="https://github.com/bjosv.png" width="32" height="32"></a>
<a href="https://github.com/bugwz"><img src="https://github.com/bugwz.png" width="32" height="32"></a>
<a href="https://github.com/catterer"><img src="https://github.com/catterer.png" width="32" height="32"></a>
<a href="https://github.com/chayim"><img src="https://github.com/chayim.png" width="32" height="32"></a>
<a href="https://github.com/devnexen"><img src="https://github.com/devnexen.png" width="32" height="32"></a>
<a href="https://github.com/ffontaine"><img src="https://github.com/ffontaine.png" width="32" height="32"></a>
<a href="https://github.com/ithewei"><img src="https://github.com/ithewei.png" width="32" height="32"></a>
<a href="https://github.com/jengab"><img src="https://github.com/jengab.png" width="32" height="32"></a>
<a href="https://github.com/kristjanvalur"><img src="https://github.com/kristjanvalur.png" width="32" height="32"></a>
<a href="https://github.com/michael-grunder"><img src="https://github.com/michael-grunder.png" width="32" height="32"></a>
<a href="https://github.com/noxiouz"><img src="https://github.com/noxiouz.png" width="32" height="32"></a>
<a href="https://github.com/mtdxc"><img src="https://github.com/mtdxc.png" width="32" height="32"></a>
<a href="https://github.com/orgads"><img src="https://github.com/orgads.png" width="32" height="32"></a>
<a href="https://github.com/pbtummillo"><img src="https://github.com/pbtummillo.png" width="32" height="32"></a>
<a href="https://github.com/pizhenwei"><img src="https://github.com/pizhenwei.png" width="32" height="32"></a>
<a href="https://github.com/scddev"><img src="https://github.com/scddev.png" width="32" height="32"></a>
<a href="https://github.com/smmir-cent"><img src="https://github.com/smmir-cent.png" width="32" height="32"></a>
<a href="https://github.com/stanhu"><img src="https://github.com/stanhu.png" width="32" height="32"></a>
<a href="https://github.com/sundb"><img src="https://github.com/sundb.png" width="32" height="32"></a>
<a href="https://github.com/vturchenko"><img src="https://github.com/vturchenko.png" width="32" height="32"></a>
<a href="https://github.com/xkszltl"><img src="https://github.com/xkszltl.png" width="32" height="32"></a>
<a href="https://github.com/yossigo"><img src="https://github.com/yossigo.png" width="32" height="32"></a>
<a href="https://github.com/zhangtaoXT5"><img src="https://github.com/zhangtaoXT5.png" width="32" height="32"></a>
<a href="https://github.com/zuiderkwast"><img src="https://github.com/zuiderkwast.png" width="32" height="32"></a>
## [1.0.2](https://github.com/redis/hiredis/tree/v1.0.2) - (2021-10-07)
Announcing Hiredis v1.0.2, which fixes CVE-2021-32765 but returns the SONAME to the correct value of `1.0.0`.
- [Revert SONAME bump](https://github.com/redis/hiredis/commit/d4e6f109a064690cde64765c654e679fea1d3548)
([Michael Grunder](https://github.com/michael-grunder))
## [1.0.1](https://github.com/redis/hiredis/tree/v1.0.1) - (2021-10-04)
<span style="color:red">This release erroneously bumped the SONAME, please use [1.0.2](https://github.com/redis/hiredis/tree/v1.0.2)</span>
Announcing Hiredis v1.0.1, a security release fixing CVE-2021-32765
- Fix for [CVE-2021-32765](https://github.com/redis/hiredis/security/advisories/GHSA-hfm9-39pp-55p2)
[commit](https://github.com/redis/hiredis/commit/76a7b10005c70babee357a7d0f2becf28ec7ed1e)
([Yossi Gottlieb](https://github.com/yossigo))
_Thanks to [Yossi Gottlieb](https://github.com/yossigo) for the security fix and to [Microsoft Security Vulnerability Research](https://www.microsoft.com/en-us/msrc/msvr) for finding the bug._ :sparkling_heart:
## [1.0.0](https://github.com/redis/hiredis/tree/v1.0.0) - (2020-08-03)
Announcing Hiredis v1.0.0, which adds support for RESP3, SSL connections, allocator injection, and better Windows support! :tada:
_A big thanks to everyone who helped with this release. The following list includes everyone who contributed at least five lines, sorted by lines contributed._ :sparkling_heart:
[Michael Grunder](https://github.com/michael-grunder), [Yossi Gottlieb](https://github.com/yossigo),
[Mark Nunberg](https://github.com/mnunberg), [Marcus Geelnard](https://github.com/mbitsnbites),
[Justin Brewer](https://github.com/justinbrewer), [Valentino Geron](https://github.com/valentinogeron),
[Minun Dragonation](https://github.com/dragonation), [Omri Steiner](https://github.com/OmriSteiner),
[Sangmoon Yi](https://github.com/jman-krafton), [Jinjiazh](https://github.com/jinjiazhang),
[Odin Hultgren Van Der Horst](https://github.com/Miniwoffer), [Muhammad Zahalqa](https://github.com/tryfinally),
[Nick Rivera](https://github.com/heronr), [Qi Yang](https://github.com/movebean),
[kevin1018](https://github.com/kevin1018)
[Full Changelog](https://github.com/redis/hiredis/compare/v0.14.1...v1.0.0)
**BREAKING CHANGES**: **BREAKING CHANGES**:
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now * `redisOptions` now has two timeout fields. One for connecting, and one for commands. If you're presently using `options->timeout` you will need to change it to use `options->connect_timeout`. (See [example](https://github.com/redis/hiredis/commit/38b5ae543f5c99eb4ccabbe277770fc6bc81226f#diff-86ba39d37aa829c8c82624cce4f049fbL36))
protocol errors. This is consistent with the RESP specification. On 32-bit
platforms, the upper bound is lowered to `SIZE_MAX`. * Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now protocol errors. This is consistent
with the RESP specification. On 32-bit platforms, the upper bound is lowered to `SIZE_MAX`.
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
**New features:**
- Support for RESP3
[\#697](https://github.com/redis/hiredis/pull/697),
[\#805](https://github.com/redis/hiredis/pull/805),
[\#819](https://github.com/redis/hiredis/pull/819),
[\#841](https://github.com/redis/hiredis/pull/841)
([Yossi Gottlieb](https://github.com/yossigo), [Michael Grunder](https://github.com/michael-grunder))
- Support for SSL connections
[\#645](https://github.com/redis/hiredis/pull/645),
[\#699](https://github.com/redis/hiredis/pull/699),
[\#702](https://github.com/redis/hiredis/pull/702),
[\#708](https://github.com/redis/hiredis/pull/708),
[\#711](https://github.com/redis/hiredis/pull/711),
[\#821](https://github.com/redis/hiredis/pull/821),
[more](https://github.com/redis/hiredis/pulls?q=is%3Apr+is%3Amerged+SSL)
([Mark Nunberg](https://github.com/mnunberg), [Yossi Gottlieb](https://github.com/yossigo))
- Run-time allocator injection
[\#800](https://github.com/redis/hiredis/pull/800)
([Michael Grunder](https://github.com/michael-grunder))
- Improved Windows support (including MinGW and Windows CI)
[\#652](https://github.com/redis/hiredis/pull/652),
[\#663](https://github.com/redis/hiredis/pull/663)
([Marcus Geelnard](https://www.bitsnbites.eu/author/m/))
- Adds support for distinct connect and command timeouts
[\#839](https://github.com/redis/hiredis/pull/839),
[\#829](https://github.com/redis/hiredis/pull/829)
([Valentino Geron](https://github.com/valentinogeron))
- Add generic pointer and destructor to `redisContext` that users can use for context.
[\#855](https://github.com/redis/hiredis/pull/855)
([Michael Grunder](https://github.com/michael-grunder))
**Closed issues (that involved code changes):**
- Makefile does not install TLS libraries [\#809](https://github.com/redis/hiredis/issues/809)
- redisConnectWithOptions should not set command timeout [\#722](https://github.com/redis/hiredis/issues/722), [\#829](https://github.com/redis/hiredis/pull/829) ([valentinogeron](https://github.com/valentinogeron))
- Fix integer overflow in `sdsrange` [\#827](https://github.com/redis/hiredis/issues/827)
- INFO & CLUSTER commands failed when using RESP3 [\#802](https://github.com/redis/hiredis/issues/802)
- Windows compatibility patches [\#687](https://github.com/redis/hiredis/issues/687), [\#838](https://github.com/redis/hiredis/issues/838), [\#842](https://github.com/redis/hiredis/issues/842)
- RESP3 PUSH messages incorrectly use pending callback [\#825](https://github.com/redis/hiredis/issues/825)
- Asynchronous PSUBSCRIBE command fails when using RESP3 [\#815](https://github.com/redis/hiredis/issues/815)
- New SSL API [\#804](https://github.com/redis/hiredis/issues/804), [\#813](https://github.com/redis/hiredis/issues/813)
- Hard-coded limit of nested reply depth [\#794](https://github.com/redis/hiredis/issues/794)
- Fix TCP_NODELAY in Windows/OSX [\#679](https://github.com/redis/hiredis/issues/679), [\#690](https://github.com/redis/hiredis/issues/690), [\#779](https://github.com/redis/hiredis/issues/779), [\#785](https://github.com/redis/hiredis/issues/785),
- Added timers to libev adapter. [\#778](https://github.com/redis/hiredis/issues/778), [\#795](https://github.com/redis/hiredis/pull/795)
- Initialization discards const qualifier [\#777](https://github.com/redis/hiredis/issues/777)
- \[BUG\]\[MinGW64\] Error setting socket timeout [\#775](https://github.com/redis/hiredis/issues/775)
- undefined reference to hi_malloc [\#769](https://github.com/redis/hiredis/issues/769)
- hiredis pkg-config file incorrectly ignores multiarch libdir spec'n [\#767](https://github.com/redis/hiredis/issues/767)
- Don't use -G to build shared object on Solaris [\#757](https://github.com/redis/hiredis/issues/757)
- error when make USE\_SSL=1 [\#748](https://github.com/redis/hiredis/issues/748)
- Allow to change SSL Mode [\#646](https://github.com/redis/hiredis/issues/646)
- hiredis/adapters/libevent.h memleak [\#618](https://github.com/redis/hiredis/issues/618)
- redisLibuvPoll crash when server closes the connetion [\#545](https://github.com/redis/hiredis/issues/545)
- about redisAsyncDisconnect question [\#518](https://github.com/redis/hiredis/issues/518)
- hiredis adapters libuv error for help [\#508](https://github.com/redis/hiredis/issues/508)
- API/ABI changes analysis [\#506](https://github.com/redis/hiredis/issues/506)
- Memory leak patch in Redis [\#502](https://github.com/redis/hiredis/issues/502)
- Remove the depth limitation [\#421](https://github.com/redis/hiredis/issues/421)
**Merged pull requests:**
- Move SSL management to a distinct private pointer [\#855](https://github.com/redis/hiredis/pull/855) ([michael-grunder](https://github.com/michael-grunder))
- Move include to sockcompat.h to maintain style [\#850](https://github.com/redis/hiredis/pull/850) ([michael-grunder](https://github.com/michael-grunder))
- Remove erroneous tag and add license to push example [\#849](https://github.com/redis/hiredis/pull/849) ([michael-grunder](https://github.com/michael-grunder))
- fix windows compiling with mingw [\#848](https://github.com/redis/hiredis/pull/848) ([rmalizia44](https://github.com/rmalizia44))
- Some Windows quality of life improvements. [\#846](https://github.com/redis/hiredis/pull/846) ([michael-grunder](https://github.com/michael-grunder))
- Use \_WIN32 define instead of WIN32 [\#845](https://github.com/redis/hiredis/pull/845) ([michael-grunder](https://github.com/michael-grunder))
- Non Linux CI fixes [\#844](https://github.com/redis/hiredis/pull/844) ([michael-grunder](https://github.com/michael-grunder))
- Resp3 oob push support [\#841](https://github.com/redis/hiredis/pull/841) ([michael-grunder](https://github.com/michael-grunder))
- fix \#785: defer TCP\_NODELAY in async tcp connections [\#836](https://github.com/redis/hiredis/pull/836) ([OmriSteiner](https://github.com/OmriSteiner))
- sdsrange overflow fix [\#830](https://github.com/redis/hiredis/pull/830) ([michael-grunder](https://github.com/michael-grunder))
- Use explicit pointer casting for c++ compatibility [\#826](https://github.com/redis/hiredis/pull/826) ([aureus1](https://github.com/aureus1))
- Document allocator injection and completeness fix in test.c [\#824](https://github.com/redis/hiredis/pull/824) ([michael-grunder](https://github.com/michael-grunder))
- Use unique names for allocator struct members [\#823](https://github.com/redis/hiredis/pull/823) ([michael-grunder](https://github.com/michael-grunder))
- New SSL API to replace redisSecureConnection\(\). [\#821](https://github.com/redis/hiredis/pull/821) ([yossigo](https://github.com/yossigo))
- Add logic to handle RESP3 push messages [\#819](https://github.com/redis/hiredis/pull/819) ([michael-grunder](https://github.com/michael-grunder))
- Use standrad isxdigit instead of custom helper function. [\#814](https://github.com/redis/hiredis/pull/814) ([tryfinally](https://github.com/tryfinally))
- Fix missing SSL build/install options. [\#812](https://github.com/redis/hiredis/pull/812) ([yossigo](https://github.com/yossigo))
- Add link to ABI tracker [\#808](https://github.com/redis/hiredis/pull/808) ([michael-grunder](https://github.com/michael-grunder))
- Resp3 verbatim string support [\#805](https://github.com/redis/hiredis/pull/805) ([michael-grunder](https://github.com/michael-grunder))
- Allow users to replace allocator and handle OOM everywhere. [\#800](https://github.com/redis/hiredis/pull/800) ([michael-grunder](https://github.com/michael-grunder))
- Remove nested depth limitation. [\#797](https://github.com/redis/hiredis/pull/797) ([michael-grunder](https://github.com/michael-grunder))
- Attempt to fix compilation on Solaris [\#796](https://github.com/redis/hiredis/pull/796) ([michael-grunder](https://github.com/michael-grunder))
- Support timeouts in libev adapater [\#795](https://github.com/redis/hiredis/pull/795) ([michael-grunder](https://github.com/michael-grunder))
- Fix pkgconfig when installing to a custom lib dir [\#793](https://github.com/redis/hiredis/pull/793) ([michael-grunder](https://github.com/michael-grunder))
- Fix USE\_SSL=1 make/cmake on OSX and CMake tests [\#789](https://github.com/redis/hiredis/pull/789) ([michael-grunder](https://github.com/michael-grunder))
- Use correct libuv call on Windows [\#784](https://github.com/redis/hiredis/pull/784) ([michael-grunder](https://github.com/michael-grunder))
- Added CMake package config and fixed hiredis\_ssl on Windows [\#783](https://github.com/redis/hiredis/pull/783) ([michael-grunder](https://github.com/michael-grunder))
- CMake: Set hiredis\_ssl shared object version. [\#780](https://github.com/redis/hiredis/pull/780) ([yossigo](https://github.com/yossigo))
- Win32 tests and timeout fix [\#776](https://github.com/redis/hiredis/pull/776) ([michael-grunder](https://github.com/michael-grunder))
- Provides an optional cleanup callback for async data. [\#768](https://github.com/redis/hiredis/pull/768) ([heronr](https://github.com/heronr))
- Housekeeping fixes [\#764](https://github.com/redis/hiredis/pull/764) ([michael-grunder](https://github.com/michael-grunder))
- install alloc.h [\#756](https://github.com/redis/hiredis/pull/756) ([ch1aki](https://github.com/ch1aki))
- fix spelling mistakes [\#746](https://github.com/redis/hiredis/pull/746) ([ShooterIT](https://github.com/ShooterIT))
- Free the reply in redisGetReply when passed NULL [\#741](https://github.com/redis/hiredis/pull/741) ([michael-grunder](https://github.com/michael-grunder))
- Fix dead code in sslLogCallback relating to should\_log variable. [\#737](https://github.com/redis/hiredis/pull/737) ([natoscott](https://github.com/natoscott))
- Fix typo in dict.c. [\#731](https://github.com/redis/hiredis/pull/731) ([Kevin-Xi](https://github.com/Kevin-Xi))
- Adding an option to DISABLE\_TESTS [\#727](https://github.com/redis/hiredis/pull/727) ([pbotros](https://github.com/pbotros))
- Update README with SSL support. [\#720](https://github.com/redis/hiredis/pull/720) ([yossigo](https://github.com/yossigo))
- Fixes leaks in unit tests [\#715](https://github.com/redis/hiredis/pull/715) ([michael-grunder](https://github.com/michael-grunder))
- SSL Tests [\#711](https://github.com/redis/hiredis/pull/711) ([yossigo](https://github.com/yossigo))
- SSL Reorganization [\#708](https://github.com/redis/hiredis/pull/708) ([yossigo](https://github.com/yossigo))
- Fix MSVC build. [\#706](https://github.com/redis/hiredis/pull/706) ([yossigo](https://github.com/yossigo))
- SSL: Properly report SSL\_connect\(\) errors. [\#702](https://github.com/redis/hiredis/pull/702) ([yossigo](https://github.com/yossigo))
- Silent SSL trace to stdout by default. [\#699](https://github.com/redis/hiredis/pull/699) ([yossigo](https://github.com/yossigo))
- Port RESP3 support from Redis. [\#697](https://github.com/redis/hiredis/pull/697) ([yossigo](https://github.com/yossigo))
- Removed whitespace before newline [\#691](https://github.com/redis/hiredis/pull/691) ([Miniwoffer](https://github.com/Miniwoffer))
- Add install adapters header files [\#688](https://github.com/redis/hiredis/pull/688) ([kevin1018](https://github.com/kevin1018))
- Remove unnecessary null check before free [\#684](https://github.com/redis/hiredis/pull/684) ([qlyoung](https://github.com/qlyoung))
- redisReaderGetReply leak memory [\#671](https://github.com/redis/hiredis/pull/671) ([movebean](https://github.com/movebean))
- fix timeout code in windows [\#670](https://github.com/redis/hiredis/pull/670) ([jman-krafton](https://github.com/jman-krafton))
- test: fix errstr matching for musl libc [\#665](https://github.com/redis/hiredis/pull/665) ([ghost](https://github.com/ghost))
- Windows: MinGW fixes and Windows Travis builders [\#663](https://github.com/redis/hiredis/pull/663) ([mbitsnbites](https://github.com/mbitsnbites))
- The setsockopt and getsockopt API diffs from BSD socket and WSA one [\#662](https://github.com/redis/hiredis/pull/662) ([dragonation](https://github.com/dragonation))
- Fix Compile Error On Windows \(Visual Studio\) [\#658](https://github.com/redis/hiredis/pull/658) ([jinjiazhang](https://github.com/jinjiazhang))
- Fix NXDOMAIN test case [\#653](https://github.com/redis/hiredis/pull/653) ([michael-grunder](https://github.com/michael-grunder))
- Add MinGW support [\#652](https://github.com/redis/hiredis/pull/652) ([mbitsnbites](https://github.com/mbitsnbites))
- SSL Support [\#645](https://github.com/redis/hiredis/pull/645) ([mnunberg](https://github.com/mnunberg))
- Fix Invalid argument after redisAsyncConnectUnix [\#644](https://github.com/redis/hiredis/pull/644) ([codehz](https://github.com/codehz))
- Makefile: use predefined AR [\#632](https://github.com/redis/hiredis/pull/632) ([Mic92](https://github.com/Mic92))
- FreeBSD build fix [\#628](https://github.com/redis/hiredis/pull/628) ([devnexen](https://github.com/devnexen))
- Fix errors not propagating properly with libuv.h. [\#624](https://github.com/redis/hiredis/pull/624) ([yossigo](https://github.com/yossigo))
- Update README.md [\#621](https://github.com/redis/hiredis/pull/621) ([Crunsher](https://github.com/Crunsher))
- Fix redisBufferRead documentation [\#620](https://github.com/redis/hiredis/pull/620) ([hacst](https://github.com/hacst))
- Add CPPFLAGS to REAL\_CFLAGS [\#614](https://github.com/redis/hiredis/pull/614) ([thomaslee](https://github.com/thomaslee))
- Update createArray to take size\_t [\#597](https://github.com/redis/hiredis/pull/597) ([justinbrewer](https://github.com/justinbrewer))
- fix common realloc mistake and add null check more [\#580](https://github.com/redis/hiredis/pull/580) ([charsyam](https://github.com/charsyam))
- Proper error reporting for connect failures [\#578](https://github.com/redis/hiredis/pull/578) ([mnunberg](https://github.com/mnunberg))
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
## [1.0.0-rc1](https://github.com/redis/hiredis/tree/v1.0.0-rc1) - (2020-07-29)
_Note: There were no changes to code between v1.0.0-rc1 and v1.0.0 so see v1.0.0 for changelog_
### 0.14.1 (2020-03-13)
* Adds safe allocation wrappers (CVE-2020-7105, #747, #752) (Michael Grunder)
### 0.14.0 (2018-09-25)
**BREAKING CHANGES**:
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string * 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 User code should compare this to `size_t` values as well.
compare to other values, casting might be necessary or can be removed, if If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
casting was applied before.
### 0.14.0 (2018-09-25)
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) * Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537]) * Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
@ -50,8 +442,9 @@
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 * Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
* Fix warnings, when compiled with -Wshadow * Fix warnings, when compiled with -Wshadow
* Make hiredis compile in Cygwin on Windows, now CI-tested * Make hiredis compile in Cygwin on Windows, now CI-tested
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
**BREAKING CHANGES**: protocol errors. This is consistent with the RESP specification. On 32-bit
platforms, the upper bound is lowered to `SIZE_MAX`.
* Remove backwards compatibility macro's * Remove backwards compatibility macro's
@ -185,4 +578,3 @@ The parser, standalone since v0.12.0, can now be compiled on Windows
### 0.10.0 ### 0.10.0
* See commit log. * See commit log.

View File

@ -1,8 +1,4 @@
CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0) CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0)
INCLUDE(GNUInstallDirs)
PROJECT(hiredis)
OPTION(HIREDIS_SSL "Link against OpenSSL" OFF)
MACRO(getVersionBit name) MACRO(getVersionBit name)
SET(VERSION_REGEX "^#define ${name} (.+)$") SET(VERSION_REGEX "^#define ${name} (.+)$")
@ -18,55 +14,224 @@ getVersionBit(HIREDIS_SONAME)
SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}")
MESSAGE("Detected version: ${VERSION}") MESSAGE("Detected version: ${VERSION}")
PROJECT(hiredis VERSION "${VERSION}") PROJECT(hiredis LANGUAGES "C" VERSION "${VERSION}")
INCLUDE(GNUInstallDirs)
SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") OPTION(BUILD_SHARED_LIBS "Build shared libraries" ON)
OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF)
OPTION(ENABLE_SSL_TESTS "Should we test SSL connections" OFF)
OPTION(ENABLE_EXAMPLES "Enable building hiredis examples" OFF)
OPTION(ENABLE_ASYNC_TESTS "Should we run all asynchronous API tests" OFF)
# Historically, the NuGet file was always install; default
# to ON for those who rely on that historical behaviour.
OPTION(ENABLE_NUGET "Install NuGET packaging details" ON)
ADD_LIBRARY(hiredis SHARED # Hiredis requires C99
SET(CMAKE_C_STANDARD 99)
SET(CMAKE_DEBUG_POSTFIX d)
SET(hiredis_sources
alloc.c
async.c async.c
dict.c
hiredis.c hiredis.c
net.c net.c
read.c read.c
sds.c sds.c
sockcompat.c sockcompat.c)
sslio.c)
SET(hiredis_sources ${hiredis_sources})
IF(WIN32)
ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN)
ENDIF()
ADD_LIBRARY(hiredis ${hiredis_sources})
ADD_LIBRARY(hiredis::hiredis ALIAS hiredis)
set(hiredis_export_name hiredis CACHE STRING "Name of the exported target")
set_target_properties(hiredis PROPERTIES EXPORT_NAME ${hiredis_export_name})
SET_TARGET_PROPERTIES(hiredis SET_TARGET_PROPERTIES(hiredis
PROPERTIES PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE
VERSION "${HIREDIS_SONAME}") VERSION "${HIREDIS_SONAME}")
IF(WIN32 OR MINGW) IF(MSVC)
TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32) SET_TARGET_PROPERTIES(hiredis
PROPERTIES COMPILE_FLAGS /Z7)
ENDIF() ENDIF()
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .) IF(WIN32)
TARGET_LINK_LIBRARIES(hiredis PUBLIC ws2_32 crypt32)
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
TARGET_LINK_LIBRARIES(hiredis PUBLIC m)
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "SunOS")
TARGET_LINK_LIBRARIES(hiredis PUBLIC socket)
ENDIF()
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
INSTALL(TARGETS hiredis set(CPACK_PACKAGE_VENDOR "Redis")
DESTINATION "${CMAKE_INSTALL_LIBDIR}") set(CPACK_PACKAGE_DESCRIPTION "\
Hiredis is a minimalistic C client library for the Redis database.
INSTALL(FILES hiredis.h read.h sds.h async.h 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.")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/redis/hiredis")
set(CPACK_PACKAGE_CONTACT "michael dot grunder at gmail dot com")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_RPM_PACKAGE_AUTOREQPROV ON)
include(CPack)
INSTALL(TARGETS hiredis
EXPORT hiredis-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
if (MSVC AND BUILD_SHARED_LIBS)
INSTALL(FILES $<TARGET_PDB_FILE:hiredis>
DESTINATION ${CMAKE_INSTALL_BINDIR}
CONFIGURATIONS Debug RelWithDebInfo)
endif()
if (ENABLE_NUGET)
# For NuGet packages
INSTALL(FILES hiredis.targets
DESTINATION build/native)
endif()
INSTALL(FILES hiredis.h read.h sds.h async.h alloc.h sockcompat.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
INSTALL(DIRECTORY adapters
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
IF(HIREDIS_SSL) export(EXPORT hiredis-targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis-targets.cmake"
NAMESPACE hiredis::)
if(WIN32)
SET(CMAKE_CONF_INSTALL_DIR share/hiredis)
else()
SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredis)
endif()
SET(INCLUDE_INSTALL_DIR include)
include(CMakePackageConfigHelpers)
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/hiredis-config-version.cmake"
COMPATIBILITY SameMajorVersion)
configure_package_config_file(hiredis-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake
INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}
PATH_VARS INCLUDE_INSTALL_DIR)
INSTALL(EXPORT hiredis-targets
FILE hiredis-targets.cmake
NAMESPACE hiredis::
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake
${CMAKE_CURRENT_BINARY_DIR}/hiredis-config-version.cmake
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
IF(ENABLE_SSL)
IF (NOT OPENSSL_ROOT_DIR) IF (NOT OPENSSL_ROOT_DIR)
IF (APPLE) IF (APPLE)
SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl") SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
ENDIF() ENDIF()
ENDIF() ENDIF()
FIND_PACKAGE(OpenSSL REQUIRED) FIND_PACKAGE(OpenSSL REQUIRED)
TARGET_COMPILE_DEFINITIONS(hiredis PRIVATE HIREDIS_SSL) SET(hiredis_ssl_sources
TARGET_INCLUDE_DIRECTORIES(hiredis PRIVATE "${OPENSSL_INCLUDE_DIR}") ssl.c)
TARGET_LINK_LIBRARIES(hiredis PRIVATE ${OPENSSL_LIBRARIES}) ADD_LIBRARY(hiredis_ssl ${hiredis_ssl_sources})
ADD_LIBRARY(hiredis::hiredis_ssl ALIAS hiredis_ssl)
IF (APPLE AND BUILD_SHARED_LIBS)
SET_PROPERTY(TARGET hiredis_ssl PROPERTY LINK_FLAGS "-Wl,-undefined -Wl,dynamic_lookup")
ENDIF() ENDIF()
IF(NOT (WIN32 OR MINGW)) SET_TARGET_PROPERTIES(hiredis_ssl
PROPERTIES
WINDOWS_EXPORT_ALL_SYMBOLS TRUE
VERSION "${HIREDIS_SONAME}")
IF(MSVC)
SET_TARGET_PROPERTIES(hiredis_ssl
PROPERTIES COMPILE_FLAGS /Z7)
ENDIF()
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE OpenSSL::SSL)
IF(WIN32)
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE hiredis)
ENDIF()
CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
INSTALL(TARGETS hiredis_ssl
EXPORT hiredis_ssl-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
if (MSVC AND BUILD_SHARED_LIBS)
INSTALL(FILES $<TARGET_PDB_FILE:hiredis_ssl>
DESTINATION ${CMAKE_INSTALL_BINDIR}
CONFIGURATIONS Debug RelWithDebInfo)
endif()
INSTALL(FILES hiredis_ssl.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
export(EXPORT hiredis_ssl-targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-targets.cmake"
NAMESPACE hiredis::)
if(WIN32)
SET(CMAKE_CONF_INSTALL_DIR share/hiredis_ssl)
else()
SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredis_ssl)
endif()
configure_package_config_file(hiredis_ssl-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake
INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}
PATH_VARS INCLUDE_INSTALL_DIR)
INSTALL(EXPORT hiredis_ssl-targets
FILE hiredis_ssl-targets.cmake
NAMESPACE hiredis::
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
ENDIF()
IF(NOT DISABLE_TESTS)
ENABLE_TESTING() ENABLE_TESTING()
ADD_EXECUTABLE(hiredis-test test.c) ADD_EXECUTABLE(hiredis-test test.c)
TARGET_LINK_LIBRARIES(hiredis-test hiredis) TARGET_LINK_LIBRARIES(hiredis-test hiredis)
IF(ENABLE_SSL_TESTS)
ADD_DEFINITIONS(-DHIREDIS_TEST_SSL=1)
TARGET_LINK_LIBRARIES(hiredis-test hiredis_ssl)
ENDIF()
IF(ENABLE_ASYNC_TESTS)
ADD_DEFINITIONS(-DHIREDIS_TEST_ASYNC=1)
TARGET_LINK_LIBRARIES(hiredis-test event)
ENDIF()
ADD_TEST(NAME hiredis-test ADD_TEST(NAME hiredis-test
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
ENDIF() ENDIF()

232
Makefile
View File

@ -3,9 +3,8 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com> # Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file # This file is released under the BSD license, see the COPYING file
OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o sslio.o OBJ=alloc.o net.o hiredis.o sds.o async.o read.o sockcompat.o
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib \ EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-push hiredis-example-poll
hiredis-example-ssl hiredis-example-libevent-ssl
TESTS=hiredis-test TESTS=hiredis-test
LIBNAME=libhiredis LIBNAME=libhiredis
PKGCONFNAME=hiredis.pc PKGCONFNAME=hiredis.pc
@ -40,9 +39,13 @@ export REDIS_TEST_CONFIG
CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc') CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++') CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
OPTIMIZATION?=-O3 OPTIMIZATION?=-O3
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers WARNINGS=-Wall -Wextra -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers
USE_WERROR?=1
ifeq ($(USE_WERROR),1)
WARNINGS+=-Werror
endif
DEBUG_FLAGS?= -g -ggdb DEBUG_FLAGS?= -g -ggdb
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(PLATFORM_FLAGS)
REAL_LDFLAGS=$(LDFLAGS) REAL_LDFLAGS=$(LDFLAGS)
DYLIBSUFFIX=so DYLIBSUFFIX=so
@ -50,70 +53,150 @@ STLIBSUFFIX=a
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME)
DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) STLIB_MAKE_CMD=$(AR) rcs
#################### SSL variables start ####################
SSL_OBJ=ssl.o
SSL_LIBNAME=libhiredis_ssl
SSL_PKGCONFNAME=hiredis_ssl.pc
SSL_INSTALLNAME=install-ssl
SSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
SSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
SSL_DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(SSL_DYLIB_MINOR_NAME)
USE_SSL?=0
ifeq ($(USE_SSL),1)
# This is required for test.c only
CFLAGS+=-DHIREDIS_TEST_SSL
EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
SSL_STLIB=$(SSL_STLIBNAME)
SSL_DYLIB=$(SSL_DYLIBNAME)
SSL_PKGCONF=$(SSL_PKGCONFNAME)
SSL_INSTALL=$(SSL_INSTALLNAME)
else
SSL_STLIB=
SSL_DYLIB=
SSL_PKGCONF=
SSL_INSTALL=
endif
##################### SSL variables end #####################
# Platform-specific overrides # Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
USE_SSL?=0 # This is required for test.c only
ifeq ($(TEST_ASYNC),1)
export CFLAGS+=-DHIREDIS_TEST_ASYNC
endif
ifeq ($(USE_SSL),1) ifeq ($(USE_SSL),1)
# This is the prefix of openssl on my system. This should be the sane default ifndef OPENSSL_PREFIX
# based on the platform ifeq ($(uname_S),Darwin)
ifeq ($(uname_S),Linux) SEARCH_PATH1=/opt/homebrew/opt/openssl
CFLAGS+=-DHIREDIS_SSL SEARCH_PATH2=/usr/local/opt/openssl
LDFLAGS+=-lssl -lcrypto
else ifneq ($(wildcard $(SEARCH_PATH1)),)
OPENSSL_PREFIX?=/usr/local/opt/openssl OPENSSL_PREFIX=$(SEARCH_PATH1)
CFLAGS+=-I$(OPENSSL_PREFIX)/include -DHIREDIS_SSL else ifneq ($(wildcard $(SEARCH_PATH2)),)
LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto OPENSSL_PREFIX=$(SEARCH_PATH2)
endif endif
endif endif
endif
ifdef OPENSSL_PREFIX
CFLAGS+=-I$(OPENSSL_PREFIX)/include
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib
endif
SSL_LDFLAGS+=-lssl -lcrypto
endif
ifeq ($(uname_S),FreeBSD)
LDFLAGS+=-lm
IS_GCC=$(shell sh -c '$(CC) --version 2>/dev/null |egrep -i -c "gcc"')
ifeq ($(IS_GCC),1)
REAL_CFLAGS+=-pedantic
endif
else
REAL_CFLAGS+=-pedantic
endif
ifeq ($(uname_S),SunOS) ifeq ($(uname_S),SunOS)
IS_SUN_CC=$(shell sh -c '$(CC) -V 2>&1 |egrep -i -c "sun|studio"')
ifeq ($(IS_SUN_CC),1)
SUN_SHARED_FLAG=-G
else
SUN_SHARED_FLAG=-shared
endif
REAL_LDFLAGS+= -ldl -lnsl -lsocket REAL_LDFLAGS+= -ldl -lnsl -lsocket
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
SSL_DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(SSL_DYLIBNAME) -h $(SSL_DYLIB_MINOR_NAME) $(LDFLAGS) $(SSL_LDFLAGS)
endif endif
ifeq ($(uname_S),Darwin) ifeq ($(uname_S),Darwin)
DYLIBSUFFIX=dylib DYLIBSUFFIX=dylib
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX) DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
SSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
SSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
SSL_DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME) -o $(SSL_DYLIBNAME) $(LDFLAGS) $(SSL_LDFLAGS)
DYLIB_PLUGIN=-Wl,-undefined -Wl,dynamic_lookup
endif endif
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) all: dynamic static hiredis-test pkgconfig
dynamic: $(DYLIBNAME) $(SSL_DYLIB)
static: $(STLIBNAME) $(SSL_STLIB)
pkgconfig: $(PKGCONFNAME) $(SSL_PKGCONF)
# Deps (use make dep to generate this) # Deps (use make dep to generate this)
async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h alloc.o: alloc.c fmacros.h alloc.h
dict.o: dict.c fmacros.h dict.h async.o: async.c fmacros.h alloc.h async.h hiredis.h read.h sds.h net.h dict.c dict.h win32.h async_private.h
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h sslio.h win32.h dict.o: dict.c fmacros.h alloc.h dict.h
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h async.h win32.h
read.o: read.c fmacros.h read.h sds.h net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h sockcompat.h win32.h
sds.o: sds.c sds.h read.o: read.c fmacros.h alloc.h read.h sds.h win32.h
sds.o: sds.c sds.h sdsalloc.h alloc.h
sockcompat.o: sockcompat.c sockcompat.h sockcompat.o: sockcompat.c sockcompat.h
sslio.o: sslio.c sslio.h hiredis.h test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h sockcompat.h win32.h
test.o: test.c fmacros.h hiredis.h read.h sds.h
$(DYLIBNAME): $(OBJ) $(DYLIBNAME): $(OBJ)
$(DYLIB_MAKE_CMD) $(OBJ) $(REAL_LDFLAGS) $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
$(STLIBNAME): $(OBJ) $(STLIBNAME): $(OBJ)
$(STLIB_MAKE_CMD) $(OBJ) $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
dynamic: $(DYLIBNAME) #################### SSL building rules start ####################
static: $(STLIBNAME) $(SSL_DYLIBNAME): $(SSL_OBJ)
$(SSL_DYLIB_MAKE_CMD) $(DYLIB_PLUGIN) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(LDFLAGS) $(SSL_LDFLAGS)
$(SSL_STLIBNAME): $(SSL_OBJ)
$(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
$(SSL_OBJ): ssl.c hiredis.h read.h sds.h alloc.h async.h win32.h async_private.h
#################### SSL building rules end ####################
# Binaries: # Binaries:
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-libhv: examples/example-libhv.c adapters/libhv.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lhv $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS)
@ -123,7 +206,10 @@ hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
hiredis-example-poll: examples/example-poll.c adapters/poll.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
ifndef AE_DIR ifndef AE_DIR
@ -136,10 +222,11 @@ hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME)
endif endif
ifndef LIBUV_DIR ifndef LIBUV_DIR
hiredis-example-libuv: # dynamic link libuv.so
@echo "Please specify LIBUV_DIR (e.g. ../libuv/)" hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
@false $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< -luv -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)
else else
# use user provided static lib
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)
endif endif
@ -160,27 +247,37 @@ endif
hiredis-example: examples/example.c $(STLIBNAME) hiredis-example: examples/example.c $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-push: examples/example-push.c $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
examples: $(EXAMPLES) examples: $(EXAMPLES)
hiredis-test: test.o $(STLIBNAME) TEST_LIBS = $(STLIBNAME) $(SSL_STLIB)
TEST_LDFLAGS = $(SSL_LDFLAGS)
ifeq ($(USE_SSL),1)
TEST_LDFLAGS += -pthread
endif
ifeq ($(TEST_ASYNC),1)
TEST_LDFLAGS += -levent
endif
hiredis-test: test.o $(TEST_LIBS)
$(CC) -o $@ $(REAL_CFLAGS) -I. $^ $(REAL_LDFLAGS) $(TEST_LDFLAGS)
hiredis-%: %.o $(STLIBNAME) hiredis-%: %.o $(STLIBNAME)
$(CC) $(REAL_CFLAGS) -o $@ $< $(STLIBNAME) $(REAL_LDFLAGS) $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
test: hiredis-test test: hiredis-test
./hiredis-test ./hiredis-test
check: hiredis-test check: hiredis-test
@echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) - TEST_SSL=$(USE_SSL) ./test.sh
$(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \
( kill `cat /tmp/hiredis-test-redis.pid` && false )
kill `cat /tmp/hiredis-test-redis.pid`
.c.o: .c.o:
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< $(CC) -std=c99 -c $(REAL_CFLAGS) $<
clean: clean:
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
dep: dep:
$(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
@ -192,27 +289,49 @@ $(PKGCONFNAME): hiredis.h
@echo prefix=$(PREFIX) > $@ @echo prefix=$(PREFIX) > $@
@echo exec_prefix=\$${prefix} >> $@ @echo exec_prefix=\$${prefix} >> $@
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ @echo includedir=$(PREFIX)/include >> $@
@echo pkgincludedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
@echo >> $@ @echo >> $@
@echo Name: hiredis >> $@ @echo Name: hiredis >> $@
@echo Description: Minimalistic C client library for Redis. >> $@ @echo Description: Minimalistic C client library for Redis. >> $@
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
@echo Libs: -L\$${libdir} -lhiredis >> $@ @echo Libs: -L\$${libdir} -lhiredis >> $@
ifdef USE_SSL @echo Cflags: -I\$${pkgincludedir} -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
@echo Libs.private: -lssl -lcrypto >> $@
endif
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) $(SSL_PKGCONFNAME): hiredis_ssl.h
@echo "Generating $@ for pkgconfig..."
@echo prefix=$(PREFIX) > $@
@echo exec_prefix=\$${prefix} >> $@
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
@echo includedir=$(PREFIX)/include >> $@
@echo pkgincludedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
@echo >> $@
@echo Name: hiredis_ssl >> $@
@echo Description: SSL Support for hiredis. >> $@
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
@echo Requires: hiredis >> $@
@echo Libs: -L\$${libdir} -lhiredis_ssl >> $@
@echo Libs.private: -lssl -lcrypto >> $@
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) $(SSL_INSTALL)
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
$(INSTALL) hiredis.h async.h read.h sds.h sslio.h $(INSTALL_INCLUDE_PATH) $(INSTALL) hiredis.h async.h read.h sds.h alloc.h sockcompat.h $(INSTALL_INCLUDE_PATH)
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME)
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
mkdir -p $(INSTALL_PKGCONF_PATH) mkdir -p $(INSTALL_PKGCONF_PATH)
$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
install-ssl: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
$(INSTALL) hiredis_ssl.h $(INSTALL_INCLUDE_PATH)
$(INSTALL) $(SSL_DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME)
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIBNAME) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIB_MAJOR_NAME)
$(INSTALL) $(SSL_STLIBNAME) $(INSTALL_LIBRARY_PATH)
mkdir -p $(INSTALL_PKGCONF_PATH)
$(INSTALL) $(SSL_PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
32bit: 32bit:
@echo "" @echo ""
@echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"
@ -227,13 +346,14 @@ gprof:
$(MAKE) CFLAGS="-pg" LDFLAGS="-pg" $(MAKE) CFLAGS="-pg" LDFLAGS="-pg"
gcov: gcov:
$(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" $(MAKE) CFLAGS+="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs"
coverage: gcov coverage: gcov
make check make check
mkdir -p tmp/lcov mkdir -p tmp/lcov
lcov -d . -c -o tmp/lcov/hiredis.info lcov -d . -c --exclude '/usr*' -o tmp/lcov/hiredis.info
genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info lcov -q -l tmp/lcov/hiredis.info
genhtml --legend -q -o tmp/lcov/report tmp/lcov/hiredis.info
noopt: noopt:
$(MAKE) OPTIMIZATION="" $(MAKE) OPTIMIZATION=""

463
README.md
View File

@ -1,10 +1,11 @@
[![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.** [![Build Status](https://github.com/redis/hiredis/actions/workflows/build.yml/badge.svg)](https://github.com/redis/hiredis/actions/workflows/build.yml)
**This Readme reflects the latest changed in the master branch. See [v1.0.0](https://github.com/redis/hiredis/tree/v1.0.0) for the Readme and documentation for the latest release ([API/ABI history](https://abi-laboratory.pro/?view=timeline&l=hiredis)).**
# HIREDIS # HIREDIS
Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. Hiredis is a minimalistic C client library for the [Redis](https://redis.io/) database.
It is minimalistic because it just adds minimal support for the protocol, but 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 at the same time it uses a high level printf-alike API in order to make it
@ -22,14 +23,57 @@ Redis version >= 1.2.0.
The library comes with multiple APIs. There is the The library comes with multiple APIs. There is the
*synchronous API*, the *asynchronous API* and the *reply parsing API*. *synchronous API*, the *asynchronous API* and the *reply parsing API*.
## Upgrading to > 1.2.0 (**PRERELEASE**)
* After v1.2.0 we modified how we invoke `poll(2)` to wait for connections to complete, such that we will now retry
the call if it is interrupted by a signal until:
a) The connection succeeds or fails.
b) The overall connection timeout is reached.
In previous versions, an interrupted `poll(2)` call would cause the connection to fail
with `c->err` set to `REDIS_ERR_IO` and `c->errstr` set to `poll(2): Interrupted system call`.
## Upgrading to `1.1.0`
Almost all users will simply need to recompile their applications against the newer version of hiredis.
**NOTE**: Hiredis can now return `nan` in addition to `-inf` and `inf` in a `REDIS_REPLY_DOUBLE`.
Applications that deal with `RESP3` doubles should make sure to account for this.
## Upgrading to `1.0.2`
<span style="color:red">NOTE: v1.0.1 erroneously bumped SONAME, which is why it is skipped here.</span>
Version 1.0.2 is simply 1.0.0 with a fix for [CVE-2021-32765](https://github.com/redis/hiredis/security/advisories/GHSA-hfm9-39pp-55p2). They are otherwise identical.
## Upgrading to `1.0.0` ## Upgrading to `1.0.0`
Version 1.0.0 marks a stable release of hiredis. Version 1.0.0 marks the first stable release of Hiredis.
It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory. 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. 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). For code changes see the [Changelog](CHANGELOG.md).
_Note: As described below, a few member names have been changed but most applications should be able to upgrade with minor code changes and recompiling._
## IMPORTANT: Breaking changes from `0.14.1` -> `1.0.0`
* `redisContext` has two additional members (`free_privdata`, and `privctx`).
* `redisOptions.timeout` has been renamed to `redisOptions.connect_timeout`, and we've added `redisOptions.command_timeout`.
* `redisReplyObjectFunctions.createArray` now takes `size_t` instead of `int` for its length parameter.
## IMPORTANT: Breaking changes when upgrading from 0.13.x -> 0.14.x
Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
protocol errors. This is consistent with the RESP specification. On 32-bit
platforms, the upper bound is lowered to `SIZE_MAX`.
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.
## Upgrading from `<0.9.0` ## Upgrading from `<0.9.0`
Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
@ -56,6 +100,7 @@ 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. the error. More information on errors can be found in the **Errors** section.
After trying to connect to Redis using `redisConnect` you should After trying to connect to Redis using `redisConnect` you should
check the `err` field to see if establishing the connection was successful: check the `err` field to see if establishing the connection was successful:
```c ```c
redisContext *c = redisConnect("127.0.0.1", 6379); redisContext *c = redisConnect("127.0.0.1", 6379);
if (c == NULL || c->err) { if (c == NULL || c->err) {
@ -68,8 +113,74 @@ if (c == NULL || c->err) {
} }
``` ```
One can also use `redisConnectWithOptions` which takes a `redisOptions` argument
that can be configured with endpoint information as well as many different flags
to change how the `redisContext` will be configured.
```c
redisOptions opt = {0};
/* One can set the endpoint with one of our helper macros */
if (tcp) {
REDIS_OPTIONS_SET_TCP(&opt, "localhost", 6379);
} else {
REDIS_OPTIONS_SET_UNIX(&opt, "/tmp/redis.sock");
}
/* And privdata can be specified with another helper */
REDIS_OPTIONS_SET_PRIVDATA(&opt, myPrivData, myPrivDataDtor);
/* Finally various options may be set via the `options` member, as described below */
opt->options |= REDIS_OPT_PREFER_IPV4;
```
If a connection is lost, `int redisReconnect(redisContext *c)` can be used to restore the connection using the same endpoint and options as the given context.
### Configurable redisOptions flags
There are several flags you may set in the `redisOptions` struct to change default behavior. You can specify the flags via the `redisOptions->options` member.
| Flag | Description |
| --- | --- |
| REDIS\_OPT\_NONBLOCK | Tells hiredis to make a non-blocking connection. |
| REDIS\_OPT\_REUSEADDR | Tells hiredis to set the [SO_REUSEADDR](https://man7.org/linux/man-pages/man7/socket.7.html) socket option |
| REDIS\_OPT\_PREFER\_IPV4<br>REDIS\_OPT\_PREFER_IPV6<br>REDIS\_OPT\_PREFER\_IP\_UNSPEC | Informs hiredis to either prefer IPv4 or IPv6 when invoking [getaddrinfo](https://man7.org/linux/man-pages/man3/gai_strerror.3.html). `REDIS_OPT_PREFER_IP_UNSPEC` will cause hiredis to specify `AF_UNSPEC` in the getaddrinfo call, which means both IPv4 and IPv6 addresses will be searched simultaneously.<br>Hiredis prefers IPv4 by default. |
| REDIS\_OPT\_NO\_PUSH\_AUTOFREE | Tells hiredis to not install the default RESP3 PUSH handler (which just intercepts and frees the replies). This is useful in situations where you want to process these messages in-band. |
| REDIS\_OPT\_NOAUTOFREEREPLIES | **ASYNC**: tells hiredis not to automatically invoke `freeReplyObject` after executing the reply callback. |
| REDIS\_OPT\_NOAUTOFREE | **ASYNC**: Tells hiredis not to automatically free the `redisAsyncContext` on connection/communication failure, but only if the user makes an explicit call to `redisAsyncDisconnect` or `redisAsyncFree` |
*Note: A `redisContext` is not thread-safe.* *Note: A `redisContext` is not thread-safe.*
### Other configuration using socket options
The following socket options are applied directly to the underlying socket.
The values are not stored in the `redisContext`, so they are not automatically applied when reconnecting using `redisReconnect()`.
These functions return `REDIS_OK` on success.
On failure, `REDIS_ERR` is returned and the underlying connection is closed.
To configure these for an asynchronous context (see *Asynchronous API* below), use `ac->c` to get the redisContext out of an asyncRedisContext.
```C
int redisEnableKeepAlive(redisContext *c);
int redisEnableKeepAliveWithInterval(redisContext *c, int interval);
```
Enables TCP keepalive by setting the following socket options (with some variations depending on OS):
* `SO_KEEPALIVE`;
* `TCP_KEEPALIVE` or `TCP_KEEPIDLE`, value configurable using the `interval` parameter, default 15 seconds;
* `TCP_KEEPINTVL` set to 1/3 of `interval`;
* `TCP_KEEPCNT` set to 3.
```C
int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout);
```
Set the `TCP_USER_TIMEOUT` Linux-specific socket option which is as described in the `tcp` man page:
> When the value is greater than 0, it specifies the maximum amount of time in milliseconds that trans mitted data may remain unacknowledged before TCP will forcibly close the corresponding connection and return ETIMEDOUT to the application.
> If the option value is specified as 0, TCP will use the system default.
### Sending commands ### Sending commands
There are several ways to issue commands to Redis. The first that will be introduced is There are several ways to issue commands to Redis. The first that will be introduced is
@ -110,6 +221,8 @@ 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 `type` field in the `redisReply` should be used to test what kind of reply
was received: was received:
### RESP2
* **`REDIS_REPLY_STATUS`**: * **`REDIS_REPLY_STATUS`**:
* The command replied with a status reply. The status string can be accessed using `reply->str`. * 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`. The length of this string can be accessed using `reply->len`.
@ -134,16 +247,51 @@ was received:
and can be accessed via `reply->element[..index..]`. and can be accessed via `reply->element[..index..]`.
Redis may reply with nested arrays but this is fully supported. Redis may reply with nested arrays but this is fully supported.
### RESP3
Hiredis also supports every new `RESP3` data type which are as follows. For more information about the protocol see the `RESP3` [specification.](https://github.com/antirez/RESP3/blob/master/spec.md)
* **`REDIS_REPLY_DOUBLE`**:
* The command replied with a double-precision floating point number.
The value is stored as a string in the `str` member, and can be converted with `strtod` or similar.
* **`REDIS_REPLY_BOOL`**:
* A boolean true/false reply.
The value is stored in the `integer` member and will be either `0` or `1`.
* **`REDIS_REPLY_MAP`**:
* An array with the added invariant that there will always be an even number of elements.
The MAP is functionally equivalent to `REDIS_REPLY_ARRAY` except for the previously mentioned invariant.
* **`REDIS_REPLY_SET`**:
* An array response where each entry is unique.
Like the MAP type, the data is identical to an array response except there are no duplicate values.
* **`REDIS_REPLY_PUSH`**:
* An array that can be generated spontaneously by Redis.
This array response will always contain at least two subelements. The first contains the type of `PUSH` message (e.g. `message`, or `invalidate`), and the second being a sub-array with the `PUSH` payload itself.
* **`REDIS_REPLY_ATTR`**:
* An array structurally identical to a `MAP` but intended as meta-data about a reply.
_As of Redis 6.0.6 this reply type is not used in Redis_
* **`REDIS_REPLY_BIGNUM`**:
* A string representing an arbitrarily large signed or unsigned integer value.
The number will be encoded as a string in the `str` member of `redisReply`.
* **`REDIS_REPLY_VERB`**:
* A verbatim string, intended to be presented to the user without modification.
The string payload is stored in the `str` member, and type data is stored in the `vtype` member (e.g. `txt` for raw text or `md` for markdown).
Replies should be freed using the `freeReplyObject()` function. Replies should be freed using the `freeReplyObject()` function.
Note that this function will take care of freeing sub-reply objects 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 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). 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 **Important:** the current version of hiredis (1.0.0) frees replies when the
asynchronous API is used. This means you should not call `freeReplyObject` when 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 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 returns. We may introduce a flag to make this configurable in future versions of the library.
keep an eye on the changelog when upgrading (see issue #39).
### Cleaning up ### Cleaning up
@ -154,7 +302,7 @@ void redisFree(redisContext *c);
This function immediately closes the socket and then frees the allocations done in This function immediately closes the socket and then frees the allocations done in
creating the context. creating the context.
### Sending commands (cont'd) ### Sending commands (continued)
Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands.
It has the following prototype: It has the following prototype:
@ -187,7 +335,7 @@ following two execution paths:
* Read from the socket until a single reply could be parsed * 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 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 is expected on the socket. To pipeline commands, the only thing that needs to be done is
filling up the output buffer. For this cause, two commands can be used that are identical 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: to the `redisCommand` family, apart from not returning a reply:
```c ```c
@ -205,16 +353,16 @@ a single call to `read(2)`):
redisReply *reply; redisReply *reply;
redisAppendCommand(context,"SET foo bar"); redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo"); redisAppendCommand(context,"GET foo");
redisGetReply(context,&reply); // reply for SET redisGetReply(context,(void**)&reply); // reply for SET
freeReplyObject(reply); freeReplyObject(reply);
redisGetReply(context,&reply); // reply for GET redisGetReply(context,(void**)&reply); // reply for GET
freeReplyObject(reply); freeReplyObject(reply);
``` ```
This API can also be used to implement a blocking subscriber: This API can also be used to implement a blocking subscriber:
```c ```c
reply = redisCommand(context,"SUBSCRIBE foo"); reply = redisCommand(context,"SUBSCRIBE foo");
freeReplyObject(reply); freeReplyObject(reply);
while(redisGetReply(context,&reply) == REDIS_OK) { while(redisGetReply(context,(void *)&reply) == REDIS_OK) {
// consume message // consume message
freeReplyObject(reply); freeReplyObject(reply);
} }
@ -257,23 +405,48 @@ Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The
should be checked after creation to see if there were errors creating the connection. 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 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. instantly return if the specified host and port is able to accept a connection.
In case of error, it is the caller's responsibility to free the context using `redisAsyncFree()`
*Note: A `redisAsyncContext` is not thread-safe.* *Note: A `redisAsyncContext` is not thread-safe.*
An application function creating a connection might look like this:
```c ```c
void appConnect(myAppData *appData)
{
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) { if (c->err) {
printf("Error: %s\n", c->errstr); printf("Error: %s\n", c->errstr);
// handle error // handle error
redisAsyncFree(c);
c = NULL;
} else {
appData->context = c;
appData->connecting = 1;
c->data = appData; /* store application pointer for the callbacks */
redisAsyncSetConnectCallback(c, appOnConnect);
redisAsyncSetDisconnectCallback(c, appOnDisconnect);
} }
}
``` ```
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 The asynchronous context _should_ hold a *connect* callback function that is called when the connection
attempt completes, either successfully or with an error.
It _can_ also hold a *disconnect* callback function that is called when the
connection is disconnected (either because of an error or per user request). Both callbacks should
have the following prototype: have the following prototype:
```c ```c
void(const redisAsyncContext *c, int status); void(const redisAsyncContext *c, int status);
``` ```
On a *connect*, the `status` argument is set to `REDIS_OK` if the connection attempt succeeded. In this
case, the context is ready to accept commands. If it is called with `REDIS_ERR` then the
connection attempt failed. The `err` field in the context can be accessed to find out the cause of the error.
After a failed connection attempt, the context object is automatically freed by the library after calling
the connect callback. This may be a good point to create a new context and retry the connection.
On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the 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` 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. field in the context can be accessed to find out the cause of the error.
@ -281,11 +454,46 @@ 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 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. 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 Setting the connect or disconnect callbacks can only be done once per context. For subsequent calls the
return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: api will return `REDIS_ERR`. The function to set the callbacks have the following prototype:
```c ```c
/* Alternatively you can use redisAsyncSetConnectCallbackNC which will be passed a non-const
redisAsyncContext* on invocation (e.g. allowing writes to the privdata member). */
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
``` ```
`ac->data` may be used to pass user data to both callbacks. A typical implementation
might look something like this:
```c
void appOnConnect(redisAsyncContext *c, int status)
{
myAppData *appData = (myAppData*)c->data; /* get my application specific context*/
appData->connecting = 0;
if (status == REDIS_OK) {
appData->connected = 1;
} else {
appData->connected = 0;
appData->err = c->err;
appData->context = NULL; /* avoid stale pointer when callback returns */
}
appAttemptReconnect();
}
void appOnDisconnect(redisAsyncContext *c, int status)
{
myAppData *appData = (myAppData*)c->data; /* get my application specific context*/
appData->connected = 0;
appData->err = c->err;
appData->context = NULL; /* avoid stale pointer when callback returns */
if (status == REDIS_OK) {
appNotifyDisconnectCompleted(mydata);
} else {
appNotifyUnexpectedDisconnect(mydata);
appAttemptReconnect();
}
}
```
### Sending commands and their callbacks ### Sending commands and their callbacks
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
@ -318,6 +526,14 @@ valid for the duration of the callback.
All pending callbacks are called with a `NULL` reply when the context encountered an error. All pending callbacks are called with a `NULL` reply when the context encountered an error.
For every command issued, with the exception of **SUBSCRIBE** and **PSUBSCRIBE**, the callback is
called exactly once. Even if the context object id disconnected or deleted, every pending callback
will be called with a `NULL` reply.
For **SUBSCRIBE** and **PSUBSCRIBE**, the callbacks may be called repeatedly until an `unsubscribe`
message arrives. This will be the last invocation of the callback. In case of error, the callbacks
may receive a final `NULL` reply instead.
### Disconnecting ### Disconnecting
An asynchronous connection can be terminated using: An asynchronous connection can be terminated using:
@ -330,6 +546,15 @@ have been written to the socket, their respective replies have been read and the
callbacks have been executed. After this, the disconnection callback is executed with the callbacks have been executed. After this, the disconnection callback is executed with the
`REDIS_OK` status and the context object is freed. `REDIS_OK` status and the context object is freed.
The connection can be forcefully disconnected using
```c
void redisAsyncFree(redisAsyncContext *ac);
```
In this case, nothing more is written to the socket, all pending callbacks are called with a `NULL`
reply and the disconnection callback is called with `REDIS_OK`, after which the context object
is freed.
### Hooking it up to event library *X* ### 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. There are a few hooks that need to be set on the context object after it is created.
@ -403,9 +628,205 @@ 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 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. as soon as possible in order to prevent allocation of useless memory.
### Reader max array elements
By default the hiredis reply parser sets the maximum number of multi-bulk elements
to 2^32 - 1 or 4,294,967,295 entries. If you need to process multi-bulk replies
with more than this many elements you can set the value higher or to zero, meaning
unlimited with:
```c
context->reader->maxelements = 0;
```
## SSL/TLS Support
### Building
SSL/TLS support is not built by default and requires an explicit flag:
make USE_SSL=1
This requires OpenSSL development package (e.g. including header files to be
available.
When enabled, SSL/TLS support is built into extra `libhiredis_ssl.a` and
`libhiredis_ssl.so` static/dynamic libraries. This leaves the original libraries
unaffected so no additional dependencies are introduced.
### Using it
First, you'll need to make sure you include the SSL header file:
```c
#include <hiredis/hiredis.h>
#include <hiredis/hiredis_ssl.h>
```
You will also need to link against `libhiredis_ssl`, **in addition** to
`libhiredis` and add `-lssl -lcrypto` to satisfy its dependencies.
Hiredis implements SSL/TLS on top of its normal `redisContext` or
`redisAsyncContext`, so you will need to establish a connection first and then
initiate an SSL/TLS handshake.
#### Hiredis OpenSSL Wrappers
Before Hiredis can negotiate an SSL/TLS connection, it is necessary to
initialize OpenSSL and create a context. You can do that in two ways:
1. Work directly with the OpenSSL API to initialize the library's global context
and create `SSL_CTX *` and `SSL *` contexts. With an `SSL *` object you can
call `redisInitiateSSL()`.
2. Work with a set of Hiredis-provided wrappers around OpenSSL, create a
`redisSSLContext` object to hold configuration and use
`redisInitiateSSLWithContext()` to initiate the SSL/TLS handshake.
```c
/* An Hiredis SSL context. It holds SSL configuration and can be reused across
* many contexts.
*/
redisSSLContext *ssl_context;
/* An error variable to indicate what went wrong, if the context fails to
* initialize.
*/
redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;
/* Initialize global OpenSSL state.
*
* You should call this only once when your app initializes, and only if
* you don't explicitly or implicitly initialize OpenSSL it elsewhere.
*/
redisInitOpenSSL();
/* Create SSL context */
ssl_context = redisCreateSSLContext(
"cacertbundle.crt", /* File name of trusted CA/ca bundle file, optional */
"/path/to/certs", /* Path of trusted certificates, optional */
"client_cert.pem", /* File name of client certificate file, optional */
"client_key.pem", /* File name of client private key, optional */
"redis.mydomain.com", /* Server name to request (SNI), optional */
&ssl_error);
if(ssl_context == NULL || ssl_error != REDIS_SSL_CTX_NONE) {
/* Handle error and abort... */
/* e.g.
printf("SSL error: %s\n",
(ssl_error != REDIS_SSL_CTX_NONE) ?
redisSSLContextGetError(ssl_error) : "Unknown error");
// Abort
*/
}
/* Create Redis context and establish connection */
c = redisConnect("localhost", 6443);
if (c == NULL || c->err) {
/* Handle error and abort... */
}
/* Negotiate SSL/TLS */
if (redisInitiateSSLWithContext(c, ssl_context) != REDIS_OK) {
/* Handle error, in c->err / c->errstr */
}
```
## RESP3 PUSH replies
Redis 6.0 introduced PUSH replies with the reply-type `>`. These messages are generated spontaneously and can arrive at any time, so must be handled using callbacks.
### Default behavior
Hiredis installs handlers on `redisContext` and `redisAsyncContext` by default, which will intercept and free any PUSH replies detected. This means existing code will work as-is after upgrading to Redis 6 and switching to `RESP3`.
### Custom PUSH handler prototypes
The callback prototypes differ between `redisContext` and `redisAsyncContext`.
#### redisContext
```c
void my_push_handler(void *privdata, void *reply) {
/* Handle the reply */
/* Note: We need to free the reply in our custom handler for
blocking contexts. This lets us keep the reply if
we want. */
freeReplyObject(reply);
}
```
#### redisAsyncContext
```c
void my_async_push_handler(redisAsyncContext *ac, void *reply) {
/* Handle the reply */
/* Note: Because async hiredis always frees replies, you should
not call freeReplyObject in an async push callback. */
}
```
### Installing a custom handler
There are two ways to set your own PUSH handlers.
1. Set `push_cb` or `async_push_cb` in the `redisOptions` struct and connect with `redisConnectWithOptions` or `redisAsyncConnectWithOptions`.
```c
redisOptions = {0};
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
options->push_cb = my_push_handler;
redisContext *context = redisConnectWithOptions(&options);
```
2. Call `redisSetPushCallback` or `redisAsyncSetPushCallback` on a connected context.
```c
redisContext *context = redisConnect("127.0.0.1", 6379);
redisSetPushCallback(context, my_push_handler);
```
_Note `redisSetPushCallback` and `redisAsyncSetPushCallback` both return any currently configured handler, making it easy to override and then return to the old value._
### Specifying no handler
If you have a unique use-case where you don't want hiredis to automatically intercept and free PUSH replies, you will want to configure no handler at all. This can be done in two ways.
1. Set the `REDIS_OPT_NO_PUSH_AUTOFREE` flag in `redisOptions` and leave the callback function pointer `NULL`.
```c
redisOptions = {0};
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
options->options |= REDIS_OPT_NO_PUSH_AUTOFREE;
redisContext *context = redisConnectWithOptions(&options);
```
3. Call `redisSetPushCallback` with `NULL` once connected.
```c
redisContext *context = redisConnect("127.0.0.1", 6379);
redisSetPushCallback(context, NULL);
```
_Note: With no handler configured, calls to `redisCommand` may generate more than one reply, so this strategy is only applicable when there's some kind of blocking `redisGetReply()` loop (e.g. `MONITOR` or `SUBSCRIBE` workloads)._
## Allocator injection
Hiredis uses a pass-thru structure of function pointers defined in [alloc.h](https://github.com/redis/hiredis/blob/f5d25850/alloc.h#L41) that contain the currently configured allocation and deallocation functions. By default they just point to libc (`malloc`, `calloc`, `realloc`, etc).
### Overriding
One can override the allocators like so:
```c
hiredisAllocFuncs myfuncs = {
.mallocFn = my_malloc,
.callocFn = my_calloc,
.reallocFn = my_realloc,
.strdupFn = my_strdup,
.freeFn = my_free,
};
// Override allocators (function returns current allocators if needed)
hiredisAllocFuncs orig = hiredisSetAllocators(&myfuncs);
```
To reset the allocators to their default libc function simply call:
```c
hiredisResetAllocators();
```
## AUTHORS ## AUTHORS
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and Salvatore Sanfilippo (antirez at gmail),\
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. Pieter Noordhuis (pcnoordhuis at gmail)\
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and Michael Grunder (michael dot grunder at gmail)
Jan-Erik Rediger (janerik at fnordig dot com)
_Hiredis is released under the BSD license._

View File

@ -96,7 +96,7 @@ static void redisAeCleanup(void *privdata) {
redisAeEvents *e = (redisAeEvents*)privdata; redisAeEvents *e = (redisAeEvents*)privdata;
redisAeDelRead(privdata); redisAeDelRead(privdata);
redisAeDelWrite(privdata); redisAeDelWrite(privdata);
free(e); hi_free(e);
} }
static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
@ -108,7 +108,10 @@ static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
return REDIS_ERR; return REDIS_ERR;
/* Create container for context and r/w events */ /* Create container for context and r/w events */
e = (redisAeEvents*)malloc(sizeof(*e)); e = (redisAeEvents*)hi_malloc(sizeof(*e));
if (e == NULL)
return REDIS_ERR;
e->context = ac; e->context = ac;
e->loop = loop; e->loop = loop;
e->fd = c->fd; e->fd = c->fd;

View File

@ -134,6 +134,9 @@ redis_source_new (redisAsyncContext *ac)
g_return_val_if_fail(ac != NULL, NULL); g_return_val_if_fail(ac != NULL, NULL);
source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); source = (RedisSource *)g_source_new(&source_funcs, sizeof *source);
if (source == NULL)
return NULL;
source->ac = ac; source->ac = ac;
source->poll_fd.fd = c->fd; source->poll_fd.fd = c->fd;
source->poll_fd.events = 0; source->poll_fd.events = 0;

View File

@ -43,7 +43,7 @@ static void redisIvykisCleanup(void *privdata) {
redisIvykisEvents *e = (redisIvykisEvents*)privdata; redisIvykisEvents *e = (redisIvykisEvents*)privdata;
iv_fd_unregister(&e->fd); iv_fd_unregister(&e->fd);
free(e); hi_free(e);
} }
static int redisIvykisAttach(redisAsyncContext *ac) { static int redisIvykisAttach(redisAsyncContext *ac) {
@ -55,7 +55,10 @@ static int redisIvykisAttach(redisAsyncContext *ac) {
return REDIS_ERR; return REDIS_ERR;
/* Create container for context and r/w events */ /* Create container for context and r/w events */
e = (redisIvykisEvents*)malloc(sizeof(*e)); e = (redisIvykisEvents*)hi_malloc(sizeof(*e));
if (e == NULL)
return REDIS_ERR;
e->context = ac; e->context = ac;
/* Register functions to start/stop listening for events */ /* Register functions to start/stop listening for events */

View File

@ -41,11 +41,12 @@ typedef struct redisLibevEvents {
struct ev_loop *loop; struct ev_loop *loop;
int reading, writing; int reading, writing;
ev_io rev, wev; ev_io rev, wev;
ev_timer timer;
} redisLibevEvents; } redisLibevEvents;
static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
#if EV_MULTIPLICITY #if EV_MULTIPLICITY
((void)loop); ((void)EV_A);
#endif #endif
((void)revents); ((void)revents);
@ -55,7 +56,7 @@ static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
#if EV_MULTIPLICITY #if EV_MULTIPLICITY
((void)loop); ((void)EV_A);
#endif #endif
((void)revents); ((void)revents);
@ -65,8 +66,9 @@ static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
static void redisLibevAddRead(void *privdata) { static void redisLibevAddRead(void *privdata) {
redisLibevEvents *e = (redisLibevEvents*)privdata; redisLibevEvents *e = (redisLibevEvents*)privdata;
#if EV_MULTIPLICITY
struct ev_loop *loop = e->loop; struct ev_loop *loop = e->loop;
((void)loop); #endif
if (!e->reading) { if (!e->reading) {
e->reading = 1; e->reading = 1;
ev_io_start(EV_A_ &e->rev); ev_io_start(EV_A_ &e->rev);
@ -75,8 +77,9 @@ static void redisLibevAddRead(void *privdata) {
static void redisLibevDelRead(void *privdata) { static void redisLibevDelRead(void *privdata) {
redisLibevEvents *e = (redisLibevEvents*)privdata; redisLibevEvents *e = (redisLibevEvents*)privdata;
#if EV_MULTIPLICITY
struct ev_loop *loop = e->loop; struct ev_loop *loop = e->loop;
((void)loop); #endif
if (e->reading) { if (e->reading) {
e->reading = 0; e->reading = 0;
ev_io_stop(EV_A_ &e->rev); ev_io_stop(EV_A_ &e->rev);
@ -85,8 +88,9 @@ static void redisLibevDelRead(void *privdata) {
static void redisLibevAddWrite(void *privdata) { static void redisLibevAddWrite(void *privdata) {
redisLibevEvents *e = (redisLibevEvents*)privdata; redisLibevEvents *e = (redisLibevEvents*)privdata;
#if EV_MULTIPLICITY
struct ev_loop *loop = e->loop; struct ev_loop *loop = e->loop;
((void)loop); #endif
if (!e->writing) { if (!e->writing) {
e->writing = 1; e->writing = 1;
ev_io_start(EV_A_ &e->wev); ev_io_start(EV_A_ &e->wev);
@ -95,19 +99,53 @@ static void redisLibevAddWrite(void *privdata) {
static void redisLibevDelWrite(void *privdata) { static void redisLibevDelWrite(void *privdata) {
redisLibevEvents *e = (redisLibevEvents*)privdata; redisLibevEvents *e = (redisLibevEvents*)privdata;
#if EV_MULTIPLICITY
struct ev_loop *loop = e->loop; struct ev_loop *loop = e->loop;
((void)loop); #endif
if (e->writing) { if (e->writing) {
e->writing = 0; e->writing = 0;
ev_io_stop(EV_A_ &e->wev); ev_io_stop(EV_A_ &e->wev);
} }
} }
static void redisLibevStopTimer(void *privdata) {
redisLibevEvents *e = (redisLibevEvents*)privdata;
#if EV_MULTIPLICITY
struct ev_loop *loop = e->loop;
#endif
ev_timer_stop(EV_A_ &e->timer);
}
static void redisLibevCleanup(void *privdata) { static void redisLibevCleanup(void *privdata) {
redisLibevEvents *e = (redisLibevEvents*)privdata; redisLibevEvents *e = (redisLibevEvents*)privdata;
redisLibevDelRead(privdata); redisLibevDelRead(privdata);
redisLibevDelWrite(privdata); redisLibevDelWrite(privdata);
free(e); redisLibevStopTimer(privdata);
hi_free(e);
}
static void redisLibevTimeout(EV_P_ ev_timer *timer, int revents) {
#if EV_MULTIPLICITY
((void)EV_A);
#endif
((void)revents);
redisLibevEvents *e = (redisLibevEvents*)timer->data;
redisAsyncHandleTimeout(e->context);
}
static void redisLibevSetTimeout(void *privdata, struct timeval tv) {
redisLibevEvents *e = (redisLibevEvents*)privdata;
#if EV_MULTIPLICITY
struct ev_loop *loop = e->loop;
#endif
if (!ev_is_active(&e->timer)) {
ev_init(&e->timer, redisLibevTimeout);
e->timer.data = e;
}
e->timer.repeat = tv.tv_sec + tv.tv_usec / 1000000.00;
ev_timer_again(EV_A_ &e->timer);
} }
static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
@ -119,14 +157,16 @@ static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
return REDIS_ERR; return REDIS_ERR;
/* Create container for context and r/w events */ /* Create container for context and r/w events */
e = (redisLibevEvents*)malloc(sizeof(*e)); e = (redisLibevEvents*)hi_calloc(1, sizeof(*e));
if (e == NULL)
return REDIS_ERR;
e->context = ac; e->context = ac;
#if EV_MULTIPLICITY #if EV_MULTIPLICITY
e->loop = loop; e->loop = EV_A;
#else #else
e->loop = NULL; e->loop = NULL;
#endif #endif
e->reading = e->writing = 0;
e->rev.data = e; e->rev.data = e;
e->wev.data = e; e->wev.data = e;
@ -136,6 +176,7 @@ static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
ac->ev.addWrite = redisLibevAddWrite; ac->ev.addWrite = redisLibevAddWrite;
ac->ev.delWrite = redisLibevDelWrite; ac->ev.delWrite = redisLibevDelWrite;
ac->ev.cleanup = redisLibevCleanup; ac->ev.cleanup = redisLibevCleanup;
ac->ev.scheduleTimer = redisLibevSetTimeout;
ac->ev.data = e; ac->ev.data = e;
/* Initialize read/write events */ /* Initialize read/write events */

View File

@ -47,10 +47,10 @@ typedef struct redisLibeventEvents {
} redisLibeventEvents; } redisLibeventEvents;
static void redisLibeventDestroy(redisLibeventEvents *e) { static void redisLibeventDestroy(redisLibeventEvents *e) {
free(e); hi_free(e);
} }
static void redisLibeventHandler(int fd, short event, void *arg) { static void redisLibeventHandler(evutil_socket_t fd, short event, void *arg) {
((void)fd); ((void)fd);
redisLibeventEvents *e = (redisLibeventEvents*)arg; redisLibeventEvents *e = (redisLibeventEvents*)arg;
e->state |= REDIS_LIBEVENT_ENTERED; e->state |= REDIS_LIBEVENT_ENTERED;
@ -152,7 +152,10 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
return REDIS_ERR; return REDIS_ERR;
/* Create container for context and r/w events */ /* Create container for context and r/w events */
e = (redisLibeventEvents*)calloc(1, sizeof(*e)); e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e));
if (e == NULL)
return REDIS_ERR;
e->context = ac; e->context = ac;
/* Register functions to start/stop listening for events */ /* Register functions to start/stop listening for events */

123
adapters/libhv.h Normal file
View File

@ -0,0 +1,123 @@
#ifndef __HIREDIS_LIBHV_H__
#define __HIREDIS_LIBHV_H__
#include <hv/hloop.h>
#include "../hiredis.h"
#include "../async.h"
typedef struct redisLibhvEvents {
hio_t *io;
htimer_t *timer;
} redisLibhvEvents;
static void redisLibhvHandleEvents(hio_t* io) {
redisAsyncContext* context = (redisAsyncContext*)hevent_userdata(io);
int events = hio_events(io);
int revents = hio_revents(io);
if (context && (events & HV_READ) && (revents & HV_READ)) {
redisAsyncHandleRead(context);
}
if (context && (events & HV_WRITE) && (revents & HV_WRITE)) {
redisAsyncHandleWrite(context);
}
}
static void redisLibhvAddRead(void *privdata) {
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
hio_add(events->io, redisLibhvHandleEvents, HV_READ);
}
static void redisLibhvDelRead(void *privdata) {
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
hio_del(events->io, HV_READ);
}
static void redisLibhvAddWrite(void *privdata) {
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
hio_add(events->io, redisLibhvHandleEvents, HV_WRITE);
}
static void redisLibhvDelWrite(void *privdata) {
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
hio_del(events->io, HV_WRITE);
}
static void redisLibhvCleanup(void *privdata) {
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
if (events->timer)
htimer_del(events->timer);
hio_close(events->io);
hevent_set_userdata(events->io, NULL);
hi_free(events);
}
static void redisLibhvTimeout(htimer_t* timer) {
hio_t* io = (hio_t*)hevent_userdata(timer);
redisAsyncHandleTimeout((redisAsyncContext*)hevent_userdata(io));
}
static void redisLibhvSetTimeout(void *privdata, struct timeval tv) {
redisLibhvEvents* events;
uint32_t millis;
hloop_t* loop;
events = (redisLibhvEvents*)privdata;
millis = tv.tv_sec * 1000 + tv.tv_usec / 1000;
if (millis == 0) {
/* Libhv disallows zero'd timers so treat this as a delete or NO OP */
if (events->timer) {
htimer_del(events->timer);
events->timer = NULL;
}
} else if (events->timer == NULL) {
/* Add new timer */
loop = hevent_loop(events->io);
events->timer = htimer_add(loop, redisLibhvTimeout, millis, 1);
hevent_set_userdata(events->timer, events->io);
} else {
/* Update existing timer */
htimer_reset(events->timer, millis);
}
}
static int redisLibhvAttach(redisAsyncContext* ac, hloop_t* loop) {
redisContext *c = &(ac->c);
redisLibhvEvents *events;
hio_t* io = NULL;
if (ac->ev.data != NULL) {
return REDIS_ERR;
}
/* Create container struct to keep track of our io and any timer */
events = (redisLibhvEvents*)hi_malloc(sizeof(*events));
if (events == NULL) {
return REDIS_ERR;
}
io = hio_get(loop, c->fd);
if (io == NULL) {
hi_free(events);
return REDIS_ERR;
}
hevent_set_userdata(io, ac);
events->io = io;
events->timer = NULL;
ac->ev.addRead = redisLibhvAddRead;
ac->ev.delRead = redisLibhvDelRead;
ac->ev.addWrite = redisLibhvAddWrite;
ac->ev.delWrite = redisLibhvDelWrite;
ac->ev.cleanup = redisLibhvCleanup;
ac->ev.scheduleTimer = redisLibhvSetTimeout;
ac->ev.data = events;
return REDIS_OK;
}
#endif

177
adapters/libsdevent.h Normal file
View File

@ -0,0 +1,177 @@
#ifndef HIREDIS_LIBSDEVENT_H
#define HIREDIS_LIBSDEVENT_H
#include <systemd/sd-event.h>
#include "../hiredis.h"
#include "../async.h"
#define REDIS_LIBSDEVENT_DELETED 0x01
#define REDIS_LIBSDEVENT_ENTERED 0x02
typedef struct redisLibsdeventEvents {
redisAsyncContext *context;
struct sd_event *event;
struct sd_event_source *fdSource;
struct sd_event_source *timerSource;
int fd;
short flags;
short state;
} redisLibsdeventEvents;
static void redisLibsdeventDestroy(redisLibsdeventEvents *e) {
if (e->fdSource) {
e->fdSource = sd_event_source_disable_unref(e->fdSource);
}
if (e->timerSource) {
e->timerSource = sd_event_source_disable_unref(e->timerSource);
}
sd_event_unref(e->event);
hi_free(e);
}
static int redisLibsdeventTimeoutHandler(sd_event_source *s, uint64_t usec, void *userdata) {
((void)s);
((void)usec);
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
redisAsyncHandleTimeout(e->context);
return 0;
}
static int redisLibsdeventHandler(sd_event_source *s, int fd, uint32_t event, void *userdata) {
((void)s);
((void)fd);
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
e->state |= REDIS_LIBSDEVENT_ENTERED;
#define CHECK_DELETED() if (e->state & REDIS_LIBSDEVENT_DELETED) {\
redisLibsdeventDestroy(e);\
return 0; \
}
if ((event & EPOLLIN) && e->context && (e->state & REDIS_LIBSDEVENT_DELETED) == 0) {
redisAsyncHandleRead(e->context);
CHECK_DELETED();
}
if ((event & EPOLLOUT) && e->context && (e->state & REDIS_LIBSDEVENT_DELETED) == 0) {
redisAsyncHandleWrite(e->context);
CHECK_DELETED();
}
e->state &= ~REDIS_LIBSDEVENT_ENTERED;
#undef CHECK_DELETED
return 0;
}
static void redisLibsdeventAddRead(void *userdata) {
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
if (e->flags & EPOLLIN) {
return;
}
e->flags |= EPOLLIN;
if (e->flags & EPOLLOUT) {
sd_event_source_set_io_events(e->fdSource, e->flags);
} else {
sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redisLibsdeventHandler, e);
}
}
static void redisLibsdeventDelRead(void *userdata) {
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
e->flags &= ~EPOLLIN;
if (e->flags) {
sd_event_source_set_io_events(e->fdSource, e->flags);
} else {
e->fdSource = sd_event_source_disable_unref(e->fdSource);
}
}
static void redisLibsdeventAddWrite(void *userdata) {
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
if (e->flags & EPOLLOUT) {
return;
}
e->flags |= EPOLLOUT;
if (e->flags & EPOLLIN) {
sd_event_source_set_io_events(e->fdSource, e->flags);
} else {
sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redisLibsdeventHandler, e);
}
}
static void redisLibsdeventDelWrite(void *userdata) {
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
e->flags &= ~EPOLLOUT;
if (e->flags) {
sd_event_source_set_io_events(e->fdSource, e->flags);
} else {
e->fdSource = sd_event_source_disable_unref(e->fdSource);
}
}
static void redisLibsdeventCleanup(void *userdata) {
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
if (!e) {
return;
}
if (e->state & REDIS_LIBSDEVENT_ENTERED) {
e->state |= REDIS_LIBSDEVENT_DELETED;
} else {
redisLibsdeventDestroy(e);
}
}
static void redisLibsdeventSetTimeout(void *userdata, struct timeval tv) {
redisLibsdeventEvents *e = (redisLibsdeventEvents *)userdata;
uint64_t usec = tv.tv_sec * 1000000 + tv.tv_usec;
if (!e->timerSource) {
sd_event_add_time_relative(e->event, &e->timerSource, CLOCK_MONOTONIC, usec, 1, redisLibsdeventTimeoutHandler, e);
} else {
sd_event_source_set_time_relative(e->timerSource, usec);
}
}
static int redisLibsdeventAttach(redisAsyncContext *ac, struct sd_event *event) {
redisContext *c = &(ac->c);
redisLibsdeventEvents *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 = (redisLibsdeventEvents*)hi_calloc(1, sizeof(*e));
if (e == NULL)
return REDIS_ERR;
/* Initialize and increase event refcount */
e->context = ac;
e->event = event;
e->fd = c->fd;
sd_event_ref(event);
/* Register functions to start/stop listening for events */
ac->ev.addRead = redisLibsdeventAddRead;
ac->ev.delRead = redisLibsdeventDelRead;
ac->ev.addWrite = redisLibsdeventAddWrite;
ac->ev.delWrite = redisLibsdeventDelWrite;
ac->ev.cleanup = redisLibsdeventCleanup;
ac->ev.scheduleTimer = redisLibsdeventSetTimeout;
ac->ev.data = e;
return REDIS_OK;
}
#endif

View File

@ -9,6 +9,7 @@
typedef struct redisLibuvEvents { typedef struct redisLibuvEvents {
redisAsyncContext* context; redisAsyncContext* context;
uv_poll_t handle; uv_poll_t handle;
uv_timer_t timer;
int events; int events;
} redisLibuvEvents; } redisLibuvEvents;
@ -29,6 +30,10 @@ static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
static void redisLibuvAddRead(void *privdata) { static void redisLibuvAddRead(void *privdata) {
redisLibuvEvents* p = (redisLibuvEvents*)privdata; redisLibuvEvents* p = (redisLibuvEvents*)privdata;
if (p->events & UV_READABLE) {
return;
}
p->events |= UV_READABLE; p->events |= UV_READABLE;
uv_poll_start(&p->handle, p->events, redisLibuvPoll); uv_poll_start(&p->handle, p->events, redisLibuvPoll);
@ -51,6 +56,10 @@ static void redisLibuvDelRead(void *privdata) {
static void redisLibuvAddWrite(void *privdata) { static void redisLibuvAddWrite(void *privdata) {
redisLibuvEvents* p = (redisLibuvEvents*)privdata; redisLibuvEvents* p = (redisLibuvEvents*)privdata;
if (p->events & UV_WRITABLE) {
return;
}
p->events |= UV_WRITABLE; p->events |= UV_WRITABLE;
uv_poll_start(&p->handle, p->events, redisLibuvPoll); uv_poll_start(&p->handle, p->events, redisLibuvPoll);
@ -69,19 +78,63 @@ static void redisLibuvDelWrite(void *privdata) {
} }
} }
static void on_timer_close(uv_handle_t *handle) {
static void on_close(uv_handle_t* handle) {
redisLibuvEvents* p = (redisLibuvEvents*)handle->data; redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
p->timer.data = NULL;
free(p); if (!p->handle.data) {
// both timer and handle are closed
hi_free(p);
}
// else, wait for `on_handle_close`
} }
static void on_handle_close(uv_handle_t *handle) {
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
p->handle.data = NULL;
if (!p->timer.data) {
// timer never started, or timer already destroyed
hi_free(p);
}
// else, wait for `on_timer_close`
}
// libuv removed `status` parameter since v0.11.23
// see: https://github.com/libuv/libuv/blob/v0.11.23/include/uv.h
#if (UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR < 11) || \
(UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR == 11 && UV_VERSION_PATCH < 23)
static void redisLibuvTimeout(uv_timer_t *timer, int status) {
(void)status; // unused
#else
static void redisLibuvTimeout(uv_timer_t *timer) {
#endif
redisLibuvEvents *e = (redisLibuvEvents*)timer->data;
redisAsyncHandleTimeout(e->context);
}
static void redisLibuvSetTimeout(void *privdata, struct timeval tv) {
redisLibuvEvents* p = (redisLibuvEvents*)privdata;
uint64_t millsec = tv.tv_sec * 1000 + tv.tv_usec / 1000.0;
if (!p->timer.data) {
// timer is uninitialized
if (uv_timer_init(p->handle.loop, &p->timer) != 0) {
return;
}
p->timer.data = p;
}
// updates the timeout if the timer has already started
// or start the timer
uv_timer_start(&p->timer, redisLibuvTimeout, millsec, 0);
}
static void redisLibuvCleanup(void *privdata) { static void redisLibuvCleanup(void *privdata) {
redisLibuvEvents* p = (redisLibuvEvents*)privdata; redisLibuvEvents* p = (redisLibuvEvents*)privdata;
p->context = NULL; // indicate that context might no longer exist p->context = NULL; // indicate that context might no longer exist
uv_close((uv_handle_t*)&p->handle, on_close); if (p->timer.data) {
uv_close((uv_handle_t*)&p->timer, on_timer_close);
}
uv_close((uv_handle_t*)&p->handle, on_handle_close);
} }
@ -97,16 +150,16 @@ static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) {
ac->ev.addWrite = redisLibuvAddWrite; ac->ev.addWrite = redisLibuvAddWrite;
ac->ev.delWrite = redisLibuvDelWrite; ac->ev.delWrite = redisLibuvDelWrite;
ac->ev.cleanup = redisLibuvCleanup; ac->ev.cleanup = redisLibuvCleanup;
ac->ev.scheduleTimer = redisLibuvSetTimeout;
redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p)); redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p));
if (p == NULL)
if (!p) {
return REDIS_ERR; return REDIS_ERR;
}
memset(p, 0, sizeof(*p)); memset(p, 0, sizeof(*p));
if (uv_poll_init(loop, &p->handle, c->fd) != 0) { if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) {
hi_free(p);
return REDIS_ERR; return REDIS_ERR;
} }

View File

@ -1,7 +1,35 @@
// /*
// Created by Дмитрий Бахвалов on 13.07.15. * Copyright (c) 2015 Дмитрий Бахвалов (Dmitry Bakhvalov)
// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. *
// * Permission for license update:
* https://github.com/redis/hiredis/issues/1271#issuecomment-2258225227
*
* 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_MACOSX_H__ #ifndef __HIREDIS_MACOSX_H__
#define __HIREDIS_MACOSX_H__ #define __HIREDIS_MACOSX_H__
@ -27,7 +55,7 @@ static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) {
CFSocketInvalidate(redisRunLoop->socketRef); CFSocketInvalidate(redisRunLoop->socketRef);
CFRelease(redisRunLoop->socketRef); CFRelease(redisRunLoop->socketRef);
} }
free(redisRunLoop); hi_free(redisRunLoop);
} }
return REDIS_ERR; return REDIS_ERR;
} }
@ -80,8 +108,9 @@ static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLo
/* Nothing should be attached when something is already attached */ /* Nothing should be attached when something is already attached */
if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR; if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;
RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop)); RedisRunLoop* redisRunLoop = (RedisRunLoop*) hi_calloc(1, sizeof(RedisRunLoop));
if( !redisRunLoop ) return REDIS_ERR; if (redisRunLoop == NULL)
return REDIS_ERR;
/* Setup redis stuff */ /* Setup redis stuff */
redisRunLoop->context = redisAsyncCtx; redisRunLoop->context = redisAsyncCtx;

197
adapters/poll.h Normal file
View File

@ -0,0 +1,197 @@
#ifndef HIREDIS_POLL_H
#define HIREDIS_POLL_H
#include "../async.h"
#include "../sockcompat.h"
#include <string.h> // for memset
#include <errno.h>
/* Values to return from redisPollTick */
#define REDIS_POLL_HANDLED_READ 1
#define REDIS_POLL_HANDLED_WRITE 2
#define REDIS_POLL_HANDLED_TIMEOUT 4
/* An adapter to allow manual polling of the async context by checking the state
* of the underlying file descriptor. Useful in cases where there is no formal
* IO event loop but regular ticking can be used, such as in game engines. */
typedef struct redisPollEvents {
redisAsyncContext *context;
redisFD fd;
char reading, writing;
char in_tick;
char deleted;
double deadline;
} redisPollEvents;
static double redisPollTimevalToDouble(struct timeval *tv) {
if (tv == NULL)
return 0.0;
return tv->tv_sec + tv->tv_usec / 1000000.00;
}
static double redisPollGetNow(void) {
#ifndef _MSC_VER
struct timeval tv;
gettimeofday(&tv,NULL);
return redisPollTimevalToDouble(&tv);
#else
FILETIME ft;
ULARGE_INTEGER li;
GetSystemTimeAsFileTime(&ft);
li.HighPart = ft.dwHighDateTime;
li.LowPart = ft.dwLowDateTime;
return (double)li.QuadPart * 1e-7;
#endif
}
/* Poll for io, handling any pending callbacks. The timeout argument can be
* positive to wait for a maximum given time for IO, zero to poll, or negative
* to wait forever */
static int redisPollTick(redisAsyncContext *ac, double timeout) {
int reading, writing;
struct pollfd pfd;
int handled;
int ns;
int itimeout;
redisPollEvents *e = (redisPollEvents*)ac->ev.data;
if (!e)
return 0;
/* local flags, won't get changed during callbacks */
reading = e->reading;
writing = e->writing;
if (!reading && !writing)
return 0;
pfd.fd = e->fd;
pfd.events = 0;
if (reading)
pfd.events = POLLIN;
if (writing)
pfd.events |= POLLOUT;
if (timeout >= 0.0) {
itimeout = (int)(timeout * 1000.0);
} else {
itimeout = -1;
}
ns = poll(&pfd, 1, itimeout);
if (ns < 0) {
/* ignore the EINTR error */
if (errno != EINTR)
return ns;
ns = 0;
}
handled = 0;
e->in_tick = 1;
if (ns) {
if (reading && (pfd.revents & POLLIN)) {
redisAsyncHandleRead(ac);
handled |= REDIS_POLL_HANDLED_READ;
}
/* on Windows, connection failure is indicated with the Exception fdset.
* handle it the same as writable. */
if (writing && (pfd.revents & (POLLOUT | POLLERR))) {
/* context Read callback may have caused context to be deleted, e.g.
by doing an redisAsyncDisconnect() */
if (!e->deleted) {
redisAsyncHandleWrite(ac);
handled |= REDIS_POLL_HANDLED_WRITE;
}
}
}
/* perform timeouts */
if (!e->deleted && e->deadline != 0.0) {
double now = redisPollGetNow();
if (now >= e->deadline) {
/* deadline has passed. disable timeout and perform callback */
e->deadline = 0.0;
redisAsyncHandleTimeout(ac);
handled |= REDIS_POLL_HANDLED_TIMEOUT;
}
}
/* do a delayed cleanup if required */
if (e->deleted)
hi_free(e);
else
e->in_tick = 0;
return handled;
}
static void redisPollAddRead(void *data) {
redisPollEvents *e = (redisPollEvents*)data;
e->reading = 1;
}
static void redisPollDelRead(void *data) {
redisPollEvents *e = (redisPollEvents*)data;
e->reading = 0;
}
static void redisPollAddWrite(void *data) {
redisPollEvents *e = (redisPollEvents*)data;
e->writing = 1;
}
static void redisPollDelWrite(void *data) {
redisPollEvents *e = (redisPollEvents*)data;
e->writing = 0;
}
static void redisPollCleanup(void *data) {
redisPollEvents *e = (redisPollEvents*)data;
/* if we are currently processing a tick, postpone deletion */
if (e->in_tick)
e->deleted = 1;
else
hi_free(e);
}
static void redisPollScheduleTimer(void *data, struct timeval tv)
{
redisPollEvents *e = (redisPollEvents*)data;
double now = redisPollGetNow();
e->deadline = now + redisPollTimevalToDouble(&tv);
}
static int redisPollAttach(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
redisPollEvents *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 = (redisPollEvents*)hi_malloc(sizeof(*e));
if (e == NULL)
return REDIS_ERR;
memset(e, 0, sizeof(*e));
e->context = ac;
e->fd = c->fd;
e->reading = e->writing = 0;
e->in_tick = e->deleted = 0;
e->deadline = 0.0;
/* Register functions to start/stop listening for events */
ac->ev.addRead = redisPollAddRead;
ac->ev.delRead = redisPollDelRead;
ac->ev.addWrite = redisPollAddWrite;
ac->ev.delWrite = redisPollDelWrite;
ac->ev.scheduleTimer = redisPollScheduleTimer;
ac->ev.cleanup = redisPollCleanup;
ac->ev.data = e;
return REDIS_OK;
}
#endif /* HIREDIS_POLL_H */

144
adapters/redismoduleapi.h Normal file
View File

@ -0,0 +1,144 @@
#ifndef __HIREDIS_REDISMODULEAPI_H__
#define __HIREDIS_REDISMODULEAPI_H__
#include "redismodule.h"
#include "../async.h"
#include "../hiredis.h"
#include <sys/types.h>
typedef struct redisModuleEvents {
redisAsyncContext *context;
RedisModuleCtx *module_ctx;
int fd;
int reading, writing;
int timer_active;
RedisModuleTimerID timer_id;
} redisModuleEvents;
static inline void redisModuleReadEvent(int fd, void *privdata, int mask) {
(void) fd;
(void) mask;
redisModuleEvents *e = (redisModuleEvents*)privdata;
redisAsyncHandleRead(e->context);
}
static inline void redisModuleWriteEvent(int fd, void *privdata, int mask) {
(void) fd;
(void) mask;
redisModuleEvents *e = (redisModuleEvents*)privdata;
redisAsyncHandleWrite(e->context);
}
static inline void redisModuleAddRead(void *privdata) {
redisModuleEvents *e = (redisModuleEvents*)privdata;
if (!e->reading) {
e->reading = 1;
RedisModule_EventLoopAdd(e->fd, REDISMODULE_EVENTLOOP_READABLE, redisModuleReadEvent, e);
}
}
static inline void redisModuleDelRead(void *privdata) {
redisModuleEvents *e = (redisModuleEvents*)privdata;
if (e->reading) {
e->reading = 0;
RedisModule_EventLoopDel(e->fd, REDISMODULE_EVENTLOOP_READABLE);
}
}
static inline void redisModuleAddWrite(void *privdata) {
redisModuleEvents *e = (redisModuleEvents*)privdata;
if (!e->writing) {
e->writing = 1;
RedisModule_EventLoopAdd(e->fd, REDISMODULE_EVENTLOOP_WRITABLE, redisModuleWriteEvent, e);
}
}
static inline void redisModuleDelWrite(void *privdata) {
redisModuleEvents *e = (redisModuleEvents*)privdata;
if (e->writing) {
e->writing = 0;
RedisModule_EventLoopDel(e->fd, REDISMODULE_EVENTLOOP_WRITABLE);
}
}
static inline void redisModuleStopTimer(void *privdata) {
redisModuleEvents *e = (redisModuleEvents*)privdata;
if (e->timer_active) {
RedisModule_StopTimer(e->module_ctx, e->timer_id, NULL);
}
e->timer_active = 0;
}
static inline void redisModuleCleanup(void *privdata) {
redisModuleEvents *e = (redisModuleEvents*)privdata;
redisModuleDelRead(privdata);
redisModuleDelWrite(privdata);
redisModuleStopTimer(privdata);
hi_free(e);
}
static inline void redisModuleTimeout(RedisModuleCtx *ctx, void *privdata) {
(void) ctx;
redisModuleEvents *e = (redisModuleEvents*)privdata;
e->timer_active = 0;
redisAsyncHandleTimeout(e->context);
}
static inline void redisModuleSetTimeout(void *privdata, struct timeval tv) {
redisModuleEvents* e = (redisModuleEvents*)privdata;
redisModuleStopTimer(privdata);
mstime_t millis = tv.tv_sec * 1000 + tv.tv_usec / 1000.0;
e->timer_id = RedisModule_CreateTimer(e->module_ctx, millis, redisModuleTimeout, e);
e->timer_active = 1;
}
/* Check if Redis version is compatible with the adapter. */
static inline int redisModuleCompatibilityCheck(void) {
if (!RedisModule_EventLoopAdd ||
!RedisModule_EventLoopDel ||
!RedisModule_CreateTimer ||
!RedisModule_StopTimer) {
return REDIS_ERR;
}
return REDIS_OK;
}
static inline int redisModuleAttach(redisAsyncContext *ac, RedisModuleCtx *module_ctx) {
redisContext *c = &(ac->c);
redisModuleEvents *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 = (redisModuleEvents*)hi_malloc(sizeof(*e));
if (e == NULL)
return REDIS_ERR;
e->context = ac;
e->module_ctx = module_ctx;
e->fd = c->fd;
e->reading = e->writing = 0;
e->timer_active = 0;
/* Register functions to start/stop listening for events */
ac->ev.addRead = redisModuleAddRead;
ac->ev.delRead = redisModuleDelRead;
ac->ev.addWrite = redisModuleAddWrite;
ac->ev.delWrite = redisModuleDelWrite;
ac->ev.cleanup = redisModuleCleanup;
ac->ev.scheduleTimer = redisModuleSetTimeout;
ac->ev.data = e;
return REDIS_OK;
}
#endif

90
alloc.c Normal file
View File

@ -0,0 +1,90 @@
/*
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
*
* 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 "alloc.h"
#include <string.h>
#include <stdlib.h>
hiredisAllocFuncs hiredisAllocFns = {
.mallocFn = malloc,
.callocFn = calloc,
.reallocFn = realloc,
.strdupFn = strdup,
.freeFn = free,
};
/* Override hiredis' allocators with ones supplied by the user */
hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *override) {
hiredisAllocFuncs orig = hiredisAllocFns;
hiredisAllocFns = *override;
return orig;
}
/* Reset allocators to use libc defaults */
void hiredisResetAllocators(void) {
hiredisAllocFns = (hiredisAllocFuncs) {
.mallocFn = malloc,
.callocFn = calloc,
.reallocFn = realloc,
.strdupFn = strdup,
.freeFn = free,
};
}
#ifdef _WIN32
void *hi_malloc(size_t size) {
return hiredisAllocFns.mallocFn(size);
}
void *hi_calloc(size_t nmemb, size_t size) {
/* Overflow check as the user can specify any arbitrary allocator */
if (SIZE_MAX / size < nmemb)
return NULL;
return hiredisAllocFns.callocFn(nmemb, size);
}
void *hi_realloc(void *ptr, size_t size) {
return hiredisAllocFns.reallocFn(ptr, size);
}
char *hi_strdup(const char *str) {
return hiredisAllocFns.strdupFn(str);
}
void hi_free(void *ptr) {
hiredisAllocFns.freeFn(ptr);
}
#endif

96
alloc.h Normal file
View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
*
* 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_ALLOC_H
#define HIREDIS_ALLOC_H
#include <stddef.h> /* for size_t */
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Structure pointing to our actually configured allocators */
typedef struct hiredisAllocFuncs {
void *(*mallocFn)(size_t);
void *(*callocFn)(size_t,size_t);
void *(*reallocFn)(void*,size_t);
char *(*strdupFn)(const char*);
void (*freeFn)(void*);
} hiredisAllocFuncs;
hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *ha);
void hiredisResetAllocators(void);
#ifndef _WIN32
/* Hiredis' configured allocator function pointer struct */
extern hiredisAllocFuncs hiredisAllocFns;
static inline void *hi_malloc(size_t size) {
return hiredisAllocFns.mallocFn(size);
}
static inline void *hi_calloc(size_t nmemb, size_t size) {
/* Overflow check as the user can specify any arbitrary allocator */
if (SIZE_MAX / size < nmemb)
return NULL;
return hiredisAllocFns.callocFn(nmemb, size);
}
static inline void *hi_realloc(void *ptr, size_t size) {
return hiredisAllocFns.reallocFn(ptr, size);
}
static inline char *hi_strdup(const char *str) {
return hiredisAllocFns.strdupFn(str);
}
static inline void hi_free(void *ptr) {
hiredisAllocFns.freeFn(ptr);
}
#else
void *hi_malloc(size_t size);
void *hi_calloc(size_t nmemb, size_t size);
void *hi_realloc(void *ptr, size_t size);
char *hi_strdup(const char *str);
void hi_free(void *ptr);
#endif
#ifdef __cplusplus
}
#endif
#endif /* HIREDIS_ALLOC_H */

598
async.c
View File

@ -30,9 +30,12 @@
*/ */
#include "fmacros.h" #include "fmacros.h"
#include "alloc.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#ifndef _MSC_VER
#include <strings.h> #include <strings.h>
#endif
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
@ -40,44 +43,18 @@
#include "net.h" #include "net.h"
#include "dict.c" #include "dict.c"
#include "sds.h" #include "sds.h"
#include "sslio.h" #include "win32.h"
#define _EL_ADD_READ(ctx) \ #include "async_private.h"
do { \
refreshTimeout(ctx); \
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 { \
refreshTimeout(ctx); \
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); \
ctx->ev.cleanup = NULL; \
} while(0);
static void refreshTimeout(redisAsyncContext *ctx) { #ifdef NDEBUG
if (ctx->c.timeout && ctx->ev.scheduleTimer && #undef assert
(ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) { #define assert(e) (void)(e)
ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout); #endif
// } else {
// printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout);
// if (ctx->c.timeout){
// printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec,
// ctx->c.timeout->tv_usec);
// }
}
}
/* Forward declaration of function in hiredis.c */ /* Forward declarations of hiredis.c functions */
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
void __redisSetError(redisContext *c, int type, const char *str);
/* Functions managing dictionary of callbacks for pub/sub. */ /* Functions managing dictionary of callbacks for pub/sub. */
static unsigned int callbackHash(const void *key) { static unsigned int callbackHash(const void *key) {
@ -87,7 +64,12 @@ static unsigned int callbackHash(const void *key) {
static void *callbackValDup(void *privdata, const void *src) { static void *callbackValDup(void *privdata, const void *src) {
((void) privdata); ((void) privdata);
redisCallback *dup = malloc(sizeof(*dup)); redisCallback *dup;
dup = hi_malloc(sizeof(*dup));
if (dup == NULL)
return NULL;
memcpy(dup,src,sizeof(*dup)); memcpy(dup,src,sizeof(*dup));
return dup; return dup;
} }
@ -109,7 +91,7 @@ static void callbackKeyDestructor(void *privdata, void *key) {
static void callbackValDestructor(void *privdata, void *val) { static void callbackValDestructor(void *privdata, void *val) {
((void) privdata); ((void) privdata);
free(val); hi_free(val);
} }
static dictType callbackDict = { static dictType callbackDict = {
@ -123,10 +105,19 @@ static dictType callbackDict = {
static redisAsyncContext *redisAsyncInitialize(redisContext *c) { static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
redisAsyncContext *ac; redisAsyncContext *ac;
dict *channels = NULL, *patterns = NULL;
ac = realloc(c,sizeof(redisAsyncContext)); channels = dictCreate(&callbackDict,NULL);
if (channels == NULL)
goto oom;
patterns = dictCreate(&callbackDict,NULL);
if (patterns == NULL)
goto oom;
ac = hi_realloc(c,sizeof(redisAsyncContext));
if (ac == NULL) if (ac == NULL)
return NULL; goto oom;
c = &(ac->c); c = &(ac->c);
@ -138,6 +129,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
ac->err = 0; ac->err = 0;
ac->errstr = NULL; ac->errstr = NULL;
ac->data = NULL; ac->data = NULL;
ac->dataCleanup = NULL;
ac->ev.data = NULL; ac->ev.data = NULL;
ac->ev.addRead = NULL; ac->ev.addRead = NULL;
@ -148,15 +140,22 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
ac->ev.scheduleTimer = NULL; ac->ev.scheduleTimer = NULL;
ac->onConnect = NULL; ac->onConnect = NULL;
ac->onConnectNC = NULL;
ac->onDisconnect = NULL; ac->onDisconnect = NULL;
ac->replies.head = NULL; ac->replies.head = NULL;
ac->replies.tail = NULL; ac->replies.tail = NULL;
ac->sub.invalid.head = NULL; ac->sub.replies.head = NULL;
ac->sub.invalid.tail = NULL; ac->sub.replies.tail = NULL;
ac->sub.channels = dictCreate(&callbackDict,NULL); ac->sub.channels = channels;
ac->sub.patterns = dictCreate(&callbackDict,NULL); ac->sub.patterns = patterns;
ac->sub.pending_unsubs = 0;
return ac; return ac;
oom:
if (channels) dictRelease(channels);
if (patterns) dictRelease(patterns);
return NULL;
} }
/* We want the error field to be accessible directly instead of requiring /* We want the error field to be accessible directly instead of requiring
@ -175,16 +174,26 @@ redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
redisContext *c; redisContext *c;
redisAsyncContext *ac; redisAsyncContext *ac;
/* Clear any erroneously set sync callback and flag that we don't want to
* use freeReplyObject by default. */
myOptions.push_cb = NULL;
myOptions.options |= REDIS_OPT_NO_PUSH_AUTOFREE;
myOptions.options |= REDIS_OPT_NONBLOCK; myOptions.options |= REDIS_OPT_NONBLOCK;
c = redisConnectWithOptions(&myOptions); c = redisConnectWithOptions(&myOptions);
if (c == NULL) { if (c == NULL) {
return NULL; return NULL;
} }
ac = redisAsyncInitialize(c); ac = redisAsyncInitialize(c);
if (ac == NULL) { if (ac == NULL) {
redisFree(c); redisFree(c);
return NULL; return NULL;
} }
/* Set any configured async push handler */
redisAsyncSetPushCallback(ac, myOptions.async_push_cb);
__redisAsyncCopyError(ac); __redisAsyncCopyError(ac);
return ac; return ac;
} }
@ -218,17 +227,34 @@ redisAsyncContext *redisAsyncConnectUnix(const char *path) {
return redisAsyncConnectWithOptions(&options); return redisAsyncConnectWithOptions(&options);
} }
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { static int
if (ac->onConnect == NULL) { redisAsyncSetConnectCallbackImpl(redisAsyncContext *ac, redisConnectCallback *fn,
redisConnectCallbackNC *fn_nc)
{
/* If either are already set, this is an error */
if (ac->onConnect || ac->onConnectNC)
return REDIS_ERR;
if (fn) {
ac->onConnect = fn; ac->onConnect = fn;
} else if (fn_nc) {
ac->onConnectNC = fn_nc;
}
/* The common way to detect an established connection is to wait for /* The common way to detect an established connection is to wait for
* the first write event to be fired. This assumes the related event * the first write event to be fired. This assumes the related event
* library functions are already set. */ * library functions are already set. */
_EL_ADD_WRITE(ac); _EL_ADD_WRITE(ac);
return REDIS_OK; return REDIS_OK;
} }
return REDIS_ERR;
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
return redisAsyncSetConnectCallbackImpl(ac, fn, NULL);
}
int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn) {
return redisAsyncSetConnectCallbackImpl(ac, NULL, fn);
} }
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
@ -244,7 +270,7 @@ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
redisCallback *cb; redisCallback *cb;
/* Copy callback from stack to heap */ /* Copy callback from stack to heap */
cb = malloc(sizeof(*cb)); cb = hi_malloc(sizeof(*cb));
if (cb == NULL) if (cb == NULL)
return REDIS_ERR_OOM; return REDIS_ERR_OOM;
@ -272,7 +298,7 @@ static int __redisShiftCallback(redisCallbackList *list, redisCallback *target)
/* Copy callback from heap to stack */ /* Copy callback from heap to stack */
if (target != NULL) if (target != NULL)
memcpy(target,cb,sizeof(*cb)); memcpy(target,cb,sizeof(*cb));
free(cb); hi_free(cb);
return REDIS_OK; return REDIS_OK;
} }
return REDIS_ERR; return REDIS_ERR;
@ -287,45 +313,95 @@ static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisRe
} }
} }
static void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) {
if (ac->push_cb != NULL) {
ac->c.flags |= REDIS_IN_CALLBACK;
ac->push_cb(ac, reply);
ac->c.flags &= ~REDIS_IN_CALLBACK;
}
}
static void __redisRunConnectCallback(redisAsyncContext *ac, int status)
{
if (ac->onConnect == NULL && ac->onConnectNC == NULL)
return;
if (!(ac->c.flags & REDIS_IN_CALLBACK)) {
ac->c.flags |= REDIS_IN_CALLBACK;
if (ac->onConnect) {
ac->onConnect(ac, status);
} else {
ac->onConnectNC(ac, status);
}
ac->c.flags &= ~REDIS_IN_CALLBACK;
} else {
/* already in callback */
if (ac->onConnect) {
ac->onConnect(ac, status);
} else {
ac->onConnectNC(ac, status);
}
}
}
static void __redisRunDisconnectCallback(redisAsyncContext *ac, int status)
{
if (ac->onDisconnect) {
if (!(ac->c.flags & REDIS_IN_CALLBACK)) {
ac->c.flags |= REDIS_IN_CALLBACK;
ac->onDisconnect(ac, status);
ac->c.flags &= ~REDIS_IN_CALLBACK;
} else {
/* already in callback */
ac->onDisconnect(ac, status);
}
}
}
/* Helper function to free the context. */ /* Helper function to free the context. */
static void __redisAsyncFree(redisAsyncContext *ac) { static void __redisAsyncFree(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb; redisCallback cb;
dictIterator *it; dictIterator it;
dictEntry *de; dictEntry *de;
/* Execute pending callbacks with NULL reply. */ /* Execute pending callbacks with NULL reply. */
while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
__redisRunCallback(ac,&cb,NULL); __redisRunCallback(ac,&cb,NULL);
while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK)
/* Execute callbacks for invalid commands */
while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
__redisRunCallback(ac,&cb,NULL); __redisRunCallback(ac,&cb,NULL);
/* Run subscription callbacks callbacks with NULL reply */ /* Run subscription callbacks with NULL reply */
it = dictGetIterator(ac->sub.channels); if (ac->sub.channels) {
while ((de = dictNext(it)) != NULL) dictInitIterator(&it,ac->sub.channels);
while ((de = dictNext(&it)) != NULL)
__redisRunCallback(ac,dictGetEntryVal(de),NULL); __redisRunCallback(ac,dictGetEntryVal(de),NULL);
dictReleaseIterator(it);
dictRelease(ac->sub.channels);
it = dictGetIterator(ac->sub.patterns); dictRelease(ac->sub.channels);
while ((de = dictNext(it)) != NULL) }
if (ac->sub.patterns) {
dictInitIterator(&it,ac->sub.patterns);
while ((de = dictNext(&it)) != NULL)
__redisRunCallback(ac,dictGetEntryVal(de),NULL); __redisRunCallback(ac,dictGetEntryVal(de),NULL);
dictReleaseIterator(it);
dictRelease(ac->sub.patterns); dictRelease(ac->sub.patterns);
}
/* Signal event lib to clean up */ /* Signal event lib to clean up */
_EL_CLEANUP(ac); _EL_CLEANUP(ac);
/* Execute disconnect callback. When redisAsyncFree() initiated destroying /* Execute disconnect callback. When redisAsyncFree() initiated destroying
* this context, the status will always be REDIS_OK. */ * this context, the status will always be REDIS_OK. */
if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { if (c->flags & REDIS_CONNECTED) {
if (c->flags & REDIS_FREEING) { int status = ac->err == 0 ? REDIS_OK : REDIS_ERR;
ac->onDisconnect(ac,REDIS_OK); if (c->flags & REDIS_FREEING)
} else { status = REDIS_OK;
ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); __redisRunDisconnectCallback(ac, status);
} }
if (ac->dataCleanup) {
ac->dataCleanup(ac->data);
} }
/* Cleanup self */ /* Cleanup self */
@ -337,14 +413,18 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
* free'ing. To do so, a flag is set on the context which is picked up by * 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. */ * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
void redisAsyncFree(redisAsyncContext *ac) { void redisAsyncFree(redisAsyncContext *ac) {
if (ac == NULL)
return;
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
c->flags |= REDIS_FREEING; c->flags |= REDIS_FREEING;
if (!(c->flags & REDIS_IN_CALLBACK)) if (!(c->flags & REDIS_IN_CALLBACK))
__redisAsyncFree(ac); __redisAsyncFree(ac);
} }
/* Helper function to make the disconnect happen and clean up. */ /* Helper function to make the disconnect happen and clean up. */
static void __redisAsyncDisconnect(redisAsyncContext *ac) { void __redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
/* Make sure error is accessible if there is any */ /* Make sure error is accessible if there is any */
@ -390,16 +470,17 @@ void redisAsyncDisconnect(redisAsyncContext *ac) {
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
dict *callbacks; dict *callbacks;
redisCallback *cb; redisCallback *cb = NULL;
dictEntry *de; dictEntry *de;
int pvariant; int pvariant;
char *stype; char *stype;
sds sname; sds sname = NULL;
/* Custom reply functions are not supported for pub/sub. This will fail /* Match reply with the expected format of a pushed message.
* very hard when they are used... */ * The type and number of elements (3 to 4) are specified at:
if (reply->type == REDIS_REPLY_ARRAY) { * https://redis.io/docs/latest/develop/interact/pubsub/#format-of-pushed-messages */
assert(reply->elements >= 2); if ((reply->type == REDIS_REPLY_ARRAY && !(c->flags & REDIS_SUPPORTS_PUSH) && reply->elements >= 3) ||
reply->type == REDIS_REPLY_PUSH) {
assert(reply->element[0]->type == REDIS_REPLY_STRING); assert(reply->element[0]->type == REDIS_REPLY_STRING);
stype = reply->element[0]->str; stype = reply->element[0]->str;
pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
@ -410,46 +491,84 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
callbacks = ac->sub.channels; callbacks = ac->sub.channels;
/* Locate the right callback */ /* Locate the right callback */
assert(reply->element[1]->type == REDIS_REPLY_STRING); if (reply->element[1]->type == REDIS_REPLY_STRING) {
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
de = dictFind(callbacks,sname); if (sname == NULL) goto oom;
if (de != NULL) {
if ((de = dictFind(callbacks,sname)) != NULL) {
cb = dictGetEntryVal(de); cb = dictGetEntryVal(de);
memcpy(dstcb,cb,sizeof(*dstcb));
}
}
/* If this is an subscribe reply decrease pending counter. */ /* If this is an subscribe reply decrease pending counter. */
if (strcasecmp(stype+pvariant,"subscribe") == 0) { if (strcasecmp(stype+pvariant,"subscribe") == 0) {
assert(cb != NULL);
cb->pending_subs -= 1; cb->pending_subs -= 1;
}
memcpy(dstcb,cb,sizeof(*dstcb)); } else if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
if (cb == NULL)
/* If this is an unsubscribe message, remove it. */ ac->sub.pending_unsubs -= 1;
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { else if (cb->pending_subs == 0)
if (cb->pending_subs == 0)
dictDelete(callbacks,sname); dictDelete(callbacks,sname);
/* If this was the last unsubscribe message, revert to /* If this was the last unsubscribe message, revert to
* non-subscribe mode. */ * non-subscribe mode. */
assert(reply->element[2]->type == REDIS_REPLY_INTEGER); assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
/* Unset subscribed flag only when no pipelined pending subscribe. */ /* Unset subscribed flag only when no pipelined pending subscribe
* or pending unsubscribe replies. */
if (reply->element[2]->integer == 0 if (reply->element[2]->integer == 0
&& dictSize(ac->sub.channels) == 0 && dictSize(ac->sub.channels) == 0
&& dictSize(ac->sub.patterns) == 0) && dictSize(ac->sub.patterns) == 0
&& ac->sub.pending_unsubs == 0) {
c->flags &= ~REDIS_SUBSCRIBED; c->flags &= ~REDIS_SUBSCRIBED;
/* Move ongoing regular command callbacks. */
redisCallback cb;
while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) {
__redisPushCallback(&ac->replies,&cb);
}
} }
} }
sdsfree(sname); sdsfree(sname);
} else { } else {
/* Shift callback for invalid commands. */ /* Shift callback for pending command in subscribed context. */
__redisShiftCallback(&ac->sub.invalid,dstcb); __redisShiftCallback(&ac->sub.replies,dstcb);
} }
return REDIS_OK; return REDIS_OK;
oom:
__redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
__redisAsyncCopyError(ac);
return REDIS_ERR;
}
#define redisIsSpontaneousPushReply(r) \
(redisIsPushReply(r) && !redisIsSubscribeReply(r))
static int redisIsSubscribeReply(redisReply *reply) {
char *str;
size_t len, off;
/* We will always have at least one string with the subscribe/message type */
if (reply->elements < 1 || reply->element[0]->type != REDIS_REPLY_STRING ||
reply->element[0]->len < sizeof("message") - 1)
{
return 0;
}
/* Get the string/len moving past 'p' if needed */
off = tolower(reply->element[0]->str[0]) == 'p';
str = reply->element[0]->str + off;
len = reply->element[0]->len - off;
return !strncasecmp(str, "subscribe", len) ||
!strncasecmp(str, "message", len) ||
!strncasecmp(str, "unsubscribe", len);
} }
void redisProcessCallbacks(redisAsyncContext *ac) { void redisProcessCallbacks(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb = {NULL, NULL, 0, NULL};
void *reply = NULL; void *reply = NULL;
int status; int status;
@ -462,19 +581,27 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
return; return;
} }
/* If monitor mode, repush callback */
if(c->flags & REDIS_MONITORING) {
__redisPushCallback(&ac->replies,&cb);
}
/* When the connection is not being disconnected, simply stop /* When the connection is not being disconnected, simply stop
* trying to get replies and wait for the next loop tick. */ * trying to get replies and wait for the next loop tick. */
break; break;
} }
/* Even if the context is subscribed, pending regular callbacks will /* Keep track of push message support for subscribe handling */
* get a reply before pub/sub messages arrive. */ if (redisIsPushReply(reply)) c->flags |= REDIS_SUPPORTS_PUSH;
/* Send any non-subscribe related PUSH messages to our PUSH handler
* while allowing subscribe related PUSH messages to pass through.
* This allows existing code to be backward compatible and work in
* either RESP2 or RESP3 mode. */
if (redisIsSpontaneousPushReply(reply)) {
__redisRunPushCallback(ac, reply);
c->reader->fn->freeObject(reply);
continue;
}
/* Even if the context is subscribed, pending regular
* callbacks will get a reply before pub/sub messages arrive. */
redisCallback cb = {NULL, NULL, 0, 0, NULL};
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
/* /*
* A spontaneous reply in a not-subscribed context can be the error * A spontaneous reply in a not-subscribed context can be the error
@ -498,15 +625,17 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
return; return;
} }
/* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ /* No more regular callbacks and no errors, the context *must* be subscribed. */
assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); assert(c->flags & REDIS_SUBSCRIBED);
if (c->flags & REDIS_SUBSCRIBED) if (c->flags & REDIS_SUBSCRIBED)
__redisGetSubscribeCallback(ac,reply,&cb); __redisGetSubscribeCallback(ac,reply,&cb);
} }
if (cb.fn != NULL) { if (cb.fn != NULL) {
__redisRunCallback(ac,&cb,reply); __redisRunCallback(ac,&cb,reply);
if (!(c->flags & REDIS_NO_AUTO_FREE_REPLIES)){
c->reader->fn->freeObject(reply); c->reader->fn->freeObject(reply);
}
/* Proceed with free'ing when redisAsyncFree() was called. */ /* Proceed with free'ing when redisAsyncFree() was called. */
if (c->flags & REDIS_FREEING) { if (c->flags & REDIS_FREEING) {
@ -520,6 +649,11 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
* doesn't know what the server will spit out over the wire. */ * doesn't know what the server will spit out over the wire. */
c->reader->fn->freeObject(reply); c->reader->fn->freeObject(reply);
} }
/* If in monitor mode, repush the callback */
if (c->flags & REDIS_MONITORING) {
__redisPushCallback(&ac->replies,&cb);
}
} }
/* Disconnect when there was an error reading the reply */ /* Disconnect when there was an error reading the reply */
@ -527,118 +661,54 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
} }
static void __redisAsyncHandleConnectFailure(redisAsyncContext *ac) {
__redisRunConnectCallback(ac, REDIS_ERR);
__redisAsyncDisconnect(ac);
}
/* Internal helper function to detect socket status the first time a read or /* Internal helper function to detect socket status the first time a read or
* write event fires. When connecting was not successful, the connect callback * write event fires. When connecting was not successful, the connect callback
* is called with a REDIS_ERR status and the context is free'd. */ * is called with a REDIS_ERR status and the context is free'd. */
static int __redisAsyncHandleConnect(redisAsyncContext *ac) { static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
int completed = 0; int completed = 0;
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) { if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
/* Error! */ /* Error! */
redisCheckSocketError(c); if (redisCheckSocketError(c) == REDIS_ERR)
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR); __redisAsyncCopyError(ac);
__redisAsyncDisconnect(ac); __redisAsyncHandleConnectFailure(ac);
return REDIS_ERR; return REDIS_ERR;
} else if (completed == 1) { } else if (completed == 1) {
/* connected! */ /* connected! */
if (ac->onConnect) ac->onConnect(ac, REDIS_OK); if (c->connection_type == REDIS_CONN_TCP &&
redisSetTcpNoDelay(c) == REDIS_ERR) {
__redisAsyncHandleConnectFailure(ac);
return REDIS_ERR;
}
/* flag us as fully connect, but allow the callback
* to disconnect. For that reason, permit the function
* to delete the context here after callback return.
*/
c->flags |= REDIS_CONNECTED; c->flags |= REDIS_CONNECTED;
__redisRunConnectCallback(ac, REDIS_OK);
if ((ac->c.flags & REDIS_DISCONNECTING)) {
redisAsyncDisconnect(ac);
return REDIS_ERR;
} else if ((ac->c.flags & REDIS_FREEING)) {
redisAsyncFree(ac);
return REDIS_ERR;
}
return REDIS_OK; return REDIS_OK;
} else { } else {
return REDIS_OK; return REDIS_OK;
} }
} }
/** void redisAsyncRead(redisAsyncContext *ac) {
* Handle SSL when socket becomes available for reading. This also handles
* read-while-write and write-while-read.
*
* These functions will not work properly unless `HIREDIS_SSL` is defined
* (however, they will compile)
*/
static void asyncSslRead(redisAsyncContext *ac) {
int rv;
redisSsl *ssl = ac->c.ssl;
redisContext *c = &ac->c;
ssl->wantRead = 0;
if (ssl->pendingWrite) {
int done;
/* This is probably just a write event */
ssl->pendingWrite = 0;
rv = redisBufferWrite(c, &done);
if (rv == REDIS_ERR) {
__redisAsyncDisconnect(ac);
return;
} else if (!done) {
_EL_ADD_WRITE(ac);
}
}
rv = redisBufferRead(c);
if (rv == REDIS_ERR) {
__redisAsyncDisconnect(ac);
} else {
_EL_ADD_READ(ac);
redisProcessCallbacks(ac);
}
}
/**
* Handle SSL when socket becomes available for writing
*/
static void asyncSslWrite(redisAsyncContext *ac) {
int rv, done = 0;
redisSsl *ssl = ac->c.ssl;
redisContext *c = &ac->c;
ssl->pendingWrite = 0;
rv = redisBufferWrite(c, &done);
if (rv == REDIS_ERR) {
__redisAsyncDisconnect(ac);
return;
}
if (!done) {
if (ssl->wantRead) {
/* Need to read-before-write */
ssl->pendingWrite = 1;
_EL_DEL_WRITE(ac);
} else {
/* No extra reads needed, just need to write more */
_EL_ADD_WRITE(ac);
}
} else {
/* Already done! */
_EL_DEL_WRITE(ac);
}
/* Always reschedule a read */
_EL_ADD_READ(ac);
}
/* 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); 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 (c->flags & REDIS_SSL) {
asyncSslRead(ac);
return;
}
if (redisBufferRead(c) == REDIS_ERR) { if (redisBufferRead(c) == REDIS_ERR) {
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
} else { } else {
@ -648,9 +718,13 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
} }
} }
void redisAsyncHandleWrite(redisAsyncContext *ac) { /* 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); redisContext *c = &(ac->c);
int done = 0; /* must not be called from a callback */
assert(!(c->flags & REDIS_IN_CALLBACK));
if (!(c->flags & REDIS_CONNECTED)) { if (!(c->flags & REDIS_CONNECTED)) {
/* Abort connect was not successful. */ /* Abort connect was not successful. */
@ -661,11 +735,13 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
return; return;
} }
if (c->flags & REDIS_SSL) { c->funcs->async_read(ac);
asyncSslWrite(ac);
return;
} }
void redisAsyncWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
int done = 0;
if (redisBufferWrite(c,&done) == REDIS_ERR) { if (redisBufferWrite(c,&done) == REDIS_ERR) {
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
} else { } else {
@ -680,23 +756,49 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
} }
} }
void __redisSetError(redisContext *c, int type, const char *str); void redisAsyncHandleWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
/* must not be called from a callback */
assert(!(c->flags & REDIS_IN_CALLBACK));
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;
}
c->funcs->async_write(ac);
}
void redisAsyncHandleTimeout(redisAsyncContext *ac) { void redisAsyncHandleTimeout(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb; redisCallback cb;
/* must not be called from a callback */
assert(!(c->flags & REDIS_IN_CALLBACK));
if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) { if ((c->flags & REDIS_CONNECTED)) {
if (ac->replies.head == NULL && ac->sub.replies.head == NULL) {
/* Nothing to do - just an idle timeout */ /* Nothing to do - just an idle timeout */
return; return;
} }
if (!c->err) { if (!ac->c.command_timeout ||
__redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout"); (!ac->c.command_timeout->tv_sec && !ac->c.command_timeout->tv_usec)) {
/* A belated connect timeout arriving, ignore */
return;
}
} }
if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) { if (!c->err) {
ac->onConnect(ac, REDIS_ERR); __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout");
__redisAsyncCopyError(ac);
}
if (!(c->flags & REDIS_CONNECTED)) {
__redisRunConnectCallback(ac, REDIS_ERR);
} }
while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) { while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
@ -733,6 +835,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb; redisCallback cb;
struct dict *cbdict; struct dict *cbdict;
dictIterator it;
dictEntry *de; dictEntry *de;
redisCallback *existcb; redisCallback *existcb;
int pvariant, hasnext; int pvariant, hasnext;
@ -749,6 +852,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
cb.fn = fn; cb.fn = fn;
cb.privdata = privdata; cb.privdata = privdata;
cb.pending_subs = 1; cb.pending_subs = 1;
cb.unsubscribe_sent = 0;
/* Find out which command will be appended. */ /* Find out which command will be appended. */
p = nextArgument(cmd,&cstr,&clen); p = nextArgument(cmd,&cstr,&clen);
@ -764,6 +868,9 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
/* Add every channel/pattern to the list of subscription callbacks. */ /* Add every channel/pattern to the list of subscription callbacks. */
while ((p = nextArgument(p,&astr,&alen)) != NULL) { while ((p = nextArgument(p,&astr,&alen)) != NULL) {
sname = sdsnewlen(astr,alen); sname = sdsnewlen(astr,alen);
if (sname == NULL)
goto oom;
if (pvariant) if (pvariant)
cbdict = ac->sub.patterns; cbdict = ac->sub.patterns;
else else
@ -785,20 +892,67 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
* subscribed to one or more channels or patterns. */ * subscribed to one or more channels or patterns. */
if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
if (pvariant)
cbdict = ac->sub.patterns;
else
cbdict = ac->sub.channels;
if (hasnext) {
/* Send an unsubscribe with specific channels/patterns.
* Bookkeeping the number of expected replies */
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
sname = sdsnewlen(astr,alen);
if (sname == NULL)
goto oom;
de = dictFind(cbdict,sname);
if (de != NULL) {
existcb = dictGetEntryVal(de);
if (existcb->unsubscribe_sent == 0)
existcb->unsubscribe_sent = 1;
else
/* Already sent, reply to be ignored */
ac->sub.pending_unsubs += 1;
} else {
/* Not subscribed to, reply to be ignored */
ac->sub.pending_unsubs += 1;
}
sdsfree(sname);
}
} else {
/* Send an unsubscribe without specific channels/patterns.
* Bookkeeping the number of expected replies */
int no_subs = 1;
dictInitIterator(&it,cbdict);
while ((de = dictNext(&it)) != NULL) {
existcb = dictGetEntryVal(de);
if (existcb->unsubscribe_sent == 0) {
existcb->unsubscribe_sent = 1;
no_subs = 0;
}
}
/* Unsubscribing to all channels/patterns, where none is
* subscribed to, results in a single reply to be ignored. */
if (no_subs == 1)
ac->sub.pending_unsubs += 1;
}
/* (P)UNSUBSCRIBE does not have its own response: every channel or /* (P)UNSUBSCRIBE does not have its own response: every channel or
* pattern that is unsubscribed will receive a message. This means we * pattern that is unsubscribed will receive a message. This means we
* should not append a callback function for this command. */ * should not append a callback function for this command. */
} else if (strncasecmp(cstr,"monitor\r\n",9) == 0) { } else if (strncasecmp(cstr,"monitor\r\n",9) == 0) {
/* Set monitor flag and push callback */ /* Set monitor flag and push callback */
c->flags |= REDIS_MONITORING; c->flags |= REDIS_MONITORING;
__redisPushCallback(&ac->replies,&cb); if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK)
goto oom;
} else { } else {
if (c->flags & REDIS_SUBSCRIBED) if (c->flags & REDIS_SUBSCRIBED) {
/* This will likely result in an error reply, but it needs to be if (__redisPushCallback(&ac->sub.replies,&cb) != REDIS_OK)
* received and passed to the callback. */ goto oom;
__redisPushCallback(&ac->sub.invalid,&cb); } else {
else if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK)
__redisPushCallback(&ac->replies,&cb); goto oom;
}
} }
__redisAppendCommand(c,cmd,len); __redisAppendCommand(c,cmd,len);
@ -807,6 +961,10 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
_EL_ADD_WRITE(ac); _EL_ADD_WRITE(ac);
return REDIS_OK; return REDIS_OK;
oom:
__redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
__redisAsyncCopyError(ac);
return REDIS_ERR;
} }
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
@ -820,7 +978,7 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat
return REDIS_ERR; return REDIS_ERR;
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
free(cmd); hi_free(cmd);
return status; return status;
} }
@ -835,7 +993,7 @@ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
sds cmd; sds cmd;
int len; long long len;
int status; int status;
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
if (len < 0) if (len < 0)
@ -850,15 +1008,27 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
return status; return status;
} }
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) { redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn) {
if (!ac->c.timeout) { redisAsyncPushFn *old = ac->push_cb;
ac->c.timeout = calloc(1, sizeof(tv)); ac->push_cb = fn;
return old;
} }
if (tv.tv_sec == ac->c.timeout->tv_sec && int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
tv.tv_usec == ac->c.timeout->tv_usec) { if (!ac->c.command_timeout) {
return; ac->c.command_timeout = hi_calloc(1, sizeof(tv));
if (ac->c.command_timeout == NULL) {
__redisSetError(&ac->c, REDIS_ERR_OOM, "Out of memory");
__redisAsyncCopyError(ac);
return REDIS_ERR;
}
} }
*ac->c.timeout = tv; if (tv.tv_sec != ac->c.command_timeout->tv_sec ||
tv.tv_usec != ac->c.command_timeout->tv_usec)
{
*ac->c.command_timeout = tv;
}
return REDIS_OK;
} }

16
async.h
View File

@ -46,6 +46,7 @@ typedef struct redisCallback {
struct redisCallback *next; /* simple singly linked list */ struct redisCallback *next; /* simple singly linked list */
redisCallbackFn *fn; redisCallbackFn *fn;
int pending_subs; int pending_subs;
int unsubscribe_sent;
void *privdata; void *privdata;
} redisCallback; } redisCallback;
@ -57,6 +58,7 @@ typedef struct redisCallbackList {
/* Connection callback prototypes */ /* Connection callback prototypes */
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallbackNC)(struct redisAsyncContext *, int status);
typedef void(redisTimerCallback)(void *timer, void *privdata); typedef void(redisTimerCallback)(void *timer, void *privdata);
/* Context for an async connection to Redis */ /* Context for an async connection to Redis */
@ -70,6 +72,7 @@ typedef struct redisAsyncContext {
/* Not used by hiredis */ /* Not used by hiredis */
void *data; void *data;
void (*dataCleanup)(void *privdata);
/* Event library data and hooks */ /* Event library data and hooks */
struct { struct {
@ -91,6 +94,7 @@ typedef struct redisAsyncContext {
/* Called when the first write event was received. */ /* Called when the first write event was received. */
redisConnectCallback *onConnect; redisConnectCallback *onConnect;
redisConnectCallbackNC *onConnectNC;
/* Regular command callbacks */ /* Regular command callbacks */
redisCallbackList replies; redisCallbackList replies;
@ -101,10 +105,14 @@ typedef struct redisAsyncContext {
/* Subscription callbacks */ /* Subscription callbacks */
struct { struct {
redisCallbackList invalid; redisCallbackList replies;
struct dict *channels; struct dict *channels;
struct dict *patterns; struct dict *patterns;
int pending_unsubs;
} sub; } sub;
/* Any configured RESP3 PUSH handler */
redisAsyncPushFn *push_cb;
} redisAsyncContext; } redisAsyncContext;
/* Functions that proxy to hiredis */ /* Functions that proxy to hiredis */
@ -115,9 +123,11 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr); const char *source_addr);
redisAsyncContext *redisAsyncConnectUnix(const char *path); redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv); redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn);
int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
void redisAsyncDisconnect(redisAsyncContext *ac); void redisAsyncDisconnect(redisAsyncContext *ac);
void redisAsyncFree(redisAsyncContext *ac); void redisAsyncFree(redisAsyncContext *ac);
@ -125,6 +135,8 @@ void redisAsyncFree(redisAsyncContext *ac);
void redisAsyncHandleRead(redisAsyncContext *ac); void redisAsyncHandleRead(redisAsyncContext *ac);
void redisAsyncHandleWrite(redisAsyncContext *ac); void redisAsyncHandleWrite(redisAsyncContext *ac);
void redisAsyncHandleTimeout(redisAsyncContext *ac); void redisAsyncHandleTimeout(redisAsyncContext *ac);
void redisAsyncRead(redisAsyncContext *ac);
void redisAsyncWrite(redisAsyncContext *ac);
/* Command functions for an async context. Write the command to the /* Command functions for an async context. Write the command to the
* output buffer and register the provided callback. */ * output buffer and register the provided callback. */

75
async_private.h Normal file
View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* 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_PRIVATE_H
#define __HIREDIS_ASYNC_PRIVATE_H
#define _EL_ADD_READ(ctx) \
do { \
refreshTimeout(ctx); \
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 { \
refreshTimeout(ctx); \
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); \
ctx->ev.cleanup = NULL; \
} while(0)
static inline void refreshTimeout(redisAsyncContext *ctx) {
#define REDIS_TIMER_ISSET(tvp) \
(tvp && ((tvp)->tv_sec || (tvp)->tv_usec))
#define REDIS_EL_TIMER(ac, tvp) \
if ((ac)->ev.scheduleTimer && REDIS_TIMER_ISSET(tvp)) { \
(ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \
}
if (ctx->c.flags & REDIS_CONNECTED) {
REDIS_EL_TIMER(ctx, ctx->c.command_timeout);
} else {
REDIS_EL_TIMER(ctx, ctx->c.connect_timeout);
}
}
void __redisAsyncDisconnect(redisAsyncContext *ac);
void redisProcessCallbacks(redisAsyncContext *ac);
#endif /* __HIREDIS_ASYNC_PRIVATE_H */

39
dict.c
View File

@ -34,6 +34,7 @@
*/ */
#include "fmacros.h" #include "fmacros.h"
#include "alloc.h"
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <limits.h> #include <limits.h>
@ -71,7 +72,10 @@ static void _dictReset(dict *ht) {
/* Create a new hash table */ /* Create a new hash table */
static dict *dictCreate(dictType *type, void *privDataPtr) { static dict *dictCreate(dictType *type, void *privDataPtr) {
dict *ht = malloc(sizeof(*ht)); dict *ht = hi_malloc(sizeof(*ht));
if (ht == NULL)
return NULL;
_dictInit(ht,type,privDataPtr); _dictInit(ht,type,privDataPtr);
return ht; return ht;
} }
@ -97,7 +101,9 @@ static int dictExpand(dict *ht, unsigned long size) {
_dictInit(&n, ht->type, ht->privdata); _dictInit(&n, ht->type, ht->privdata);
n.size = realsize; n.size = realsize;
n.sizemask = realsize-1; n.sizemask = realsize-1;
n.table = calloc(realsize,sizeof(dictEntry*)); n.table = hi_calloc(realsize,sizeof(dictEntry*));
if (n.table == NULL)
return DICT_ERR;
/* Copy all the elements from the old to the new table: /* Copy all the elements from the old to the new table:
* note that if the old hash table is empty ht->size is zero, * note that if the old hash table is empty ht->size is zero,
@ -124,7 +130,7 @@ static int dictExpand(dict *ht, unsigned long size) {
} }
} }
assert(ht->used == 0); assert(ht->used == 0);
free(ht->table); hi_free(ht->table);
/* Remap the new hashtable in the old */ /* Remap the new hashtable in the old */
*ht = n; *ht = n;
@ -142,7 +148,10 @@ static int dictAdd(dict *ht, void *key, void *val) {
return DICT_ERR; return DICT_ERR;
/* Allocates the memory and stores key */ /* Allocates the memory and stores key */
entry = malloc(sizeof(*entry)); entry = hi_malloc(sizeof(*entry));
if (entry == NULL)
return DICT_ERR;
entry->next = ht->table[index]; entry->next = ht->table[index];
ht->table[index] = entry; ht->table[index] = entry;
@ -166,6 +175,9 @@ static int dictReplace(dict *ht, void *key, void *val) {
return 1; return 1;
/* It already exists, get the entry */ /* It already exists, get the entry */
entry = dictFind(ht, key); entry = dictFind(ht, key);
if (entry == NULL)
return 0;
/* Free the old value and set the new one */ /* Free the old value and set the new one */
/* Set the new value and free the old one. Note that it is important /* 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 * to do that in this order, as the value may just be exactly the same
@ -199,7 +211,7 @@ static int dictDelete(dict *ht, const void *key) {
dictFreeEntryKey(ht,de); dictFreeEntryKey(ht,de);
dictFreeEntryVal(ht,de); dictFreeEntryVal(ht,de);
free(de); hi_free(de);
ht->used--; ht->used--;
return DICT_OK; return DICT_OK;
} }
@ -222,13 +234,13 @@ static int _dictClear(dict *ht) {
nextHe = he->next; nextHe = he->next;
dictFreeEntryKey(ht, he); dictFreeEntryKey(ht, he);
dictFreeEntryVal(ht, he); dictFreeEntryVal(ht, he);
free(he); hi_free(he);
ht->used--; ht->used--;
he = nextHe; he = nextHe;
} }
} }
/* Free the table and the allocated cache structure */ /* Free the table and the allocated cache structure */
free(ht->table); hi_free(ht->table);
/* Re-initialize the table */ /* Re-initialize the table */
_dictReset(ht); _dictReset(ht);
return DICT_OK; /* never fails */ return DICT_OK; /* never fails */
@ -237,7 +249,7 @@ static int _dictClear(dict *ht) {
/* Clear & Release the hash table */ /* Clear & Release the hash table */
static void dictRelease(dict *ht) { static void dictRelease(dict *ht) {
_dictClear(ht); _dictClear(ht);
free(ht); hi_free(ht);
} }
static dictEntry *dictFind(dict *ht, const void *key) { static dictEntry *dictFind(dict *ht, const void *key) {
@ -255,14 +267,11 @@ static dictEntry *dictFind(dict *ht, const void *key) {
return NULL; return NULL;
} }
static dictIterator *dictGetIterator(dict *ht) { static void dictInitIterator(dictIterator *iter, dict *ht) {
dictIterator *iter = malloc(sizeof(*iter));
iter->ht = ht; iter->ht = ht;
iter->index = -1; iter->index = -1;
iter->entry = NULL; iter->entry = NULL;
iter->nextEntry = NULL; iter->nextEntry = NULL;
return iter;
} }
static dictEntry *dictNext(dictIterator *iter) { static dictEntry *dictNext(dictIterator *iter) {
@ -285,16 +294,12 @@ static dictEntry *dictNext(dictIterator *iter) {
return NULL; return NULL;
} }
static void dictReleaseIterator(dictIterator *iter) {
free(iter);
}
/* ------------------------- private functions ------------------------------ */ /* ------------------------- private functions ------------------------------ */
/* Expand the hash table if needed */ /* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *ht) { static int _dictExpandIfNeeded(dict *ht) {
/* If the hash table is empty expand it to the initial size, /* If the hash table is empty expand it to the initial size,
* if the table is "full" dobule its size. */ * if the table is "full" double its size. */
if (ht->size == 0) if (ht->size == 0)
return dictExpand(ht, DICT_HT_INITIAL_SIZE); return dictExpand(ht, DICT_HT_INITIAL_SIZE);
if (ht->used == ht->size) if (ht->used == ht->size)

3
dict.h
View File

@ -119,8 +119,7 @@ static int dictReplace(dict *ht, void *key, void *val);
static int dictDelete(dict *ht, const void *key); static int dictDelete(dict *ht, const void *key);
static void dictRelease(dict *ht); static void dictRelease(dict *ht);
static dictEntry * dictFind(dict *ht, const void *key); static dictEntry * dictFind(dict *ht, const void *key);
static dictIterator *dictGetIterator(dict *ht); static void dictInitIterator(dictIterator *iter, dict *ht);
static dictEntry *dictNext(dictIterator *iter); static dictEntry *dictNext(dictIterator *iter);
static void dictReleaseIterator(dictIterator *iter);
#endif /* __DICT_H */ #endif /* __DICT_H */

View File

@ -21,26 +21,41 @@ ENDIF()
FIND_PATH(LIBEVENT event.h) FIND_PATH(LIBEVENT event.h)
if (LIBEVENT) if (LIBEVENT)
ADD_EXECUTABLE(example-libevent example-libevent) ADD_EXECUTABLE(example-libevent example-libevent.c)
TARGET_LINK_LIBRARIES(example-libevent hiredis event) TARGET_LINK_LIBRARIES(example-libevent hiredis event)
ENDIF() ENDIF()
FIND_PATH(LIBHV hv/hv.h)
IF (LIBHV)
ADD_EXECUTABLE(example-libhv example-libhv.c)
TARGET_LINK_LIBRARIES(example-libhv hiredis hv)
ENDIF()
FIND_PATH(LIBUV uv.h) FIND_PATH(LIBUV uv.h)
IF (LIBUV) IF (LIBUV)
ADD_EXECUTABLE(example-libuv example-libuv.c) ADD_EXECUTABLE(example-libuv example-libuv.c)
TARGET_LINK_LIBRARIES(example-libuv hiredis uv) TARGET_LINK_LIBRARIES(example-libuv hiredis uv)
ENDIF() ENDIF()
FIND_PATH(LIBSDEVENT systemd/sd-event.h)
IF (LIBSDEVENT)
ADD_EXECUTABLE(example-libsdevent example-libsdevent.c)
TARGET_LINK_LIBRARIES(example-libsdevent hiredis systemd)
ENDIF()
IF (APPLE) IF (APPLE)
FIND_LIBRARY(CF CoreFoundation) FIND_LIBRARY(CF CoreFoundation)
ADD_EXECUTABLE(example-macosx example-macosx.c) ADD_EXECUTABLE(example-macosx example-macosx.c)
TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF}) TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF})
ENDIF() ENDIF()
IF (HIREDIS_SSL) IF (ENABLE_SSL)
ADD_EXECUTABLE(example-ssl example-ssl.c) ADD_EXECUTABLE(example-ssl example-ssl.c)
TARGET_LINK_LIBRARIES(example-ssl hiredis) TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl)
ENDIF() ENDIF()
ADD_EXECUTABLE(example example.c) ADD_EXECUTABLE(example example.c)
TARGET_LINK_LIBRARIES(example hiredis) TARGET_LINK_LIBRARIES(example hiredis)
ADD_EXECUTABLE(example-push example-push.c)
TARGET_LINK_LIBRARIES(example-push hiredis)

View File

@ -33,7 +33,9 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
} }
int main (int argc, char **argv) { int main (int argc, char **argv) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif
iv_init(); iv_init();

View File

@ -33,7 +33,9 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
} }
int main (int argc, char **argv) { int main (int argc, char **argv) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) { if (c->err) {

View File

@ -4,6 +4,7 @@
#include <signal.h> #include <signal.h>
#include <hiredis.h> #include <hiredis.h>
#include <hiredis_ssl.h>
#include <async.h> #include <async.h>
#include <adapters/libevent.h> #include <adapters/libevent.h>
@ -33,7 +34,10 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
} }
int main (int argc, char **argv) { int main (int argc, char **argv) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif
struct event_base *base = event_base_new(); struct event_base *base = event_base_new();
if (argc < 5) { if (argc < 5) {
fprintf(stderr, fprintf(stderr,
@ -51,13 +55,25 @@ int main (int argc, char **argv) {
const char *certKey = argv[5]; const char *certKey = argv[5];
const char *caCert = argc > 5 ? argv[6] : NULL; const char *caCert = argc > 5 ? argv[6] : NULL;
redisSSLContext *ssl;
redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;
redisInitOpenSSL();
ssl = redisCreateSSLContext(caCert, NULL,
cert, certKey, NULL, &ssl_error);
if (!ssl) {
printf("Error: %s\n", redisSSLContextGetError(ssl_error));
return 1;
}
redisAsyncContext *c = redisAsyncConnect(hostname, port); redisAsyncContext *c = redisAsyncConnect(hostname, port);
if (c->err) { if (c->err) {
/* Let *c leak for now... */ /* Let *c leak for now... */
printf("Error: %s\n", c->errstr); printf("Error: %s\n", c->errstr);
return 1; return 1;
} }
if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) { if (redisInitiateSSLWithContext(&c->c, ssl) != REDIS_OK) {
printf("SSL Error!\n"); printf("SSL Error!\n");
exit(1); exit(1);
} }
@ -68,5 +84,7 @@ int main (int argc, char **argv) {
redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue); redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
event_base_dispatch(base); event_base_dispatch(base);
redisFreeSSLContext(ssl);
return 0; return 0;
} }

View File

@ -38,13 +38,16 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
} }
int main (int argc, char **argv) { int main (int argc, char **argv) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif
struct event_base *base = event_base_new(); struct event_base *base = event_base_new();
redisOptions options = {0}; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379); REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
struct timeval tv = {0}; struct timeval tv = {0};
tv.tv_sec = 1; tv.tv_sec = 1;
options.timeout = &tv; options.connect_timeout = &tv;
redisAsyncContext *c = redisAsyncConnectWithOptions(&options); redisAsyncContext *c = redisAsyncConnectWithOptions(&options);

70
examples/example-libhv.c Normal file
View File

@ -0,0 +1,70 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/libhv.h>
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 debugCallback(redisAsyncContext *c, void *r, void *privdata) {
(void)privdata;
redisReply *reply = r;
if (reply == NULL) {
printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error");
return;
}
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) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN);
#endif
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
hloop_t* loop = hloop_new(HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS);
redisLibhvAttach(c, loop);
redisAsyncSetTimeout(c, (struct timeval){.tv_sec = 0, .tv_usec = 500000});
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");
redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %d", 1);
hloop_run(loop);
hloop_free(&loop);
return 0;
}

View File

@ -0,0 +1,86 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/libsdevent.h>
void debugCallback(redisAsyncContext *c, void *r, void *privdata) {
(void)privdata;
redisReply *reply = r;
if (reply == NULL) {
/* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */
printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error");
return;
}
/* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/
redisAsyncDisconnect(c);
}
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) {
printf("`GET key` error: %s\n", c->errstr ? c->errstr : "unknown error");
return;
}
printf("`GET key` result: argv[%s]: %s\n", (char*)privdata, reply->str);
/* start another request that demonstrate timeout */
redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5);
}
void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("connect error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("disconnect because of error: %s\n", c->errstr);
return;
}
printf("Disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
struct sd_event *event;
sd_event_default(&event);
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
printf("Error: %s\n", c->errstr);
redisAsyncFree(c);
return 1;
}
redisLibsdeventAttach(c,event);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0});
/*
In this demo, we first `set key`, then `get key` to demonstrate the basic usage of libsdevent adapter.
Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request.
Because we have set a 1 second timeout to the connection, the command will always fail with a
timeout error, which is shown in the `debugCallback`.
*/
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
/* sd-event does not quit when there are no handlers registered. Manually exit after 1.5 seconds */
sd_event_source *s;
sd_event_add_time_relative(event, &s, CLOCK_MONOTONIC, 1500000, 1, NULL, NULL);
sd_event_loop(event);
sd_event_source_disable_unref(s);
sd_event_unref(event);
return 0;
}

View File

@ -7,18 +7,33 @@
#include <async.h> #include <async.h>
#include <adapters/libuv.h> #include <adapters/libuv.h>
void debugCallback(redisAsyncContext *c, void *r, void *privdata) {
(void)privdata; //unused
redisReply *reply = r;
if (reply == NULL) {
/* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */
printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error");
return;
}
/* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/
redisAsyncDisconnect(c);
}
void getCallback(redisAsyncContext *c, void *r, void *privdata) { void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r; redisReply *reply = r;
if (reply == NULL) return; if (reply == NULL) {
printf("argv[%s]: %s\n", (char*)privdata, reply->str); printf("`GET key` error: %s\n", c->errstr ? c->errstr : "unknown error");
return;
}
printf("`GET key` result: argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */ /* start another request that demonstrate timeout */
redisAsyncDisconnect(c); redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5);
} }
void connectCallback(const redisAsyncContext *c, int status) { void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) { if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr); printf("connect error: %s\n", c->errstr);
return; return;
} }
printf("Connected...\n"); printf("Connected...\n");
@ -26,14 +41,17 @@ void connectCallback(const redisAsyncContext *c, int status) {
void disconnectCallback(const redisAsyncContext *c, int status) { void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) { if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr); printf("disconnect because of error: %s\n", c->errstr);
return; return;
} }
printf("Disconnected...\n"); printf("Disconnected...\n");
} }
int main (int argc, char **argv) { int main (int argc, char **argv) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif
uv_loop_t* loop = uv_default_loop(); uv_loop_t* loop = uv_default_loop();
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
@ -46,8 +64,18 @@ int main (int argc, char **argv) {
redisLibuvAttach(c,loop); redisLibuvAttach(c,loop);
redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0});
/*
In this demo, we first `set key`, then `get key` to demonstrate the basic usage of libuv adapter.
Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request.
Because we have set a 1 second timeout to the connection, the command will always fail with a
timeout error, which is shown in the `debugCallback`.
*/
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
uv_run(loop, UV_RUN_DEFAULT); uv_run(loop, UV_RUN_DEFAULT);
return 0; return 0;
} }

View File

@ -1,7 +1,35 @@
// /*
// Created by Дмитрий Бахвалов on 13.07.15. * Copyright (c) 2015 Дмитрий Бахвалов (Dmitry Bakhvalov)
// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. *
// * Permission for license update:
* https://github.com/redis/hiredis/issues/1271#issuecomment-2258225227
*
* 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 <stdio.h> #include <stdio.h>

62
examples/example-poll.c Normal file
View File

@ -0,0 +1,62 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <async.h>
#include <adapters/poll.h>
/* Put in the global scope, so that loop can be explicitly stopped */
static int exit_loop = 0;
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);
exit_loop = 1;
return;
}
printf("Connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
exit_loop = 1;
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;
}
redisPollAttach(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");
while (!exit_loop)
{
redisPollTick(c, 0.1);
}
return 0;
}

159
examples/example-push.c Normal file
View File

@ -0,0 +1,159 @@
/*
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis.h>
#define KEY_COUNT 5
#define panicAbort(fmt, ...) \
do { \
fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); \
exit(-1); \
} while (0)
static void assertReplyAndFree(redisContext *context, redisReply *reply, int type) {
if (reply == NULL)
panicAbort("NULL reply from server (error: %s)", context->errstr);
if (reply->type != type) {
if (reply->type == REDIS_REPLY_ERROR)
fprintf(stderr, "Redis Error: %s\n", reply->str);
panicAbort("Expected reply type %d but got type %d", type, reply->type);
}
freeReplyObject(reply);
}
/* Switch to the RESP3 protocol and enable client tracking */
static void enableClientTracking(redisContext *c) {
redisReply *reply = redisCommand(c, "HELLO 3");
if (reply == NULL || c->err) {
panicAbort("NULL reply or server error (error: %s)", c->errstr);
}
if (reply->type != REDIS_REPLY_MAP) {
fprintf(stderr, "Error: Can't send HELLO 3 command. Are you sure you're ");
fprintf(stderr, "connected to redis-server >= 6.0.0?\nRedis error: %s\n",
reply->type == REDIS_REPLY_ERROR ? reply->str : "(unknown)");
exit(-1);
}
freeReplyObject(reply);
/* Enable client tracking */
reply = redisCommand(c, "CLIENT TRACKING ON");
assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);
}
void pushReplyHandler(void *privdata, void *r) {
redisReply *reply = r;
int *invalidations = privdata;
/* Sanity check on the invalidation reply */
if (reply->type != REDIS_REPLY_PUSH || reply->elements != 2 ||
reply->element[1]->type != REDIS_REPLY_ARRAY ||
reply->element[1]->element[0]->type != REDIS_REPLY_STRING)
{
panicAbort("%s", "Can't parse PUSH message!");
}
/* Increment our invalidation count */
*invalidations += 1;
printf("pushReplyHandler(): INVALIDATE '%s' (invalidation count: %d)\n",
reply->element[1]->element[0]->str, *invalidations);
freeReplyObject(reply);
}
/* We aren't actually freeing anything here, but it is included to show that we can
* have hiredis call our data destructor when freeing the context */
void privdata_dtor(void *privdata) {
unsigned int *icount = privdata;
printf("privdata_dtor(): In context privdata dtor (invalidations: %u)\n", *icount);
}
int main(int argc, char **argv) {
unsigned int j, invalidations = 0;
redisContext *c;
redisReply *reply;
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
int port = (argc > 2) ? atoi(argv[2]) : 6379;
redisOptions o = {0};
REDIS_OPTIONS_SET_TCP(&o, hostname, port);
/* Set our context privdata to the address of our invalidation counter. Each
* time our PUSH handler is called, hiredis will pass the privdata for context.
*
* This could also be done after we create the context like so:
*
* c->privdata = &invalidations;
* c->free_privdata = privdata_dtor;
*/
REDIS_OPTIONS_SET_PRIVDATA(&o, &invalidations, privdata_dtor);
/* Set our custom PUSH message handler */
o.push_cb = pushReplyHandler;
c = redisConnectWithOptions(&o);
if (c == NULL || c->err)
panicAbort("Connection error: %s", c ? c->errstr : "OOM");
/* Enable RESP3 and turn on client tracking */
enableClientTracking(c);
/* Set some keys and then read them back. Once we do that, Redis will deliver
* invalidation push messages whenever the key is modified */
for (j = 0; j < KEY_COUNT; j++) {
reply = redisCommand(c, "SET key:%d initial:%d", j, j);
assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);
reply = redisCommand(c, "GET key:%d", j);
assertReplyAndFree(c, reply, REDIS_REPLY_STRING);
}
/* Trigger invalidation messages by updating keys we just read */
for (j = 0; j < KEY_COUNT; j++) {
printf(" main(): SET key:%d update:%d\n", j, j);
reply = redisCommand(c, "SET key:%d update:%d", j, j);
assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);
printf(" main(): SET REPLY OK\n");
}
printf("\nTotal detected invalidations: %d, expected: %d\n", invalidations, KEY_COUNT);
/* PING server */
redisFree(c);
}

View File

@ -0,0 +1,101 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/redismoduleapi.h>
void debugCallback(redisAsyncContext *c, void *r, void *privdata) {
(void)privdata; //unused
redisReply *reply = r;
if (reply == NULL) {
/* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */
printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error");
return;
}
/* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/
redisAsyncDisconnect(c);
}
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) {
if (c->errstr) {
printf("errstr: %s\n", c->errstr);
}
return;
}
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* start another request that demonstrate timeout */
redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5);
}
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");
}
/*
* This example requires Redis 7.0 or above.
*
* 1- Compile this file as a shared library. Directory of "redismodule.h" must
* be in the include path.
* gcc -fPIC -shared -I../../redis/src/ -I.. example-redismoduleapi.c -o example-redismoduleapi.so
*
* 2- Load module:
* redis-server --loadmodule ./example-redismoduleapi.so value
*/
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
int ret = RedisModule_Init(ctx, "example-redismoduleapi", 1, REDISMODULE_APIVER_1);
if (ret != REDISMODULE_OK) {
printf("error module init \n");
return REDISMODULE_ERR;
}
if (redisModuleCompatibilityCheck() != REDIS_OK) {
printf("Redis 7.0 or above is required! \n");
return REDISMODULE_ERR;
}
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
size_t len;
const char *val = RedisModule_StringPtrLen(argv[argc-1], &len);
RedisModuleCtx *module_ctx = RedisModule_GetDetachedThreadSafeContext(ctx);
redisModuleAttach(c, module_ctx);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0});
/*
In this demo, we first `set key`, then `get key` to demonstrate the basic usage of the adapter.
Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request.
Because we have set a 1 second timeout to the connection, the command will always fail with a
timeout error, which is shown in the `debugCallback`.
*/
redisAsyncCommand(c, NULL, NULL, "SET key %b", val, len);
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
return 0;
}

View File

@ -3,9 +3,16 @@
#include <string.h> #include <string.h>
#include <hiredis.h> #include <hiredis.h>
#include <hiredis_ssl.h>
#ifdef _MSC_VER
#include <winsock2.h> /* For struct timeval */
#endif
int main(int argc, char **argv) { int main(int argc, char **argv) {
unsigned int j; unsigned int j;
redisSSLContext *ssl;
redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;
redisContext *c; redisContext *c;
redisReply *reply; redisReply *reply;
if (argc < 4) { if (argc < 4) {
@ -18,10 +25,17 @@ int main(int argc, char **argv) {
const char *key = argv[4]; const char *key = argv[4];
const char *ca = argc > 4 ? argv[5] : NULL; const char *ca = argc > 4 ? argv[5] : NULL;
redisInitOpenSSL();
ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error);
if (!ssl || ssl_error != REDIS_SSL_CTX_NONE) {
printf("SSL Context error: %s\n", redisSSLContextGetError(ssl_error));
exit(1);
}
struct timeval tv = { 1, 500000 }; // 1.5 seconds struct timeval tv = { 1, 500000 }; // 1.5 seconds
redisOptions options = {0}; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, hostname, port); REDIS_OPTIONS_SET_TCP(&options, hostname, port);
options.timeout = &tv; options.connect_timeout = &tv;
c = redisConnectWithOptions(&options); c = redisConnectWithOptions(&options);
if (c == NULL || c->err) { if (c == NULL || c->err) {
@ -34,7 +48,7 @@ int main(int argc, char **argv) {
exit(1); exit(1);
} }
if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) { if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) {
printf("Couldn't initialize SSL!\n"); printf("Couldn't initialize SSL!\n");
printf("Error: %s\n", c->errstr); printf("Error: %s\n", c->errstr);
redisFree(c); redisFree(c);
@ -92,5 +106,7 @@ int main(int argc, char **argv) {
/* Disconnects and frees the context */ /* Disconnects and frees the context */
redisFree(c); redisFree(c);
redisFreeSSLContext(ssl);
return 0; return 0;
} }

View File

@ -1,9 +1,60 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <hiredis.h> #include <hiredis.h>
#ifdef _MSC_VER
#include <winsock2.h> /* For struct timeval */
#endif
static void example_argv_command(redisContext *c, size_t n) {
char **argv, tmp[42];
size_t *argvlen;
redisReply *reply;
/* We're allocating two additional elements for command and key */
argv = malloc(sizeof(*argv) * (2 + n));
argvlen = malloc(sizeof(*argvlen) * (2 + n));
/* First the command */
argv[0] = (char*)"RPUSH";
argvlen[0] = sizeof("RPUSH") - 1;
/* Now our key */
argv[1] = (char*)"argvlist";
argvlen[1] = sizeof("argvlist") - 1;
/* Now add the entries we wish to add to the list */
for (size_t i = 2; i < (n + 2); i++) {
argvlen[i] = snprintf(tmp, sizeof(tmp), "argv-element-%zu", i - 2);
argv[i] = strdup(tmp);
}
/* Execute the command using redisCommandArgv. We're sending the arguments with
* two explicit arrays. One for each argument's string, and the other for its
* length. */
reply = redisCommandArgv(c, n + 2, (const char **)argv, (const size_t*)argvlen);
if (reply == NULL || c->err) {
fprintf(stderr, "Error: Couldn't execute redisCommandArgv\n");
exit(1);
}
if (reply->type == REDIS_REPLY_INTEGER) {
printf("%s reply: %lld\n", argv[0], reply->integer);
}
freeReplyObject(reply);
/* Clean up */
for (size_t i = 2; i < (n + 2); i++) {
free(argv[i]);
}
free(argv);
free(argvlen);
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
unsigned int j, isunix = 0; unsigned int j, isunix = 0;
redisContext *c; redisContext *c;
@ -84,6 +135,9 @@ int main(int argc, char **argv) {
} }
freeReplyObject(reply); freeReplyObject(reply);
/* See function for an example of redisCommandArgv */
example_argv_command(c, 10);
/* Disconnects and frees the context */ /* Disconnects and frees the context */
redisFree(c); redisFree(c);

View File

@ -1,8 +1,10 @@
#ifndef __HIREDIS_FMACRO_H #ifndef __HIREDIS_FMACRO_H
#define __HIREDIS_FMACRO_H #define __HIREDIS_FMACRO_H
#ifndef _AIX
#define _XOPEN_SOURCE 600 #define _XOPEN_SOURCE 600
#define _POSIX_C_SOURCE 200112L #define _POSIX_C_SOURCE 200112L
#endif
#if defined(__APPLE__) && defined(__MACH__) #if defined(__APPLE__) && defined(__MACH__)
/* Enable TCP_KEEPALIVE */ /* Enable TCP_KEEPALIVE */

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2020, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2020, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2020, Matt Stancliff <matt at genges dot com>,
* Jan-Erik Rediger <janerik at fnordig dot com>
*
* 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 <stdlib.h>
#include <string.h>
#include "hiredis.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char *new_str, *cmd;
if (size < 3)
return 0;
new_str = malloc(size+1);
if (new_str == NULL)
return 0;
memcpy(new_str, data, size);
new_str[size] = '\0';
if (redisFormatCommand(&cmd, new_str) != -1)
hi_free(cmd);
free(new_str);
return 0;
}

13
hiredis-config.cmake.in Normal file
View File

@ -0,0 +1,13 @@
@PACKAGE_INIT@
set_and_check(hiredis_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
IF (NOT TARGET hiredis::@hiredis_export_name@)
INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis-targets.cmake)
ENDIF()
SET(hiredis_LIBRARIES hiredis::@hiredis_export_name@)
SET(hiredis_INCLUDE_DIRS ${hiredis_INCLUDEDIR})
check_required_components(hiredis)

443
hiredis.c
View File

@ -41,14 +41,28 @@
#include "hiredis.h" #include "hiredis.h"
#include "net.h" #include "net.h"
#include "sds.h" #include "sds.h"
#include "sslio.h" #include "async.h"
#include "win32.h" #include "win32.h"
extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout);
extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);
static redisContextFuncs redisContextDefaultFuncs = {
.close = redisNetClose,
.free_privctx = NULL,
.async_read = redisAsyncRead,
.async_write = redisAsyncWrite,
.read = redisNetRead,
.write = redisNetWrite
};
static redisReply *createReplyObject(int type); static redisReply *createReplyObject(int type);
static void *createStringObject(const redisReadTask *task, char *str, size_t len); static void *createStringObject(const redisReadTask *task, char *str, size_t len);
static void *createArrayObject(const redisReadTask *task, int elements); static void *createArrayObject(const redisReadTask *task, size_t elements);
static void *createIntegerObject(const redisReadTask *task, long long value); static void *createIntegerObject(const redisReadTask *task, long long value);
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
static void *createNilObject(const redisReadTask *task); static void *createNilObject(const redisReadTask *task);
static void *createBoolObject(const redisReadTask *task, int bval);
/* Default set of functions to build the reply. Keep in mind that such a /* Default set of functions to build the reply. Keep in mind that such a
* function returning NULL is interpreted as OOM. */ * function returning NULL is interpreted as OOM. */
@ -56,13 +70,15 @@ static redisReplyObjectFunctions defaultFunctions = {
createStringObject, createStringObject,
createArrayObject, createArrayObject,
createIntegerObject, createIntegerObject,
createDoubleObject,
createNilObject, createNilObject,
createBoolObject,
freeReplyObject freeReplyObject
}; };
/* Create a reply object */ /* Create a reply object */
static redisReply *createReplyObject(int type) { static redisReply *createReplyObject(int type) {
redisReply *r = calloc(1,sizeof(*r)); redisReply *r = hi_calloc(1,sizeof(*r));
if (r == NULL) if (r == NULL)
return NULL; return NULL;
@ -81,21 +97,30 @@ void freeReplyObject(void *reply) {
switch(r->type) { switch(r->type) {
case REDIS_REPLY_INTEGER: case REDIS_REPLY_INTEGER:
case REDIS_REPLY_NIL:
case REDIS_REPLY_BOOL:
break; /* Nothing to free */ break; /* Nothing to free */
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
case REDIS_REPLY_MAP:
case REDIS_REPLY_ATTR:
case REDIS_REPLY_SET:
case REDIS_REPLY_PUSH:
if (r->element != NULL) { if (r->element != NULL) {
for (j = 0; j < r->elements; j++) for (j = 0; j < r->elements; j++)
freeReplyObject(r->element[j]); freeReplyObject(r->element[j]);
free(r->element); hi_free(r->element);
} }
break; break;
case REDIS_REPLY_ERROR: case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS: case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
free(r->str); case REDIS_REPLY_DOUBLE:
case REDIS_REPLY_VERB:
case REDIS_REPLY_BIGNUM:
hi_free(r->str);
break; break;
} }
free(r); hi_free(r);
} }
static void *createStringObject(const redisReadTask *task, char *str, size_t len) { static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
@ -106,39 +131,57 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
if (r == NULL) if (r == NULL)
return NULL; return NULL;
buf = malloc(len+1); assert(task->type == REDIS_REPLY_ERROR ||
if (buf == NULL) { task->type == REDIS_REPLY_STATUS ||
task->type == REDIS_REPLY_STRING ||
task->type == REDIS_REPLY_VERB ||
task->type == REDIS_REPLY_BIGNUM);
/* Copy string value */
if (task->type == REDIS_REPLY_VERB) {
buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
if (buf == NULL) goto oom;
memcpy(r->vtype,str,3);
r->vtype[3] = '\0';
memcpy(buf,str+4,len-4);
buf[len-4] = '\0';
r->len = len - 4;
} else {
buf = hi_malloc(len+1);
if (buf == NULL) goto oom;
memcpy(buf,str,len);
buf[len] = '\0';
r->len = len;
}
r->str = buf;
if (task->parent) {
parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_ATTR ||
parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r;
}
return r;
oom:
freeReplyObject(r); freeReplyObject(r);
return NULL; return NULL;
} }
assert(task->type == REDIS_REPLY_ERROR || static void *createArrayObject(const redisReadTask *task, size_t elements) {
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; redisReply *r, *parent;
r = createReplyObject(REDIS_REPLY_ARRAY); r = createReplyObject(task->type);
if (r == NULL) if (r == NULL)
return NULL; return NULL;
if (elements > 0) { if (elements > 0) {
r->element = calloc(elements,sizeof(redisReply*)); r->element = hi_calloc(elements,sizeof(redisReply*));
if (r->element == NULL) { if (r->element == NULL) {
freeReplyObject(r); freeReplyObject(r);
return NULL; return NULL;
@ -149,7 +192,11 @@ static void *createArrayObject(const redisReadTask *task, int elements) {
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY); assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_ATTR ||
parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
@ -166,7 +213,49 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY); assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_ATTR ||
parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r;
}
return r;
}
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {
redisReply *r, *parent;
if (len == SIZE_MAX) // Prevents hi_malloc(0) if len equals to SIZE_MAX
return NULL;
r = createReplyObject(REDIS_REPLY_DOUBLE);
if (r == NULL)
return NULL;
r->dval = value;
r->str = hi_malloc(len+1);
if (r->str == NULL) {
freeReplyObject(r);
return NULL;
}
/* The double reply also has the original protocol string representing a
* double as a null terminated string. This way the caller does not need
* to format back for string conversion, especially since Redis does efforts
* to make the string more human readable avoiding the calssical double
* decimal string conversion artifacts. */
memcpy(r->str, str, len);
r->str[len] = '\0';
r->len = len;
if (task->parent) {
parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_ATTR ||
parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
@ -181,7 +270,32 @@ static void *createNilObject(const redisReadTask *task) {
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY); assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_ATTR ||
parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r;
}
return r;
}
static void *createBoolObject(const redisReadTask *task, int bval) {
redisReply *r, *parent;
r = createReplyObject(REDIS_REPLY_BOOL);
if (r == NULL)
return NULL;
r->integer = bval != 0;
if (task->parent) {
parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_ATTR ||
parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
@ -231,7 +345,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
if (*c != '%' || c[1] == '\0') { if (*c != '%' || c[1] == '\0') {
if (*c == ' ') { if (*c == ' ') {
if (touched) { if (touched) {
newargv = realloc(curargv,sizeof(char*)*(argc+1)); newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
if (newargv == NULL) goto memory_err; if (newargv == NULL) goto memory_err;
curargv = newargv; curargv = newargv;
curargv[argc++] = curarg; curargv[argc++] = curarg;
@ -285,17 +399,22 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++;
/* Field width */ /* Field width */
while (*_p != '\0' && isdigit(*_p)) _p++; while (*_p != '\0' && isdigit((int) *_p)) _p++;
/* Precision */ /* Precision */
if (*_p == '.') { if (*_p == '.') {
_p++; _p++;
while (*_p != '\0' && isdigit(*_p)) _p++; while (*_p != '\0' && isdigit((int) *_p)) _p++;
} }
/* Copy va_list before consuming with va_arg */ /* Copy va_list before consuming with va_arg */
va_copy(_cpy,ap); va_copy(_cpy,ap);
/* Make sure we have more characters otherwise strchr() accepts
* '\0' as an integer specifier. This is checked after above
* va_copy() to avoid UB in fmt_invalid's call to va_end(). */
if (*_p == '\0') goto fmt_invalid;
/* Integer conversion (without modifiers) */ /* Integer conversion (without modifiers) */
if (strchr(intfmts,*_p) != NULL) { if (strchr(intfmts,*_p) != NULL) {
va_arg(ap,int); va_arg(ap,int);
@ -374,13 +493,15 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
touched = 1; touched = 1;
c++; c++;
if (*c == '\0')
break;
} }
c++; c++;
} }
/* Add the last argument if needed */ /* Add the last argument if needed */
if (touched) { if (touched) {
newargv = realloc(curargv,sizeof(char*)*(argc+1)); newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
if (newargv == NULL) goto memory_err; if (newargv == NULL) goto memory_err;
curargv = newargv; curargv = newargv;
curargv[argc++] = curarg; curargv[argc++] = curarg;
@ -396,7 +517,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
totlen += 1+countDigits(argc)+2; totlen += 1+countDigits(argc)+2;
/* Build the command at protocol level */ /* Build the command at protocol level */
cmd = malloc(totlen+1); cmd = hi_malloc(totlen+1);
if (cmd == NULL) goto memory_err; if (cmd == NULL) goto memory_err;
pos = sprintf(cmd,"*%d\r\n",argc); pos = sprintf(cmd,"*%d\r\n",argc);
@ -411,7 +532,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
assert(pos == totlen); assert(pos == totlen);
cmd[pos] = '\0'; cmd[pos] = '\0';
free(curargv); hi_free(curargv);
*target = cmd; *target = cmd;
return totlen; return totlen;
@ -427,11 +548,11 @@ cleanup:
if (curargv) { if (curargv) {
while(argc--) while(argc--)
sdsfree(curargv[argc]); sdsfree(curargv[argc]);
free(curargv); hi_free(curargv);
} }
sdsfree(curarg); sdsfree(curarg);
free(cmd); hi_free(cmd);
return error_type; return error_type;
} }
@ -469,13 +590,12 @@ int redisFormatCommand(char **target, const char *format, ...) {
* lengths. If the latter is set to NULL, strlen will be used to compute the * lengths. If the latter is set to NULL, strlen will be used to compute the
* argument lengths. * argument lengths.
*/ */
int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, long long redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
const size_t *argvlen) const size_t *argvlen)
{ {
sds cmd; sds cmd, aux;
unsigned long long totlen; unsigned long long totlen, len;
int j; int j;
size_t len;
/* Abort on a NULL target */ /* Abort on a NULL target */
if (target == NULL) if (target == NULL)
@ -494,15 +614,19 @@ int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
return -1; return -1;
/* We already know how much storage we need */ /* We already know how much storage we need */
cmd = sdsMakeRoomFor(cmd, totlen); aux = sdsMakeRoomFor(cmd, totlen);
if (cmd == NULL) if (aux == NULL) {
sdsfree(cmd);
return -1; return -1;
}
cmd = aux;
/* Construct command */ /* Construct command */
cmd = sdscatfmt(cmd, "*%i\r\n", argc); cmd = sdscatfmt(cmd, "*%i\r\n", argc);
for (j=0; j < argc; j++) { for (j=0; j < argc; j++) {
len = argvlen ? argvlen[j] : strlen(argv[j]); len = argvlen ? argvlen[j] : strlen(argv[j]);
cmd = sdscatfmt(cmd, "$%u\r\n", len); cmd = sdscatfmt(cmd, "$%U\r\n", len);
cmd = sdscatlen(cmd, argv[j], len); cmd = sdscatlen(cmd, argv[j], len);
cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1);
} }
@ -522,11 +646,11 @@ void redisFreeSdsCommand(sds cmd) {
* lengths. If the latter is set to NULL, strlen will be used to compute the * lengths. If the latter is set to NULL, strlen will be used to compute the
* argument lengths. * argument lengths.
*/ */
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) {
char *cmd = NULL; /* final command */ char *cmd = NULL; /* final command */
int pos; /* position in final command */ size_t pos; /* position in final command */
size_t len; size_t len, totlen;
int totlen, j; int j;
/* Abort on a NULL target */ /* Abort on a NULL target */
if (target == NULL) if (target == NULL)
@ -540,7 +664,7 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
} }
/* Build the command at protocol level */ /* Build the command at protocol level */
cmd = malloc(totlen+1); cmd = hi_malloc(totlen+1);
if (cmd == NULL) if (cmd == NULL)
return -1; return -1;
@ -561,7 +685,7 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
} }
void redisFreeCommand(char *cmd) { void redisFreeCommand(char *cmd) {
free(cmd); hi_free(cmd);
} }
void __redisSetError(redisContext *c, int type, const char *str) { void __redisSetError(redisContext *c, int type, const char *str) {
@ -584,13 +708,20 @@ redisReader *redisReaderCreate(void) {
return redisReaderCreateWithFunctions(&defaultFunctions); return redisReaderCreateWithFunctions(&defaultFunctions);
} }
static redisContext *redisContextInit(const redisOptions *options) { static void redisPushAutoFree(void *privdata, void *reply) {
(void)privdata;
freeReplyObject(reply);
}
static redisContext *redisContextInit(void) {
redisContext *c; redisContext *c;
c = calloc(1, sizeof(*c)); c = hi_calloc(1, sizeof(*c));
if (c == NULL) if (c == NULL)
return NULL; return NULL;
c->funcs = &redisContextDefaultFuncs;
c->obuf = sdsempty(); c->obuf = sdsempty();
c->reader = redisReaderCreate(); c->reader = redisReaderCreate();
c->fd = REDIS_INVALID_FD; c->fd = REDIS_INVALID_FD;
@ -599,27 +730,35 @@ static redisContext *redisContextInit(const redisOptions *options) {
redisFree(c); redisFree(c);
return NULL; return NULL;
} }
(void)options; /* options are used in other functions */
return c; return c;
} }
void redisFree(redisContext *c) { void redisFree(redisContext *c) {
if (c == NULL) if (c == NULL)
return; return;
redisNetClose(c);
if (c->funcs && c->funcs->close) {
c->funcs->close(c);
}
sdsfree(c->obuf); sdsfree(c->obuf);
redisReaderFree(c->reader); redisReaderFree(c->reader);
free(c->tcp.host); hi_free(c->tcp.host);
free(c->tcp.source_addr); hi_free(c->tcp.source_addr);
free(c->unix_sock.path); hi_free(c->unix_sock.path);
free(c->timeout); hi_free(c->connect_timeout);
free(c->saddr); hi_free(c->command_timeout);
if (c->ssl) { hi_free(c->saddr);
redisFreeSsl(c->ssl);
} if (c->privdata && c->free_privdata)
c->free_privdata(c->privdata);
if (c->funcs && c->funcs->free_privctx)
c->funcs->free_privctx(c->privctx);
memset(c, 0xff, sizeof(*c)); memset(c, 0xff, sizeof(*c));
free(c); hi_free(c);
} }
redisFD redisFreeKeepFd(redisContext *c) { redisFD redisFreeKeepFd(redisContext *c) {
@ -633,7 +772,14 @@ int redisReconnect(redisContext *c) {
c->err = 0; c->err = 0;
memset(c->errstr, '\0', strlen(c->errstr)); memset(c->errstr, '\0', strlen(c->errstr));
redisNetClose(c); if (c->privctx && c->funcs->free_privctx) {
c->funcs->free_privctx(c->privctx);
c->privctx = NULL;
}
if (c->funcs && c->funcs->close) {
c->funcs->close(c);
}
sdsfree(c->obuf); sdsfree(c->obuf);
redisReaderFree(c->reader); redisReaderFree(c->reader);
@ -641,22 +787,33 @@ int redisReconnect(redisContext *c) {
c->obuf = sdsempty(); c->obuf = sdsempty();
c->reader = redisReaderCreate(); c->reader = redisReaderCreate();
if (c->obuf == NULL || c->reader == NULL) {
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR;
}
int ret = REDIS_ERR;
if (c->connection_type == REDIS_CONN_TCP) { if (c->connection_type == REDIS_CONN_TCP) {
return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, ret = redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
c->timeout, c->tcp.source_addr); c->connect_timeout, c->tcp.source_addr);
} else if (c->connection_type == REDIS_CONN_UNIX) { } else if (c->connection_type == REDIS_CONN_UNIX) {
return redisContextConnectUnix(c, c->unix_sock.path, c->timeout); ret = redisContextConnectUnix(c, c->unix_sock.path, c->connect_timeout);
} else { } else {
/* Something bad happened here and shouldn't have. There isn't /* Something bad happened here and shouldn't have. There isn't
enough information in the context to reconnect. */ enough information in the context to reconnect. */
__redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect");
ret = REDIS_ERR;
} }
return REDIS_ERR; if (c->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
redisContextSetTimeout(c, *c->command_timeout);
}
return ret;
} }
redisContext *redisConnectWithOptions(const redisOptions *options) { redisContext *redisConnectWithOptions(const redisOptions *options) {
redisContext *c = redisContextInit(options); redisContext *c = redisContextInit();
if (c == NULL) { if (c == NULL) {
return NULL; return NULL;
} }
@ -669,24 +826,53 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
if (options->options & REDIS_OPT_NOAUTOFREE) { if (options->options & REDIS_OPT_NOAUTOFREE) {
c->flags |= REDIS_NO_AUTO_FREE; c->flags |= REDIS_NO_AUTO_FREE;
} }
if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) {
c->flags |= REDIS_NO_AUTO_FREE_REPLIES;
}
if (options->options & REDIS_OPT_PREFER_IPV4) {
c->flags |= REDIS_PREFER_IPV4;
}
if (options->options & REDIS_OPT_PREFER_IPV6) {
c->flags |= REDIS_PREFER_IPV6;
}
/* Set any user supplied RESP3 PUSH handler or use freeReplyObject
* as a default unless specifically flagged that we don't want one. */
if (options->push_cb != NULL)
redisSetPushCallback(c, options->push_cb);
else if (!(options->options & REDIS_OPT_NO_PUSH_AUTOFREE))
redisSetPushCallback(c, redisPushAutoFree);
c->privdata = options->privdata;
c->free_privdata = options->free_privdata;
if (redisContextUpdateConnectTimeout(c, options->connect_timeout) != REDIS_OK ||
redisContextUpdateCommandTimeout(c, options->command_timeout) != REDIS_OK) {
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return c;
}
if (options->type == REDIS_CONN_TCP) { if (options->type == REDIS_CONN_TCP) {
redisContextConnectBindTcp(c, options->endpoint.tcp.ip, redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
options->endpoint.tcp.port, options->timeout, options->endpoint.tcp.port, options->connect_timeout,
options->endpoint.tcp.source_addr); options->endpoint.tcp.source_addr);
} else if (options->type == REDIS_CONN_UNIX) { } else if (options->type == REDIS_CONN_UNIX) {
redisContextConnectUnix(c, options->endpoint.unix_socket, redisContextConnectUnix(c, options->endpoint.unix_socket,
options->timeout); options->connect_timeout);
} else if (options->type == REDIS_CONN_USERFD) { } else if (options->type == REDIS_CONN_USERFD) {
c->fd = options->endpoint.fd; c->fd = options->endpoint.fd;
c->flags |= REDIS_CONNECTED; c->flags |= REDIS_CONNECTED;
} else { } else {
// Unknown type - FIXME - FREE redisFree(c);
return NULL; return NULL;
} }
if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
redisContextSetTimeout(c, *options->timeout); if (c->err == 0 && c->fd != REDIS_INVALID_FD &&
options->command_timeout != NULL && (c->flags & REDIS_BLOCK))
{
redisContextSetTimeout(c, *options->command_timeout);
} }
return c; return c;
} }
@ -702,7 +888,7 @@ redisContext *redisConnect(const char *ip, int port) {
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
redisOptions options = {0}; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, ip, port); REDIS_OPTIONS_SET_TCP(&options, ip, port);
options.timeout = &tv; options.connect_timeout = &tv;
return redisConnectWithOptions(&options); return redisConnectWithOptions(&options);
} }
@ -740,7 +926,7 @@ redisContext *redisConnectUnix(const char *path) {
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
redisOptions options = {0}; redisOptions options = {0};
REDIS_OPTIONS_SET_UNIX(&options, path); REDIS_OPTIONS_SET_UNIX(&options, path);
options.timeout = &tv; options.connect_timeout = &tv;
return redisConnectWithOptions(&options); return redisConnectWithOptions(&options);
} }
@ -758,11 +944,6 @@ redisContext *redisConnectFd(redisFD fd) {
return redisConnectWithOptions(&options); return redisConnectWithOptions(&options);
} }
int redisSecureConnection(redisContext *c, const char *caPath,
const char *certPath, const char *keyPath, const char *servername) {
return redisSslCreate(c, caPath, certPath, keyPath, servername);
}
/* Set read/write timeout on a blocking socket. */ /* Set read/write timeout on a blocking socket. */
int redisSetTimeout(redisContext *c, const struct timeval tv) { int redisSetTimeout(redisContext *c, const struct timeval tv) {
if (c->flags & REDIS_BLOCK) if (c->flags & REDIS_BLOCK)
@ -770,11 +951,25 @@ int redisSetTimeout(redisContext *c, const struct timeval tv) {
return REDIS_ERR; return REDIS_ERR;
} }
int redisEnableKeepAliveWithInterval(redisContext *c, int interval) {
return redisKeepAlive(c, interval);
}
/* Enable connection KeepAlive. */ /* Enable connection KeepAlive. */
int redisEnableKeepAlive(redisContext *c) { int redisEnableKeepAlive(redisContext *c) {
if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) return redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL);
return REDIS_ERR; }
return REDIS_OK;
/* Set the socket option TCP_USER_TIMEOUT. */
int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout) {
return redisContextSetTcpUserTimeout(c, timeout);
}
/* Set a user provided RESP3 PUSH handler and return any old one set. */
redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn) {
redisPushFn *old = c->push_cb;
c->push_cb = fn;
return old;
} }
/* Use this function to handle a read event on the descriptor. It will try /* Use this function to handle a read event on the descriptor. It will try
@ -790,15 +985,12 @@ int redisBufferRead(redisContext *c) {
if (c->err) if (c->err)
return REDIS_ERR; return REDIS_ERR;
nread = c->flags & REDIS_SSL ? nread = c->funcs->read(c, buf, sizeof(buf));
redisSslRead(c, buf, sizeof(buf)) : redisNetRead(c, buf, sizeof(buf)); if (nread < 0) {
if (nread > 0) {
if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
__redisSetError(c, c->reader->err, c->reader->errstr);
return REDIS_ERR; return REDIS_ERR;
} else {
} }
} else if (nread < 0) { if (nread > 0 && redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
__redisSetError(c, c->reader->err, c->reader->errstr);
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
@ -810,8 +1002,8 @@ int redisBufferRead(redisContext *c) {
* successfully written to the socket. When the buffer is empty after the * successfully written to the socket. When the buffer is empty after the
* write operation, "done" is set to 1 (if given). * write operation, "done" is set to 1 (if given).
* *
* Returns REDIS_ERR if an error occurred trying to write and sets * Returns REDIS_ERR if an unrecoverable error occurred in the underlying
* c->errstr to hold the appropriate error string. * c->funcs->write function.
*/ */
int redisBufferWrite(redisContext *c, int *done) { int redisBufferWrite(redisContext *c, int *done) {
@ -820,29 +1012,58 @@ int redisBufferWrite(redisContext *c, int *done) {
return REDIS_ERR; return REDIS_ERR;
if (sdslen(c->obuf) > 0) { if (sdslen(c->obuf) > 0) {
int nwritten = (c->flags & REDIS_SSL) ? redisSslWrite(c) : redisNetWrite(c); ssize_t nwritten = c->funcs->write(c);
if (nwritten < 0) { if (nwritten < 0) {
return REDIS_ERR; return REDIS_ERR;
} else if (nwritten > 0) { } else if (nwritten > 0) {
if (nwritten == (signed)sdslen(c->obuf)) { if (nwritten == (ssize_t)sdslen(c->obuf)) {
sdsfree(c->obuf); sdsfree(c->obuf);
c->obuf = sdsempty(); c->obuf = sdsempty();
if (c->obuf == NULL)
goto oom;
} else { } else {
sdsrange(c->obuf,nwritten,-1); if (sdsrange(c->obuf,nwritten,-1) < 0) goto oom;
} }
} }
} }
if (done != NULL) *done = (sdslen(c->obuf) == 0); if (done != NULL) *done = (sdslen(c->obuf) == 0);
return REDIS_OK; return REDIS_OK;
oom:
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR;
} }
/* Internal helper function to try and get a reply from the reader, /* Internal helper that returns 1 if the reply was a RESP3 PUSH
* or set an error in the context otherwise. */ * message and we handled it with a user-provided callback. */
static int redisHandledPushReply(redisContext *c, void *reply) {
if (reply && c->push_cb && redisIsPushReply(reply)) {
c->push_cb(c->privdata, reply);
return 1;
}
return 0;
}
/* Get a reply from our reader or set an error in the context. */
int redisGetReplyFromReader(redisContext *c, void **reply) { int redisGetReplyFromReader(redisContext *c, void **reply) {
if (redisReaderGetReply(c->reader, reply) == REDIS_ERR) { if (redisReaderGetReply(c->reader, reply) == REDIS_ERR) {
__redisSetError(c,c->reader->err,c->reader->errstr); __redisSetError(c,c->reader->err,c->reader->errstr);
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK;
}
/* Internal helper to get the next reply from our reader while handling
* any PUSH messages we encounter along the way. This is separate from
* redisGetReplyFromReader so as to not change its behavior. */
static int redisNextInBandReplyFromReader(redisContext *c, void **reply) {
do {
if (redisGetReplyFromReader(c, reply) == REDIS_ERR)
return REDIS_ERR;
} while (redisHandledPushReply(c, *reply));
return REDIS_OK; return REDIS_OK;
} }
@ -851,7 +1072,7 @@ int redisGetReply(redisContext *c, void **reply) {
void *aux = NULL; void *aux = NULL;
/* Try to read pending replies */ /* Try to read pending replies */
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR)
return REDIS_ERR; return REDIS_ERR;
/* For the blocking context, flush output buffer and read reply */ /* For the blocking context, flush output buffer and read reply */
@ -866,13 +1087,19 @@ int redisGetReply(redisContext *c, void **reply) {
do { do {
if (redisBufferRead(c) == REDIS_ERR) if (redisBufferRead(c) == REDIS_ERR)
return REDIS_ERR; return REDIS_ERR;
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR)
return REDIS_ERR; return REDIS_ERR;
} while (aux == NULL); } while (aux == NULL);
} }
/* Set reply object */ /* Set reply or free it if we were passed NULL */
if (reply != NULL) *reply = aux; if (reply != NULL) {
*reply = aux;
} else {
freeReplyObject(aux);
}
return REDIS_OK; return REDIS_OK;
} }
@ -919,11 +1146,11 @@ int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
} }
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
free(cmd); hi_free(cmd);
return REDIS_ERR; return REDIS_ERR;
} }
free(cmd); hi_free(cmd);
return REDIS_OK; return REDIS_OK;
} }
@ -939,7 +1166,7 @@ int redisAppendCommand(redisContext *c, const char *format, ...) {
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
sds cmd; sds cmd;
int len; long long len;
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
if (len == -1) { if (len == -1) {

136
hiredis.h
View File

@ -35,14 +35,20 @@
#define __HIREDIS_H #define __HIREDIS_H
#include "read.h" #include "read.h"
#include <stdarg.h> /* for va_list */ #include <stdarg.h> /* for va_list */
#ifndef _MSC_VER
#include <sys/time.h> /* for struct timeval */ #include <sys/time.h> /* for struct timeval */
#else
struct timeval; /* forward declaration */
typedef long long ssize_t;
#endif
#include <stdint.h> /* uintXX_t, etc */ #include <stdint.h> /* uintXX_t, etc */
#include "sds.h" /* for sds */ #include "sds.h" /* for sds */
#include "alloc.h" /* for allocation wrappers */
#define HIREDIS_MAJOR 0 #define HIREDIS_MAJOR 1
#define HIREDIS_MINOR 14 #define HIREDIS_MINOR 2
#define HIREDIS_PATCH 0 #define HIREDIS_PATCH 0
#define HIREDIS_SONAME 0.14 #define HIREDIS_SONAME 1.2.1-dev
/* Connection type can be blocking or non-blocking and is set in the /* Connection type can be blocking or non-blocking and is set in the
* least significant bit of the flags field in redisContext. */ * least significant bit of the flags field in redisContext. */
@ -74,8 +80,8 @@
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ /* Flag that is set when we should set SO_REUSEADDR before calling bind() */
#define REDIS_REUSEADDR 0x80 #define REDIS_REUSEADDR 0x80
/* Flag that is set when this connection is done through SSL */ /* Flag that is set when the async connection supports push replies. */
#define REDIS_SSL 0x100 #define REDIS_SUPPORTS_PUSH 0x100
/** /**
* Flag that indicates the user does not want the context to * Flag that indicates the user does not want the context to
@ -83,12 +89,29 @@
*/ */
#define REDIS_NO_AUTO_FREE 0x200 #define REDIS_NO_AUTO_FREE 0x200
/* Flag that indicates the user does not want replies to be automatically freed */
#define REDIS_NO_AUTO_FREE_REPLIES 0x400
/* Flags to prefer IPv6 or IPv4 when doing DNS lookup. (If both are set,
* AF_UNSPEC is used.) */
#define REDIS_PREFER_IPV4 0x800
#define REDIS_PREFER_IPV6 0x1000
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
/* number of times we retry to connect in the case of EADDRNOTAVAIL and /* number of times we retry to connect in the case of EADDRNOTAVAIL and
* SO_REUSEADDR is being used. */ * SO_REUSEADDR is being used. */
#define REDIS_CONNECT_RETRIES 10 #define REDIS_CONNECT_RETRIES 10
/* Forward declarations for structs defined elsewhere */
struct redisAsyncContext;
struct redisContext;
/* RESP3 push helpers and callback prototypes */
#define redisIsPushReply(r) (((redisReply*)(r))->type == REDIS_REPLY_PUSH)
typedef void (redisPushFn)(void *, void *);
typedef void (redisAsyncPushFn)(struct redisAsyncContext *, void *);
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -97,8 +120,13 @@ extern "C" {
typedef struct redisReply { typedef struct redisReply {
int type; /* REDIS_REPLY_* */ int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
double dval; /* The double when type is REDIS_REPLY_DOUBLE */
size_t len; /* Length of string */ size_t len; /* Length of string */
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
REDIS_REPLY_VERB, REDIS_REPLY_DOUBLE (in additional to dval),
and REDIS_REPLY_BIGNUM. */
char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
terminated 3 character content type, such as "txt". */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply; } redisReply;
@ -111,8 +139,8 @@ void freeReplyObject(void *reply);
/* Functions to format a command according to the protocol. */ /* Functions to format a command according to the protocol. */
int redisvFormatCommand(char **target, const char *format, va_list ap); int redisvFormatCommand(char **target, const char *format, va_list ap);
int redisFormatCommand(char **target, const char *format, ...); int redisFormatCommand(char **target, const char *format, ...);
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); long long 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); long long redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen);
void redisFreeCommand(char *cmd); void redisFreeCommand(char *cmd);
void redisFreeSdsCommand(sds cmd); void redisFreeSdsCommand(sds cmd);
@ -126,12 +154,17 @@ struct redisSsl;
#define REDIS_OPT_NONBLOCK 0x01 #define REDIS_OPT_NONBLOCK 0x01
#define REDIS_OPT_REUSEADDR 0x02 #define REDIS_OPT_REUSEADDR 0x02
#define REDIS_OPT_NOAUTOFREE 0x04 /* Don't automatically free the async
/** * object on a connection failure, or
* Don't automatically free the async object on a connection failure, * other implicit conditions. Only free
* or other implicit conditions. Only free on an explicit call to disconnect() or free() * on an explicit call to disconnect()
*/ * or free() */
#define REDIS_OPT_NOAUTOFREE 0x04 #define REDIS_OPT_NO_PUSH_AUTOFREE 0x08 /* Don't automatically intercept and
* free RESP3 PUSH replies. */
#define REDIS_OPT_NOAUTOFREEREPLIES 0x10 /* Don't automatically free replies. */
#define REDIS_OPT_PREFER_IPV4 0x20 /* Prefer IPv4 in DNS lookups. */
#define REDIS_OPT_PREFER_IPV6 0x40 /* Prefer IPv6 in DNS lookups. */
#define REDIS_OPT_PREFER_IP_UNSPEC (REDIS_OPT_PREFER_IPV4 | REDIS_OPT_PREFER_IPV6)
/* In Unix systems a file descriptor is a regular signed int, with -1 /* In Unix systems a file descriptor is a regular signed int, with -1
* representing an invalid descriptor. In Windows it is a SOCKET * representing an invalid descriptor. In Windows it is a SOCKET
@ -157,8 +190,11 @@ typedef struct {
int type; int type;
/* bit field of REDIS_OPT_xxx */ /* bit field of REDIS_OPT_xxx */
int options; int options;
/* timeout value. if NULL, no timeout is used */ /* timeout value for connect operation. If NULL, no timeout is used */
const struct timeval *timeout; const struct timeval *connect_timeout;
/* timeout value for commands. If NULL, no timeout is used. This can be
* updated at runtime with redisSetTimeout/redisAsyncSetTimeout. */
const struct timeval *command_timeout;
union { union {
/** use this field for tcp/ip connections */ /** use this field for tcp/ip connections */
struct { struct {
@ -173,22 +209,54 @@ typedef struct {
* file descriptor */ * file descriptor */
redisFD fd; redisFD fd;
} endpoint; } endpoint;
/* Optional user defined data/destructor */
void *privdata;
void (*free_privdata)(void *);
/* A user defined PUSH message callback */
redisPushFn *push_cb;
redisAsyncPushFn *async_push_cb;
} redisOptions; } redisOptions;
/** /**
* Helper macros to initialize options to their specified fields. * Helper macros to initialize options to their specified fields.
*/ */
#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \ #define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) do { \
(opts)->type = REDIS_CONN_TCP; \ (opts)->type = REDIS_CONN_TCP; \
(opts)->endpoint.tcp.ip = ip_; \ (opts)->endpoint.tcp.ip = ip_; \
(opts)->endpoint.tcp.port = port_; (opts)->endpoint.tcp.port = port_; \
} while(0)
#define REDIS_OPTIONS_SET_UNIX(opts, path) \ #define REDIS_OPTIONS_SET_UNIX(opts, path) do { \
(opts)->type = REDIS_CONN_UNIX; \ (opts)->type = REDIS_CONN_UNIX; \
(opts)->endpoint.unix_socket = path; (opts)->endpoint.unix_socket = path; \
} while(0)
#define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) do { \
(opts)->privdata = data; \
(opts)->free_privdata = dtor; \
} while(0)
typedef struct redisContextFuncs {
void (*close)(struct redisContext *);
void (*free_privctx)(void *);
void (*async_read)(struct redisAsyncContext *);
void (*async_write)(struct redisAsyncContext *);
/* Read/Write data to the underlying communication stream, returning the
* number of bytes read/written. In the event of an unrecoverable error
* these functions shall return a value < 0. In the event of a
* recoverable error, they should return 0. */
ssize_t (*read)(struct redisContext *, char *, size_t);
ssize_t (*write)(struct redisContext *);
} redisContextFuncs;
/* Context for a connection to Redis */ /* Context for a connection to Redis */
typedef struct redisContext { typedef struct redisContext {
const redisContextFuncs *funcs; /* Function table */
int err; /* Error flags, 0 when there is no error */ int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */ char errstr[128]; /* String representation of error when applicable */
redisFD fd; redisFD fd;
@ -197,7 +265,8 @@ typedef struct redisContext {
redisReader *reader; /* Protocol reader */ redisReader *reader; /* Protocol reader */
enum redisConnectionType connection_type; enum redisConnectionType connection_type;
struct timeval *timeout; struct timeval *connect_timeout;
struct timeval *command_timeout;
struct { struct {
char *host; char *host;
@ -210,11 +279,20 @@ typedef struct redisContext {
} unix_sock; } unix_sock;
/* For non-blocking connect */ /* For non-blocking connect */
struct sockadr *saddr; struct sockaddr *saddr;
size_t addrlen; size_t addrlen;
/* For SSL communication */
struct redisSsl *ssl;
/* Optional data and corresponding destructor users can use to provide
* context to a given redisContext. Not used by hiredis. */
void *privdata;
void (*free_privdata)(void *);
/* Internal context pointer presently used by hiredis to manage
* SSL connections. */
void *privctx;
/* An optional RESP3 PUSH handler */
redisPushFn *push_cb;
} redisContext; } redisContext;
redisContext *redisConnectWithOptions(const redisOptions *options); redisContext *redisConnectWithOptions(const redisOptions *options);
@ -230,13 +308,6 @@ redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval
redisContext *redisConnectUnixNonBlock(const char *path); redisContext *redisConnectUnixNonBlock(const char *path);
redisContext *redisConnectFd(redisFD fd); redisContext *redisConnectFd(redisFD fd);
/**
* Secure the connection using SSL. This should be done before any command is
* executed on the connection.
*/
int redisSecureConnection(redisContext *c, const char *capath, const char *certpath,
const char *keypath, const char *servername);
/** /**
* Reconnect the given context using the saved information. * Reconnect the given context using the saved information.
* *
@ -248,8 +319,11 @@ int redisSecureConnection(redisContext *c, const char *capath, const char *certp
*/ */
int redisReconnect(redisContext *c); int redisReconnect(redisContext *c);
redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn);
int redisSetTimeout(redisContext *c, const struct timeval tv); int redisSetTimeout(redisContext *c, const struct timeval tv);
int redisEnableKeepAlive(redisContext *c); int redisEnableKeepAlive(redisContext *c);
int redisEnableKeepAliveWithInterval(redisContext *c, int interval);
int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout);
void redisFree(redisContext *c); void redisFree(redisContext *c);
redisFD redisFreeKeepFd(redisContext *c); redisFD redisFreeKeepFd(redisContext *c);
int redisBufferRead(redisContext *c); int redisBufferRead(redisContext *c);

View File

@ -1,6 +1,7 @@
prefix=@CMAKE_INSTALL_PREFIX@ prefix=@CMAKE_INSTALL_PREFIX@
install_libdir=@CMAKE_INSTALL_LIBDIR@
exec_prefix=${prefix} exec_prefix=${prefix}
libdir=${exec_prefix}/lib libdir=${exec_prefix}/${install_libdir}
includedir=${prefix}/include includedir=${prefix}/include
pkgincludedir=${includedir}/hiredis pkgincludedir=${includedir}/hiredis
@ -8,4 +9,4 @@ Name: hiredis
Description: Minimalistic C client library for Redis. Description: Minimalistic C client library for Redis.
Version: @PROJECT_VERSION@ Version: @PROJECT_VERSION@
Libs: -L${libdir} -lhiredis Libs: -L${libdir} -lhiredis
Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64 Cflags: -I${pkgincludedir} -I${includedir} -D_FILE_OFFSET_BITS=64

11
hiredis.targets Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)\..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(MSBuildThisFileDirectory)\..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
</Project>

View File

@ -0,0 +1,16 @@
@PACKAGE_INIT@
set_and_check(hiredis_ssl_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
include(CMakeFindDependencyMacro)
find_dependency(OpenSSL)
IF (NOT TARGET hiredis::hiredis_ssl)
INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_ssl-targets.cmake)
ENDIF()
SET(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl)
SET(hiredis_ssl_INCLUDE_DIRS ${hiredis_ssl_INCLUDEDIR})
check_required_components(hiredis_ssl)

163
hiredis_ssl.h Normal file
View File

@ -0,0 +1,163 @@
/*
* Copyright (c) 2019, Redis Labs
*
* 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_SSL_H
#define __HIREDIS_SSL_H
#ifdef __cplusplus
extern "C" {
#endif
/* This is the underlying struct for SSL in ssl.h, which is not included to
* keep build dependencies short here.
*/
struct ssl_st;
/* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly
* calling OpenSSL.
*/
typedef struct redisSSLContext redisSSLContext;
/**
* Initialization errors that redisCreateSSLContext() may return.
*/
typedef enum {
REDIS_SSL_CTX_NONE = 0, /* No Error */
REDIS_SSL_CTX_CREATE_FAILED, /* Failed to create OpenSSL SSL_CTX */
REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */
REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */
REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */
REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED, /* Failed to set client default certificate directory */
REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED, /* Failed to load private key */
REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certificate store */
REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */
} redisSSLContextError;
/* Constants that mirror OpenSSL's verify modes. By default,
* REDIS_SSL_VERIFY_PEER is used with redisCreateSSLContext().
* Some Redis clients disable peer verification if there are no
* certificates specified.
*/
#define REDIS_SSL_VERIFY_NONE 0x00
#define REDIS_SSL_VERIFY_PEER 0x01
#define REDIS_SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02
#define REDIS_SSL_VERIFY_CLIENT_ONCE 0x04
#define REDIS_SSL_VERIFY_POST_HANDSHAKE 0x08
/* Options to create an OpenSSL context. */
typedef struct {
const char *cacert_filename;
const char *capath;
const char *cert_filename;
const char *private_key_filename;
const char *server_name;
int verify_mode;
} redisSSLOptions;
/**
* Return the error message corresponding with the specified error code.
*/
const char *redisSSLContextGetError(redisSSLContextError error);
/**
* Helper function to initialize the OpenSSL library.
*
* OpenSSL requires one-time initialization before it can be used. Callers should
* call this function only once, and only if OpenSSL is not directly initialized
* elsewhere.
*/
int redisInitOpenSSL(void);
/**
* Helper function to initialize an OpenSSL context that can be used
* to initiate SSL connections.
*
* cacert_filename is an optional name of a CA certificate/bundle file to load
* and use for validation.
*
* capath is an optional directory path where trusted CA certificate files are
* stored in an OpenSSL-compatible structure.
*
* cert_filename and private_key_filename are optional names of a client side
* certificate and private key files to use for authentication. They need to
* be both specified or omitted.
*
* server_name is an optional and will be used as a server name indication
* (SNI) TLS extension.
*
* If error is non-null, it will be populated in case the context creation fails
* (returning a NULL).
*/
redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
const char *cert_filename, const char *private_key_filename,
const char *server_name, redisSSLContextError *error);
/**
* Helper function to initialize an OpenSSL context that can be used
* to initiate SSL connections. This is a more extensible version of redisCreateSSLContext().
*
* options contains a structure of SSL options to use.
*
* If error is non-null, it will be populated in case the context creation fails
* (returning a NULL).
*/
redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options,
redisSSLContextError *error);
/**
* Free a previously created OpenSSL context.
*/
void redisFreeSSLContext(redisSSLContext *redis_ssl_ctx);
/**
* Initiate SSL on an existing redisContext.
*
* This is similar to redisInitiateSSL() but does not require the caller
* to directly interact with OpenSSL, and instead uses a redisSSLContext
* previously created using redisCreateSSLContext().
*/
int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx);
/**
* Initiate SSL/TLS negotiation on a provided OpenSSL SSL object.
*/
int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
#ifdef __cplusplus
}
#endif
#endif /* __HIREDIS_SSL_H */

13
hiredis_ssl.pc.in Normal file
View File

@ -0,0 +1,13 @@
prefix=@CMAKE_INSTALL_PREFIX@
install_libdir=@CMAKE_INSTALL_LIBDIR@
exec_prefix=${prefix}
libdir=${exec_prefix}/${install_libdir}
includedir=${prefix}/include
pkgincludedir=${includedir}/hiredis
Name: hiredis_ssl
Description: SSL Support for hiredis.
Version: @PROJECT_VERSION@
Requires: hiredis
Libs: -L${libdir} -lhiredis_ssl
Libs.private: -lssl -lcrypto

264
net.c
View File

@ -41,6 +41,7 @@
#include <stdio.h> #include <stdio.h>
#include <limits.h> #include <limits.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h>
#include "net.h" #include "net.h"
#include "sds.h" #include "sds.h"
@ -50,6 +51,8 @@
/* Defined in hiredis.c */ /* Defined in hiredis.c */
void __redisSetError(redisContext *c, int type, const char *str); void __redisSetError(redisContext *c, int type, const char *str);
int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);
void redisNetClose(redisContext *c) { void redisNetClose(redisContext *c) {
if (c && c->fd != REDIS_INVALID_FD) { if (c && c->fd != REDIS_INVALID_FD) {
close(c->fd); close(c->fd);
@ -57,14 +60,18 @@ void redisNetClose(redisContext *c) {
} }
} }
int redisNetRead(redisContext *c, char *buf, size_t bufcap) { ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {
int nread = recv(c->fd, buf, bufcap, 0); ssize_t nread = recv(c->fd, buf, bufcap, 0);
if (nread == -1) { if (nread == -1) {
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
/* Try again later */ /* Try again later */
return 0; return 0;
} else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) {
/* especially in windows */
__redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
return -1;
} else { } else {
__redisSetError(c, REDIS_ERR_IO, NULL); __redisSetError(c, REDIS_ERR_IO, strerror(errno));
return -1; return -1;
} }
} else if (nread == 0) { } else if (nread == 0) {
@ -75,16 +82,20 @@ int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
} }
} }
int redisNetWrite(redisContext *c) { ssize_t redisNetWrite(redisContext *c) {
int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0); ssize_t nwritten;
nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);
if (nwritten < 0) { if (nwritten < 0) {
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
/* Try again later */ /* Try again */
return 0;
} else { } else {
__redisSetError(c, REDIS_ERR_IO, NULL); __redisSetError(c, REDIS_ERR_IO, strerror(errno));
return -1; return -1;
} }
} }
return nwritten; return nwritten;
} }
@ -162,6 +173,11 @@ int redisKeepAlive(redisContext *c, int interval) {
int val = 1; int val = 1;
redisFD fd = c->fd; redisFD fd = c->fd;
/* TCP_KEEPALIVE makes no sense with AF_UNIX connections */
if (c->connection_type == REDIS_CONN_UNIX)
return REDIS_ERR;
#ifndef _WIN32
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR; return REDIS_ERR;
@ -195,11 +211,19 @@ int redisKeepAlive(redisContext *c, int interval) {
} }
#endif #endif
#endif #endif
#else
int res;
res = win32_redisKeepAlive(fd, interval * 1000);
if (res != 0) {
__redisSetError(c, REDIS_ERR_OTHER, strerror(res));
return REDIS_ERR;
}
#endif
return REDIS_OK; return REDIS_OK;
} }
static int redisSetTcpNoDelay(redisContext *c) { int redisSetTcpNoDelay(redisContext *c) {
int yes = 1; int yes = 1;
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
@ -209,16 +233,34 @@ static int redisSetTcpNoDelay(redisContext *c) {
return REDIS_OK; return REDIS_OK;
} }
int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout) {
int res;
#ifdef TCP_USER_TIMEOUT
res = setsockopt(c->fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout));
#else
res = -1;
errno = ENOTSUP;
(void)timeout;
#endif
if (res == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_USER_TIMEOUT)");
redisNetClose(c);
return REDIS_ERR;
}
return REDIS_OK;
}
#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) #define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
static int redisContextTimeoutMsec(redisContext *c, long *result) static int redisContextTimeoutMsec(redisContext *c, long *result)
{ {
const struct timeval *timeout = c->timeout; const struct timeval *timeout = c->connect_timeout;
long msec = -1; long msec = -1;
/* Only use timeout when not NULL. */ /* Only use timeout when not NULL. */
if (timeout != NULL) { if (timeout != NULL) {
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
__redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
*result = msec; *result = msec;
return REDIS_ERR; return REDIS_ERR;
} }
@ -234,24 +276,46 @@ static int redisContextTimeoutMsec(redisContext *c, long *result)
return REDIS_OK; return REDIS_OK;
} }
static long redisPollMillis(void) {
#ifndef _MSC_VER
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return (now.tv_sec * 1000) + now.tv_nsec / 1000000;
#else
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10;
#endif
}
static int redisContextWaitReady(redisContext *c, long msec) { static int redisContextWaitReady(redisContext *c, long msec) {
struct pollfd wfd[1]; struct pollfd wfd;
long end;
wfd[0].fd = c->fd;
wfd[0].events = POLLOUT;
if (errno == EINPROGRESS) {
int res; int res;
if ((res = poll(wfd, 1, msec)) == -1) { if (errno != EINPROGRESS) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
redisNetClose(c);
return REDIS_ERR;
}
wfd.fd = c->fd;
wfd.events = POLLOUT;
end = msec >= 0 ? redisPollMillis() + msec : 0;
while ((res = poll(&wfd, 1, msec)) <= 0) {
if (res < 0 && errno != EINTR) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
redisNetClose(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} else if (res == 0) { } else if (res == 0 || (msec >= 0 && redisPollMillis() >= end)) {
errno = ETIMEDOUT; errno = ETIMEDOUT;
__redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);
redisNetClose(c); redisNetClose(c);
return REDIS_ERR; return REDIS_ERR;
} else {
/* res < 0 && errno == EINTR, try again */
}
} }
if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) { if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
@ -262,23 +326,34 @@ static int redisContextWaitReady(redisContext *c, long msec) {
return REDIS_OK; return REDIS_OK;
} }
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
redisNetClose(c);
return REDIS_ERR;
}
int redisCheckConnectDone(redisContext *c, int *completed) { int redisCheckConnectDone(redisContext *c, int *completed) {
int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen); int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen);
if (rc == 0) { if (rc == 0) {
*completed = 1; *completed = 1;
return REDIS_OK; return REDIS_OK;
} }
switch (errno) { int error = errno;
if (error == EINPROGRESS) {
/* must check error to see if connect failed. Get the socket error */
int fail, so_error;
socklen_t optlen = sizeof(so_error);
fail = getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &so_error, &optlen);
if (fail == 0) {
if (so_error == 0) {
/* Socket is connected! */
*completed = 1;
return REDIS_OK;
}
/* connection error; */
errno = so_error;
error = so_error;
}
}
switch (error) {
case EISCONN: case EISCONN:
*completed = 1; *completed = 1;
return REDIS_OK; return REDIS_OK;
case EALREADY: case EALREADY:
case EINPROGRESS:
case EWOULDBLOCK: case EWOULDBLOCK:
*completed = 0; *completed = 0;
return REDIS_OK; return REDIS_OK;
@ -310,17 +385,56 @@ int redisCheckSocketError(redisContext *c) {
} }
int redisContextSetTimeout(redisContext *c, const struct timeval tv) { int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { const void *to_ptr = &tv;
size_t to_sz = sizeof(tv);
if (redisContextUpdateCommandTimeout(c, &tv) != REDIS_OK) {
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR;
}
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
return REDIS_ERR; return REDIS_ERR;
} }
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
} }
int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout) {
/* Same timeval struct, short circuit */
if (c->connect_timeout == timeout)
return REDIS_OK;
/* Allocate context timeval if we need to */
if (c->connect_timeout == NULL) {
c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout));
if (c->connect_timeout == NULL)
return REDIS_ERR;
}
memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout));
return REDIS_OK;
}
int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout) {
/* Same timeval struct, short circuit */
if (c->command_timeout == timeout)
return REDIS_OK;
/* Allocate context timeval if we need to */
if (c->command_timeout == NULL) {
c->command_timeout = hi_malloc(sizeof(*c->command_timeout));
if (c->command_timeout == NULL)
return REDIS_ERR;
}
memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout));
return REDIS_OK;
}
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout, const struct timeval *timeout,
const char *source_addr) { const char *source_addr) {
@ -345,34 +459,31 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
* This is a bit ugly, but atleast it works and doesn't leak memory. * This is a bit ugly, but atleast it works and doesn't leak memory.
**/ **/
if (c->tcp.host != addr) { if (c->tcp.host != addr) {
free(c->tcp.host); hi_free(c->tcp.host);
c->tcp.host = strdup(addr); c->tcp.host = hi_strdup(addr);
if (c->tcp.host == NULL)
goto oom;
} }
if (timeout) { if (timeout) {
if (c->timeout != timeout) { if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
if (c->timeout == NULL) goto oom;
c->timeout = malloc(sizeof(struct timeval));
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else { } else {
free(c->timeout); hi_free(c->connect_timeout);
c->timeout = NULL; c->connect_timeout = NULL;
} }
if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
__redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
goto error; goto error;
} }
if (source_addr == NULL) { if (source_addr == NULL) {
free(c->tcp.source_addr); hi_free(c->tcp.source_addr);
c->tcp.source_addr = NULL; c->tcp.source_addr = NULL;
} else if (c->tcp.source_addr != source_addr) { } else if (c->tcp.source_addr != source_addr) {
free(c->tcp.source_addr); hi_free(c->tcp.source_addr);
c->tcp.source_addr = strdup(source_addr); c->tcp.source_addr = hi_strdup(source_addr);
} }
snprintf(_port, 6, "%d", port); snprintf(_port, 6, "%d", port);
@ -380,18 +491,26 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
hints.ai_family = AF_INET; hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
/* Try with IPv6 if no IPv4 address was found. We do it in this order since /* DNS lookup. To use dual stack, set both flags to prefer both IPv4 and
* in a Redis client you can't afford to test if you have IPv6 connectivity * IPv6. By default, for historical reasons, we try IPv4 first and then we
* as this would add latency to every connect. Otherwise a more sensible * try IPv6 only if no IPv4 address was found. */
* route could be: Use IPv6 if both addresses are available and there is IPv6 if (c->flags & REDIS_PREFER_IPV6 && c->flags & REDIS_PREFER_IPV4)
* connectivity. */ hints.ai_family = AF_UNSPEC;
if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { else if (c->flags & REDIS_PREFER_IPV6)
hints.ai_family = AF_INET6; hints.ai_family = AF_INET6;
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { else
hints.ai_family = AF_INET;
rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo);
if (rv != 0 && hints.ai_family != AF_UNSPEC) {
/* Try again with the other IP version. */
hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET;
rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo);
}
if (rv != 0) {
__redisSetError(c, REDIS_ERR_OTHER, gai_strerror(rv)); __redisSetError(c, REDIS_ERR_OTHER, gai_strerror(rv));
return REDIS_ERR; return REDIS_ERR;
} }
}
for (p = servinfo; p != NULL; p = p->ai_next) { for (p = servinfo; p != NULL; p = p->ai_next) {
addrretry: addrretry:
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD) if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD)
@ -435,10 +554,11 @@ addrretry:
} }
/* For repeat connection */ /* For repeat connection */
if (c->saddr) { hi_free(c->saddr);
free(c->saddr); c->saddr = hi_malloc(p->ai_addrlen);
} if (c->saddr == NULL)
c->saddr = malloc(p->ai_addrlen); goto oom;
memcpy(c->saddr, p->ai_addr, p->ai_addrlen); memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
c->addrlen = p->ai_addrlen; c->addrlen = p->ai_addrlen;
@ -465,12 +585,12 @@ addrretry:
wait_for_ready: wait_for_ready:
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
goto error; goto error;
if (redisSetTcpNoDelay(c) != REDIS_OK)
goto error;
} }
} }
if (blocking && redisSetBlocking(c,1) != REDIS_OK) if (blocking && redisSetBlocking(c,1) != REDIS_OK)
goto error; goto error;
if (redisSetTcpNoDelay(c) != REDIS_OK)
goto error;
c->flags |= REDIS_CONNECTED; c->flags |= REDIS_CONNECTED;
rv = REDIS_OK; rv = REDIS_OK;
@ -483,6 +603,8 @@ addrretry:
goto error; goto error;
} }
oom:
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
error: error:
rv = REDIS_ERR; rv = REDIS_ERR;
end: end:
@ -516,30 +638,37 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
return REDIS_ERR; return REDIS_ERR;
c->connection_type = REDIS_CONN_UNIX; c->connection_type = REDIS_CONN_UNIX;
if (c->unix_sock.path != path) if (c->unix_sock.path != path) {
c->unix_sock.path = strdup(path); hi_free(c->unix_sock.path);
c->unix_sock.path = hi_strdup(path);
if (c->unix_sock.path == NULL)
goto oom;
}
if (timeout) { if (timeout) {
if (c->timeout != timeout) { if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
if (c->timeout == NULL) goto oom;
c->timeout = malloc(sizeof(struct timeval));
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else { } else {
free(c->timeout); hi_free(c->connect_timeout);
c->timeout = NULL; c->connect_timeout = NULL;
} }
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un))); /* Don't leak sockaddr if we're reconnecting */
if (c->saddr) hi_free(c->saddr);
sa = (struct sockaddr_un*)(c->saddr = hi_malloc(sizeof(struct sockaddr_un)));
if (sa == NULL)
goto oom;
c->addrlen = sizeof(struct sockaddr_un); c->addrlen = sizeof(struct sockaddr_un);
sa->sun_family = AF_UNIX; sa->sun_family = AF_UNIX;
strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1); strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) {
if (errno == EINPROGRESS && !blocking) { if ((errno == EAGAIN || errno == EINPROGRESS) && !blocking) {
/* This is ok. */ /* This is ok. */
} else { } else {
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
@ -559,4 +688,7 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
errno = EPROTONOSUPPORT; errno = EPROTONOSUPPORT;
return REDIS_ERR; return REDIS_ERR;
#endif /* _WIN32 */ #endif /* _WIN32 */
oom:
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR;
} }

7
net.h
View File

@ -38,8 +38,8 @@
#include "hiredis.h" #include "hiredis.h"
void redisNetClose(redisContext *c); void redisNetClose(redisContext *c);
int redisNetRead(redisContext *c, char *buf, size_t bufcap); ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap);
int redisNetWrite(redisContext *c); ssize_t redisNetWrite(redisContext *c);
int redisCheckSocketError(redisContext *c); int redisCheckSocketError(redisContext *c);
int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextSetTimeout(redisContext *c, const struct timeval tv);
@ -51,4 +51,7 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
int redisKeepAlive(redisContext *c, int interval); int redisKeepAlive(redisContext *c, int interval);
int redisCheckConnectDone(redisContext *c, int *completed); int redisCheckConnectDone(redisContext *c, int *completed);
int redisSetTcpNoDelay(redisContext *c);
int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout);
#endif #endif

327
read.c
View File

@ -29,20 +29,26 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#include "fmacros.h" #include "fmacros.h"
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#ifndef _MSC_VER #ifndef _MSC_VER
#include <unistd.h> #include <unistd.h>
#include <strings.h>
#endif #endif
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <ctype.h> #include <ctype.h>
#include <limits.h> #include <limits.h>
#include <math.h>
#include "alloc.h"
#include "read.h" #include "read.h"
#include "sds.h" #include "sds.h"
#include "win32.h"
/* Initial size of our nested reply stack and how much we grow it when needd */
#define REDIS_READER_STACK_SIZE 9
static void __redisReaderSetError(redisReader *r, int type, const char *str) { static void __redisReaderSetError(redisReader *r, int type, const char *str) {
size_t len; size_t len;
@ -117,29 +123,28 @@ static char *readBytes(redisReader *r, unsigned int bytes) {
/* Find pointer to \r\n. */ /* Find pointer to \r\n. */
static char *seekNewline(char *s, size_t len) { static char *seekNewline(char *s, size_t len) {
int pos = 0; char *ret;
int _len = len-1;
/* Position should be < len-1 because the character at "pos" should be /* We cannot match with fewer than 2 bytes */
* followed by a \n. Note that strchr cannot be used because it doesn't if (len < 2)
* 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; return NULL;
} else {
if (s[pos+1] == '\n') { /* Search up to len - 1 characters */
len--;
/* Look for the \r */
while ((ret = memchr(s, '\r', len)) != NULL) {
if (ret[1] == '\n') {
/* Found. */ /* Found. */
return s+pos; break;
} else { }
/* Continue searching. */ /* Continue searching. */
pos++; ret++;
len -= ret - s;
s = ret;
} }
}
} return ret;
return NULL;
} }
/* Convert a string into a long long. Returns REDIS_OK if the string could be /* Convert a string into a long long. Returns REDIS_OK if the string could be
@ -241,9 +246,13 @@ static void moveToNextTask(redisReader *r) {
return; return;
} }
cur = &(r->rstack[r->ridx]); cur = r->task[r->ridx];
prv = &(r->rstack[r->ridx-1]); prv = r->task[r->ridx-1];
assert(prv->type == REDIS_REPLY_ARRAY); assert(prv->type == REDIS_REPLY_ARRAY ||
prv->type == REDIS_REPLY_MAP ||
prv->type == REDIS_REPLY_ATTR ||
prv->type == REDIS_REPLY_SET ||
prv->type == REDIS_REPLY_PUSH);
if (cur->idx == prv->elements-1) { if (cur->idx == prv->elements-1) {
r->ridx--; r->ridx--;
} else { } else {
@ -258,30 +267,118 @@ static void moveToNextTask(redisReader *r) {
} }
static int processLineItem(redisReader *r) { static int processLineItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask *cur = r->task[r->ridx];
void *obj; void *obj;
char *p; char *p;
int len; int len;
if ((p = readLine(r,&len)) != NULL) { if ((p = readLine(r,&len)) != NULL) {
if (cur->type == REDIS_REPLY_INTEGER) { if (cur->type == REDIS_REPLY_INTEGER) {
if (r->fn && r->fn->createInteger) {
long long v; long long v;
if (string2ll(p, len, &v) == REDIS_ERR) { if (string2ll(p, len, &v) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad integer value"); "Bad integer value");
return REDIS_ERR; return REDIS_ERR;
} }
if (r->fn && r->fn->createInteger) {
obj = r->fn->createInteger(cur,v); obj = r->fn->createInteger(cur,v);
} else { } else {
obj = (void*)REDIS_REPLY_INTEGER; obj = (void*)REDIS_REPLY_INTEGER;
} }
} else if (cur->type == REDIS_REPLY_DOUBLE) {
char buf[326], *eptr;
double d;
if ((size_t)len >= sizeof(buf)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Double value is too large");
return REDIS_ERR;
}
memcpy(buf,p,len);
buf[len] = '\0';
if (len == 3 && strcasecmp(buf,"inf") == 0) {
d = INFINITY; /* Positive infinite. */
} else if (len == 4 && strcasecmp(buf,"-inf") == 0) {
d = -INFINITY; /* Negative infinite. */
} else if ((len == 3 && strcasecmp(buf,"nan") == 0) ||
(len == 4 && strcasecmp(buf, "-nan") == 0)) {
d = NAN; /* nan. */
} else { } else {
/* Type will be error or status. */ d = strtod((char*)buf,&eptr);
/* RESP3 only allows "inf", "-inf", and finite values, while
* strtod() allows other variations on infinity,
* etc. We explicity handle our two allowed infinite cases and NaN
* above, so strtod() should only result in finite values. */
if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad double value");
return REDIS_ERR;
}
}
if (r->fn && r->fn->createDouble) {
obj = r->fn->createDouble(cur,d,buf,len);
} else {
obj = (void*)REDIS_REPLY_DOUBLE;
}
} else if (cur->type == REDIS_REPLY_NIL) {
if (len != 0) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad nil value");
return REDIS_ERR;
}
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
else
obj = (void*)REDIS_REPLY_NIL;
} else if (cur->type == REDIS_REPLY_BOOL) {
int bval;
if (len != 1 || !strchr("tTfF", p[0])) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad bool value");
return REDIS_ERR;
}
bval = p[0] == 't' || p[0] == 'T';
if (r->fn && r->fn->createBool)
obj = r->fn->createBool(cur,bval);
else
obj = (void*)REDIS_REPLY_BOOL;
} else if (cur->type == REDIS_REPLY_BIGNUM) {
/* Ensure all characters are decimal digits (with possible leading
* minus sign). */
for (int i = 0; i < len; i++) {
/* XXX Consider: Allow leading '+'? Error on leading '0's? */
if (i == 0 && p[0] == '-') continue;
if (p[i] < '0' || p[i] > '9') {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad bignum value");
return REDIS_ERR;
}
}
if (r->fn && r->fn->createString) if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,p,len); obj = r->fn->createString(cur,p,len);
else else
obj = (void*)(size_t)(cur->type); obj = (void*)REDIS_REPLY_BIGNUM;
} else {
/* Type will be error or status. */
for (int i = 0; i < len; i++) {
if (p[i] == '\r' || p[i] == '\n') {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad simple string value");
return REDIS_ERR;
}
}
if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,p,len);
else
obj = (void*)(uintptr_t)(cur->type);
} }
if (obj == NULL) { if (obj == NULL) {
@ -299,7 +396,7 @@ static int processLineItem(redisReader *r) {
} }
static int processBulkItem(redisReader *r) { static int processBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask *cur = r->task[r->ridx];
void *obj = NULL; void *obj = NULL;
char *p, *s; char *p, *s;
long long len; long long len;
@ -335,10 +432,18 @@ static int processBulkItem(redisReader *r) {
/* Only continue when the buffer contains the entire bulk item. */ /* Only continue when the buffer contains the entire bulk item. */
bytelen += len+2; /* include \r\n */ bytelen += len+2; /* include \r\n */
if (r->pos+bytelen <= r->len) { if (r->pos+bytelen <= r->len) {
if ((cur->type == REDIS_REPLY_VERB && len < 4) ||
(cur->type == REDIS_REPLY_VERB && s[5] != ':'))
{
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Verbatim string 4 bytes of content type are "
"missing or incorrectly encoded.");
return REDIS_ERR;
}
if (r->fn && r->fn->createString) if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,s+2,len); obj = r->fn->createString(cur,s+2,len);
else else
obj = (void*)REDIS_REPLY_STRING; obj = (void*)(uintptr_t)cur->type;
success = 1; success = 1;
} }
} }
@ -362,17 +467,41 @@ static int processBulkItem(redisReader *r) {
return REDIS_ERR; return REDIS_ERR;
} }
static int processMultiBulkItem(redisReader *r) { static int redisReaderGrow(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask **aux;
int newlen;
/* Grow our stack size */
newlen = r->tasks + REDIS_READER_STACK_SIZE;
aux = hi_realloc(r->task, sizeof(*r->task) * newlen);
if (aux == NULL)
goto oom;
r->task = aux;
/* Allocate new tasks */
for (; r->tasks < newlen; r->tasks++) {
r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
if (r->task[r->tasks] == NULL)
goto oom;
}
return REDIS_OK;
oom:
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
/* Process the array, map and set types. */
static int processAggregateItem(redisReader *r) {
redisReadTask *cur = r->task[r->ridx];
void *obj; void *obj;
char *p; char *p;
long long elements; long long elements;
int root = 0, len; int root = 0, len;
/* Set error for nested multi bulks with depth > 7 */ if (r->ridx == r->tasks - 1) {
if (r->ridx == 8) { if (redisReaderGrow(r) == REDIS_ERR)
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"No support for nested multi bulk replies with depth > 7");
return REDIS_ERR; return REDIS_ERR;
} }
@ -385,7 +514,9 @@ static int processMultiBulkItem(redisReader *r) {
root = (r->ridx == 0); root = (r->ridx == 0);
if (elements < -1 || elements > INT_MAX) { if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX) ||
(r->maxelements > 0 && elements > r->maxelements))
{
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Multi-bulk length out of range"); "Multi-bulk length out of range");
return REDIS_ERR; return REDIS_ERR;
@ -404,10 +535,12 @@ static int processMultiBulkItem(redisReader *r) {
moveToNextTask(r); moveToNextTask(r);
} else { } else {
if (cur->type == REDIS_REPLY_MAP || cur->type == REDIS_REPLY_ATTR) elements *= 2;
if (r->fn && r->fn->createArray) if (r->fn && r->fn->createArray)
obj = r->fn->createArray(cur,elements); obj = r->fn->createArray(cur,elements);
else else
obj = (void*)REDIS_REPLY_ARRAY; obj = (void*)(uintptr_t)cur->type;
if (obj == NULL) { if (obj == NULL) {
__redisReaderSetErrorOOM(r); __redisReaderSetErrorOOM(r);
@ -419,12 +552,12 @@ static int processMultiBulkItem(redisReader *r) {
cur->elements = elements; cur->elements = elements;
cur->obj = obj; cur->obj = obj;
r->ridx++; r->ridx++;
r->rstack[r->ridx].type = -1; r->task[r->ridx]->type = -1;
r->rstack[r->ridx].elements = -1; r->task[r->ridx]->elements = -1;
r->rstack[r->ridx].idx = 0; r->task[r->ridx]->idx = 0;
r->rstack[r->ridx].obj = NULL; r->task[r->ridx]->obj = NULL;
r->rstack[r->ridx].parent = cur; r->task[r->ridx]->parent = cur;
r->rstack[r->ridx].privdata = r->privdata; r->task[r->ridx]->privdata = r->privdata;
} else { } else {
moveToNextTask(r); moveToNextTask(r);
} }
@ -439,7 +572,7 @@ static int processMultiBulkItem(redisReader *r) {
} }
static int processItem(redisReader *r) { static int processItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask *cur = r->task[r->ridx];
char *p; char *p;
/* check if we need to read type */ /* check if we need to read type */
@ -455,12 +588,39 @@ static int processItem(redisReader *r) {
case ':': case ':':
cur->type = REDIS_REPLY_INTEGER; cur->type = REDIS_REPLY_INTEGER;
break; break;
case ',':
cur->type = REDIS_REPLY_DOUBLE;
break;
case '_':
cur->type = REDIS_REPLY_NIL;
break;
case '$': case '$':
cur->type = REDIS_REPLY_STRING; cur->type = REDIS_REPLY_STRING;
break; break;
case '*': case '*':
cur->type = REDIS_REPLY_ARRAY; cur->type = REDIS_REPLY_ARRAY;
break; break;
case '%':
cur->type = REDIS_REPLY_MAP;
break;
case '|':
cur->type = REDIS_REPLY_ATTR;
break;
case '~':
cur->type = REDIS_REPLY_SET;
break;
case '#':
cur->type = REDIS_REPLY_BOOL;
break;
case '=':
cur->type = REDIS_REPLY_VERB;
break;
case '>':
cur->type = REDIS_REPLY_PUSH;
break;
case '(':
cur->type = REDIS_REPLY_BIGNUM;
break;
default: default:
__redisReaderSetErrorProtocolByte(r,*p); __redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR; return REDIS_ERR;
@ -476,11 +636,20 @@ static int processItem(redisReader *r) {
case REDIS_REPLY_ERROR: case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS: case REDIS_REPLY_STATUS:
case REDIS_REPLY_INTEGER: case REDIS_REPLY_INTEGER:
case REDIS_REPLY_DOUBLE:
case REDIS_REPLY_NIL:
case REDIS_REPLY_BOOL:
case REDIS_REPLY_BIGNUM:
return processLineItem(r); return processLineItem(r);
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
case REDIS_REPLY_VERB:
return processBulkItem(r); return processBulkItem(r);
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
return processMultiBulkItem(r); case REDIS_REPLY_MAP:
case REDIS_REPLY_ATTR:
case REDIS_REPLY_SET:
case REDIS_REPLY_PUSH:
return processAggregateItem(r);
default: default:
assert(NULL); assert(NULL);
return REDIS_ERR; /* Avoid warning. */ return REDIS_ERR; /* Avoid warning. */
@ -490,29 +659,53 @@ static int processItem(redisReader *r) {
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
redisReader *r; redisReader *r;
r = calloc(1,sizeof(redisReader)); r = hi_calloc(1,sizeof(redisReader));
if (r == NULL) if (r == NULL)
return NULL; return NULL;
r->fn = fn;
r->buf = sdsempty(); r->buf = sdsempty();
r->maxbuf = REDIS_READER_MAX_BUF; if (r->buf == NULL)
if (r->buf == NULL) { goto oom;
free(r);
return NULL; r->task = hi_calloc(REDIS_READER_STACK_SIZE, sizeof(*r->task));
if (r->task == NULL)
goto oom;
for (; r->tasks < REDIS_READER_STACK_SIZE; r->tasks++) {
r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
if (r->task[r->tasks] == NULL)
goto oom;
} }
r->fn = fn;
r->maxbuf = REDIS_READER_MAX_BUF;
r->maxelements = REDIS_READER_MAX_ARRAY_ELEMENTS;
r->ridx = -1; r->ridx = -1;
return r; return r;
oom:
redisReaderFree(r);
return NULL;
} }
void redisReaderFree(redisReader *r) { void redisReaderFree(redisReader *r) {
if (r == NULL) if (r == NULL)
return; return;
if (r->reply != NULL && r->fn && r->fn->freeObject) if (r->reply != NULL && r->fn && r->fn->freeObject)
r->fn->freeObject(r->reply); r->fn->freeObject(r->reply);
if (r->task) {
/* We know r->task[i] is allocated if i < r->tasks */
for (int i = 0; i < r->tasks; i++) {
hi_free(r->task[i]);
}
hi_free(r->task);
}
sdsfree(r->buf); sdsfree(r->buf);
free(r); hi_free(r);
} }
int redisReaderFeed(redisReader *r, const char *buf, size_t len) { int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
@ -528,23 +721,22 @@ int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
sdsfree(r->buf); sdsfree(r->buf);
r->buf = sdsempty(); r->buf = sdsempty();
r->pos = 0; if (r->buf == 0) goto oom;
/* r->buf should not be NULL since we just free'd a larger one. */ r->pos = 0;
assert(r->buf != NULL);
} }
newbuf = sdscatlen(r->buf,buf,len); newbuf = sdscatlen(r->buf,buf,len);
if (newbuf == NULL) { if (newbuf == NULL) goto oom;
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
r->buf = newbuf; r->buf = newbuf;
r->len = sdslen(r->buf); r->len = sdslen(r->buf);
} }
return REDIS_OK; return REDIS_OK;
oom:
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
} }
int redisReaderGetReply(redisReader *r, void **reply) { int redisReaderGetReply(redisReader *r, void **reply) {
@ -562,12 +754,12 @@ int redisReaderGetReply(redisReader *r, void **reply) {
/* Set first item to process when the stack is empty. */ /* Set first item to process when the stack is empty. */
if (r->ridx == -1) { if (r->ridx == -1) {
r->rstack[0].type = -1; r->task[0]->type = -1;
r->rstack[0].elements = -1; r->task[0]->elements = -1;
r->rstack[0].idx = -1; r->task[0]->idx = -1;
r->rstack[0].obj = NULL; r->task[0]->obj = NULL;
r->rstack[0].parent = NULL; r->task[0]->parent = NULL;
r->rstack[0].privdata = r->privdata; r->task[0]->privdata = r->privdata;
r->ridx = 0; r->ridx = 0;
} }
@ -583,15 +775,18 @@ int redisReaderGetReply(redisReader *r, void **reply) {
/* Discard part of the buffer when we've consumed at least 1k, to avoid /* Discard part of the buffer when we've consumed at least 1k, to avoid
* doing unnecessary calls to memmove() in sds.c. */ * doing unnecessary calls to memmove() in sds.c. */
if (r->pos >= 1024) { if (r->pos >= 1024) {
sdsrange(r->buf,r->pos,-1); if (sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR;
r->pos = 0; r->pos = 0;
r->len = sdslen(r->buf); r->len = sdslen(r->buf);
} }
/* Emit a reply when there is one. */ /* Emit a reply when there is one. */
if (r->ridx == -1) { if (r->ridx == -1) {
if (reply != NULL) if (reply != NULL) {
*reply = r->reply; *reply = r->reply;
} else if (r->reply != NULL && r->fn && r->fn->freeObject) {
r->fn->freeObject(r->reply);
}
r->reply = NULL; r->reply = NULL;
} }
return REDIS_OK; return REDIS_OK;

25
read.h
View File

@ -54,8 +54,20 @@
#define REDIS_REPLY_NIL 4 #define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5 #define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6 #define REDIS_REPLY_ERROR 6
#define REDIS_REPLY_DOUBLE 7
#define REDIS_REPLY_BOOL 8
#define REDIS_REPLY_MAP 9
#define REDIS_REPLY_SET 10
#define REDIS_REPLY_ATTR 11
#define REDIS_REPLY_PUSH 12
#define REDIS_REPLY_BIGNUM 13
#define REDIS_REPLY_VERB 14
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ /* Default max unused reader buffer. */
#define REDIS_READER_MAX_BUF (1024*16)
/* Default multi-bulk element limit */
#define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1)
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -63,7 +75,7 @@ extern "C" {
typedef struct redisReadTask { typedef struct redisReadTask {
int type; int type;
int elements; /* number of elements in multibulk container */ long long elements; /* number of elements in multibulk container */
int idx; /* index in parent (array) object */ int idx; /* index in parent (array) object */
void *obj; /* holds user-generated value for a read task */ void *obj; /* holds user-generated value for a read task */
struct redisReadTask *parent; /* parent task */ struct redisReadTask *parent; /* parent task */
@ -72,9 +84,11 @@ typedef struct redisReadTask {
typedef struct redisReplyObjectFunctions { typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t); void *(*createString)(const redisReadTask*, char*, size_t);
void *(*createArray)(const redisReadTask*, int); void *(*createArray)(const redisReadTask*, size_t);
void *(*createInteger)(const redisReadTask*, long long); void *(*createInteger)(const redisReadTask*, long long);
void *(*createDouble)(const redisReadTask*, double, char*, size_t);
void *(*createNil)(const redisReadTask*); void *(*createNil)(const redisReadTask*);
void *(*createBool)(const redisReadTask*, int);
void (*freeObject)(void*); void (*freeObject)(void*);
} redisReplyObjectFunctions; } redisReplyObjectFunctions;
@ -86,8 +100,11 @@ typedef struct redisReader {
size_t pos; /* Buffer cursor */ size_t pos; /* Buffer cursor */
size_t len; /* Buffer length */ size_t len; /* Buffer length */
size_t maxbuf; /* Max length of unused buffer */ size_t maxbuf; /* Max length of unused buffer */
long long maxelements; /* Max multi-bulk elements */
redisReadTask **task;
int tasks;
redisReadTask rstack[9];
int ridx; /* Index of current read task */ int ridx; /* Index of current read task */
void *reply; /* Temporary reply pointer */ void *reply; /* Temporary reply pointer */

63
sds.c
View File

@ -30,11 +30,13 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#include "fmacros.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include <assert.h> #include <assert.h>
#include <limits.h>
#include "sds.h" #include "sds.h"
#include "sdsalloc.h" #include "sdsalloc.h"
@ -70,7 +72,7 @@ static inline char sdsReqType(size_t string_size) {
* and 'initlen'. * and 'initlen'.
* If NULL is used for 'init' the string is initialized with zero bytes. * 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 * The string is always null-terminated (all the sds strings are, always) so
* even if you create an sds string with: * even if you create an sds string with:
* *
* mystring = sdsnewlen("abc",3); * mystring = sdsnewlen("abc",3);
@ -88,6 +90,7 @@ sds sdsnewlen(const void *init, size_t initlen) {
int hdrlen = sdsHdrSize(type); int hdrlen = sdsHdrSize(type);
unsigned char *fp; /* flags pointer. */ unsigned char *fp; /* flags pointer. */
if (hdrlen+initlen+1 <= initlen) return NULL; /* Catch size_t overflow */
sh = s_malloc(hdrlen+initlen+1); sh = s_malloc(hdrlen+initlen+1);
if (sh == NULL) return NULL; if (sh == NULL) return NULL;
if (!init) if (!init)
@ -172,7 +175,7 @@ void sdsfree(sds s) {
* the output will be "6" as the string was modified but the logical length * the output will be "6" as the string was modified but the logical length
* remains 6 bytes. */ * remains 6 bytes. */
void sdsupdatelen(sds s) { void sdsupdatelen(sds s) {
int reallen = strlen(s); size_t reallen = strlen(s);
sdssetlen(s, reallen); sdssetlen(s, reallen);
} }
@ -194,7 +197,7 @@ void sdsclear(sds s) {
sds sdsMakeRoomFor(sds s, size_t addlen) { sds sdsMakeRoomFor(sds s, size_t addlen) {
void *sh, *newsh; void *sh, *newsh;
size_t avail = sdsavail(s); size_t avail = sdsavail(s);
size_t len, newlen; size_t len, newlen, reqlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK; char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen; int hdrlen;
@ -203,7 +206,8 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
len = sdslen(s); len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype); sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen); reqlen = newlen = (len+addlen);
if (newlen <= len) return NULL; /* Catch size_t overflow */
if (newlen < SDS_MAX_PREALLOC) if (newlen < SDS_MAX_PREALLOC)
newlen *= 2; newlen *= 2;
else else
@ -217,12 +221,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
if (type == SDS_TYPE_5) type = SDS_TYPE_8; if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type); hdrlen = sdsHdrSize(type);
if (hdrlen+newlen+1 <= reqlen) return NULL; /* Catch size_t overflow */
if (oldtype==type) { if (oldtype==type) {
newsh = s_realloc(sh, hdrlen+newlen+1); newsh = s_realloc(sh, hdrlen+newlen+1);
if (newsh == NULL) { if (newsh == NULL) return NULL;
s_free(sh);
return NULL;
}
s = (char*)newsh+hdrlen; s = (char*)newsh+hdrlen;
} else { } else {
/* Since the header size changes, need to move the string forward, /* Since the header size changes, need to move the string forward,
@ -416,7 +418,7 @@ sds sdscpylen(sds s, const char *t, size_t len) {
return s; return s;
} }
/* Like sdscpylen() but 't' must be a null-termined string so that the length /* Like sdscpylen() but 't' must be a null-terminated string so that the length
* of the string is obtained with strlen(). */ * of the string is obtained with strlen(). */
sds sdscpy(sds s, const char *t) { sds sdscpy(sds s, const char *t) {
return sdscpylen(s, t, strlen(t)); return sdscpylen(s, t, strlen(t));
@ -581,7 +583,7 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
*/ */
sds sdscatfmt(sds s, char const *fmt, ...) { sds sdscatfmt(sds s, char const *fmt, ...) {
const char *f = fmt; const char *f = fmt;
int i; long i;
va_list ap; va_list ap;
va_start(ap,fmt); va_start(ap,fmt);
@ -690,10 +692,10 @@ fmt_error:
* Output will be just "Hello World". * Output will be just "Hello World".
*/ */
sds sdstrim(sds s, const char *cset) { sds sdstrim(sds s, const char *cset) {
char *start, *end, *sp, *ep; char *end, *sp, *ep;
size_t len; size_t len;
sp = start = s; sp = s;
ep = end = s+sdslen(s)-1; ep = end = s+sdslen(s)-1;
while(sp <= end && strchr(cset, *sp)) sp++; while(sp <= end && strchr(cset, *sp)) sp++;
while(ep > sp && strchr(cset, *ep)) ep--; while(ep > sp && strchr(cset, *ep)) ep--;
@ -715,15 +717,20 @@ sds sdstrim(sds s, const char *cset) {
* *
* The string is modified in-place. * The string is modified in-place.
* *
* Return value:
* -1 (error) if sdslen(s) is larger than maximum positive ssize_t value.
* 0 on success.
*
* Example: * Example:
* *
* s = sdsnew("Hello World"); * s = sdsnew("Hello World");
* sdsrange(s,1,-1); => "ello World" * sdsrange(s,1,-1); => "ello World"
*/ */
void sdsrange(sds s, int start, int end) { int sdsrange(sds s, ssize_t start, ssize_t end) {
size_t newlen, len = sdslen(s); size_t newlen, len = sdslen(s);
if (len > SSIZE_MAX) return -1;
if (len == 0) return; if (len == 0) return 0;
if (start < 0) { if (start < 0) {
start = len+start; start = len+start;
if (start < 0) start = 0; if (start < 0) start = 0;
@ -734,9 +741,9 @@ void sdsrange(sds s, int start, int end) {
} }
newlen = (start > end) ? 0 : (end-start)+1; newlen = (start > end) ? 0 : (end-start)+1;
if (newlen != 0) { if (newlen != 0) {
if (start >= (signed)len) { if (start >= (ssize_t)len) {
newlen = 0; newlen = 0;
} else if (end >= (signed)len) { } else if (end >= (ssize_t)len) {
end = len-1; end = len-1;
newlen = (start > end) ? 0 : (end-start)+1; newlen = (start > end) ? 0 : (end-start)+1;
} }
@ -746,18 +753,19 @@ void sdsrange(sds s, int start, int end) {
if (start && newlen) memmove(s, s+start, newlen); if (start && newlen) memmove(s, s+start, newlen);
s[newlen] = 0; s[newlen] = 0;
sdssetlen(s,newlen); sdssetlen(s,newlen);
return 0;
} }
/* Apply tolower() to every character of the sds string 's'. */ /* Apply tolower() to every character of the sds string 's'. */
void sdstolower(sds s) { void sdstolower(sds s) {
int len = sdslen(s), j; size_t len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = tolower(s[j]); for (j = 0; j < len; j++) s[j] = tolower(s[j]);
} }
/* Apply toupper() to every character of the sds string 's'. */ /* Apply toupper() to every character of the sds string 's'. */
void sdstoupper(sds s) { void sdstoupper(sds s) {
int len = sdslen(s), j; size_t len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = toupper(s[j]); for (j = 0; j < len; j++) s[j] = toupper(s[j]);
} }
@ -878,7 +886,7 @@ sds sdscatrepr(sds s, const char *p, size_t len) {
case '\a': s = sdscatlen(s,"\\a",2); break; case '\a': s = sdscatlen(s,"\\a",2); break;
case '\b': s = sdscatlen(s,"\\b",2); break; case '\b': s = sdscatlen(s,"\\b",2); break;
default: default:
if (isprint(*p)) if (isprint((int) *p))
s = sdscatprintf(s,"%c",*p); s = sdscatprintf(s,"%c",*p);
else else
s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
@ -889,13 +897,6 @@ sds sdscatrepr(sds s, const char *p, size_t len) {
return sdscatlen(s,"\"",1); 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 /* Helper function for sdssplitargs() that converts a hex digit into an
* integer from 0 to 15 */ * integer from 0 to 15 */
int hex_digit_to_int(char c) { int hex_digit_to_int(char c) {
@ -947,7 +948,7 @@ sds *sdssplitargs(const char *line, int *argc) {
*argc = 0; *argc = 0;
while(1) { while(1) {
/* skip blanks */ /* skip blanks */
while(*p && isspace(*p)) p++; while(*p && isspace((int) *p)) p++;
if (*p) { if (*p) {
/* get a token */ /* get a token */
int inq=0; /* set to 1 if we are in "quotes" */ int inq=0; /* set to 1 if we are in "quotes" */
@ -958,8 +959,8 @@ sds *sdssplitargs(const char *line, int *argc) {
while(!done) { while(!done) {
if (inq) { if (inq) {
if (*p == '\\' && *(p+1) == 'x' && if (*p == '\\' && *(p+1) == 'x' &&
is_hex_digit(*(p+2)) && isxdigit((int) *(p+2)) &&
is_hex_digit(*(p+3))) isxdigit((int) *(p+3)))
{ {
unsigned char byte; unsigned char byte;
@ -983,7 +984,7 @@ sds *sdssplitargs(const char *line, int *argc) {
} else if (*p == '"') { } else if (*p == '"') {
/* closing quote must be followed by a space or /* closing quote must be followed by a space or
* nothing at all. */ * nothing at all. */
if (*(p+1) && !isspace(*(p+1))) goto err; if (*(p+1) && !isspace((int) *(p+1))) goto err;
done=1; done=1;
} else if (!*p) { } else if (!*p) {
/* unterminated quotes */ /* unterminated quotes */
@ -998,7 +999,7 @@ sds *sdssplitargs(const char *line, int *argc) {
} else if (*p == '\'') { } else if (*p == '\'') {
/* closing quote must be followed by a space or /* closing quote must be followed by a space or
* nothing at all. */ * nothing at all. */
if (*(p+1) && !isspace(*(p+1))) goto err; if (*(p+1) && !isspace((int) *(p+1))) goto err;
done=1; done=1;
} else if (!*p) { } else if (!*p) {
/* unterminated quotes */ /* unterminated quotes */

37
sds.h
View File

@ -34,6 +34,13 @@
#define __SDS_H #define __SDS_H
#define SDS_MAX_PREALLOC (1024*1024) #define SDS_MAX_PREALLOC (1024*1024)
#ifdef _MSC_VER
typedef long long ssize_t;
#define SSIZE_MAX (LLONG_MAX >> 1)
#ifndef __clang__
#define __attribute__(x)
#endif
#endif
#include <sys/types.h> #include <sys/types.h>
#include <stdarg.h> #include <stdarg.h>
@ -132,20 +139,20 @@ static inline void sdssetlen(sds s, size_t newlen) {
case SDS_TYPE_5: case SDS_TYPE_5:
{ {
unsigned char *fp = ((unsigned char*)s)-1; unsigned char *fp = ((unsigned char*)s)-1;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));
} }
break; break;
case SDS_TYPE_8: case SDS_TYPE_8:
SDS_HDR(8,s)->len = newlen; SDS_HDR(8,s)->len = (uint8_t)newlen;
break; break;
case SDS_TYPE_16: case SDS_TYPE_16:
SDS_HDR(16,s)->len = newlen; SDS_HDR(16,s)->len = (uint16_t)newlen;
break; break;
case SDS_TYPE_32: case SDS_TYPE_32:
SDS_HDR(32,s)->len = newlen; SDS_HDR(32,s)->len = (uint32_t)newlen;
break; break;
case SDS_TYPE_64: case SDS_TYPE_64:
SDS_HDR(64,s)->len = newlen; SDS_HDR(64,s)->len = (uint64_t)newlen;
break; break;
} }
} }
@ -156,21 +163,21 @@ static inline void sdsinclen(sds s, size_t inc) {
case SDS_TYPE_5: case SDS_TYPE_5:
{ {
unsigned char *fp = ((unsigned char*)s)-1; unsigned char *fp = ((unsigned char*)s)-1;
unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
} }
break; break;
case SDS_TYPE_8: case SDS_TYPE_8:
SDS_HDR(8,s)->len += inc; SDS_HDR(8,s)->len += (uint8_t)inc;
break; break;
case SDS_TYPE_16: case SDS_TYPE_16:
SDS_HDR(16,s)->len += inc; SDS_HDR(16,s)->len += (uint16_t)inc;
break; break;
case SDS_TYPE_32: case SDS_TYPE_32:
SDS_HDR(32,s)->len += inc; SDS_HDR(32,s)->len += (uint32_t)inc;
break; break;
case SDS_TYPE_64: case SDS_TYPE_64:
SDS_HDR(64,s)->len += inc; SDS_HDR(64,s)->len += (uint64_t)inc;
break; break;
} }
} }
@ -200,16 +207,16 @@ static inline void sdssetalloc(sds s, size_t newlen) {
/* Nothing to do, this type has no total allocation info. */ /* Nothing to do, this type has no total allocation info. */
break; break;
case SDS_TYPE_8: case SDS_TYPE_8:
SDS_HDR(8,s)->alloc = newlen; SDS_HDR(8,s)->alloc = (uint8_t)newlen;
break; break;
case SDS_TYPE_16: case SDS_TYPE_16:
SDS_HDR(16,s)->alloc = newlen; SDS_HDR(16,s)->alloc = (uint16_t)newlen;
break; break;
case SDS_TYPE_32: case SDS_TYPE_32:
SDS_HDR(32,s)->alloc = newlen; SDS_HDR(32,s)->alloc = (uint32_t)newlen;
break; break;
case SDS_TYPE_64: case SDS_TYPE_64:
SDS_HDR(64,s)->alloc = newlen; SDS_HDR(64,s)->alloc = (uint64_t)newlen;
break; break;
} }
} }
@ -236,7 +243,7 @@ sds sdscatprintf(sds s, const char *fmt, ...);
sds sdscatfmt(sds s, char const *fmt, ...); sds sdscatfmt(sds s, char const *fmt, ...);
sds sdstrim(sds s, const char *cset); sds sdstrim(sds s, const char *cset);
void sdsrange(sds s, int start, int end); int sdsrange(sds s, ssize_t start, ssize_t end);
void sdsupdatelen(sds s); void sdsupdatelen(sds s);
void sdsclear(sds s); void sdsclear(sds s);
int sdscmp(const sds s1, const sds s2); int sdscmp(const sds s1, const sds s2);

View File

@ -37,6 +37,8 @@
* the include of your alternate allocator if needed (not needed in order * the include of your alternate allocator if needed (not needed in order
* to use the default libc allocator). */ * to use the default libc allocator). */
#define s_malloc malloc #include "alloc.h"
#define s_realloc realloc
#define s_free free #define s_malloc hi_malloc
#define s_realloc hi_realloc
#define s_free hi_free

View File

@ -180,22 +180,59 @@ int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen)
/* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as
* EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX
* logic consistent. */ * logic consistent.
if (errno == EWOULDBLOCK) { * Additionally, WSAALREADY is can be reported as WSAEINVAL to and this is
* translated to EIO. Convert appropriately
*/
int err = errno;
if (err == EWOULDBLOCK) {
errno = EINPROGRESS; errno = EINPROGRESS;
} }
else if (err == EIO) {
errno = EALREADY;
}
return ret != SOCKET_ERROR ? ret : -1; return ret != SOCKET_ERROR ? ret : -1;
} }
int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) { int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) {
int ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); int ret = 0;
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
if (*optlen >= sizeof (struct timeval)) {
struct timeval *tv = optval;
DWORD timeout = 0;
socklen_t dwlen = 0;
ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen);
tv->tv_sec = timeout / 1000;
tv->tv_usec = (timeout * 1000) % 1000000;
} else {
ret = WSAEFAULT;
}
*optlen = sizeof (struct timeval);
} else {
ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);
}
if (ret != SOCKET_ERROR && level == SOL_SOCKET && optname == SO_ERROR) {
/* translate SO_ERROR codes, if non-zero */
int err = *(int*)optval;
if (err != 0) {
err = _wsaErrorToErrno(err);
*(int*)optval = err;
}
}
_updateErrno(ret != SOCKET_ERROR); _updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1; return ret != SOCKET_ERROR ? ret : -1;
} }
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) { int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
int ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen); int ret = 0;
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
const struct timeval *tv = optval;
DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
} else {
ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen);
}
_updateErrno(ret != SOCKET_ERROR); _updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1; return ret != SOCKET_ERROR ? ret : -1;
} }
@ -223,4 +260,21 @@ int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
_updateErrno(ret != SOCKET_ERROR); _updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1; return ret != SOCKET_ERROR ? ret : -1;
} }
int win32_redisKeepAlive(SOCKET sockfd, int interval_ms) {
struct tcp_keepalive cfg;
DWORD bytes_in;
int res;
cfg.onoff = 1;
cfg.keepaliveinterval = interval_ms;
cfg.keepalivetime = interval_ms;
res = WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, &cfg,
sizeof(struct tcp_keepalive), NULL, 0,
&bytes_in, NULL, NULL);
return res == 0 ? 0 : _wsaErrorToErrno(res);
}
#endif /* _WIN32 */ #endif /* _WIN32 */

View File

@ -49,6 +49,12 @@
#include <winsock2.h> #include <winsock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <stddef.h> #include <stddef.h>
#include <errno.h>
#include <mstcpip.h>
#ifdef _MSC_VER
typedef long long ssize_t;
#endif
/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */ /* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */
int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
@ -66,6 +72,8 @@ ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);
typedef ULONG nfds_t; typedef ULONG nfds_t;
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout); int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);
int win32_redisKeepAlive(SOCKET sockfd, int interval_ms);
#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION #ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION
#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res) #define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)
#undef gai_strerror #undef gai_strerror

620
ssl.c Normal file
View File

@ -0,0 +1,620 @@
/*
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2019, Redis Labs
*
* 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 "hiredis.h"
#include "async.h"
#include "net.h"
#include <assert.h>
#include <errno.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#include <wincrypt.h>
#ifdef OPENSSL_IS_BORINGSSL
#undef X509_NAME
#undef X509_EXTENSIONS
#undef PKCS7_ISSUER_AND_SERIAL
#undef PKCS7_SIGNER_INFO
#undef OCSP_REQUEST
#undef OCSP_RESPONSE
#endif
#else
#include <pthread.h>
#endif
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "win32.h"
#include "async_private.h"
#include "hiredis_ssl.h"
#define OPENSSL_1_1_0 0x10100000L
void __redisSetError(redisContext *c, int type, const char *str);
struct redisSSLContext {
/* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
SSL_CTX *ssl_ctx;
/* Requested SNI, or NULL */
char *server_name;
};
/* The SSL connection context is attached to SSL/TLS connections as a privdata. */
typedef struct redisSSL {
/**
* OpenSSL SSL object.
*/
SSL *ssl;
/**
* SSL_write() requires to be called again with the same arguments it was
* previously called with in the event of an SSL_read/SSL_write situation
*/
size_t lastLen;
/** Whether the SSL layer requires read (possibly before a write) */
int wantRead;
/**
* Whether a write was requested prior to a read. If set, the write()
* should resume whenever a read takes place, if possible
*/
int pendingWrite;
} redisSSL;
/* Forward declaration */
redisContextFuncs redisContextSSLFuncs;
/**
* OpenSSL global initialization and locking handling callbacks.
* Note that this is only required for OpenSSL < 1.1.0.
*/
#if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0
#define HIREDIS_USE_CRYPTO_LOCKS
#endif
#ifdef HIREDIS_USE_CRYPTO_LOCKS
#ifdef _WIN32
typedef CRITICAL_SECTION sslLockType;
static void sslLockInit(sslLockType* l) {
InitializeCriticalSection(l);
}
static void sslLockAcquire(sslLockType* l) {
EnterCriticalSection(l);
}
static void sslLockRelease(sslLockType* l) {
LeaveCriticalSection(l);
}
#else
typedef pthread_mutex_t sslLockType;
static void sslLockInit(sslLockType *l) {
pthread_mutex_init(l, NULL);
}
static void sslLockAcquire(sslLockType *l) {
pthread_mutex_lock(l);
}
static void sslLockRelease(sslLockType *l) {
pthread_mutex_unlock(l);
}
#endif
static sslLockType* ossl_locks;
static void opensslDoLock(int mode, int lkid, const char *f, int line) {
sslLockType *l = ossl_locks + lkid;
if (mode & CRYPTO_LOCK) {
sslLockAcquire(l);
} else {
sslLockRelease(l);
}
(void)f;
(void)line;
}
static int initOpensslLocks(void) {
unsigned ii, nlocks;
if (CRYPTO_get_locking_callback() != NULL) {
/* Someone already set the callback before us. Don't destroy it! */
return REDIS_OK;
}
nlocks = CRYPTO_num_locks();
ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks);
if (ossl_locks == NULL)
return REDIS_ERR;
for (ii = 0; ii < nlocks; ii++) {
sslLockInit(ossl_locks + ii);
}
CRYPTO_set_locking_callback(opensslDoLock);
return REDIS_OK;
}
#endif /* HIREDIS_USE_CRYPTO_LOCKS */
int redisInitOpenSSL(void)
{
#ifdef HIREDIS_USE_CRYPTO_LOCKS
SSL_library_init();
initOpensslLocks();
#endif
return REDIS_OK;
}
/**
* redisSSLContext helper context destruction.
*/
const char *redisSSLContextGetError(redisSSLContextError error)
{
switch (error) {
case REDIS_SSL_CTX_NONE:
return "No Error";
case REDIS_SSL_CTX_CREATE_FAILED:
return "Failed to create OpenSSL SSL_CTX";
case REDIS_SSL_CTX_CERT_KEY_REQUIRED:
return "Client cert and key must both be specified or skipped";
case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:
return "Failed to load CA Certificate or CA Path";
case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:
return "Failed to load client certificate";
case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
return "Failed to load private key";
case REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED:
return "Failed to open system certificate store";
case REDIS_SSL_CTX_OS_CERT_ADD_FAILED:
return "Failed to add CA certificates obtained from system to the SSL context";
default:
return "Unknown error code";
}
}
void redisFreeSSLContext(redisSSLContext *ctx)
{
if (!ctx)
return;
if (ctx->server_name) {
hi_free(ctx->server_name);
ctx->server_name = NULL;
}
if (ctx->ssl_ctx) {
SSL_CTX_free(ctx->ssl_ctx);
ctx->ssl_ctx = NULL;
}
hi_free(ctx);
}
/**
* redisSSLContext helper context initialization.
*/
redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
const char *cert_filename, const char *private_key_filename,
const char *server_name, redisSSLContextError *error)
{
redisSSLOptions options = {
.cacert_filename = cacert_filename,
.capath = capath,
.cert_filename = cert_filename,
.private_key_filename = private_key_filename,
.server_name = server_name,
.verify_mode = REDIS_SSL_VERIFY_PEER,
};
return redisCreateSSLContextWithOptions(&options, error);
}
redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options, redisSSLContextError *error) {
const char *cacert_filename = options->cacert_filename;
const char *capath = options->capath;
const char *cert_filename = options->cert_filename;
const char *private_key_filename = options->private_key_filename;
const char *server_name = options->server_name;
#ifdef _WIN32
HCERTSTORE win_store = NULL;
PCCERT_CONTEXT win_ctx = NULL;
#endif
redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));
if (ctx == NULL)
goto error;
const SSL_METHOD *ssl_method;
#if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0
ssl_method = TLS_client_method();
#else
ssl_method = SSLv23_client_method();
#endif
ctx->ssl_ctx = SSL_CTX_new(ssl_method);
if (!ctx->ssl_ctx) {
if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;
goto error;
}
#if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0
SSL_CTX_set_min_proto_version(ctx->ssl_ctx, TLS1_2_VERSION);
#else
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
#endif
SSL_CTX_set_verify(ctx->ssl_ctx, options->verify_mode, NULL);
if ((cert_filename != NULL && private_key_filename == NULL) ||
(private_key_filename != NULL && cert_filename == NULL)) {
if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;
goto error;
}
if (capath || cacert_filename) {
#ifdef _WIN32
if (0 == strcmp(cacert_filename, "wincert")) {
win_store = CertOpenSystemStore(NULL, "Root");
if (!win_store) {
if (error) *error = REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED;
goto error;
}
X509_STORE* store = SSL_CTX_get_cert_store(ctx->ssl_ctx);
while (win_ctx = CertEnumCertificatesInStore(win_store, win_ctx)) {
X509* x509 = NULL;
x509 = d2i_X509(NULL, (const unsigned char**)&win_ctx->pbCertEncoded, win_ctx->cbCertEncoded);
if (x509) {
if ((1 != X509_STORE_add_cert(store, x509)) ||
(1 != SSL_CTX_add_client_CA(ctx->ssl_ctx, x509)))
{
if (error) *error = REDIS_SSL_CTX_OS_CERT_ADD_FAILED;
goto error;
}
X509_free(x509);
}
}
CertFreeCertificateContext(win_ctx);
CertCloseStore(win_store, 0);
} else
#endif
if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
goto error;
}
} else {
if (!SSL_CTX_set_default_verify_paths(ctx->ssl_ctx)) {
if (error) *error = REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED;
goto error;
}
}
if (cert_filename) {
if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) {
if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED;
goto error;
}
if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) {
if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED;
goto error;
}
}
if (server_name)
ctx->server_name = hi_strdup(server_name);
return ctx;
error:
#ifdef _WIN32
CertFreeCertificateContext(win_ctx);
CertCloseStore(win_store, 0);
#endif
redisFreeSSLContext(ctx);
return NULL;
}
/**
* SSL Connection initialization.
*/
static int redisSSLConnect(redisContext *c, SSL *ssl) {
if (c->privctx) {
__redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
return REDIS_ERR;
}
redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));
if (rssl == NULL) {
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR;
}
rssl->ssl = ssl;
SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_set_fd(rssl->ssl, c->fd);
SSL_set_connect_state(rssl->ssl);
ERR_clear_error();
int rv = SSL_connect(rssl->ssl);
if (rv == 1) {
c->funcs = &redisContextSSLFuncs;
c->privctx = rssl;
return REDIS_OK;
}
rv = SSL_get_error(rssl->ssl, rv);
if (((c->flags & REDIS_BLOCK) == 0) &&
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE))
{
c->funcs = &redisContextSSLFuncs;
c->privctx = rssl;
return REDIS_OK;
}
if (c->err == 0) {
char err[512];
if (rv == SSL_ERROR_SYSCALL)
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
else {
unsigned long e = ERR_peek_last_error();
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
ERR_reason_error_string(e));
}
__redisSetError(c, REDIS_ERR_IO, err);
}
hi_free(rssl);
return REDIS_ERR;
}
/**
* A wrapper around redisSSLConnect() for users who manage their own context and
* create their own SSL object.
*/
int redisInitiateSSL(redisContext *c, SSL *ssl) {
return redisSSLConnect(c, ssl);
}
/**
* A wrapper around redisSSLConnect() for users who use redisSSLContext and don't
* manage their own SSL objects.
*/
int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
{
if (!c || !redis_ssl_ctx)
return REDIS_ERR;
/* We want to verify that redisSSLConnect() won't fail on this, as it will
* not own the SSL object in that case and we'll end up leaking.
*/
if (c->privctx)
return REDIS_ERR;
SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
if (!ssl) {
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
goto error;
}
if (redis_ssl_ctx->server_name) {
if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {
__redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI");
goto error;
}
}
if (redisSSLConnect(c, ssl) != REDIS_OK) {
goto error;
}
return REDIS_OK;
error:
if (ssl)
SSL_free(ssl);
return REDIS_ERR;
}
static int maybeCheckWant(redisSSL *rssl, int rv) {
/**
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
* and true is returned. False is returned otherwise
*/
if (rv == SSL_ERROR_WANT_READ) {
rssl->wantRead = 1;
return 1;
} else if (rv == SSL_ERROR_WANT_WRITE) {
rssl->pendingWrite = 1;
return 1;
} else {
return 0;
}
}
/**
* Implementation of redisContextFuncs for SSL connections.
*/
static void redisSSLFree(void *privctx){
redisSSL *rsc = privctx;
if (!rsc) return;
if (rsc->ssl) {
SSL_free(rsc->ssl);
rsc->ssl = NULL;
}
hi_free(rsc);
}
static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
redisSSL *rssl = c->privctx;
int nread = SSL_read(rssl->ssl, buf, bufcap);
if (nread > 0) {
return nread;
} else if (nread == 0) {
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
return -1;
} else {
int err = SSL_get_error(rssl->ssl, nread);
if (c->flags & REDIS_BLOCK) {
/**
* In blocking mode, we should never end up in a situation where
* we get an error without it being an actual error, except
* in the case of EINTR, which can be spuriously received from
* debuggers or whatever.
*/
if (errno == EINTR) {
return 0;
} else {
const char *msg = NULL;
if (errno == EAGAIN) {
msg = "Resource temporarily unavailable";
}
__redisSetError(c, REDIS_ERR_IO, msg);
return -1;
}
}
/**
* We can very well get an EWOULDBLOCK/EAGAIN, however
*/
if (maybeCheckWant(rssl, err)) {
return 0;
} else {
__redisSetError(c, REDIS_ERR_IO, NULL);
return -1;
}
}
}
static ssize_t redisSSLWrite(redisContext *c) {
redisSSL *rssl = c->privctx;
size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
int rv = SSL_write(rssl->ssl, c->obuf, len);
if (rv > 0) {
rssl->lastLen = 0;
} else if (rv < 0) {
rssl->lastLen = len;
int err = SSL_get_error(rssl->ssl, rv);
if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
return 0;
} else {
__redisSetError(c, REDIS_ERR_IO, NULL);
return -1;
}
}
return rv;
}
static void redisSSLAsyncRead(redisAsyncContext *ac) {
int rv;
redisSSL *rssl = ac->c.privctx;
redisContext *c = &ac->c;
rssl->wantRead = 0;
if (rssl->pendingWrite) {
int done;
/* This is probably just a write event */
rssl->pendingWrite = 0;
rv = redisBufferWrite(c, &done);
if (rv == REDIS_ERR) {
__redisAsyncDisconnect(ac);
return;
} else if (!done) {
_EL_ADD_WRITE(ac);
}
}
rv = redisBufferRead(c);
if (rv == REDIS_ERR) {
__redisAsyncDisconnect(ac);
} else {
_EL_ADD_READ(ac);
redisProcessCallbacks(ac);
}
}
static void redisSSLAsyncWrite(redisAsyncContext *ac) {
int rv, done = 0;
redisSSL *rssl = ac->c.privctx;
redisContext *c = &ac->c;
rssl->pendingWrite = 0;
rv = redisBufferWrite(c, &done);
if (rv == REDIS_ERR) {
__redisAsyncDisconnect(ac);
return;
}
if (!done) {
if (rssl->wantRead) {
/* Need to read-before-write */
rssl->pendingWrite = 1;
_EL_DEL_WRITE(ac);
} else {
/* No extra reads needed, just need to write more */
_EL_ADD_WRITE(ac);
}
} else {
/* Already done! */
_EL_DEL_WRITE(ac);
}
/* Always reschedule a read */
_EL_ADD_READ(ac);
}
redisContextFuncs redisContextSSLFuncs = {
.close = redisNetClose,
.free_privctx = redisSSLFree,
.async_read = redisSSLAsyncRead,
.async_write = redisSSLAsyncWrite,
.read = redisSSLRead,
.write = redisSSLWrite
};

239
sslio.c
View File

@ -1,239 +0,0 @@
#include "hiredis.h"
#include "sslio.h"
#include <assert.h>
#ifdef HIREDIS_SSL
#include <pthread.h>
#include <errno.h>
void __redisSetError(redisContext *c, int type, const char *str);
/**
* Callback used for debugging
*/
static void sslLogCallback(const SSL *ssl, int where, int ret) {
const char *retstr = "";
int should_log = 1;
/* Ignore low-level SSL stuff */
if (where & SSL_CB_ALERT) {
should_log = 1;
}
if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) {
should_log = 1;
}
if ((where & SSL_CB_EXIT) && ret == 0) {
should_log = 1;
}
if (!should_log) {
return;
}
retstr = SSL_alert_type_string(ret);
printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr);
if (where == SSL_CB_HANDSHAKE_DONE) {
printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl));
}
}
typedef pthread_mutex_t sslLockType;
static void sslLockInit(sslLockType *l) {
pthread_mutex_init(l, NULL);
}
static void sslLockAcquire(sslLockType *l) {
pthread_mutex_lock(l);
}
static void sslLockRelease(sslLockType *l) {
pthread_mutex_unlock(l);
}
static pthread_mutex_t *ossl_locks;
static void opensslDoLock(int mode, int lkid, const char *f, int line) {
sslLockType *l = ossl_locks + lkid;
if (mode & CRYPTO_LOCK) {
sslLockAcquire(l);
} else {
sslLockRelease(l);
}
(void)f;
(void)line;
}
static void initOpensslLocks(void) {
unsigned ii, nlocks;
if (CRYPTO_get_locking_callback() != NULL) {
/* Someone already set the callback before us. Don't destroy it! */
return;
}
nlocks = CRYPTO_num_locks();
ossl_locks = malloc(sizeof(*ossl_locks) * nlocks);
for (ii = 0; ii < nlocks; ii++) {
sslLockInit(ossl_locks + ii);
}
CRYPTO_set_locking_callback(opensslDoLock);
}
void redisFreeSsl(redisSsl *ssl){
if (ssl->ctx) {
SSL_CTX_free(ssl->ctx);
}
if (ssl->ssl) {
SSL_free(ssl->ssl);
}
free(ssl);
}
int redisSslCreate(redisContext *c, const char *capath, const char *certpath,
const char *keypath, const char *servername) {
assert(!c->ssl);
c->ssl = calloc(1, sizeof(*c->ssl));
static int isInit = 0;
if (!isInit) {
isInit = 1;
SSL_library_init();
initOpensslLocks();
}
redisSsl *s = c->ssl;
s->ctx = SSL_CTX_new(SSLv23_client_method());
SSL_CTX_set_info_callback(s->ctx, sslLogCallback);
SSL_CTX_set_mode(s->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_options(s->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_verify(s->ctx, SSL_VERIFY_PEER, NULL);
if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) {
__redisSetError(c, REDIS_ERR, "certpath and keypath must be specified together");
return REDIS_ERR;
}
if (capath) {
if (!SSL_CTX_load_verify_locations(s->ctx, capath, NULL)) {
__redisSetError(c, REDIS_ERR, "Invalid CA certificate");
return REDIS_ERR;
}
}
if (certpath) {
if (!SSL_CTX_use_certificate_chain_file(s->ctx, certpath)) {
__redisSetError(c, REDIS_ERR, "Invalid client certificate");
return REDIS_ERR;
}
if (!SSL_CTX_use_PrivateKey_file(s->ctx, keypath, SSL_FILETYPE_PEM)) {
__redisSetError(c, REDIS_ERR, "Invalid client key");
return REDIS_ERR;
}
}
s->ssl = SSL_new(s->ctx);
if (!s->ssl) {
__redisSetError(c, REDIS_ERR, "Couldn't create new SSL instance");
return REDIS_ERR;
}
if (servername) {
if (!SSL_set_tlsext_host_name(s->ssl, servername)) {
__redisSetError(c, REDIS_ERR, "Couldn't set server name indication");
return REDIS_ERR;
}
}
SSL_set_fd(s->ssl, c->fd);
SSL_set_connect_state(s->ssl);
c->flags |= REDIS_SSL;
int rv = SSL_connect(c->ssl->ssl);
if (rv == 1) {
return REDIS_OK;
}
rv = SSL_get_error(s->ssl, rv);
if (((c->flags & REDIS_BLOCK) == 0) &&
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
return REDIS_OK;
}
if (c->err == 0) {
__redisSetError(c, REDIS_ERR_IO, "SSL_connect() failed");
}
return REDIS_ERR;
}
static int maybeCheckWant(redisSsl *rssl, int rv) {
/**
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
* and true is returned. False is returned otherwise
*/
if (rv == SSL_ERROR_WANT_READ) {
rssl->wantRead = 1;
return 1;
} else if (rv == SSL_ERROR_WANT_WRITE) {
rssl->pendingWrite = 1;
return 1;
} else {
return 0;
}
}
int redisSslRead(redisContext *c, char *buf, size_t bufcap) {
int nread = SSL_read(c->ssl->ssl, buf, bufcap);
if (nread > 0) {
return nread;
} else if (nread == 0) {
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
return -1;
} else {
int err = SSL_get_error(c->ssl->ssl, nread);
if (c->flags & REDIS_BLOCK) {
/**
* In blocking mode, we should never end up in a situation where
* we get an error without it being an actual error, except
* in the case of EINTR, which can be spuriously received from
* debuggers or whatever.
*/
if (errno == EINTR) {
return 0;
} else {
const char *msg = NULL;
if (errno == EAGAIN) {
msg = "Timed out";
}
__redisSetError(c, REDIS_ERR_IO, msg);
return -1;
}
}
/**
* We can very well get an EWOULDBLOCK/EAGAIN, however
*/
if (maybeCheckWant(c->ssl, err)) {
return 0;
} else {
__redisSetError(c, REDIS_ERR_IO, NULL);
return -1;
}
}
}
int redisSslWrite(redisContext *c) {
size_t len = c->ssl->lastLen ? c->ssl->lastLen : sdslen(c->obuf);
int rv = SSL_write(c->ssl->ssl, c->obuf, len);
if (rv > 0) {
c->ssl->lastLen = 0;
} else if (rv < 0) {
c->ssl->lastLen = len;
int err = SSL_get_error(c->ssl->ssl, rv);
if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(c->ssl, err)) {
return 0;
} else {
__redisSetError(c, REDIS_ERR_IO, NULL);
return -1;
}
}
return rv;
}
#endif

64
sslio.h
View File

@ -1,64 +0,0 @@
#ifndef REDIS_SSLIO_H
#define REDIS_SSLIO_H
#ifndef HIREDIS_SSL
typedef struct redisSsl {
size_t lastLen;
int wantRead;
int pendingWrite;
} redisSsl;
static inline void redisFreeSsl(redisSsl *ssl) {
(void)ssl;
}
static inline int redisSslCreate(struct redisContext *c, const char *ca,
const char *cert, const char *key, const char *servername) {
(void)c;(void)ca;(void)cert;(void)key;(void)servername;
return REDIS_ERR;
}
static inline int redisSslRead(struct redisContext *c, char *s, size_t n) {
(void)c;(void)s;(void)n;
return -1;
}
static inline int redisSslWrite(struct redisContext *c) {
(void)c;
return -1;
}
#else
#include <openssl/ssl.h>
/**
* This file contains routines for HIREDIS' SSL
*/
typedef struct redisSsl {
SSL *ssl;
SSL_CTX *ctx;
/**
* SSL_write() requires to be called again with the same arguments it was
* previously called with in the event of an SSL_read/SSL_write situation
*/
size_t lastLen;
/** Whether the SSL layer requires read (possibly before a write) */
int wantRead;
/**
* Whether a write was requested prior to a read. If set, the write()
* should resume whenever a read takes place, if possible
*/
int pendingWrite;
} redisSsl;
struct redisContext;
void redisFreeSsl(redisSsl *);
int redisSslCreate(struct redisContext *c, const char *caPath,
const char *certPath, const char *keyPath, const char *servername);
int redisSslRead(struct redisContext *c, char *buf, size_t bufcap);
int redisSslWrite(struct redisContext *c);
#endif /* HIREDIS_SSL */
#endif /* HIREDIS_SSLIO_H */

1730
test.c

File diff suppressed because it is too large Load Diff

95
test.sh
View File

@ -2,24 +2,111 @@
REDIS_SERVER=${REDIS_SERVER:-redis-server} REDIS_SERVER=${REDIS_SERVER:-redis-server}
REDIS_PORT=${REDIS_PORT:-56379} REDIS_PORT=${REDIS_PORT:-56379}
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
TEST_SSL=${TEST_SSL:-0}
SKIPS_AS_FAILS=${SKIPS_AS_FAILS:-0}
ENABLE_DEBUG_CMD=
SSL_TEST_ARGS=
SKIPS_ARG=${SKIPS_ARG:-}
REDIS_DOCKER=${REDIS_DOCKER:-}
# We need to enable the DEBUG command for redis-server >= 7.0.0
REDIS_MAJOR_VERSION="$(${REDIS_SERVER} --version|awk -F'[^0-9]+' '{ print $2 }')"
if [ "$REDIS_MAJOR_VERSION" -gt "6" ]; then
ENABLE_DEBUG_CMD="enable-debug-command local"
fi
tmpdir=$(mktemp -d) tmpdir=$(mktemp -d)
PID_FILE=${tmpdir}/hiredis-test-redis.pid PID_FILE=${tmpdir}/hiredis-test-redis.pid
SOCK_FILE=${tmpdir}/hiredis-test-redis.sock SOCK_FILE=${tmpdir}/hiredis-test-redis.sock
if [ "$TEST_SSL" = "1" ]; then
SSL_CA_CERT=${tmpdir}/ca.crt
SSL_CA_KEY=${tmpdir}/ca.key
SSL_CERT=${tmpdir}/redis.crt
SSL_KEY=${tmpdir}/redis.key
openssl genrsa -out ${tmpdir}/ca.key 4096
openssl req \
-x509 -new -nodes -sha256 \
-key ${SSL_CA_KEY} \
-days 3650 \
-subj '/CN=Hiredis Test CA' \
-out ${SSL_CA_CERT}
openssl genrsa -out ${SSL_KEY} 2048
openssl req \
-new -sha256 \
-key ${SSL_KEY} \
-subj '/CN=Hiredis Test Cert' | \
openssl x509 \
-req -sha256 \
-CA ${SSL_CA_CERT} \
-CAkey ${SSL_CA_KEY} \
-CAserial ${tmpdir}/ca.txt \
-CAcreateserial \
-days 365 \
-out ${SSL_CERT}
SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}"
fi
cleanup() { cleanup() {
if [ -n "${REDIS_DOCKER}" ] ; then
docker kill redis-test-server
else
set +e set +e
kill $(cat ${PID_FILE}) kill $(cat ${PID_FILE})
fi
rm -rf ${tmpdir} rm -rf ${tmpdir}
} }
trap cleanup INT TERM EXIT trap cleanup INT TERM EXIT
${REDIS_SERVER} - <<EOF # base config
daemonize yes cat > ${tmpdir}/redis.conf <<EOF
pidfile ${PID_FILE} pidfile ${PID_FILE}
port ${REDIS_PORT} port ${REDIS_PORT}
bind 127.0.0.1
unixsocket ${SOCK_FILE} unixsocket ${SOCK_FILE}
unixsocketperm 777
EOF EOF
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} # if not running in docker add these:
if [ ! -n "${REDIS_DOCKER}" ]; then
cat >> ${tmpdir}/redis.conf <<EOF
daemonize yes
${ENABLE_DEBUG_CMD}
bind 127.0.0.1
EOF
fi
# if doing ssl, add these
if [ "$TEST_SSL" = "1" ]; then
cat >> ${tmpdir}/redis.conf <<EOF
tls-port ${REDIS_SSL_PORT}
tls-ca-cert-file ${SSL_CA_CERT}
tls-cert-file ${SSL_CERT}
tls-key-file ${SSL_KEY}
EOF
fi
echo ${tmpdir}
cat ${tmpdir}/redis.conf
if [ -n "${REDIS_DOCKER}" ] ; then
chmod a+wx ${tmpdir}
chmod a+r ${tmpdir}/*
docker run -d --rm --name redis-test-server \
-p ${REDIS_PORT}:${REDIS_PORT} \
-p ${REDIS_SSL_PORT}:${REDIS_SSL_PORT} \
-v ${tmpdir}:${tmpdir} \
${REDIS_DOCKER} \
${REDIS_SERVER} ${tmpdir}/redis.conf
else
${REDIS_SERVER} ${tmpdir}/redis.conf
fi
# Wait until we detect the unix socket
echo waiting for server
while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done
# Treat skips as failures if directed
[ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="${SKIPS_ARG} --skips-as-fails"
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG}

10
win32.h
View File

@ -2,10 +2,20 @@
#define _WIN32_HELPER_INCLUDE #define _WIN32_HELPER_INCLUDE
#ifdef _MSC_VER #ifdef _MSC_VER
#include <winsock2.h> /* for struct timeval */
#ifndef inline #ifndef inline
#define inline __inline #define inline __inline
#endif #endif
#ifndef strcasecmp
#define strcasecmp stricmp
#endif
#ifndef strncasecmp
#define strncasecmp strnicmp
#endif
#ifndef va_copy #ifndef va_copy
#define va_copy(d,s) ((d) = (s)) #define va_copy(d,s) ((d) = (s))
#endif #endif