Skip to content

Commit

Permalink
Merge pull request twitter#634 from twitter/test
Browse files Browse the repository at this point in the history
Test edge cases of parsing memcache/redis protocol
  • Loading branch information
TysonAndre authored Jul 3, 2021
2 parents 1165675 + 848d7c1 commit 1653924
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 12 deletions.
11 changes: 2 additions & 9 deletions src/proto/nc_redis.c
Original file line number Diff line number Diff line change
Expand Up @@ -1960,7 +1960,6 @@ redis_parse_rsp(struct msg *r)
SW_START,
SW_STATUS,
SW_ERROR,
SW_INTEGER,
SW_INTEGER_START,
SW_SIMPLE,
SW_BULK,
Expand Down Expand Up @@ -2014,8 +2013,8 @@ redis_parse_rsp(struct msg *r)

case ':':
r->type = MSG_RSP_REDIS_INTEGER;
p = p - 1; /* go back by 1 byte */
state = SW_INTEGER;
r->integer = 0;
state = SW_INTEGER_START;
break;

case '$':
Expand Down Expand Up @@ -2168,12 +2167,6 @@ redis_parse_rsp(struct msg *r)

break;

case SW_INTEGER:
/* rsp_start <- p */
state = SW_INTEGER_START;
r->integer = 0;
break;

case SW_SIMPLE:
if (ch == CR) {
ASSERT(r->rnarg > 0);
Expand Down
174 changes: 171 additions & 3 deletions src/test_all.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ static void test_redis_parse_rsp_success_case(const char* data, int expected) {
msg_put(rsp);
/* mbuf_put(m); */
if (failures > original_failures) {
fprintf(stderr, "test_redis_parse_rsp_success_case failed for %s", data);
fprintf(stderr, "test_redis_parse_rsp_success_case failed for (%s)", data);
}
}

Expand Down Expand Up @@ -174,6 +174,56 @@ static void test_redis_parse_rsp_success(void) {
"-Bar\r\n", MSG_RSP_REDIS_MULTIBULK); /* array of 2 arrays */
}

static void test_redis_parse_rsp_failure_case(const char* data) {
int original_failures = failures;
struct conn fake_client = {0};
struct mbuf *m = mbuf_get();
const int SW_START = 0; /* Same as SW_START in redis_parse_rsp */

struct msg *rsp = msg_get(&fake_client, 0, 1);
rsp->state = SW_START;
rsp->token = NULL;
const size_t datalen = strlen(data);

/* Copy data into the message */
mbuf_copy(m, (const uint8_t*)data, datalen);
/* Insert a single buffer into the message mbuf header */
STAILQ_INIT(&rsp->mhdr);
ASSERT(STAILQ_EMPTY(&rsp->mhdr));
mbuf_insert(&rsp->mhdr, m);
rsp->pos = m->start;
errno = 0;

redis_parse_rsp(rsp);
expect_same_ptr(m->start, rsp->pos, "redis_parse_rsp: expected rsp->pos to be m->start");
expect_same_int(MSG_PARSE_ERROR, rsp->result, "redis_parse_rsp: expected MSG_PARSE_ERROR");
expect_same_int(EINVAL, errno, "redis_parse_rsp: expected errno=EINVAL");

msg_put(rsp);
/* mbuf_put(m); */
if (failures > original_failures) {
fprintf(stderr, "test_redis_parse_rsp_failure_case failed for (%s)", data);
}
}


/* Test support for https://redis.io/topics/protocol */
static void test_redis_parse_rsp_failure(void) {
test_redis_parse_rsp_failure_case("*\r\n");
test_redis_parse_rsp_failure_case(":x\r\n");
test_redis_parse_rsp_failure_case("$6\r\nfoobarr\r\n");
test_redis_parse_rsp_failure_case("$6\r\nfoobar\n\n");
test_redis_parse_rsp_failure_case("$0\r\nx\r\n");
test_redis_parse_rsp_failure_case("$0\n");
test_redis_parse_rsp_failure_case("*2\r\n"
"*3\r\n"
":1\r\n"
":2\r\n"
":3\r\n"
"*2\r\n"
"\r\n");
}

