use chrono::{NaiveDate, NaiveTime};
use encoding_rs::WINDOWS_1251;
use lazy_static::lazy_static;
use libc::{c_char, c_double, c_long, c_ulonglong, intptr_t};
use libloading::{Error as LibloadingError, Library, Symbol};
use std::error;
use std::ffi::{CStr, CString, NulError};
use std::fmt::{self, Debug};
use std::str;
use std::string::FromUtf8Error;
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc::UnboundedSender;
use tracing::{error, info};
lazy_static! {
pub static ref TRANSACTION_REPLY_SENDER: Mutex<Option<UnboundedSender<TransactionInfo>>> =
Mutex::new(None);
pub static ref ORDER_STATUS_SENDER: Mutex<Option<UnboundedSender<OrderInfo>>> =
Mutex::new(None);
pub static ref TRADE_STATUS_SENDER: Mutex<Option<UnboundedSender<TradeInfo>>> =
Mutex::new(None);
static ref TERMINAL_INSTANCE: Mutex<Option<Arc<Mutex<Terminal>>>> = Mutex::new(None);
}
type Trans2QuikConnectionStatusCallback =
unsafe extern "C" fn(connection_event: c_long, error_code: c_long, error_message: *mut c_char);
type Trans2QuikTransactionReplyCallback = unsafe extern "C" fn(
result_code: c_long,
error_code: c_long,
reply_code: c_long,
trans_id: c_long,
order_num: c_ulonglong,
reply_message: *mut c_char,
trans_reply_descriptor: intptr_t,
);
type Trans2QuikOrderStatusCallback = unsafe extern "C" fn(
mode: c_long,
trans_id: c_long,
order_num: c_ulonglong,
class_code: *mut c_char,
sec_code: *mut c_char,
price: c_double,
balance: i64,
value: c_double,
is_sell: c_long,
status: c_long,
order_descriptor: intptr_t,
);
type Trans2QuikTradeStatusCallback = unsafe extern "C" fn(
mode: c_long,
trade_num: c_ulonglong,
order_num: c_ulonglong,
class_code: *mut c_char,
sec_code: *mut c_char,
price: c_double,
quantity: i64,
is_sell: c_long,
value: c_double,
trade_descriptor: intptr_t,
);
#[derive(Debug, PartialEq)]
pub enum Mode {
NewOrder = 0,
InitialOrder = 1,
LastOrderReceived = 2,
Unknown,
}
impl From<c_long> for Mode {
fn from(code: c_long) -> Self {
match code {
0 => Mode::NewOrder,
1 => Mode::InitialOrder,
2 => Mode::LastOrderReceived,
_ => Mode::Unknown,
}
}
}
#[derive(Debug, PartialEq)]
pub enum TransId {
Id(c_long),
Unknown(c_long),
}
impl From<c_long> for TransId {
fn from(id: c_long) -> Self {
match id {
0 => TransId::Unknown(id),
_ => TransId::Id(id),
}
}
}
#[derive(Debug, PartialEq)]
pub enum IsSell {
Buy = 0,
Sell,
}
impl From<c_long> for IsSell {
fn from(code: c_long) -> Self {
match code {
0 => IsSell::Buy,
_ => IsSell::Sell,
}
}
}
#[derive(Debug, PartialEq)]
pub enum Status {
Active = 1,
Canceled = 2,
Executed,
}
impl From<c_long> for Status {
fn from(code: c_long) -> Self {
match code {
1 => Status::Active,
2 => Status::Canceled,
_ => Status::Executed,
}
}
}
#[derive(Debug, PartialEq)]
#[repr(i32)]
pub enum Trans2QuikResult {
Success = 0,
Failed = 1,
TerminalNotFound = 2,
DllVersionNotSupported = 3,
AlreadyConnectedToQuik = 4,
WrongSyntax = 5,
QuikNotConnected = 6,
DllNotConnected = 7,
QuikConnected = 8,
QuikDisconnected = 9,
DllConnected = 10,
DllDisconnected = 11,
MemoryAllocationError = 12,
WrongConnectionHandle = 13,
WrongInputParams = 14,
Unknown,
}
impl From<c_long> for Trans2QuikResult {
fn from(code: c_long) -> Self {
match code {
0 => Trans2QuikResult::Success,
1 => Trans2QuikResult::Failed,
2 => Trans2QuikResult::TerminalNotFound,
3 => Trans2QuikResult::DllVersionNotSupported,
4 => Trans2QuikResult::AlreadyConnectedToQuik,
5 => Trans2QuikResult::WrongSyntax,
6 => Trans2QuikResult::QuikNotConnected,
7 => Trans2QuikResult::DllNotConnected,
8 => Trans2QuikResult::QuikConnected,
9 => Trans2QuikResult::QuikDisconnected,
10 => Trans2QuikResult::DllConnected,
11 => Trans2QuikResult::DllDisconnected,
12 => Trans2QuikResult::MemoryAllocationError,
13 => Trans2QuikResult::WrongConnectionHandle,
14 => Trans2QuikResult::WrongInputParams,
_ => Trans2QuikResult::Unknown,
}
}
}
#[derive(Debug)]
pub enum Trans2QuikError {
LibLoading(LibloadingError),
NulError(NulError),
}
impl fmt::Display for Trans2QuikError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Trans2QuikError::LibLoading(err) => write!(f, "Library loading error: {}", err),
Trans2QuikError::NulError(err) => write!(f, "Nul error: {}", err),
}
}
}
impl error::Error for Trans2QuikError {}
impl From<LibloadingError> for Trans2QuikError {
fn from(err: LibloadingError) -> Trans2QuikError {
Trans2QuikError::LibLoading(err)
}
}
impl From<NulError> for Trans2QuikError {
fn from(err: NulError) -> Trans2QuikError {
Trans2QuikError::NulError(err)
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct OrderInfo {
pub mode: Mode,
pub trans_id: TransId,
pub order_num: u64,
pub class_code: String,
pub sec_code: String,
pub price: f64,
pub balance: i64,
pub value: f64,
pub is_sell: IsSell,
pub status: Status,
pub date: NaiveDate,
pub time: NaiveTime,
}
impl OrderInfo {
pub fn is_valid(&self) -> bool {
self.date != NaiveDate::default() && self.time != NaiveTime::default()
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct TradeInfo {
pub mode: Mode,
pub trade_num: u64,
pub order_num: u64,
pub class_code: String,
pub sec_code: String,
pub price: f64,
pub quantity: i64,
pub is_sell: IsSell,
pub value: f64,
pub date: NaiveDate,
pub time: NaiveTime,
}
impl TradeInfo {
pub fn is_valid(&self) -> bool {
self.date != NaiveDate::default() && self.time != NaiveTime::default()
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct TransactionInfo {
pub trans2quik_result: Trans2QuikResult,
pub error_code: i32,
pub reply_code: i32,
pub trans_id: TransId,
pub order_num: u64,
pub reply_message: String,
pub sec_code: String,
pub price: f64,
}
#[derive(Debug)]
enum DecodeLpstrError {
NullPointer,
DecodeError,
InvalidString(NulError),
}
impl fmt::Display for DecodeLpstrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DecodeLpstrError::NullPointer => write!(f, "{:?}", self),
DecodeLpstrError::DecodeError => write!(f, "{:?}", self),
DecodeLpstrError::InvalidString(err) => write!(f, "NulError: {}", err),
}
}
}
impl error::Error for DecodeLpstrError {}
impl From<NulError> for DecodeLpstrError {
fn from(err: NulError) -> DecodeLpstrError {
DecodeLpstrError::InvalidString(err)
}
}
#[derive(Debug)]
enum DateTimeError {
InvalidDate,
InvalidTime,
ParseError(chrono::ParseError),
}
impl fmt::Display for DateTimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DateTimeError::InvalidDate => write!(f, "{:?}", self),
DateTimeError::InvalidTime => write!(f, "{:?}", self),
DateTimeError::ParseError(err) => write!(f, "ParseError: {:?}", err),
}
}
}
impl error::Error for DateTimeError {}
impl From<chrono::ParseError> for DateTimeError {
fn from(err: chrono::ParseError) -> DateTimeError {
DateTimeError::ParseError(err)
}
}
pub struct Terminal {
path_to_quik: String,
library: Arc<Library>,
trans2quik_connect:
unsafe extern "C" fn(*mut c_char, *mut c_long, *mut c_char, c_long) -> c_long,
trans2quik_disconnect: unsafe extern "C" fn(*mut c_long, *mut c_char, c_long) -> c_long,
trans2quik_is_quik_connected: unsafe extern "C" fn(*mut c_long, *mut c_char, c_long) -> c_long,
trans2quik_is_dll_connected: unsafe extern "C" fn(*mut c_long, *mut c_char, c_long) -> c_long,
trans2quik_send_sync_transaction: unsafe extern "C" fn(
trans_str_ptr: *mut c_char,
reply_code_ptr: *mut c_long,
trans_id_ptr: *mut c_long,
order_num_ptr: *mut c_double,
result_message_ptr: *mut c_char,
result_message_len: c_long,
error_code_ptr: *mut c_long,
error_message_ptr: *mut c_char,
error_message_len: c_long,
) -> c_long,
trans2quik_send_async_transaction:
unsafe extern "C" fn(*mut c_char, *mut c_long, *mut c_char, c_long) -> c_long,
trans2quik_set_connection_status_callback: unsafe extern "C" fn(
Trans2QuikConnectionStatusCallback,
*mut c_long,
*mut c_char,
c_long,
) -> c_long,
trans2quik_set_transactions_reply_callback: unsafe extern "C" fn(
Trans2QuikTransactionReplyCallback,
*mut c_long,
*mut c_char,
c_long,
) -> c_long,
trans2quik_subscribe_orders:
unsafe extern "C" fn(class_code: *mut c_char, sec_code: *mut c_char) -> c_long,
trans2quik_subscribe_trades:
unsafe extern "C" fn(class_code: *mut c_char, sec_code: *mut c_char) -> c_long,
trans2quik_start_orders: unsafe extern "C" fn(Trans2QuikOrderStatusCallback),
trans2quik_start_trades: unsafe extern "C" fn(Trans2QuikTradeStatusCallback),
trans2quik_unsubscribe_orders: unsafe extern "C" fn() -> c_long,
trans2quik_unsubscribe_trades: unsafe extern "C" fn() -> c_long,
trans2quik_transaction_reply_sec_code:
unsafe extern "C" fn(trans_reply_descriptor: intptr_t) -> *mut c_char,
trans2quik_transaction_reply_price:
unsafe extern "C" fn(trans_reply_descriptor: intptr_t) -> c_double,
trans2quik_order_date: unsafe extern "C" fn(order_descriptor: intptr_t) -> c_long,
trans2quik_order_time: unsafe extern "C" fn(order_descriptor: intptr_t) -> c_long,
trans2quik_trade_date: unsafe extern "C" fn(trade_descriptor: intptr_t) -> c_long,
trans2quik_trade_time: unsafe extern "C" fn(trade_descriptor: intptr_t) -> c_long,
}
impl Clone for Terminal {
fn clone(&self) -> Self {
Terminal {
path_to_quik: self.path_to_quik.clone(),
library: Arc::clone(&self.library),
trans2quik_connect: self.trans2quik_connect,
trans2quik_disconnect: self.trans2quik_disconnect,
trans2quik_is_quik_connected: self.trans2quik_is_quik_connected,
trans2quik_is_dll_connected: self.trans2quik_is_dll_connected,
trans2quik_send_sync_transaction: self.trans2quik_send_sync_transaction,
trans2quik_send_async_transaction: self.trans2quik_send_async_transaction,
trans2quik_set_connection_status_callback: self
.trans2quik_set_connection_status_callback,
trans2quik_set_transactions_reply_callback: self
.trans2quik_set_transactions_reply_callback,
trans2quik_subscribe_orders: self.trans2quik_subscribe_orders,
trans2quik_subscribe_trades: self.trans2quik_subscribe_trades,
trans2quik_start_orders: self.trans2quik_start_orders,
trans2quik_start_trades: self.trans2quik_start_trades,
trans2quik_unsubscribe_orders: self.trans2quik_unsubscribe_orders,
trans2quik_unsubscribe_trades: self.trans2quik_unsubscribe_trades,
trans2quik_transaction_reply_sec_code: self.trans2quik_transaction_reply_sec_code,
trans2quik_transaction_reply_price: self.trans2quik_transaction_reply_price,
trans2quik_order_date: self.trans2quik_order_date,
trans2quik_order_time: self.trans2quik_order_time,
trans2quik_trade_date: self.trans2quik_trade_date,
trans2quik_trade_time: self.trans2quik_trade_time,
}
}
}
impl Terminal {
pub fn new(path_to_lib: &str, path_to_quik: &str) -> Result<Self, Trans2QuikError> {
let path_to_quik = path_to_quik.to_string();
let library = unsafe { Library::new(path_to_lib)? };
let trans2quik_connect = load_symbol::<
unsafe extern "C" fn(*mut c_char, *mut c_long, *mut c_char, c_long) -> c_long,
>(&library, b"TRANS2QUIK_CONNECT\0")?;
let trans2quik_disconnect = load_symbol::<
unsafe extern "C" fn(*mut c_long, *mut c_char, c_long) -> c_long,
>(&library, b"TRANS2QUIK_DISCONNECT\0")?;
let trans2quik_is_quik_connected = load_symbol::<
unsafe extern "C" fn(*mut c_long, *mut c_char, c_long) -> c_long,
>(&library, b"TRANS2QUIK_IS_QUIK_CONNECTED\0")?;
let trans2quik_is_dll_connected = load_symbol::<
unsafe extern "C" fn(*mut c_long, *mut c_char, c_long) -> c_long,
>(&library, b"TRANS2QUIK_IS_DLL_CONNECTED\0")?;
let trans2quik_send_sync_transaction =
load_symbol::<
unsafe extern "C" fn(
*mut c_char,
*mut c_long,
*mut c_long,
*mut c_double,
*mut c_char,
c_long,
*mut c_long,
*mut c_char,
c_long,
) -> c_long,
>(&library, b"TRANS2QUIK_SEND_SYNC_TRANSACTION\0")?;
let trans2quik_send_async_transaction =
load_symbol::<
unsafe extern "C" fn(*mut c_char, *mut c_long, *mut c_char, c_long) -> c_long,
>(&library, b"TRANS2QUIK_SEND_ASYNC_TRANSACTION\0")?;
let trans2quik_set_connection_status_callback =
load_symbol::<
unsafe extern "C" fn(
Trans2QuikConnectionStatusCallback,
*mut c_long,
*mut c_char,
c_long,
) -> c_long,
>(&library, b"TRANS2QUIK_SET_CONNECTION_STATUS_CALLBACK\0")?;
let trans2quik_set_transactions_reply_callback =
load_symbol::<
unsafe extern "C" fn(
Trans2QuikTransactionReplyCallback,
*mut c_long,
*mut c_char,
c_long,
) -> c_long,
>(&library, b"TRANS2QUIK_SET_TRANSACTIONS_REPLY_CALLBACK\0")?;
let trans2quik_subscribe_orders = load_symbol::<
unsafe extern "C" fn(*mut c_char, *mut c_char) -> c_long,
>(&library, b"TRANS2QUIK_SUBSCRIBE_ORDERS\0")?;
let trans2quik_subscribe_trades = load_symbol::<
unsafe extern "C" fn(*mut c_char, *mut c_char) -> c_long,
>(&library, b"TRANS2QUIK_SUBSCRIBE_TRADES\0")?;
let trans2quik_start_orders = load_symbol::<
unsafe extern "C" fn(Trans2QuikOrderStatusCallback),
>(&library, b"TRANS2QUIK_START_ORDERS\0")?;
let trans2quik_start_trades = load_symbol::<
unsafe extern "C" fn(Trans2QuikTradeStatusCallback),
>(&library, b"TRANS2QUIK_START_TRADES\0")?;
let trans2quik_unsubscribe_orders = load_symbol::<unsafe extern "C" fn() -> c_long>(
&library,
b"TRANS2QUIK_UNSUBSCRIBE_ORDERS\0",
)?;
let trans2quik_unsubscribe_trades = load_symbol::<unsafe extern "C" fn() -> c_long>(
&library,
b"TRANS2QUIK_UNSUBSCRIBE_TRADES\0",
)?;
let trans2quik_transaction_reply_sec_code =
load_symbol::<unsafe extern "C" fn(intptr_t) -> *mut c_char>(
&library,
b"TRANS2QUIK_TRANSACTION_REPLY_SEC_CODE\0",
)?;
let trans2quik_transaction_reply_price = load_symbol::<
unsafe extern "C" fn(intptr_t) -> c_double,
>(&library, b"TRANS2QUIK_ORDER_DATE\0")?;
let trans2quik_order_date = load_symbol::<unsafe extern "C" fn(intptr_t) -> c_long>(
&library,
b"TRANS2QUIK_ORDER_DATE\0",
)?;
let trans2quik_order_time = load_symbol::<unsafe extern "C" fn(intptr_t) -> c_long>(
&library,
b"TRANS2QUIK_ORDER_TIME\0",
)?;
let trans2quik_trade_date = load_symbol::<unsafe extern "C" fn(intptr_t) -> c_long>(
&library,
b"TRANS2QUIK_TRADE_DATE\0",
)?;
let trans2quik_trade_time = load_symbol::<unsafe extern "C" fn(intptr_t) -> c_long>(
&library,
b"TRANS2QUIK_TRADE_TIME\0",
)?;
Ok(Terminal {
path_to_quik,
library: library.into(),
trans2quik_connect,
trans2quik_disconnect,
trans2quik_is_quik_connected,
trans2quik_is_dll_connected,
trans2quik_send_sync_transaction,
trans2quik_send_async_transaction,
trans2quik_set_connection_status_callback,
trans2quik_set_transactions_reply_callback,
trans2quik_subscribe_orders,
trans2quik_subscribe_trades,
trans2quik_start_orders,
trans2quik_start_trades,
trans2quik_unsubscribe_orders,
trans2quik_unsubscribe_trades,
trans2quik_transaction_reply_sec_code,
trans2quik_transaction_reply_price,
trans2quik_order_date,
trans2quik_order_time,
trans2quik_trade_date,
trans2quik_trade_time,
})
}
fn call_trans2quik_function<F>(
&self,
function_name: &str,
func: F,
) -> Result<Trans2QuikResult, Trans2QuikError>
where
F: FnOnce(*mut c_long, *mut c_char, c_long) -> c_long,
{
let mut error_code: c_long = 0;
let error_code_ptr = &mut error_code as *mut c_long;
let mut error_message = vec![0 as c_char; 256];
let error_message_ptr = error_message.as_mut_ptr() as *mut c_char;
let function_result = func(
error_code_ptr,
error_message_ptr,
error_message.len() as c_long,
);
let error_message = match extract_string_from_vec(error_message) {
Ok(message) => message,
Err(e) => {
error!("Warning: error_message contains invalid UTF-8: {}", e);
String::from("Invalid UTF-8 in error_message")
}
};
let trans2quik_result = Trans2QuikResult::from(function_result);
info!(
"{} -> {:?}, error_code: {}, error_message: {}",
function_name, trans2quik_result, error_code, error_message
);
Ok(trans2quik_result)
}
pub fn connect(&self) -> Result<Trans2QuikResult, Trans2QuikError> {
let connection_str = CString::new(&*self.path_to_quik)?;
let connection_str_ptr = connection_str.as_ptr() as *mut c_char;
let function = |error_code_ptr: *mut c_long,
error_message_ptr: *mut c_char,
error_message_len: c_long| unsafe {
(self.trans2quik_connect)(
connection_str_ptr,
error_code_ptr,
error_message_ptr,
error_message_len,
)
};
self.call_trans2quik_function("TRANS2QUIK_CONNECT", function)
}
pub fn disconnect(&self) -> Result<Trans2QuikResult, Trans2QuikError> {
let function = |error_code: *mut c_long,
error_message: *mut c_char,
error_message_len: c_long| unsafe {
(self.trans2quik_disconnect)(error_code, error_message, error_message_len)
};
self.call_trans2quik_function("TRANS2QUIK_DISCONNECT", function)
}
pub fn is_quik_connected(&self) -> Result<Trans2QuikResult, Trans2QuikError> {
let function = |error_code: *mut c_long,
error_message: *mut c_char,
error_message_len: c_long| unsafe {
(self.trans2quik_is_quik_connected)(error_code, error_message, error_message_len)
};
self.call_trans2quik_function("TRANS2QUIK_IS_QUIK_CONNECTED", function)
}
pub fn is_dll_connected(&self) -> Result<Trans2QuikResult, Trans2QuikError> {
let function = |error_code: *mut c_long,
error_message: *mut c_char,
error_message_len: c_long| unsafe {
(self.trans2quik_is_dll_connected)(error_code, error_message, error_message_len)
};
self.call_trans2quik_function("TRANS2QUIK_IS_DLL_CONNECTED", function)
}
#[allow(dead_code)]
pub fn send_sync_transaction(
&self,
transaction_str: &str,
) -> Result<Trans2QuikResult, Trans2QuikError> {
let trans_str = CString::new(transaction_str)?;
let trans_str_ptr = trans_str.as_ptr() as *mut c_char;
let mut reply_code: c_long = 0;
let reply_code_ptr = &mut reply_code as *mut c_long;
let mut trans_id: c_long = 0;
let trans_id_ptr = &mut trans_id as *mut c_long;
let mut order_num: c_double = 0.0;
let order_num_ptr = &mut order_num as *mut c_double;
let mut result_message = vec![0 as c_char; 256];
let result_message_ptr = result_message.as_mut_ptr() as *mut c_char;
let mut error_code: c_long = 0;
let error_code_ptr = &mut error_code as *mut c_long;
let mut error_message = vec![0 as c_char; 256];
let error_message_ptr = error_message.as_mut_ptr() as *mut c_char;
let function_result = unsafe {
(self.trans2quik_send_sync_transaction)(
trans_str_ptr,
reply_code_ptr,
trans_id_ptr,
order_num_ptr,
result_message_ptr,
result_message.len() as c_long,
error_code_ptr,
error_message_ptr,
error_message.len() as c_long,
)
};
let result_message = match extract_string_from_vec(result_message) {
Ok(message) => message,
Err(e) => {
error!("Warning: result_message contains invalid UTF-8: {}", e);
String::from("Invalid UTF-8 in result_message")
}
};
let error_message = match extract_string_from_vec(error_message) {
Ok(message) => message,
Err(e) => {
error!("Warning: error_message contains invalid UTF-8: {}", e);
String::from("Invalid UTF-8 in error_message")
}
};
let trans2quik_result = Trans2QuikResult::from(function_result);
info!("TRANS2QUIK_SEND_SYNC_TRANSACTION -> {:?}, reply_code: {}, trans_id: {}, order_num: {}, result_message: {}, error_code: {}, error_message: {}",
trans2quik_result,
reply_code,
trans_id,
order_num,
result_message,
error_code,
error_message,
);
Ok(trans2quik_result)
}
pub fn send_async_transaction(
&self,
transaction_str: &str,
) -> Result<Trans2QuikResult, Trans2QuikError> {
let trans_str = CString::new(transaction_str)?;
let trans_str_ptr = trans_str.as_ptr() as *mut c_char;
let mut error_code: c_long = 0;
let error_code_ptr = &mut error_code as *mut c_long;
let mut error_message = vec![0 as c_char; 256];
let error_message_ptr = error_message.as_mut_ptr() as *mut c_char;
let function_result = unsafe {
(self.trans2quik_send_async_transaction)(
trans_str_ptr,
error_code_ptr,
error_message_ptr,
error_message.len() as c_long,
)
};
let error_message = match extract_string_from_vec(error_message) {
Ok(message) => message,
Err(e) => {
error!("Warning: error_message contains invalid UTF-8: {}", e);
String::from("Invalid UTF-8 in error_message")
}
};
let trans2quik_result = Trans2QuikResult::from(function_result);
info!(
"TRANS2QUIK_SEND_ASYNC_TRANSACTION -> {:?}, error_code: {}, error_message: {}",
trans2quik_result, error_code, error_message,
);
Ok(trans2quik_result)
}
pub fn set_connection_status_callback(&self) -> Result<Trans2QuikResult, Trans2QuikError> {
let mut error_code: c_long = 0;
let error_code_ptr = &mut error_code as *mut c_long;
let mut error_message = vec![0 as c_char; 256];
let error_message_ptr = error_message.as_mut_ptr() as *mut c_char;
let function_result = unsafe {
(self.trans2quik_set_connection_status_callback)(
connection_status_callback,
error_code_ptr,
error_message_ptr,
error_message.len() as c_long,
)
};
let error_message = match extract_string_from_vec(error_message) {
Ok(message) => message,
Err(e) => {
error!("Warning: error_message contains invalid UTF-8: {}", e);
String::from("Invalid UTF-8 in error_message")
}
};
let trans2quik_result = Trans2QuikResult::from(function_result);
info!(
"TRANS2QUIK_SET_CONNECTION_STATUS_CALLBACK -> {:?}, error_code: {}, error_message: {}",
trans2quik_result, error_code, error_message
);
Ok(trans2quik_result)
}
pub fn set_transactions_reply_callback(&self) -> Result<Trans2QuikResult, Trans2QuikError> {
let mut error_code: c_long = 0;
let error_code_ptr = &mut error_code as *mut c_long;
let mut error_message = vec![0 as c_char; 256];
let error_message_ptr = error_message.as_mut_ptr() as *mut c_char;
let function_result = unsafe {
(self.trans2quik_set_transactions_reply_callback)(
transaction_reply_callback,
error_code_ptr,
error_message_ptr,
error_message.len() as c_long,
)
};
let error_message = match extract_string_from_vec(error_message) {
Ok(message) => message,
Err(e) => {
error!("Warning: error_message contains invalid UTF-8: {}", e);
String::from("Invalid UTF-8 in error_message")
}
};
let trans2quik_result = Trans2QuikResult::from(function_result);
info!(
"TRANS2QUIK_SET_TRANSACTIONS_REPLY_CALLBACK -> {:?}, error_code: {}, error_message: {}",
trans2quik_result, error_code, error_message
);
Ok(trans2quik_result)
}
pub fn subscribe_orders(
&self,
class_code: &str,
sec_code: &str,
) -> Result<Trans2QuikResult, Trans2QuikError> {
let class_code_c = CString::new(class_code)?;
let class_code_ptr = class_code_c.as_ptr() as *mut c_char;
let sec_code_c = CString::new(sec_code)?;
let sec_code_ptr = sec_code_c.as_ptr() as *mut c_char;
let function_result =
unsafe { (self.trans2quik_subscribe_orders)(class_code_ptr, sec_code_ptr) };
let trans2quik_result = Trans2QuikResult::from(function_result);
info!(
"TRANS2QUIK_SUBSCRIBE_ORDERS -> {:?}, class_code: {}, sec_code: {}",
trans2quik_result, class_code, sec_code
);
Ok(trans2quik_result)
}
pub fn subscribe_trades(
&self,
class_code: &str,
sec_code: &str,
) -> Result<Trans2QuikResult, Trans2QuikError> {
let class_code_c = CString::new(class_code)?;
let class_code_ptr = class_code_c.as_ptr() as *mut c_char;
let sec_code_c = CString::new(sec_code)?;
let sec_code_ptr = sec_code_c.as_ptr() as *mut c_char;
let function_result =
unsafe { (self.trans2quik_subscribe_trades)(class_code_ptr, sec_code_ptr) };
let trans2quik_result = Trans2QuikResult::from(function_result);
info!(
"TRANS2QUIK_SUBSCRIBE_TRADES -> {:?}, class_code: {}, sec_code: {}",
trans2quik_result, class_code, sec_code
);
Ok(trans2quik_result)
}
pub fn start_orders(&self) {
unsafe { (self.trans2quik_start_orders)(order_status_callback) }
}
pub fn start_trades(&self) {
let terminal_clone = (*self).clone();
let terminal_instance = Arc::new(Mutex::new(terminal_clone));
*TERMINAL_INSTANCE.lock().unwrap() = Some(terminal_instance);
unsafe { (self.trans2quik_start_trades)(trade_status_callback) }
}
pub fn unsubscribe_orders(&self) -> Result<Trans2QuikResult, Trans2QuikError> {
let function_result = unsafe { (self.trans2quik_unsubscribe_orders)() };
let trans2quik_result = Trans2QuikResult::from(function_result);
info!("TRANS2QUIK_UNSUBSCRIBE_ORDERS -> {:?}", trans2quik_result);
Ok(trans2quik_result)
}
pub fn unsubscribe_trades(&self) -> Result<Trans2QuikResult, Trans2QuikError> {
let function_result = unsafe { (self.trans2quik_unsubscribe_trades)() };
let trans2quik_result = Trans2QuikResult::from(function_result);
info!("TRANS2QUIK_UNSUBSCRIBE_TRADES -> {:?}", trans2quik_result);
Ok(trans2quik_result)
}
}
fn load_symbol<T>(library: &Library, name: &[u8]) -> Result<T, LibloadingError>
where
T: Copy,
{
unsafe {
let symbol: Symbol<T> = library.get(name)?;
Ok(*symbol)
}
}
fn extract_string_from_vec(vec_i8: Vec<i8>) -> Result<String, FromUtf8Error> {
let vec_u8: Vec<u8> = vec_i8.into_iter().map(|byte| byte as u8).collect();
let null_pos = vec_u8
.iter()
.position(|&byte| byte == 0)
.unwrap_or(vec_u8.len());
let vec_u8_trimmed = &vec_u8[..null_pos];
let (decoded_str, _, _) = WINDOWS_1251.decode(vec_u8_trimmed);
Ok(decoded_str.into_owned())
}
fn decode_lpstr(code: *mut c_char) -> Result<String, DecodeLpstrError> {
if code.is_null() {
return Err(DecodeLpstrError::NullPointer);
}
let c_str = unsafe { CStr::from_ptr(code) };
let bytes = c_str.to_bytes();
let (decoded_str, _, had_errors) = WINDOWS_1251.decode(bytes);
if had_errors {
return Err(DecodeLpstrError::DecodeError);
}
Ok(decoded_str.into_owned())
}
fn format_date(date: i32) -> Result<NaiveDate, DateTimeError> {
if date <= 0 {
return Err(DateTimeError::InvalidDate);
}
let date_str = format!("{:08}", date);
let naive_date = NaiveDate::parse_from_str(&date_str, "%Y%m%d")?;
Ok(naive_date)
}
fn format_time(time: i32) -> Result<NaiveTime, DateTimeError> {
if time <= 0 {
return Err(DateTimeError::InvalidTime);
}
let time_str = format!("{:06}", time);
let naive_time = NaiveTime::parse_from_str(&time_str, "%H%M%S")?;
Ok(naive_time)
}
unsafe extern "C" fn connection_status_callback(
connection_event: c_long,
error_code: c_long,
error_message: *mut c_char,
) {
let error_message = if !error_message.is_null() {
let c_str = CStr::from_ptr(error_message);
let bytes = c_str.to_bytes();
let (decoded_str, _, _) = WINDOWS_1251.decode(bytes);
decoded_str.into_owned().to_owned()
} else {
String::from("error_message is null")
};
let trans2quik_result = Trans2QuikResult::from(connection_event);
info!(
"TRANS2QUIK_CONNECTION_STATUS_CALLBACK -> {:?}, error_code: {}, error_message: {}",
trans2quik_result, error_code, error_message
);
}
unsafe extern "C" fn transaction_reply_callback(
result_code: c_long,
error_code: c_long,
reply_code: c_long,
trans_id: c_long,
order_num: c_ulonglong,
reply_message: *mut c_char,
trans_reply_descriptor: intptr_t,
) {
if let Some(terminal_instance) = TERMINAL_INSTANCE.lock().unwrap().as_ref() {
let terminal = terminal_instance.lock().unwrap();
let trans2quik_result = Trans2QuikResult::from(result_code);
let trans_id = TransId::from(trans_id);
let reply_message = match decode_lpstr(reply_message) {
Ok(reply_message) => reply_message,
Err(e) => {
let error = format!("decode reply_message error: {:?}", e);
error!("{}", error);
error
}
};
let sec_code = (terminal.trans2quik_transaction_reply_sec_code)(trans_reply_descriptor);
let sec_code = match decode_lpstr(sec_code) {
Ok(sec_code) => sec_code,
Err(e) => {
let error = format!("decode sec_code error: {:?}", e);
error!("{}", error);
error
}
};
let price = (terminal.trans2quik_transaction_reply_price)(trans_reply_descriptor);
info!("TRANS2QUIK_TRANSACTION_REPLY_CALLBACK -> {:?}, error_code: {}, reply_code: {}, trans_id: {:?}, order_num: {}, reply_message: {}, sec_code: {}, price: {}", trans2quik_result, error_code, reply_code, trans_id, order_num, reply_message, sec_code, price);
if let Some(sender) = TRANSACTION_REPLY_SENDER.lock().unwrap().as_ref() {
let transaction_info = TransactionInfo {
trans2quik_result,
error_code,
reply_code,
trans_id,
order_num,
reply_message,
sec_code,
price,
};
if let Err(err) = sender.send(transaction_info) {
error!("transaction_reply_callback send error: {}", err);
}
} else {
error!("TRANSACTION_REPLY_SENDER is not initialized");
}
} else {
error!("TERMINAL_INSTANCE is not initialized");
}
}
unsafe extern "C" fn order_status_callback(
mode: c_long,
trans_id: c_long,
order_num: c_ulonglong,
class_code: *mut c_char,
sec_code: *mut c_char,
price: c_double,
balance: i64,
value: c_double,
is_sell: c_long,
status: c_long,
order_descriptor: intptr_t,
) {
if let Some(terminal_instance) = TERMINAL_INSTANCE.lock().unwrap().as_ref() {
let terminal = terminal_instance.lock().unwrap();
let mode = Mode::from(mode);
let trans_id = TransId::from(trans_id);
let class_code = match decode_lpstr(class_code) {
Ok(class_code) => class_code,
Err(e) => {
let error = format!("decode class_code error: {:?}", e);
error!("{}", error);
error
}
};
let sec_code = match decode_lpstr(sec_code) {
Ok(sec_code) => sec_code,
Err(e) => {
let error = format!("decode sec_code error: {:?}", e);
error!("{}", error);
error
}
};
let is_sell = IsSell::from(is_sell);
let status = Status::from(status);
let date = (terminal.trans2quik_order_date)(order_descriptor);
let date = match format_date(date) {
Ok(date) => date,
Err(e) => {
error!("format_date error: {}", e);
NaiveDate::default()
}
};
let time = (terminal.trans2quik_order_time)(order_descriptor);
let time = match format_time(time) {
Ok(time) => time,
Err(e) => {
error!("format_time error: {}", e);
NaiveTime::default()
}
};
info!("TRANS2QUIK_ORDER_STATUS_CALLBACK -> mode: {:?}, trans_id: {:?}, order_num: {}, class_code: {}, sec_code: {}, price: {}, balance: {}, value: {}, is_sell: {:?}, status: {:?}, date: {}, time: {}", mode, trans_id, order_num, class_code, sec_code, price, balance, value, is_sell, status, date, time);
if let Some(sender) = ORDER_STATUS_SENDER.lock().unwrap().as_ref() {
let order_info = OrderInfo {
mode,
trans_id,
order_num,
class_code,
sec_code,
price,
balance,
value,
is_sell,
status,
date,
time,
};
if let Err(err) = sender.send(order_info) {
error!("order_status_callback send error: {}", err);
}
} else {
error!("ORDER_SENDER is not initialized");
}
} else {
error!("TERMINAL_INSTANCE is not initialized");
}
}
unsafe extern "C" fn trade_status_callback(
mode: c_long,
trade_num: c_ulonglong,
order_num: c_ulonglong,
class_code: *mut c_char,
sec_code: *mut c_char,
price: c_double,
quantity: i64,
is_sell: c_long,
value: c_double,
trade_descriptor: intptr_t,
) {
if let Some(terminal_instance) = TERMINAL_INSTANCE.lock().unwrap().as_ref() {
let terminal = terminal_instance.lock().unwrap();
let mode = Mode::from(mode);
let class_code = match decode_lpstr(class_code) {
Ok(class_code) => class_code,
Err(e) => {
let error = format!("decode class_code error: {:?}", e);
error!("{}", error);
error
}
};
let sec_code = match decode_lpstr(sec_code) {
Ok(sec_code) => sec_code,
Err(e) => {
let error = format!("decode sec_code error: {:?}", e);
error!("{}", error);
error
}
};
let is_sell = IsSell::from(is_sell);
let date = (terminal.trans2quik_trade_date)(trade_descriptor);
let date = match format_date(date) {
Ok(date) => date,
Err(e) => {
error!("format_date error: {}", e);
NaiveDate::default()
}
};
let time = (terminal.trans2quik_trade_time)(trade_descriptor);
let time = match format_time(time) {
Ok(time) => time,
Err(e) => {
error!("format_time error: {}", e);
NaiveTime::default()
}
};
info!("TRANS2QUIK_TRADE_STATUS_CALLBACK -> mode: {:?}, trade_num: {}, order_num: {}, class_code: {}, sec_code: {}, price: {}, quantity: {}, is_sell: {:?}, value: {}, date: {}, time: {}", mode, trade_num, order_num, class_code, sec_code, price, quantity, is_sell, value, date, time);
if let Some(sender) = TRADE_STATUS_SENDER.lock().unwrap().as_ref() {
let trade_info = TradeInfo {
mode,
trade_num,
order_num,
class_code,
sec_code,
price,
quantity,
is_sell,
value,
date,
time,
};
if let Err(err) = sender.send(trade_info) {
error!("trade_status_callback send error: {}", err);
}
} else {
error!("TRADE_SENDER is not initialized");
}
} else {
error!("TERMINAL_INSTANCE is not initialized");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trans2quik_result_conversion() {
assert_eq!(Trans2QuikResult::from(0), Trans2QuikResult::Success);
assert_eq!(Trans2QuikResult::from(1), Trans2QuikResult::Failed);
assert_eq!(
Trans2QuikResult::from(2),
Trans2QuikResult::TerminalNotFound
);
assert_eq!(
Trans2QuikResult::from(3),
Trans2QuikResult::DllVersionNotSupported
);
assert_eq!(
Trans2QuikResult::from(4),
Trans2QuikResult::AlreadyConnectedToQuik
);
assert_eq!(Trans2QuikResult::from(5), Trans2QuikResult::WrongSyntax);
assert_eq!(
Trans2QuikResult::from(6),
Trans2QuikResult::QuikNotConnected
);
assert_eq!(Trans2QuikResult::from(7), Trans2QuikResult::DllNotConnected);
assert_eq!(Trans2QuikResult::from(8), Trans2QuikResult::QuikConnected);
assert_eq!(
Trans2QuikResult::from(9),
Trans2QuikResult::QuikDisconnected
);
assert_eq!(Trans2QuikResult::from(10), Trans2QuikResult::DllConnected);
assert_eq!(
Trans2QuikResult::from(11),
Trans2QuikResult::DllDisconnected
);
assert_eq!(
Trans2QuikResult::from(12),
Trans2QuikResult::MemoryAllocationError
);
assert_eq!(
Trans2QuikResult::from(13),
Trans2QuikResult::WrongConnectionHandle
);
assert_eq!(
Trans2QuikResult::from(14),
Trans2QuikResult::WrongInputParams
);
assert_eq!(Trans2QuikResult::from(999), Trans2QuikResult::Unknown);
}
#[test]
fn test_trans2quikerror_from_libloadingerror() {
let libloading_error = unsafe {
match Library::new("/invalid/path/to/nonexistent/library") {
Ok(_) => panic!("Expected an error, but library loaded successfully."),
Err(e) => e,
}
};
let trans2quik_error: Trans2QuikError = Trans2QuikError::from(libloading_error);
if let Trans2QuikError::LibLoading(_) = trans2quik_error {
} else {
panic!("Expected Trans2QuikError::LibLoading variant.");
}
}
#[test]
fn test_trans2quikerror_from_nulerror() {
let nul_err = CString::new("Invalid\0String").unwrap_err();
let trans2quik_error: Trans2QuikError = Trans2QuikError::from(nul_err);
matches!(trans2quik_error, Trans2QuikError::NulError(_));
}
#[test]
fn test_display_for_trans2quikerror() {
let nul_err = CString::new("Invalid\0String").unwrap_err();
let trans2quik_error_nul: Trans2QuikError = Trans2QuikError::from(nul_err);
let expected_display_nul = format!("{:?}", trans2quik_error_nul);
assert_eq!(expected_display_nul, format!("{}", trans2quik_error_nul));
let libloading_error = unsafe {
match Library::new("/invalid/path/to/nonexistent/lib") {
Ok(_) => panic!("Expected an error, but library loaded successfully"),
Err(e) => e,
}
};
let trans2quik_error_lib: Trans2QuikError = Trans2QuikError::from(libloading_error);
let expected_display_lib = format!("{:?}", trans2quik_error_lib);
assert_eq!(expected_display_lib, format!("{}", trans2quik_error_lib));
}
}