From a3a405bcbab4947fea0b155e93cc752953428e52 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Sun, 31 Oct 2010 10:32:31 +0100 Subject: [PATCH] Format a command using an argument vector --- hiredis.c | 36 ++++++++++++++++++++++++++++++++++++ hiredis.h | 1 + test.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/hiredis.c b/hiredis.c index 771673f..8fa04dc 100644 --- a/hiredis.c +++ b/hiredis.c @@ -578,6 +578,42 @@ int redisFormatCommand(char **target, const char *format, ...) { return len; } +/* Format a command according to the Redis protocol. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + size_t len; + int totlen, j; + + /* Calculate number of bytes needed for the command */ + totlen = 1+intlen(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += 1+intlen(len)+2+len+2; + } + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (!cmd) redisOOM(); + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + pos += sprintf(cmd+pos,"$%zu\r\n",len); + memcpy(cmd+pos,argv[j],len); + pos += len; + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[totlen] = '\0'; + *target = cmd; + return totlen; +} + static int redisContextConnect(redisContext *c, const char *ip, int port) { char err[ANET_ERR_LEN]; if (c->flags & REDIS_BLOCK) { diff --git a/hiredis.h b/hiredis.h index 2bce8d6..d7f7ecc 100644 --- a/hiredis.h +++ b/hiredis.h @@ -125,6 +125,7 @@ int redisReplyReaderGetReply(void *reader, void **reply); /* Functions to format a command according to the protocol. */ int redisvFormatCommand(char **target, const char *format, va_list ap); int redisFormatCommand(char **target, const char *format, ...); +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); redisContext *redisConnect(const char *ip, int port, redisReplyObjectFunctions *fn); redisContext *redisConnectNonBlock(const char *ip, int port, redisReplyObjectFunctions *fn); diff --git a/test.c b/test.c index cc15ba5..b4a5447 100644 --- a/test.c +++ b/test.c @@ -28,6 +28,48 @@ static void __connect(redisContext **target) { } } +static void test_format_commands() { + char *cmd; + int len; + + test("Format command without interpolation: "); + len = redisFormatCommand(&cmd,"SET foo bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%s string interpolation: "); + len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%b string interpolation: "); + len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + const char *argv[3]; + argv[0] = "SET"; + argv[1] = "foo"; + argv[2] = "bar"; + size_t lens[3] = { 3, 3, 3 }; + int argc = 3; + + test("Format command by passing argc/argv without lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,NULL); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command by passing argc/argv with lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,lens); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); +} + static void test_blocking_connection() { redisContext *c; redisReply *reply; @@ -294,6 +336,7 @@ static void test_nonblocking_connection() { } int main(void) { + test_format_commands(); test_blocking_connection(); test_reply_reader(); test_nonblocking_connection();