futu_backend/auth/
broker.rs1use crate::conn::BackendProtocolIdentity;
19use futu_core::error::{FutuError, Result};
20pub(crate) use futu_domain_broker_reconnect::BrokerAuthStage;
21pub use futu_domain_broker_reconnect::{
22 BrokerAuth, BrokerConfig, broker_config, is_cpp_known_broker_id,
23};
24use futu_domain_broker_reconnect::{
25 BrokerAuthDomainPlan, BrokerAuthDomainPlanInput, BrokerAuthStartStageInput,
26 BrokerAuthStartStageLogAction, plan_broker_auth_start_stage,
27};
28
29use super::commconfig::AuthGuaranteedDomainMap;
30
31mod domain_attempt;
32mod failure;
33mod http;
34mod init_stage;
35mod request;
36mod response;
37mod retry_ip;
38mod retry_ip_attempt;
39mod retry_ip_candidates;
40mod route_cache;
41mod success_finalize;
42mod success_log;
43mod webtcp_attempt;
44mod workflow;
45pub(crate) use init_stage::broker_auth_init_stage_from_site_config;
46pub(crate) use request::broker_auth_request_body;
47pub(crate) use response::parse_broker_auth_response;
48#[cfg(test)]
49pub(crate) use retry_ip::broker_auth_retry_ip_candidates;
50pub(crate) use retry_ip::broker_auth_retry_ips;
51#[cfg(test)]
52pub(crate) use retry_ip::{
53 broker_auth_retry_ip_list_url, broker_auth_retry_ip_request_headers,
54 parse_broker_auth_retry_ip_snapshot,
55};
56pub use route_cache::BrokerAuthRouteCache;
57#[cfg(test)]
58pub(crate) use success_log::broker_auth_success_log_fields;
59use workflow::{BrokerAuthWorkflowInput, execute_broker_auth_workflow};
60
61pub(crate) fn broker_auth_domain_plan(
71 cfg: BrokerConfig,
72 client_type: u8,
73 auth_guaranteed_domains: &AuthGuaranteedDomainMap,
74 auth_guaranteed_domains_configured: bool,
75) -> BrokerAuthDomainPlan {
76 futu_domain_broker_reconnect::plan_broker_auth_domains(BrokerAuthDomainPlanInput {
77 auth_domain: cfg.auth_domain,
78 client_type,
79 guaranteed_retry_domain: auth_guaranteed_domains
80 .get(cfg.auth_domain)
81 .map(String::as_str),
82 guaranteed_domains_configured: auth_guaranteed_domains_configured,
83 })
84}
85
86pub struct BrokerAuthRequest<'a> {
101 pub http: &'a reqwest::Client,
102 pub protocol_identity: BackendProtocolIdentity,
103 pub uid: u64,
104 pub broker_id: u32,
105 pub auth_code: &'a str,
106 pub device_id: &'a str,
107 pub web_tcp_identity: u32,
108 pub web_tcp_addrs: &'a [(String, u16)],
109 pub site_config: Option<&'a super::site_config::SharedSiteConfig>,
110 pub auth_guaranteed_domains: &'a AuthGuaranteedDomainMap,
111 pub auth_guaranteed_domains_configured: bool,
112 pub route_cache: Option<&'a BrokerAuthRouteCache>,
113}
114
115pub async fn broker_auth(input: BrokerAuthRequest<'_>) -> Result<BrokerAuth> {
116 let BrokerAuthRequest {
117 http,
118 protocol_identity,
119 uid,
120 broker_id,
121 auth_code,
122 device_id,
123 web_tcp_identity,
124 web_tcp_addrs,
125 site_config,
126 auth_guaranteed_domains,
127 auth_guaranteed_domains_configured,
128 route_cache,
129 } = input;
130
131 let cfg = broker_config(broker_id).ok_or_else(|| {
132 FutuError::Codec(format!(
133 "broker_auth: unknown broker_id {broker_id} (not in broker_config map)"
134 ))
135 })?;
136
137 let body = broker_auth_request_body(uid, auth_code, device_id, broker_id);
138
139 let domain_plan = broker_auth_domain_plan(
140 cfg,
141 protocol_identity.client_type(),
142 auth_guaranteed_domains,
143 auth_guaranteed_domains_configured,
144 );
145 let primary_url = format!(
146 "https://{}/broker_auth/client_auth",
147 domain_plan.primary_domain
148 );
149 let start_stage_input =
150 match route_cache.and_then(|cache| cache.preferred_stage(cfg.auth_domain)) {
151 Some(stage) => BrokerAuthStartStageInput::CachedRoute(stage),
152 None => {
153 let site_config_stage = broker_auth_init_stage_from_site_config(
154 web_tcp_identity,
155 &primary_url,
156 site_config,
157 )
158 .await;
159 BrokerAuthStartStageInput::SiteConfig(site_config_stage)
160 }
161 };
162 let start_stage_plan = plan_broker_auth_start_stage(start_stage_input, web_tcp_addrs.len());
163 let start_stage = start_stage_plan.start_stage;
164 let start_stage_source = start_stage_plan.source.as_str();
165 let transport_plan = start_stage_plan.transport_plan;
166 if start_stage_plan.log_action == BrokerAuthStartStageLogAction::SelectedNonWebTcp {
167 tracing::debug!(
168 broker_id,
169 original_domain = cfg.auth_domain,
170 stage = ?start_stage,
171 source = start_stage_source,
172 "broker_auth starting from selected FTLogin stage"
173 );
174 } else if start_stage_plan.log_action
175 == BrokerAuthStartStageLogAction::WebTcpSelectedWithoutAddrs
176 {
177 tracing::warn!(
178 broker_id,
179 original_domain = cfg.auth_domain,
180 web_identity = web_tcp_identity,
181 source = start_stage_source,
182 "broker_auth WebTCP-short selected but no WebTCP addresses are loaded; falling back to HTTP domain"
183 );
184 }
185 execute_broker_auth_workflow(BrokerAuthWorkflowInput {
186 http,
187 client_type: protocol_identity.client_type(),
188 protocol_identity,
189 uid,
190 broker_id,
191 cfg,
192 domain_plan,
193 transport_plan,
194 start_stage,
195 start_stage_source,
196 web_tcp_identity,
197 web_tcp_addrs,
198 route_cache,
199 primary_url: &primary_url,
200 body: &body,
201 device_id,
202 })
203 .await
204}