1use bytes::{Buf, BufMut, BytesMut};
2
3pub const HEADER_SIZE: usize = 44;
15
16pub const MAGIC: [u8; 2] = [b'F', b'T'];
18
19#[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#[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 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 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 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 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]); }
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}