File size: 4,086 Bytes
f86fb3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#pragma once

#include <cstdint>
#include <span>
#include <string_view>
#include <cstring>
#include <optional>
#include <tuple>
#include "../core/types.hpp"

namespace quant::protocol {

constexpr uint16_t PROTOCOL_MAGIC = 0x5154; // 'Q' 'T' (Quant Trading)

enum class MessageType : uint8_t {
    MARKET_DATA_TICK = 0x01,
    NEW_ORDER_SINGLE = 0x02,
    ORDER_CANCEL_REQ = 0x03,
    EXECUTION_REPORT = 0x04,
    HEARTBEAT        = 0x05,
    GAP_RECOVERY_REQ = 0x06,
    GAP_RECOVERY_RES = 0x07
};

#pragma pack(push, 1)

// Standard 19-byte protocol framing header
struct FrameHeader {
    uint16_t magic{PROTOCOL_MAGIC};
    uint8_t msg_type{0};
    uint16_t body_len{0};
    uint64_t seq_num{0};
    uint64_t timestamp_ns{0};
};

struct MarketDataTickPayload {
    core::Symbol symbol;
    int64_t bid_price_raw{0};
    int64_t ask_price_raw{0};
    uint32_t bid_qty{0};
    uint32_t ask_qty{0};
    int64_t last_price_raw{0};
    uint32_t last_qty{0};
};

struct NewOrderSinglePayload {
    core::ClientId client_id{0};
    core::ClientOrderId client_order_id{0};
    core::Symbol symbol;
    uint8_t side{0};         // core::Side
    uint8_t order_type{0};    // core::OrderType
    int64_t price_raw{0};
    uint32_t qty{0};
};

struct OrderCancelReqPayload {
    core::ClientId client_id{0};
    core::ClientOrderId client_order_id{0};
    core::ClientOrderId orig_client_order_id{0};
    core::Symbol symbol;
};

struct ExecutionReportPayload {
    core::ClientId client_id{0};
    core::ClientOrderId client_order_id{0};
    core::OrderId engine_order_id{0};
    uint64_t exec_id{0};
    uint8_t exec_type{0};     // core::ExecType
    int64_t fill_price_raw{0};
    uint32_t fill_qty{0};
    uint32_t leaves_qty{0};
    uint8_t reject_reason{0}; // core::RejectReason
};

struct HeartbeatPayload {
    uint64_t ping_timestamp_ns{0};
};

struct GapRecoveryReqPayload {
    core::ClientId client_id{0};
    uint64_t from_seq{0};
    uint64_t to_seq{0};
};

#pragma pack(pop)

// Maximum single message frame size (for network buffers)
constexpr size_t MAX_FRAME_SIZE = 128;

class BinaryProtocol {
public:
    /**
     * Parse header from raw buffer using C++20 std::span.
     * Returns std::nullopt if buffer is smaller than FrameHeader or magic mismatch.
     */
    static std::optional<FrameHeader> parse_header(std::span<const uint8_t> buffer) noexcept;

    /**
     * Serialize a complete message (Header + Payload) into destination buffer.
     * Returns total bytes written, or 0 if buffer too small.
     */
    template <typename PayloadType>
    static size_t serialize_message(
        std::span<uint8_t> dest,
        MessageType msg_type,
        uint64_t seq_num,
        uint64_t timestamp_ns,
        const PayloadType& payload
    ) noexcept {
        constexpr size_t total_size = sizeof(FrameHeader) + sizeof(PayloadType);
        if (dest.size() < total_size) return 0;

        FrameHeader hdr;
        hdr.magic = PROTOCOL_MAGIC;
        hdr.msg_type = static_cast<uint8_t>(msg_type);
        hdr.body_len = static_cast<uint16_t>(sizeof(PayloadType));
        hdr.seq_num = seq_num;
        hdr.timestamp_ns = timestamp_ns;

        std::memcpy(dest.data(), &hdr, sizeof(FrameHeader));
        std::memcpy(dest.data() + sizeof(FrameHeader), &payload, sizeof(PayloadType));
        return total_size;
    }

    /**
     * Zero-copy cast payload from span buffer.
     * Returns pointer to payload inside the span, or nullptr if length check fails.
     */
    template <typename PayloadType>
    static const PayloadType* parse_payload(std::span<const uint8_t> buffer) noexcept {
        constexpr size_t total_size = sizeof(FrameHeader) + sizeof(PayloadType);
        if (buffer.size() < total_size) return nullptr;

        const auto* hdr = reinterpret_cast<const FrameHeader*>(buffer.data());
        if (hdr->magic != PROTOCOL_MAGIC || hdr->body_len != sizeof(PayloadType)) {
            return nullptr;
        }

        return reinterpret_cast<const PayloadType*>(buffer.data() + sizeof(FrameHeader));
    }
};

} // namespace quant::protocol