Skip to main content

futu_backend/auth/commconfig/
fetch.rs

1//! auth/commconfig/fetch — fetch_all 主入口 + SharedCommConfig + empty_snapshot + spawn_refresher
2//! (v1.4.110 CC Batch M: 拆自 commconfig.rs L666-869 + 1050-1121)
3
4use std::collections::HashMap;
5use std::sync::Arc;
6
7use super::parsers::{
8    fetch_page, parse_auth_guaranteed_domain_list, parse_forced_ip, parse_guaranteed_ip,
9    parse_web_tcp_config_identity, value_kind,
10};
11use super::types::{
12    AuthGuaranteedDomainMap, CommonConfigSnapshot, ForcedIpMap, GuaranteedBrokerIpMap,
13    GuaranteedIpMap, GuaranteedWebIpMap,
14};
15use arc_swap::ArcSwap;
16
17pub async fn fetch_all(
18    http: &reqwest::Client,
19    client_type: u8,
20    device_id: &str,
21    user_id: u64,
22    svr_time_offset: i64,
23) -> CommonConfigSnapshot {
24    let mut guaranteed_ip: GuaranteedIpMap = HashMap::new();
25    let mut guaranteed_ip_broker: GuaranteedBrokerIpMap = HashMap::new();
26    let mut guaranteed_ip_web: GuaranteedWebIpMap = HashMap::new();
27    let mut web_conn_identity: Option<u32> = None;
28    let mut auth_guaranteed_domains: AuthGuaranteedDomainMap = HashMap::new();
29    let mut auth_guaranteed_domains_configured = false;
30    let mut forced_ip: ForcedIpMap = HashMap::new();
31    let mut next_refresh_ts: i64 = 0;
32    let mut begin_id: i32 = -1; // C++ 首次 `OMLParam param(-1)`
33    let max_pages = 20;
34
35    for page in 0..max_pages {
36        let json = match fetch_page(
37            http,
38            client_type,
39            device_id,
40            user_id,
41            begin_id,
42            svr_time_offset,
43        )
44        .await
45        {
46            Ok(j) => j,
47            Err(e) => {
48                tracing::warn!(error = %e, page, "commconfig: fetch page failed, giving up");
49                break;
50            }
51        };
52
53        // C++ NNBiz_CommonConfig.cpp:105 无 ret_code 直接丢
54        let ret_code = json.get("ret_code").and_then(|v| v.as_i64());
55        if ret_code != Some(0) {
56            tracing::warn!(
57                ret_code = ?ret_code,
58                msg = ?json.get("ret_msg").and_then(|v| v.as_str()),
59                "commconfig: non-zero ret_code, abort"
60            );
61            break;
62        }
63
64        let data = match json.get("data").and_then(|v| v.as_object()) {
65            Some(d) => d,
66            None => {
67                tracing::warn!("commconfig: response missing `data` object");
68                break;
69            }
70        };
71
72        // conf_info.<name> → value(形态可能是 string / array / null,
73        // parse_guaranteed_ip 三态都能处理)
74        if let Some(conf_info) = data.get("conf_info").and_then(|v| v.as_object()) {
75            if let Some(val) = conf_info.get("guaranteed_ip_for_conn") {
76                tracing::debug!(
77                    kind = value_kind(val),
78                    "commconfig: guaranteed_ip_for_conn received"
79                );
80                let (platform_map, broker_map, web_map) = parse_guaranteed_ip(val);
81                // Platform 跨页去重合并
82                for (attr, pool) in platform_map {
83                    let slot = guaranteed_ip.entry(attr).or_default();
84                    for ip in pool {
85                        if !slot.contains(&ip) {
86                            slot.push(ip);
87                        }
88                    }
89                }
90                // Broker 跨页去重合并
91                for (broker_id, pool) in broker_map {
92                    let slot = guaranteed_ip_broker.entry(broker_id).or_default();
93                    for ip in pool {
94                        if !slot.contains(&ip) {
95                            slot.push(ip);
96                        }
97                    }
98                }
99                // WebTCP-short 跨页去重合并
100                for (identity, pool) in web_map {
101                    let slot = guaranteed_ip_web.entry(identity).or_default();
102                    for ip in pool {
103                        if !slot.contains(&ip) {
104                            slot.push(ip);
105                        }
106                    }
107                }
108            }
109
110            if let Some(val) = conf_info.get("web_tcp_config") {
111                tracing::debug!(
112                    kind = value_kind(val),
113                    "commconfig: web_tcp_config received"
114                );
115                if let Some(identity) = parse_web_tcp_config_identity(val) {
116                    web_conn_identity = Some(identity);
117                    tracing::info!(
118                        identity,
119                        "commconfig: web_tcp_config.web_conn_identity loaded"
120                    );
121                }
122            }
123
124            if let Some(val) = conf_info.get("auth_guaranteed_domain_list") {
125                tracing::debug!(
126                    kind = value_kind(val),
127                    "commconfig: auth_guaranteed_domain_list received"
128                );
129                let (domains, configured) = parse_auth_guaranteed_domain_list(val);
130                auth_guaranteed_domains_configured |= configured;
131                for (domain, retry_domain) in domains {
132                    auth_guaranteed_domains.insert(domain, retry_domain);
133                }
134            }
135
136            // v1.4.22:forced_ip_for_conn —— 最高优先级 IP,带过期时间
137            // 对齐 C++ `address.cpp:360-400` ParseForcedIpConfig
138            if let Some(val) = conf_info.get("forced_ip_for_conn") {
139                tracing::debug!(
140                    kind = value_kind(val),
141                    "commconfig: forced_ip_for_conn received"
142                );
143                let page_map = parse_forced_ip(val);
144                for (attr, entry) in page_map {
145                    // 跨页取 expire 更晚的那条(更新鲜)
146                    let existing = forced_ip.get(&attr);
147                    if existing.is_none_or(|e| e.expire_ts < entry.expire_ts) {
148                        forced_ip.insert(attr, entry);
149                    }
150                }
151            }
152        }
153
154        let control = data.get("config_control").and_then(|v| v.as_object());
155        if let Some(c) = control
156            && let Some(limit) = c.get("limit_time").and_then(|v| v.as_i64())
157        {
158            let now = chrono::Utc::now().timestamp();
159            next_refresh_ts = now + limit;
160        }
161
162        // 分页判断——和 C++ `NNBiz_CommonConfig.cpp:153-160` 对齐
163        let has_more = control
164            .and_then(|c| c.get("has_more"))
165            .and_then(|v| v.as_bool())
166            .unwrap_or(false);
167        if !has_more {
168            break;
169        }
170        let next_id = control
171            .and_then(|c| c.get("next_id"))
172            .and_then(|v| v.as_i64())
173            .unwrap_or(0);
174        begin_id = next_id as i32;
175    }
176
177    tracing::info!(
178        platform_pools = guaranteed_ip.len(),
179        broker_pools = guaranteed_ip_broker.len(),
180        web_pools = guaranteed_ip_web.len(),
181        web_conn_identity,
182        auth_retry_domains = auth_guaranteed_domains.len(),
183        auth_retry_domain_configured = auth_guaranteed_domains_configured,
184        forced_count = forced_ip.len(),
185        "commconfig: fetched"
186    );
187    CommonConfigSnapshot {
188        guaranteed_ip,
189        guaranteed_ip_broker,
190        guaranteed_ip_web,
191        web_conn_identity,
192        auth_guaranteed_domains,
193        auth_guaranteed_domains_configured,
194        forced_ip,
195        next_refresh_ts,
196    }
197}
198
199/// 一个 `ArcSwap` 缓存槽——Bridge 持有,重连/后台刷新器共享。
200/// `load()` 无锁读,`store(Arc::new(new))` 原子替换。
201pub type SharedCommConfig = Arc<ArcSwap<CommonConfigSnapshot>>;
202
203/// 生成一个空 snapshot —— 用于 `SharedCommConfig` 初始化 / fallback。
204pub fn empty_snapshot() -> CommonConfigSnapshot {
205    CommonConfigSnapshot {
206        guaranteed_ip: HashMap::new(),
207        guaranteed_ip_broker: HashMap::new(),
208        guaranteed_ip_web: HashMap::new(),
209        web_conn_identity: None,
210        auth_guaranteed_domains: HashMap::new(),
211        auth_guaranteed_domains_configured: false,
212        forced_ip: HashMap::new(),
213        next_refresh_ts: 0,
214    }
215}