Skip to main content

futu_codec/
frame.rs

1use bytes::Bytes;
2
3use crate::header::{FutuHeader, ProtoFmtType};
4
5/// **Stable API** — FutuOpenD 协议帧(帧头 + body)。
6///
7/// 44 字节 [`FutuHeader`] + 变长 [`bytes::Bytes`] body。SHA1 基于 body 明文
8/// 在 [`FutuFrame::new`] 中自动计算。加密在更上层(`futu-net`)完成。
9#[derive(Debug, Clone)]
10pub struct FutuFrame {
11    pub header: FutuHeader,
12    pub body: Bytes,
13}
14
15impl FutuFrame {
16    /// **Stable API** — 计算 Futu 协议帧头里的明文 body SHA1。
17    #[must_use]
18    pub fn body_sha1(body: &[u8]) -> [u8; 20] {
19        use sha1::{Digest, Sha1};
20
21        let mut hasher = Sha1::new();
22        hasher.update(body);
23        let sha1_result = hasher.finalize();
24        let mut body_sha1 = [0u8; 20];
25        body_sha1.copy_from_slice(&sha1_result);
26        body_sha1
27    }
28
29    /// **Stable API** — 构造新协议帧,自动计算 body SHA1 + 填帧头。
30    pub fn new(proto_id: u32, serial_no: u32, body: Bytes) -> Self {
31        let body_sha1 = Self::body_sha1(&body);
32        Self::with_sha1(proto_id, serial_no, body, body_sha1)
33    }
34
35    /// **Stable API** — 构造新协议帧,使用调用方已计算好的 body SHA1。
36    ///
37    /// Futu 协议的 `body_sha1` 基于**明文** body;加密发送路径会先计算明文
38    /// SHA1,再把 body 替换成密文。该构造器避免先对密文重复计算一次无用
39    /// SHA1 再覆盖帧头。
40    pub fn with_sha1(proto_id: u32, serial_no: u32, body: Bytes, body_sha1: [u8; 20]) -> Self {
41        Self {
42            header: FutuHeader {
43                proto_id,
44                proto_fmt_type: ProtoFmtType::Protobuf,
45                proto_ver: 0,
46                serial_no,
47                body_len: body.len() as u32,
48                body_sha1,
49            },
50            body,
51        }
52    }
53
54    /// **Stable API** — 校验 body 的 SHA1 是否与帧头中的一致。
55    ///
56    /// 解密后立即调用,失败返 false → 上层报 `FutuError::Sha1Mismatch`。
57    #[must_use]
58    pub fn verify_sha1(&self) -> bool {
59        Self::body_sha1(&self.body) == self.header.body_sha1
60    }
61}
62
63#[cfg(test)]
64mod tests;