/* packet-ouch.c * Routines for OUCH 4.x protocol dissection * Copyright (C) 2013, 2015, 2016 David Arnold * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ /* OUCH is a stock exchange order entry protocol published and used by * NASDAQ. This dissector supports versions 4.x, which differ from * earlier versions by adopting binary encoding for numeric values. * * OUCH is usually encapsulated within NASDAQ's SoupBinTCP protocol, * running over a TCP connection from the trading application to the * exchange. SOUP provides framing, heartbeats and authentication; * consequently none of these is present in OUCH. * * Other exchanges have created order entry protocols very similar to * OUCH, but typically they differ in subtle ways (and continue to * diverge as time progresses) so I have not attempted to dissect * anything other than proper NASDAQ OUCH in this code. * * Specifications are available from NASDAQ's website, although the * links to find them tend to move around over time. At the time of * writing, the correct URL is: * * http://www.nasdaqtrader.com/content/technicalsupport/specifications/TradingProducts/OUCH4.2.pdf */ #include "config.h" #include void proto_register_ouch(void); void proto_reg_handoff_ouch(void); static const value_string pkt_type_val[] = { { 'O', "Enter Order" }, { 'U', "Replace Order" }, { 'X', "Cancel Order" }, { 'M', "Modify Order" }, { 'S', "System Event" }, { 'A', "Accepted" }, { 'R', "Replaced" }, /* 'U' on the wire, but use 'R' to disambiguate */ { 'C', "Canceled" }, { 'D', "AIQ Canceled" }, { 'E', "Executed" }, { 'F', "Trade Correction" }, { 'G', "Executed with Reference Price" }, { 'B', "Broken Trade" }, { 'K', "Price Correction" }, { 'J', "Rejected" }, { 'P', "Cancel Pending" }, { 'I', "Cancel Reject" }, { 'T', "Order Priority Update" }, { 'm', "Order Modified" }, /* 'M' on the wire; 'm' to disambiguate */ { 0, NULL } }; static const value_string ouch_bbo_weight_indicator_val[] = { { '0', "0 - 0.2%" }, { '1', "0.2 - 1%" }, { '2', "1 - 2%" }, { '3', "Greater than 2%" }, { ' ', "Unspecified" }, { 'S', "Sets the QBBO while joining the NBBO" }, { 'N', "Improves the NBBO upon entry" }, { 0, NULL } }; static const value_string ouch_broken_trade_reason_val[] = { { 'E', "Erroneous" }, { 'C', "Consent" }, { 'S', "Supervisory" }, { 'X', "External" }, { 0, NULL } }; static const value_string ouch_buy_sell_indicator_val[] = { { 'B', "Buy Order" }, { 'S', "Sell Order" }, { 'T', "Sell Short" }, { 'E', "Sell Short Exempt" }, { 0, NULL } }; static const value_string ouch_cancel_reason_val[] = { { 'C', "Cross cancel" }, { 'D', "Regulatory restriction" }, { 'E', "Closed" }, { 'H', "Halted" }, { 'I', "Immediate or Cancel order" }, { 'K', "Market Collars" }, { 'Q', "Self-match prevention" }, { 'S', "Supervisory" }, { 'T', "Timeout" }, { 'U', "User requested cancel" }, { 'X', "Open Protection" }, { 'Z', "System cancel" }, { 0, NULL } }; static const value_string ouch_capacity_val[] = { { 'A', "Agency" }, { 'O', "Other" }, { 'P', "Principal" }, { 'R', "Riskless" }, { 0, NULL } }; static const value_string ouch_cross_type_val[] = { { 'N', "No Cross" }, { 'O', "Opening Cross" }, { 'C', "Closing Cross" }, { 'I', "Intra-day Cross" }, /* Seems to have been removed */ { 'H', "Halt/IPO Cross" }, { 'R', "Retail" }, /* Not in 4.0 */ { 'S', "Supplemental Order" }, { 0, NULL } }; /* Not in 4.0 */ static const value_string ouch_customer_type_val[] = { { 'R', "Retail designated order" }, { 'N', "Not a retail designated order" }, { ' ', "Default configured for port" }, { 0, NULL } }; static const value_string ouch_display_val[] = { { 'A', "Attributable-Price to Display" }, { 'I', "Imbalance-Only" }, { 'L', "Post-Only and Attributable - Price to Display" }, { 'M', "Mid-Point Peg" }, { 'N', "Non-Display" }, { 'O', "Retail Order Type 1" }, /* Not in 4.0 */ { 'P', "Post-Only" }, { 'Q', "Retail Price Improvement Order" }, /* Not in 4.0 */ { 'R', "Round-Lot Only" }, /* Seems to have been removed? */ { 'T', "Retail Order Type 2" }, /* Not in 4.0 */ { 'W', "Mid-point Peg Post Only" }, { 'Y', "Anonymous-Price to Comply" }, { 'Z', "Entered as displayed bu changed to non-displayed " "(Priced to comply)" }, /* New in 4.2 */ { 0, NULL} }; static const value_string ouch_event_code_val[] = { { 'S', "Start of Day" }, { 'E', "End of Day" }, { 0, NULL} }; static const value_string ouch_iso_eligibility_val[] = { { 'Y', "Eligible" }, { 'N', "Not eligible" }, { 0, NULL } }; static const value_string ouch_liquidity_flag_val[] = { { '0', "Supplemental Order Execution" }, { '4', "Added displayed liquidity in a Group A Symbol" }, { '5', "Added non-displayed liquidity in a Group A Symbol" }, { '6', "Removed liquidity in a Group A Symbol" }, { '7', "Displayed, liquidity-adding order improves the NBBO" }, { '8', "Displayed, liquidity-adding order sets the QBBO while joining the NBBO" }, { 'A', "Added" }, { 'C', "Closing Cross" }, { 'H', "Halt/IPO Cross" }, { 'I', "Intraday/Post-Market Cross" }, /* Seems to have been removed */ { 'J', "Non-displayed adding liquidity" }, { 'K', "Halt Cross" }, { 'L', "Closing Cross (imbalance-only)" }, { 'M', "Opening Cross (imbalance-only)" }, { 'N', "Halt Cross, orders entered in pilot symbols during the LULD Trading Pause" }, { 'O', "Opening Cross" }, { 'R', "Removed" }, { 'W', "Added post-only" }, /* Removed 4.2 2013/02/05 */ { 'a', "Added displayed liquidity in a SCIP Symbol" }, { 'b', "Displayed, liquidity-adding order improves the NBBO in pilot symbol during specified LULD Pricing Pilot timeframe" }, { 'c', "Added displayed liquidity in a pilot symbol during specified LULD Pricing Pilot timeframe" }, { 'd', "Retail designated execution that removed liquidity" }, { 'e', "Retail designated execution that added displayed liquidity" }, { 'f', "Retail designated execution that added non-displayed liquidity" }, { 'g', "Added non-displayed mid-point liquidity in a Group A Symbol" }, { 'h', "Removed liquidity in a pilot symbol during specified LULD Pricing Pilot timeframe" }, { 'j', "RPI (Retail Price Improving) order provides liquidity" }, { 'k', "Added liquidity via a midpoint order" }, { 'm', "Removed liquidity at a midpoint" }, { 'r', "Retail Order removes RPI liquidity" }, { 't', "Retail Order removes price improving non-displayed liquidity other than RPI liquidity" }, { 'x', "Displayed, liquidity-adding order improves the NBBO in a SCIP Symbol" }, { 'y', "Displayed, liquidity-adding order set the QBBO while joining the NBBO in a SCIP Symbol" }, { 0, NULL } }; static const value_string ouch_order_state_val[] = { { 'L', "Order Live" }, { 'D', "Order Dead" }, { 0, NULL } }; static const value_string ouch_price_correction_reason_val[] = { { 'E', "Erroneous" }, { 'C', "Consent" }, { 'S', "Supervisory" }, { 'X', "External" }, { 0, NULL } }; static const value_string ouch_reference_price_type_val[] = { { 'I', "Intraday Indicative Value" }, { 0, NULL } }; static const value_string ouch_reject_reason_val[] = { { 'T', "Test Mode" }, { 'H', "Halted" }, { 'Z', "Shares exceeds configured safety threshold" }, { 'S', "Invalid Stock" }, { 'D', "Invalid Display Type" }, { 'C', "NASDAQ is Closed" }, { 'L', "Requested firm not authorized for requested clearing " "type on this account" }, { 'M', "Outside of permitted times for requested clearing type" }, { 'R', "This order is not allowed in this type of cross" }, { 'X', "Invalid Price" }, { 'N', "Invalid Minimum Quantity" }, { 'O', "Other" }, { 'W', "Invalid Mid-point Post Only Price" }, { 'a', "Reject All enabled" }, { 'b', "Easy to Borrow (ETB) reject" }, { 'c', "Restricted symbol list reject" }, { 'd', "ISO order restriction" }, { 'e', "Odd lot order restriction" }, { 'f', "Mid-Point order restriction" }, { 'g', "Pre-market order restriction" }, { 'h', "Post-market order restriction" }, { 'i', "Short sale order restriction" }, { 'j', "On Open order restriction" }, { 'k', "On Close order restriction" }, { 'l', "Two sided quote reject" }, { 'm', "Exceeded shares limit" }, { 'n', "Exceeded dollar value limit" }, { 0, NULL} }; static const value_string ouch_trade_correction_reason_val[] = { { 'N', "Adjusted to NAV" }, { 0, NULL } }; /* Initialize the protocol and registered fields */ static int proto_ouch = -1; static dissector_handle_t ouch_handle; /* Initialize the subtree pointers */ static gint ett_ouch = -1; static int hf_ouch_bbo_weight_indicator = -1; static int hf_ouch_broken_trade_reason = -1; static int hf_ouch_buy_sell_indicator = -1; static int hf_ouch_cancel_reason = -1; static int hf_ouch_capacity = -1; static int hf_ouch_cross_type = -1; static int hf_ouch_customer_type = -1; static int hf_ouch_decrement_shares = -1; static int hf_ouch_display = -1; static int hf_ouch_event_code = -1; static int hf_ouch_executed_shares = -1; static int hf_ouch_execution_price = -1; static int hf_ouch_existing_order_token = -1; static int hf_ouch_firm = -1; static int hf_ouch_iso_eligible = -1; static int hf_ouch_liquidity_flag = -1; static int hf_ouch_match_number = -1; static int hf_ouch_message = -1; static int hf_ouch_min_quantity = -1; static int hf_ouch_new_execution_price = -1; static int hf_ouch_order_reference_number = -1; static int hf_ouch_order_state = -1; static int hf_ouch_order_token = -1; static int hf_ouch_packet_type = -1; static int hf_ouch_previous_order_token = -1; static int hf_ouch_price = -1; static int hf_ouch_price_correction_reason = -1; static int hf_ouch_quantity_prevented_from_trading = -1; static int hf_ouch_reference_price = -1; static int hf_ouch_reference_price_type = -1; static int hf_ouch_reject_reason = -1; static int hf_ouch_replacement_order_token = -1; static int hf_ouch_shares = -1; static int hf_ouch_stock = -1; static int hf_ouch_tif = -1; static int hf_ouch_timestamp = -1; static int hf_ouch_trade_correction_reason = -1; /** Format an OUCH timestamp into a useful string * * We use this function rather than a BASE_CUSTOM formatter because * BASE_CUSTOM doesn't support passing a 64-bit value to the * formatting function. */ static void ouch_tree_add_timestamp( proto_tree *tree, const int hf, tvbuff_t *tvb, gint offset) { guint64 ts = tvb_get_ntoh64(tvb, offset); char *buf = (char *)wmem_alloc(wmem_packet_scope(), ITEM_LABEL_LENGTH); guint32 tmp, hours, mins, secs, nsecs; nsecs = (guint32)(ts % G_GUINT64_CONSTANT(1000000000)); tmp = (guint32)(ts / G_GUINT64_CONSTANT(1000000000)); hours = tmp / 3600; mins = (tmp % 3600) / 60; secs = tmp % 60; g_snprintf(buf, ITEM_LABEL_LENGTH, "%u:%02u:%02u.%09u", hours, mins, secs, nsecs); proto_tree_add_string(tree, hf, tvb, offset, 8, buf); } /** BASE_CUSTOM formatter for prices * * OUCH prices are integers, with four implicit decimal places. So we * insert the decimal point, and add a leading dollar sign as well. */ static void format_price( char *buf, guint32 value) { if (value == 0x7fffffff) { g_snprintf(buf, ITEM_LABEL_LENGTH, "%s", "Market"); } else { g_snprintf(buf, ITEM_LABEL_LENGTH, "$%u.%04u", value / 10000, value % 10000); } } /** BASE_CUSTOM formatter for reference price type code * * Displays the code value as a character, not its ASCII value, as * would be done by BASE_DEC and friends. */ static void format_reference_price_type( char *buf, guint32 value) { g_snprintf(buf, ITEM_LABEL_LENGTH, "%s (%c)", val_to_str_const(value, ouch_reference_price_type_val, "Unknown"), value); } /** BASE_CUSTOM formatter for the Time In Force (TIF) code * * There are three reserved values for the TIF: 0, 99998 and 99999. * These are trapped and displayed as an appropriate string. All * other values are printed as a duration in hours, minutes and * seconds. */ static void format_tif( gchar *buf, guint32 value) { guint32 hours; guint32 mins; guint32 secs; switch (value) { case 0: g_snprintf(buf, ITEM_LABEL_LENGTH, "Immediate Or Cancel (%u)", value); break; case 99998: g_snprintf(buf, ITEM_LABEL_LENGTH, "Market Hours (%u)", value); break; case 99999: g_snprintf(buf, ITEM_LABEL_LENGTH, "System Hours (%u)", value); break; default: hours = value / 3600; mins = (value % 3600) / 60; secs = value % 60; g_snprintf(buf, ITEM_LABEL_LENGTH, "%uh %02um %02us (%u seconds)", hours, mins, secs, value); break; } } static int dissect_ouch( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { proto_item *ti; proto_tree *ouch_tree = NULL; const char *pkt_name; guint16 reported_len; guint8 pkt_type; int offset = 0; /* Get the OUCH message type value */ pkt_type = tvb_get_guint8(tvb, offset); reported_len = tvb_reported_length(tvb); /* OUCH has two messages with the same code: Replace Order and * Replaced. It's possible to tell which is which because clients * send the Replace Order, and NASDAQ sends Replaced replies. * Nonetheless, this complicates the switch, so instead we * distinguish between them by length, and use 'R' for Replaced * (like XPRS does). */ if (pkt_type == 'U' && (reported_len == 79 || reported_len == 80)) { pkt_type = 'R'; } /* OUCH has two messages with the same code: Modify Order and * Modified. Again, one is sent by clients, the other sent by * NASDAQ. We change Modified to 'm' for simplicity in the * switch. */ if (pkt_type == 'M' && reported_len == 28) { pkt_type = 'm'; } /* Since we use the packet name a few times, get and save that value */ pkt_name = val_to_str(pkt_type, pkt_type_val, "Unknown (%u)"); /* Set the protocol name in the summary display */ col_set_str(pinfo->cinfo, COL_PROTOCOL, "OUCH"); /* Set the packet name in the info column */ col_add_str(pinfo->cinfo, COL_INFO, pkt_name); if (tree) { /* Create a sub-tree for the OUCH packet details */ ti = proto_tree_add_item(tree, proto_ouch, tvb, 0, -1, ENC_NA); ouch_tree = proto_item_add_subtree(ti, ett_ouch); /* Append the packet name to the sub-tree item */ proto_item_append_text(ti, ", %s", pkt_name); /* Packet type (using the cooked value). */ proto_tree_add_item(ouch_tree, hf_ouch_packet_type, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; switch (pkt_type) { case 'O': /* Enter Order */ proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_buy_sell_indicator, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_stock, tvb, offset, 8, ENC_ASCII|ENC_NA); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_price, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_tif, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_firm, tvb, offset, 4, ENC_ASCII|ENC_NA); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_display, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_capacity, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_iso_eligible, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_min_quantity, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_cross_type, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; if (reported_len >= 49) { /* Added in 4.1 */ proto_tree_add_item(ouch_tree, hf_ouch_customer_type, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; } break; case 'A': /* Accepted */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_buy_sell_indicator, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_stock, tvb, offset, 8, ENC_ASCII|ENC_NA); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_price, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_tif, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_firm, tvb, offset, 4, ENC_ASCII|ENC_NA); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_display, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_order_reference_number, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_capacity, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_iso_eligible, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_min_quantity, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_cross_type, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_order_state, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; if (reported_len >= 66) { /* Added in 4.2 */ proto_tree_add_item(ouch_tree, hf_ouch_bbo_weight_indicator, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; } break; case 'U': /* Replace Order */ proto_tree_add_item(ouch_tree, hf_ouch_existing_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_replacement_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_price, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_tif, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_display, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_iso_eligible, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_min_quantity, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; break; case 'X': /* Cancel Order */ proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; break; case 'M': /* Modify Order (from 4.2 onwards) */ proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_buy_sell_indicator, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; break; case 'S': /* System Event */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_event_code, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; break; case 'R': /* Replaced */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_replacement_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_buy_sell_indicator, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_stock, tvb, offset, 8, ENC_ASCII|ENC_NA); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_price, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_tif, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_firm, tvb, offset, 4, ENC_ASCII|ENC_NA); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_display, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_order_reference_number, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_capacity, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_iso_eligible, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_min_quantity, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_cross_type, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_order_state, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_previous_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; if (reported_len >= 80) { /* Added in 4.2 */ proto_tree_add_item(ouch_tree, hf_ouch_bbo_weight_indicator, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; } break; case 'C': /* Canceled */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_decrement_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_cancel_reason, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; break; case 'D': /* AIQ Canceled */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_decrement_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_cancel_reason, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_quantity_prevented_from_trading, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_execution_price, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_liquidity_flag, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; break; case 'E': /* Executed */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_executed_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_execution_price, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_liquidity_flag, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_match_number, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; break; case 'B': /* Broken Trade */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_match_number, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_broken_trade_reason, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; break; case 'F': /* Trade Correction (4.2 onwards) */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_executed_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_execution_price, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_liquidity_flag, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_match_number, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_trade_correction_reason, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; break; case 'G': /* Executed with Reference Price (4.2 onwards) */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_executed_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_execution_price, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_liquidity_flag, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_match_number, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_reference_price, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_reference_price_type, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; break; case 'K': /* Price Correction */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_match_number, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_new_execution_price, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_price_correction_reason, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; break; case 'J': /* Rejected Order */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_reject_reason, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; break; case 'P': /* Cancel Pending */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; break; case 'I': /* Cancel Reject */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; break; case 'T': /* Order Priority Update (4.2 onwards) */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_price, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; proto_tree_add_item(ouch_tree, hf_ouch_display, tvb, offset, 1, ENC_ASCII|ENC_NA); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_order_reference_number, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; break; case 'm': /* Order Modified (4.2 onwards) */ ouch_tree_add_timestamp(ouch_tree, hf_ouch_timestamp, tvb, offset); offset += 8; proto_tree_add_item(ouch_tree, hf_ouch_order_token, tvb, offset, 14, ENC_ASCII|ENC_NA); offset += 14; proto_tree_add_item(ouch_tree, hf_ouch_buy_sell_indicator, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item(ouch_tree, hf_ouch_shares, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; break; default: /* Unknown */ proto_tree_add_item(tree, hf_ouch_message, tvb, offset, -1, ENC_NA); offset += reported_len - 1; break; } } return offset; } /** Returns a guess if a packet is OUCH or not * * Since SOUP doesn't have a sub-protocol type flag, we have to use a * heuristic decision to determine if the contained protocol is OUCH * or ITCH (or something else entirely). We look at the message type * code, and since we know that we're being called from SOUP, we can * check the passed-in length too: if the type code and the length * match, we guess at OUCH. */ static gboolean dissect_ouch_heur( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { guint8 msg_type = tvb_get_guint8(tvb, 0); guint msg_len = tvb_reported_length(tvb); switch (msg_type) { case 'O': /* Enter order (with or without optional customer type) */ if (msg_len != 48 && msg_len != 49) { return FALSE; } break; case 'U': /* Replace order or Replaced (4.0, 4.1) or Replaced (4.2) */ if (msg_len != 47 && msg_len != 79 && msg_len != 80) { return FALSE; } break; case 'X': /* Cancel order */ if (msg_len != 19) { return FALSE; } break; case 'M': /* Modify Order or Order Modified (added 4.2) */ if (msg_len != 20 && msg_len != 28) { return FALSE; } break; case 'S': /* System event */ if (msg_len != 10) { return FALSE; } break; case 'A': /* Accepted */ if (msg_len != 65 && msg_len != 66) { return FALSE; } break; case 'C': /* Canceled */ if (msg_len != 28) { return FALSE; } break; case 'D': /* AIQ Canceled */ if (msg_len != 37) { return FALSE; } break; case 'E': /* Executed */ if (msg_len != 40) { return FALSE; } break; case 'F': /* Trade Correction */ if (msg_len != 41) { return FALSE; } break; case 'G': /* Executed with Reference Price */ if (msg_len != 45) { return FALSE; } break; case 'B': /* Broken Trade */ if (msg_len != 32) { return FALSE; } break; case 'K': /* Correction */ if (msg_len != 36) { return FALSE; } break; case 'J': /* Rejected */ if (msg_len != 24) { return FALSE; } break; case 'P': /* Cancel Pending */ if (msg_len != 23) { return FALSE; } break; case 'I': /* Cancel Reject */ if (msg_len != 23) { return FALSE; } break; case 'T': /* Order Priority Update */ if (msg_len != 36) { return FALSE; } break; default: /* Not a known OUCH message code */ return FALSE; } /* Perform dissection of this (initial) packet */ dissect_ouch(tvb, pinfo, tree, NULL); return TRUE; } void proto_register_ouch(void) { /* Setup list of header fields See Section 1.6.1 for details*/ static hf_register_info hf[] = { { &hf_ouch_bbo_weight_indicator, { "BBO Weight Indicator", "ouch.bbo_weight_indicator", FT_CHAR, BASE_HEX, VALS(ouch_bbo_weight_indicator_val), 0x0, NULL, HFILL }}, { &hf_ouch_broken_trade_reason, { "Broken Trade Reason", "ouch.broken_trade_reason", FT_CHAR, BASE_HEX, VALS(ouch_broken_trade_reason_val), 0x0, NULL, HFILL }}, { &hf_ouch_buy_sell_indicator, { "Buy/Sell Indicator", "ouch.buy_sell_indicator", FT_CHAR, BASE_HEX, VALS(ouch_buy_sell_indicator_val), 0x0, NULL, HFILL }}, { &hf_ouch_cancel_reason, { "Cancel Reason", "ouch.cancel_reason", FT_CHAR, BASE_HEX, VALS(ouch_cancel_reason_val), 0x0, NULL, HFILL }}, { &hf_ouch_capacity, { "Capacity", "ouch.capacity", FT_CHAR, BASE_HEX, VALS(ouch_capacity_val), 0x0, NULL, HFILL }}, { &hf_ouch_cross_type, { "Cross Type", "ouch.cross_type", FT_CHAR, BASE_HEX, VALS(ouch_cross_type_val), 0x0, NULL, HFILL }}, { &hf_ouch_customer_type, { "Customer Type", "ouch.customer_type", FT_CHAR, BASE_HEX, VALS(ouch_customer_type_val), 0x0, NULL, HFILL }}, { &hf_ouch_decrement_shares, { "Decrement Shares", "ouch.decrement_shares", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_display, { "Display", "ouch.display", FT_CHAR, BASE_HEX, VALS(ouch_display_val), 0x0, NULL, HFILL }}, { &hf_ouch_event_code, { "Event Code", "ouch.event_code", FT_CHAR, BASE_HEX, VALS(ouch_event_code_val), 0x0, NULL, HFILL }}, { &hf_ouch_executed_shares, { "Executed Shares", "ouch.executed_shares", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_execution_price, { "Execution Price", "ouch.execution_price", FT_UINT32, BASE_CUSTOM, CF_FUNC(format_price), 0x0, NULL, HFILL }}, { &hf_ouch_existing_order_token, { "Existing Order Token", "ouch.existing_order_token", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_firm, { "Firm", "ouch.firm", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_iso_eligible, { "Intermarket Sweep Eligibility", "ouch.iso_eligible", FT_CHAR, BASE_HEX, VALS(ouch_iso_eligibility_val), 0x0, NULL, HFILL }}, { &hf_ouch_liquidity_flag, { "Liquidity Flag", "ouch.liquidity_flag", FT_CHAR, BASE_HEX, VALS(ouch_liquidity_flag_val), 0x0, NULL, HFILL }}, { &hf_ouch_match_number, { "Match Number", "ouch.match_number", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_message, { "Unknown Message", "ouch.unknown_message", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_min_quantity, { "Minimum Quantity", "ouch.min_quantity", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_new_execution_price, { "New Execution Price", "ouch.new_execution_price", FT_UINT32, BASE_CUSTOM, CF_FUNC(format_price), 0x0, NULL, HFILL }}, { &hf_ouch_order_reference_number, { "Order Reference Number", "ouch.order_reference_number", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_order_state, { "Order State", "ouch.order_state", FT_CHAR, BASE_HEX, VALS(ouch_order_state_val), 0x0, NULL, HFILL }}, { &hf_ouch_order_token, { "Order Token", "ouch.order_token", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_packet_type, { "Packet Type", "ouch.packet_type", FT_CHAR, BASE_HEX, VALS(pkt_type_val), 0x0, NULL, HFILL }}, { &hf_ouch_previous_order_token, { "Previous Order Token", "ouch.previous_order_token", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_price, { "Price", "ouch.price", FT_UINT32, BASE_CUSTOM, CF_FUNC(format_price), 0x0, NULL, HFILL }}, { &hf_ouch_price_correction_reason, { "Price Correction Reason", "ouch.price_correction_reason", FT_CHAR, BASE_HEX, VALS(ouch_price_correction_reason_val), 0x0, NULL, HFILL }}, { &hf_ouch_quantity_prevented_from_trading, { "Quantity Prevented from Trading", "ouch.quantity_prevented_from_trading", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_reference_price, { "Reference Price", "ouch.reference_price", FT_UINT32, BASE_CUSTOM, CF_FUNC(format_price), 0x0, NULL, HFILL }}, { &hf_ouch_reference_price_type, { "Reference Price Type", "ouch.reference_price_type", FT_UINT32, BASE_CUSTOM, CF_FUNC(format_reference_price_type), 0x0, NULL, HFILL }}, { &hf_ouch_reject_reason, { "Reject Reason", "ouch.reject_reason", FT_CHAR, BASE_HEX, VALS(ouch_reject_reason_val), 0x0, NULL, HFILL }}, { &hf_ouch_replacement_order_token, { "Replacement Order Token", "ouch.replacement_order_token", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_shares, { "Shares", "ouch.shares", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_stock, { "Stock", "ouch.stock", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_tif, { "Time In Force", "ouch.tif", FT_UINT32, BASE_CUSTOM, CF_FUNC(format_tif), 0x0, NULL, HFILL }}, { &hf_ouch_timestamp, { "Timestamp", "ouch.timestamp", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_ouch_trade_correction_reason, { "Trade Correction Reason", "ouch.trade_correction_reason", FT_CHAR, BASE_HEX, VALS(ouch_trade_correction_reason_val), 0x0, NULL, HFILL }} }; /* Setup protocol subtree array */ static gint *ett[] = { &ett_ouch }; /* Register the protocol name and description */ proto_ouch = proto_register_protocol("OUCH", "OUCH", "ouch"); /* Required function calls to register the header fields and * subtrees used */ proto_register_field_array(proto_ouch, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); } /* If this dissector uses sub-dissector registration add a * registration routine. This format is required because a script is * used to find these routines and create the code that calls these * routines. */ void proto_reg_handoff_ouch(void) { ouch_handle = create_dissector_handle(dissect_ouch, proto_ouch); heur_dissector_add("soupbintcp", dissect_ouch_heur, "OUCH over SoupBinTCP", "ouch_soupbintcp", proto_ouch, HEURISTIC_ENABLE); dissector_add_uint_range_with_preference("tcp.port", "", ouch_handle); } /* * Editor modelines - http://www.wireshark.org/tools/modelines.html * * Local variables: * c-basic-offset: 4 * tab-width: 8 * indent-tabs-mode: nil * End: * * vi: set shiftwidth=4 tabstop=8 expandtab: * :indentSize=4:tabSize=8:noTabs=true: */