ITCHCPP 1.6.1
High-Performance NASDAQ TotalView-ITCH 5.0 Parser & Market-Data Platform
Loading...
Searching...
No Matches
soupbintcp.cpp
Go to the documentation of this file.
2
3#include <array>
4#include <bit>
5#include <charconv>
6#include <cstring>
7#include <stdexcept>
8#include <utility>
9
10namespace itch::transport {
11
12namespace {
13
14constexpr std::size_t LENGTH_PREFIX_SIZE = 2;
15constexpr std::size_t LOGIN_SESSION_SIZE = 10;
16constexpr std::size_t LOGIN_SEQUENCE_SIZE = 20;
17constexpr std::size_t MAX_WIRE_MESSAGE_LEN = 0xFFFF;
18
19// Trims trailing spaces from a fixed-width ASCII field.
20auto trim_field(std::span<const std::byte> field) -> std::string {
21 std::size_t length = field.size();
22 while (length > 0 && static_cast<char>(field[length - 1]) == ' ') {
23 --length;
24 }
25 std::string result(length, '\0');
26 for (std::size_t index = 0; index < length; ++index) {
27 result[index] = static_cast<char>(field[index]);
28 }
29 return result;
30}
31
32// Parses the right-justified numeric sequence number field of Login Accepted.
33auto parse_sequence_field(std::span<const std::byte> field) -> std::uint64_t {
34 const std::string text = trim_field(field);
35 std::uint64_t value = 0;
36 const char* begin = text.data();
37 // Skip any leading spaces left after trimming only trailing ones.
38 while (begin != text.data() + text.size() && *begin == ' ') {
39 ++begin;
40 }
41 std::from_chars(begin, text.data() + text.size(), value);
42 return value;
43}
44
45} // namespace
46
47SoupBinDecoder::SoupBinDecoder(MessageCallback callback) : m_callback {std::move(callback)} {}
48
50 m_event_callback = std::move(callback);
51}
52
53auto SoupBinDecoder::feed(std::span<const std::byte> bytes) -> void {
54 m_buffer.insert(m_buffer.end(), bytes.begin(), bytes.end());
55
56 std::size_t offset = 0;
57 while (m_buffer.size() - offset >= LENGTH_PREFIX_SIZE) {
58 std::uint16_t packet_length {};
59 std::memcpy(&packet_length, m_buffer.data() + offset, LENGTH_PREFIX_SIZE);
60 packet_length = utils::from_big_endian(packet_length);
61
62 if (packet_length == 0) {
63 offset += LENGTH_PREFIX_SIZE; // Defensive: skip a zero-length frame.
64 continue;
65 }
66 if (m_buffer.size() - offset < LENGTH_PREFIX_SIZE + packet_length) {
67 break; // The rest of this packet has not arrived yet.
68 }
69
70 const auto type =
71 static_cast<SoupBinPacketType>(static_cast<char>(m_buffer[offset + LENGTH_PREFIX_SIZE])
72 );
73 const std::span<const std::byte> payload {
74 m_buffer.data() + offset + LENGTH_PREFIX_SIZE + 1,
75 static_cast<std::size_t>(packet_length) - 1
76 };
77 process_packet(type, payload);
78 offset += LENGTH_PREFIX_SIZE + packet_length;
79 }
80
81 // Drop the bytes we have fully consumed, keeping any partial trailing packet.
82 if (offset > 0) {
83 m_buffer.erase(m_buffer.begin(), m_buffer.begin() + static_cast<std::ptrdiff_t>(offset));
84 }
85}
86
87auto SoupBinDecoder::process_packet(SoupBinPacketType type, std::span<const std::byte> payload)
88 -> void {
89 switch (type) {
91 if (payload.size() >= LOGIN_SESSION_SIZE + LOGIN_SEQUENCE_SIZE) {
92 m_session = trim_field(payload.subspan(0, LOGIN_SESSION_SIZE));
93 m_next_sequence =
94 parse_sequence_field(payload.subspan(LOGIN_SESSION_SIZE, LOGIN_SEQUENCE_SIZE));
95 if (m_next_sequence == 0) {
96 m_next_sequence = 1;
97 }
98 }
99 break;
100 }
102 m_tracker.observe(m_session, m_next_sequence, 1);
103 ++m_next_sequence;
104 decode_application_message(payload);
105 return;
106 }
108 decode_application_message(payload);
109 return;
110 }
118 break;
119 }
120
121 if (m_event_callback) {
122 m_event_callback(type, payload);
123 }
124}
125
126auto SoupBinDecoder::decode_application_message(std::span<const std::byte> payload) -> void {
127 if (payload.empty() || payload.size() > MAX_WIRE_MESSAGE_LEN) {
128 return;
129 }
130
131 // A data packet carries one ITCH message without its own 2-byte length
132 // prefix; re-prefixing it lets the ordinary framing parser decode it. The
133 // length is written big-endian (network order) to match the wire framing.
134 std::vector<std::byte> framed;
135 framed.reserve(LENGTH_PREFIX_SIZE + payload.size());
136 const auto length = static_cast<std::uint16_t>(payload.size());
137 const auto big_endian = utils::from_big_endian(length);
138 const auto len_bytes = std::bit_cast<std::array<std::byte, LENGTH_PREFIX_SIZE>>(big_endian);
139 framed.push_back(len_bytes[0]);
140 framed.push_back(len_bytes[1]);
141 framed.insert(framed.end(), payload.begin(), payload.end());
142
143 auto counting_callback = [this](const Message& message) {
144 ++m_messages_decoded;
145 if (m_callback) {
146 m_callback(message);
147 }
148 };
149 // A malformed single-message payload is skipped rather than aborting the
150 // whole stream. try_parse would avoid the exception, but it needs C++23's
151 // std::expected and this decoder must also build under C++20.
152 try {
153 m_parser.parse(std::span<const std::byte> {framed}, counting_callback);
154 // NOLINTNEXTLINE(bugprone-empty-catch)
155 } catch (const std::runtime_error&) {
156 }
157}
158
159} // namespace itch::transport
auto set_event_callback(EventCallback callback) -> void
Installs the control-packet event callback (empty clears it).
SoupBinDecoder(MessageCallback callback)
Constructs a decoder that calls callback for each ITCH message.
std::function< void(SoupBinPacketType type, std::span< const std::byte > payload)> EventCallback
Invoked for each non-data control packet, with its raw payload.
auto feed(std::span< const std::byte > bytes) -> void
Feeds a chunk of the TCP byte stream, processing any complete packets it completes.
SoupBinPacketType
The SoupBinTCP packet types (the one-byte type that follows the 2-byte length prefix).
@ login_request
Client login request.
@ login_rejected
Reject reason code (1).
@ login_accepted
Session id (10) + starting sequence number (20, ASCII).
@ end_of_session
The server has finished the session.
@ server_heartbeat
Keep-alive from the server.
@ logout_request
Client logout request.
@ unsequenced_data
One unsequenced application message.
@ debug
Free-form debug text (either direction).
@ sequenced_data
One sequenced application (ITCH) message.
@ client_heartbeat
Keep-alive from the client.
std::variant< SystemEventMessage, StockDirectoryMessage, StockTradingActionMessage, RegSHOMessage, MarketParticipantPositionMessage, MWCBDeclineLevelMessage, MWCBStatusMessage, IPOQuotingPeriodUpdateMessage, LULDAuctionCollarMessage, OperationalHaltMessage, AddOrderMessage, AddOrderMPIDAttributionMessage, OrderExecutedMessage, OrderExecutedWithPriceMessage, OrderCancelMessage, OrderDeleteMessage, OrderReplaceMessage, NonCrossTradeMessage, CrossTradeMessage, BrokenTradeMessage, NOIIMessage, RetailPriceImprovementIndicatorMessage, DLCRMessage > Message
A variant able to hold any one of the ITCH 5.0 message structs.
Definition messages.hpp:483
std::function< void(const Message &)> MessageCallback
The signature for the callback function used in streaming parse methods.
Definition parser.hpp:40
Stateful decoder for the SoupBinTCP session-layer protocol.