1use crate::ftlogin_wire;
25use bytes::{Buf, BufMut, BytesMut};
26use tokio_util::codec::{Decoder, Encoder};
27
28pub const NN_HEADER_SIZE: usize = ftlogin_wire::HEADER_LEN;
30
31pub const NN_PROTO_VER: u8 = ftlogin_wire::PROTO_VERSION;
33
34pub const MAGIC: [u8; 2] = ftlogin_wire::MAGIC;
36
37#[derive(Debug, Clone)]
39pub struct NNHeader {
40 pub proto_ver: u8,
41 pub client_type: u8,
42 pub client_ver: u16,
43 pub lang_id: u8,
44 pub user_id: u32,
45 pub is_push: bool,
47 pub is_compressed: bool,
49 pub serial_no: u32,
50 pub cmd_id: u16,
51 pub body_len: u32,
53 pub reserved: [u8; 8],
56 pub ex_head_len: u16,
58}
59
60impl NNHeader {
61 pub fn new(cmd_id: u16, serial_no: u32) -> Self {
62 Self {
63 proto_ver: NN_PROTO_VER,
64 client_type: 0,
65 client_ver: 0,
66 lang_id: 0,
67 user_id: 0,
68 is_push: false,
69 is_compressed: false,
70 serial_no,
71 cmd_id,
72 body_len: 0,
73 reserved: [0u8; 8],
74 ex_head_len: 0,
75 }
76 }
77
78 pub fn encode(&self, dst: &mut BytesMut) {
82 let wire = ftlogin_wire::ProtocolHeader {
83 proto_version: self.proto_ver,
84 client_type: self.client_type,
85 client_version: self.client_ver,
86 language: self.lang_id,
87 user_id: self.user_id,
88 is_push: self.is_push,
89 is_compressed: self.is_compressed,
90 serial_num: self.serial_no,
91 cmd: self.cmd_id,
92 ex_head_body_len: self.ex_head_len as u32 + self.body_len,
93 reserved: self.reserved,
94 ex_head_len: self.ex_head_len,
95 };
96 dst.reserve(NN_HEADER_SIZE);
97 dst.put_slice(&wire.encode());
98 }
99
100 pub fn decode(src: &BytesMut) -> Result<Option<Self>, futu_core::error::FutuError> {
101 if src.len() < NN_HEADER_SIZE {
102 return Ok(None);
103 }
104
105 let wire = ftlogin_wire::ProtocolHeader::decode(&src[..NN_HEADER_SIZE])?;
106 let body_len = wire.body_len()? as u32;
107
108 Ok(Some(Self {
109 proto_ver: wire.proto_version,
110 client_type: wire.client_type,
111 client_ver: wire.client_version,
112 lang_id: wire.language,
113 user_id: wire.user_id,
114 is_push: wire.is_push,
115 is_compressed: wire.is_compressed,
116 serial_no: wire.serial_num,
117 cmd_id: wire.cmd,
118 body_len,
119 reserved: wire.reserved,
120 ex_head_len: wire.ex_head_len,
121 }))
122 }
123
124 #[inline]
125 pub fn is_compressed(&self) -> bool {
126 self.is_compressed
127 }
128
129 #[inline]
130 pub fn flags(&self) -> u8 {
131 u8::from(self.is_push) | (u8::from(self.is_compressed) << 1)
132 }
133}
134
135#[derive(Debug, Clone)]
137pub struct NNFrame {
138 pub header: NNHeader,
139 pub body: bytes::Bytes,
140 pub ex_head: bytes::Bytes,
143}
144
145#[derive(Debug, Clone, Default)]
147pub struct ExHeadErrorInfo {
148 pub cmd_result: i32,
150 pub source: String,
151 pub code: i32,
152 pub message: String,
153}
154
155impl NNFrame {
156 pub fn parse_ex_head_error(&self) -> Option<ExHeadErrorInfo> {
159 if self.ex_head.is_empty() {
160 return None;
161 }
162 parse_err_info_from_ex_head(&self.ex_head)
163 }
164}
165
166fn parse_err_info_from_ex_head(data: &[u8]) -> Option<ExHeadErrorInfo> {
169 let mut pos = 0;
170 while pos < data.len() {
172 let (tag, new_pos) = decode_varint(data, pos)?;
173 pos = new_pos;
174 let field_num = (tag >> 3) as u32;
175 let wire_type = (tag & 0x07) as u8;
176
177 if wire_type == 2 {
178 let (len, new_pos) = decode_varint(data, pos)?;
179 pos = new_pos;
180 let len = len as usize;
181 if pos + len > data.len() {
182 return None;
183 }
184 if field_num == 5 {
185 return Some(decode_error_info(&data[pos..pos + len]));
187 }
188 pos += len;
189 } else if wire_type == 0 {
190 let (_v, new_pos) = decode_varint(data, pos)?;
191 pos = new_pos;
192 } else if wire_type == 1 {
193 pos += 8;
194 } else if wire_type == 5 {
195 pos += 4;
196 } else {
197 return None;
198 }
199 }
200 None
201}
202
203fn decode_error_info(data: &[u8]) -> ExHeadErrorInfo {
204 let mut info = ExHeadErrorInfo::default();
205 let mut pos = 0;
206 while pos < data.len() {
207 let Some((tag, new_pos)) = decode_varint(data, pos) else {
208 break;
209 };
210 pos = new_pos;
211 let field_num = (tag >> 3) as u32;
212 let wire_type = (tag & 0x07) as u8;
213
214 match (field_num, wire_type) {
215 (1, 0) => {
216 if let Some((v, p)) = decode_varint(data, pos) {
218 info.cmd_result = v as i32;
219 pos = p;
220 } else {
221 break;
222 }
223 }
224 (3, 0) => {
225 if let Some((v, p)) = decode_varint(data, pos) {
227 info.code = v as i32;
228 pos = p;
229 } else {
230 break;
231 }
232 }
233 (2, 2) | (4, 2) => {
234 let Some((len, new_pos)) = decode_varint(data, pos) else {
236 break;
237 };
238 pos = new_pos;
239 let end = pos + len as usize;
240 if end > data.len() {
241 break;
242 }
243 let s = String::from_utf8_lossy(&data[pos..end]).to_string();
244 if field_num == 2 {
245 info.source = s;
246 } else {
247 info.message = s;
248 }
249 pos = end;
250 }
251 (_, 0) => {
252 let Some((_v, p)) = decode_varint(data, pos) else {
253 break;
254 };
255 pos = p;
256 }
257 (_, 2) => {
258 let Some((len, p)) = decode_varint(data, pos) else {
259 break;
260 };
261 pos = p + len as usize;
262 }
263 (_, 1) => pos += 8,
264 (_, 5) => pos += 4,
265 _ => break,
266 }
267 }
268 info
269}
270
271fn decode_varint(data: &[u8], start: usize) -> Option<(u64, usize)> {
272 let mut result: u64 = 0;
273 let mut shift = 0;
274 let mut pos = start;
275 loop {
276 if pos >= data.len() {
277 return None;
278 }
279 let byte = data[pos];
280 result |= ((byte & 0x7F) as u64) << shift;
281 pos += 1;
282 if byte & 0x80 == 0 {
283 return Some((result, pos));
284 }
285 shift += 7;
286 if shift >= 64 {
287 return None;
288 }
289 }
290}
291
292pub struct NNCodec;
294
295impl Decoder for NNCodec {
296 type Item = NNFrame;
297 type Error = futu_core::error::FutuError;
298
299 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
300 let header = match NNHeader::decode(src)? {
301 Some(h) => h,
302 None => return Ok(None),
303 };
304
305 let ex_head_body_len = header.ex_head_len as usize + header.body_len as usize;
307 let total = NN_HEADER_SIZE + ex_head_body_len;
308 if src.len() < total {
309 src.reserve(total - src.len());
310 return Ok(None);
311 }
312
313 src.advance(NN_HEADER_SIZE);
314 let ex_head = if header.ex_head_len > 0 {
316 src.split_to(header.ex_head_len as usize).freeze()
317 } else {
318 bytes::Bytes::new()
319 };
320 let body = src.split_to(header.body_len as usize).freeze();
324
325 Ok(Some(NNFrame {
326 header,
327 body,
328 ex_head,
329 }))
330 }
331}
332
333impl Encoder<NNFrame> for NNCodec {
334 type Error = futu_core::error::FutuError;
335
336 fn encode(&mut self, item: NNFrame, dst: &mut BytesMut) -> Result<(), Self::Error> {
337 let mut header = item.header;
338 header.body_len = item.body.len() as u32;
339 header.encode(dst);
340 dst.extend_from_slice(&item.body);
341 Ok(())
342 }
343}
344
345pub fn is_login_cmd(cmd_id: u16) -> bool {
347 matches!(cmd_id, 1001 | 6001 | 2001 | 4001)
348}
349
350pub fn is_unencrypted_proto(cmd_id: u16) -> bool {
353 matches!(
354 cmd_id,
355 1306 | 1316 | 1321 | 20147 | 5115 | 5120 | 5121 | 6682 | 6032 | 6128 | 6160 | 6161 | 6211 | 6212 | 6301 | 6304 | 6311 | 6337 | 6365 | 6366 | 6503 | 6513 | 6600 | 6608 | 6621 | 6693 | 6694 | 6695 | 6701 | 6733 | 6736 | 6745 | 6746 | 6747 | 6801 | 6802 | 6804 | 6803 | 6808 | 6809 | 6811 | 6822 | 6823 | 6824 | 6825 | 6956 | 6957 | 8017 | 18008 | 18012 | 65507 | 65509 | 20106 | 20287 | 20334 )
409}
410
411pub fn should_skip_encryption(cmd_id: u16) -> bool {
413 is_login_cmd(cmd_id) || is_unencrypted_proto(cmd_id)
414}
415
416#[cfg(test)]
417mod tests;