Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / epan / dissectors / packet-resp.c
blobfc5be87591c0c53ce8fabdc93f237fe1dd7afccd
1 /* packet-resp.c
2 * Routines for Redis Client/Server RESP (REdis Serialization Protocol) v2 as
3 * documented by https://redis.io/topics/protocol
5 * Copyright 2022 Ryan Doyle <ryan <AT> doylenet dot net>
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
11 * SPDX-License-Identifier: GPL-2.0-or-later
13 #include "config.h"
14 #include "packet-tcp.h"
16 #include <epan/packet.h>
17 #include <epan/expert.h>
19 #define RESP_PORT 6379
20 #define CRLF_LENGTH 2
21 #define RESP_TOKEN_PREFIX_LENGTH 1
22 #define MAX_ARRAY_DEPTH_TO_RECURSE 30
23 #define BULK_STRING_MAX_DISPLAY 100
24 #define RESP_NULL_STRING (-1)
25 #define RESP_NULL_ARRAY (-1)
26 #define RESP_REQUEST(pinfo) ((pinfo)->match_uint == (pinfo)->destport)
27 #define RESP_RESPONSE(pinfo) ((pinfo)->match_uint == (pinfo)->srcport)
28 #define DESEGMENT_ENABLED(pinfo) ((pinfo)->can_desegment && resp_desegment)
30 static dissector_handle_t resp_handle;
31 static bool resp_desegment = true;
33 static int proto_resp;
35 static int ett_resp;
36 static int ett_resp_bulk_string;
37 static int ett_resp_array;
39 static expert_field ei_resp_partial;
40 static expert_field ei_resp_malformed_length;
41 static expert_field ei_resp_array_recursion_too_deep;
42 static expert_field ei_resp_reassembled_in_next_frame;
44 static int hf_resp_string;
45 static int hf_resp_error;
46 static int hf_resp_bulk_string;
47 static int hf_resp_bulk_string_length;
48 static int hf_resp_bulk_string_value;
49 static int hf_resp_integer;
50 static int hf_resp_array;
51 static int hf_resp_array_length;
52 static int hf_resp_fragment;
54 static int dissect_resp_loop(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int array_depth, int64_t expected_elements);
55 static void resp_bulk_string_enhance_colinfo_ascii(packet_info *pinfo, int array_depth, int bulk_string_length, const uint8_t *bulk_string_as_str);
56 void proto_reg_handoff_resp(void);
57 void proto_register_resp(void);
59 static int dissect_resp_string(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int string_lenth, int array_depth) {
60 uint8_t *string_value;
62 string_value = tvb_get_string_enc(pinfo->pool, tvb, offset + RESP_TOKEN_PREFIX_LENGTH,
63 string_lenth - RESP_TOKEN_PREFIX_LENGTH, ENC_ASCII);
64 proto_tree_add_string(tree, hf_resp_string, tvb, offset, string_lenth + CRLF_LENGTH, string_value);
66 /* Simple strings can be used as a response for commands */
67 if (RESP_RESPONSE(pinfo) && array_depth == 0) {
68 col_append_sep_str(pinfo->cinfo, COL_INFO, " ", string_value);
71 return string_lenth + CRLF_LENGTH;
74 static int dissect_resp_error(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int string_lenth) {
75 uint8_t *error_value;
77 error_value = tvb_get_string_enc(pinfo->pool, tvb, offset + RESP_TOKEN_PREFIX_LENGTH,
78 string_lenth - RESP_TOKEN_PREFIX_LENGTH, ENC_ASCII);
79 proto_tree_add_string(tree, hf_resp_error, tvb, offset, string_lenth + CRLF_LENGTH, error_value);
80 col_append_fstr(pinfo->cinfo, COL_INFO, " Error: %s", error_value);
81 return string_lenth + CRLF_LENGTH;
84 static int dissect_resp_bulk_string(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int bulk_string_string_length, int array_depth) {
85 uint8_t *bulk_string_length_as_str;
86 int bulk_string_length;
87 int bulk_string_captured_length;
88 int bulk_string_captured_length_with_crlf;
89 proto_item *resp_string_item;
90 proto_tree *resp_string_tree;
92 bulk_string_length_as_str = tvb_get_string_enc(pinfo->pool, tvb, offset + RESP_TOKEN_PREFIX_LENGTH,
93 bulk_string_string_length - RESP_TOKEN_PREFIX_LENGTH, ENC_ASCII);
94 bulk_string_length = (int)g_ascii_strtoll(bulk_string_length_as_str, NULL, 10);
95 /* Negative string lengths */
96 if (bulk_string_length < 0) {
97 /* NULL string */
98 resp_string_item = proto_tree_add_item(tree, hf_resp_bulk_string, tvb, offset,bulk_string_string_length + CRLF_LENGTH, ENC_NA);
99 resp_string_tree = proto_item_add_subtree(resp_string_item, ett_resp_bulk_string);
100 proto_tree_add_int(resp_string_tree, hf_resp_bulk_string_length, tvb, offset, bulk_string_string_length + CRLF_LENGTH, bulk_string_length);
101 if (bulk_string_length == RESP_NULL_STRING) {
102 proto_item_append_text(resp_string_item, ": [NULL]");
103 } else {
104 expert_add_info(pinfo, resp_string_item, &ei_resp_malformed_length);
106 return bulk_string_string_length + CRLF_LENGTH;
109 /* We have either a bulk string or an empty string */
110 int remaining_bytes_for_bulkstring = tvb_captured_length_remaining(tvb, offset + bulk_string_string_length + CRLF_LENGTH);
111 /* Do we have enough bytes in the tvb for what was reported in the string length? */
112 int is_fragmented = remaining_bytes_for_bulkstring < bulk_string_length + CRLF_LENGTH;
113 if (is_fragmented) {
114 if (DESEGMENT_ENABLED(pinfo)) {
115 /* Desegment at the start of the bulk string instead of part way through */
116 pinfo->desegment_offset = offset;
117 /* We know how many bytes we will need */
118 pinfo->desegment_len = bulk_string_length + CRLF_LENGTH - remaining_bytes_for_bulkstring;
119 return -1;
121 /* There's no CRLF, we didn't get all the bytes needed */
122 bulk_string_captured_length = remaining_bytes_for_bulkstring;
123 bulk_string_captured_length_with_crlf = remaining_bytes_for_bulkstring;
124 col_append_str(pinfo->cinfo, COL_INFO, " [partial]");
125 } else {
126 bulk_string_captured_length = bulk_string_length;
127 bulk_string_captured_length_with_crlf = bulk_string_length + CRLF_LENGTH;
130 /* Add protocol items */
131 resp_string_item = proto_tree_add_item(tree, hf_resp_bulk_string, tvb, offset,
132 bulk_string_string_length + CRLF_LENGTH + bulk_string_captured_length_with_crlf,ENC_NA);
133 if (is_fragmented) {
134 expert_add_info(pinfo, resp_string_item, &ei_resp_partial);
136 resp_string_tree = proto_item_add_subtree(resp_string_item, ett_resp_bulk_string);
137 proto_tree_add_int(resp_string_tree, hf_resp_bulk_string_length, tvb, offset, bulk_string_string_length + CRLF_LENGTH, bulk_string_length);
138 offset += bulk_string_string_length + CRLF_LENGTH;
139 if (bulk_string_captured_length > 0) {
140 proto_tree_add_item(resp_string_tree, hf_resp_bulk_string_value, tvb, offset, bulk_string_captured_length, ENC_NA);
143 /* Enhance display */
144 uint8_t *bulk_string_as_str = tvb_get_string_enc(pinfo->pool, tvb, offset, bulk_string_captured_length, ENC_NA);
145 if (g_str_is_ascii(bulk_string_as_str)) {
146 proto_item_append_text(resp_string_item, ": %s", bulk_string_as_str);
147 resp_bulk_string_enhance_colinfo_ascii(pinfo, array_depth, bulk_string_length, bulk_string_as_str);
148 } else if(array_depth == 0) {
149 /* Otherwise, just append that we captured bulk strings (and only do so if they aren't part of an array */
150 col_append_fstr(pinfo->cinfo, COL_INFO, " BulkString(%d)", bulk_string_length);
153 return bulk_string_string_length + CRLF_LENGTH + bulk_string_captured_length_with_crlf;
156 static void resp_bulk_string_enhance_colinfo_ascii(packet_info *pinfo, int array_depth, int bulk_string_length, const uint8_t *bulk_string_as_str) {
157 /* Request commands are arrays */
158 if (RESP_REQUEST(pinfo) && array_depth == 1) {
159 if (bulk_string_length < BULK_STRING_MAX_DISPLAY) {
160 col_append_sep_str(pinfo->cinfo, COL_INFO, " ", bulk_string_as_str);
161 } else {
162 col_append_fstr(pinfo->cinfo, COL_INFO, " BulkString(%d)", bulk_string_length);
164 return;
166 /* Print responses with zero depth */
167 if (RESP_RESPONSE(pinfo) && array_depth == 0 && bulk_string_length < BULK_STRING_MAX_DISPLAY) {
168 col_append_sep_str(pinfo->cinfo, COL_INFO, " ", bulk_string_as_str);
169 return;
171 /* Otherwise, just display that there is a bulk string in the response (for top-level) */
172 if (array_depth == 0) {
173 col_append_fstr(pinfo->cinfo, COL_INFO, " BulkString(%d)", bulk_string_length);
177 static int dissect_resp_integer(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int bulk_string_string_length, int array_depth) {
178 uint8_t *integer_as_string;
179 int64_t integer;
180 integer_as_string = tvb_get_string_enc(pinfo->pool, tvb, offset + RESP_TOKEN_PREFIX_LENGTH,
181 bulk_string_string_length - RESP_TOKEN_PREFIX_LENGTH, ENC_ASCII);
182 integer = g_ascii_strtoll(integer_as_string, NULL, 10);
183 proto_tree_add_int64(tree, hf_resp_integer, tvb, offset, bulk_string_string_length + CRLF_LENGTH, integer);
184 /* Simple integers can be used as a response for commands */
185 if (RESP_RESPONSE(pinfo) && array_depth == 0) {
186 col_append_fstr(pinfo->cinfo, COL_INFO, " %" PRId64, integer);
188 return bulk_string_string_length + CRLF_LENGTH;
191 // NOLINTNEXTLINE(misc-no-recursion)
192 static int dissect_resp_array(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int string_length, int array_depth) {
193 uint8_t *array_length_as_string = tvb_get_string_enc(pinfo->pool, tvb, offset + RESP_TOKEN_PREFIX_LENGTH,
194 string_length - RESP_TOKEN_PREFIX_LENGTH, ENC_ASCII);
195 int64_t array_length = g_ascii_strtoll(array_length_as_string, NULL, 10);
197 /* We'll fix up the length later when we know how long it actually was */
198 proto_item *array_item = proto_tree_add_item(tree, hf_resp_array, tvb, offset, string_length + CRLF_LENGTH, ENC_NA);
200 proto_tree *array_tree = proto_item_add_subtree(array_item, ett_resp_array);
201 proto_tree_add_int64(array_tree, hf_resp_array_length, tvb, offset, string_length + CRLF_LENGTH, array_length);
203 /* Null array, no elements */
204 if (array_length <= 0) {
205 switch (array_length) {
206 case RESP_NULL_ARRAY:
207 proto_item_append_text(array_item, ": NULL");
208 break;
209 case 0:
210 proto_item_append_text(array_item, ": Empty");
211 break;
212 default:
213 expert_add_info(pinfo, array_item, &ei_resp_malformed_length);
214 break;
216 return string_length + CRLF_LENGTH;
219 proto_item_append_text(array_item, ": Length %" PRId64, array_length);
221 /* Bail out if we're recursing too much */
222 if (array_depth > MAX_ARRAY_DEPTH_TO_RECURSE) {
223 expert_add_info(pinfo, array_item, &ei_resp_array_recursion_too_deep);
224 return string_length + CRLF_LENGTH;
227 /* Non-empty array, but we've ran out of bytes in the tvb */
228 if (!tvb_offset_exists(tvb, offset + string_length + CRLF_LENGTH) && array_length > 0) {
229 if (DESEGMENT_ENABLED(pinfo)) {
230 pinfo->desegment_offset = offset;
231 pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
232 return -1;
234 expert_add_info(pinfo, array_item, &ei_resp_partial);
237 /* Add to the info column for responses. Don't do this for requests which are typically commands.
238 * These are extracted in the bulk string dissector */
239 if (RESP_RESPONSE(pinfo) && array_depth == 0) {
240 col_append_fstr(pinfo->cinfo, COL_INFO, " Array(%" PRId64 ")" , array_length);
244 int dissected_offset = dissect_resp_loop(tvb, pinfo, array_tree, offset + string_length + CRLF_LENGTH,array_depth + 1, array_length);
245 if (dissected_offset == -1) {
246 /* Override any desegment lengths previously set as we want to start from the beginning of the array */
247 pinfo->desegment_offset = offset;
248 pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
249 /* We've partially decoded some of the array, but we've asked for all. It will still show in the proto tree so give an
250 * indication as to why it's only partially there*/
251 expert_add_info(pinfo, array_item, &ei_resp_reassembled_in_next_frame);
252 return -1;
254 proto_item_set_len(array_item, dissected_offset - offset);
255 return dissected_offset - offset;
258 // NOLINTNEXTLINE(misc-no-recursion)
259 static int dissect_resp_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int string_length, int array_depth) {
260 switch (tvb_get_uint8(tvb, offset)) {
261 case '+':
262 return dissect_resp_string(tvb, pinfo, tree, offset, string_length, array_depth);
263 case '-':
264 return dissect_resp_error(tvb, pinfo, tree, offset, string_length);
265 case ':':
266 return dissect_resp_integer(tvb, pinfo, tree, offset, string_length, array_depth);
267 case '$':
268 return dissect_resp_bulk_string(tvb, pinfo, tree, offset, string_length, array_depth);
269 case '*':
270 return dissect_resp_array(tvb, pinfo, tree, offset, string_length, array_depth);
271 default:
272 /* We have an erroneous \r\n if the string length is 0. */
273 if (string_length == 0) {
274 return CRLF_LENGTH;
276 /* Otherwise, its:
277 * - A command we don't support yet (RESPv3 commands that aren't implemented yet)
278 * - Reassembly is disabled and data is between packet boundaries
279 * - It's the first frame in a partial capture
280 * */
281 col_append_str(pinfo->cinfo, COL_INFO, " [fragment]");
282 proto_tree_add_item(tree, hf_resp_fragment, tvb, offset, string_length + CRLF_LENGTH,ENC_NA);
283 return string_length + CRLF_LENGTH;
287 // NOLINTNEXTLINE(misc-no-recursion)
288 static int dissect_resp_loop(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int array_depth, int64_t expected_elements) {
289 int error_or_offset;
290 int crlf_string_line_length;
291 int done_elements = 0;
293 while (tvb_offset_exists(tvb, offset)) {
294 /* If we ended up in here from a recursive call when traversing an array, don't drain the tvb to empty.
295 * Only do the amount of elements that are expected in the array */
296 if (expected_elements >= 0 && done_elements == expected_elements) {
297 return offset;
299 crlf_string_line_length = tvb_find_line_end(tvb, offset, -1, NULL, DESEGMENT_ENABLED(pinfo));
300 /* If desegment is disabled, tvb_find_line_end() will return a positive length, regardless if it finds a CRLF */
301 if (crlf_string_line_length == -1) {
302 pinfo->desegment_offset = offset;
303 pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
304 return -1;
306 increment_dissection_depth(pinfo);
307 error_or_offset = dissect_resp_message(tvb, pinfo, tree, offset, crlf_string_line_length, array_depth);
308 decrement_dissection_depth(pinfo);
309 if (error_or_offset == -1) {
310 return -1;
312 done_elements++;
313 offset += error_or_offset;
316 return offset;
319 static int dissect_resp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) {
320 int offset = 0;
321 proto_item *root_resp_item;
322 proto_tree *resp_tree;
324 col_set_str(pinfo->cinfo, COL_PROTOCOL, "RESP");
325 col_clear(pinfo->cinfo, COL_INFO);
326 col_append_str(pinfo->cinfo, COL_INFO, RESP_RESPONSE(pinfo) ? "Response:" : "Request:");
327 root_resp_item = proto_tree_add_item(tree, proto_resp, tvb, 0, -1, ENC_NA);
328 resp_tree = proto_item_add_subtree(root_resp_item, ett_resp);
330 int dissected_length = dissect_resp_loop(tvb, pinfo, resp_tree, offset, 0, -1);
331 if (dissected_length == -1) {
332 col_append_str(pinfo->cinfo, COL_INFO, " [continuation]");
333 return tvb_captured_length(tvb);
335 return dissected_length;
339 void proto_register_resp(void) {
341 static hf_register_info hf[] = {
342 { &hf_resp_string,
343 { "String", "resp.string",
344 FT_STRING, BASE_NONE,
345 NULL, 0x0,
346 NULL, HFILL
349 { &hf_resp_error,
350 { "Error", "resp.error",
351 FT_STRING, BASE_NONE,
352 NULL, 0x0,
353 NULL, HFILL
356 { &hf_resp_bulk_string,
357 { "Bulk String", "resp.bulk_string",
358 FT_NONE, BASE_NONE,
359 NULL, 0x0,
360 NULL, HFILL
363 { &hf_resp_bulk_string_value,
364 { "Value", "resp.bulk_string.value",
365 FT_BYTES, BASE_NONE,
366 NULL, 0x0,
367 NULL, HFILL
370 { &hf_resp_bulk_string_length,
371 { "Length", "resp.bulk_string.length",
372 FT_INT32, BASE_DEC,
373 NULL, 0x0,
374 NULL, HFILL
377 { &hf_resp_integer,
378 { "Integer", "resp.integer",
379 FT_INT64, BASE_DEC,
380 NULL, 0x0,
381 NULL, HFILL
384 { &hf_resp_array,
385 { "Array", "resp.array",
386 FT_NONE, BASE_NONE,
387 NULL, 0x0,
388 NULL, HFILL
391 { &hf_resp_array_length,
392 { "Length", "resp.array.length",
393 FT_INT64, BASE_DEC,
394 NULL, 0x0,
395 NULL, HFILL
398 { &hf_resp_fragment,
399 { "Fragment", "resp.fragment",
400 FT_NONE, BASE_NONE,
401 NULL, 0x0,
402 NULL, HFILL
408 static int *ett[] = {
409 &ett_resp,
410 &ett_resp_bulk_string,
411 &ett_resp_array,
414 static ei_register_info ei[] = {
415 { &ei_resp_partial, { "resp.partial", PI_UNDECODED, PI_NOTE, "Field is only partially decoded", EXPFILL }},
416 { &ei_resp_malformed_length, { "resp.malformed_length", PI_UNDECODED, PI_ERROR, "Malformed length specified", EXPFILL }},
417 { &ei_resp_reassembled_in_next_frame, {"resp.reassembled_in_next_frame", PI_UNDECODED, PI_NOTE,
418 "Array is partially decoded. Re-assembled array is in the next frame", EXPFILL }},
419 { &ei_resp_array_recursion_too_deep, { "resp.array_recursion_too_deep", PI_UNDECODED, PI_NOTE,
420 "Array is too deep to recurse any further. Subsequent elements attached to the protocol "
421 "tree may not reflect their actual location in the array", EXPFILL }},
424 proto_resp = proto_register_protocol("REdis Serialization Protocol", "RESP", "resp");
425 module_t *resp_module = prefs_register_protocol(proto_resp, NULL);
426 prefs_register_bool_preference(resp_module, "desegment_data",
427 "Reassemble RESP data spanning multiple TCP segments",
428 "Whether the RESP dissector should reassemble command and response lines"
429 " spanning multiple TCP segments. To use this option, you must also enable "
430 "\"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
431 &resp_desegment);
433 expert_module_t *expert_pcp = expert_register_protocol(proto_resp);
434 expert_register_field_array(expert_pcp, ei, array_length(ei));
436 resp_handle = register_dissector("resp", dissect_resp, proto_resp);
438 proto_register_field_array(proto_resp, hf, array_length(hf));
439 proto_register_subtree_array(ett, array_length(ett));
443 void proto_reg_handoff_resp(void) {
444 dissector_add_uint_with_preference("tcp.port", RESP_PORT, resp_handle);