futu_codec/
header.rs

1use bytes::{Buf, BufMut, BytesMut};
2
3/// 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/// Magic bytes: "FT"
17pub const MAGIC: [u8; 2] = [b'F', b'T'];
18
19/// 协议格式类型
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[repr(u8)]
22pub enum ProtoFmtType {
23    Protobuf = 0,
24    Json = 1,
25}
26
27impl TryFrom<u8> for ProtoFmtType {
28    type Error = u8;
29
30    fn try_from(value: u8) -> Result<Self, u8> {
31        match value {
32            0 => Ok(Self::Protobuf),
33            1 => Ok(Self::Json),
34            other => Err(other),
35        }
36    }
37}
38
39/// FutuOpenD API 协议帧头
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct FutuHeader {
42    pub proto_id: u32,
43    pub proto_fmt_type: ProtoFmtType,
44    pub proto_ver: u8,
45    pub serial_no: u32,
46    pub body_len: u32,
47    pub body_sha1: [u8; 20],
48}
49
50impl FutuHeader {
51    /// 从 BytesMut 中解析帧头(不消费 bytes,仅 peek)
52    ///
53    /// 返回 None 如果数据不足 HEADER_SIZE 字节。
54    /// 返回 Err 如果 magic bytes 不匹配或格式类型无效。
55    pub fn peek(src: &BytesMut) -> Result<Option<Self>, futu_core::error::FutuError> {
56        if src.len() < HEADER_SIZE {
57            return Ok(None);
58        }
59
60        // 校验 magic bytes
61        if src[0] != MAGIC[0] || src[1] != MAGIC[1] {
62            return Err(futu_core::error::FutuError::InvalidHeader);
63        }
64
65        let proto_id = u32::from_le_bytes([src[2], src[3], src[4], src[5]]);
66        let proto_fmt_type = ProtoFmtType::try_from(src[6]).map_err(|v| {
67            futu_core::error::FutuError::Codec(format!("unknown proto fmt type: {v}"))
68        })?;
69        let proto_ver = src[7];
70        let serial_no = u32::from_le_bytes([src[8], src[9], src[10], src[11]]);
71        let body_len = u32::from_le_bytes([src[12], src[13], src[14], src[15]]);
72
73        let mut body_sha1 = [0u8; 20];
74        body_sha1.copy_from_slice(&src[16..36]);
75
76        Ok(Some(Self {
77            proto_id,
78            proto_fmt_type,
79            proto_ver,
80            serial_no,
81            body_len,
82            body_sha1,
83        }))
84    }
85
86    /// 从 BytesMut 中解析并消费帧头
87    pub fn decode(src: &mut BytesMut) -> Result<Option<Self>, futu_core::error::FutuError> {
88        let header = Self::peek(src)?;
89        if header.is_some() {
90            src.advance(HEADER_SIZE);
91        }
92        Ok(header)
93    }
94
95    /// 将帧头写入 BytesMut
96    pub fn encode(&self, dst: &mut BytesMut) {
97        dst.reserve(HEADER_SIZE);
98        dst.put_slice(&MAGIC);
99        dst.put_u32_le(self.proto_id);
100        dst.put_u8(self.proto_fmt_type as u8);
101        dst.put_u8(self.proto_ver);
102        dst.put_u32_le(self.serial_no);
103        dst.put_u32_le(self.body_len);
104        dst.put_slice(&self.body_sha1);
105        dst.put_slice(&[0u8; 8]); // reserved
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_header_roundtrip() {
115        let header = FutuHeader {
116            proto_id: 1001,
117            proto_fmt_type: ProtoFmtType::Protobuf,
118            proto_ver: 0,
119            serial_no: 42,
120            body_len: 128,
121            body_sha1: [0xAB; 20],
122        };
123
124        let mut buf = BytesMut::new();
125        header.encode(&mut buf);
126        assert_eq!(buf.len(), HEADER_SIZE);
127
128        let decoded = FutuHeader::peek(&buf).unwrap().unwrap();
129        assert_eq!(header, decoded);
130    }
131
132    #[test]
133    fn test_header_insufficient_data() {
134        let buf = BytesMut::from(&[0u8; 10][..]);
135        assert!(FutuHeader::peek(&buf).unwrap().is_none());
136    }
137
138    #[test]
139    fn test_header_invalid_magic() {
140        let mut buf = BytesMut::from(&[0u8; HEADER_SIZE][..]);
141        buf[0] = b'X';
142        buf[1] = b'Y';
143        assert!(FutuHeader::peek(&buf).is_err());
144    }
145}