wireshark/epan/dissectors/packet-ouch.c

1590 lines
52 KiB
C

/* packet-ouch.c
* Routines for OUCH 4.x protocol dissection
* Copyright (C) 2013, 2015, 2016 David Arnold <d@0x1.org>
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* 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 <epan/packet.h>
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 - https://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:
*/