Commit 2eaf2360 authored by Yossi Gottlieb's avatar Yossi Gottlieb
Browse files

Merge commit 'fad6c713' into hiredis-refresh

parents c2f1815b fad6c713
#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;
}
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
int main(int argc, char **argv) { int main(int argc, char **argv) {
unsigned int j; unsigned int j;
redisSSLContext *ssl; redisSSLContext *ssl;
redisSSLContextError ssl_error; redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;
redisContext *c; redisContext *c;
redisReply *reply; redisReply *reply;
if (argc < 4) { if (argc < 4) {
...@@ -27,9 +27,8 @@ int main(int argc, char **argv) { ...@@ -27,9 +27,8 @@ int main(int argc, char **argv) {
redisInitOpenSSL(); redisInitOpenSSL();
ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error); ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error);
if (!ssl) { if (!ssl || ssl_error != REDIS_SSL_CTX_NONE) {
printf("SSL Context error: %s\n", printf("SSL Context error: %s\n", redisSSLContextGetError(ssl_error));
redisSSLContextGetError(ssl_error));
exit(1); exit(1);
} }
......
...@@ -7,6 +7,54 @@ ...@@ -7,6 +7,54 @@
#include <winsock2.h> /* For struct timeval */ #include <winsock2.h> /* For struct timeval */
#endif #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;
...@@ -87,6 +135,9 @@ int main(int argc, char **argv) { ...@@ -87,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);
......
...@@ -48,10 +48,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ...@@ -48,10 +48,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
memcpy(new_str, data, size); memcpy(new_str, data, size);
new_str[size] = '\0'; new_str[size] = '\0';
redisFormatCommand(&cmd, new_str); if (redisFormatCommand(&cmd, new_str) != -1)
if (cmd != NULL)
hi_free(cmd); hi_free(cmd);
free(new_str); free(new_str);
return 0; return 0;
} }
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
set_and_check(hiredis_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@") set_and_check(hiredis_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
IF (NOT TARGET hiredis::hiredis) IF (NOT TARGET hiredis::@hiredis_export_name@)
INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis-targets.cmake) INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis-targets.cmake)
ENDIF() ENDIF()
SET(hiredis_LIBRARIES hiredis::hiredis) SET(hiredis_LIBRARIES hiredis::@hiredis_export_name@)
SET(hiredis_INCLUDE_DIRS ${hiredis_INCLUDEDIR}) SET(hiredis_INCLUDE_DIRS ${hiredis_INCLUDEDIR})
check_required_components(hiredis) check_required_components(hiredis)
......
...@@ -48,6 +48,7 @@ extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeva ...@@ -48,6 +48,7 @@ extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeva
extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout); extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);
static redisContextFuncs redisContextDefaultFuncs = { static redisContextFuncs redisContextDefaultFuncs = {
.close = redisNetClose,
.free_privctx = NULL, .free_privctx = NULL,
.async_read = redisAsyncRead, .async_read = redisAsyncRead,
.async_write = redisAsyncWrite, .async_write = redisAsyncWrite,
...@@ -221,6 +222,9 @@ static void *createIntegerObject(const redisReadTask *task, long long value) { ...@@ -221,6 +222,9 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) { static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {
redisReply *r, *parent; redisReply *r, *parent;
if (len == SIZE_MAX) // Prevents hi_malloc(0) if len equals to SIZE_MAX
return NULL;
r = createReplyObject(REDIS_REPLY_DOUBLE); r = createReplyObject(REDIS_REPLY_DOUBLE);
if (r == NULL) if (r == NULL)
return NULL; return NULL;
...@@ -399,6 +403,11 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { ...@@ -399,6 +403,11 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
/* 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);
...@@ -477,6 +486,8 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { ...@@ -477,6 +486,8 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
touched = 1; touched = 1;
c++; c++;
if (*c == '\0')
break;
} }
c++; c++;
} }
...@@ -719,7 +730,10 @@ static redisContext *redisContextInit(void) { ...@@ -719,7 +730,10 @@ static redisContext *redisContextInit(void) {
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);
}
hi_sdsfree(c->obuf); hi_sdsfree(c->obuf);
redisReaderFree(c->reader); redisReaderFree(c->reader);
...@@ -733,7 +747,7 @@ void redisFree(redisContext *c) { ...@@ -733,7 +747,7 @@ void redisFree(redisContext *c) {
if (c->privdata && c->free_privdata) if (c->privdata && c->free_privdata)
c->free_privdata(c->privdata); c->free_privdata(c->privdata);
if (c->funcs->free_privctx) if (c->funcs && c->funcs->free_privctx)
c->funcs->free_privctx(c->privctx); c->funcs->free_privctx(c->privctx);
memset(c, 0xff, sizeof(*c)); memset(c, 0xff, sizeof(*c));
...@@ -756,7 +770,9 @@ int redisReconnect(redisContext *c) { ...@@ -756,7 +770,9 @@ int redisReconnect(redisContext *c) {
c->privctx = NULL; c->privctx = NULL;
} }
redisNetClose(c); if (c->funcs && c->funcs->close) {
c->funcs->close(c);
}
hi_sdsfree(c->obuf); hi_sdsfree(c->obuf);
redisReaderFree(c->reader); redisReaderFree(c->reader);
...@@ -806,6 +822,12 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { ...@@ -806,6 +822,12 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) { if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) {
c->flags |= REDIS_NO_AUTO_FREE_REPLIES; 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 /* Set any user supplied RESP3 PUSH handler or use freeReplyObject
* as a default unless specifically flagged that we don't want one. */ * as a default unless specifically flagged that we don't want one. */
...@@ -838,7 +860,9 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { ...@@ -838,7 +860,9 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
return NULL; return NULL;
} }
if (options->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) { if (c->err == 0 && c->fd != REDIS_INVALID_FD &&
options->command_timeout != NULL && (c->flags & REDIS_BLOCK))
{
redisContextSetTimeout(c, *options->command_timeout); redisContextSetTimeout(c, *options->command_timeout);
} }
...@@ -920,11 +944,18 @@ int redisSetTimeout(redisContext *c, const struct timeval tv) { ...@@ -920,11 +944,18 @@ 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. */ /* Set a user provided RESP3 PUSH handler and return any old one set. */
...@@ -964,8 +995,8 @@ int redisBufferRead(redisContext *c) { ...@@ -964,8 +995,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) {
......
...@@ -46,9 +46,9 @@ typedef long long ssize_t; ...@@ -46,9 +46,9 @@ typedef long long ssize_t;
#include "alloc.h" /* for allocation wrappers */ #include "alloc.h" /* for allocation wrappers */
#define HIREDIS_MAJOR 1 #define HIREDIS_MAJOR 1
#define HIREDIS_MINOR 0 #define HIREDIS_MINOR 1
#define HIREDIS_PATCH 3 #define HIREDIS_PATCH 1
#define HIREDIS_SONAME 1.0.3-dev #define HIREDIS_SONAME 1.1.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. */
...@@ -92,6 +92,11 @@ typedef long long ssize_t; ...@@ -92,6 +92,11 @@ typedef long long ssize_t;
/* Flag that indicates the user does not want replies to be automatically freed */ /* Flag that indicates the user does not want replies to be automatically freed */
#define REDIS_NO_AUTO_FREE_REPLIES 0x400 #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
...@@ -149,20 +154,17 @@ struct redisSsl; ...@@ -149,20 +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. */
/* Don't automatically intercept and free RESP3 PUSH replies. */ #define REDIS_OPT_NOAUTOFREEREPLIES 0x10 /* Don't automatically free replies. */
#define REDIS_OPT_NO_PUSH_AUTOFREE 0x08 #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)
* Don't automatically free replies
*/
#define REDIS_OPT_NOAUTOFREEREPLIES 0x10
/* 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
...@@ -220,27 +222,37 @@ typedef struct { ...@@ -220,27 +222,37 @@ typedef struct {
/** /**
* 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) \
(opts)->type = REDIS_CONN_UNIX; \ #define REDIS_OPTIONS_SET_UNIX(opts, path) do { \
(opts)->endpoint.unix_socket = path; (opts)->type = REDIS_CONN_UNIX; \
(opts)->endpoint.unix_socket = path; \
#define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) \ } while(0)
(opts)->privdata = data; \
(opts)->free_privdata = dtor; \ #define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) do { \
(opts)->privdata = data; \
(opts)->free_privdata = dtor; \
} while(0)
typedef struct redisContextFuncs { typedef struct redisContextFuncs {
void (*close)(struct redisContext *);
void (*free_privctx)(void *); void (*free_privctx)(void *);
void (*async_read)(struct redisAsyncContext *); void (*async_read)(struct redisAsyncContext *);
void (*async_write)(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 (*read)(struct redisContext *, char *, size_t);
ssize_t (*write)(struct redisContext *); ssize_t (*write)(struct redisContext *);
} redisContextFuncs; } redisContextFuncs;
/* Context for a connection to Redis */ /* Context for a connection to Redis */
typedef struct redisContext { typedef struct redisContext {
const redisContextFuncs *funcs; /* Function table */ const redisContextFuncs *funcs; /* Function table */
...@@ -310,6 +322,8 @@ int redisReconnect(redisContext *c); ...@@ -310,6 +322,8 @@ int redisReconnect(redisContext *c);
redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn); 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);
......
...@@ -9,4 +9,4 @@ Name: hiredis ...@@ -9,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
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
set_and_check(hiredis_ssl_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@") set_and_check(hiredis_ssl_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
include(CMakeFindDependencyMacro)
find_dependency(OpenSSL)
IF (NOT TARGET hiredis::hiredis_ssl) IF (NOT TARGET hiredis::hiredis_ssl)
INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_ssl-targets.cmake) INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_ssl-targets.cmake)
ENDIF() ENDIF()
......
...@@ -56,11 +56,33 @@ typedef enum { ...@@ -56,11 +56,33 @@ typedef enum {
REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */ 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_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_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_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_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 */ REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */
} redisSSLContextError; } 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. * Return the error message corresponding with the specified error code.
*/ */
...@@ -101,6 +123,18 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * ...@@ -101,6 +123,18 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
const char *cert_filename, const char *private_key_filename, const char *cert_filename, const char *private_key_filename,
const char *server_name, redisSSLContextError *error); 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. * Free a previously created OpenSSL context.
*/ */
......
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
......
...@@ -50,6 +50,8 @@ ...@@ -50,6 +50,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);
...@@ -68,7 +70,7 @@ ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) { ...@@ -68,7 +70,7 @@ ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {
__redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout"); __redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
return -1; 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) {
...@@ -80,15 +82,19 @@ ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) { ...@@ -80,15 +82,19 @@ ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {
} }
ssize_t redisNetWrite(redisContext *c) { ssize_t redisNetWrite(redisContext *c) {
ssize_t nwritten = send(c->fd, c->obuf, hi_sdslen(c->obuf), 0); ssize_t nwritten;
nwritten = send(c->fd, c->obuf, hi_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;
} }
...@@ -166,6 +172,7 @@ int redisKeepAlive(redisContext *c, int interval) { ...@@ -166,6 +172,7 @@ int redisKeepAlive(redisContext *c, int interval) {
int val = 1; int val = 1;
redisFD fd = c->fd; redisFD fd = c->fd;
#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;
...@@ -199,7 +206,15 @@ int redisKeepAlive(redisContext *c, int interval) { ...@@ -199,7 +206,15 @@ 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;
} }
...@@ -213,6 +228,22 @@ int redisSetTcpNoDelay(redisContext *c) { ...@@ -213,6 +228,22 @@ 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;
(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)
...@@ -223,6 +254,7 @@ static int redisContextTimeoutMsec(redisContext *c, long *result) ...@@ -223,6 +254,7 @@ static int redisContextTimeoutMsec(redisContext *c, long *result)
/* 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;
} }
...@@ -277,12 +309,28 @@ int redisCheckConnectDone(redisContext *c, int *completed) { ...@@ -277,12 +309,28 @@ int redisCheckConnectDone(redisContext *c, int *completed) {
*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;
...@@ -317,6 +365,10 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) { ...@@ -317,6 +365,10 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
const void *to_ptr = &tv; const void *to_ptr = &tv;
size_t to_sz = sizeof(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) { 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;
...@@ -400,7 +452,6 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, ...@@ -400,7 +452,6 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
} }
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;
} }
...@@ -417,17 +468,25 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, ...@@ -417,17 +468,25 @@ 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
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); hints.ai_family = AF_INET;
return REDIS_ERR;
} 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));
return REDIS_ERR;
} }
for (p = servinfo; p != NULL; p = p->ai_next) { for (p = servinfo; p != NULL; p = p->ai_next) {
addrretry: addrretry:
......
...@@ -52,5 +52,6 @@ int redisKeepAlive(redisContext *c, int interval); ...@@ -52,5 +52,6 @@ int redisKeepAlive(redisContext *c, int interval);
int redisCheckConnectDone(redisContext *c, int *completed); int redisCheckConnectDone(redisContext *c, int *completed);
int redisSetTcpNoDelay(redisContext *c); int redisSetTcpNoDelay(redisContext *c);
int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout);
#endif #endif
...@@ -303,11 +303,14 @@ static int processLineItem(redisReader *r) { ...@@ -303,11 +303,14 @@ static int processLineItem(redisReader *r) {
d = INFINITY; /* Positive infinite. */ d = INFINITY; /* Positive infinite. */
} else if (len == 4 && strcasecmp(buf,"-inf") == 0) { } else if (len == 4 && strcasecmp(buf,"-inf") == 0) {
d = -INFINITY; /* Negative infinite. */ d = -INFINITY; /* Negative infinite. */
} else if ((len == 3 && strcasecmp(buf,"nan") == 0) ||
(len == 4 && strcasecmp(buf, "-nan") == 0)) {
d = NAN; /* nan. */
} else { } else {
d = strtod((char*)buf,&eptr); d = strtod((char*)buf,&eptr);
/* RESP3 only allows "inf", "-inf", and finite values, while /* RESP3 only allows "inf", "-inf", and finite values, while
* strtod() allows other variations on infinity, NaN, * strtod() allows other variations on infinity,
* etc. We explicity handle our two allowed infinite cases * etc. We explicity handle our two allowed infinite cases and NaN
* above, so strtod() should only result in finite values. */ * above, so strtod() should only result in finite values. */
if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) { if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
...@@ -374,7 +377,7 @@ static int processLineItem(redisReader *r) { ...@@ -374,7 +377,7 @@ static int processLineItem(redisReader *r) {
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*)(uintptr_t)(cur->type);
} }
if (obj == NULL) { if (obj == NULL) {
...@@ -439,7 +442,7 @@ static int processBulkItem(redisReader *r) { ...@@ -439,7 +442,7 @@ static int processBulkItem(redisReader *r) {
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*)(long)cur->type; obj = (void*)(uintptr_t)cur->type;
success = 1; success = 1;
} }
} }
...@@ -536,7 +539,7 @@ static int processAggregateItem(redisReader *r) { ...@@ -536,7 +539,7 @@ static int processAggregateItem(redisReader *r) {
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*)(long)cur->type; obj = (void*)(uintptr_t)cur->type;
if (obj == NULL) { if (obj == NULL) {
__redisReaderSetErrorOOM(r); __redisReaderSetErrorOOM(r);
......
...@@ -90,6 +90,7 @@ hisds hi_sdsnewlen(const void *init, size_t initlen) { ...@@ -90,6 +90,7 @@ hisds hi_sdsnewlen(const void *init, size_t initlen) {
int hdrlen = hi_sdsHdrSize(type); int hdrlen = hi_sdsHdrSize(type);
unsigned char *fp; /* flags pointer. */ unsigned char *fp; /* flags pointer. */
if (hdrlen+initlen+1 <= initlen) return NULL; /* Catch size_t overflow */
sh = hi_s_malloc(hdrlen+initlen+1); sh = hi_s_malloc(hdrlen+initlen+1);
if (sh == NULL) return NULL; if (sh == NULL) return NULL;
if (!init) if (!init)
...@@ -174,7 +175,7 @@ void hi_sdsfree(hisds s) { ...@@ -174,7 +175,7 @@ void hi_sdsfree(hisds 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 hi_sdsupdatelen(hisds s) { void hi_sdsupdatelen(hisds s) {
int reallen = strlen(s); size_t reallen = strlen(s);
hi_sdssetlen(s, reallen); hi_sdssetlen(s, reallen);
} }
...@@ -196,7 +197,7 @@ void hi_sdsclear(hisds s) { ...@@ -196,7 +197,7 @@ void hi_sdsclear(hisds s) {
hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) { hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) {
void *sh, *newsh; void *sh, *newsh;
size_t avail = hi_sdsavail(s); size_t avail = hi_sdsavail(s);
size_t len, newlen; size_t len, newlen, reqlen;
char type, oldtype = s[-1] & HI_SDS_TYPE_MASK; char type, oldtype = s[-1] & HI_SDS_TYPE_MASK;
int hdrlen; int hdrlen;
...@@ -205,7 +206,8 @@ hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) { ...@@ -205,7 +206,8 @@ hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) {
len = hi_sdslen(s); len = hi_sdslen(s);
sh = (char*)s-hi_sdsHdrSize(oldtype); sh = (char*)s-hi_sdsHdrSize(oldtype);
newlen = (len+addlen); reqlen = newlen = (len+addlen);
if (newlen <= len) return NULL; /* Catch size_t overflow */
if (newlen < HI_SDS_MAX_PREALLOC) if (newlen < HI_SDS_MAX_PREALLOC)
newlen *= 2; newlen *= 2;
else else
...@@ -219,6 +221,7 @@ hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) { ...@@ -219,6 +221,7 @@ hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) {
if (type == HI_SDS_TYPE_5) type = HI_SDS_TYPE_8; if (type == HI_SDS_TYPE_5) type = HI_SDS_TYPE_8;
hdrlen = hi_sdsHdrSize(type); hdrlen = hi_sdsHdrSize(type);
if (hdrlen+newlen+1 <= reqlen) return NULL; /* Catch size_t overflow */
if (oldtype==type) { if (oldtype==type) {
newsh = hi_s_realloc(sh, hdrlen+newlen+1); newsh = hi_s_realloc(sh, hdrlen+newlen+1);
if (newsh == NULL) return NULL; if (newsh == NULL) return NULL;
...@@ -580,7 +583,7 @@ hisds hi_sdscatprintf(hisds s, const char *fmt, ...) { ...@@ -580,7 +583,7 @@ hisds hi_sdscatprintf(hisds s, const char *fmt, ...) {
*/ */
hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { hisds hi_sdscatfmt(hisds 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);
...@@ -753,16 +756,16 @@ int hi_sdsrange(hisds s, ssize_t start, ssize_t end) { ...@@ -753,16 +756,16 @@ int hi_sdsrange(hisds s, ssize_t start, ssize_t end) {
return 0; return 0;
} }
/* Apply tolower() to every character of the hisds string 's'. */ /* Apply tolower() to every character of the sds string 's'. */
void hi_sdstolower(hisds s) { void hi_sdstolower(hisds s) {
int len = hi_sdslen(s), j; size_t len = hi_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 hisds string 's'. */ /* Apply toupper() to every character of the sds string 's'. */
void hi_sdstoupper(hisds s) { void hi_sdstoupper(hisds s) {
int len = hi_sdslen(s), j; size_t len = hi_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]);
} }
......
...@@ -35,9 +35,11 @@ ...@@ -35,9 +35,11 @@
#define HI_SDS_MAX_PREALLOC (1024*1024) #define HI_SDS_MAX_PREALLOC (1024*1024)
#ifdef _MSC_VER #ifdef _MSC_VER
#define __attribute__(x)
typedef long long ssize_t; typedef long long ssize_t;
#define SSIZE_MAX (LLONG_MAX >> 1) #define SSIZE_MAX (LLONG_MAX >> 1)
#ifndef __clang__
#define __attribute__(x)
#endif
#endif #endif
#include <sys/types.h> #include <sys/types.h>
......
...@@ -180,10 +180,17 @@ int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) ...@@ -180,10 +180,17 @@ 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;
} }
...@@ -205,6 +212,14 @@ int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, sockle ...@@ -205,6 +212,14 @@ int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, sockle
} else { } else {
ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); 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;
} }
...@@ -245,4 +260,21 @@ int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) { ...@@ -245,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 */
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <stddef.h> #include <stddef.h>
#include <errno.h> #include <errno.h>
#include <mstcpip.h>
#ifdef _MSC_VER #ifdef _MSC_VER
typedef long long ssize_t; typedef long long ssize_t;
...@@ -71,6 +72,8 @@ ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags); ...@@ -71,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
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "hiredis.h" #include "hiredis.h"
#include "async.h" #include "async.h"
#include "net.h"
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
...@@ -39,6 +40,14 @@ ...@@ -39,6 +40,14 @@
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#include <wincrypt.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 #else
#include <pthread.h> #include <pthread.h>
#endif #endif
...@@ -219,6 +228,25 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * ...@@ -219,6 +228,25 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
const char *cert_filename, const char *private_key_filename, const char *cert_filename, const char *private_key_filename,
const char *server_name, redisSSLContextError *error) 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 #ifdef _WIN32
HCERTSTORE win_store = NULL; HCERTSTORE win_store = NULL;
PCCERT_CONTEXT win_ctx = NULL; PCCERT_CONTEXT win_ctx = NULL;
...@@ -235,7 +263,7 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * ...@@ -235,7 +263,7 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
} }
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL); SSL_CTX_set_verify(ctx->ssl_ctx, options->verify_mode, NULL);
if ((cert_filename != NULL && private_key_filename == NULL) || if ((cert_filename != NULL && private_key_filename == NULL) ||
(private_key_filename != NULL && cert_filename == NULL)) { (private_key_filename != NULL && cert_filename == NULL)) {
...@@ -273,6 +301,11 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * ...@@ -273,6 +301,11 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED; if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
goto error; 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 (cert_filename) {
...@@ -560,6 +593,7 @@ static void redisSSLAsyncWrite(redisAsyncContext *ac) { ...@@ -560,6 +593,7 @@ static void redisSSLAsyncWrite(redisAsyncContext *ac) {
} }
redisContextFuncs redisContextSSLFuncs = { redisContextFuncs redisContextSSLFuncs = {
.close = redisNetClose,
.free_privctx = redisSSLFree, .free_privctx = redisSSLFree,
.async_read = redisSSLAsyncRead, .async_read = redisSSLAsyncRead,
.async_write = redisSSLAsyncWrite, .async_write = redisSSLAsyncWrite,
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "hiredis.h" #include "hiredis.h"
#include "async.h" #include "async.h"
#include "adapters/poll.h"
#ifdef HIREDIS_TEST_SSL #ifdef HIREDIS_TEST_SSL
#include "hiredis_ssl.h" #include "hiredis_ssl.h"
#endif #endif
...@@ -34,11 +35,11 @@ enum connection_type { ...@@ -34,11 +35,11 @@ enum connection_type {
struct config { struct config {
enum connection_type type; enum connection_type type;
struct timeval connect_timeout;
struct { struct {
const char *host; const char *host;
int port; int port;
struct timeval timeout;
} tcp; } tcp;
struct { struct {
...@@ -75,6 +76,15 @@ static int tests = 0, fails = 0, skips = 0; ...@@ -75,6 +76,15 @@ static int tests = 0, fails = 0, skips = 0;
#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} #define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
#define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; } #define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; }
static void millisleep(int ms)
{
#if _MSC_VER
Sleep(ms);
#else
usleep(ms*1000);
#endif
}
static long long usec(void) { static long long usec(void) {
#ifndef _MSC_VER #ifndef _MSC_VER
struct timeval tv; struct timeval tv;
...@@ -329,10 +339,14 @@ static void test_format_commands(void) { ...@@ -329,10 +339,14 @@ static void test_format_commands(void) {
FLOAT_WIDTH_TEST(float); FLOAT_WIDTH_TEST(float);
FLOAT_WIDTH_TEST(double); FLOAT_WIDTH_TEST(double);
test("Format command with invalid printf format: "); test("Format command with unhandled printf format (specifier 'p' not supported): ");
len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3);
test_cond(len == -1); test_cond(len == -1);
test("Format command with invalid printf format (specifier missing): ");
len = redisFormatCommand(&cmd,"%-");
test_cond(len == -1);
const char *argv[3]; const char *argv[3];
argv[0] = "SET"; argv[0] = "SET";
argv[1] = "foo\0xxx"; argv[1] = "foo\0xxx";
...@@ -391,6 +405,16 @@ static void test_append_formatted_commands(struct config config) { ...@@ -391,6 +405,16 @@ static void test_append_formatted_commands(struct config config) {
disconnect(c, 0); disconnect(c, 0);
} }
static void test_tcp_options(struct config cfg) {
redisContext *c;
c = do_connect(cfg);
test("We can enable TCP_KEEPALIVE: ");
test_cond(redisEnableKeepAlive(c) == REDIS_OK);
disconnect(c, 0);
}
static void test_reply_reader(void) { static void test_reply_reader(void) {
redisReader *reader; redisReader *reader;
void *reply, *root; void *reply, *root;
...@@ -568,6 +592,19 @@ static void test_reply_reader(void) { ...@@ -568,6 +592,19 @@ static void test_reply_reader(void) {
test_cond(ret == REDIS_ERR && reply == NULL); test_cond(ret == REDIS_ERR && reply == NULL);
redisReaderFree(reader); redisReaderFree(reader);
test("Don't reset state after protocol error(not segfault): ");
reader = redisReaderCreate();
redisReaderFeed(reader,(char*)"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$", 25);
ret = redisReaderGetReply(reader,&reply);
assert(ret == REDIS_OK);
redisReaderFeed(reader,(char*)"3\r\nval\r\n", 8);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
((redisReply*)reply)->elements == 3);
freeReplyObject(reply);
redisReaderFree(reader);
/* Regression test for issue #45 on GitHub. */ /* Regression test for issue #45 on GitHub. */
test("Don't do empty allocation for empty multi bulk: "); test("Don't do empty allocation for empty multi bulk: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
...@@ -637,12 +674,23 @@ static void test_reply_reader(void) { ...@@ -637,12 +674,23 @@ static void test_reply_reader(void) {
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
test("Set error when RESP3 double is NaN: "); test("Correctly parses RESP3 double NaN: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
redisReaderFeed(reader, ",nan\r\n",6); redisReaderFeed(reader, ",nan\r\n",6);
ret = redisReaderGetReply(reader,&reply); ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_ERR && test_cond(ret == REDIS_OK &&
strcasecmp(reader->errstr,"Bad double value") == 0); ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
isnan(((redisReply*)reply)->dval));
freeReplyObject(reply);
redisReaderFree(reader);
test("Correctly parses RESP3 double -Nan: ");
reader = redisReaderCreate();
redisReaderFeed(reader, ",-nan\r\n", 7);
ret = redisReaderGetReply(reader, &reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
isnan(((redisReply*)reply)->dval));
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
...@@ -745,6 +793,20 @@ static void test_reply_reader(void) { ...@@ -745,6 +793,20 @@ static void test_reply_reader(void) {
!strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385")); !strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385"));
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
test("Can parse RESP3 doubles in an array: ");
reader = redisReaderCreate();
redisReaderFeed(reader, "*1\r\n,3.14159265358979323846\r\n",31);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
((redisReply*)reply)->elements == 1 &&
((redisReply*)reply)->element[0]->type == REDIS_REPLY_DOUBLE &&
fabs(((redisReply*)reply)->element[0]->dval - 3.14159265358979323846) < 0.00000001 &&
((redisReply*)reply)->element[0]->len == 22 &&
strcmp(((redisReply*)reply)->element[0]->str, "3.14159265358979323846") == 0);
freeReplyObject(reply);
redisReaderFree(reader);
} }
static void test_free_null(void) { static void test_free_null(void) {
...@@ -819,9 +881,9 @@ static void test_allocator_injection(void) { ...@@ -819,9 +881,9 @@ static void test_allocator_injection(void) {
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com" #define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
static void test_blocking_connection_errors(void) { static void test_blocking_connection_errors(void) {
redisContext *c;
struct addrinfo hints = {.ai_family = AF_INET}; struct addrinfo hints = {.ai_family = AF_INET};
struct addrinfo *ai_tmp = NULL; struct addrinfo *ai_tmp = NULL;
redisContext *c;
int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp); int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
if (rv != 0) { if (rv != 0) {
...@@ -835,6 +897,7 @@ static void test_blocking_connection_errors(void) { ...@@ -835,6 +897,7 @@ static void test_blocking_connection_errors(void) {
strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 || strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
strcmp(c->errstr, "Name does not resolve") == 0 || strcmp(c->errstr, "Name does not resolve") == 0 ||
strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 || strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 ||
strcmp(c->errstr, "node name or service name not known") == 0 ||
strcmp(c->errstr, "No address associated with hostname") == 0 || strcmp(c->errstr, "No address associated with hostname") == 0 ||
strcmp(c->errstr, "Temporary failure in name resolution") == 0 || strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 || strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 ||
...@@ -847,12 +910,26 @@ static void test_blocking_connection_errors(void) { ...@@ -847,12 +910,26 @@ static void test_blocking_connection_errors(void) {
} }
#ifndef _WIN32 #ifndef _WIN32
redisOptions opt = {0};
struct timeval tv;
test("Returns error when the port is not open: "); test("Returns error when the port is not open: ");
c = redisConnect((char*)"localhost", 1); c = redisConnect((char*)"localhost", 1);
test_cond(c->err == REDIS_ERR_IO && test_cond(c->err == REDIS_ERR_IO &&
strcmp(c->errstr,"Connection refused") == 0); strcmp(c->errstr,"Connection refused") == 0);
redisFree(c); redisFree(c);
/* Verify we don't regress from the fix in PR #1180 */
test("We don't clobber connection exception with setsockopt error: ");
tv = (struct timeval){.tv_sec = 0, .tv_usec = 500000};
opt.command_timeout = opt.connect_timeout = &tv;
REDIS_OPTIONS_SET_TCP(&opt, "localhost", 10337);
c = redisConnectWithOptions(&opt);
test_cond(c->err == REDIS_ERR_IO &&
strcmp(c->errstr, "Connection refused") == 0);
redisFree(c);
test("Returns error when the unix_sock socket path doesn't accept connections: "); test("Returns error when the unix_sock socket path doesn't accept connections: ");
c = redisConnectUnix((char*)"/tmp/idontexist.sock"); c = redisConnectUnix((char*)"/tmp/idontexist.sock");
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
...@@ -914,11 +991,19 @@ static void test_resp3_push_handler(redisContext *c) { ...@@ -914,11 +991,19 @@ static void test_resp3_push_handler(redisContext *c) {
old = redisSetPushCallback(c, push_handler); old = redisSetPushCallback(c, push_handler);
test("We can set a custom RESP3 PUSH handler: "); test("We can set a custom RESP3 PUSH handler: ");
reply = redisCommand(c, "SET key:0 val:0"); reply = redisCommand(c, "SET key:0 val:0");
/* We need another command because depending on the version of Redis, the
* notification may be delivered after the command's reply. */
assert(reply != NULL);
freeReplyObject(reply);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1);
freeReplyObject(reply); freeReplyObject(reply);
test("We properly handle a NIL invalidation payload: "); test("We properly handle a NIL invalidation payload: ");
reply = redisCommand(c, "FLUSHDB"); reply = redisCommand(c, "FLUSHDB");
assert(reply != NULL);
freeReplyObject(reply);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1);
freeReplyObject(reply); freeReplyObject(reply);
...@@ -929,6 +1014,12 @@ static void test_resp3_push_handler(redisContext *c) { ...@@ -929,6 +1014,12 @@ static void test_resp3_push_handler(redisContext *c) {
assert((reply = redisCommand(c, "GET key:0")) != NULL); assert((reply = redisCommand(c, "GET key:0")) != NULL);
freeReplyObject(reply); freeReplyObject(reply);
assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL); assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL);
/* Depending on Redis version, we may receive either push notification or
* status reply. Both cases are valid. */
if (reply->type == REDIS_REPLY_STATUS) {
freeReplyObject(reply);
reply = redisCommand(c, "PING");
}
test_cond(reply->type == REDIS_REPLY_PUSH); test_cond(reply->type == REDIS_REPLY_PUSH);
freeReplyObject(reply); freeReplyObject(reply);
...@@ -1089,6 +1180,13 @@ static void test_blocking_connection(struct config config) { ...@@ -1089,6 +1180,13 @@ static void test_blocking_connection(struct config config) {
strcasecmp(reply->element[1]->str,"pong") == 0); strcasecmp(reply->element[1]->str,"pong") == 0);
freeReplyObject(reply); freeReplyObject(reply);
test("Send command by passing argc/argv: ");
const char *argv[3] = {"SET", "foo", "bar"};
size_t argvlen[3] = {3, 3, 3};
reply = redisCommandArgv(c,3,argv,argvlen);
test_cond(reply->type == REDIS_REPLY_STATUS);
freeReplyObject(reply);
/* Make sure passing NULL to redisGetReply is safe */ /* Make sure passing NULL to redisGetReply is safe */
test("Can pass NULL to redisGetReply: "); test("Can pass NULL to redisGetReply: ");
assert(redisAppendCommand(c, "PING") == REDIS_OK); assert(redisAppendCommand(c, "PING") == REDIS_OK);
...@@ -1143,7 +1241,13 @@ static void test_blocking_connection_timeouts(struct config config) { ...@@ -1143,7 +1241,13 @@ static void test_blocking_connection_timeouts(struct config config) {
test("Does not return a reply when the command times out: "); test("Does not return a reply when the command times out: ");
if (detect_debug_sleep(c)) { if (detect_debug_sleep(c)) {
redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd)); redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));
// flush connection buffer without waiting for the reply
s = c->funcs->write(c); s = c->funcs->write(c);
assert(s == (ssize_t)hi_sdslen(c->obuf));
hi_sdsfree(c->obuf);
c->obuf = hi_sdsempty();
tv.tv_sec = 0; tv.tv_sec = 0;
tv.tv_usec = 10000; tv.tv_usec = 10000;
redisSetTimeout(c, tv); redisSetTimeout(c, tv);
...@@ -1156,6 +1260,9 @@ static void test_blocking_connection_timeouts(struct config config) { ...@@ -1156,6 +1260,9 @@ static void test_blocking_connection_timeouts(struct config config) {
strcmp(c->errstr, "recv timeout") == 0); strcmp(c->errstr, "recv timeout") == 0);
#endif #endif
freeReplyObject(reply); freeReplyObject(reply);
// wait for the DEBUG SLEEP to complete so that Redis server is unblocked for the following tests
millisleep(3000);
} else { } else {
test_skipped(); test_skipped();
} }
...@@ -1226,22 +1333,34 @@ static void test_blocking_io_errors(struct config config) { ...@@ -1226,22 +1333,34 @@ static void test_blocking_io_errors(struct config config) {
static void test_invalid_timeout_errors(struct config config) { static void test_invalid_timeout_errors(struct config config) {
redisContext *c; redisContext *c;
test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); test("Set error when an invalid timeout usec value is used during connect: ");
config.tcp.timeout.tv_sec = 0; config.connect_timeout.tv_sec = 0;
config.tcp.timeout.tv_usec = 10000001; config.connect_timeout.tv_usec = 10000001;
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); if (config.type == CONN_TCP || config.type == CONN_SSL) {
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);
} else if(config.type == CONN_UNIX) {
c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);
} else {
assert(NULL);
}
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
redisFree(c); redisFree(c);
test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); test("Set error when an invalid timeout sec value is used during connect: ");
config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; config.connect_timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
config.tcp.timeout.tv_usec = 0; config.connect_timeout.tv_usec = 0;
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); if (config.type == CONN_TCP || config.type == CONN_SSL) {
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);
} else if(config.type == CONN_UNIX) {
c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);
} else {
assert(NULL);
}
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
redisFree(c); redisFree(c);
...@@ -1729,10 +1848,14 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) { ...@@ -1729,10 +1848,14 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) {
strcmp(reply->element[2]->str,"Hello!") == 0); strcmp(reply->element[2]->str,"Hello!") == 0);
state->checkpoint++; state->checkpoint++;
/* Unsubscribe to channels, including a channel X which we don't subscribe to */ /* Unsubscribe to channels, including channel X & Z which we don't subscribe to */
redisAsyncCommand(ac,unexpected_cb, redisAsyncCommand(ac,unexpected_cb,
(void*)"unsubscribe should not call unexpected_cb()", (void*)"unsubscribe should not call unexpected_cb()",
"unsubscribe B X A"); "unsubscribe B X A A Z");
/* Unsubscribe to patterns, none which we subscribe to */
redisAsyncCommand(ac,unexpected_cb,
(void*)"punsubscribe should not call unexpected_cb()",
"punsubscribe");
/* Send a regular command after unsubscribing, then disconnect */ /* Send a regular command after unsubscribing, then disconnect */
state->disconnect = 1; state->disconnect = 1;
redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo"); redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo");
...@@ -1749,6 +1872,7 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) { ...@@ -1749,6 +1872,7 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) {
void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) { void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) {
redisReply *reply = r; redisReply *reply = r;
TestState *state = privdata; TestState *state = privdata;
(void)ac;
assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY && assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY &&
reply->elements == 3); reply->elements == 3);
...@@ -1767,8 +1891,10 @@ void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) { ...@@ -1767,8 +1891,10 @@ void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) {
/* Test handling of multiple channels /* Test handling of multiple channels
* - subscribe to channel A and B * - subscribe to channel A and B
* - a published message on A triggers an unsubscribe of channel B, X and A * - a published message on A triggers an unsubscribe of channel B, X, A and Z
* where channel X is not subscribed to. * where channel X and Z are not subscribed to.
* - the published message also triggers an unsubscribe to patterns. Since no
* pattern is subscribed to the responded pattern element type is NIL.
* - a command sent after unsubscribe triggers a disconnect */ * - a command sent after unsubscribe triggers a disconnect */
static void test_pubsub_multiple_channels(struct config config) { static void test_pubsub_multiple_channels(struct config config) {
test("Subscribe to multiple channels: "); test("Subscribe to multiple channels: ");
...@@ -1881,6 +2007,250 @@ static void test_monitor(struct config config) { ...@@ -1881,6 +2007,250 @@ static void test_monitor(struct config config) {
} }
#endif /* HIREDIS_TEST_ASYNC */ #endif /* HIREDIS_TEST_ASYNC */
/* tests for async api using polling adapter, requires no extra libraries*/
/* enum for the test cases, the callbacks have different logic based on them */
typedef enum astest_no
{
ASTEST_CONNECT=0,
ASTEST_CONN_TIMEOUT,
ASTEST_PINGPONG,
ASTEST_PINGPONG_TIMEOUT,
ASTEST_ISSUE_931,
ASTEST_ISSUE_931_PING
}astest_no;
/* a static context for the async tests */
struct _astest {
redisAsyncContext *ac;
astest_no testno;
int counter;
int connects;
int connect_status;
int disconnects;
int pongs;
int disconnect_status;
int connected;
int err;
char errstr[256];
};
static struct _astest astest;
/* async callbacks */
static void asCleanup(void* data)
{
struct _astest *t = (struct _astest *)data;
t->ac = NULL;
}
static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata);
static void connectCallback(redisAsyncContext *c, int status) {
struct _astest *t = (struct _astest *)c->data;
assert(t == &astest);
assert(t->connects == 0);
t->err = c->err;
strcpy(t->errstr, c->errstr);
t->connects++;
t->connect_status = status;
t->connected = status == REDIS_OK ? 1 : -1;
if (t->testno == ASTEST_ISSUE_931) {
/* disconnect again */
redisAsyncDisconnect(c);
}
else if (t->testno == ASTEST_ISSUE_931_PING)
{
redisAsyncCommand(c, commandCallback, NULL, "PING");
}
}
static void disconnectCallback(const redisAsyncContext *c, int status) {
assert(c->data == (void*)&astest);
assert(astest.disconnects == 0);
astest.err = c->err;
strcpy(astest.errstr, c->errstr);
astest.disconnects++;
astest.disconnect_status = status;
astest.connected = 0;
}
static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata)
{
redisReply *reply = (redisReply*)_reply;
struct _astest *t = (struct _astest *)ac->data;
assert(t == &astest);
(void)_privdata;
t->err = ac->err;
strcpy(t->errstr, ac->errstr);
t->counter++;
if (t->testno == ASTEST_PINGPONG ||t->testno == ASTEST_ISSUE_931_PING)
{
assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
t->pongs++;
redisAsyncFree(ac);
}
if (t->testno == ASTEST_PINGPONG_TIMEOUT)
{
/* two ping pongs */
assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
t->pongs++;
if (t->counter == 1) {
int status = redisAsyncCommand(ac, commandCallback, NULL, "PING");
assert(status == REDIS_OK);
} else {
redisAsyncFree(ac);
}
}
}
static redisAsyncContext *do_aconnect(struct config config, astest_no testno)
{
redisOptions options = {0};
memset(&astest, 0, sizeof(astest));
astest.testno = testno;
astest.connect_status = astest.disconnect_status = -2;
if (config.type == CONN_TCP) {
options.type = REDIS_CONN_TCP;
options.connect_timeout = &config.connect_timeout;
REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);
} else if (config.type == CONN_SSL) {
options.type = REDIS_CONN_TCP;
options.connect_timeout = &config.connect_timeout;
REDIS_OPTIONS_SET_TCP(&options, config.ssl.host, config.ssl.port);
} else if (config.type == CONN_UNIX) {
options.type = REDIS_CONN_UNIX;
options.endpoint.unix_socket = config.unix_sock.path;
} else if (config.type == CONN_FD) {
options.type = REDIS_CONN_USERFD;
/* Create a dummy connection just to get an fd to inherit */
redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
if (dummy_ctx) {
redisFD fd = disconnect(dummy_ctx, 1);
printf("Connecting to inherited fd %d\n", (int)fd);
options.endpoint.fd = fd;
}
}
redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
assert(c);
astest.ac = c;
c->data = &astest;
c->dataCleanup = asCleanup;
redisPollAttach(c);
redisAsyncSetConnectCallbackNC(c, connectCallback);
redisAsyncSetDisconnectCallback(c, disconnectCallback);
return c;
}
static void as_printerr(void) {
printf("Async err %d : %s\n", astest.err, astest.errstr);
}
#define ASASSERT(e) do { \
if (!(e)) \
as_printerr(); \
assert(e); \
} while (0);
static void test_async_polling(struct config config) {
int status;
redisAsyncContext *c;
struct config defaultconfig = config;
test("Async connect: ");
c = do_aconnect(config, ASTEST_CONNECT);
assert(c);
while(astest.connected == 0)
redisPollTick(c, 0.1);
assert(astest.connects == 1);
ASASSERT(astest.connect_status == REDIS_OK);
assert(astest.disconnects == 0);
test_cond(astest.connected == 1);
test("Async free after connect: ");
assert(astest.ac != NULL);
redisAsyncFree(c);
assert(astest.disconnects == 1);
assert(astest.ac == NULL);
test_cond(astest.disconnect_status == REDIS_OK);
if (config.type == CONN_TCP || config.type == CONN_SSL) {
/* timeout can only be simulated with network */
test("Async connect timeout: ");
config.tcp.host = "192.168.254.254"; /* blackhole ip */
config.connect_timeout.tv_usec = 100000;
c = do_aconnect(config, ASTEST_CONN_TIMEOUT);
assert(c);
assert(c->err == 0);
while(astest.connected == 0)
redisPollTick(c, 0.1);
assert(astest.connected == -1);
/*
* freeing should not be done, clearing should have happened.
*redisAsyncFree(c);
*/
assert(astest.ac == NULL);
test_cond(astest.connect_status == REDIS_ERR);
config = defaultconfig;
}
/* Test a ping/pong after connection */
test("Async PING/PONG: ");
c = do_aconnect(config, ASTEST_PINGPONG);
while(astest.connected == 0)
redisPollTick(c, 0.1);
status = redisAsyncCommand(c, commandCallback, NULL, "PING");
assert(status == REDIS_OK);
while(astest.ac)
redisPollTick(c, 0.1);
test_cond(astest.pongs == 1);
/* Test a ping/pong after connection that didn't time out.
* see https://github.com/redis/hiredis/issues/945
*/
if (config.type == CONN_TCP || config.type == CONN_SSL) {
test("Async PING/PONG after connect timeout: ");
config.connect_timeout.tv_usec = 10000; /* 10ms */
c = do_aconnect(config, ASTEST_PINGPONG_TIMEOUT);
while(astest.connected == 0)
redisPollTick(c, 0.1);
/* sleep 0.1 s, allowing old timeout to arrive */
millisleep(10);
status = redisAsyncCommand(c, commandCallback, NULL, "PING");
assert(status == REDIS_OK);
while(astest.ac)
redisPollTick(c, 0.1);
test_cond(astest.pongs == 2);
config = defaultconfig;
}
/* Test disconnect from an on_connect callback
* see https://github.com/redis/hiredis/issues/931
*/
test("Disconnect from onConnected callback (Issue #931): ");
c = do_aconnect(config, ASTEST_ISSUE_931);
while(astest.disconnects == 0)
redisPollTick(c, 0.1);
assert(astest.connected == 0);
assert(astest.connects == 1);
test_cond(astest.disconnects == 1);
/* Test ping/pong from an on_connect callback
* see https://github.com/redis/hiredis/issues/931
*/
test("Ping/Pong from onConnected callback (Issue #931): ");
c = do_aconnect(config, ASTEST_ISSUE_931_PING);
/* connect callback issues ping, reponse callback destroys context */
while(astest.ac)
redisPollTick(c, 0.1);
assert(astest.connected == 0);
assert(astest.connects == 1);
assert(astest.disconnects == 1);
test_cond(astest.pongs == 1);
}
/* End of Async polling_adapter driven tests */
int main(int argc, char **argv) { int main(int argc, char **argv) {
struct config cfg = { struct config cfg = {
.tcp = { .tcp = {
...@@ -1963,6 +2333,7 @@ int main(int argc, char **argv) { ...@@ -1963,6 +2333,7 @@ int main(int argc, char **argv) {
test_blocking_io_errors(cfg); test_blocking_io_errors(cfg);
test_invalid_timeout_errors(cfg); test_invalid_timeout_errors(cfg);
test_append_formatted_commands(cfg); test_append_formatted_commands(cfg);
test_tcp_options(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path); printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path);
...@@ -1972,6 +2343,7 @@ int main(int argc, char **argv) { ...@@ -1972,6 +2343,7 @@ int main(int argc, char **argv) {
test_blocking_connection(cfg); test_blocking_connection(cfg);
test_blocking_connection_timeouts(cfg); test_blocking_connection_timeouts(cfg);
test_blocking_io_errors(cfg); test_blocking_io_errors(cfg);
test_invalid_timeout_errors(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
} else { } else {
test_skipped(); test_skipped();
...@@ -2000,6 +2372,7 @@ int main(int argc, char **argv) { ...@@ -2000,6 +2372,7 @@ int main(int argc, char **argv) {
#endif #endif
#ifdef HIREDIS_TEST_ASYNC #ifdef HIREDIS_TEST_ASYNC
cfg.type = CONN_TCP;
printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
cfg.type = CONN_TCP; cfg.type = CONN_TCP;
...@@ -2017,6 +2390,15 @@ int main(int argc, char **argv) { ...@@ -2017,6 +2390,15 @@ int main(int argc, char **argv) {
} }
#endif /* HIREDIS_TEST_ASYNC */ #endif /* HIREDIS_TEST_ASYNC */
cfg.type = CONN_TCP;
printf("\nTesting asynchronous API using polling_adapter TCP (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
test_async_polling(cfg);
if (test_unix_socket) {
cfg.type = CONN_UNIX;
printf("\nTesting asynchronous API using polling_adapter UNIX (%s):\n", cfg.unix_sock.path);
test_async_polling(cfg);
}
if (test_inherit_fd) { if (test_inherit_fd) {
printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path); printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path);
if (test_unix_socket) { if (test_unix_socket) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment