use crate::tcp::{helper, Tdx};
use crate::bytes_helper::{u16_from_le_bytes, u32_from_le_bytes};
#[derive(Debug, Clone)]
pub struct SecurityList {
pub send: Box<[u8]>,
pub market: u16,
pub start: u16,
pub response: Vec<u8>,
pub data: Vec<SecurityListData>,
}
impl SecurityList {
pub fn new(market: u16, start: u16) -> Self {
let mut send = [0u8; Self::LEN];
send[0..12].copy_from_slice(Self::SEND);
send[12..14].copy_from_slice(&market.to_le_bytes());
send[14..16].copy_from_slice(&start.to_le_bytes());
Self {
send: send.into(),
market,
start,
response: Vec::new(),
data: Vec::new(),
}
}
}
impl Tdx for SecurityList {
type Item = [SecurityListData];
const SEND: &'static [u8] = &[
0x0c, 0x01, 0x18, 0x64, 0x01, 0x01, 0x06, 0x00, 0x06, 0x00, 0x50,
0x04, ];
const TAG: &'static str = "股票列表";
const LEN: usize = 12 + 2 + 2;
fn send(&mut self) -> &[u8] {
&self.send
}
fn parse(&mut self, v: Vec<u8>) {
if v.len() < 2 {
eprintln!("⚠️ 股票列表数据长度不足: {} 字节(需要至少 2 字节)", v.len());
self.response = v;
self.data = Vec::new();
return;
}
let mut pos = 0;
let num_stocks = u16_from_le_bytes(&v, pos);
pos += 2;
let expected_len = 2 + (num_stocks as usize) * 29;
if v.len() < expected_len {
eprintln!("⚠️ 股票列表数据长度不足: {} 字节(预期 {} 字节,包含 {} 只股票)",
v.len(), expected_len, num_stocks);
let available_stocks = (v.len() - 2) / 29;
self.data = Vec::with_capacity(available_stocks);
for _ in 0..available_stocks {
if pos + 29 <= v.len() {
let stock = parse_security_list_data(&v, &mut pos);
self.data.push(stock);
} else {
break;
}
}
} else {
self.data = Vec::with_capacity(num_stocks as usize);
for _ in 0..num_stocks {
let stock = parse_security_list_data(&v, &mut pos);
self.data.push(stock);
}
}
self.response = v;
}
fn result(&self) -> &Self::Item {
&self.data
}
}
fn parse_security_list_data(data: &[u8], pos: &mut usize) -> SecurityListData {
let code_bytes = &data[*pos..*pos + 6];
*pos += 6;
let code = unsafe { std::str::from_utf8_unchecked(code_bytes) };
let code = String::from(code);
let volunit = u16_from_le_bytes(data, *pos);
*pos += 2;
let name_bytes = &data[*pos..*pos + 8];
*pos += 8;
let name = crate::tcp::helper::gbk_to_string_trim_null(name_bytes);
let _reversed_bytes1 = &data[*pos..*pos + 4];
*pos += 4;
let decimal_point = data[*pos];
*pos += 1;
let pre_close_raw = u32_from_le_bytes(data, *pos);
*pos += 4;
let pre_close = helper::vol_amount(pre_close_raw as i32);
let _reversed_bytes2 = &data[*pos..*pos + 4];
*pos += 4;
SecurityListData {
code,
volunit: volunit as u32,
decimal_point,
name,
pre_close,
}
}
#[derive(Debug, Default, Clone, serde::Serialize)]
pub struct SecurityListData {
pub code: String,
pub name: String,
pub volunit: u32,
pub decimal_point: u8,
pub pre_close: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_security_list_new() {
let list = SecurityList::new(0, 0);
assert_eq!(list.market, 0);
assert_eq!(list.start, 0);
assert_eq!(list.send.len(), 16);
}
#[test]
fn test_security_list_new_with_start() {
let list = SecurityList::new(1, 1000);
assert_eq!(list.market, 1);
assert_eq!(list.start, 1000);
}
#[test]
fn test_security_list_send_bytes() {
let list = SecurityList::new(0, 0);
assert_eq!(&list.send[0..12], &[0x0c, 0x01, 0x18, 0x64, 0x01, 0x01, 0x06, 0x00, 0x06, 0x00, 0x50, 0x04]);
assert_eq!(&list.send[12..14], &[0x00, 0x00]);
assert_eq!(&list.send[14..16], &[0x00, 0x00]);
}
#[test]
fn test_security_list_send_bytes_with_params() {
let list = SecurityList::new(1, 1000);
assert_eq!(&list.send[12..14], &[0x01, 0x00]);
assert_eq!(&list.send[14..16], &[0xe8, 0x03]); }
#[test]
fn test_connection() {
if std::env::var("RUSTDX_SKIP_INTEGRATION_TESTS").is_ok() {
println!("⚠️ 跳过集成测试 (RUSTDX_SKIP_INTEGRATION_TESTS 已设置)");
return;
}
println!("⚠️ 集成测试需要手动验证(需要实际TCP连接)");
}
}