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()
This commit is contained in:
commit
d6a0b192b4
13
hiredis.c
13
hiredis.c
@ -96,6 +96,8 @@ void freeReplyObject(void *reply) {
|
||||
|
||||
switch(r->type) {
|
||||
case REDIS_REPLY_INTEGER:
|
||||
case REDIS_REPLY_NIL:
|
||||
case REDIS_REPLY_BOOL:
|
||||
break; /* Nothing to free */
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_MAP:
|
||||
@ -112,6 +114,7 @@ void freeReplyObject(void *reply) {
|
||||
case REDIS_REPLY_STRING:
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
case REDIS_REPLY_VERB:
|
||||
case REDIS_REPLY_BIGNUM:
|
||||
hi_free(r->str);
|
||||
break;
|
||||
}
|
||||
@ -129,7 +132,8 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
||||
assert(task->type == REDIS_REPLY_ERROR ||
|
||||
task->type == REDIS_REPLY_STATUS ||
|
||||
task->type == REDIS_REPLY_STRING ||
|
||||
task->type == REDIS_REPLY_VERB);
|
||||
task->type == REDIS_REPLY_VERB ||
|
||||
task->type == REDIS_REPLY_BIGNUM);
|
||||
|
||||
/* Copy string value */
|
||||
if (task->type == REDIS_REPLY_VERB) {
|
||||
@ -235,12 +239,14 @@ static void *createDoubleObject(const redisReadTask *task, double value, char *s
|
||||
* 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_SET);
|
||||
parent->type == REDIS_REPLY_SET ||
|
||||
parent->type == REDIS_REPLY_PUSH);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@ -277,7 +283,8 @@ static void *createBoolObject(const redisReadTask *task, int bval) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->type == REDIS_REPLY_SET ||
|
||||
parent->type == REDIS_REPLY_PUSH);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
|
@ -112,7 +112,8 @@ typedef struct redisReply {
|
||||
double dval; /* The double when type is REDIS_REPLY_DOUBLE */
|
||||
size_t len; /* Length of string */
|
||||
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
||||
REDIS_REPLY_VERB, and REDIS_REPLY_DOUBLE (in additional to dval). */
|
||||
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 */
|
||||
|
142
read.c
142
read.c
@ -123,29 +123,28 @@ static char *readBytes(redisReader *r, unsigned int bytes) {
|
||||
|
||||
/* Find pointer to \r\n. */
|
||||
static char *seekNewline(char *s, size_t len) {
|
||||
int pos = 0;
|
||||
int _len = len-1;
|
||||
char *ret;
|
||||
|
||||
/* Position should be < len-1 because the character at "pos" should be
|
||||
* followed by a \n. Note that strchr cannot be used because it doesn't
|
||||
* allow to search a limited length and the buffer that is being searched
|
||||
* might not have a trailing NULL character. */
|
||||
while (pos < _len) {
|
||||
while(pos < _len && s[pos] != '\r') pos++;
|
||||
if (pos==_len) {
|
||||
/* Not found. */
|
||||
return NULL;
|
||||
} else {
|
||||
if (s[pos+1] == '\n') {
|
||||
/* Found. */
|
||||
return s+pos;
|
||||
} else {
|
||||
/* Continue searching. */
|
||||
pos++;
|
||||
}
|
||||
/* We cannot match with fewer than 2 bytes */
|
||||
if (len < 2)
|
||||
return NULL;
|
||||
|
||||
/* Search up to len - 1 characters */
|
||||
len--;
|
||||
|
||||
/* Look for the \r */
|
||||
while ((ret = memchr(s, '\r', len)) != NULL) {
|
||||
if (ret[1] == '\n') {
|
||||
/* Found. */
|
||||
break;
|
||||
}
|
||||
/* Continue searching. */
|
||||
ret++;
|
||||
len -= ret - s;
|
||||
s = ret;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Convert a string into a long long. Returns REDIS_OK if the string could be
|
||||
@ -274,60 +273,104 @@ static int processLineItem(redisReader *r) {
|
||||
|
||||
if ((p = readLine(r,&len)) != NULL) {
|
||||
if (cur->type == REDIS_REPLY_INTEGER) {
|
||||
long long v;
|
||||
|
||||
if (string2ll(p, len, &v) == REDIS_ERR) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad integer value");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (r->fn && r->fn->createInteger) {
|
||||
long long v;
|
||||
if (string2ll(p, len, &v) == REDIS_ERR) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad integer value");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
obj = r->fn->createInteger(cur,v);
|
||||
} 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;
|
||||
char buf[326], *eptr;
|
||||
double d;
|
||||
|
||||
if ((size_t)len >= sizeof(buf)) {
|
||||
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 {
|
||||
d = strtod((char*)buf,&eptr);
|
||||
/* RESP3 only allows "inf", "-inf", and finite values, while
|
||||
* strtod() allows other variations on infinity, NaN,
|
||||
* etc. We explicity handle our two allowed infinite cases
|
||||
* above, so strtod() should only result in finite values. */
|
||||
if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Double value is too large");
|
||||
"Bad double value");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(buf,p,len);
|
||||
buf[len] = '\0';
|
||||
|
||||
if (strcasecmp(buf,",inf") == 0) {
|
||||
d = INFINITY; /* Positive infinite. */
|
||||
} else if (strcasecmp(buf,",-inf") == 0) {
|
||||
d = -INFINITY; /* Negative 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;
|
||||
}
|
||||
}
|
||||
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 = p[0] == 't' || p[0] == 'T';
|
||||
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)
|
||||
obj = r->fn->createString(cur,p,len);
|
||||
else
|
||||
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
|
||||
@ -453,7 +496,6 @@ static int processAggregateItem(redisReader *r) {
|
||||
long long elements;
|
||||
int root = 0, len;
|
||||
|
||||
/* Set error for nested multi bulks with depth > 7 */
|
||||
if (r->ridx == r->tasks - 1) {
|
||||
if (redisReaderGrow(r) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
@ -569,6 +611,9 @@ static int processItem(redisReader *r) {
|
||||
case '>':
|
||||
cur->type = REDIS_REPLY_PUSH;
|
||||
break;
|
||||
case '(':
|
||||
cur->type = REDIS_REPLY_BIGNUM;
|
||||
break;
|
||||
default:
|
||||
__redisReaderSetErrorProtocolByte(r,*p);
|
||||
return REDIS_ERR;
|
||||
@ -587,6 +632,7 @@ static int processItem(redisReader *r) {
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
case REDIS_REPLY_NIL:
|
||||
case REDIS_REPLY_BOOL:
|
||||
case REDIS_REPLY_BIGNUM:
|
||||
return processLineItem(r);
|
||||
case REDIS_REPLY_STRING:
|
||||
case REDIS_REPLY_VERB:
|
||||
|
142
test.c
142
test.c
@ -11,6 +11,7 @@
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
@ -583,6 +584,147 @@ static void test_reply_reader(void) {
|
||||
((redisReply*)reply)->element[1]->integer == 42);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Can parse RESP3 doubles: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ",3.14159265358979323846\r\n",25);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
|
||||
fabs(((redisReply*)reply)->dval - 3.14159265358979323846) < 0.00000001 &&
|
||||
((redisReply*)reply)->len == 22 &&
|
||||
strcmp(((redisReply*)reply)->str, "3.14159265358979323846") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error on invalid RESP3 double: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ",3.14159\000265358979323846\r\n",26);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bad double value") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Correctly parses RESP3 double INFINITY: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ",inf\r\n",6);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
|
||||
isinf(((redisReply*)reply)->dval) &&
|
||||
((redisReply*)reply)->dval > 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when RESP3 double is NaN: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ",nan\r\n",6);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bad double value") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Can parse RESP3 nil: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "_\r\n",3);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_NIL);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error on invalid RESP3 nil: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "_nil\r\n",6);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bad nil value") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Can parse RESP3 bool (true): ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "#t\r\n",4);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_BOOL &&
|
||||
((redisReply*)reply)->integer);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Can parse RESP3 bool (false): ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "#f\r\n",4);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_BOOL &&
|
||||
!((redisReply*)reply)->integer);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error on invalid RESP3 bool: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "#foobar\r\n",9);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bad bool value") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Can parse RESP3 map: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "%2\r\n+first\r\n:123\r\n$6\r\nsecond\r\n#t\r\n",34);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_MAP &&
|
||||
((redisReply*)reply)->elements == 4 &&
|
||||
((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS &&
|
||||
((redisReply*)reply)->element[0]->len == 5 &&
|
||||
!strcmp(((redisReply*)reply)->element[0]->str,"first") &&
|
||||
((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER &&
|
||||
((redisReply*)reply)->element[1]->integer == 123 &&
|
||||
((redisReply*)reply)->element[2]->type == REDIS_REPLY_STRING &&
|
||||
((redisReply*)reply)->element[2]->len == 6 &&
|
||||
!strcmp(((redisReply*)reply)->element[2]->str,"second") &&
|
||||
((redisReply*)reply)->element[3]->type == REDIS_REPLY_BOOL &&
|
||||
((redisReply*)reply)->element[3]->integer);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Can parse RESP3 set: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "~5\r\n+orange\r\n$5\r\napple\r\n#f\r\n:100\r\n:999\r\n",40);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_SET &&
|
||||
((redisReply*)reply)->elements == 5 &&
|
||||
((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS &&
|
||||
((redisReply*)reply)->element[0]->len == 6 &&
|
||||
!strcmp(((redisReply*)reply)->element[0]->str,"orange") &&
|
||||
((redisReply*)reply)->element[1]->type == REDIS_REPLY_STRING &&
|
||||
((redisReply*)reply)->element[1]->len == 5 &&
|
||||
!strcmp(((redisReply*)reply)->element[1]->str,"apple") &&
|
||||
((redisReply*)reply)->element[2]->type == REDIS_REPLY_BOOL &&
|
||||
!((redisReply*)reply)->element[2]->integer &&
|
||||
((redisReply*)reply)->element[3]->type == REDIS_REPLY_INTEGER &&
|
||||
((redisReply*)reply)->element[3]->integer == 100 &&
|
||||
((redisReply*)reply)->element[4]->type == REDIS_REPLY_INTEGER &&
|
||||
((redisReply*)reply)->element[4]->integer == 999);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Can parse RESP3 bignum: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader,"(3492890328409238509324850943850943825024385\r\n",46);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_BIGNUM &&
|
||||
((redisReply*)reply)->len == 43 &&
|
||||
!strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385"));
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
}
|
||||
|
||||
static void test_free_null(void) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user