static void test_memcache_parse_rsp_success_case(const char* data, int expected) {
struct conn fake_client = {0};
struct mbuf *m = mbuf_get();
Expand All @@ -197,7 +247,11 @@ static void test_memcache_parse_rsp_success_case(const char* data, int expected)
expect_same_ptr(m->last, rsp->pos, "memcache_parse_rsp: expected rsp->pos to be m->last");
expect_same_int(SW_START, rsp->state, "memcache_parse_rsp: expected state to be SW_START after parsing full buffer");
expect_same_int(expected, rsp->type, "memcache_parse_rsp: expected response type to be parsed");
expect_same_int(0, fake_client.err, "redis_parse_req: expected no connection error");
expect_same_int(0, fake_client.err, "memcache_parse_rsp: expected no connection error");
expect_same_int(0, rsp->request, "memcache_parse_rsp: expected response");
expect_same_int(0, rsp->error, "memcache_parse_rsp: expected no error");
expect_same_int(0, rsp->swallow, "memcache_parse_rsp: expected swallow=0");
expect_same_int(0, errno, "memcache_parse_rsp: expected errno=0");

msg_put(rsp);
/* mbuf_put(m); */
Expand All @@ -224,11 +278,55 @@ static void test_memcache_parse_rsp_success(void) {
test_memcache_parse_rsp_success_case("VERSION 1.5.22\r\n", MSG_RSP_MC_VERSION);
}

static void test_memcache_parse_rsp_failure_case(const char* data) {
struct conn fake_client = {0};
struct mbuf *m = mbuf_get();
const int SW_START = 0; /* Same as SW_START in memcache_parse_rsp */
const int original_failures = failures;

struct msg *rsp = msg_get(&fake_client, 0, 0);
rsp->state = SW_START;
rsp->token = NULL;
const size_t datalen = strlen(data);

/* Copy data into the message */
mbuf_copy(m, (const uint8_t*)data, datalen);
/* Insert a single buffer into the message mbuf header */
STAILQ_INIT(&rsp->mhdr);
ASSERT(STAILQ_EMPTY(&rsp->mhdr));
mbuf_insert(&rsp->mhdr, m);
rsp->pos = m->start;
errno = 0;

memcache_parse_rsp(rsp);
expect_same_ptr(m->start, rsp->pos, "memcache_parse_rsp: expected rsp->pos to be m->start");
expect_same_int(0, rsp->type, "memcache_parse_rsp: expected response type to be parsed");
expect_same_int(MSG_PARSE_ERROR, rsp->result, "memcache_parse_rsp: expected MSG_PARSE_ERROR");
expect_same_int(EINVAL, errno, "memcache_parse_rsp: expected EINVAL");

msg_put(rsp);
/* mbuf_put(m); */
if (original_failures != failures) {
printf("Saw test failures for test_memcache_parse_rsp_success_case (%s)\n",
data);
}
}


static void test_memcache_parse_rsp_failure(void) {
test_memcache_parse_rsp_failure_case("\r\n");
test_memcache_parse_rsp_failure_case("ENDD\r\n");
test_memcache_parse_rsp_failure_case("\r");
test_memcache_parse_rsp_failure_case("-1\r\n");
}

static void test_memcache_parse_req_success_case(const char* data, int expected) {
const int original_failures = failures;
struct conn fake_client = {0};
struct mbuf *m = mbuf_get();
const int SW_START = 0; /* Same as SW_START in memcache_parse_req */
/* in the test cases, the substring noreply only occurs for valid noreply requests */
const int expected_noreply = strstr(data, " noreply") != NULL;

struct msg *req = msg_get(&fake_client, 1, 0);
req->state = SW_START;
Expand All @@ -247,7 +345,13 @@ static void test_memcache_parse_req_success_case(const char* data, int expected)
expect_same_ptr(m->last, req->pos, "memcache_parse_req: expected req->pos to be m->last");
expect_same_int(SW_START, req->state, "memcache_parse_req: expected state to be SW_START after parsing full buffer");
expect_same_int(expected, req->type, "memcache_parse_req: expected response type to be parsed");
expect_same_int(0, fake_client.err, "redis_parse_req: expected no connection error");
expect_same_int(expected_noreply, req->noreply, "memcache_parse_req: unexpected noreply value");
expect_same_int(0, req->noforward, "memcache_parse_req: unexpected noforward value");
expect_same_int(1, req->request, "memcache_parse_req: expected request");
expect_same_int(0, req->error, "memcache_parse_req: expected no error");
expect_same_int(strstr(data, "quit\r\n") != NULL ? 1 : 0, req->quit,
"memcache_parse_req: unexpected quit value");
expect_same_int(0, fake_client.err, "memcache_parse_req: expected no connection error");

msg_put(req);
/* mbuf_put(m); */
Expand Down Expand Up @@ -288,6 +392,66 @@ static void test_memcache_parse_req_success(void) {
test_memcache_parse_req_success_case("version\r\n", MSG_REQ_MC_VERSION);
}

static void test_memcache_parse_req_failure_case(const char* data) {
const int original_failures = failures;
struct conn fake_client = {0};
struct mbuf *m = mbuf_get();
const int SW_START = 0; /* Same as SW_START in memcache_parse_req */

struct msg *req = msg_get(&fake_client, 1, 0);
req->state = SW_START;
req->token = NULL;
const size_t datalen = strlen(data);

/* Copy data into the message */
mbuf_copy(m, (const uint8_t*)data, datalen);
/* Insert a single buffer into the message mbuf header */
STAILQ_INIT(&req->mhdr);
ASSERT(STAILQ_EMPTY(&req->mhdr));
mbuf_insert(&req->mhdr, m);
req->pos = m->start;
errno = 0;

memcache_parse_req(req);

expect_same_ptr(m->start, req->pos, "memcache_parse_rsp: expected rsp->pos to be m->start");
expect_same_int(MSG_PARSE_ERROR, req->result, "memcache_parse_rsp: expected MSG_PARSE_ERROR");
expect_same_int(EINVAL, errno, "memcache_parse_rsp: expected EINVAL");

msg_put(req);
/* mbuf_put(m); */
if (original_failures != failures) {
printf("Saw test failures for test_memcache_parse_req_success_case (%s)\n",
data);
}
}

static void test_memcache_parse_req_failure(void) {
/* key length exceeds 250 */
test_memcache_parse_req_failure_case("add xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 0 600 5\r\nvalue\r\n");
test_memcache_parse_req_failure_case("add\r\n");
test_memcache_parse_req_failure_case("get\r\n");
test_memcache_parse_req_failure_case("get \r\n");
test_memcache_parse_req_failure_case("get key\r\r");
test_memcache_parse_req_failure_case("append key 0 600\r\n");
test_memcache_parse_req_failure_case("cas key 0 600 5 \r\n");
test_memcache_parse_req_failure_case("decr key 0 extra\r\n");
test_memcache_parse_req_failure_case("decr key\r\n");
test_memcache_parse_req_failure_case("delete \r\n");
test_memcache_parse_req_failure_case("DELETE key\r\n");
test_memcache_parse_req_failure_case("gets\r\n");
test_memcache_parse_req_failure_case("incr\r\n");
test_memcache_parse_req_failure_case("incr key 0notanint\r\n");
test_memcache_parse_req_failure_case("prepend key 0 600 5\r\nvalueextra\r\n");
test_memcache_parse_req_failure_case("prepend key 0 600 0 noreply\r\r");
/* test_memcache_parse_req_failure_case("quit unknownarg\r\n"); */
test_memcache_parse_req_failure_case("replace key 0 9 ?\r\n\r\n");
test_memcache_parse_req_failure_case("set key 0 5 10 noreply\r\nvalue12345\r\r");
test_memcache_parse_req_failure_case("set key 0 600 5\r\nvaluee\r\n");
test_memcache_parse_req_failure_case("touch missingarg\r\n");
test_memcache_parse_req_failure_case("version extra\r\n");
}

int main(int argc, char **argv) {
struct instance nci = {0};
nci.mbuf_chunk_size = MBUF_SIZE;
Expand All @@ -301,6 +465,10 @@ int main(int argc, char **argv) {
test_redis_parse_req_success();
test_memcache_parse_rsp_success();
test_memcache_parse_req_success();
printf("Starting tests of request/response parsing failures\n");
test_memcache_parse_rsp_failure();
test_memcache_parse_req_failure();
test_redis_parse_rsp_failure();
printf("%d successes, %d failures\n", successes, failures);

msg_deinit();
Expand Down

0 comments on commit 1653924

Please sign in to comment.