1use std::collections::{BTreeMap, BTreeSet};
2use std::sync::atomic::Ordering;
3
4use dashmap::mapref::entry::Entry;
5use futu_core::trade_broker::broker_id_for_security_firm_like_cpp;
6
7use super::{AccKey, TrdCache};
8
9#[derive(Clone, Copy, PartialEq, Eq)]
10pub(super) enum CipherFreshness {
11 Unbound,
12 Pending {
13 broker_id: u32,
14 connection_epoch: u64,
15 request_id: u64,
16 },
17 Fresh {
18 broker_id: u32,
19 connection_epoch: u64,
20 },
21 Failed {
22 broker_id: u32,
23 connection_epoch: u64,
24 },
25}
26
27pub(super) struct CipherRecord {
29 revision: u64,
30 cipher: Option<Vec<u8>>,
31 freshness: CipherFreshness,
32}
33
34pub struct CipherExchangeAccount {
37 account_id: AccKey,
38 revision: u64,
39 trade_cipher: Vec<u8>,
40}
41
42impl CipherExchangeAccount {
43 #[must_use]
44 pub const fn account_id(&self) -> AccKey {
45 self.account_id
46 }
47
48 #[must_use]
49 pub const fn revision(&self) -> u64 {
50 self.revision
51 }
52
53 #[must_use]
54 pub fn trade_cipher(&self) -> &[u8] {
55 &self.trade_cipher
56 }
57}
58
59pub struct CipherExchangeLease {
63 broker_id: u32,
64 connection_epoch: u64,
65 request_id: u64,
66 broker_generation: u64,
67 accounts: Vec<CipherExchangeAccount>,
68}
69
70impl CipherExchangeLease {
71 #[must_use]
72 pub const fn broker_id(&self) -> u32 {
73 self.broker_id
74 }
75
76 #[must_use]
77 pub const fn connection_epoch(&self) -> u64 {
78 self.connection_epoch
79 }
80
81 #[must_use]
82 pub const fn request_id(&self) -> u64 {
83 self.request_id
84 }
85
86 #[must_use]
87 pub const fn broker_generation(&self) -> u64 {
88 self.broker_generation
89 }
90
91 #[must_use]
92 pub fn accounts(&self) -> &[CipherExchangeAccount] {
93 &self.accounts
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum CipherExchangeError {
99 DuplicateAccount,
100 EmptyCipher,
101 AccountSetMismatch,
102 LeaseStale,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub struct CipherExchangePublishReport {
107 published: usize,
108 stale: usize,
109 broker_generation: u64,
110}
111
112impl CipherExchangePublishReport {
113 #[must_use]
114 pub const fn published(self) -> usize {
115 self.published
116 }
117
118 #[must_use]
119 pub const fn stale(self) -> usize {
120 self.stale
121 }
122
123 #[must_use]
124 pub const fn broker_generation(self) -> u64 {
125 self.broker_generation
126 }
127}
128
129impl TrdCache {
130 #[must_use]
131 pub fn get_cipher(&self, acc_id: AccKey) -> Option<Vec<u8>> {
132 self.cipher_records
133 .get(&acc_id)
134 .and_then(|record| record.cipher.clone())
135 }
136
137 pub fn set_cipher(&self, acc_id: AccKey, cipher: Vec<u8>) {
140 let broker_id = self.broker_id_for_cipher_account(acc_id);
141 let _publication = self.cipher_publication_lock.lock();
142 match self.cipher_records.entry(acc_id) {
143 Entry::Occupied(mut entry) => {
144 let record = entry.get_mut();
145 record.revision = record.revision.saturating_add(1);
146 record.cipher = Some(cipher);
147 record.freshness = CipherFreshness::Unbound;
148 }
149 Entry::Vacant(entry) => {
150 entry.insert(CipherRecord {
151 revision: 1,
152 cipher: Some(cipher),
153 freshness: CipherFreshness::Unbound,
154 });
155 }
156 }
157 if let Some(broker_id) = broker_id {
158 self.bump_broker_cipher_generation_locked(broker_id);
159 }
160 }
161
162 #[must_use]
163 pub fn get_cipher_revision(&self, acc_id: AccKey) -> u64 {
164 self.cipher_records
165 .get(&acc_id)
166 .map_or(0, |record| record.revision)
167 }
168
169 #[must_use]
170 pub fn cipher_count(&self) -> usize {
171 self.cipher_records
172 .iter()
173 .filter(|record| record.cipher.is_some())
174 .count()
175 }
176
177 #[must_use]
179 pub fn clear_cipher(&self, acc_id: AccKey) -> bool {
180 let broker_id = self.broker_id_for_cipher_account(acc_id);
181 let _publication = self.cipher_publication_lock.lock();
182 let Some(mut record) = self.cipher_records.get_mut(&acc_id) else {
183 return false;
184 };
185 if record.cipher.take().is_none() {
186 return false;
187 }
188 record.revision = record.revision.saturating_add(1);
189 record.freshness = CipherFreshness::Failed {
190 broker_id: 0,
191 connection_epoch: 0,
192 };
193 if let Some(broker_id) = broker_id {
194 self.bump_broker_cipher_generation_locked(broker_id);
195 }
196 true
197 }
198
199 #[must_use]
200 pub fn clear_all_ciphers_and_bump_versions(&self) -> (usize, Vec<(u64, u64)>) {
201 let acc_ids: Vec<_> = self
202 .cipher_records
203 .iter()
204 .filter_map(|record| record.cipher.as_ref().map(|_| *record.key()))
205 .collect();
206 let mut bumped = Vec::with_capacity(acc_ids.len());
207 for acc_id in acc_ids {
208 if self.clear_cipher(acc_id) {
209 bumped.push((acc_id, self.bump_cipher_state_version(acc_id)));
210 }
211 }
212 (bumped.len(), bumped)
213 }
214
215 #[must_use]
219 pub fn begin_cipher_exchange(
220 &self,
221 broker_id: u32,
222 connection_epoch: u64,
223 ) -> CipherExchangeLease {
224 let _publication = self.cipher_publication_lock.lock();
225 let request_id = self
226 .next_cipher_exchange_request_id
227 .fetch_add(1, Ordering::SeqCst)
228 .wrapping_add(1);
229 let mut account_ids: Vec<_> = self
230 .accounts
231 .iter()
232 .filter(|account| account.trd_env == 1)
233 .filter(|account| {
234 account
235 .security_firm
236 .and_then(broker_id_for_security_firm_like_cpp)
237 == Some(broker_id)
238 })
239 .map(|account| *account.key())
240 .collect();
241 account_ids.sort_unstable();
242
243 let mut accounts = Vec::new();
244 for account_id in account_ids {
245 let Some(mut record) = self.cipher_records.get_mut(&account_id) else {
246 continue;
247 };
248 let Some(cipher) = record
249 .cipher
250 .as_ref()
251 .filter(|cipher| !cipher.is_empty())
252 .cloned()
253 else {
254 continue;
255 };
256 let revision = record.revision;
257 record.freshness = CipherFreshness::Pending {
258 broker_id,
259 connection_epoch,
260 request_id,
261 };
262 accounts.push(CipherExchangeAccount {
263 account_id,
264 revision,
265 trade_cipher: cipher,
266 });
267 }
268
269 CipherExchangeLease {
270 broker_id,
271 connection_epoch,
272 request_id,
273 broker_generation: self.broker_cipher_generation_locked(broker_id),
274 accounts,
275 }
276 }
277
278 pub fn complete_cipher_exchange(
282 &self,
283 lease: &CipherExchangeLease,
284 response: Vec<(AccKey, Vec<u8>)>,
285 ) -> Result<CipherExchangePublishReport, CipherExchangeError> {
286 let expected: BTreeSet<_> = lease.accounts.iter().map(|row| row.account_id).collect();
287 let mut response_by_account = BTreeMap::new();
288 for (account_id, cipher) in response {
289 if cipher.is_empty() {
290 self.fail_cipher_exchange(lease);
291 return Err(CipherExchangeError::EmptyCipher);
292 }
293 if response_by_account.insert(account_id, cipher).is_some() {
294 self.fail_cipher_exchange(lease);
295 return Err(CipherExchangeError::DuplicateAccount);
296 }
297 }
298 let actual: BTreeSet<_> = response_by_account.keys().copied().collect();
299 if actual != expected {
300 self.fail_cipher_exchange(lease);
301 return Err(CipherExchangeError::AccountSetMismatch);
302 }
303
304 let _publication = self.cipher_publication_lock.lock();
305 if self.broker_cipher_generation_locked(lease.broker_id) != lease.broker_generation {
306 self.fail_cipher_exchange_locked(lease);
307 return Err(CipherExchangeError::LeaseStale);
308 }
309 let mut published = 0;
310 let mut stale = 0;
311 for requested in &lease.accounts {
312 let Some(mut record) = self.cipher_records.get_mut(&requested.account_id) else {
313 stale += 1;
314 continue;
315 };
316 let owns_lease = record.revision == requested.revision
317 && record.freshness
318 == (CipherFreshness::Pending {
319 broker_id: lease.broker_id,
320 connection_epoch: lease.connection_epoch,
321 request_id: lease.request_id,
322 });
323 if !owns_lease {
324 stale += 1;
325 continue;
326 }
327 record.revision = record.revision.saturating_add(1);
328 record.cipher = response_by_account.remove(&requested.account_id);
329 record.freshness = CipherFreshness::Fresh {
330 broker_id: lease.broker_id,
331 connection_epoch: lease.connection_epoch,
332 };
333 published += 1;
334 self.bump_broker_cipher_generation_locked(lease.broker_id);
335 }
336 Ok(CipherExchangePublishReport {
337 published,
338 stale,
339 broker_generation: self.broker_cipher_generation_locked(lease.broker_id),
340 })
341 }
342
343 pub fn fail_cipher_exchange(&self, lease: &CipherExchangeLease) {
346 let _publication = self.cipher_publication_lock.lock();
347 self.fail_cipher_exchange_locked(lease);
348 }
349
350 fn fail_cipher_exchange_locked(&self, lease: &CipherExchangeLease) {
351 for requested in &lease.accounts {
352 let Some(mut record) = self.cipher_records.get_mut(&requested.account_id) else {
353 continue;
354 };
355 if record.revision == requested.revision
356 && record.freshness
357 == (CipherFreshness::Pending {
358 broker_id: lease.broker_id,
359 connection_epoch: lease.connection_epoch,
360 request_id: lease.request_id,
361 })
362 {
363 record.freshness = CipherFreshness::Failed {
364 broker_id: lease.broker_id,
365 connection_epoch: lease.connection_epoch,
366 };
367 }
368 }
369 }
370
371 pub fn with_current_broker_cipher_generation<T>(
375 &self,
376 broker_id: u32,
377 expected_generation: u64,
378 publish: impl FnOnce() -> T,
379 ) -> Option<T> {
380 let _publication = self.cipher_publication_lock.lock();
381 (self.broker_cipher_generation_locked(broker_id) == expected_generation).then(publish)
382 }
383
384 #[must_use]
388 pub fn get_cipher_for_broker_epoch(
389 &self,
390 acc_id: AccKey,
391 broker_id: u32,
392 connection_epoch: u64,
393 ) -> Option<Vec<u8>> {
394 let account = self.accounts.get(&acc_id)?;
395 if account.trd_env != 1
396 || account
397 .security_firm
398 .and_then(broker_id_for_security_firm_like_cpp)
399 != Some(broker_id)
400 {
401 return None;
402 }
403 let record = self.cipher_records.get(&acc_id)?;
404 let usable = match record.freshness {
405 CipherFreshness::Unbound => true,
406 CipherFreshness::Fresh {
407 broker_id: fresh_broker,
408 connection_epoch: fresh_epoch,
409 } => fresh_broker == broker_id && fresh_epoch == connection_epoch,
410 CipherFreshness::Pending { .. } | CipherFreshness::Failed { .. } => false,
411 };
412 usable.then(|| record.cipher.clone()).flatten()
413 }
414
415 fn broker_id_for_cipher_account(&self, acc_id: AccKey) -> Option<u32> {
416 let account = self.accounts.get(&acc_id)?;
417 (account.trd_env == 1)
418 .then(|| {
419 account
420 .security_firm
421 .and_then(broker_id_for_security_firm_like_cpp)
422 })
423 .flatten()
424 }
425
426 fn broker_cipher_generation_locked(&self, broker_id: u32) -> u64 {
427 self.cipher_broker_generations
428 .get(&broker_id)
429 .map_or(0, |generation| *generation.value())
430 }
431
432 fn bump_broker_cipher_generation_locked(&self, broker_id: u32) -> u64 {
433 let mut generation = self.cipher_broker_generations.entry(broker_id).or_insert(0);
434 *generation = generation.saturating_add(1);
435 *generation
436 }
437}