Skip to main content

futu_backend/
system_profile.rs

1use bytes::Bytes;
2use prost::Message;
3
4use futu_command_spec::{BackendExtensionOperation, SystemReadOperation};
5use futu_core::error::{FutuError, Result};
6
7use crate::command_runtime::{execute_backend_extension, execute_system_read};
8use crate::conn::BackendConn;
9use crate::proto_internal::{account_flag, auth_query_user_agree_api, sig};
10
11pub struct AppJumpSigBatch {
12    pub invalid_time_secs: u32,
13    pub tickets: Vec<Vec<u8>>,
14}
15
16pub async fn query_agree_api_license(backend: &BackendConn, user_id: u64) -> Result<bool> {
17    let request = auth_query_user_agree_api::AuthQueryUserAgreeApiReq { uid: Some(user_id) };
18    let frame = execute_system_read(
19        backend,
20        SystemReadOperation::AgreeApiLicense,
21        Bytes::from(request.encode_to_vec()),
22    )
23    .await?;
24    let response = auth_query_user_agree_api::AuthQueryUserAgreeApiRsp::decode(frame.body.as_ref())
25        .map_err(|error| FutuError::Codec(format!("CMD5243 decode failed: {error}")))?;
26    match (response.ret_code, response.agree_api) {
27        (Some(0), Some(agreed)) => Ok(agreed != 0),
28        (Some(0), None) | (None, _) => Err(FutuError::Codec(
29            "CMD5243 response missing ret_code or agree_api".to_string(),
30        )),
31        (Some(code), _) => Err(FutuError::ServerError {
32            ret_type: code,
33            msg: response
34                .ret_msg
35                .unwrap_or_else(|| "CMD5243 agreement query rejected".to_string()),
36        }),
37    }
38}
39
40pub async fn query_broker_disclaimer_flag(backend: &BackendConn, user_id: u64) -> Result<bool> {
41    // Ref: NNBiz_Disclaimer.cpp:195-213. Flag 46 is a fixed protocol enum,
42    // not server-discovered configuration; upstream reassignment invalidates it.
43    const OPEN_API_DISCLAIMER_FLAG_ID: u32 = 46;
44    let request = account_flag::GetFlagReq {
45        uid: Some(user_id),
46        flag_id: Some(OPEN_API_DISCLAIMER_FLAG_ID),
47    };
48    let frame = execute_backend_extension(
49        backend,
50        BackendExtensionOperation::AccountFlag,
51        Bytes::from(request.encode_to_vec()),
52    )
53    .await?;
54    let response = account_flag::GetFlagRsp::decode(frame.body.as_ref())
55        .map_err(|error| FutuError::Codec(format!("CMD5281 decode failed: {error}")))?;
56    match response.ret_code {
57        Some(0) => response
58            .item
59            .and_then(|item| item.flag_value)
60            .map(|value| value != 0)
61            .ok_or_else(|| FutuError::Codec("CMD5281 response missing flag_value".to_string())),
62        Some(-40) => Ok(false),
63        Some(code) => Err(FutuError::ServerError {
64            ret_type: code,
65            msg: response
66                .ret_msg
67                .unwrap_or_else(|| "CMD5281 disclaimer flag query rejected".to_string()),
68        }),
69        None => Err(FutuError::Codec(
70            "CMD5281 response missing ret_code".to_string(),
71        )),
72    }
73}
74
75pub async fn fetch_app_jump_sig_batch(backend: &BackendConn) -> Result<AppJumpSigBatch> {
76    let request = sig::GetAppJumpSigRequest {
77        // Ref: NNBiz_AppJumpSig.cpp:29-31. The backend records but does not
78        // interpret this fixed acquisition-method marker.
79        dummy: Some("1".to_string()),
80    };
81    let frame = execute_system_read(
82        backend,
83        SystemReadOperation::AppJumpSig,
84        Bytes::from(request.encode_to_vec()),
85    )
86    .await?;
87    let response = sig::GetAppJumpSigResponse::decode(frame.body.as_ref())
88        .map_err(|error| FutuError::Codec(format!("CMD1009 decode failed: {error}")))?;
89    match response.ret_code {
90        Some(0) => {
91            let invalid_time_secs = response.invalid_time.ok_or_else(|| {
92                FutuError::Codec("CMD1009 success missing invalid_time".to_string())
93            })?;
94            Ok(AppJumpSigBatch {
95                invalid_time_secs,
96                tickets: response.app_jump_str,
97            })
98        }
99        Some(code) => Err(FutuError::ServerError {
100            ret_type: code,
101            msg: response
102                .ret_msg
103                .unwrap_or_else(|| "CMD1009 ticket refill rejected".to_string()),
104        }),
105        None => Err(FutuError::Codec(
106            "CMD1009 response missing ret_code".to_string(),
107        )),
108    }
109}