futu_backend/
valid_brokers.rs1use futu_core::error::{FutuError, Result};
24use prost::Message;
25
26use crate::auth::redact::uid_log_fingerprint;
27use crate::conn::BackendConn;
28use crate::proto_internal::ft_conn_bind::{GetValidBrokerListReq, GetValidBrokerListRsp};
29
30pub const CMD_FETCH_VALID_BROKER_LIST: u16 = 20176;
32
33pub async fn fetch_valid_broker_list(backend: &BackendConn, uid: u64) -> Result<Vec<u32>> {
36 let req = GetValidBrokerListReq { uid: Some(uid) };
37 let body = req.encode_to_vec();
38 tracing::debug!(
39 uid_fp = %uid_log_fingerprint(uid),
40 body_len = body.len(),
41 "sending CMD20176 GetValidBrokerListReq"
42 );
43
44 let resp = backend.request(CMD_FETCH_VALID_BROKER_LIST, body).await?;
45
46 let rsp = GetValidBrokerListRsp::decode(resp.body.as_ref())
47 .map_err(|e| FutuError::Codec(format!("CMD20176 decode: {e}")))?;
48
49 let ret_code = rsp.ret_code.unwrap_or(-1);
50 if ret_code != 0 {
51 return Err(FutuError::ServerError {
52 ret_type: ret_code,
53 msg: format!(
54 "CMD20176 ret_code={ret_code} msg={:?}",
55 rsp.ret_msg.as_deref().unwrap_or("")
56 ),
57 });
58 }
59
60 tracing::info!(
61 uid_fp = %uid_log_fingerprint(rsp.uid.unwrap_or(0)),
62 count = rsp.broker_ids.len(),
63 broker_ids = ?rsp.broker_ids,
64 "CMD20176 valid broker list received"
65 );
66 Ok(rsp.broker_ids)
67}
68
69pub fn diff_broker_sources(auth_code_broker_ids: &[u32], cmd20176_broker_ids: &[u32]) -> Vec<u32> {
75 use std::collections::HashSet;
76 let auth_set: HashSet<u32> = auth_code_broker_ids.iter().copied().collect();
77 let cmd_set: HashSet<u32> = cmd20176_broker_ids.iter().copied().collect();
78
79 let only_auth: Vec<u32> = auth_set.difference(&cmd_set).copied().collect();
80 let only_cmd: Vec<u32> = cmd_set.difference(&auth_set).copied().collect();
81
82 if !only_auth.is_empty() || !only_cmd.is_empty() {
83 tracing::warn!(
84 only_in_auth_code_list = ?only_auth,
85 only_in_cmd20176 = ?only_cmd,
86 "broker source mismatch: HTTP auth_code_list vs CMD20176 differ — \
87 using CMD20176 as authority"
88 );
89 } else {
90 tracing::debug!(
91 count = auth_code_broker_ids.len(),
92 "broker source consistent between auth_code_list and CMD20176"
93 );
94 }
95
96 cmd20176_broker_ids.to_vec()
97}
98
99#[cfg(test)]
100mod tests;