LCOV - code coverage report
Current view: top level - include/fix - fix_codec.hpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 95.5 % 89 85
Test Date: 2025-12-19 03:13:09 Functions: 100.0 % 11 11

            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 名称空间结束
        

Generated by: LCOV version 2.0-1