1mod address_plan;
8
9pub use address_plan::{
10 CHANNEL_CONCURRENT_ADDRESS_LIMIT, CPP_CONCURRENCY_STAGE_DELAY, CPP_GUARANTEED_STAGE_DELAY,
11 CPP_PLATFORM_HARDCODED_BACKUP_PORT, CPP_PLATFORM_HARDCODED_PRIMARY_PORT, ChannelAddressAttempt,
12 ChannelAddressCandidate, ChannelAddressPlan, ChannelAddressPlanFacts,
13 channel_address_attempt_schedule_like_cpp, plan_channel_addresses_like_cpp,
14};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct ConnIpBackendReject {
18 result_code: i32,
19}
20
21impl ConnIpBackendReject {
22 pub const fn result_code(self) -> i32 {
23 self.result_code
24 }
25}
26
27pub fn ensure_conn_ip_backend_success(
28 result_code: Option<i32>,
29) -> std::result::Result<(), ConnIpBackendReject> {
30 match result_code {
31 Some(0) => Ok(()),
32 Some(result_code) => Err(ConnIpBackendReject { result_code }),
33 None => Err(ConnIpBackendReject { result_code: -1 }),
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct ConnIpAddressFacts {
39 pub ip: Option<String>,
40 pub port: Option<u32>,
41 pub region: Option<u32>,
42 pub simplified_description: Option<String>,
43 pub traditional_description: Option<String>,
44 pub english_description: Option<String>,
45 pub enable_backup_port: Option<bool>,
46 pub backup_port: Option<u32>,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct ConnIpAddress {
51 pub ip: String,
52 pub port: u16,
53 pub region: u32,
54 pub simplified_description: String,
55 pub traditional_description: String,
56 pub english_description: String,
57 pub enable_backup_port: bool,
58 pub backup_port: u16,
59 pub condition_flag: i32,
60}
61
62impl ConnIpAddress {
63 #[must_use]
64 pub fn endpoint(&self) -> String {
65 format!("{}:{}", self.ip, self.port)
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct ConnIpSnapshotFacts {
71 pub expected_conn_identity: u32,
72 pub response_conn_identity: Option<u32>,
73 pub addresses: Vec<ConnIpAddressFacts>,
74 pub anti_ddos_ip: Option<String>,
75 pub condition_flag: Option<i32>,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct ConnIpSnapshot {
80 pub conn_identity: u32,
81 pub addresses: Vec<ConnIpAddress>,
82 pub anti_ddos_ip: Option<String>,
83 pub condition_flag: i32,
84}
85
86pub const CPP_MAX_PERSISTED_CONN_IP_ADDRESSES: usize = 100;
87
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct ConnIpCatalog {
90 pub snapshot: ConnIpSnapshot,
91 pub previous_endpoint: Option<String>,
92}
93
94impl ConnIpCatalog {
95 #[must_use]
96 pub fn new(snapshot: ConnIpSnapshot) -> Self {
97 Self {
98 snapshot,
99 previous_endpoint: None,
100 }
101 }
102
103 pub fn replace_snapshot_like_cpp(&mut self, snapshot: ConnIpSnapshot) {
104 self.snapshot = snapshot;
105 }
106
107 pub fn record_success_like_cpp(&mut self, connected_endpoint: &str) {
108 let anti_ddos_endpoint = self
109 .snapshot
110 .anti_ddos_ip
111 .as_ref()
112 .map(|ip| format!("{ip}:443"));
113 if anti_ddos_endpoint.as_deref() == Some(connected_endpoint) {
114 self.previous_endpoint = None;
115 return;
116 }
117
118 let canonical = self.snapshot.addresses.iter().find_map(|address| {
119 let primary = address.endpoint();
120 let backup = address
121 .enable_backup_port
122 .then(|| format!("{}:{}", address.ip, address.backup_port));
123 (primary == connected_endpoint || backup.as_deref() == Some(connected_endpoint))
124 .then_some(primary)
125 });
126 self.previous_endpoint = Some(canonical.unwrap_or_else(|| connected_endpoint.to_string()));
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct RestoredConnIpCatalogFacts {
132 pub expected_conn_identity: u32,
133 pub snapshot: ConnIpSnapshot,
134 pub previous_endpoint: Option<String>,
135}
136
137#[must_use]
142pub fn restore_conn_ip_catalog_like_cpp(
143 facts: RestoredConnIpCatalogFacts,
144) -> Option<ConnIpCatalog> {
145 if facts.snapshot.conn_identity != facts.expected_conn_identity
146 || facts.snapshot.addresses.len() > CPP_MAX_PERSISTED_CONN_IP_ADDRESSES
147 {
148 return None;
149 }
150
151 let previous_endpoint = facts.previous_endpoint.and_then(|previous| {
152 facts
153 .snapshot
154 .addresses
155 .iter()
156 .any(|address| address.endpoint() == previous && address.enable_backup_port)
157 .then_some(previous)
158 });
159 Some(ConnIpCatalog {
160 snapshot: facts.snapshot,
161 previous_endpoint,
162 })
163}
164
165impl ConnIpSnapshot {
166 #[must_use]
167 pub fn primary_endpoints(&self) -> Vec<String> {
168 self.addresses.iter().map(ConnIpAddress::endpoint).collect()
169 }
170
171 #[must_use]
178 pub fn backup_endpoint_for(&self, primary_endpoint: &str) -> Option<String> {
179 self.addresses.iter().find_map(|address| {
180 (address.endpoint() == primary_endpoint
181 && address.enable_backup_port
182 && address.backup_port != 0
183 && address.backup_port != address.port)
184 .then(|| format!("{}:{}", address.ip, address.backup_port))
185 })
186 }
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub enum ConnIpSnapshotDecision {
191 Store(ConnIpSnapshot),
192 IgnoreIdentityMismatch { expected: u32, actual: u32 },
193 IgnoreEmptyCatalog,
194}
195
196#[must_use]
204pub fn plan_conn_ip_snapshot_like_cpp(facts: ConnIpSnapshotFacts) -> ConnIpSnapshotDecision {
205 if let Some(actual) = facts.response_conn_identity
206 && actual != facts.expected_conn_identity
207 {
208 return ConnIpSnapshotDecision::IgnoreIdentityMismatch {
209 expected: facts.expected_conn_identity,
210 actual,
211 };
212 }
213
214 let condition_flag = facts.condition_flag.unwrap_or(0);
215 let mut addresses: Vec<_> = facts
216 .addresses
217 .into_iter()
218 .filter_map(|address| project_conn_ip_address_like_cpp(address, condition_flag))
219 .collect();
220 addresses.sort_by(|left, right| left.english_description.cmp(&right.english_description));
221 if addresses.is_empty() {
222 return ConnIpSnapshotDecision::IgnoreEmptyCatalog;
223 }
224
225 ConnIpSnapshotDecision::Store(ConnIpSnapshot {
226 conn_identity: facts.expected_conn_identity,
227 addresses,
228 anti_ddos_ip: facts.anti_ddos_ip.filter(|ip| !ip.is_empty()),
229 condition_flag,
230 })
231}
232
233fn project_conn_ip_address_like_cpp(
234 facts: ConnIpAddressFacts,
235 condition_flag: i32,
236) -> Option<ConnIpAddress> {
237 Some(ConnIpAddress {
238 ip: facts.ip?,
239 port: facts.port.unwrap_or(443) as u16,
242 region: facts.region?,
243 simplified_description: facts.simplified_description?,
244 traditional_description: facts.traditional_description?,
245 english_description: facts.english_description?,
246 enable_backup_port: facts.enable_backup_port?,
247 backup_port: facts.backup_port? as u16,
248 condition_flag,
249 })
250}