1pub fn make_success_response<M: prost::Message>(resp: &M) -> Vec<u8> {
17 prost::Message::encode_to_vec(resp)
18}
19
20pub fn decode_backend_proto<M: prost::Message + Default>(
25 body: &[u8],
26) -> Result<M, prost::DecodeError> {
27 match prost::Message::decode(body) {
29 Ok(m) => Ok(m),
30 Err(e1) => {
31 if body.len() >= 4 {
33 match prost::Message::decode(&body[4..]) {
34 Ok(m) => {
35 tracing::debug!(
36 body_len = body.len(),
37 "decoded backend proto after skipping 4-byte length prefix"
38 );
39 Ok(m)
40 }
41 Err(_) => Err(e1), }
43 } else {
44 Err(e1)
45 }
46 }
47 }
48}
49
50pub fn extract_repeated_field<M: prost::Message + Default>(
54 body: &[u8],
55 target_field: u32,
56) -> Vec<M> {
57 let mut results = Vec::new();
58 let mut pos = 0;
59 while pos < body.len() {
60 let (tag, new_pos) = match decode_varint(body, pos) {
62 Some(v) => v,
63 None => break,
64 };
65 let field_number = (tag >> 3) as u32;
66 let wire_type = (tag & 0x7) as u8;
67
68 match wire_type {
69 0 => {
70 match decode_varint(body, new_pos) {
72 Some((_, p)) => pos = p,
73 None => break,
74 }
75 }
76 1 => {
77 pos = new_pos + 8;
79 }
80 2 => {
81 let (length, data_start) = match decode_varint(body, new_pos) {
83 Some(v) => (v.0 as usize, v.1),
84 None => break,
85 };
86 if data_start + length > body.len() {
87 break;
88 }
89 if field_number == target_field
90 && let Ok(msg) = prost::Message::decode(&body[data_start..data_start + length])
91 {
92 results.push(msg);
93 }
94 pos = data_start + length;
95 }
96 5 => {
97 pos = new_pos + 4;
99 }
100 _ => break,
101 }
102 }
103 results
104}
105
106pub fn extract_field5_message<M: prost::Message + Default>(body: &[u8]) -> Option<M> {
109 extract_field5_message_with(body, |_| true)
110}
111
112pub fn extract_field5_validated<M: prost::Message + Default>(
115 body: &[u8],
116 validator: impl Fn(&M) -> bool,
117) -> Option<M> {
118 extract_field5_message_with(body, validator)
119}
120
121fn extract_field5_message_with<M: prost::Message + Default>(
122 body: &[u8],
123 validator: impl Fn(&M) -> bool,
124) -> Option<M> {
125 let field5_data = extract_raw_field(body, 5)?;
126
127 if let Ok(m) = prost::Message::decode(field5_data)
129 && validator(&m)
130 {
131 return Some(m);
132 }
133
134 if let Some(inner) = extract_raw_field(field5_data, 4)
137 && let Ok(m) = prost::Message::decode(inner)
138 && validator(&m)
139 {
140 tracing::debug!("decoded message from SRPC field 5 → inner field 4");
141 return Some(m);
142 }
143
144 None
145}
146
147fn extract_raw_field(body: &[u8], target_field: u32) -> Option<&[u8]> {
149 let mut pos = 0;
150 while pos < body.len() {
151 let (tag, new_pos) = decode_varint(body, pos)?;
152 let field_number = (tag >> 3) as u32;
153 let wire_type = (tag & 0x7) as u8;
154
155 match wire_type {
156 0 => {
157 let (_, p) = decode_varint(body, new_pos)?;
158 pos = p;
159 }
160 1 => {
161 pos = new_pos + 8;
162 }
163 2 => {
164 let (length, data_start) = decode_varint(body, new_pos)?;
165 let length = length as usize;
166 if data_start + length > body.len() {
167 return None;
168 }
169 if field_number == target_field {
170 return Some(&body[data_start..data_start + length]);
171 }
172 pos = data_start + length;
173 }
174 5 => {
175 pos = new_pos + 4;
176 }
177 _ => return None,
178 }
179 }
180 None
181}
182
183fn decode_varint(body: &[u8], start: usize) -> Option<(u64, usize)> {
184 let mut result: u64 = 0;
185 let mut shift = 0;
186 let mut pos = start;
187 loop {
188 if pos >= body.len() {
189 return None;
190 }
191 let b = body[pos];
192 pos += 1;
193 result |= ((b & 0x7f) as u64) << shift;
194 if b & 0x80 == 0 {
195 return Some((result, pos));
196 }
197 shift += 7;
198 if shift >= 64 {
199 return None;
200 }
201 }
202}
203
204#[derive(Debug)]
206pub enum SrpcDecodeError {
207 AllPathsFailed { body_len: usize },
209 ValidatorRejected,
211}
212
213impl std::fmt::Display for SrpcDecodeError {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 match self {
216 Self::AllPathsFailed { body_len } => {
217 write!(
218 f,
219 "SRPC decode failed: all paths failed (body_len={body_len})"
220 )
221 }
222 Self::ValidatorRejected => {
223 write!(f, "SRPC decode succeeded but validator rejected response")
224 }
225 }
226 }
227}
228
229impl std::error::Error for SrpcDecodeError {}
230
231pub fn decode_srpc_or_direct<M: prost::Message + Default>(
244 body: &[u8],
245 validator: impl Fn(&M) -> bool,
246) -> Result<M, SrpcDecodeError> {
247 if let Ok(r) = decode_backend_proto::<M>(body)
249 && validator(&r)
250 {
251 return Ok(r);
252 }
253
254 if let Some(r) = extract_field5_validated::<M>(body, &validator) {
256 tracing::debug!("decoded message from SRPC field 5");
257 return Ok(r);
258 }
259
260 let raw_decoded = decode_backend_proto::<M>(body).is_ok()
263 || extract_field5_message_with(body, |_: &M| true).is_some();
264
265 if raw_decoded {
266 tracing::warn!(
267 body_len = body.len(),
268 "SRPC decode: validator rejected all candidate decodes"
269 );
270 Err(SrpcDecodeError::ValidatorRejected)
271 } else {
272 tracing::warn!(
273 body_len = body.len(),
274 "SRPC decode: all paths failed (cannot parse body as target message)"
275 );
276 Err(SrpcDecodeError::AllPathsFailed {
277 body_len: body.len(),
278 })
279 }
280}
281
282#[derive(Debug, Clone)]
284pub struct MultiLangName {
285 pub language_id: i32,
286 pub name: String,
287}
288
289pub fn decode_cmd5121_groups(
295 body: &[u8],
296) -> (
297 futu_backend::proto_internal::wch_lst::GetGroupListResp,
298 Vec<Vec<MultiLangName>>,
299) {
300 use futu_backend::proto_internal::wch_lst::{GetGroupListResp, GroupInfo};
301
302 if let Ok(r) = decode_backend_proto::<GetGroupListResp>(body)
304 && r.result_code.is_some()
305 && !r.group_list.is_empty()
306 {
307 let empty_langs = vec![vec![]; r.group_list.len()];
308 return (r, empty_langs);
309 }
310
311 let srpc_meta = extract_raw_field_bytes(body, 3);
314 let srpc_result_code = srpc_meta
315 .first()
316 .and_then(|meta| decode_srpc_result_code(meta));
317
318 if let Some(code) = srpc_result_code
319 && code != 0
320 {
321 let hex = srpc_meta_hex(&srpc_meta);
322 tracing::warn!(body_len = body.len(), result_code = code, srpc_meta_hex = %hex, "CMD5121: SRPC metadata returned error");
325 return (
326 GetGroupListResp {
327 result_code: Some(code),
328 ..Default::default()
329 },
330 vec![],
331 );
332 }
333
334 let group_list: Vec<GroupInfo> = extract_repeated_field(body, 5);
337 if group_list.is_empty() {
338 tracing::warn!(body_len = body.len(), "CMD5121 decode: no GroupInfo found");
339 return (GetGroupListResp::default(), vec![]);
340 }
341
342 let raw_items: Vec<&[u8]> = extract_raw_field_bytes(body, 5);
344 let lang_list: Vec<Vec<MultiLangName>> = raw_items
345 .iter()
346 .map(|raw| extract_multi_lang_names(raw))
347 .collect();
348
349 tracing::debug!(
350 count = group_list.len(),
351 "decoded GroupInfo from SRPC field 5 with multi_lang_name"
352 );
353 let resp = GetGroupListResp {
354 result_code: Some(0),
355 group_count: Some(group_list.len() as u32),
356 group_list,
357 max_reached: None,
358 };
359 (resp, lang_list)
360}
361
362fn decode_srpc_result_code(meta: &[u8]) -> Option<i32> {
363 let mut pos = 0;
364 while pos < meta.len() {
365 let (tag, new_pos) = decode_varint(meta, pos)?;
366 let field_number = (tag >> 3) as u32;
367 let wire_type = (tag & 0x7) as u8;
368 match wire_type {
369 0 => {
370 let (val, p) = decode_varint(meta, new_pos)?;
371 if field_number == 1 {
372 return Some(val as i32);
373 }
374 pos = p;
375 }
376 1 => {
377 pos = new_pos + 8;
378 }
379 2 => {
380 let (len, start) = decode_varint(meta, new_pos)?;
381 pos = start + len as usize;
382 }
383 5 => {
384 pos = new_pos + 4;
385 }
386 _ => break,
387 }
388 }
389 None
390}
391
392fn srpc_meta_hex(meta: &[&[u8]]) -> String {
393 meta.first()
394 .map(|m| {
395 m.iter()
396 .map(|b| format!("{b:02x}"))
397 .collect::<Vec<_>>()
398 .join(" ")
399 })
400 .unwrap_or_default()
401}
402
403fn extract_raw_field_bytes(body: &[u8], target_field: u32) -> Vec<&[u8]> {
405 let mut results = Vec::new();
406 let mut pos = 0;
407 while pos < body.len() {
408 let (tag, new_pos) = match decode_varint(body, pos) {
409 Some(v) => v,
410 None => break,
411 };
412 let field_number = (tag >> 3) as u32;
413 let wire_type = (tag & 0x7) as u8;
414
415 match wire_type {
416 0 => match decode_varint(body, new_pos) {
417 Some((_, p)) => pos = p,
418 None => break,
419 },
420 1 => {
421 pos = new_pos + 8;
422 }
423 2 => {
424 let (length, data_start) = match decode_varint(body, new_pos) {
425 Some(v) => (v.0 as usize, v.1),
426 None => break,
427 };
428 if data_start + length > body.len() {
429 break;
430 }
431 if field_number == target_field {
432 results.push(&body[data_start..data_start + length]);
433 }
434 pos = data_start + length;
435 }
436 5 => {
437 pos = new_pos + 4;
438 }
439 _ => break,
440 }
441 }
442 results
443}
444
445fn extract_multi_lang_names(group_info_bytes: &[u8]) -> Vec<MultiLangName> {
450 let mut results = Vec::new();
451
452 let raw_items = extract_raw_field_bytes(group_info_bytes, 4);
454
455 for raw in raw_items {
456 let mut language_id: i32 = 0;
458 let mut name = String::new();
459 let mut pos = 0;
460
461 while pos < raw.len() {
462 let (tag, new_pos) = match decode_varint(raw, pos) {
463 Some(v) => v,
464 None => break,
465 };
466 let field_number = (tag >> 3) as u32;
467 let wire_type = (tag & 0x7) as u8;
468
469 match wire_type {
470 0 => {
471 let (val, p) = match decode_varint(raw, new_pos) {
473 Some(v) => v,
474 None => break,
475 };
476 if field_number == 1 {
477 language_id = val as i32;
478 }
479 pos = p;
480 }
481 1 => {
482 pos = new_pos + 8;
483 }
484 2 => {
485 let (length, data_start) = match decode_varint(raw, new_pos) {
486 Some(v) => (v.0 as usize, v.1),
487 None => break,
488 };
489 if data_start + length > raw.len() {
490 break;
491 }
492 if field_number == 2 {
493 name = String::from_utf8_lossy(&raw[data_start..data_start + length])
494 .to_string();
495 }
496 pos = data_start + length;
497 }
498 5 => {
499 pos = new_pos + 4;
500 }
501 _ => break,
502 }
503 }
504
505 if !name.is_empty() {
506 results.push(MultiLangName { language_id, name });
507 }
508 }
509
510 results
511}
512
513pub fn make_error_response(ret_type: i32, msg: &str) -> Vec<u8> {
515 let resp = futu_proto::init_connect::Response {
516 ret_type,
517 ret_msg: Some(msg.to_string()),
518 err_code: None,
519 s2c: None,
520 };
521 prost::Message::encode_to_vec(&resp)
522}
523
524pub fn make_standard_error_response(msg: impl AsRef<str>) -> Vec<u8> {
526 make_error_response(-1, msg.as_ref())
527}
528
529pub fn decode_request_or_error<M>(
530 body: &[u8],
531 iface: &'static str,
532 endpoint: &'static str,
533) -> Result<M, Vec<u8>>
534where
535 M: prost::Message + Default,
536{
537 <M as prost::Message>::decode(body).map_err(|err| {
538 tracing::warn!(
539 target: futu_auth::audit::TARGET,
540 iface,
541 endpoint,
542 error = %err,
543 "request body decode failed before handler dispatch"
544 );
545 make_error_response(-1, &format!("{endpoint}: 请求体解码失败"))
546 })
547}
548
549pub fn make_error_response_with_code(ret_type: i32, err_code: i32, msg: &str) -> Vec<u8> {
555 let resp = futu_proto::init_connect::Response {
556 ret_type,
557 ret_msg: Some(msg.to_string()),
558 err_code: Some(err_code),
559 s2c: None,
560 };
561 prost::Message::encode_to_vec(&resp)
562}
563
564#[cfg(test)]
565mod tests;