1use futu_core::error::Result;
7
8use crate::auth::UserAttribution;
9use crate::conn::BackendConn;
10use crate::proto_internal::ft_conn_ip;
11
12pub const CMD_CONN_IP: u16 = CMD_CONN_IP_PLATFORM;
14
15pub const CMD_CONN_IP_PLATFORM: u16 = 1321;
17
18pub const CMD_CONN_IP_BROKER: u16 = 20147;
23
24#[derive(Debug, Clone)]
26pub struct ConnPoint {
27 pub ip: String,
28 pub port: u16,
29 pub region: u32,
30 pub desc: String,
31 pub backup_port: Option<u16>,
32}
33
34pub async fn fetch_conn_ip_list(
43 backend: &BackendConn,
44 user_id: u64,
45 device_id: &[u8],
46 attribution: UserAttribution,
47 client_ip: &str,
48) -> Result<Vec<ConnPoint>> {
49 use prost::Message;
50 let conn_identity = attribution.to_conn_identity();
51 let req = build_conn_ip_req(user_id, device_id, conn_identity, Some(client_ip));
52
53 let body = req.encode_to_vec();
54 tracing::info!(
55 user_id,
56 ?attribution,
57 conn_identity,
58 client_ip_present = !client_ip.is_empty(),
59 body_len = body.len(),
60 "sending CMD1321 ConnIpReq"
61 );
62
63 let resp = backend.request(CMD_CONN_IP_PLATFORM, body).await?;
64 parse_conn_ip_rsp(
65 CMD_CONN_IP_PLATFORM,
66 resp.body.as_ref(),
67 Some(conn_identity),
68 )
69}
70
71pub async fn send_broker_conn_ip_update(
79 backend: &BackendConn,
80 customer_id: u64,
81 device_id: &[u8],
82 conn_identity: u32,
83 client_ip: &str,
84) -> Result<Vec<ConnPoint>> {
85 use prost::Message;
86
87 let req = build_conn_ip_req(customer_id, device_id, conn_identity, Some(client_ip));
88 let body = req.encode_to_vec();
89 tracing::info!(
90 customer_id,
91 conn_identity,
92 client_ip_present = !client_ip.is_empty(),
93 body_len = body.len(),
94 "sending CMD20147 broker ConnIpReq"
95 );
96
97 let resp = backend.request(CMD_CONN_IP_BROKER, body).await?;
98 parse_conn_ip_rsp(CMD_CONN_IP_BROKER, resp.body.as_ref(), Some(conn_identity))
99}
100
101fn build_conn_ip_req(
102 user_id: u64,
103 device_id: &[u8],
104 conn_identity: u32,
105 client_ip: Option<&str>,
106) -> ft_conn_ip::ConnIpReq {
107 ft_conn_ip::ConnIpReq {
108 device_id: Some(device_id.to_vec()),
109 user_id: Some(user_id),
110 net_type: Some(3), conn_identity: Some(conn_identity),
112 client_feature: Some(ft_conn_ip::ClientFeature {
113 device_model: Some("pc".to_string()),
114 net_type: Some("wire".to_string()),
115 carrier: None,
116 client_ip: client_ip.map(|ip| ip.to_string()),
120 }),
121 }
122}
123
124fn parse_conn_ip_rsp(
125 cmd_id: u16,
126 body: &[u8],
127 expected_conn_identity: Option<u32>,
128) -> Result<Vec<ConnPoint>> {
129 use prost::Message;
130
131 let rsp: ft_conn_ip::ConnIpRsp = Message::decode(body)?;
132
133 if rsp.result_code.unwrap_or(-1) != 0 {
134 tracing::warn!(
135 cmd_id,
136 result_code = rsp.result_code.unwrap_or(-1),
137 err_msg = ?rsp.err_msg,
138 "ConnIpRsp error"
139 );
140 return Ok(vec![]);
141 }
142 if let (Some(expected), Some(actual)) = (expected_conn_identity, rsp.conn_identity)
143 && expected != actual
144 {
145 tracing::warn!(cmd_id, expected, actual, "ConnIpRsp conn_identity mismatch");
146 return Ok(vec![]);
147 }
148
149 let mut points: Vec<ConnPoint> = rsp
150 .ip_list
151 .iter()
152 .filter_map(conn_point_from_item)
153 .collect();
154 points.sort_by(|a, b| a.desc.cmp(&b.desc));
155
156 tracing::info!(
157 cmd_id,
158 count = points.len(),
159 anti_ddos = ?rsp.anti_ddos_ip,
160 "ConnIpRsp: got {} IPs",
161 points.len()
162 );
163 for (i, p) in points.iter().enumerate() {
164 tracing::info!(
165 idx = i,
166 ip = %p.ip,
167 port = p.port,
168 region = p.region,
169 desc = %p.desc,
170 " ConnIP[{}]",
171 i
172 );
173 }
174
175 Ok(points)
176}
177
178fn conn_point_from_item(item: &ft_conn_ip::ConnIpItem) -> Option<ConnPoint> {
179 let ip = item.ip.clone()?;
184 item.sc_desc.as_ref()?;
185 item.tc_desc.as_ref()?;
186 let region = item.region?;
187 let desc = item.en_desc.clone()?;
188 let enable_backup_port = item.enable_backup_port?;
189 let backup_port_raw = item.backup_port?;
190
191 Some(ConnPoint {
192 ip,
193 port: item.port.unwrap_or(443) as u16,
194 region,
195 desc,
196 backup_port: enable_backup_port.then_some(backup_port_raw as u16),
197 })
198}
199
200#[cfg(test)]
201mod tests;