use crate::wire::{
DepthMarketData, ErrorField, InputOrder, InputOrderAction, InstrumentCommissionRateField,
InstrumentField, InstrumentMarginRateField, InstrumentStatusField, InvestorPositionField,
OrderField, QryInstrumentCommissionRate, QryInstrumentMarginRate, TradeField,
TradingAccountField,
};
use std::ffi::{CStr, CString, c_char, c_int};
unsafe fn cstr_or_empty(p: *const c_char) -> String {
if p.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
}
}
fn cstring(value: &str) -> CString {
CString::new(value)
.unwrap_or_else(|_| panic!("ek-ctp native pod: string field contains an interior NUL byte"))
}
#[repr(C)]
pub struct EkErrorField {
pub error_id: c_int,
pub error_msg: *const c_char,
}
#[repr(C)]
pub struct EkDepthMarketData {
pub instrument_id: *const c_char,
pub exchange_id: *const c_char,
pub last_price: f64,
pub volume: f64,
pub turnover: f64,
pub open_interest: f64,
pub upper_limit_price: f64,
pub lower_limit_price: f64,
pub open_price: f64,
pub highest_price: f64,
pub lowest_price: f64,
pub pre_close_price: f64,
pub bid_price1: f64,
pub bid_price2: f64,
pub bid_price3: f64,
pub bid_price4: f64,
pub bid_price5: f64,
pub ask_price1: f64,
pub ask_price2: f64,
pub ask_price3: f64,
pub ask_price4: f64,
pub ask_price5: f64,
pub bid_volume1: f64,
pub bid_volume2: f64,
pub bid_volume3: f64,
pub bid_volume4: f64,
pub bid_volume5: f64,
pub ask_volume1: f64,
pub ask_volume2: f64,
pub ask_volume3: f64,
pub ask_volume4: f64,
pub ask_volume5: f64,
pub update_time: *const c_char,
pub update_millisec: i64,
pub trading_day: *const c_char,
pub action_day: *const c_char,
}
#[repr(C)]
pub struct EkInstrumentField {
pub instrument_id: *const c_char,
pub exchange_id: *const c_char,
pub instrument_name: *const c_char,
pub product_class: *const c_char,
pub volume_multiple: f64,
pub price_tick: f64,
pub max_limit_order_volume: f64,
pub min_limit_order_volume: f64,
pub max_market_order_volume: f64,
pub min_market_order_volume: f64,
pub delivery_year: i64,
pub delivery_month: i64,
pub product_id: *const c_char,
pub is_trading: i64,
pub open_date: *const c_char,
}
#[repr(C)]
pub struct EkOrderField {
pub instrument_id: *const c_char,
pub exchange_id: *const c_char,
pub order_ref: *const c_char,
pub front_id: i64,
pub session_id: i64,
pub order_sys_id: *const c_char,
pub direction: *const c_char,
pub comb_offset_flag: *const c_char,
pub order_price_type: *const c_char,
pub time_condition: *const c_char,
pub volume_condition: *const c_char,
pub limit_price: f64,
pub volume_total_original: f64,
pub volume_total: f64,
pub volume_traded: f64,
pub order_status: *const c_char,
pub insert_date: *const c_char,
pub insert_time: *const c_char,
}
#[repr(C)]
pub struct EkTradeField {
pub instrument_id: *const c_char,
pub exchange_id: *const c_char,
pub order_sys_id: *const c_char,
pub trade_id: *const c_char,
pub direction: *const c_char,
pub offset_flag: *const c_char,
pub price: f64,
pub volume: f64,
pub trade_date: *const c_char,
pub trade_time: *const c_char,
}
#[repr(C)]
pub struct EkInvestorPositionField {
pub instrument_id: *const c_char,
pub exchange_id: *const c_char,
pub posi_direction: *const c_char,
pub yd_position: f64,
pub position: f64,
pub today_position: f64,
pub position_cost: f64,
pub position_profit: f64,
pub short_frozen: f64,
pub long_frozen: f64,
}
#[repr(C)]
pub struct EkTradingAccountField {
pub account_id: *const c_char,
pub balance: f64,
pub frozen_margin: f64,
pub frozen_cash: f64,
pub frozen_commission: f64,
pub available: f64,
}
#[repr(C)]
pub struct EkInputOrder {
pub instrument_id: *const c_char,
pub exchange_id: *const c_char,
pub limit_price: f64,
pub volume_total_original: i64,
pub order_price_type: *const c_char,
pub direction: *const c_char,
pub comb_offset_flag: *const c_char,
pub order_ref: *const c_char,
pub investor_id: *const c_char,
pub user_id: *const c_char,
pub broker_id: *const c_char,
pub comb_hedge_flag: *const c_char,
pub contingent_condition: *const c_char,
pub force_close_reason: *const c_char,
pub is_auto_suspend: c_int,
pub time_condition: *const c_char,
pub volume_condition: *const c_char,
pub min_volume: i64,
}
#[repr(C)]
pub struct EkInputOrderAction {
pub instrument_id: *const c_char,
pub exchange_id: *const c_char,
pub order_ref: *const c_char,
pub front_id: i64,
pub session_id: i64,
pub action_flag: *const c_char,
pub broker_id: *const c_char,
pub investor_id: *const c_char,
}
#[repr(C)]
pub struct EkInstrumentMarginRate {
pub investor_range: *const c_char,
pub broker_id: *const c_char,
pub investor_id: *const c_char,
pub hedge_flag: *const c_char,
pub long_margin_ratio_by_money: f64,
pub long_margin_ratio_by_volume: f64,
pub short_margin_ratio_by_money: f64,
pub short_margin_ratio_by_volume: f64,
pub is_relative: c_int,
pub exchange_id: *const c_char,
pub invest_unit_id: *const c_char,
pub instrument_id: *const c_char,
}
#[repr(C)]
pub struct EkInstrumentCommissionRate {
pub investor_range: *const c_char,
pub broker_id: *const c_char,
pub investor_id: *const c_char,
pub open_ratio_by_money: f64,
pub open_ratio_by_volume: f64,
pub close_ratio_by_money: f64,
pub close_ratio_by_volume: f64,
pub close_today_ratio_by_money: f64,
pub close_today_ratio_by_volume: f64,
pub exchange_id: *const c_char,
pub biz_type: *const c_char,
pub invest_unit_id: *const c_char,
pub instrument_id: *const c_char,
}
#[repr(C)]
pub struct EkInstrumentStatus {
pub exchange_id: *const c_char,
pub settlement_group_id: *const c_char,
pub instrument_status: *const c_char,
pub trading_segment_sn: c_int,
pub enter_time: *const c_char,
pub enter_reason: *const c_char,
pub exchange_inst_id: *const c_char,
pub instrument_id: *const c_char,
}
#[repr(C)]
pub struct EkQryInstrumentMarginRate {
pub broker_id: *const c_char,
pub investor_id: *const c_char,
pub hedge_flag: *const c_char,
pub exchange_id: *const c_char,
pub invest_unit_id: *const c_char,
pub instrument_id: *const c_char,
}
#[repr(C)]
pub struct EkQryInstrumentCommissionRate {
pub broker_id: *const c_char,
pub investor_id: *const c_char,
pub exchange_id: *const c_char,
pub invest_unit_id: *const c_char,
pub instrument_id: *const c_char,
}
unsafe extern "C" {
fn ek_gbk_to_utf8(
gbk: *const c_char,
gbk_len: usize,
out: *mut c_char,
out_cap: usize,
) -> usize;
fn ek_utf8_to_gbk(utf8: *const c_char, out: *mut c_char, out_cap: usize) -> usize;
}
#[cfg(test)]
unsafe extern "C" {
fn ek_pod_sizeof_error() -> c_int;
fn ek_pod_sizeof_depth() -> c_int;
fn ek_pod_sizeof_instrument() -> c_int;
fn ek_pod_sizeof_order() -> c_int;
fn ek_pod_sizeof_trade() -> c_int;
fn ek_pod_sizeof_position() -> c_int;
fn ek_pod_sizeof_account() -> c_int;
fn ek_pod_sizeof_input_order() -> c_int;
fn ek_pod_sizeof_input_order_action() -> c_int;
fn ek_pod_sizeof_margin_rate() -> c_int;
fn ek_pod_sizeof_commission_rate() -> c_int;
fn ek_pod_sizeof_instrument_status() -> c_int;
fn ek_pod_sizeof_qry_margin_rate() -> c_int;
fn ek_pod_sizeof_qry_commission_rate() -> c_int;
fn ek_pod_selftest_error(out: *mut EkErrorField, scratch: *mut c_char, cap: usize);
fn ek_shim_selftest_copy_field(out: *mut c_char, cap: usize);
fn ek_shim_selftest_copy_field_truncated(out: *mut c_char, cap: usize);
fn ek_pod_selftest_depth(out: *mut EkDepthMarketData, scratch: *mut c_char, cap: usize);
fn ek_pod_selftest_instrument(out: *mut EkInstrumentField, scratch: *mut c_char, cap: usize);
fn ek_pod_selftest_order(out: *mut EkOrderField, scratch: *mut c_char, cap: usize);
fn ek_pod_selftest_trade(out: *mut EkTradeField, scratch: *mut c_char, cap: usize);
fn ek_pod_selftest_position(
out: *mut EkInvestorPositionField,
scratch: *mut c_char,
cap: usize,
);
fn ek_pod_selftest_account(out: *mut EkTradingAccountField, scratch: *mut c_char, cap: usize);
fn ek_pod_selftest_input_order(out: *mut EkInputOrder, scratch: *mut c_char, cap: usize);
fn ek_pod_selftest_io_roundtrip_input_order(
input: *const EkInputOrder,
out: *mut EkInputOrder,
scratch: *mut c_char,
cap: usize,
);
fn ek_pod_selftest_io_roundtrip_input_order_action(
input: *const EkInputOrderAction,
out: *mut EkInputOrderAction,
scratch: *mut c_char,
cap: usize,
);
fn ek_ctp_verify_input_order_action(input: *const EkInputOrderAction) -> c_int;
fn ek_pod_selftest_margin_rate(
out: *mut EkInstrumentMarginRate,
scratch: *mut c_char,
cap: usize,
);
fn ek_pod_selftest_commission_rate(
out: *mut EkInstrumentCommissionRate,
scratch: *mut c_char,
cap: usize,
);
fn ek_pod_selftest_instrument_status(
out: *mut EkInstrumentStatus,
scratch: *mut c_char,
cap: usize,
);
fn ek_ctp_verify_qry_margin_rate(input: *const EkQryInstrumentMarginRate) -> c_int;
fn ek_ctp_verify_qry_commission_rate(input: *const EkQryInstrumentCommissionRate) -> c_int;
}
pub fn gbk_to_utf8(gbk: &[u8]) -> String {
if gbk.is_empty() {
return String::new();
}
let mut out = vec![0u8; gbk.len() * 3 + 1];
let n = unsafe {
ek_gbk_to_utf8(
gbk.as_ptr() as *const c_char,
gbk.len(),
out.as_mut_ptr() as *mut c_char,
out.len(),
)
};
out.truncate(n.min(out.len()));
String::from_utf8_lossy(&out).into_owned()
}
pub fn utf8_to_gbk(text: &str) -> Vec<u8> {
if text.is_empty() {
return Vec::new();
}
let Ok(src) = CString::new(text) else {
return Vec::new();
};
let mut out = vec![0u8; text.len() * 2 + 1];
let n = unsafe { ek_utf8_to_gbk(src.as_ptr(), out.as_mut_ptr() as *mut c_char, out.len()) };
out.truncate(n.min(out.len()));
out
}
impl EkErrorField {
pub fn to_wire(&self) -> ErrorField {
unsafe {
ErrorField {
error_id: self.error_id,
error_msg: cstr_or_empty(self.error_msg),
}
}
}
}
impl EkDepthMarketData {
pub fn to_wire(&self) -> DepthMarketData {
unsafe {
DepthMarketData {
instrument_id: cstr_or_empty(self.instrument_id),
exchange_id: cstr_or_empty(self.exchange_id),
last_price: self.last_price,
volume: self.volume,
turnover: self.turnover,
open_interest: self.open_interest,
upper_limit_price: self.upper_limit_price,
lower_limit_price: self.lower_limit_price,
open_price: self.open_price,
highest_price: self.highest_price,
lowest_price: self.lowest_price,
pre_close_price: self.pre_close_price,
bid_price1: self.bid_price1,
bid_price2: self.bid_price2,
bid_price3: self.bid_price3,
bid_price4: self.bid_price4,
bid_price5: self.bid_price5,
ask_price1: self.ask_price1,
ask_price2: self.ask_price2,
ask_price3: self.ask_price3,
ask_price4: self.ask_price4,
ask_price5: self.ask_price5,
bid_volume1: self.bid_volume1,
bid_volume2: self.bid_volume2,
bid_volume3: self.bid_volume3,
bid_volume4: self.bid_volume4,
bid_volume5: self.bid_volume5,
ask_volume1: self.ask_volume1,
ask_volume2: self.ask_volume2,
ask_volume3: self.ask_volume3,
ask_volume4: self.ask_volume4,
ask_volume5: self.ask_volume5,
update_time: cstr_or_empty(self.update_time),
update_millisec: self.update_millisec,
trading_day: cstr_or_empty(self.trading_day),
action_day: cstr_or_empty(self.action_day),
}
}
}
}
impl EkInstrumentField {
pub fn to_wire(&self) -> InstrumentField {
unsafe {
InstrumentField {
instrument_id: cstr_or_empty(self.instrument_id),
exchange_id: cstr_or_empty(self.exchange_id),
instrument_name: cstr_or_empty(self.instrument_name),
product_class: cstr_or_empty(self.product_class),
volume_multiple: self.volume_multiple,
price_tick: self.price_tick,
max_limit_order_volume: self.max_limit_order_volume,
min_limit_order_volume: self.min_limit_order_volume,
max_market_order_volume: self.max_market_order_volume,
min_market_order_volume: self.min_market_order_volume,
delivery_year: self.delivery_year,
delivery_month: self.delivery_month,
product_id: cstr_or_empty(self.product_id),
is_trading: self.is_trading,
open_date: cstr_or_empty(self.open_date),
}
}
}
}
impl EkOrderField {
pub fn to_wire(&self) -> OrderField {
unsafe {
OrderField {
instrument_id: cstr_or_empty(self.instrument_id),
exchange_id: cstr_or_empty(self.exchange_id),
order_ref: cstr_or_empty(self.order_ref),
front_id: self.front_id,
session_id: self.session_id,
order_sys_id: cstr_or_empty(self.order_sys_id),
direction: cstr_or_empty(self.direction),
comb_offset_flag: cstr_or_empty(self.comb_offset_flag),
order_price_type: cstr_or_empty(self.order_price_type),
time_condition: cstr_or_empty(self.time_condition),
volume_condition: cstr_or_empty(self.volume_condition),
limit_price: self.limit_price,
volume_total_original: self.volume_total_original,
volume_total: self.volume_total,
volume_traded: self.volume_traded,
order_status: cstr_or_empty(self.order_status),
insert_date: cstr_or_empty(self.insert_date),
insert_time: cstr_or_empty(self.insert_time),
}
}
}
}
impl EkTradeField {
pub fn to_wire(&self) -> TradeField {
unsafe {
TradeField {
instrument_id: cstr_or_empty(self.instrument_id),
exchange_id: cstr_or_empty(self.exchange_id),
order_sys_id: cstr_or_empty(self.order_sys_id),
trade_id: cstr_or_empty(self.trade_id),
direction: cstr_or_empty(self.direction),
offset_flag: cstr_or_empty(self.offset_flag),
price: self.price,
volume: self.volume,
trade_date: cstr_or_empty(self.trade_date),
trade_time: cstr_or_empty(self.trade_time),
}
}
}
}
impl EkInvestorPositionField {
pub fn to_wire(&self) -> InvestorPositionField {
unsafe {
InvestorPositionField {
instrument_id: cstr_or_empty(self.instrument_id),
exchange_id: cstr_or_empty(self.exchange_id),
posi_direction: cstr_or_empty(self.posi_direction),
yd_position: self.yd_position,
position: self.position,
today_position: self.today_position,
position_cost: self.position_cost,
position_profit: self.position_profit,
short_frozen: self.short_frozen,
long_frozen: self.long_frozen,
}
}
}
}
impl EkTradingAccountField {
pub fn to_wire(&self) -> TradingAccountField {
unsafe {
TradingAccountField {
account_id: cstr_or_empty(self.account_id),
balance: self.balance,
frozen_margin: self.frozen_margin,
frozen_cash: self.frozen_cash,
frozen_commission: self.frozen_commission,
available: self.available,
}
}
}
}
impl EkInputOrder {
pub fn to_wire(&self) -> InputOrder {
unsafe {
InputOrder {
instrument_id: cstr_or_empty(self.instrument_id),
exchange_id: cstr_or_empty(self.exchange_id),
limit_price: self.limit_price,
volume_total_original: self.volume_total_original,
order_price_type: cstr_or_empty(self.order_price_type),
direction: cstr_or_empty(self.direction),
comb_offset_flag: cstr_or_empty(self.comb_offset_flag),
order_ref: cstr_or_empty(self.order_ref),
investor_id: cstr_or_empty(self.investor_id),
user_id: cstr_or_empty(self.user_id),
broker_id: cstr_or_empty(self.broker_id),
comb_hedge_flag: cstr_or_empty(self.comb_hedge_flag),
contingent_condition: cstr_or_empty(self.contingent_condition),
force_close_reason: cstr_or_empty(self.force_close_reason),
is_auto_suspend: self.is_auto_suspend,
time_condition: cstr_or_empty(self.time_condition),
volume_condition: cstr_or_empty(self.volume_condition),
min_volume: self.min_volume,
}
}
}
}
impl EkInputOrderAction {
pub fn to_wire(&self) -> InputOrderAction {
unsafe {
InputOrderAction {
instrument_id: cstr_or_empty(self.instrument_id),
exchange_id: cstr_or_empty(self.exchange_id),
order_ref: cstr_or_empty(self.order_ref),
front_id: self.front_id,
session_id: self.session_id,
action_flag: cstr_or_empty(self.action_flag),
broker_id: cstr_or_empty(self.broker_id),
investor_id: cstr_or_empty(self.investor_id),
}
}
}
}
impl EkInstrumentMarginRate {
pub fn to_wire(&self) -> InstrumentMarginRateField {
unsafe {
InstrumentMarginRateField {
investor_range: cstr_or_empty(self.investor_range),
broker_id: cstr_or_empty(self.broker_id),
investor_id: cstr_or_empty(self.investor_id),
hedge_flag: cstr_or_empty(self.hedge_flag),
long_margin_ratio_by_money: self.long_margin_ratio_by_money,
long_margin_ratio_by_volume: self.long_margin_ratio_by_volume,
short_margin_ratio_by_money: self.short_margin_ratio_by_money,
short_margin_ratio_by_volume: self.short_margin_ratio_by_volume,
is_relative: self.is_relative,
exchange_id: cstr_or_empty(self.exchange_id),
invest_unit_id: cstr_or_empty(self.invest_unit_id),
instrument_id: cstr_or_empty(self.instrument_id),
}
}
}
}
impl EkInstrumentCommissionRate {
pub fn to_wire(&self) -> InstrumentCommissionRateField {
unsafe {
InstrumentCommissionRateField {
investor_range: cstr_or_empty(self.investor_range),
broker_id: cstr_or_empty(self.broker_id),
investor_id: cstr_or_empty(self.investor_id),
open_ratio_by_money: self.open_ratio_by_money,
open_ratio_by_volume: self.open_ratio_by_volume,
close_ratio_by_money: self.close_ratio_by_money,
close_ratio_by_volume: self.close_ratio_by_volume,
close_today_ratio_by_money: self.close_today_ratio_by_money,
close_today_ratio_by_volume: self.close_today_ratio_by_volume,
exchange_id: cstr_or_empty(self.exchange_id),
biz_type: cstr_or_empty(self.biz_type),
invest_unit_id: cstr_or_empty(self.invest_unit_id),
instrument_id: cstr_or_empty(self.instrument_id),
}
}
}
}
impl EkInstrumentStatus {
pub fn to_wire(&self) -> InstrumentStatusField {
unsafe {
InstrumentStatusField {
exchange_id: cstr_or_empty(self.exchange_id),
settlement_group_id: cstr_or_empty(self.settlement_group_id),
instrument_status: cstr_or_empty(self.instrument_status),
trading_segment_sn: self.trading_segment_sn,
enter_time: cstr_or_empty(self.enter_time),
enter_reason: cstr_or_empty(self.enter_reason),
exchange_inst_id: cstr_or_empty(self.exchange_inst_id),
instrument_id: cstr_or_empty(self.instrument_id),
}
}
}
}
impl InputOrder {
pub fn to_pod_cstr(&self) -> (EkInputOrder, Vec<CString>) {
let instrument_id = cstring(&self.instrument_id);
let exchange_id = cstring(&self.exchange_id);
let order_price_type = cstring(&self.order_price_type);
let direction = cstring(&self.direction);
let comb_offset_flag = cstring(&self.comb_offset_flag);
let order_ref = cstring(&self.order_ref);
let investor_id = cstring(&self.investor_id);
let user_id = cstring(&self.user_id);
let broker_id = cstring(&self.broker_id);
let comb_hedge_flag = cstring(&self.comb_hedge_flag);
let contingent_condition = cstring(&self.contingent_condition);
let force_close_reason = cstring(&self.force_close_reason);
let time_condition = cstring(&self.time_condition);
let volume_condition = cstring(&self.volume_condition);
let pod = EkInputOrder {
instrument_id: instrument_id.as_ptr(),
exchange_id: exchange_id.as_ptr(),
limit_price: self.limit_price,
volume_total_original: self.volume_total_original,
order_price_type: order_price_type.as_ptr(),
direction: direction.as_ptr(),
comb_offset_flag: comb_offset_flag.as_ptr(),
order_ref: order_ref.as_ptr(),
investor_id: investor_id.as_ptr(),
user_id: user_id.as_ptr(),
broker_id: broker_id.as_ptr(),
comb_hedge_flag: comb_hedge_flag.as_ptr(),
contingent_condition: contingent_condition.as_ptr(),
force_close_reason: force_close_reason.as_ptr(),
is_auto_suspend: self.is_auto_suspend,
time_condition: time_condition.as_ptr(),
volume_condition: volume_condition.as_ptr(),
min_volume: self.min_volume,
};
let keep = vec![
instrument_id,
exchange_id,
order_price_type,
direction,
comb_offset_flag,
order_ref,
investor_id,
user_id,
broker_id,
comb_hedge_flag,
contingent_condition,
force_close_reason,
time_condition,
volume_condition,
];
(pod, keep)
}
}
impl InputOrderAction {
pub fn to_pod_cstr(&self) -> (EkInputOrderAction, Vec<CString>) {
let instrument_id = cstring(&self.instrument_id);
let exchange_id = cstring(&self.exchange_id);
let order_ref = cstring(&self.order_ref);
let action_flag = cstring(&self.action_flag);
let broker_id = cstring(&self.broker_id);
let investor_id = cstring(&self.investor_id);
let pod = EkInputOrderAction {
instrument_id: instrument_id.as_ptr(),
exchange_id: exchange_id.as_ptr(),
order_ref: order_ref.as_ptr(),
front_id: self.front_id,
session_id: self.session_id,
action_flag: action_flag.as_ptr(),
broker_id: broker_id.as_ptr(),
investor_id: investor_id.as_ptr(),
};
let keep = vec![
instrument_id,
exchange_id,
order_ref,
action_flag,
broker_id,
investor_id,
];
(pod, keep)
}
}
impl QryInstrumentMarginRate {
pub fn to_pod_cstr(&self) -> (EkQryInstrumentMarginRate, Vec<CString>) {
let broker_id = cstring(&self.broker_id);
let investor_id = cstring(&self.investor_id);
let hedge_flag = cstring(&self.hedge_flag);
let exchange_id = cstring(&self.exchange_id);
let invest_unit_id = cstring(&self.invest_unit_id);
let instrument_id = cstring(&self.instrument_id);
let pod = EkQryInstrumentMarginRate {
broker_id: broker_id.as_ptr(),
investor_id: investor_id.as_ptr(),
hedge_flag: hedge_flag.as_ptr(),
exchange_id: exchange_id.as_ptr(),
invest_unit_id: invest_unit_id.as_ptr(),
instrument_id: instrument_id.as_ptr(),
};
let keep = vec![
broker_id,
investor_id,
hedge_flag,
exchange_id,
invest_unit_id,
instrument_id,
];
(pod, keep)
}
}
impl QryInstrumentCommissionRate {
pub fn to_pod_cstr(&self) -> (EkQryInstrumentCommissionRate, Vec<CString>) {
let broker_id = cstring(&self.broker_id);
let investor_id = cstring(&self.investor_id);
let exchange_id = cstring(&self.exchange_id);
let invest_unit_id = cstring(&self.invest_unit_id);
let instrument_id = cstring(&self.instrument_id);
let pod = EkQryInstrumentCommissionRate {
broker_id: broker_id.as_ptr(),
investor_id: investor_id.as_ptr(),
exchange_id: exchange_id.as_ptr(),
invest_unit_id: invest_unit_id.as_ptr(),
instrument_id: instrument_id.as_ptr(),
};
let keep = vec![
broker_id,
investor_id,
exchange_id,
invest_unit_id,
instrument_id,
];
(pod, keep)
}
}
#[cfg(test)]
mod tests {
use super::*;
const SCRATCH_CAP: usize = 8192;
fn scratch() -> Vec<u8> {
vec![0u8; SCRATCH_CAP]
}
fn zeroed<T>() -> T {
unsafe { std::mem::zeroed() }
}
fn scratch_ptr(buf: &mut [u8]) -> *mut c_char {
buf.as_mut_ptr() as *mut c_char
}
struct K(u32);
impl K {
fn new() -> Self {
K(0)
}
fn next(&mut self) -> u32 {
self.0 += 1;
self.0
}
fn f64(&mut self) -> f64 {
f64::from(self.next())
}
fn i64(&mut self) -> i64 {
1000 + i64::from(self.next())
}
fn i32(&mut self) -> i32 {
2000 + self.next() as i32
}
fn s(&mut self) -> String {
format!("s{}", self.next())
}
fn ch(&mut self) -> String {
char::from(b'A' + (self.next() % 26) as u8).to_string()
}
}
#[test]
fn pod_sizeof_matches_rust_layout() {
unsafe {
assert_eq!(ek_pod_sizeof_error() as usize, size_of::<EkErrorField>());
assert_eq!(
ek_pod_sizeof_depth() as usize,
size_of::<EkDepthMarketData>()
);
assert_eq!(
ek_pod_sizeof_instrument() as usize,
size_of::<EkInstrumentField>()
);
assert_eq!(ek_pod_sizeof_order() as usize, size_of::<EkOrderField>());
assert_eq!(ek_pod_sizeof_trade() as usize, size_of::<EkTradeField>());
assert_eq!(
ek_pod_sizeof_position() as usize,
size_of::<EkInvestorPositionField>()
);
assert_eq!(
ek_pod_sizeof_account() as usize,
size_of::<EkTradingAccountField>()
);
assert_eq!(
ek_pod_sizeof_input_order() as usize,
size_of::<EkInputOrder>()
);
assert_eq!(
ek_pod_sizeof_input_order_action() as usize,
size_of::<EkInputOrderAction>()
);
assert_eq!(
ek_pod_sizeof_margin_rate() as usize,
size_of::<EkInstrumentMarginRate>()
);
assert_eq!(
ek_pod_sizeof_commission_rate() as usize,
size_of::<EkInstrumentCommissionRate>()
);
assert_eq!(
ek_pod_sizeof_instrument_status() as usize,
size_of::<EkInstrumentStatus>()
);
assert_eq!(
ek_pod_sizeof_qry_margin_rate() as usize,
size_of::<EkQryInstrumentMarginRate>()
);
assert_eq!(
ek_pod_sizeof_qry_commission_rate() as usize,
size_of::<EkQryInstrumentCommissionRate>()
);
}
}
#[test]
fn selftest_error_follows_wire_order() {
let mut out = zeroed::<EkErrorField>();
let mut sc = scratch();
unsafe { ek_pod_selftest_error(&mut out, scratch_ptr(&mut sc), sc.len()) };
let w = out.to_wire();
let mut k = K::new();
assert_eq!(w.error_id, k.i32());
assert_eq!(w.error_msg, k.s());
}
#[test]
fn request_path_fields_are_gbk_encoded() {
let mut buf = [0 as c_char; 64];
unsafe { ek_shim_selftest_copy_field(buf.as_mut_ptr(), buf.len()) };
let bytes: Vec<u8> = buf
.iter()
.take_while(|c| **c != 0)
.map(|c| *c as u8)
.collect();
assert_eq!(
bytes,
vec![0xC3, 0xDC, 0xC2, 0xEB],
"「密码」必须按 GBK 送出去(C3DC C2EB)"
);
assert_ne!(
bytes,
b"\xE5\xAF\x86\xE7\xA0\x81".to_vec(),
"原样返回 UTF-8 字节 = 请求路径漏了编码转换"
);
}
#[test]
fn overlong_request_field_truncates_on_character_boundary() {
let before = crate::native::common::truncations();
let mut buf = [0 as c_char; 64];
unsafe { ek_shim_selftest_copy_field_truncated(buf.as_mut_ptr(), buf.len()) };
let bytes: Vec<u8> = buf
.iter()
.take_while(|c| **c != 0)
.map(|c| *c as u8)
.collect();
assert_eq!(
bytes.len(),
40,
"`char[41]` 只能装 40 字节,got {}",
bytes.len()
);
let mut i = 0;
while i < bytes.len() {
assert!(bytes[i] >= 0x80, "第 {i} 字节应是 GBK 首字节");
assert!(i + 1 < bytes.len(), "尾部留下半个汉字(位置 {i})");
i += 2;
}
for chunk in bytes.chunks(2) {
assert_eq!(chunk, [0xC3, 0xDC], "截断后每个汉字都应完整(C3 DC)");
}
let after = crate::native::common::truncations();
assert!(
after > before,
"截断必须记账:计数 {before} → {after} 没变,说明 note_truncation 没走到"
);
}
#[test]
fn selftest_depth_follows_wire_order() {
let mut out = zeroed::<EkDepthMarketData>();
let mut sc = scratch();
unsafe { ek_pod_selftest_depth(&mut out, scratch_ptr(&mut sc), sc.len()) };
let w = out.to_wire();
let mut k = K::new();
assert_eq!(w.instrument_id, k.s());
assert_eq!(w.last_price, k.f64());
assert_eq!(w.volume, k.f64());
assert_eq!(w.turnover, k.f64());
assert_eq!(w.open_interest, k.f64());
assert_eq!(w.upper_limit_price, k.f64());
assert_eq!(w.lower_limit_price, k.f64());
assert_eq!(w.open_price, k.f64());
assert_eq!(w.highest_price, k.f64());
assert_eq!(w.lowest_price, k.f64());
assert_eq!(w.pre_close_price, k.f64());
assert_eq!(w.bid_price1, k.f64());
assert_eq!(w.bid_price2, k.f64());
assert_eq!(w.bid_price3, k.f64());
assert_eq!(w.bid_price4, k.f64());
assert_eq!(w.bid_price5, k.f64());
assert_eq!(w.ask_price1, k.f64());
assert_eq!(w.ask_price2, k.f64());
assert_eq!(w.ask_price3, k.f64());
assert_eq!(w.ask_price4, k.f64());
assert_eq!(w.ask_price5, k.f64());
assert_eq!(w.bid_volume1, k.f64());
assert_eq!(w.bid_volume2, k.f64());
assert_eq!(w.bid_volume3, k.f64());
assert_eq!(w.bid_volume4, k.f64());
assert_eq!(w.bid_volume5, k.f64());
assert_eq!(w.ask_volume1, k.f64());
assert_eq!(w.ask_volume2, k.f64());
assert_eq!(w.ask_volume3, k.f64());
assert_eq!(w.ask_volume4, k.f64());
assert_eq!(w.ask_volume5, k.f64());
assert_eq!(w.update_time, k.s());
assert_eq!(w.update_millisec, k.i64());
assert_eq!(w.trading_day, k.s());
assert_eq!(w.action_day, k.s());
assert_eq!(w.exchange_id, k.s());
assert_eq!(k.next(), 37, "36 个字段,计数器应停在 36 之后");
}
#[test]
fn selftest_instrument_follows_wire_order() {
let mut out = zeroed::<EkInstrumentField>();
let mut sc = scratch();
unsafe { ek_pod_selftest_instrument(&mut out, scratch_ptr(&mut sc), sc.len()) };
let w = out.to_wire();
let mut k = K::new();
assert_eq!(w.instrument_id, k.s());
assert_eq!(w.exchange_id, k.s());
assert_eq!(w.instrument_name, k.s());
assert_eq!(w.product_class, k.ch());
assert_eq!(w.volume_multiple, k.f64());
assert_eq!(w.price_tick, k.f64());
assert_eq!(w.max_limit_order_volume, k.f64());
assert_eq!(w.min_limit_order_volume, k.f64());
assert_eq!(w.max_market_order_volume, k.f64());
assert_eq!(w.min_market_order_volume, k.f64());
assert_eq!(w.delivery_year, k.i64());
assert_eq!(w.delivery_month, k.i64());
assert_eq!(w.product_id, k.s());
assert_eq!(w.is_trading, k.i64());
assert_eq!(k.next(), 15, "14 个字段");
}
#[test]
fn selftest_order_follows_wire_order() {
let mut out = zeroed::<EkOrderField>();
let mut sc = scratch();
unsafe { ek_pod_selftest_order(&mut out, scratch_ptr(&mut sc), sc.len()) };
let w = out.to_wire();
let mut k = K::new();
assert_eq!(w.instrument_id, k.s());
assert_eq!(w.order_ref, k.s());
assert_eq!(w.front_id, k.i64());
assert_eq!(w.session_id, k.i64());
assert_eq!(w.order_sys_id, k.s());
assert_eq!(w.direction, k.ch());
assert_eq!(w.comb_offset_flag, k.s());
assert_eq!(w.order_price_type, k.ch());
assert_eq!(w.time_condition, k.ch());
assert_eq!(w.volume_condition, k.ch());
assert_eq!(w.limit_price, k.f64());
assert_eq!(w.volume_total_original, k.f64());
assert_eq!(w.volume_total, k.f64());
assert_eq!(w.volume_traded, k.f64());
assert_eq!(w.order_status, k.ch());
assert_eq!(w.insert_date, k.s());
assert_eq!(w.insert_time, k.s());
assert_eq!(k.next(), 18, "17 个字段");
}
#[test]
fn selftest_trade_follows_wire_order() {
let mut out = zeroed::<EkTradeField>();
let mut sc = scratch();
unsafe { ek_pod_selftest_trade(&mut out, scratch_ptr(&mut sc), sc.len()) };
let w = out.to_wire();
let mut k = K::new();
assert_eq!(w.instrument_id, k.s());
assert_eq!(w.order_sys_id, k.s());
assert_eq!(w.trade_id, k.s());
assert_eq!(w.direction, k.ch());
assert_eq!(w.offset_flag, k.ch());
assert_eq!(w.price, k.f64());
assert_eq!(w.volume, k.f64());
assert_eq!(w.trade_date, k.s());
assert_eq!(w.trade_time, k.s());
assert_eq!(k.next(), 10, "9 个字段");
}
#[test]
fn selftest_position_follows_wire_order() {
let mut out = zeroed::<EkInvestorPositionField>();
let mut sc = scratch();
unsafe { ek_pod_selftest_position(&mut out, scratch_ptr(&mut sc), sc.len()) };
let w = out.to_wire();
let mut k = K::new();
assert_eq!(w.instrument_id, k.s());
assert_eq!(w.posi_direction, k.ch());
assert_eq!(w.yd_position, k.f64());
assert_eq!(w.position, k.f64());
assert_eq!(w.today_position, k.f64());
assert_eq!(w.position_cost, k.f64());
assert_eq!(w.position_profit, k.f64());
assert_eq!(w.short_frozen, k.f64());
assert_eq!(w.long_frozen, k.f64());
assert_eq!(k.next(), 10, "9 个字段");
}
#[test]
fn selftest_account_follows_wire_order() {
let mut out = zeroed::<EkTradingAccountField>();
let mut sc = scratch();
unsafe { ek_pod_selftest_account(&mut out, scratch_ptr(&mut sc), sc.len()) };
let w = out.to_wire();
let mut k = K::new();
assert_eq!(w.account_id, k.s());
assert_eq!(w.balance, k.f64());
assert_eq!(w.frozen_margin, k.f64());
assert_eq!(w.frozen_cash, k.f64());
assert_eq!(w.frozen_commission, k.f64());
assert_eq!(w.available, k.f64());
assert_eq!(k.next(), 7, "6 个字段");
}
#[test]
fn selftest_input_order_follows_wire_order() {
let mut out = zeroed::<EkInputOrder>();
let mut sc = scratch();
unsafe { ek_pod_selftest_input_order(&mut out, scratch_ptr(&mut sc), sc.len()) };
let w = out.to_wire();
let mut k = K::new();
assert_eq!(w.instrument_id, k.s());
assert_eq!(w.exchange_id, k.s());
assert_eq!(w.limit_price, k.f64());
assert_eq!(w.volume_total_original, k.i64());
assert_eq!(w.order_price_type, k.ch());
assert_eq!(w.direction, k.ch());
assert_eq!(w.comb_offset_flag, k.s());
assert_eq!(w.order_ref, k.s());
assert_eq!(w.investor_id, k.s());
assert_eq!(w.user_id, k.s());
assert_eq!(w.broker_id, k.s());
assert_eq!(w.comb_hedge_flag, k.s());
assert_eq!(w.contingent_condition, k.ch());
assert_eq!(w.force_close_reason, k.ch());
assert_eq!(w.is_auto_suspend, k.i32());
assert_eq!(w.time_condition, k.ch());
assert_eq!(w.volume_condition, k.ch());
assert_eq!(w.min_volume, k.i64());
assert_eq!(k.next(), 19, "18 个字段");
}
#[test]
fn roundtrip_input_order_preserves_every_field() {
let order = InputOrder {
instrument_id: "rb2601".to_string(),
exchange_id: "SHFE".to_string(),
limit_price: 3456.5,
volume_total_original: 7,
order_price_type: "2".to_string(),
direction: "0".to_string(),
comb_offset_flag: "1".to_string(),
order_ref: "90001".to_string(),
investor_id: "investor-1".to_string(),
user_id: "user-1".to_string(),
broker_id: "broker-1".to_string(),
comb_hedge_flag: "1".to_string(),
contingent_condition: "1".to_string(),
force_close_reason: "0".to_string(),
is_auto_suspend: 0,
time_condition: "3".to_string(),
volume_condition: "1".to_string(),
min_volume: 1,
};
let (pod, keep) = order.to_pod_cstr();
let mut out = zeroed::<EkInputOrder>();
let mut sc = scratch();
unsafe {
ek_pod_selftest_io_roundtrip_input_order(&pod, &mut out, scratch_ptr(&mut sc), sc.len())
};
let back = out.to_wire();
assert_eq!(back, order, "POD -> CTP -> POD 必须逐字段还原");
drop(keep);
}
#[test]
fn roundtrip_input_order_action_preserves_every_field() {
let action = InputOrderAction {
instrument_id: "ag2612".to_string(),
exchange_id: "SHFE".to_string(),
order_ref: "90002".to_string(),
front_id: 1234,
session_id: 5678,
action_flag: "0".to_string(),
broker_id: "broker-2".to_string(),
investor_id: "investor-2".to_string(),
};
let (pod, keep) = action.to_pod_cstr();
let mut out = zeroed::<EkInputOrderAction>();
let mut sc = scratch();
unsafe {
ek_pod_selftest_io_roundtrip_input_order_action(
&pod,
&mut out,
scratch_ptr(&mut sc),
sc.len(),
)
};
let back = out.to_wire();
assert_eq!(back, action, "POD -> CTP -> POD 必须逐字段还原");
drop(keep);
}
#[test]
fn input_order_action_converter_fields_match_names() {
let action = InputOrderAction {
instrument_id: "vf0001".to_string(),
exchange_id: "vf0002".to_string(),
order_ref: "vf0003".to_string(),
front_id: 910,
session_id: 920,
action_flag: "z".to_string(),
broker_id: "vf0005".to_string(),
investor_id: "vf0006".to_string(),
};
let (pod, keep) = action.to_pod_cstr();
let rc = unsafe { ek_ctp_verify_input_order_action(&pod) };
drop(keep);
assert_eq!(rc, 0, "写方向转换存在错位字段(首个错位序号={rc})");
}
#[test]
fn selftest_margin_rate_follows_wire_order() {
let mut out = zeroed::<EkInstrumentMarginRate>();
let mut sc = scratch();
unsafe { ek_pod_selftest_margin_rate(&mut out, scratch_ptr(&mut sc), sc.len()) };
let w = out.to_wire();
let mut k = K::new();
assert_eq!(w.investor_range, k.ch());
assert_eq!(w.broker_id, k.s());
assert_eq!(w.investor_id, k.s());
assert_eq!(w.hedge_flag, k.ch());
assert_eq!(w.long_margin_ratio_by_money, k.f64());
assert_eq!(w.long_margin_ratio_by_volume, k.f64());
assert_eq!(w.short_margin_ratio_by_money, k.f64());
assert_eq!(w.short_margin_ratio_by_volume, k.f64());
assert_eq!(w.is_relative, k.i32());
assert_eq!(w.exchange_id, k.s());
assert_eq!(w.invest_unit_id, k.s());
assert_eq!(w.instrument_id, k.s());
assert_eq!(k.next(), 13, "12 个字段");
}
#[test]
fn selftest_commission_rate_follows_wire_order() {
let mut out = zeroed::<EkInstrumentCommissionRate>();
let mut sc = scratch();
unsafe { ek_pod_selftest_commission_rate(&mut out, scratch_ptr(&mut sc), sc.len()) };
let w = out.to_wire();
let mut k = K::new();
assert_eq!(w.investor_range, k.ch());
assert_eq!(w.broker_id, k.s());
assert_eq!(w.investor_id, k.s());
assert_eq!(w.open_ratio_by_money, k.f64());
assert_eq!(w.open_ratio_by_volume, k.f64());
assert_eq!(w.close_ratio_by_money, k.f64());
assert_eq!(w.close_ratio_by_volume, k.f64());
assert_eq!(w.close_today_ratio_by_money, k.f64());
assert_eq!(w.close_today_ratio_by_volume, k.f64());
assert_eq!(w.exchange_id, k.s());
assert_eq!(w.biz_type, k.ch());
assert_eq!(w.invest_unit_id, k.s());
assert_eq!(w.instrument_id, k.s());
assert_eq!(k.next(), 14, "13 个字段");
}
#[test]
fn selftest_instrument_status_follows_wire_order() {
let mut out = zeroed::<EkInstrumentStatus>();
let mut sc = scratch();
unsafe { ek_pod_selftest_instrument_status(&mut out, scratch_ptr(&mut sc), sc.len()) };
let w = out.to_wire();
let mut k = K::new();
assert_eq!(w.exchange_id, k.s());
assert_eq!(w.settlement_group_id, k.s());
assert_eq!(w.instrument_status, k.ch());
assert_eq!(w.trading_segment_sn, k.i32());
assert_eq!(w.enter_time, k.s());
assert_eq!(w.enter_reason, k.ch());
assert_eq!(w.exchange_inst_id, k.s());
assert_eq!(w.instrument_id, k.s());
assert_eq!(k.next(), 9, "8 个字段");
}
#[test]
fn qry_margin_rate_converter_fields_match_names() {
let query = QryInstrumentMarginRate {
broker_id: "wg0001".to_string(),
investor_id: "wg0002".to_string(),
hedge_flag: "w".to_string(),
exchange_id: "wg0003".to_string(),
invest_unit_id: "wg0004".to_string(),
instrument_id: "wg0005".to_string(),
};
let (pod, keep) = query.to_pod_cstr();
let rc = unsafe { ek_ctp_verify_qry_margin_rate(&pod) };
drop(keep);
assert_eq!(rc, 0, "保证金率查询写方向存在错位字段(首个错位序号={rc})");
}
#[test]
fn qry_commission_rate_converter_fields_match_names() {
let query = QryInstrumentCommissionRate {
broker_id: "wk0001".to_string(),
investor_id: "wk0002".to_string(),
exchange_id: "wk0003".to_string(),
invest_unit_id: "wk0004".to_string(),
instrument_id: "wk0005".to_string(),
};
let (pod, keep) = query.to_pod_cstr();
let rc = unsafe { ek_ctp_verify_qry_commission_rate(&pod) };
drop(keep);
assert_eq!(rc, 0, "手续费率查询写方向存在错位字段(首个错位序号={rc})");
}
#[test]
fn gbk_utf8_helpers_roundtrip_non_ascii() {
let text = "螺纹钢主力合约";
let gbk = utf8_to_gbk(text);
assert_eq!(gbk.len(), 14, "GBK 应是 2 字节/汉字");
assert_eq!(gbk_to_utf8(&gbk), text, "GBK -> UTF-8 必须还原");
assert_eq!(gbk_to_utf8(&[]), "");
assert_eq!(utf8_to_gbk(""), Vec::<u8>::new());
assert_eq!(gbk_to_utf8(&[0x81]), "");
assert_eq!(gbk_to_utf8(&[0xB0]), "");
let lead = utf8_to_gbk("螺");
assert_eq!(lead.len(), 2);
assert_eq!(gbk_to_utf8(&lead), "螺");
}
#[test]
fn roundtrip_input_order_preserves_gbk_text() {
let order = InputOrder {
instrument_id: "螺纹钢2610".to_string(),
exchange_id: "SHFE".to_string(),
limit_price: 3200.0,
volume_total_original: 3,
order_price_type: "2".to_string(),
direction: "0".to_string(),
comb_offset_flag: "1".to_string(),
order_ref: "70001".to_string(),
investor_id: "投资者一号".to_string(),
user_id: "用户甲".to_string(),
broker_id: "经纪公司".to_string(),
comb_hedge_flag: "1".to_string(),
contingent_condition: "1".to_string(),
force_close_reason: "0".to_string(),
is_auto_suspend: 0,
time_condition: "3".to_string(),
volume_condition: "1".to_string(),
min_volume: 1,
};
let (pod, keep) = order.to_pod_cstr();
let mut out = zeroed::<EkInputOrder>();
let mut sc = scratch();
unsafe {
ek_pod_selftest_io_roundtrip_input_order(&pod, &mut out, scratch_ptr(&mut sc), sc.len())
};
let back = out.to_wire();
assert_eq!(back, order, "GBK 字段往返必须无损");
drop(keep);
}
#[test]
fn exhausted_or_null_scratch_degrades_to_empty_strings() {
let mut out = zeroed::<EkErrorField>();
let mut tiny = [0u8; 2];
unsafe { ek_pod_selftest_error(&mut out, scratch_ptr(&mut tiny), tiny.len()) };
let w = out.to_wire();
assert_eq!(w.error_id, 2001, "数值字段不受 scratch 影响");
assert_eq!(w.error_msg, "");
let mut out2 = zeroed::<EkErrorField>();
unsafe { ek_pod_selftest_error(&mut out2, std::ptr::null_mut(), 0) };
let w2 = out2.to_wire();
assert_eq!(w2.error_id, 2001);
assert_eq!(w2.error_msg, "");
}
}