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;
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]) ==
' ') {
25 std::string result(length,
'\0');
26 for (std::size_t index = 0; index < length; ++index) {
27 result[index] =
static_cast<char>(field[index]);
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();
38 while (begin != text.data() + text.size() && *begin ==
' ') {
41 std::from_chars(begin, text.data() + text.size(), value);
50 m_event_callback = std::move(callback);
54 m_buffer.insert(m_buffer.end(), bytes.begin(), bytes.end());
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);
62 if (packet_length == 0) {
63 offset += LENGTH_PREFIX_SIZE;
66 if (m_buffer.size() - offset < LENGTH_PREFIX_SIZE + packet_length) {
71 static_cast<SoupBinPacketType>(
static_cast<char>(m_buffer[offset + LENGTH_PREFIX_SIZE])
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
77 process_packet(type, payload);
78 offset += LENGTH_PREFIX_SIZE + packet_length;
83 m_buffer.erase(m_buffer.begin(), m_buffer.begin() +
static_cast<std::ptrdiff_t
>(offset));
87auto SoupBinDecoder::process_packet(
SoupBinPacketType type, std::span<const std::byte> payload)
91 if (payload.size() >= LOGIN_SESSION_SIZE + LOGIN_SEQUENCE_SIZE) {
92 m_session = trim_field(payload.subspan(0, LOGIN_SESSION_SIZE));
94 parse_sequence_field(payload.subspan(LOGIN_SESSION_SIZE, LOGIN_SEQUENCE_SIZE));
95 if (m_next_sequence == 0) {
102 m_tracker.observe(m_session, m_next_sequence, 1);
104 decode_application_message(payload);
108 decode_application_message(payload);
121 if (m_event_callback) {
122 m_event_callback(type, payload);
126auto SoupBinDecoder::decode_application_message(std::span<const std::byte> payload) ->
void {
127 if (payload.empty() || payload.size() > MAX_WIRE_MESSAGE_LEN) {
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());
143 auto counting_callback = [
this](
const Message& message) {
144 ++m_messages_decoded;
153 m_parser.parse(std::span<const std::byte> {framed}, counting_callback);
155 }
catch (
const std::runtime_error&) {
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.
std::function< void(const Message &)> MessageCallback
The signature for the callback function used in streaming parse methods.
Stateful decoder for the SoupBinTCP session-layer protocol.