Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port RESP3 support from Redis. #697

Merged
merged 2 commits into from
Aug 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ os:
- linux
- osx

branches:
only:
- staging
- trying
- master

before_script:
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi

Expand Down
76 changes: 71 additions & 5 deletions hiredis.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,19 @@ static redisReply *createReplyObject(int type);
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
static void *createArrayObject(const redisReadTask *task, int elements);
static void *createIntegerObject(const redisReadTask *task, long long value);
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
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
* function returning NULL is interpreted as OOM. */
static redisReplyObjectFunctions defaultFunctions = {
createStringObject,
createArrayObject,
createIntegerObject,
createDoubleObject,
createNilObject,
createBoolObject,
freeReplyObject
};

Expand All @@ -83,6 +87,8 @@ void freeReplyObject(void *reply) {
case REDIS_REPLY_INTEGER:
break; /* Nothing to free */
case REDIS_REPLY_ARRAY:
case REDIS_REPLY_MAP:
case REDIS_REPLY_SET:
if (r->element != NULL) {
for (j = 0; j < r->elements; j++)
freeReplyObject(r->element[j]);
Expand All @@ -92,6 +98,7 @@ void freeReplyObject(void *reply) {
case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING:
case REDIS_REPLY_DOUBLE:
free(r->str);
break;
}
Expand Down Expand Up @@ -124,7 +131,9 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len

if (task->parent) {
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_SET);
parent->element[task->idx] = r;
}
return r;
Expand All @@ -133,7 +142,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
static void *createArrayObject(const redisReadTask *task, int elements) {
redisReply *r, *parent;

r = createReplyObject(REDIS_REPLY_ARRAY);
r = createReplyObject(task->type);
if (r == NULL)
return NULL;

Expand All @@ -149,7 +158,9 @@ static void *createArrayObject(const redisReadTask *task, int elements) {

if (task->parent) {
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_SET);
parent->element[task->idx] = r;
}
return r;
Expand All @@ -166,7 +177,41 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {

if (task->parent) {
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_SET);
parent->element[task->idx] = r;
}
return r;
}

static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {
redisReply *r, *parent;

r = createReplyObject(REDIS_REPLY_DOUBLE);
if (r == NULL)
return NULL;

r->dval = value;
r->str = 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';

if (task->parent) {
parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r;
}
return r;
Expand All @@ -181,7 +226,28 @@ static void *createNilObject(const redisReadTask *task) {

if (task->parent) {
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_SET);
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_SET);
parent->element[task->idx] = r;
}
return r;
Expand Down
4 changes: 3 additions & 1 deletion hiredis.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@ extern "C" {
typedef struct redisReply {
int type; /* REDIS_REPLY_* */
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 */
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
and REDIS_REPLY_DOUBLE (in additionl to dval). */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply;
Expand Down
77 changes: 72 additions & 5 deletions read.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
* POSSIBILITY OF SUCH DAMAGE.
*/


#include "fmacros.h"
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#ifndef _MSC_VER
#include <unistd.h>
Expand All @@ -40,6 +40,7 @@
#include <errno.h>
#include <ctype.h>
#include <limits.h>
#include <math.h>

#include "read.h"
#include "sds.h"
Expand Down Expand Up @@ -243,7 +244,9 @@ static void moveToNextTask(redisReader *r) {

cur = &(r->rstack[r->ridx]);
prv = &(r->rstack[r->ridx-1]);
assert(prv->type == REDIS_REPLY_ARRAY);
assert(prv->type == REDIS_REPLY_ARRAY ||
prv->type == REDIS_REPLY_MAP ||
prv->type == REDIS_REPLY_SET);
if (cur->idx == prv->elements-1) {
r->ridx--;
} else {
Expand Down Expand Up @@ -276,6 +279,47 @@ static int processLineItem(redisReader *r) {
} else {
obj = (void*)REDIS_REPLY_INTEGER;
}
} else if (cur->type == REDIS_REPLY_DOUBLE) {
if (r->fn && r->fn->createDouble) {
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 (strcasecmp(buf,",inf") == 0) {
d = 1.0/0.0; /* Positive infinite. */
} else if (strcasecmp(buf,",-inf") == 0) {
d = -1.0/0.0; /* Nevative infinite. */
} else {
d = strtod((char*)buf,&eptr);
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad double value");
return REDIS_ERR;
}
}
obj = r->fn->createDouble(cur,d,buf,len);
} else {
obj = (void*)REDIS_REPLY_DOUBLE;
}
} else if (cur->type == REDIS_REPLY_NIL) {
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 = 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 {
/* Type will be error or status. */
if (r->fn && r->fn->createString)
Expand Down Expand Up @@ -362,7 +406,8 @@ static int processBulkItem(redisReader *r) {
return REDIS_ERR;
}

static int processMultiBulkItem(redisReader *r) {
/* Process the array, map and set types. */
static int processAggregateItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj;
char *p;
Expand Down Expand Up @@ -404,10 +449,12 @@ static int processMultiBulkItem(redisReader *r) {

moveToNextTask(r);
} else {
if (cur->type == REDIS_REPLY_MAP) elements *= 2;

if (r->fn && r->fn->createArray)
obj = r->fn->createArray(cur,elements);
else
obj = (void*)REDIS_REPLY_ARRAY;
obj = (void*)(long)cur->type;

if (obj == NULL) {
__redisReaderSetErrorOOM(r);
Expand Down Expand Up @@ -455,12 +502,27 @@ static int processItem(redisReader *r) {
case ':':
cur->type = REDIS_REPLY_INTEGER;
break;
case ',':
cur->type = REDIS_REPLY_DOUBLE;
break;
case '_':
cur->type = REDIS_REPLY_NIL;
break;
case '$':
cur->type = REDIS_REPLY_STRING;
break;
case '*':
cur->type = REDIS_REPLY_ARRAY;
break;
case '%':
cur->type = REDIS_REPLY_MAP;
break;
case '~':
cur->type = REDIS_REPLY_SET;
break;
case '#':
cur->type = REDIS_REPLY_BOOL;
break;
default:
__redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR;
Expand All @@ -476,11 +538,16 @@ static int processItem(redisReader *r) {
case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS:
case REDIS_REPLY_INTEGER:
case REDIS_REPLY_DOUBLE:
case REDIS_REPLY_NIL:
case REDIS_REPLY_BOOL:
return processLineItem(r);
case REDIS_REPLY_STRING:
return processBulkItem(r);
case REDIS_REPLY_ARRAY:
return processMultiBulkItem(r);
case REDIS_REPLY_MAP:
case REDIS_REPLY_SET:
return processAggregateItem(r);
default:
assert(NULL);
return REDIS_ERR; /* Avoid warning. */
Expand Down
10 changes: 10 additions & 0 deletions read.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6
#define REDIS_REPLY_DOUBLE 7
#define REDIS_REPLY_BOOL 8
#define REDIS_REPLY_VERB 9
#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_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */

Expand All @@ -74,7 +82,9 @@ typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t);
void *(*createArray)(const redisReadTask*, int);
void *(*createInteger)(const redisReadTask*, long long);
void *(*createDouble)(const redisReadTask*, double, char*, size_t);
void *(*createNil)(const redisReadTask*);
void *(*createBool)(const redisReadTask*, int);
void (*freeObject)(void*);
} redisReplyObjectFunctions;

Expand Down