Skip to main content

futu_codec/
header.rs

1use bytes::{Buf, BufMut, BytesMut};
2
3/// **Stable API** — API 协议帧头大小 (44 bytes)。
4///
5/// 对应 C++ `APIProtoHeader` 结构体:
6/// - szHeaderFlag[2]:  "FT" magic bytes
7/// - nProtoID:         u32 LE, 协议 ID
8/// - nProtoFmtType:    u8, 0=Protobuf, 1=JSON
9/// - nProtoVer:        u8, 协议版本
10/// - nSerialNo:        u32 LE, 序列号
11/// - nBodyLen:         u32 LE, body 长度
12/// - arrBodySHA1[20]:  body 的 SHA1 哈希
13/// - arrReserved[8]:   保留字段
14pub const HEADER_SIZE: usize = 44;
15
16/// **Internal** — Magic bytes "FT",仅 `header.rs` 内部 encode/decode 用。
17///
18/// v1.4.89 API audit 确认无跨 crate 使用(`grep MAGIC` 返 0 match in
19/// downstream crates),gate 为 `pub(crate)` 防意外外部依赖。
20pub(crate) const MAGIC: [u8; 2] = [b'F', b'T'];
21
22/// **Stable API** — 协议格式类型(Protobuf=0 / JSON=1)。
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[repr(u8)]
25#[non_exhaustive]
26pub enum ProtoFmtType {
27    Protobuf = 0,
28    Json = 1,
29}
30
31impl TryFrom<u8> for ProtoFmtType {
32    type Error = u8;
33
34    fn try_from(value: u8) -> Result<Self, u8> {
35        match value {
36            0 => Ok(Self::Protobuf),
37            1 => Ok(Self::Json),
38            other => Err(other),
39        }
40    }
41}
42
43/// **Stable API** — FutuOpenD API 协议帧头(44 字节,手工 encode/decode)。
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct FutuHeader {
46    pub proto_id: u32,
47    pub proto_fmt_type: ProtoFmtType,
48    pub proto_ver: u8,
49    pub serial_no: u32,
50    pub body_len: u32,
51    pub body_sha1: [u8; 20],
52}
53
54impl FutuHeader {
55    /// **Stable API** — 从 `BytesMut` 中解析帧头(不消费 bytes,仅 peek)。
56    ///
57    /// 返回 `None` 如果数据不足 [`HEADER_SIZE`] 字节。
58    /// 返回 `Err` 如果 magic bytes 不匹配或格式类型无效。
59    pub fn peek(src: &BytesMut) -> Result<Option<Self>, futu_core::error::FutuError> {
60        if src.len() < HEADER_SIZE {
61            return Ok(None);
62        }
63
64        // 校验 magic bytes
65        if src[0] != MAGIC[0] || src[1] != MAGIC[1] {
66            return Err(futu_core::error::FutuError::InvalidHeader);
67        }
68
69        let proto_id = u32::from_le_bytes([src[2], src[3], src[4], src[5]]);
70        let proto_fmt_type = ProtoFmtType::try_from(src[6]).map_err(|v| {
71            futu_core::error::FutuError::Codec(format!("unknown proto fmt type: {v}"))
72        })?;
73        let proto_ver = src[7];
74        let serial_no = u32::from_le_bytes([src[8], src[9], src[10], src[11]]);
75        let body_len = u32::from_le_bytes([src[12], src[13], src[14], src[15]]);
76
77        let mut body_sha1 = [0u8; 20];
78        body_sha1.copy_from_slice(&src[16..36]);
79
80        Ok(Some(Self {
81            proto_id,
82            proto_fmt_type,
83            proto_ver,
84            serial_no,
85            body_len,
86            body_sha1,
87        }))
88    }
89
90    /// **Stable API** — 从 `BytesMut` 中解析并消费帧头(成功时 advance `HEADER_SIZE` bytes)。
91    pub fn decode(src: &mut BytesMut) -> Result<Option<Self>, futu_core::error::FutuError> {
92        let header = Self::peek(src)?;
93        if header.is_some() {
94            src.advance(HEADER_SIZE);
95        }
96        Ok(header)
97    }
98
99    /// **Stable API** — 将帧头写入 `BytesMut`(44 字节,LE 序 + MAGIC + SHA1 + reserved)。
100    pub fn encode(&self, dst: &mut BytesMut) {
101        dst.reserve(HEADER_SIZE);
102        dst.put_slice(&MAGIC);
103        dst.put_u32_le(self.proto_id);
104        dst.put_u8(self.proto_fmt_type as u8);
105        dst.put_u8(self.proto_ver);
106        dst.put_u32_le(self.serial_no);
107        dst.put_u32_le(self.body_len);
108        dst.put_slice(&self.body_sha1);
109        dst.put_slice(&[0u8; 8]); // reserved
110    }
111}
112
113#[cfg(test)]
114mod tests;