Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
hiredis
Commits
07db464b
Commit
07db464b
authored
Oct 10, 2011
by
Pieter Noordhuis
Browse files
Factor protocol formatting code to separate file
parent
10da4544
Changes
4
Hide whitespace changes
Inline
Side-by-side
Makefile
View file @
07db464b
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file
# This file is released under the BSD license, see the COPYING file
OBJ
=
net.o hiredis.o sds.o async.o parser.o object.o handle.o
OBJ
=
net.o hiredis.o sds.o async.o parser.o object.o handle.o
format.o
BINS
=
hiredis-example hiredis-test
BINS
=
hiredis-example hiredis-test
LIBNAME
=
libhiredis
LIBNAME
=
libhiredis
...
...
format.c
0 → 100644
View file @
07db464b
#include "fmacros.h"
/* misc */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
/* local */
#include "format.h"
#include "sds.h"
/* Number of bytes to represent specified integer as string */
static
int
intlen
(
int
i
)
{
int
len
=
0
;
if
(
i
<
0
)
{
len
++
;
i
=
-
i
;
}
do
{
len
++
;
i
/=
10
;
}
while
(
i
);
return
len
;
}
/* Protocol length of bulk with specified length */
static
size_t
bulklen
(
size_t
len
)
{
return
1
+
intlen
(
len
)
+
2
+
len
+
2
;
}
int
redis_format_vcommand
(
char
**
target
,
const
char
*
format
,
va_list
ap
)
{
const
char
*
c
=
format
;
char
*
cmd
=
NULL
;
/* final command */
int
pos
;
/* position in final command */
sds
curarg
,
newarg
;
/* current argument */
int
touched
=
0
;
/* was the current argument touched? */
char
**
curargv
=
NULL
,
**
newargv
=
NULL
;
int
argc
=
0
;
int
totlen
=
0
;
int
j
;
/* Abort if there is not target to set */
if
(
target
==
NULL
)
return
-
1
;
/* Build the command string accordingly to protocol */
curarg
=
sdsempty
();
if
(
curarg
==
NULL
)
return
-
1
;
while
(
*
c
!=
'\0'
)
{
if
(
*
c
!=
'%'
||
c
[
1
]
==
'\0'
)
{
if
(
*
c
==
' '
)
{
if
(
touched
)
{
newargv
=
realloc
(
curargv
,
sizeof
(
char
*
)
*
(
argc
+
1
));
if
(
newargv
==
NULL
)
goto
err
;
curargv
=
newargv
;
curargv
[
argc
++
]
=
curarg
;
totlen
+=
bulklen
(
sdslen
(
curarg
));
/* curarg is put in argv so it can be overwritten. */
curarg
=
sdsempty
();
if
(
curarg
==
NULL
)
goto
err
;
touched
=
0
;
}
}
else
{
newarg
=
sdscatlen
(
curarg
,
c
,
1
);
if
(
newarg
==
NULL
)
goto
err
;
curarg
=
newarg
;
touched
=
1
;
}
}
else
{
char
*
arg
;
size_t
size
;
/* Set newarg so it can be checked even if it is not touched. */
newarg
=
curarg
;
switch
(
c
[
1
])
{
case
's'
:
arg
=
va_arg
(
ap
,
char
*
);
size
=
strlen
(
arg
);
if
(
size
>
0
)
newarg
=
sdscatlen
(
curarg
,
arg
,
size
);
break
;
case
'b'
:
arg
=
va_arg
(
ap
,
char
*
);
size
=
va_arg
(
ap
,
size_t
);
if
(
size
>
0
)
newarg
=
sdscatlen
(
curarg
,
arg
,
size
);
break
;
case
'%'
:
newarg
=
sdscat
(
curarg
,
"%"
);
break
;
default:
/* Try to detect printf format */
{
char
_format
[
16
];
const
char
*
_p
=
c
+
1
;
size_t
_l
=
0
;
va_list
_cpy
;
/* Flags */
if
(
*
_p
!=
'\0'
&&
*
_p
==
'#'
)
_p
++
;
if
(
*
_p
!=
'\0'
&&
*
_p
==
'0'
)
_p
++
;
if
(
*
_p
!=
'\0'
&&
*
_p
==
'-'
)
_p
++
;
if
(
*
_p
!=
'\0'
&&
*
_p
==
' '
)
_p
++
;
if
(
*
_p
!=
'\0'
&&
*
_p
==
'+'
)
_p
++
;
/* Field width */
while
(
*
_p
!=
'\0'
&&
isdigit
(
*
_p
))
_p
++
;
/* Precision */
if
(
*
_p
==
'.'
)
{
_p
++
;
while
(
*
_p
!=
'\0'
&&
isdigit
(
*
_p
))
_p
++
;
}
/* Copy va_list before consuming with va_arg */
va_copy
(
_cpy
,
ap
);
/* Integer conversion (without modifiers) */
if
(
strchr
(
"diouxX"
,
*
_p
)
!=
NULL
)
{
va_arg
(
ap
,
int
);
goto
fmt_valid
;
}
/* Double conversion (without modifiers) */
if
(
strchr
(
"eEfFgGaA"
,
*
_p
)
!=
NULL
)
{
va_arg
(
ap
,
double
);
goto
fmt_valid
;
}
/* Size: char */
if
(
_p
[
0
]
==
'h'
&&
_p
[
1
]
==
'h'
)
{
_p
+=
2
;
if
(
*
_p
!=
'\0'
&&
strchr
(
"diouxX"
,
*
_p
)
!=
NULL
)
{
va_arg
(
ap
,
int
);
/* char gets promoted to int */
goto
fmt_valid
;
}
goto
fmt_invalid
;
}
/* Size: short */
if
(
_p
[
0
]
==
'h'
)
{
_p
+=
1
;
if
(
*
_p
!=
'\0'
&&
strchr
(
"diouxX"
,
*
_p
)
!=
NULL
)
{
va_arg
(
ap
,
int
);
/* short gets promoted to int */
goto
fmt_valid
;
}
goto
fmt_invalid
;
}
/* Size: long long */
if
(
_p
[
0
]
==
'l'
&&
_p
[
1
]
==
'l'
)
{
_p
+=
2
;
if
(
*
_p
!=
'\0'
&&
strchr
(
"diouxX"
,
*
_p
)
!=
NULL
)
{
va_arg
(
ap
,
long
long
);
goto
fmt_valid
;
}
goto
fmt_invalid
;
}
/* Size: long */
if
(
_p
[
0
]
==
'l'
)
{
_p
+=
1
;
if
(
*
_p
!=
'\0'
&&
strchr
(
"diouxX"
,
*
_p
)
!=
NULL
)
{
va_arg
(
ap
,
long
);
goto
fmt_valid
;
}
goto
fmt_invalid
;
}
fmt_invalid:
va_end
(
_cpy
);
goto
err
;
fmt_valid:
_l
=
(
_p
+
1
)
-
c
;
if
(
_l
<
sizeof
(
_format
)
-
2
)
{
memcpy
(
_format
,
c
,
_l
);
_format
[
_l
]
=
'\0'
;
newarg
=
sdscatvprintf
(
curarg
,
_format
,
_cpy
);
/* Update current position (note: outer blocks
* increment c twice so compensate here) */
c
=
_p
-
1
;
}
va_end
(
_cpy
);
break
;
}
}
if
(
newarg
==
NULL
)
goto
err
;
curarg
=
newarg
;
touched
=
1
;
c
++
;
}
c
++
;
}
/* Add the last argument if needed */
if
(
touched
)
{
newargv
=
realloc
(
curargv
,
sizeof
(
char
*
)
*
(
argc
+
1
));
if
(
newargv
==
NULL
)
goto
err
;
curargv
=
newargv
;
curargv
[
argc
++
]
=
curarg
;
totlen
+=
bulklen
(
sdslen
(
curarg
));
}
else
{
sdsfree
(
curarg
);
}
/* Clear curarg because it was put in curargv or was free'd. */
curarg
=
NULL
;
/* Add bytes needed to hold multi bulk count */
totlen
+=
1
+
intlen
(
argc
)
+
2
;
/* Build the command at protocol level */
cmd
=
malloc
(
totlen
+
1
);
if
(
cmd
==
NULL
)
goto
err
;
pos
=
sprintf
(
cmd
,
"*%d
\r\n
"
,
argc
);
for
(
j
=
0
;
j
<
argc
;
j
++
)
{
pos
+=
sprintf
(
cmd
+
pos
,
"$%zu
\r\n
"
,
sdslen
(
curargv
[
j
]));
memcpy
(
cmd
+
pos
,
curargv
[
j
],
sdslen
(
curargv
[
j
]));
pos
+=
sdslen
(
curargv
[
j
]);
sdsfree
(
curargv
[
j
]);
cmd
[
pos
++
]
=
'\r'
;
cmd
[
pos
++
]
=
'\n'
;
}
assert
(
pos
==
totlen
);
cmd
[
pos
]
=
'\0'
;
free
(
curargv
);
*
target
=
cmd
;
return
totlen
;
err:
while
(
argc
--
)
sdsfree
(
curargv
[
argc
]);
free
(
curargv
);
if
(
curarg
!=
NULL
)
sdsfree
(
curarg
);
/* No need to check cmd since it is the last statement that can fail,
* but do it anyway to be as defensive as possible. */
if
(
cmd
!=
NULL
)
free
(
cmd
);
return
-
1
;
}
/* Format a command according to the Redis protocol. This function
* takes a format similar to printf:
*
* %s represents a C null terminated string you want to interpolate
* %b represents a binary safe string
*
* When using %b you need to provide both the pointer to the string
* and the length in bytes. Examples:
*
* len = redisFormatCommand(target, "GET %s", mykey);
* len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen);
*/
int
redis_format_command
(
char
**
target
,
const
char
*
format
,
...)
{
va_list
ap
;
int
len
;
va_start
(
ap
,
format
);
len
=
redis_format_vcommand
(
target
,
format
,
ap
);
va_end
(
ap
);
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
redis_format_command_argv
(
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
+=
bulklen
(
len
);
}
/* Build the command at protocol level */
cmd
=
malloc
(
totlen
+
1
);
if
(
cmd
==
NULL
)
return
-
1
;
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
[
pos
]
=
'\0'
;
*
target
=
cmd
;
return
totlen
;
}
format.h
0 → 100644
View file @
07db464b
#ifndef _HIREDIS_FORMAT_H
#define _HIREDIS_FORMAT_H 1
int
redis_format_vcommand
(
char
**
target
,
const
char
*
format
,
va_list
ap
);
int
redis_format_command
(
char
**
target
,
const
char
*
format
,
...);
int
redis_format_command_argv
(
char
**
target
,
int
argc
,
const
char
**
argv
,
const
size_t
*
argvlen
);
#endif
test/test-format.c
0 → 100644
View file @
07db464b
#include "../fmacros.h"
/* misc */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
/* local */
#include "../format.h"
#include "test-helper.h"
void
test_format_without_interpolation
(
void
)
{
char
*
cmd
;
int
len
;
len
=
redis_format_command
(
&
cmd
,
"SET foo bar"
);
assert
(
strncmp
(
cmd
,
"*3
\r\n
$3
\r\n
SET
\r\n
$3
\r\n
foo
\r\n
$3
\r\n
bar
\r\n
"
,
len
)
==
0
&&
len
==
4
+
4
+
(
3
+
2
)
+
4
+
(
3
+
2
)
+
4
+
(
3
+
2
));
free
(
cmd
);
}
void
test_format_with_string_interpolation
(
void
)
{
char
*
cmd
;
int
len
;
/* Regular */
len
=
redis_format_command
(
&
cmd
,
"SET %s %s"
,
"foo"
,
"bar"
);
assert
(
strncmp
(
cmd
,
"*3
\r\n
$3
\r\n
SET
\r\n
$3
\r\n
foo
\r\n
$3
\r\n
bar
\r\n
"
,
len
)
==
0
&&
len
==
4
+
4
+
(
3
+
2
)
+
4
+
(
3
+
2
)
+
4
+
(
3
+
2
));
free
(
cmd
);
/* With empty string */
len
=
redis_format_command
(
&
cmd
,
"SET %s %s"
,
"foo"
,
""
);
assert
(
strncmp
(
cmd
,
"*3
\r\n
$3
\r\n
SET
\r\n
$3
\r\n
foo
\r\n
$0
\r\n\r\n
"
,
len
)
==
0
&&
len
==
4
+
4
+
(
3
+
2
)
+
4
+
(
3
+
2
)
+
4
+
(
0
+
2
));
free
(
cmd
);
/* Empty string between non-empty args */
len
=
redis_format_command
(
&
cmd
,
"SET %s %s"
,
""
,
"bar"
);
assert
(
strncmp
(
cmd
,
"*3
\r\n
$3
\r\n
SET
\r\n
$0
\r\n\r\n
$3
\r\n
bar
\r\n
"
,
len
)
==
0
&&
len
==
4
+
4
+
(
3
+
2
)
+
4
+
(
0
+
2
)
+
4
+
(
3
+
2
));
free
(
cmd
);
}
void
test_format_with_binary_interpolation
(
void
)
{
char
*
cmd
;
int
len
;
/* Regular */
len
=
redis_format_command
(
&
cmd
,
"SET %b %b"
,
"f
\0
o"
,
3
,
"b
\0
r"
,
3
);
assert
(
strncmp
(
cmd
,
"*3
\r\n
$3
\r\n
SET
\r\n
$3
\r\n
f
\0
o
\r\n
$3
\r\n
b
\0
r
\r\n
"
,
len
)
==
0
&&
len
==
4
+
4
+
(
3
+
2
)
+
4
+
(
3
+
2
)
+
4
+
(
3
+
2
));
free
(
cmd
);
/* With empty string */
len
=
redis_format_command
(
&
cmd
,
"SET %b %b"
,
"f
\0
o"
,
3
,
""
,
0
);
assert
(
strncmp
(
cmd
,
"*3
\r\n
$3
\r\n
SET
\r\n
$3
\r\n
f
\0
o
\r\n
$0
\r\n\r\n
"
,
len
)
==
0
&&
len
==
4
+
4
+
(
3
+
2
)
+
4
+
(
3
+
2
)
+
4
+
(
0
+
2
));
free
(
cmd
);
/* Empty string between non-empty args */
len
=
redis_format_command
(
&
cmd
,
"SET %b %b"
,
""
,
0
,
"b
\0
r"
,
3
);
assert
(
strncmp
(
cmd
,
"*3
\r\n
$3
\r\n
SET
\r\n
$0
\r\n\r\n
$3
\r\n
b
\0
r
\r\n
"
,
len
)
==
0
&&
len
==
4
+
4
+
(
3
+
2
)
+
4
+
(
0
+
2
)
+
4
+
(
3
+
2
));
free
(
cmd
);
}
void
test_format_with_literal_percent
(
void
)
{
char
*
cmd
;
int
len
;
len
=
redis_format_command
(
&
cmd
,
"SET %% %%"
);
assert
(
strncmp
(
cmd
,
"*3
\r\n
$3
\r\n
SET
\r\n
$1
\r\n
%
\r\n
$1
\r\n
%
\r\n
"
,
len
)
==
0
&&
len
==
4
+
4
+
(
3
+
2
)
+
4
+
(
1
+
2
)
+
4
+
(
1
+
2
));
free
(
cmd
);
}
void
test_format_with_printf_format
(
void
)
{
char
*
cmd
;
int
len
;
/* Vararg width depends on the type. These tests make sure that the
* width is correctly determined using the format and subsequent varargs
* can correctly be interpolated. */
#define INTEGER_WIDTH_TEST(fmt, type) do { \
type value = 123; \
len = redis_format_command(&cmd,"key:%08" fmt " str:%s", value, "hello"); \
assert(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
len == 4+5+(12+2)+4+(9+2)); \
free(cmd); \
} while(0)
#define FLOAT_WIDTH_TEST(type) do { \
type value = 123.0; \
len = redis_format_command(&cmd,"key:%08.3f str:%s", value, "hello"); \
assert(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
len == 4+5+(12+2)+4+(9+2)); \
free(cmd); \
} while(0)
INTEGER_WIDTH_TEST
(
"d"
,
int
);
INTEGER_WIDTH_TEST
(
"hhd"
,
char
);
INTEGER_WIDTH_TEST
(
"hd"
,
short
);
INTEGER_WIDTH_TEST
(
"ld"
,
long
);
INTEGER_WIDTH_TEST
(
"lld"
,
long
long
);
INTEGER_WIDTH_TEST
(
"u"
,
unsigned
int
);
INTEGER_WIDTH_TEST
(
"hhu"
,
unsigned
char
);
INTEGER_WIDTH_TEST
(
"hu"
,
unsigned
short
);
INTEGER_WIDTH_TEST
(
"lu"
,
unsigned
long
);
INTEGER_WIDTH_TEST
(
"llu"
,
unsigned
long
long
);
FLOAT_WIDTH_TEST
(
float
);
FLOAT_WIDTH_TEST
(
double
);
}
void
test_format_with_invalid_printf_format
(
void
)
{
char
*
cmd
;
int
len
;
len
=
redis_format_command
(
&
cmd
,
"key:%08p %b"
,
1234
,
"foo"
,
3
);
assert
(
len
==
-
1
);
}
void
test_format_argv
(
void
)
{
char
*
cmd
;
int
len
;
int
argc
=
3
;
const
char
*
argv
[
3
]
=
{
"SET"
,
"foo
\0
xxx"
,
"bar"
};
size_t
lens
[
3
]
=
{
3
,
7
,
3
};
/* Without argument length */
len
=
redis_format_command_argv
(
&
cmd
,
argc
,
argv
,
NULL
);
assert
(
strncmp
(
cmd
,
"*3
\r\n
$3
\r\n
SET
\r\n
$3
\r\n
foo
\r\n
$3
\r\n
bar
\r\n
"
,
len
)
==
0
&&
len
==
4
+
4
+
(
3
+
2
)
+
4
+
(
3
+
2
)
+
4
+
(
3
+
2
));
free
(
cmd
);
/* With argument length */
len
=
redis_format_command_argv
(
&
cmd
,
argc
,
argv
,
lens
);
assert
(
strncmp
(
cmd
,
"*3
\r\n
$3
\r\n
SET
\r\n
$7
\r\n
foo
\0
xxx
\r\n
$3
\r\n
bar
\r\n
"
,
len
)
==
0
&&
len
==
4
+
4
+
(
3
+
2
)
+
4
+
(
7
+
2
)
+
4
+
(
3
+
2
));
free
(
cmd
);
}
int
main
(
void
)
{
test_format_without_interpolation
();
test_format_with_string_interpolation
();
test_format_with_binary_interpolation
();
test_format_with_literal_percent
();
test_format_with_printf_format
();
test_format_with_invalid_printf_format
();
test_format_argv
();
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment