ctp_common/
lib.rs

1mod binding;
2use std::borrow::Cow;
3use std::fmt;
4use std::os::raw::c_int;
5
6pub use binding::*;
7use encoding::all::GB18030;
8use encoding::{DecoderTrap, Encoding};
9use simple_error::SimpleError;
10use time::{Timespec, Tm};
11
12/// 交易接口中的查询操作的限制为:
13///   每秒钟最多只能进行一次查询操作。
14///   在途的查询操作最多只能有一个。
15/// 在途:查询操作从发送请求,到接收到响应为一个完整的过程。如果请求已经发送,但是未收到响应,则称
16/// 该查询操作在途。
17/// 上述限制只针对交易接口中的数据查询操作(ReqQryXXX),对报单,撤单,报价,询价等操作没有影响。
18pub const DEFAULT_MAX_NUM_QUERY_REQUEST_PER_SECOND: usize = 1;
19
20/// 报单流量限制是由期货公司通过在系统中配置相关参数实现限制的。
21/// 不进行配置的情况下,默认流量限制为:
22/// 在一个连接会话(Session)中,每个客户端每秒钟最多只能发送 6 笔交易相关的指令(报单,撤单等)。
23pub const DEFAULT_MAX_NUM_ORDER_REQUEST_PER_SECOND: usize = 6;
24/// 同一个账户同时最多只能建立 6 个会话(Session)。
25pub const DEFAULT_MAX_NUM_CONCURRENT_SESSION: usize = 6;
26
27pub fn ascii_cstr_to_str(s: &[u8]) -> Result<&str, SimpleError> {
28    match s.last() {
29        Some(&0u8) => {
30            let len = memchr::memchr(0, s).unwrap();
31            let ascii_s = &s[0..len];
32            if ascii_s.is_ascii() {
33                unsafe { Ok(std::str::from_utf8_unchecked(ascii_s)) }
34            } else {
35                Err(SimpleError::new("cstr is not ascii"))
36            }
37        }
38        Some(&c) => Err(SimpleError::new(format!(
39            "cstr should terminate with null instead of {:#x}",
40            c
41        ))),
42        None => Err(SimpleError::new("cstr cannot have 0 length")),
43    }
44}
45
46pub fn gb18030_cstr_to_str(v: &[u8]) -> Cow<str> {
47    let slice = v.split(|&c| c == 0u8).next().unwrap();
48    if slice.is_ascii() {
49        unsafe {
50            return Cow::Borrowed::<str>(std::str::from_utf8_unchecked(slice));
51        }
52    }
53    match GB18030.decode(slice, DecoderTrap::Replace) {
54        Ok(s) => Cow::Owned(s),
55        Err(e) => e,
56    }
57}
58
59pub fn reduce_comb_flags(flags: &[u8]) -> String {
60    flags
61        .iter()
62        .filter(|&&c| c != 0)
63        .map(|&c| char::from(c))
64        .collect()
65}
66
67pub fn maybe_char(c: u8) -> Option<char> {
68    if c != 0u8 {
69        Some(char::from(c))
70    } else {
71        None
72    }
73}
74
75#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
76pub struct OrderIdLocalTrio {
77    pub front_id: TThostFtdcFrontIDType,
78    pub session_id: TThostFtdcSessionIDType,
79    pub order_ref: TThostFtdcOrderRefType,
80}
81
82#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
83pub struct OrderIdExchangeDuo {
84    pub exchange_id: TThostFtdcExchangeIDType,
85    pub order_sys_id: TThostFtdcOrderSysIDType,
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum ResumeType {
90    Restart = THOST_TE_RESUME_TYPE::THOST_TERT_RESTART as isize,
91    Resume = THOST_TE_RESUME_TYPE::THOST_TERT_RESUME as isize,
92    Quick = THOST_TE_RESUME_TYPE::THOST_TERT_QUICK as isize,
93}
94
95impl std::convert::Into<THOST_TE_RESUME_TYPE> for ResumeType {
96    fn into(self) -> THOST_TE_RESUME_TYPE {
97        match self {
98            ResumeType::Restart => THOST_TE_RESUME_TYPE::THOST_TERT_RESTART,
99            ResumeType::Resume => THOST_TE_RESUME_TYPE::THOST_TERT_RESUME,
100            ResumeType::Quick => THOST_TE_RESUME_TYPE::THOST_TERT_QUICK,
101        }
102    }
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub enum DisconnectionReason {
107    ReadError = 0x1001,
108    WriteError = 0x1002,
109    HeartbeatTimeout = 0x2001,
110    HeartbeatSendError = 0x2002,
111    ErrorMessageReceived = 0x2003,
112    Unknown = 0x0000,
113}
114
115impl std::convert::From<c_int> for DisconnectionReason {
116    fn from(reason: c_int) -> DisconnectionReason {
117        match reason {
118            0x1001 => DisconnectionReason::ReadError,
119            0x1002 => DisconnectionReason::WriteError,
120            0x2001 => DisconnectionReason::HeartbeatTimeout,
121            0x2002 => DisconnectionReason::HeartbeatSendError,
122            0x2003 => DisconnectionReason::ErrorMessageReceived,
123            _ => DisconnectionReason::Unknown,
124        }
125    }
126}
127
128impl fmt::Display for DisconnectionReason {
129    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130        use DisconnectionReason::*;
131        match *self {
132            ReadError => f.write_str("read error"),
133            WriteError => f.write_str("write error"),
134            HeartbeatTimeout => f.write_str("heartbeat timeout"),
135            HeartbeatSendError => f.write_str("heatbeat send error"),
136            ErrorMessageReceived => f.write_str("error message received"),
137            Unknown => f.write_str("unknown"),
138        }
139    }
140}
141
142#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub enum ApiError {
144    NetworkError = -1,
145    QueueFull = -2,
146    Throttled = -3,
147}
148
149impl std::error::Error for ApiError {}
150
151impl fmt::Display for ApiError {
152    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
153        match *self {
154            ApiError::NetworkError => f.write_str("network error"),
155            ApiError::QueueFull => f.write_str("queue full"),
156            ApiError::Throttled => f.write_str("throttled"),
157        }
158    }
159}
160
161#[must_use]
162pub type ApiResult = Result<(), ApiError>;
163
164// TODO: use std::convert::From after trait specialization?
165pub fn from_api_return_to_api_result(api_return: c_int) -> ApiResult {
166    match api_return {
167        0 => Ok(()),
168        -1 => Err(ApiError::NetworkError),
169        -2 => Err(ApiError::QueueFull),
170        -3 => Err(ApiError::Throttled),
171        // no other values are possible in specification
172        i => panic!("api return unspecified {}", i),
173    }
174}
175
176#[derive(Clone, Debug, PartialEq)]
177pub struct RspError {
178    pub id: TThostFtdcErrorIDType,
179    pub msg: String,
180}
181
182impl std::error::Error for RspError {}
183
184impl fmt::Display for RspError {
185    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186        write!(f, "{} {}", self.id, self.msg)
187    }
188}
189
190#[must_use]
191pub type RspResult = Result<(), RspError>;
192
193pub fn from_rsp_result_to_string(rsp_result: &RspResult) -> String {
194    match rsp_result {
195        Ok(()) => "Ok(())".to_string(),
196        Err(err) => format!("Err(RspError{{ id: {}, msg: {} }})", err.id, err.msg),
197    }
198}
199
200/// # Safety
201///
202/// `rsp_info` needs to be either a null pointer or a valid pointer of type `CThostFtdcRspInfoField`.
203pub unsafe fn from_rsp_info_to_rsp_result(rsp_info: *const CThostFtdcRspInfoField) -> RspResult {
204    #[allow(unused_unsafe)] // for future "unsafe blocks in unsafe fn" feature
205    match unsafe { rsp_info.as_ref() } {
206        Some(info) => match info {
207            CThostFtdcRspInfoField { ErrorID: 0, .. } => Ok(()),
208            CThostFtdcRspInfoField {
209                ErrorID: id,
210                ErrorMsg: msg,
211            } => Err(RspError {
212                id: *id,
213                msg: gb18030_cstr_to_str(msg).into_owned(),
214            }),
215        },
216        None => Ok(()),
217    }
218}
219
220pub fn is_terminal_order_status(order_status: TThostFtdcOrderStatusType) -> bool {
221    order_status == THOST_FTDC_OST_AllTraded
222        || order_status == THOST_FTDC_OST_PartTradedNotQueueing
223        || order_status == THOST_FTDC_OST_NoTradeNotQueueing
224        || order_status == THOST_FTDC_OST_Canceled
225}
226
227pub fn is_valid_order_sys_id(order_sys_id: &TThostFtdcOrderSysIDType) -> bool {
228    order_sys_id[0] != b'\0'
229}
230
231#[allow(clippy::trivially_copy_pass_by_ref)] // Will be removed
232#[deprecated(since = "0.9.0", note = "This will be removed in 0.10.0")]
233pub fn to_exchange_timestamp(
234    trading_day: &TThostFtdcDateType,
235    update_time: &TThostFtdcTimeType,
236    update_millisec: &TThostFtdcMillisecType,
237) -> Result<Timespec, SimpleError> {
238    let year = match ::std::str::from_utf8(&trading_day[0..4]) {
239        Ok(year_str) => match year_str.parse::<u16>() {
240            Ok(year) => year,
241            Err(err) => {
242                return Err(SimpleError::new(format!("invalid year string, {}", err)));
243            }
244        },
245        Err(err) => {
246            return Err(SimpleError::new(format!("year not utf8, {}", err)));
247        }
248    };
249    let month = match ::std::str::from_utf8(&trading_day[4..6]) {
250        Ok(month_str) => match month_str.parse::<u8>() {
251            Ok(month) => month,
252            Err(err) => {
253                return Err(SimpleError::new(format!("invalid month string, {}", err)));
254            }
255        },
256        Err(err) => {
257            return Err(SimpleError::new(format!("month not utf8, {}", err)));
258        }
259    };
260    let day = match ::std::str::from_utf8(&trading_day[6..8]) {
261        Ok(day_str) => match day_str.parse::<u8>() {
262            Ok(day) => day,
263            Err(err) => {
264                return Err(SimpleError::new(format!("invalid day string, {}", err)));
265            }
266        },
267        Err(err) => {
268            return Err(SimpleError::new(format!("day not utf8, {}", err)));
269        }
270    };
271    let hour = match ::std::str::from_utf8(&update_time[0..2]) {
272        Ok(hour_str) => match hour_str.parse::<u8>() {
273            Ok(hour) => hour,
274            Err(err) => {
275                return Err(SimpleError::new(format!("invalid hour string, {}", err)));
276            }
277        },
278        Err(err) => {
279            return Err(SimpleError::new(format!("hour not utf8, {}", err)));
280        }
281    };
282    let minute = match ::std::str::from_utf8(&update_time[3..5]) {
283        Ok(minute_str) => match minute_str.parse::<u8>() {
284            Ok(minute) => minute,
285            Err(err) => {
286                return Err(SimpleError::new(format!("invalid minute string, {}", err)));
287            }
288        },
289        Err(err) => {
290            return Err(SimpleError::new(format!("minute not utf8, {}", err)));
291        }
292    };
293    let second = match ::std::str::from_utf8(&update_time[6..8]) {
294        Ok(second_str) => match second_str.parse::<u8>() {
295            Ok(second) => second,
296            Err(err) => {
297                return Err(SimpleError::new(format!("invalid second string, {}", err)));
298            }
299        },
300        Err(err) => {
301            return Err(SimpleError::new(format!("second not utf8, {}", err)));
302        }
303    };
304    let nanosec = *update_millisec as i32 * 1000 * 1000;
305    let tm = Tm {
306        tm_sec: second as i32,
307        tm_min: minute as i32,
308        tm_hour: hour as i32 - 8, // UTC+8
309        tm_mday: day as i32,
310        tm_mon: month as i32 - 1,
311        tm_year: year as i32 - 1900,
312        tm_wday: 0i32,
313        tm_yday: 0i32,
314        tm_isdst: 0i32,
315        tm_utcoff: 0i32,
316        tm_nsec: nanosec,
317    };
318    Ok(tm.to_timespec())
319}
320
321pub fn set_cstr_from_str(buffer: &mut [u8], text: &str) -> Result<(), SimpleError> {
322    if let Some(i) = memchr::memchr(0, text.as_bytes()) {
323        return Err(SimpleError::new(format!(
324            "null found in str at offset {} when filling cstr",
325            i
326        )));
327    }
328    if text.len() + 1 > buffer.len() {
329        return Err(SimpleError::new(format!(
330            "str len {} too long when filling cstr with buffer len {}",
331            text.len(),
332            buffer.len()
333        )));
334    }
335    unsafe {
336        std::ptr::copy_nonoverlapping(text.as_ptr(), buffer.as_mut_ptr(), text.len());
337        *buffer.get_unchecked_mut(text.len()) = 0u8;
338    }
339    Ok(())
340}
341
342pub fn set_cstr_from_str_truncate(buffer: &mut [u8], text: &str) {
343    for (place, data) in buffer
344        .split_last_mut()
345        .expect("buffer len 0 in set_cstr_from_str_truncate")
346        .1
347        .iter_mut()
348        .zip(text.as_bytes().iter())
349    {
350        *place = *data;
351    }
352    unsafe {
353        *buffer.get_unchecked_mut(text.len()) = 0u8;
354    }
355}
356
357pub fn normalize_double(d: f64) -> Option<f64> {
358    if d == std::f64::MAX {
359        None
360    } else {
361        Some(d)
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use std::borrow::Cow;
368
369    use time::Timespec;
370
371    use super::{
372        ascii_cstr_to_str, gb18030_cstr_to_str, set_cstr_from_str, set_cstr_from_str_truncate,
373        to_exchange_timestamp, CThostFtdcDepthMarketDataField,
374    };
375
376    #[test]
377    fn len_0_ascii_cstr_to_str() {
378        assert!(ascii_cstr_to_str(b"").is_err());
379    }
380
381    #[test]
382    fn ascii_cstr_to_str_trivial() {
383        assert_eq!(ascii_cstr_to_str(b"hello\0"), Ok("hello"));
384    }
385
386    #[test]
387    fn non_null_terminated_ascii_cstr_to_str() {
388        assert!(ascii_cstr_to_str(b"hello").is_err());
389    }
390
391    #[test]
392    fn non_ascii_cstr_to_str() {
393        assert!(ascii_cstr_to_str(b"\xd5\xfd\xc8\xb7\0").is_err());
394    }
395
396    #[test]
397    fn cstr_conversion_empty_str() {
398        match gb18030_cstr_to_str(b"") {
399            Cow::Borrowed::<str>(s) => assert_eq!(s, ""),
400            Cow::Owned::<str>(_) => panic!("ascii str should not allocate"),
401        };
402        match gb18030_cstr_to_str(b"\0") {
403            Cow::Borrowed::<str>(s) => assert_eq!(s, ""),
404            Cow::Owned::<str>(_) => panic!("ascii str should not allocate"),
405        };
406    }
407
408    #[test]
409    fn cstr_conversion_ascii() {
410        match gb18030_cstr_to_str(b"ascii") {
411            Cow::Borrowed::<str>(s) => assert_eq!(s, "ascii"),
412            Cow::Owned::<str>(_) => panic!("ascii str should not allocate"),
413        };
414    }
415
416    #[test]
417    fn cstr_conversion_ascii_cstr() {
418        match gb18030_cstr_to_str(b"ascii\0") {
419            Cow::Borrowed::<str>(s) => assert_eq!(s, "ascii"),
420            Cow::Owned::<str>(_) => panic!("ascii str should not allocate"),
421        };
422    }
423
424    #[test]
425    fn cstr_conversion_gb2312() {
426        assert_eq!(gb18030_cstr_to_str(b"\xd5\xfd\xc8\xb7"), "正确");
427    }
428
429    #[test]
430    fn cstr_conversion_gb2312_cstr() {
431        assert_eq!(gb18030_cstr_to_str(b"\xd5\xfd\xc8\xb7\0"), "正确");
432    }
433
434    #[test]
435    fn fill_cstr_with_str() {
436        let mut buffer: [u8; 8] = Default::default();
437        set_cstr_from_str(buffer.as_mut(), "hello").unwrap();
438        assert_eq!(buffer.as_ref(), b"hello\0\0\0");
439    }
440
441    #[test]
442    fn fill_cstr_with_long_str() {
443        let mut buffer: [u8; 1] = Default::default();
444        assert!(set_cstr_from_str(buffer.as_mut(), "hello").is_err());
445    }
446
447    #[test]
448    fn fill_cstr_with_str_containing_null() {
449        let mut buffer: [u8; 8] = Default::default();
450        assert!(set_cstr_from_str(buffer.as_mut(), "he\0llo").is_err());
451    }
452
453    #[test]
454    fn fill_cstr_with_str_truncate() {
455        let mut buffer: [u8; 8] = Default::default();
456        set_cstr_from_str_truncate(buffer.as_mut(), "hello");
457        assert_eq!(buffer.as_ref(), b"hello\0\0\0");
458    }
459
460    #[test]
461    #[should_panic]
462    fn fill_0_len_cstr_with_str_truncate_panic() {
463        let mut buffer: [u8; 0] = Default::default();
464        set_cstr_from_str_truncate(buffer.as_mut(), "hello");
465    }
466
467    #[test]
468    fn fill_cstr_with_long_str_truncate() {
469        let mut buffer: [u8; 6] = Default::default();
470        set_cstr_from_str_truncate(buffer.as_mut(), "hello world");
471        assert_eq!(buffer.as_ref(), b"hello\0");
472    }
473
474    #[test]
475    fn exchange_timestamp_conversion() {
476        let mut md: CThostFtdcDepthMarketDataField = Default::default();
477        md.TradingDay = *b"19700101\0";
478        md.UpdateTime = *b"08:00:00\0";
479        md.UpdateMillisec = 0;
480        let ts1 = to_exchange_timestamp(&md.TradingDay, &md.UpdateTime, &md.UpdateMillisec);
481        assert_eq!(Ok(Timespec { sec: 0, nsec: 0 }), ts1);
482        md.TradingDay = *b"19700102\0";
483        md.UpdateTime = *b"00:00:00\0";
484        let ts2 = to_exchange_timestamp(&md.TradingDay, &md.UpdateTime, &md.UpdateMillisec);
485        assert_eq!(
486            Ok(Timespec {
487                sec: 57600,
488                nsec: 0
489            }),
490            ts2
491        );
492    }
493}