1use std::sync::Arc;
4
5use async_trait::async_trait;
6
7use futu_cache::login_cache::LoginCache;
8use futu_cache::qot_right::QotRightCache;
9use futu_core::proto_id;
10use futu_server::conn::IncomingRequest;
11use futu_server::router::{RequestHandler, RequestRouter};
12
13use crate::bridge::GatewayBridge;
14
15pub fn register_handlers(router: &Arc<RequestRouter>, bridge: &GatewayBridge) {
17 router.register(
18 proto_id::GET_USER_INFO,
19 Arc::new(GetUserInfoHandler {
20 login_cache: Arc::clone(&bridge.login_cache),
21 qot_right_cache: Arc::clone(&bridge.qot_right_cache),
22 }),
23 );
24 router.register(
25 proto_id::GET_DELAY_STATISTICS,
26 Arc::new(GetDelayStatisticsHandler),
27 );
28 router.register(proto_id::VERIFICATION, Arc::new(VerificationHandler));
29 router.register(proto_id::TEST_CMD, Arc::new(TestCmdHandler));
30 router.register(proto_id::REMOTE_CMD, Arc::new(RemoteCmdHandler));
31
32 tracing::debug!("system handlers registered");
33}
34
35struct GetUserInfoHandler {
39 login_cache: Arc<LoginCache>,
40 qot_right_cache: Arc<QotRightCache>,
41}
42
43#[async_trait]
44impl RequestHandler for GetUserInfoHandler {
45 async fn handle(&self, _conn_id: u64, request: &IncomingRequest) -> Option<Vec<u8>> {
46 let _req: futu_proto::get_user_info::Request =
47 prost::Message::decode(request.body.as_ref()).ok()?;
48
49 let login_state = self.login_cache.get_login_state();
50 let user_id = login_state.as_ref().map(|s| s.user_id as i64).unwrap_or(0);
51 let nick_name = login_state
52 .as_ref()
53 .map(|s| s.login_account.clone())
54 .unwrap_or_default();
55
56 let qr = self.qot_right_cache.get();
58
59 let s2c = futu_proto::get_user_info::S2c {
60 nick_name: Some(nick_name),
61 avatar_url: None,
62 api_level: None,
63 hk_qot_right: Some(qr.hk_qot_right),
64 us_qot_right: Some(qr.us_qot_right),
65 cn_qot_right: Some(qr.sh_qot_right), is_need_agree_disclaimer: Some(false),
67 user_id: Some(user_id),
68 update_type: Some(0), web_key: None,
70 web_jump_url_head: None,
71 hk_option_qot_right: Some(qr.hk_option_qot_right),
72 has_us_option_qot_right: Some(qr.has_us_option_qot_right),
73 hk_future_qot_right: Some(qr.hk_future_qot_right),
74 sub_quota: Some(qr.sub_quota),
75 history_kl_quota: Some(qr.history_kl_quota),
76 us_future_qot_right: Some(qr.us_cme_future_qot_right), us_option_qot_right: Some(qr.us_option_qot_right),
78 user_attribution: None,
79 update_whats_new: None,
80 us_index_qot_right: Some(qr.us_index_qot_right),
81 us_otc_qot_right: Some(qr.us_otc_qot_right),
82 us_cme_future_qot_right: Some(qr.us_cme_future_qot_right),
83 us_cbot_future_qot_right: Some(qr.us_cbot_future_qot_right),
84 us_nymex_future_qot_right: Some(qr.us_nymex_future_qot_right),
85 us_comex_future_qot_right: Some(qr.us_comex_future_qot_right),
86 us_cboe_future_qot_right: Some(qr.us_cboe_future_qot_right),
87 sg_future_qot_right: Some(qr.sg_future_qot_right),
88 jp_future_qot_right: Some(qr.jp_future_qot_right),
89 is_app_nn_or_mm: Some(true), sh_qot_right: Some(qr.sh_qot_right),
91 sz_qot_right: Some(qr.sz_qot_right),
92 extra: None,
93 };
94
95 let resp = futu_proto::get_user_info::Response {
96 ret_type: 0,
97 ret_msg: None,
98 err_code: None,
99 s2c: Some(s2c),
100 };
101 Some(prost::Message::encode_to_vec(&resp))
102 }
103}
104
105struct GetDelayStatisticsHandler;
110
111#[async_trait]
112impl RequestHandler for GetDelayStatisticsHandler {
113 async fn handle(&self, _conn_id: u64, request: &IncomingRequest) -> Option<Vec<u8>> {
114 let _req: futu_proto::get_delay_statistics::Request =
115 prost::Message::decode(request.body.as_ref()).ok()?;
116
117 let s2c = futu_proto::get_delay_statistics::S2c {
119 qot_push_statistics_list: vec![],
120 req_reply_statistics_list: vec![],
121 place_order_statistics_list: vec![],
122 };
123
124 let resp = futu_proto::get_delay_statistics::Response {
125 ret_type: 0,
126 ret_msg: None,
127 err_code: None,
128 s2c: Some(s2c),
129 };
130 Some(prost::Message::encode_to_vec(&resp))
131 }
132}
133
134struct TestCmdHandler;
139
140#[async_trait]
141impl RequestHandler for TestCmdHandler {
142 async fn handle(&self, conn_id: u64, request: &IncomingRequest) -> Option<Vec<u8>> {
143 let req: futu_proto::test_cmd::Request =
144 prost::Message::decode(request.body.as_ref()).ok()?;
145 let c2s = &req.c2s;
146 let cmd = &c2s.cmd;
147
148 tracing::info!(conn_id, cmd = %cmd, "TestCmd received");
149
150 match cmd.as_str() {
151 "exit" => {
152 let resp = futu_proto::test_cmd::Response {
154 ret_type: 0,
155 ret_msg: None,
156 err_code: None,
157 s2c: Some(futu_proto::test_cmd::S2c {
158 cmd: cmd.clone(),
159 result_str: None,
160 result_bytes: None,
161 }),
162 };
163 let body = prost::Message::encode_to_vec(&resp);
164
165 tokio::spawn(async {
167 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
168 tracing::info!("TestCmd: exit requested, shutting down");
169 std::process::exit(0);
170 });
171
172 Some(body)
173 }
174 "request_highest_quote_right" => {
175 tracing::info!("TestCmd: request_highest_quote_right (no-op in Rust gateway)");
176 let resp = futu_proto::test_cmd::Response {
177 ret_type: 0,
178 ret_msg: None,
179 err_code: None,
180 s2c: Some(futu_proto::test_cmd::S2c {
181 cmd: cmd.clone(),
182 result_str: None,
183 result_bytes: None,
184 }),
185 };
186 Some(prost::Message::encode_to_vec(&resp))
187 }
188 _ => {
189 tracing::warn!(conn_id, cmd = %cmd, "TestCmd: unknown command");
190 let resp = futu_proto::test_cmd::Response {
191 ret_type: -1,
192 ret_msg: Some("cmd not found".to_string()),
193 err_code: None,
194 s2c: None,
195 };
196 Some(prost::Message::encode_to_vec(&resp))
197 }
198 }
199 }
200}
201
202struct RemoteCmdHandler;
206
207#[async_trait]
208impl RequestHandler for RemoteCmdHandler {
209 async fn handle(&self, conn_id: u64, request: &IncomingRequest) -> Option<Vec<u8>> {
210 let req: futu_proto::remote_cmd::Request =
211 prost::Message::decode(request.body.as_ref()).ok()?;
212 let c2s = &req.c2s;
213 let cmd = &c2s.cmd;
214 let param = c2s.param_str.as_deref().unwrap_or("");
215
216 tracing::info!(conn_id, cmd = %cmd, param = %param, "RemoteCmd received");
217
218 match cmd.as_str() {
219 "set_log_level" => {
220 let level_str = param
223 .split('&')
224 .find_map(|kv| {
225 let (key, val) = kv.split_once('=')?;
226 if key.eq_ignore_ascii_case("level") {
227 Some(val.to_lowercase())
228 } else {
229 None
230 }
231 })
232 .unwrap_or_default();
233
234 let valid = matches!(
235 level_str.as_str(),
236 "no" | "debug" | "info" | "warning" | "error" | "fatal"
237 );
238
239 if valid {
240 tracing::info!(level = %level_str, "log level change requested");
244
245 let resp = futu_proto::remote_cmd::Response {
246 ret_type: 0,
247 ret_msg: None,
248 err_code: None,
249 s2c: Some(futu_proto::remote_cmd::S2c {
250 cmd: cmd.clone(),
251 result_str: Some(format!("log level set to {level_str}")),
252 }),
253 };
254 Some(prost::Message::encode_to_vec(&resp))
255 } else {
256 let resp = futu_proto::remote_cmd::Response {
257 ret_type: -1,
258 ret_msg: Some(format!(
259 "invalid level '{}', valid: no/debug/info/warning/error/fatal",
260 level_str
261 )),
262 err_code: None,
263 s2c: None,
264 };
265 Some(prost::Message::encode_to_vec(&resp))
266 }
267 }
268 "request_highest_quote_right" => {
269 tracing::info!("RemoteCmd: request_highest_quote_right");
272 let resp = futu_proto::remote_cmd::Response {
273 ret_type: 0,
274 ret_msg: None,
275 err_code: None,
276 s2c: Some(futu_proto::remote_cmd::S2c {
277 cmd: cmd.clone(),
278 result_str: None,
279 }),
280 };
281 Some(prost::Message::encode_to_vec(&resp))
282 }
283 _ => {
284 tracing::warn!(conn_id, cmd = %cmd, "RemoteCmd: unknown command");
285 let resp = futu_proto::remote_cmd::Response {
286 ret_type: -1,
287 ret_msg: Some(format!("cmd '{}' not found", cmd)),
288 err_code: None,
289 s2c: None,
290 };
291 Some(prost::Message::encode_to_vec(&resp))
292 }
293 }
294 }
295}
296
297struct VerificationHandler;
301
302#[async_trait]
303impl RequestHandler for VerificationHandler {
304 async fn handle(&self, _conn_id: u64, request: &IncomingRequest) -> Option<Vec<u8>> {
305 let _req: futu_proto::verification::Request =
306 prost::Message::decode(request.body.as_ref()).ok()?;
307
308 let resp = futu_proto::verification::Response {
310 ret_type: 0,
311 ret_msg: Some("No verification required".to_string()),
312 err_code: None,
313 s2c: None,
314 };
315 Some(prost::Message::encode_to_vec(&resp))
316 }
317}