futu_backend/stock_list/
event_notice_push.rs1use futu_core::error::{FutuError, Result};
6use futu_domain_qot_market_state::{MarketEventUpdate, MarketStatePatch, MarketTradeDateSnapshot};
7use prost::Message;
8
9use super::market::quote_market_type_from_market_id;
10
11const EVENT_TYPE_TRADING_DAY: u16 = 5;
14const EVENT_TYPE_MARKET_STATE: u16 = 9;
15
16#[derive(Clone, Debug, Default, PartialEq, Eq)]
17pub struct MarketEventPushBatch {
18 pub updates: Vec<MarketEventUpdate>,
19}
20
21#[derive(Clone, PartialEq, Message)]
25struct MarketStatusListWire {
26 #[prost(message, repeated, tag = "1")]
27 list: Vec<MarketStatusItemWire>,
28}
29
30#[derive(Clone, PartialEq, Message)]
31struct MarketStatusItemWire {
32 #[prost(uint32, optional, tag = "1")]
34 market_id: Option<u32>,
35 #[prost(uint32, optional, tag = "2")]
37 status: Option<u32>,
38 #[prost(string, optional, tag = "3")]
39 status_text_sc: Option<String>,
40 #[prost(string, optional, tag = "4")]
41 status_text_tc: Option<String>,
42}
43
44pub fn decode_event_notice_push(body: &[u8]) -> Result<MarketEventPushBatch> {
45 let mut cursor = NetworkCursor::new(body);
46 let event_count = cursor.read_u16("event count")?;
47 let mut updates = Vec::new();
48
49 for _ in 0..event_count {
50 let event_type = cursor.read_u16("event type")?;
51 let payload_len = usize::from(cursor.read_u16("event payload length")?);
52 let payload = cursor.take(payload_len, "event payload")?;
53 match event_type {
54 EVENT_TYPE_TRADING_DAY => decode_trading_day_payload(payload, &mut updates)?,
55 EVENT_TYPE_MARKET_STATE if payload.is_empty() => {
56 }
59 EVENT_TYPE_MARKET_STATE => decode_market_state_payload(payload, &mut updates)?,
60 _ => {}
61 }
62 }
63
64 Ok(MarketEventPushBatch { updates })
65}
66
67fn decode_trading_day_payload(payload: &[u8], updates: &mut Vec<MarketEventUpdate>) -> Result<()> {
68 let mut cursor = NetworkCursor::new(payload);
69 let row_count = cursor.read_u8("trading-day row count")?;
70 for _ in 0..row_count {
71 let market_id = u32::from(cursor.read_u8("trading-day market id")?);
72 let trade_date = cursor.read_u32("trading-day timestamp")?;
73 updates.push(MarketEventUpdate::MarketTradeDate(
74 MarketTradeDateSnapshot {
75 market_id,
76 quote_market_type: quote_market_type_for_market_id(market_id),
77 trade_date,
78 },
79 ));
80 }
81 Ok(())
82}
83
84fn decode_market_state_payload(payload: &[u8], updates: &mut Vec<MarketEventUpdate>) -> Result<()> {
85 let list = MarketStatusListWire::decode(payload).map_err(FutuError::Proto)?;
86 for item in list.list {
87 let market_id = item.market_id.ok_or_else(|| {
88 FutuError::Codec("CMD6301 market state missing required market_id".to_string())
89 })?;
90 let status = item.status.ok_or_else(|| {
91 FutuError::Codec("CMD6301 market state missing required status".to_string())
92 })?;
93 updates.push(MarketEventUpdate::MarketState(MarketStatePatch {
94 market_id,
95 quote_market_type: quote_market_type_for_market_id(market_id),
96 status,
97 status_text_sc: item.status_text_sc,
98 status_text_tc: item.status_text_tc,
99 }));
100 }
101 Ok(())
102}
103
104fn quote_market_type_for_market_id(market_id: u32) -> u8 {
105 quote_market_type_from_market_id(market_id)
106 .unwrap_or(0)
109}
110
111struct NetworkCursor<'a> {
112 bytes: &'a [u8],
113 position: usize,
114}
115
116impl<'a> NetworkCursor<'a> {
117 fn new(bytes: &'a [u8]) -> Self {
118 Self { bytes, position: 0 }
119 }
120
121 fn read_u8(&mut self, field: &str) -> Result<u8> {
122 Ok(self.take(1, field)?[0])
123 }
124
125 fn read_u16(&mut self, field: &str) -> Result<u16> {
126 let bytes: [u8; 2] = self
127 .take(2, field)?
128 .try_into()
129 .map_err(|_| FutuError::Codec(format!("CMD6301 invalid {field}")))?;
130 Ok(u16::from_be_bytes(bytes))
131 }
132
133 fn read_u32(&mut self, field: &str) -> Result<u32> {
134 let bytes: [u8; 4] = self
135 .take(4, field)?
136 .try_into()
137 .map_err(|_| FutuError::Codec(format!("CMD6301 invalid {field}")))?;
138 Ok(u32::from_be_bytes(bytes))
139 }
140
141 fn take(&mut self, len: usize, field: &str) -> Result<&'a [u8]> {
142 let end = self
143 .position
144 .checked_add(len)
145 .ok_or_else(|| FutuError::Codec(format!("CMD6301 {field} length overflow")))?;
146 let bytes = self.bytes.get(self.position..end).ok_or_else(|| {
147 FutuError::Codec(format!(
148 "CMD6301 truncated {field}: need {len} bytes at {}",
149 self.position
150 ))
151 })?;
152 self.position = end;
153 Ok(bytes)
154 }
155}