]>
The Tcpdump Group git mirrors - tcpdump/blob - print-resp.c
2 * This file implements decoding of the REdis Serialization Protocol.
5 * Copyright (c) 2015 The TCPDUMP project
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
30 * Initial contribution by Andrew Darqui (andrew.darqui@gmail.com).
33 #define NETDISSECT_REWORKED
38 #include <netdissect-stdinc.h>
39 #include "netdissect.h"
47 static const char tstr
[] = " [|RESP]";
50 * For information regarding RESP, see: http://redis.io/topics/protocol
53 #define RESP_SIMPLE_STRING '+'
54 #define RESP_ERROR '-'
55 #define RESP_INTEGER ':'
56 #define RESP_BULK_STRING '$'
57 #define RESP_ARRAY '*'
59 #define resp_print_empty(ndo) ND_PRINT((ndo, " empty"))
60 #define resp_print_null(ndo) ND_PRINT((ndo, " null"))
61 #define resp_print_invalid(ndo) ND_PRINT((ndo, " invalid"))
63 void resp_print(netdissect_options
*, const u_char
*, u_int
);
64 static int resp_parse(netdissect_options
*, register const u_char
*, int);
65 static int resp_print_string_error_integer(netdissect_options
*, register const u_char
*, int);
66 static int resp_print_simple_string(netdissect_options
*, register const u_char
*, int);
67 static int resp_print_integer(netdissect_options
*, register const u_char
*, int);
68 static int resp_print_error(netdissect_options
*, register const u_char
*, int);
69 static int resp_print_bulk_string(netdissect_options
*, register const u_char
*, int);
70 static int resp_print_bulk_array(netdissect_options
*, register const u_char
*, int);
71 static int resp_print_inline(netdissect_options
*, register const u_char
*, int);
75 * Attempts to move our 'ptr' forward until a \r\n is found,
76 * while also making sure we don't exceed the buffer 'len'.
77 * If we exceed, jump to trunc.
79 #define MOVE_FORWARD(ptr, len) \
80 while(*ptr != '\r' && *(ptr+1) != '\n') { ND_TCHECK2(*ptr, 2); ptr++; len--; }
83 * MOVE_FORWARD_CR_OR_LF
84 * Attempts to move our 'ptr' forward until a \r or \n is found,
85 * while also making sure we don't exceed the buffer 'len'.
86 * If we exceed, jump to trunc.
88 #define MOVE_FORWARD_CR_OR_LF(ptr, len) \
89 while(*ptr != '\r' && *ptr != '\n') { ND_TCHECK(*ptr); ptr++; len--; }
93 * Consume all consecutive \r and \n bytes.
94 * If we exceed 'len', jump to trunc.
96 #define CONSUME_CR_OR_LF(ptr, len) \
97 while (*ptr == '\r' || *ptr == '\n') { ND_TCHECK(*ptr); ptr++; len--; }
101 * Attempts to increment our 'ptr' by 'increment' bytes.
102 * If our increment exceeds the buffer length (len - increment),
103 * bail out by jumping to the trunc goto tag.
105 #define INCBY(ptr, increment, len) \
106 { ND_TCHECK2(*ptr, increment); ptr+=increment; len-=increment; }
110 * Increment our ptr by 1 byte.
111 * Most often used to skip an opcode (+-:*$ etc)
113 #define INC1(ptr, len) INCBY(ptr, 1, len)
117 * Increment our ptr by 2 bytes.
118 * Most often used to skip CRLF (\r\n).
120 #define INC2(ptr, len) INCBY(ptr, 2, len)
124 * If ret_len is < 0, jump to the trunc tag which returns (-1)
125 * and 'bubbles up' to printing tstr. Otherwise, return ret_len.
127 #define TEST_RET_LEN(rl) \
128 if (rl < 0) { goto trunc; } else { return rl; }
131 * TEST_RET_LEN_NORETURN
132 * If ret_len is < 0, jump to the trunc tag which returns (-1)
133 * and 'bubbles up' to printing tstr. Otherwise, continue onward.
135 #define TEST_RET_LEN_NORETURN(rl) \
136 if (rl < 0) { goto trunc; }
140 * Prints a segment in the form of: ' "<stuff>"\n"
142 #define RESP_PRINT_SEGMENT(_ndo, _bp, _len) \
143 ND_PRINT((_ndo, " \"")); \
144 if (fn_printn(_ndo, _bp, _len, _ndo->ndo_snapend)) \
146 fn_print_char(_ndo, '"');
149 resp_print(netdissect_options
*ndo
, const u_char
*bp
, u_int length
)
151 int ret_len
= 0, length_cur
= length
;
153 if(!bp
|| length
<= 0)
156 ND_PRINT((ndo
, ": RESP"));
157 while (length_cur
> 0) {
159 * This block supports redis pipelining.
160 * For example, multiple operations can be pipelined within the same string:
161 * "*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n"
163 * "PING\r\nPING\r\nPING\r\n"
164 * In order to handle this case, we must try and parse 'bp' until
165 * 'length' bytes have been processed or we reach a trunc condition.
167 ret_len
= resp_parse(ndo
, bp
, length_cur
);
168 TEST_RET_LEN_NORETURN(ret_len
);
170 length_cur
-= ret_len
;
176 ND_PRINT((ndo
, "%s", tstr
));
180 resp_parse(netdissect_options
*ndo
, register const u_char
*bp
, int length
)
188 case RESP_SIMPLE_STRING
: ret_len
= resp_print_simple_string(ndo
, bp
, length
); break;
189 case RESP_INTEGER
: ret_len
= resp_print_integer(ndo
, bp
, length
); break;
190 case RESP_ERROR
: ret_len
= resp_print_error(ndo
, bp
, length
); break;
191 case RESP_BULK_STRING
: ret_len
= resp_print_bulk_string(ndo
, bp
, length
); break;
192 case RESP_ARRAY
: ret_len
= resp_print_bulk_array(ndo
, bp
, length
); break;
193 default: ret_len
= resp_print_inline(ndo
, bp
, length
); break;
196 TEST_RET_LEN(ret_len
);
203 resp_print_simple_string(netdissect_options
*ndo
, register const u_char
*bp
, int length
) {
204 return resp_print_string_error_integer(ndo
, bp
, length
);
208 resp_print_integer(netdissect_options
*ndo
, register const u_char
*bp
, int length
) {
209 return resp_print_string_error_integer(ndo
, bp
, length
);
213 resp_print_error(netdissect_options
*ndo
, register const u_char
*bp
, int length
) {
214 return resp_print_string_error_integer(ndo
, bp
, length
);
218 resp_print_string_error_integer(netdissect_options
*ndo
, register const u_char
*bp
, int length
) {
219 int length_cur
= length
, len
, ret_len
= 0;
220 const u_char
*bp_ptr
= bp
;
223 * MOVE_FORWARD moves past the string that follows the (+-;) opcodes
228 MOVE_FORWARD(bp_ptr
, length_cur
);
230 ND_TCHECK2(*bp
, len
);
231 RESP_PRINT_SEGMENT(ndo
, bp
+1, len
-1);
232 ret_len
= len
/*<1byte>+<string>*/ + 2 /*<CRLF>*/;
234 TEST_RET_LEN(ret_len
);
241 resp_print_bulk_string(netdissect_options
*ndo
, register const u_char
*bp
, int length
) {
242 int length_cur
= length
, string_len
;
249 INC1(bp
, length_cur
);
254 strtol_ret
= strtol((const char *)bp
, &p
, 10);
255 if (errno
!= 0 || p
== (const char *)bp
|| strtol_ret
< -1 ||
256 strtol_ret
> INT_MAX
)
257 string_len
= -2; /* invalid */
259 string_len
= (int)strtol_ret
;
262 MOVE_FORWARD(bp
, length_cur
);
265 INC2(bp
, length_cur
);
267 if (string_len
> 0) {
268 /* Byte string of length string_len */
269 ND_TCHECK2(*bp
, string_len
);
270 RESP_PRINT_SEGMENT(ndo
, bp
, string_len
);
273 case 0: resp_print_empty(ndo
); break;
275 /* This is the NULL response. It follows a different pattern: $-1\r\n */
276 resp_print_null(ndo
);
277 TEST_RET_LEN(length
- length_cur
);
278 /* returned ret_len or jumped to trunc */
280 default: resp_print_invalid(ndo
); break;
285 INCBY(bp
, string_len
, length_cur
);
288 INC2(bp
, length_cur
);
290 TEST_RET_LEN(length
- length_cur
);
297 resp_print_bulk_array(netdissect_options
*ndo
, register const u_char
*bp
, int length
) {
298 int length_cur
= length
, array_len
, i
, ret_len
= 0;
305 INC1(bp
, length_cur
);
310 strtol_ret
= strtol((const char *)bp
, &p
, 10);
311 if (errno
!= 0 || p
== (const char *)bp
|| strtol_ret
< -1 ||
312 strtol_ret
> INT_MAX
)
313 array_len
= -2; /* invalid */
315 array_len
= (int)strtol_ret
;
318 MOVE_FORWARD(bp
, length_cur
);
321 INC2(bp
, length_cur
);
324 /* non empty array */
325 for (i
= 0; i
< array_len
; i
++) {
326 ret_len
= resp_parse(ndo
, bp
, length_cur
);
328 TEST_RET_LEN_NORETURN(ret_len
);
331 length_cur
-= ret_len
;
333 TEST_RET_LEN_NORETURN(length
- length_cur
);
336 /* empty, null, or invalid */
338 case 0: resp_print_empty(ndo
); break;
339 case (-1): resp_print_null(ndo
); break;
340 default: resp_print_invalid(ndo
); break;
344 TEST_RET_LEN(length
- length_cur
);
351 resp_print_inline(netdissect_options
*ndo
, register const u_char
*bp
, int length
) {
352 int length_cur
= length
, len
;
353 const u_char
*bp_ptr
;
356 * Inline commands are simply 'strings' followed by \r or \n or both.
357 * Redis will do it's best to split/parse these strings.
358 * This feature of redis is implemented to support the ability of
359 * command parsing from telnet/nc sessions etc.
361 * <string><\r||\n||\r\n...>
363 CONSUME_CR_OR_LF(bp
, length_cur
);
365 MOVE_FORWARD_CR_OR_LF(bp_ptr
, length_cur
);
367 ND_TCHECK2(*bp
, len
);
368 RESP_PRINT_SEGMENT(ndo
, bp
, len
);
369 CONSUME_CR_OR_LF(bp_ptr
, length_cur
);
371 TEST_RET_LEN(length
- length_cur
);