truefix_twsapi_client/
comm.rs1use crate::constants::{INFINITY_STR, UNSET_DOUBLE, UNSET_INTEGER};
2use crate::error::{TwsApiError, TwsApiResult};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct Frame<'a> {
7 pub size: usize,
9 pub payload: &'a [u8],
11 pub rest: &'a [u8],
13}
14
15pub fn make_initial_msg(text: &str) -> Vec<u8> {
17 let bytes = text.as_bytes();
18 let mut out = Vec::with_capacity(4 + bytes.len());
19 out.extend_from_slice(&(bytes.len() as u32).to_be_bytes());
20 out.extend_from_slice(bytes);
21 out
22}
23
24pub fn make_client_handshake(
26 min_client_ver: i32,
27 max_client_ver: i32,
28 options: Option<&str>,
29) -> Vec<u8> {
30 let mut version = format!("v{min_client_ver}..{max_client_ver}");
31 if let Some(options) = options.filter(|value| !value.is_empty()) {
32 version.push(' ');
33 version.push_str(options);
34 }
35
36 let mut out = b"API\0".to_vec();
37 out.extend_from_slice(&make_initial_msg(&version));
38 out
39}
40
41pub fn make_msg_proto(msg_id: i32, protobuf_data: &[u8]) -> Vec<u8> {
43 let mut payload = Vec::with_capacity(4 + protobuf_data.len());
44 payload.extend_from_slice(&msg_id.to_be_bytes());
45 payload.extend_from_slice(protobuf_data);
46
47 let mut out = Vec::with_capacity(4 + payload.len());
48 out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
49 out.extend_from_slice(&payload);
50 out
51}
52
53pub fn make_msg(msg_id: i32, use_raw_int_msg_id: bool, text: &str) -> TwsApiResult<Vec<u8>> {
55 let mut payload = Vec::new();
56 if use_raw_int_msg_id {
57 payload.extend_from_slice(&msg_id.to_be_bytes());
58 payload.extend_from_slice(text.as_bytes());
59 } else {
60 payload.extend_from_slice(make_field(msg_id)?.as_bytes());
61 payload.extend_from_slice(text.as_bytes());
62 }
63
64 let mut out = Vec::with_capacity(4 + payload.len());
65 out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
66 out.extend_from_slice(&payload);
67 Ok(out)
68}
69
70pub fn make_field<T>(value: T) -> TwsApiResult<String>
72where
73 T: TwsField,
74{
75 let text = value.to_tws_field()?;
76 ensure_ascii_printable(&text)?;
77 Ok(format!("{text}\0"))
78}
79
80pub fn make_field_handle_empty<T>(value: T) -> TwsApiResult<String>
82where
83 T: TwsNullableField,
84{
85 let text = value.to_tws_nullable_field()?;
86 ensure_ascii_printable(&text)?;
87 Ok(format!("{text}\0"))
88}
89
90pub fn read_msg(buf: &[u8]) -> TwsApiResult<Option<Frame<'_>>> {
92 if buf.len() < 4 {
93 return Ok(None);
94 }
95
96 let (prefix, data) = buf.split_at(4);
97 let prefix: [u8; 4] = prefix
98 .try_into()
99 .map_err(|_| TwsApiError::IncompleteFrame {
100 needed: 4,
101 available: buf.len(),
102 })?;
103 let size = u32::from_be_bytes(prefix);
104 let size = usize::try_from(size).map_err(|_| TwsApiError::FrameTooLarge(size))?;
105 let needed = 4 + size;
106 if buf.len() < needed {
107 return Ok(None);
108 }
109
110 Ok(Some(Frame {
111 size,
112 payload: data.get(..size).ok_or(TwsApiError::IncompleteFrame {
113 needed,
114 available: buf.len(),
115 })?,
116 rest: data.get(size..).ok_or(TwsApiError::IncompleteFrame {
117 needed,
118 available: buf.len(),
119 })?,
120 }))
121}
122
123pub fn read_fields(buf: &[u8]) -> Vec<&[u8]> {
125 let mut fields = buf.split(|byte| *byte == 0).collect::<Vec<_>>();
126 if fields.last().is_some_and(|field| field.is_empty()) {
127 fields.pop();
128 }
129 fields
130}
131
132pub trait TwsField {
134 fn to_tws_field(self) -> TwsApiResult<String>;
136}
137
138pub trait TwsNullableField {
140 fn to_tws_nullable_field(self) -> TwsApiResult<String>;
142}
143
144impl TwsField for &str {
145 fn to_tws_field(self) -> TwsApiResult<String> {
146 Ok(self.to_owned())
147 }
148}
149
150impl TwsField for &String {
151 fn to_tws_field(self) -> TwsApiResult<String> {
152 Ok(self.clone())
153 }
154}
155
156impl TwsField for String {
157 fn to_tws_field(self) -> TwsApiResult<String> {
158 Ok(self)
159 }
160}
161
162impl TwsField for bool {
163 fn to_tws_field(self) -> TwsApiResult<String> {
164 Ok(i32::from(self).to_string())
165 }
166}
167
168impl TwsField for i32 {
169 fn to_tws_field(self) -> TwsApiResult<String> {
170 Ok(self.to_string())
171 }
172}
173
174impl TwsField for i64 {
175 fn to_tws_field(self) -> TwsApiResult<String> {
176 Ok(self.to_string())
177 }
178}
179
180impl TwsField for usize {
181 fn to_tws_field(self) -> TwsApiResult<String> {
182 Ok(self.to_string())
183 }
184}
185
186impl TwsField for f64 {
187 fn to_tws_field(self) -> TwsApiResult<String> {
188 let mut value = self.to_string();
189 if self.is_finite() && !value.contains('.') && !value.contains('e') && !value.contains('E')
190 {
191 value.push_str(".0");
192 }
193 Ok(value)
194 }
195}
196
197impl TwsNullableField for i32 {
198 fn to_tws_nullable_field(self) -> TwsApiResult<String> {
199 if self == UNSET_INTEGER {
200 return Ok(String::new());
201 }
202 self.to_tws_field()
203 }
204}
205
206impl TwsNullableField for f64 {
207 fn to_tws_nullable_field(self) -> TwsApiResult<String> {
208 if self == UNSET_DOUBLE {
209 return Ok(String::new());
210 }
211 if self.is_infinite() && self.is_sign_positive() {
212 return Ok(INFINITY_STR.to_owned());
213 }
214 self.to_tws_field()
215 }
216}
217
218impl<T> TwsNullableField for Option<T>
219where
220 T: TwsField,
221{
222 fn to_tws_nullable_field(self) -> TwsApiResult<String> {
223 match self {
224 Some(value) => value.to_tws_field(),
225 None => Err(TwsApiError::MissingField),
226 }
227 }
228}
229
230fn ensure_ascii_printable(value: &str) -> TwsApiResult<()> {
231 if value
232 .bytes()
233 .all(|byte| byte == b'\t' || (0x20..=0x7e).contains(&byte))
234 {
235 return Ok(());
236 }
237 Err(TwsApiError::NonPrintableAscii(value.to_owned()))
238}