Line data Source code
1 : /**
2 : * @file fix_codec.hpp
3 : * @brief FIX 消息编解码器
4 : *
5 : * 提供 FIX 消息的序列化和反序列化功能,
6 : * 自动处理 BodyLength 和 CheckSum 的计算与校验。
7 : */
8 :
9 : #pragma once
10 :
11 : #include <cstdint>
12 : #include <ctime>
13 : #include <iomanip>
14 : #include <numeric>
15 : #include <stdexcept>
16 : #include <string>
17 : #include <sstream>
18 : #include <unordered_map>
19 : #include <vector>
20 : #include <algorithm>
21 : #include <array>
22 :
23 : #include "fix/fix_tags.hpp"
24 :
25 : namespace fix40 {
26 :
27 : /// FIX 字段分隔符:ASCII SOH (0x01)
28 : constexpr char SOH = '\x01';
29 :
30 : /**
31 : * @class FixMessage
32 : * @brief FIX 消息的面向对象封装
33 : *
34 : * 封装 FIX 消息的字段,提供类型安全的访问接口。
35 : *
36 : * @par 使用示例
37 : * @code
38 : * FixMessage msg;
39 : * msg.set(tags::MsgType, "A");
40 : * msg.set(tags::HeartBtInt, 30);
41 : *
42 : * std::string type = msg.get_string(tags::MsgType);
43 : * int hb = msg.get_int(tags::HeartBtInt);
44 : * @endcode
45 : */
46 : class FixMessage {
47 : public:
48 : /**
49 : * @brief 设置字符串类型字段
50 : * @param tag 标签编号
51 : * @param value 字段值
52 : */
53 12453 : void set(int tag, const std::string& value) { fields_[tag] = value; }
54 :
55 : /**
56 : * @brief 设置整数类型字段
57 : * @param tag 标签编号
58 : * @param value 字段值(自动转换为字符串)
59 : */
60 1183 : void set(int tag, int value) { fields_[tag] = std::to_string(value); }
61 :
62 : /**
63 : * @brief 获取字符串类型字段值
64 : * @param tag 标签编号
65 : * @return std::string 字段值
66 : * @throws std::runtime_error 标签不存在时抛出异常
67 : */
68 4511 : std::string get_string(int tag) const {
69 4511 : auto it = fields_.find(tag);
70 4511 : if (it == fields_.end()) {
71 1 : throw std::runtime_error("Tag not found: " + std::to_string(tag));
72 : }
73 9020 : return it->second;
74 : }
75 :
76 : /**
77 : * @brief 获取整数类型字段值
78 : * @param tag 标签编号
79 : * @return int 字段值
80 : * @throws std::runtime_error 标签不存在或转换失败时抛出异常
81 : */
82 342 : int get_int(int tag) const {
83 342 : return std::stoi(get_string(tag));
84 : }
85 :
86 : /**
87 : * @brief 检查标签是否存在
88 : * @param tag 标签编号
89 : * @return true 标签存在
90 : * @return false 标签不存在
91 : */
92 1738 : bool has(int tag) const {
93 1738 : return fields_.count(tag) > 0;
94 : }
95 :
96 : /**
97 : * @brief 获取所有字段的只读引用
98 : * @return const std::unordered_map<int, std::string>& 字段映射
99 : */
100 278 : const std::unordered_map<int, std::string>& get_fields() const {
101 278 : return fields_;
102 : }
103 :
104 : private:
105 : friend class FixCodec;
106 : std::unordered_map<int, std::string> fields_; ///< 字段存储:tag -> value
107 : };
108 :
109 :
110 : /**
111 : * @class FixCodec
112 : * @brief FIX 消息编解码器
113 : *
114 : * 负责 FIX 消息的序列化(编码)和反序列化(解码)。
115 : *
116 : * @par 编码流程
117 : * 1. 自动添加 SendingTime (52)
118 : * 2. 按标准顺序构造消息头
119 : * 3. 计算并设置 BodyLength (9)
120 : * 4. 计算并附加 CheckSum (10)
121 : *
122 : * @par 解码流程
123 : * 1. 校验 CheckSum
124 : * 2. 解析所有字段
125 : * 3. 校验 BodyLength
126 : *
127 : * @par FIX 消息格式
128 : * @code
129 : * 8=FIX.4.0|9=BodyLength|35=MsgType|49=Sender|56=Target|34=SeqNum|52=Time|...|10=Checksum|
130 : * @endcode
131 : * 其中 | 代表 SOH 分隔符
132 : */
133 : class FixCodec {
134 : public:
135 : /**
136 : * @brief 将 FixMessage 编码为 FIX 协议字符串
137 : * @param msg 要编码的消息对象(会被修改以添加时间戳等字段)
138 : * @return std::string 编码后的 FIX 消息字符串
139 : *
140 : * @note 自动计算并设置 BodyLength 和 CheckSum
141 : */
142 277 : std::string encode(FixMessage& msg) const {
143 : // 1. 准备时间戳
144 : char ts[32];
145 277 : generate_utc_timestamp(ts, sizeof(ts));
146 277 : msg.set(tags::SendingTime, ts);
147 :
148 : // 2. 构造标准 Header(除 8= 和 9= 之外)
149 : static constexpr std::array<int, 5> kStdHeaderOrder = {
150 : tags::MsgType, // 35
151 : tags::SenderCompID, // 49
152 : tags::TargetCompID, // 56
153 : tags::MsgSeqNum, // 34
154 : tags::SendingTime // 52
155 : };
156 :
157 277 : std::ostringstream header_rest_ss;
158 1662 : for (int tag : kStdHeaderOrder) {
159 1385 : if (msg.has(tag)) {
160 1377 : header_rest_ss << tag << "=" << msg.get_string(tag) << SOH;
161 : }
162 : }
163 :
164 : // 3. 构造报文体 (Body) —— 仅业务字段
165 277 : std::string body_str = build_body_from_message(msg);
166 :
167 277 : std::string header_rest = header_rest_ss.str();
168 :
169 : // 4. 计算 BodyLength (从 35= 起始到 CheckSum 前一个 SOH)
170 277 : std::size_t body_length_val = header_rest.size() + body_str.size();
171 277 : msg.set(tags::BodyLength, static_cast<int>(body_length_val));
172 :
173 : // 5. 构造最终前缀(8= & 9= + HeaderRest + Body)
174 277 : std::ostringstream prefix_ss;
175 277 : prefix_ss << tags::BeginString << "=" << "FIX.4.0" << SOH
176 277 : << tags::BodyLength << "=" << msg.get_string(tags::BodyLength) << SOH
177 : << header_rest
178 277 : << body_str; // 如果 body_str 非空,则其已包含 SOH 分隔符
179 :
180 277 : std::string prefix = prefix_ss.str();
181 :
182 : // 6. 计算并附加校验和
183 277 : std::string checksum = calculate_checksum(prefix);
184 554 : return prefix + std::to_string(tags::CheckSum) + "=" + checksum + SOH;
185 277 : }
186 :
187 : /**
188 : * @brief 将 FIX 协议字符串解码为 FixMessage 对象
189 : * @param raw 原始 FIX 消息字符串
190 : * @return FixMessage 解码后的消息对象
191 : * @throws std::runtime_error CheckSum 校验失败、BodyLength 不匹配或格式错误时抛出
192 : */
193 17 : FixMessage decode(const std::string& raw) const {
194 : // 1. 校验和验证
195 51 : const std::string checksum_tag = std::string(1, SOH) + std::to_string(tags::CheckSum) + "=";
196 17 : const size_t checksum_pos = raw.rfind(checksum_tag);
197 17 : if (checksum_pos == std::string::npos) {
198 1 : throw std::runtime_error("Tag 10 (Checksum) not found");
199 : }
200 16 : const std::string prefix = raw.substr(0, checksum_pos + 1);
201 16 : const std::string expected_checksum = raw.substr(checksum_pos + checksum_tag.length(), 3);
202 16 : const std::string actual_checksum = calculate_checksum(prefix);
203 16 : if (expected_checksum != actual_checksum) {
204 2 : throw std::runtime_error("Checksum mismatch: expected " + expected_checksum + ", got " + actual_checksum);
205 : }
206 :
207 : // 2. 逐字段解析
208 14 : FixMessage msg;
209 14 : size_t pos = 0;
210 : size_t next_soh;
211 152 : while ((next_soh = raw.find(SOH, pos)) != std::string::npos) {
212 138 : const std::string field = raw.substr(pos, next_soh - pos);
213 138 : if (field.empty()) {
214 0 : pos = next_soh + 1;
215 0 : continue;
216 : }
217 138 : const size_t eq_pos = field.find('=');
218 138 : if (eq_pos == std::string::npos) {
219 0 : throw std::runtime_error("Invalid field format: " + field);
220 : }
221 138 : int tag = std::stoi(field.substr(0, eq_pos));
222 138 : std::string value = field.substr(eq_pos + 1);
223 138 : msg.set(tag, value);
224 138 : pos = next_soh + 1;
225 138 : }
226 :
227 : // 3. BodyLength 验证
228 14 : const int body_len_from_msg = msg.get_int(tags::BodyLength);
229 14 : const size_t body_start_pos = raw.find(SOH, raw.find(std::to_string(tags::BodyLength) + "=")) + 1;
230 14 : const size_t actual_body_len = checksum_pos + 1 - body_start_pos;
231 14 : if (static_cast<size_t>(body_len_from_msg) != actual_body_len) {
232 0 : throw std::runtime_error("BodyLength mismatch: expected " + std::to_string(body_len_from_msg) + ", got " + std::to_string(actual_body_len));
233 : }
234 :
235 28 : return msg;
236 23 : }
237 :
238 : private:
239 : /**
240 : * @brief 从消息中构建消息体部分
241 : * @param msg 消息对象
242 : * @return std::string 消息体字符串(不含标准头和尾)
243 : */
244 277 : std::string build_body_from_message(const FixMessage& msg) const {
245 277 : std::ostringstream body;
246 :
247 : // 定义哪些 tag 属于 Body,并按 tag 升序排列以提高一致性
248 277 : std::vector<int> body_tags;
249 2036 : for (const auto& pair : msg.get_fields()) {
250 : // Body 部分包含除标准头 (8,9,35,49,56,34,52) 和尾 (10) 之外的所有字段
251 1759 : if (pair.first != tags::BeginString && pair.first != tags::BodyLength && pair.first != tags::CheckSum &&
252 1759 : pair.first != tags::MsgType && pair.first != tags::SenderCompID && pair.first != tags::TargetCompID &&
253 936 : pair.first != tags::MsgSeqNum && pair.first != tags::SendingTime) {
254 382 : body_tags.push_back(pair.first);
255 : }
256 : }
257 277 : std::sort(body_tags.begin(), body_tags.end());
258 :
259 659 : for (int tag : body_tags) {
260 382 : body << tag << "=" << msg.get_string(tag) << SOH;
261 : }
262 :
263 554 : return body.str();
264 277 : }
265 :
266 : /**
267 : * @brief 计算 FIX 校验和
268 : * @param data 要计算校验和的数据
269 : * @return std::string 3 位数字的校验和字符串
270 : *
271 : * 校验和 = 所有字节之和 mod 256,格式化为 3 位数字
272 : */
273 293 : std::string calculate_checksum(const std::string& data) const {
274 293 : const uint32_t sum = std::accumulate(data.begin(), data.end(), 0U);
275 293 : std::ostringstream oss;
276 293 : oss << std::setfill('0') << std::setw(3) << (sum % 256);
277 586 : return oss.str();
278 293 : }
279 :
280 : /**
281 : * @brief 生成 UTC 时间戳
282 : * @param buf 输出缓冲区
283 : * @param buf_size 缓冲区大小
284 : *
285 : * 格式:YYYYMMDD-HH:MM:SS
286 : */
287 277 : void generate_utc_timestamp(char* buf, size_t buf_size) const {
288 277 : std::time_t t = std::time(nullptr);
289 277 : std::tm tm{};
290 : #ifdef _WIN32
291 : gmtime_s(&tm, &t);
292 : #else
293 277 : gmtime_r(&t, &tm);
294 : #endif
295 277 : std::strftime(buf, buf_size, "%Y%m%d-%H:%M:%S", &tm);
296 277 : }
297 : };
298 : } // fix40 名称空间结束
|