futu_backend/auth/webtcp/
connect.rs1use crate::conn::{BackendConn, BackendProtocolIdentity, PushCallback};
2use futu_core::error::{FutuError, Result};
3use futu_core::log_redact::endpoint_log_fingerprint;
4use rustls_pki_types::ServerName;
5use std::net::{IpAddr, SocketAddr};
6use std::sync::Arc;
7use std::sync::atomic::Ordering;
8use std::time::Duration;
9use tokio::net::TcpSocket;
10use tokio_rustls::TlsConnector;
11use tokio_rustls::rustls::{ClientConfig, RootCertStore};
12
13const WEBTCP_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
16const WEBTCP_TLS_TIMEOUT: Duration = Duration::from_secs(10);
20const WEBTCP_SEND_BUFFER_SIZE: u32 = 64 * 1024;
22const WEBTCP_RECV_BUFFER_SIZE: u32 = 512 * 1024;
24
25pub(super) const fn webtcp_header_user_id_like_cpp(user_id: u64) -> u32 {
30 user_id as u32
31}
32
33fn initialize_webtcp_header_user_id(conn: BackendConn, header_user_id: u64) -> BackendConn {
34 conn.user_id.store(
35 webtcp_header_user_id_like_cpp(header_user_id),
36 Ordering::Relaxed,
37 );
38 conn
39}
40
41pub fn install_default_rustls_crypto_provider() {
45 futu_core::http_client::install_default_rustls_crypto_provider();
46}
47
48pub(in crate::auth) fn tls_server_name_for_web_identity(identity: u32) -> &'static str {
51 match identity {
52 crate::auth::commconfig::CONN_WEB_CN | crate::auth::commconfig::CONN_WEB_HK => {
53 "www.futunn.com"
54 }
55 _ => "www.moomoo.com",
56 }
57}
58
59fn tls_connector() -> TlsConnector {
60 install_default_rustls_crypto_provider();
61
62 let roots = RootCertStore {
63 roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
64 };
65 let mut config = ClientConfig::builder()
66 .with_root_certificates(roots)
67 .with_no_client_auth();
68 config.enable_sni = false;
72 TlsConnector::from(Arc::new(config))
73}
74
75pub(in crate::auth) async fn connect_webtcp(
76 ip: &str,
77 port: u16,
78 tls_server_name: &'static str,
79 protocol_identity: BackendProtocolIdentity,
80 header_user_id: u64,
81) -> Result<BackendConn> {
82 let endpoint_fingerprint = endpoint_log_fingerprint(&format!("{ip}:{port}"));
83 let ip_addr = ip.parse::<IpAddr>().map_err(|_error| {
84 FutuError::Codec(format!(
85 "webtcp_invalid_ip endpoint_fingerprint={endpoint_fingerprint}"
86 ))
87 })?;
88 let addr = SocketAddr::new(ip_addr, port);
89 let socket = match addr {
90 SocketAddr::V4(_) => TcpSocket::new_v4(),
91 SocketAddr::V6(_) => TcpSocket::new_v6(),
92 }
93 .map_err(|error| {
94 FutuError::Network(std::io::Error::new(
95 error.kind(),
96 format!("webtcp_socket_create_failed endpoint_fingerprint={endpoint_fingerprint}"),
97 ))
98 })?;
99 socket.set_nodelay(true).map_err(|error| {
105 FutuError::Network(std::io::Error::new(
106 error.kind(),
107 format!("webtcp_socket_config_failed endpoint_fingerprint={endpoint_fingerprint}"),
108 ))
109 })?;
110 socket
111 .set_send_buffer_size(WEBTCP_SEND_BUFFER_SIZE)
112 .map_err(|error| {
113 FutuError::Network(std::io::Error::new(
114 error.kind(),
115 format!("webtcp_socket_config_failed endpoint_fingerprint={endpoint_fingerprint}"),
116 ))
117 })?;
118 socket
119 .set_recv_buffer_size(WEBTCP_RECV_BUFFER_SIZE)
120 .map_err(|error| {
121 FutuError::Network(std::io::Error::new(
122 error.kind(),
123 format!("webtcp_socket_config_failed endpoint_fingerprint={endpoint_fingerprint}"),
124 ))
125 })?;
126
127 let stream = tokio::time::timeout(WEBTCP_CONNECT_TIMEOUT, socket.connect(addr))
128 .await
129 .map_err(|_elapsed| {
130 FutuError::Network(std::io::Error::new(
131 std::io::ErrorKind::TimedOut,
132 format!("webtcp_connect_timeout endpoint_fingerprint={endpoint_fingerprint}"),
133 ))
134 })?
135 .map_err(|error| {
136 FutuError::Network(std::io::Error::new(
137 error.kind(),
138 format!("webtcp_connect_failed endpoint_fingerprint={endpoint_fingerprint}"),
139 ))
140 })?;
141
142 let server_name = ServerName::try_from(tls_server_name.to_string()).map_err(|e| {
143 FutuError::Codec(format!(
144 "webtcp invalid tls server name {tls_server_name}: {e}"
145 ))
146 })?;
147 let tls = tokio::time::timeout(
148 WEBTCP_TLS_TIMEOUT,
149 tls_connector().connect(server_name, stream),
150 )
151 .await
152 .map_err(|_elapsed| {
153 FutuError::Network(std::io::Error::new(
154 std::io::ErrorKind::TimedOut,
155 format!("webtcp_tls_timeout endpoint_fingerprint={endpoint_fingerprint}"),
156 ))
157 })?
158 .map_err(|_error| {
159 FutuError::Codec(format!(
160 "webtcp_tls_failed endpoint_fingerprint={endpoint_fingerprint}"
161 ))
162 })?;
163
164 let noop: PushCallback = Arc::new(|_, _, _, _| {});
165 let endpoint = addr.to_string();
166 Ok(initialize_webtcp_header_user_id(
167 BackendConn::from_stream_with_endpoint(tls, noop, &endpoint, protocol_identity),
168 header_user_id,
169 ))
170}
171
172#[cfg(test)]
173pub(in crate::auth) fn backend_conn_from_duplex_for_test(
174 stream: tokio::io::DuplexStream,
175 protocol_identity: BackendProtocolIdentity,
176 header_user_id: u64,
177) -> BackendConn {
178 let noop: PushCallback = Arc::new(|_, _, _, _| {});
179 initialize_webtcp_header_user_id(
180 BackendConn::from_stream_with_endpoint(stream, noop, "webtcp-test-peer", protocol_identity),
181 header_user_id,
182 )
183}