2 * Routines for SoupBinTCP 3.0 protocol dissection
3 * Copyright 2013 David Arnold <davida@pobox.com>
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
13 * SoupBinTCP is a framing protocol published and used by NASDAQ to
14 * encapsulate both market data (ITCH) and order entry (OUCH)
15 * protocols. It is derived from the original SOUP protocol, which
16 * was ASCII-based, and relied on an EOL indicator as a message
19 * SoupBinTCP was introduced with OUCH-4.0 / ITCH-4.0 when those
20 * protocols also switched to using a binary representation for
23 * The SOUP/SoupBinTCP protocols are also commonly used by other
24 * financial exchanges, although frequently they are more SOUP-like
25 * than exactly the same. This dissector doesn't attempt to support
26 * any other SOUP-like variants; I think it's probably better to have
27 * separate (if similar) dissectors for them.
29 * The only really complexity in the protocol is the message sequence
30 * numbering. See the comments below for an explanation of how it is
33 * Specifications are available from NASDAQ's website, although the
34 * links to find them tend to move around over time. At the time of
35 * writing the correct URL is:
37 * http://www.nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/soupbintcp.pdf
45 #include <epan/packet.h>
46 #include <epan/prefs.h>
47 #include <epan/proto_data.h>
48 #include <epan/expert.h>
50 #include <wsutil/strtoi.h>
52 /* For tcp_dissect_pdus() */
53 #include "packet-tcp.h"
55 void proto_register_soupbintcp(void);
56 void proto_reg_handoff_soupbintcp(void);
58 /** Session data stored in the conversation */
60 /** Next expected sequence number
62 * Set by the Login Accepted packet, and then updated for each
63 * subsequent Sequenced Data packet during dissection. */
68 /** Per-PDU data, stored in the frame's private data pointer */
70 /** Sequence number for this PDU */
75 /** Packet names, indexed by message type code value */
76 static const value_string pkt_type_val
[] = {
77 { '+', "Debug Packet" },
78 { 'A', "Login Accepted" },
79 { 'H', "Server Heartbeat" },
80 { 'J', "Login Rejected" },
81 { 'L', "Login Request" },
82 { 'O', "Logout Request" },
83 { 'R', "Client Heartbeat" },
84 { 'S', "Sequenced Data" },
85 { 'U', "Unsequenced Data" },
86 { 'Z', "End of Session" },
91 /** Login reject reasons, indexed by code value */
92 static const value_string reject_code_val
[] = {
93 { 'A', "Not authorized" },
94 { 'S', "Session not available" },
99 /* Initialize the protocol and registered fields */
100 static int proto_soupbintcp
;
101 static dissector_handle_t soupbintcp_handle
;
102 static heur_dissector_list_t heur_subdissector_list
;
105 static bool soupbintcp_desegment
= true;
107 /* Initialize the subtree pointers */
108 static int ett_soupbintcp
;
110 /* Header field formatting */
111 static int hf_soupbintcp_packet_length
;
112 static int hf_soupbintcp_packet_type
;
113 static int hf_soupbintcp_message
;
114 static int hf_soupbintcp_text
;
115 static int hf_soupbintcp_username
;
116 static int hf_soupbintcp_password
;
117 static int hf_soupbintcp_session
;
118 static int hf_soupbintcp_seq_num
;
119 static int hf_soupbintcp_next_seq_num
;
120 static int hf_soupbintcp_req_seq_num
;
121 static int hf_soupbintcp_reject_code
;
123 static expert_field ei_soupbintcp_next_seq_num_invalid
;
124 static expert_field ei_soupbintcp_req_seq_num_invalid
;
126 /** Dissector for SoupBinTCP messages */
128 dissect_soupbintcp_common(
133 struct conv_data
*conv_data
;
134 struct pdu_data
*pdu_data
;
135 const char *pkt_name
;
139 proto_tree
*soupbintcp_tree
= NULL
;
140 conversation_t
*conv
= NULL
;
141 uint16_t expected_len
;
144 unsigned this_seq
= 0, next_seq
= 0, key
;
145 heur_dtbl_entry_t
*hdtbl_entry
;
148 /* Record the start of the packet to use as a sequence number key */
149 key
= (unsigned)tvb_raw_offset(tvb
);
151 /* Get the 16-bit big-endian SOUP packet length */
152 expected_len
= tvb_get_ntohs(tvb
, 0);
154 /* Get the 1-byte SOUP message type */
155 pkt_type
= tvb_get_uint8(tvb
, 2);
157 /* Since we use the packet name a few times, get and save that value */
158 pkt_name
= val_to_str(pkt_type
, pkt_type_val
, "Unknown (%u)");
160 /* Set the protocol name in the summary display */
161 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "SoupBinTCP");
163 /* Set the packet name in the info column */
164 col_add_str(pinfo
->cinfo
, COL_INFO
, pkt_name
);
166 /* Sequence number tracking
168 * SOUP does not number packets from client to server (the server
169 * acknowledges all important messages, so the client should use
170 * the acks to figure out if the server received the message, and
171 * otherwise resend it).
173 * Packets from server to client are numbered, but it's implicit.
174 * The Login Accept packet contains the next sequence number that
175 * the server will send, and the client needs to count the
176 * Sequenced Data packets that it receives to know what their
177 * sequence numbers are.
179 * So, we grab the next sequence number from the Login Acceptance
180 * packet, and save it in a conversation_t we associate with the
181 * TCP session. Then, for each Sequenced Data packet we receive,
182 * the first time it's processed (when PINFO_FD_VISITED() is
183 * false), we write it into the PDU's frame's private data pointer
184 * and increment the saved sequence number (in the conversation_t).
186 * If the visited flag is true, then we've dissected this packet
187 * already, and so we can fetch the sequence number from the
188 * frame's private data area.
190 * In either case, if there's any problem, we report zero as the
191 * sequence number, and try to continue dissecting. */
193 /* If first dissection of Login Accept, save sequence number */
194 if (pkt_type
== 'A' && !PINFO_FD_VISITED(pinfo
)) {
195 ws_strtou32(tvb_get_string_enc(pinfo
->pool
, tvb
, 13, 20, ENC_ASCII
),
198 /* Create new conversation for this session */
199 conv
= conversation_new(pinfo
->num
,
202 conversation_pt_to_conversation_type(pinfo
->ptype
),
207 /* Store starting sequence number for session's packets */
208 conv_data
= wmem_new(wmem_file_scope(), struct conv_data
);
209 conv_data
->next_seq
= next_seq
;
210 conversation_add_proto_data(conv
, proto_soupbintcp
, conv_data
);
213 /* Handle sequence numbering for a Sequenced Data packet */
214 if (pkt_type
== 'S') {
215 if (!PINFO_FD_VISITED(pinfo
)) {
216 /* Get next expected sequence number from conversation */
217 conv
= find_conversation_pinfo(pinfo
, 0);
221 conv_data
= (struct conv_data
*)conversation_get_proto_data(conv
,
224 this_seq
= conv_data
->next_seq
++;
229 pdu_data
= wmem_new(wmem_file_scope(), struct pdu_data
);
230 pdu_data
->seq_num
= this_seq
;
231 p_add_proto_data(wmem_file_scope(), pinfo
, proto_soupbintcp
, key
, pdu_data
);
234 pdu_data
= (struct pdu_data
*)p_get_proto_data(wmem_file_scope(), pinfo
, proto_soupbintcp
, key
);
236 this_seq
= pdu_data
->seq_num
;
242 col_append_fstr(pinfo
->cinfo
, COL_INFO
, ", SeqNum = %u", this_seq
);
246 /* Create sub-tree for SoupBinTCP details */
247 ti
= proto_tree_add_item(tree
,
251 soupbintcp_tree
= proto_item_add_subtree(ti
, ett_soupbintcp
);
253 /* Append the packet name to the sub-tree item */
254 proto_item_append_text(ti
, ", %s", pkt_name
);
257 proto_tree_add_item(soupbintcp_tree
,
258 hf_soupbintcp_packet_length
,
259 tvb
, offset
, 2, ENC_BIG_ENDIAN
);
263 proto_tree_add_item(soupbintcp_tree
,
264 hf_soupbintcp_packet_type
,
265 tvb
, offset
, 1, ENC_ASCII
|ENC_NA
);
269 case '+': /* Debug Message */
270 proto_tree_add_item(soupbintcp_tree
,
272 tvb
, offset
, expected_len
- 1, ENC_ASCII
);
275 case 'A': /* Login Accept */
276 proto_tree_add_item(soupbintcp_tree
,
277 hf_soupbintcp_session
,
278 tvb
, offset
, 10, ENC_ASCII
);
281 seq_num_valid
= ws_strtoi32(tvb_get_string_enc(pinfo
->pool
,
282 tvb
, offset
, 20, ENC_ASCII
), NULL
, &seq_num
);
283 pi
= proto_tree_add_string_format_value(soupbintcp_tree
,
284 hf_soupbintcp_next_seq_num
,
288 expert_add_info(pinfo
, pi
, &ei_soupbintcp_next_seq_num_invalid
);
292 case 'J': /* Login Reject */
293 proto_tree_add_item(soupbintcp_tree
,
294 hf_soupbintcp_reject_code
,
295 tvb
, offset
, 1, ENC_ASCII
|ENC_NA
);
298 case 'U': /* Unsequenced Data */
299 /* Display handled by sub-dissector */
302 case 'S': /* Sequenced Data */
303 proto_item_append_text(ti
, ", SeqNum=%u", this_seq
);
304 proto_tree_add_string_format_value(soupbintcp_tree
,
305 hf_soupbintcp_seq_num
,
311 /* Display handled by sub-dissector */
314 case 'L': /* Login Request */
315 proto_tree_add_item(soupbintcp_tree
,
316 hf_soupbintcp_username
,
317 tvb
, offset
, 6, ENC_ASCII
);
320 proto_tree_add_item(soupbintcp_tree
,
321 hf_soupbintcp_password
,
322 tvb
, offset
, 10, ENC_ASCII
);
325 proto_tree_add_item(soupbintcp_tree
,
326 hf_soupbintcp_session
,
327 tvb
, offset
, 10, ENC_ASCII
);
330 seq_num_valid
= ws_strtoi32(tvb_get_string_enc(pinfo
->pool
,
331 tvb
, offset
, 20, ENC_ASCII
), NULL
, &seq_num
);
332 pi
= proto_tree_add_string_format_value(soupbintcp_tree
,
333 hf_soupbintcp_req_seq_num
,
337 expert_add_info(pinfo
, pi
, &ei_soupbintcp_req_seq_num_invalid
);
341 case 'H': /* Server Heartbeat */
344 case 'O': /* Logout Request */
347 case 'R': /* Client Heartbeat */
350 case 'Z': /* End of Session */
355 proto_tree_add_item(tree
,
356 hf_soupbintcp_message
,
357 tvb
, offset
, -1, ENC_NA
);
362 /* Call sub-dissector for encapsulated data */
363 if (pkt_type
== 'S' || pkt_type
== 'U') {
366 /* Sub-dissector tvb starts at 3 (length (2) + pkt_type (1)) */
367 sub_tvb
= tvb_new_subset_remaining(tvb
, 3);
369 /* Otherwise, try heuristic dissectors */
370 if (dissector_try_heuristic(heur_subdissector_list
,
379 /* Otherwise, give up, and just print the bytes in hex */
381 proto_tree_add_item(soupbintcp_tree
,
382 hf_soupbintcp_message
,
390 /** Return the size of the PDU in @p tvb, starting at @p offset */
392 get_soupbintcp_pdu_len(
393 packet_info
*pinfo _U_
,
398 /* Determine the length of the PDU using the SOUP header's 16-bit
399 big-endian length (at offset zero). We're guaranteed to get at
400 least two bytes here because we told tcp_dissect_pdus() that we
401 needed them. Add 2 to the retrieved value, because the SOUP
402 length doesn't include the length field itself. */
403 return (unsigned)tvb_get_ntohs(tvb
, offset
) + 2;
407 /** Dissect a possibly-reassembled TCP PDU */
409 dissect_soupbintcp_tcp_pdu(
415 dissect_soupbintcp_common(tvb
, pinfo
, tree
);
416 return tvb_captured_length(tvb
);
420 /** Dissect a TCP segment containing SoupBinTCP data */
422 dissect_soupbintcp_tcp(
428 tcp_dissect_pdus(tvb
, pinfo
, tree
,
429 soupbintcp_desegment
, 2,
430 get_soupbintcp_pdu_len
,
431 dissect_soupbintcp_tcp_pdu
, data
);
432 return tvb_captured_length(tvb
);
436 proto_register_soupbintcp(void)
438 expert_module_t
* expert_soupbinttcp
;
440 static hf_register_info hf
[] = {
442 { &hf_soupbintcp_packet_length
,
443 { "Packet Length", "soupbintcp.packet_length",
444 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
445 "Packet length, in bytes, NOT including these two bytes.",
448 { &hf_soupbintcp_packet_type
,
449 { "Packet Type", "soupbintcp.packet_type",
450 FT_CHAR
, BASE_HEX
, VALS(pkt_type_val
), 0x0,
454 { &hf_soupbintcp_reject_code
,
455 { "Login Reject Code", "soupbintcp.reject_code",
456 FT_CHAR
, BASE_HEX
, VALS(reject_code_val
), 0x0,
457 "Login reject reason code",
460 { &hf_soupbintcp_message
,
461 { "Message", "soupbintcp.message",
462 FT_BYTES
, BASE_NONE
, NULL
, 0x0,
463 "Content of SoupBinTCP frame",
466 { &hf_soupbintcp_text
,
467 { "Debug Text", "soupbintcp.text",
468 FT_STRING
, BASE_NONE
, NULL
, 0x0,
469 "Free-form, human-readable text",
472 { &hf_soupbintcp_username
,
473 { "User Name", "soupbintcp.username",
474 FT_STRING
, BASE_NONE
, NULL
, 0x0,
478 { &hf_soupbintcp_password
,
479 { "Password", "soupbintcp.password",
480 FT_STRING
, BASE_NONE
, NULL
, 0x0,
481 "User's login password",
484 { &hf_soupbintcp_session
,
485 { "Session", "soupbintcp.session",
486 FT_STRING
, BASE_NONE
, NULL
, 0x0,
487 "Session identifier, or send all spaces to log into the currently "
491 { &hf_soupbintcp_seq_num
,
492 { "Sequence number", "soupbintcp.seq_num",
493 FT_STRING
, BASE_NONE
, NULL
, 0x0,
494 "Calculated sequence number for this message",
497 { &hf_soupbintcp_next_seq_num
,
498 { "Next sequence number", "soupbintcp.next_seq_num",
499 FT_STRING
, BASE_NONE
, NULL
, 0x0,
500 "Sequence number of next Sequenced Data message to be delivered",
503 { &hf_soupbintcp_req_seq_num
,
504 { "Requested sequence number", "soupbintcp.req_seq_num",
505 FT_STRING
, BASE_NONE
, NULL
, 0x0,
506 "Request to begin (re)transmission of Sequenced Data at this "
507 "sequence number, or, if zero, to begin transmission with the "
508 "next message generated",
512 static int *ett
[] = {
516 static ei_register_info ei
[] = {
517 { &ei_soupbintcp_req_seq_num_invalid
, { "soupbintcp.req_seq_num.invalid", PI_MALFORMED
, PI_ERROR
,
518 "Sequence number of next Sequenced Data message to be delivered is an invalid string", EXPFILL
}},
519 { &ei_soupbintcp_next_seq_num_invalid
, { "soupbintcp.next_seq_num.invalid", PI_MALFORMED
, PI_ERROR
,
520 "Request to begin (re)transmission is an invalid string", EXPFILL
}}
523 module_t
*soupbintcp_module
;
525 proto_soupbintcp
= proto_register_protocol("SoupBinTCP", "SoupBinTCP", "soupbintcp");
526 soupbintcp_handle
= register_dissector("soupbintcp", dissect_soupbintcp_tcp
, proto_soupbintcp
);
528 proto_register_field_array(proto_soupbintcp
, hf
, array_length(hf
));
529 proto_register_subtree_array(ett
, array_length(ett
));
531 soupbintcp_module
= prefs_register_protocol(proto_soupbintcp
, NULL
);
533 prefs_register_bool_preference(
536 "Reassemble SoupBinTCP messages spanning multiple TCP segments",
537 "Whether the SoupBinTCP dissector should reassemble messages "
538 "spanning multiple TCP segments.",
539 &soupbintcp_desegment
);
541 heur_subdissector_list
= register_heur_dissector_list_with_description("soupbintcp", "SoupBinTCP encapsulated data", proto_soupbintcp
);
543 expert_soupbinttcp
= expert_register_protocol(proto_soupbintcp
);
544 expert_register_field_array(expert_soupbinttcp
, ei
, array_length(ei
));
549 proto_reg_handoff_soupbintcp(void)
551 dissector_add_uint_range_with_preference("tcp.port", "", soupbintcp_handle
);
556 * Editor modelines - https://www.wireshark.org/tools/modelines.html
561 * indent-tabs-mode: nil
564 * vi: set shiftwidth=4 tabstop=8 expandtab:
565 * :indentSize=4:tabSize=8:noTabs=true: