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
12pub const DEFAULT_MAX_NUM_QUERY_REQUEST_PER_SECOND: usize = 1;
19
20pub const DEFAULT_MAX_NUM_ORDER_REQUEST_PER_SECOND: usize = 6;
24pub 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
164pub 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 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
200pub unsafe fn from_rsp_info_to_rsp_result(rsp_info: *const CThostFtdcRspInfoField) -> RspResult {
204 #[allow(unused_unsafe)] 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)] #[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, 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}