Skip to main content

futu_backend/valid_brokers/
store.rs

1use std::collections::BTreeSet;
2use std::path::{Path, PathBuf};
3
4use futu_core::error::{FutuError, Result};
5
6const VALID_BROKER_SNAPSHOT_SCHEMA_VERSION: u32 = 1;
7
8#[derive(Debug, serde::Serialize, serde::Deserialize)]
9struct ValidBrokerSnapshotFile {
10    schema_version: u32,
11    uid: u64,
12    broker_ids: Vec<u32>,
13}
14
15pub fn load_last_good_valid_broker_ids(uid: u64) -> Result<Option<Vec<u32>>> {
16    load_valid_broker_snapshot_from_path(&valid_broker_snapshot_path(uid)?, uid)
17}
18
19pub fn save_last_good_valid_broker_ids(uid: u64, broker_ids: &[u32]) -> Result<()> {
20    save_valid_broker_snapshot_to_path(&valid_broker_snapshot_path(uid)?, uid, broker_ids)
21}
22
23fn valid_broker_snapshot_path(uid: u64) -> Result<PathBuf> {
24    let dir = crate::auth::try_futu_opend_dir()
25        .map_err(|error| FutuError::Codec(format!("valid broker store dir: {error}")))?;
26    let uid_key = format!("{:x}", md5::compute(uid.to_string().as_bytes()));
27    Ok(dir.join(format!("valid-brokers-{}.json", &uid_key[..16])))
28}
29
30pub(super) fn load_valid_broker_snapshot_from_path(
31    path: &Path,
32    expected_uid: u64,
33) -> Result<Option<Vec<u32>>> {
34    let data = match std::fs::read(path) {
35        Ok(data) => data,
36        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
37        Err(error) => return Err(FutuError::Network(error)),
38    };
39    let snapshot: ValidBrokerSnapshotFile = serde_json::from_slice(&data)
40        .map_err(|error| FutuError::Codec(format!("valid broker store decode: {error}")))?;
41    if snapshot.schema_version != VALID_BROKER_SNAPSHOT_SCHEMA_VERSION {
42        return Err(FutuError::Codec(format!(
43            "valid broker store schema {} unsupported",
44            snapshot.schema_version
45        )));
46    }
47    if snapshot.uid != expected_uid {
48        return Err(FutuError::Codec(
49            "valid broker store uid mismatch".to_string(),
50        ));
51    }
52    Ok(Some(normalize_broker_ids(snapshot.broker_ids)))
53}
54
55pub(super) fn save_valid_broker_snapshot_to_path(
56    path: &Path,
57    uid: u64,
58    broker_ids: &[u32],
59) -> Result<()> {
60    let snapshot = ValidBrokerSnapshotFile {
61        schema_version: VALID_BROKER_SNAPSHOT_SCHEMA_VERSION,
62        uid,
63        broker_ids: normalize_broker_ids(broker_ids.iter().copied()),
64    };
65    let body = serde_json::to_vec_pretty(&snapshot)
66        .map_err(|error| FutuError::Codec(format!("valid broker store encode: {error}")))?;
67    crate::auth::write_secret_file(path, &body).map_err(FutuError::Network)
68}
69
70fn normalize_broker_ids<I>(broker_ids: I) -> Vec<u32>
71where
72    I: IntoIterator<Item = u32>,
73{
74    broker_ids
75        .into_iter()
76        .collect::<BTreeSet<_>>()
77        .into_iter()
78        .collect()
79}