futu_backend/
session_key_refresh.rs1use futu_command_spec::{CommandSpecId, command_spec};
2use futu_core::error::{FutuError, Result};
3use futu_domain_auth::{
4 SessionKeyRefreshChannelKind, SessionKeyRefreshPlan, SessionKeyRefreshReplyFacts,
5 SessionKeyRefreshWireKind, ensure_session_key_refresh_success_like_cpp,
6 plan_session_key_refresh_like_cpp,
7};
8use futu_proto_internal::f3clogin_ft_conn_session_key::{
9 RefreshSessionKeyReq, RefreshSessionKeyRsp,
10};
11use prost::Message;
12
13use crate::conn::BackendConn;
14use crate::connection_lifecycle_runtime::execute_connection_lifecycle;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct SessionKeyRefreshOutcome {
18 pub command_id: u16,
19 pub new_session_key_len: usize,
20}
21
22pub async fn refresh_session_key(
28 conn: &BackendConn,
29 channel: SessionKeyRefreshChannelKind,
30 client_key_len: usize,
31) -> Result<SessionKeyRefreshOutcome> {
32 let plan = plan_session_key_refresh_like_cpp(channel, client_key_len);
33 let spec = command_spec(plan.command_id).ok_or_else(|| {
34 FutuError::Codec(format!(
35 "session-key refresh command {} is not registered",
36 plan.command_id
37 ))
38 })?;
39 if !matches!(spec.id, CommandSpecId::SessionKeyRefresh(_)) {
40 return Err(FutuError::Codec(format!(
41 "command {} is not a session-key refresh identity",
42 plan.command_id
43 )));
44 }
45
46 let request_body = encode_request(plan);
47 let response = execute_connection_lifecycle(conn, spec.id, request_body).await?;
48 let facts = decode_response(plan, response.body.as_ref())?;
49 let new_session_key = ensure_session_key_refresh_success_like_cpp(facts)
50 .map_err(|error| FutuError::Codec(error.to_string()))?;
51 let new_session_key_len = new_session_key.len();
52 conn.set_session_key(new_session_key);
53
54 Ok(SessionKeyRefreshOutcome {
55 command_id: spec.cmd_id,
56 new_session_key_len,
57 })
58}
59
60fn encode_request(plan: SessionKeyRefreshPlan) -> Vec<u8> {
61 match plan.wire {
62 SessionKeyRefreshWireKind::LegacyBinary => Vec::new(),
63 SessionKeyRefreshWireKind::Protobuf => RefreshSessionKeyReq {}.encode_to_vec(),
64 }
65}
66
67fn decode_response(
68 plan: SessionKeyRefreshPlan,
69 body: &[u8],
70) -> Result<SessionKeyRefreshReplyFacts> {
71 match plan.wire {
72 SessionKeyRefreshWireKind::LegacyBinary => decode_legacy_response(body),
73 SessionKeyRefreshWireKind::Protobuf => {
74 let response = RefreshSessionKeyRsp::decode(body).map_err(|error| {
75 FutuError::Codec(format!("decode session-key refresh PB: {error}"))
76 })?;
77 Ok(SessionKeyRefreshReplyFacts {
78 result_code: response.result_code,
79 new_session_key: response.new_session_key,
80 })
81 }
82 }
83}
84
85fn decode_legacy_response(body: &[u8]) -> Result<SessionKeyRefreshReplyFacts> {
86 let Some(&result_code) = body.first() else {
87 return Err(FutuError::Codec(
88 "session-key refresh legacy response is empty".into(),
89 ));
90 };
91 let result_code = i8::from_ne_bytes([result_code]) as i32;
92 let new_session_key = if result_code == 0 {
93 let mut key = vec![0_u8; 16];
94 if body.len() >= 17 {
100 key.copy_from_slice(&body[1..17]);
101 }
102 Some(key)
103 } else {
104 None
105 };
106 Ok(SessionKeyRefreshReplyFacts {
107 result_code: Some(result_code),
108 new_session_key,
109 })
110}
111
112#[cfg(test)]
113mod tests;