use crate::constants::{INFINITY_STR, UNSET_DOUBLE, UNSET_INTEGER};
use crate::error::{TwsApiError, TwsApiResult};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Frame<'a> {
pub size: usize,
pub payload: &'a [u8],
pub rest: &'a [u8],
}
pub fn make_initial_msg(text: &str) -> Vec<u8> {
let bytes = text.as_bytes();
let mut out = Vec::with_capacity(4 + bytes.len());
out.extend_from_slice(&(bytes.len() as u32).to_be_bytes());
out.extend_from_slice(bytes);
out
}
pub fn make_client_handshake(
min_client_ver: i32,
max_client_ver: i32,
options: Option<&str>,
) -> Vec<u8> {
let mut version = format!("v{min_client_ver}..{max_client_ver}");
if let Some(options) = options.filter(|value| !value.is_empty()) {
version.push(' ');
version.push_str(options);
}
let mut out = b"API\0".to_vec();
out.extend_from_slice(&make_initial_msg(&version));
out
}
pub fn make_msg_proto(msg_id: i32, protobuf_data: &[u8]) -> Vec<u8> {
let mut payload = Vec::with_capacity(4 + protobuf_data.len());
payload.extend_from_slice(&msg_id.to_be_bytes());
payload.extend_from_slice(protobuf_data);
let mut out = Vec::with_capacity(4 + payload.len());
out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
out.extend_from_slice(&payload);
out
}
pub fn make_msg(msg_id: i32, use_raw_int_msg_id: bool, text: &str) -> TwsApiResult<Vec<u8>> {
let mut payload = Vec::new();
if use_raw_int_msg_id {
payload.extend_from_slice(&msg_id.to_be_bytes());
payload.extend_from_slice(text.as_bytes());
} else {
payload.extend_from_slice(make_field(msg_id)?.as_bytes());
payload.extend_from_slice(text.as_bytes());
}
let mut out = Vec::with_capacity(4 + payload.len());
out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
out.extend_from_slice(&payload);
Ok(out)
}
pub fn make_field<T>(value: T) -> TwsApiResult<String>
where
T: TwsField,
{
let text = value.to_tws_field()?;
ensure_ascii_printable(&text)?;
Ok(format!("{text}\0"))
}
pub fn make_field_handle_empty<T>(value: T) -> TwsApiResult<String>
where
T: TwsNullableField,
{
let text = value.to_tws_nullable_field()?;
ensure_ascii_printable(&text)?;
Ok(format!("{text}\0"))
}
pub fn read_msg(buf: &[u8]) -> TwsApiResult<Option<Frame<'_>>> {
if buf.len() < 4 {
return Ok(None);
}
let (prefix, data) = buf.split_at(4);
let prefix: [u8; 4] = prefix
.try_into()
.map_err(|_| TwsApiError::IncompleteFrame {
needed: 4,
available: buf.len(),
})?;
let size = u32::from_be_bytes(prefix);
let size = usize::try_from(size).map_err(|_| TwsApiError::FrameTooLarge(size))?;
let needed = 4 + size;
if buf.len() < needed {
return Ok(None);
}
Ok(Some(Frame {
size,
payload: data.get(..size).ok_or(TwsApiError::IncompleteFrame {
needed,
available: buf.len(),
})?,
rest: data.get(size..).ok_or(TwsApiError::IncompleteFrame {
needed,
available: buf.len(),
})?,
}))
}
pub fn read_fields(buf: &[u8]) -> Vec<&[u8]> {
let mut fields = buf.split(|byte| *byte == 0).collect::<Vec<_>>();
if fields.last().is_some_and(|field| field.is_empty()) {
fields.pop();
}
fields
}
pub trait TwsField {
fn to_tws_field(self) -> TwsApiResult<String>;
}
pub trait TwsNullableField {
fn to_tws_nullable_field(self) -> TwsApiResult<String>;
}
impl TwsField for &str {
fn to_tws_field(self) -> TwsApiResult<String> {
Ok(self.to_owned())
}
}
impl TwsField for &String {
fn to_tws_field(self) -> TwsApiResult<String> {
Ok(self.clone())
}
}
impl TwsField for String {
fn to_tws_field(self) -> TwsApiResult<String> {
Ok(self)
}
}
impl TwsField for bool {
fn to_tws_field(self) -> TwsApiResult<String> {
Ok(i32::from(self).to_string())
}
}
impl TwsField for i32 {
fn to_tws_field(self) -> TwsApiResult<String> {
Ok(self.to_string())
}
}
impl TwsField for i64 {
fn to_tws_field(self) -> TwsApiResult<String> {
Ok(self.to_string())
}
}
impl TwsField for usize {
fn to_tws_field(self) -> TwsApiResult<String> {
Ok(self.to_string())
}
}
impl TwsField for f64 {
fn to_tws_field(self) -> TwsApiResult<String> {
let mut value = self.to_string();
if self.is_finite() && !value.contains('.') && !value.contains('e') && !value.contains('E')
{
value.push_str(".0");
}
Ok(value)
}
}
impl TwsNullableField for i32 {
fn to_tws_nullable_field(self) -> TwsApiResult<String> {
if self == UNSET_INTEGER {
return Ok(String::new());
}
self.to_tws_field()
}
}
impl TwsNullableField for f64 {
fn to_tws_nullable_field(self) -> TwsApiResult<String> {
if self == UNSET_DOUBLE {
return Ok(String::new());
}
if self.is_infinite() && self.is_sign_positive() {
return Ok(INFINITY_STR.to_owned());
}
self.to_tws_field()
}
}
impl<T> TwsNullableField for Option<T>
where
T: TwsField,
{
fn to_tws_nullable_field(self) -> TwsApiResult<String> {
match self {
Some(value) => value.to_tws_field(),
None => Err(TwsApiError::MissingField),
}
}
}
fn ensure_ascii_printable(value: &str) -> TwsApiResult<()> {
if value
.bytes()
.all(|byte| byte == b'\t' || (0x20..=0x7e).contains(&byte))
{
return Ok(());
}
Err(TwsApiError::NonPrintableAscii(value.to_owned()))
}