pub struct ClientConn {Show 13 fields
pub conn_id: u64,
pub state: ConnState,
pub aes_key: [u8; 16],
pub aes_encrypt_enabled: bool,
pub proto_fmt_type: ProtoFmtType,
pub last_keepalive: Instant,
pub recv_notify: bool,
pub keepalive_count: AtomicU32,
pub tx: Sender<FutuFrame>,
pub key_id: Option<String>,
pub scopes: HashSet<Scope>,
pub allowed_markets: Option<Arc<HashSet<String>>>,
pub allowed_acc_ids: Option<Arc<HashSet<u64>>>,
}Expand description
单个客户端连接
Fields§
§conn_id: u64随机连接 ID(对应 C++ GetRand_MilliTimeAndU22)
state: ConnState连接状态:InitConnect 前 / 后 / 已断开
aes_key: [u8; 16]随机 AES-128 key(InitConnect 响应里下发给客户端)
aes_encrypt_enabled: boolAES 加解密已启用(InitConnect 完成且配置了 RSA 时为 true)
proto_fmt_type: ProtoFmtType该连接协商的 proto 格式(Protobuf / JSON)
last_keepalive: Instant上次收到 KeepAlive 的时间,用于超时检查
recv_notify: boolInitConnect.C2S.recvNotify:此连接是否接收市场状态 / 交易解锁等通知。
C++ 在 APIServer_InitConnect.cpp 里把该字段写入 ConnInfo;
RegQotPush / Qot_Sub(isRegOrUnRegPush) 不会修改这个开关。
keepalive_count: AtomicU32已收到的 KeepAlive 计数(监控用)
tx: Sender<FutuFrame>发送帧到此连接
key_id: Option<String>该连接绑定的 API key id;WS 握手时填,未配 keys.json 时为 None
scopes: HashSet<Scope>该连接持有的 scope 集合;空集 = legacy 模式 / TCP 直连,scope 检查放行
allowed_markets: Option<Arc<HashSet<String>>>v1.4.105 D3 (Phase 4) T-B2: 该连接 caller key 的 allowed_markets 硬
限额 (大写字符串 set, e.g. {“HK”,“US”}). None = 无限制 (legacy 模式
/ TCP 直连默认全开 / 未配 allowed_markets); Some(set) 非空 → push 端
应过滤 trd_market 不在 set 中的 trade event.
触发: WS handshake 时从 KeyRecord.allowed_markets 拷贝过来.
PushDispatcher::push_trd_acc 端 Layer 3 filter 检查. 与
caller_allowed_acc_ids (Layer 1, per-call snapshot in IncomingRequest)
区别: 本字段是 per-conn snapshot (handshake 时一次性), 不随 per-call
重读 — KeyRecord SIGHUP reload 后仅新建连接生效, 老连接保持 snapshot
(与 scopes / caller_allowed_acc_ids 的 snapshot 语义一致).
allowed_acc_ids: Option<Arc<HashSet<u64>>>codex round 1 F4 (P2) v1.4.105: 该连接 caller key 的 allowed_acc_ids
硬限额 (per-conn snapshot, handshake 时一次性). None / Some(empty) =
无限制 (legacy 模式 / TCP 直连默认全开 / 未配 allowed_acc_ids);
Some(non-empty set) →
PushDispatcher::push_trd_acc 端 push-time 硬过滤 acc_id 不在 set 中
的 trade event (Layer 1, 与 allowed_markets 的 Layer 3 互补).
Deny-all 使用 sentinel {0},不使用空集合。
触发: codex F4 指出 raw TCP push 端只查 acc:read scope +
allowed_markets, 不查 allowed_acc_ids. 即使 request-time
SubAccPushHandler 已阻止越权订阅, stale subscription / KeyRecord
reload 后窄化的 acc 范围 / 历史 bug 留下的 conn→acc 关系 仍可能让 push
漏 leak. 本字段提供第二层 push-time 兜底.
与 caller_allowed_acc_ids (IncomingRequest, per-call) 区别: 本字段
在 push-time 用 (无 IncomingRequest), per-conn snapshot 与 scopes /
allowed_markets 的 snapshot 语义一致.
Implementations§
Source§impl ClientConn
impl ClientConn
Sourcepub fn generate_conn_id() -> u64
pub fn generate_conn_id() -> u64
生成随机连接 ID(与 C++ 的 GetRand_MilliTimeAndU22 对应)
Sourcepub fn generate_aes_key() -> [u8; 16]
pub fn generate_aes_key() -> [u8; 16]
生成随机 AES key(16 字节 hex 字符串的 ASCII 字节)
Sourcepub fn make_frame(
&self,
proto_id: u32,
serial_no: u32,
body: Bytes,
) -> FutuFrame
pub fn make_frame( &self, proto_id: u32, serial_no: u32, body: Bytes, ) -> FutuFrame
创建发送帧,自动处理 AES 加密
当 aes_encrypt_enabled 为 true 时:
- SHA1 基于明文计算(FutuFrame::new 自动处理)
- body 使用 AES-128 ECB 加密
- header.body_len 更新为密文长度
对应 C++ APIServerCS_Conn::OnSendPacketData 的加密逻辑
Sourcepub fn decrypt_body(&self, body: &[u8]) -> Result<Vec<u8>, FutuError>
pub fn decrypt_body(&self, body: &[u8]) -> Result<Vec<u8>, FutuError>
解密请求 body(如果启用了 AES 加密)
对应 C++ APIServerCS_Conn::OnRecvPacket 的解密逻辑
Sourcepub fn handle_init_connect(
&mut self,
body: &[u8],
server_ver: i32,
login_user_id: u64,
keepalive_interval: i32,
rsa_private_key: Option<&str>,
) -> Result<Vec<u8>, FutuError>
pub fn handle_init_connect( &mut self, body: &[u8], server_ver: i32, login_user_id: u64, keepalive_interval: i32, rsa_private_key: Option<&str>, ) -> Result<Vec<u8>, FutuError>
处理 InitConnect 请求,返回 InitConnect 响应 body
当配置了 RSA 私钥时:
- C2S 请求 body 使用 RSA 公钥加密(需要用私钥解密)
- S2C 响应 body 使用 RSA 公钥加密(客户端用私钥解密)
对应 C++ APIServer::OnRecvInitConnect