1use crate::config::FixVersion;
4use crate::error::{FixError, Result};
5use std::collections::HashMap;
6
7pub const SOH: char = '\x01';
9
10pub mod tags {
12 pub const BEGIN_STRING: u32 = 8;
14 pub const BODY_LENGTH: u32 = 9;
16 pub const MSG_TYPE: u32 = 35;
18 pub const SENDER_COMP_ID: u32 = 49;
20 pub const TARGET_COMP_ID: u32 = 56;
22 pub const MSG_SEQ_NUM: u32 = 34;
24 pub const SENDING_TIME: u32 = 52;
26 pub const CHECKSUM: u32 = 10;
28 pub const CL_ORD_ID: u32 = 11;
30 pub const ORDER_ID: u32 = 37;
32 pub const ORIG_CL_ORD_ID: u32 = 41;
34 pub const EXEC_ID: u32 = 17;
36 pub const EXEC_TYPE: u32 = 150;
38 pub const ORD_STATUS: u32 = 39;
40 pub const SYMBOL: u32 = 55;
42 pub const SIDE: u32 = 54;
44 pub const ORD_TYPE: u32 = 40;
46 pub const ORDER_QTY: u32 = 38;
48 pub const PRICE: u32 = 44;
50 pub const STOP_PX: u32 = 99;
52 pub const TIME_IN_FORCE: u32 = 59;
54 pub const LAST_QTY: u32 = 32;
56 pub const LAST_PX: u32 = 31;
58 pub const CUM_QTY: u32 = 14;
60 pub const AVG_PX: u32 = 6;
62 pub const LEAVES_QTY: u32 = 151;
64 pub const TEXT: u32 = 58;
66 pub const ACCOUNT: u32 = 1;
68 pub const HEART_BT_INT: u32 = 108;
70 pub const ENCRYPT_METHOD: u32 = 98;
72 pub const RESET_SEQ_NUM_FLAG: u32 = 141;
74 pub const TEST_REQ_ID: u32 = 112;
76 pub const BEGIN_SEQ_NO: u32 = 7;
78 pub const END_SEQ_NO: u32 = 16;
80 pub const MD_REQ_ID: u32 = 262;
82 pub const SUBSCRIPTION_REQUEST_TYPE: u32 = 263;
84 pub const MARKET_DEPTH: u32 = 264;
86 pub const MD_ENTRY_TYPE: u32 = 269;
88 pub const MD_ENTRY_PX: u32 = 270;
90 pub const MD_ENTRY_SIZE: u32 = 271;
92}
93
94#[derive(Debug, Clone)]
96pub struct FixMessage {
97 pub fields: HashMap<u32, String>,
99 pub raw: String,
101}
102
103impl FixMessage {
104 #[must_use]
106 pub fn new() -> Self {
107 Self {
108 fields: HashMap::new(),
109 raw: String::new(),
110 }
111 }
112
113 #[must_use]
115 pub fn get(&self, tag: u32) -> Option<&str> {
116 self.fields.get(&tag).map(String::as_str)
117 }
118
119 #[must_use]
121 pub fn msg_type(&self) -> Option<&str> {
122 self.get(tags::MSG_TYPE)
123 }
124
125 pub fn set(&mut self, tag: u32, value: impl Into<String>) {
127 self.fields.insert(tag, value.into());
128 }
129
130 #[must_use]
132 pub fn has(&self, tag: u32) -> bool {
133 self.fields.contains_key(&tag)
134 }
135}
136
137impl Default for FixMessage {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143#[derive(Debug)]
145pub struct FixEncoder {
146 version: FixVersion,
147 sender_comp_id: String,
148 target_comp_id: String,
149}
150
151impl FixEncoder {
152 #[must_use]
154 pub fn new(version: FixVersion, sender_comp_id: &str, target_comp_id: &str) -> Self {
155 Self {
156 version,
157 sender_comp_id: sender_comp_id.to_string(),
158 target_comp_id: target_comp_id.to_string(),
159 }
160 }
161
162 pub fn encode(&self, msg_type: &str, seq_num: u64, fields: &[(u32, String)]) -> String {
164 let sending_time = chrono::Utc::now().format("%Y%m%d-%H:%M:%S%.3f").to_string();
165
166 let mut body = String::new();
168 body.push_str(&format!("{}={}{}", tags::MSG_TYPE, msg_type, SOH));
169 body.push_str(&format!(
170 "{}={}{}",
171 tags::SENDER_COMP_ID,
172 self.sender_comp_id,
173 SOH
174 ));
175 body.push_str(&format!(
176 "{}={}{}",
177 tags::TARGET_COMP_ID,
178 self.target_comp_id,
179 SOH
180 ));
181 body.push_str(&format!("{}={}{}", tags::MSG_SEQ_NUM, seq_num, SOH));
182 body.push_str(&format!("{}={}{}", tags::SENDING_TIME, sending_time, SOH));
183
184 for (tag, value) in fields {
185 body.push_str(&format!("{}={}{}", tag, value, SOH));
186 }
187
188 let header = format!(
190 "{}={}{}{}={}{}",
191 tags::BEGIN_STRING,
192 self.version.begin_string(),
193 SOH,
194 tags::BODY_LENGTH,
195 body.len(),
196 SOH
197 );
198
199 let checksum = Self::calculate_checksum(&format!("{}{}", header, body));
201
202 format!(
203 "{}{}{}={:03}{}",
204 header,
205 body,
206 tags::CHECKSUM,
207 checksum,
208 SOH
209 )
210 }
211
212 fn calculate_checksum(data: &str) -> u8 {
214 data.bytes().fold(0u32, |acc, b| acc + b as u32) as u8
215 }
216}
217
218#[derive(Debug, Default)]
220pub struct FixDecoder;
221
222impl FixDecoder {
223 #[must_use]
225 pub fn new() -> Self {
226 Self
227 }
228
229 pub fn decode(&self, data: &str) -> Result<FixMessage> {
231 let mut msg = FixMessage::new();
232 msg.raw = data.to_string();
233
234 for field in data.split(SOH) {
235 if field.is_empty() {
236 continue;
237 }
238
239 let parts: Vec<&str> = field.splitn(2, '=').collect();
240 if parts.len() != 2 {
241 return Err(FixError::Decoding(format!("invalid field: {}", field)));
242 }
243
244 let tag: u32 = parts[0]
245 .parse()
246 .map_err(|_| FixError::Decoding(format!("invalid tag: {}", parts[0])))?;
247 msg.fields.insert(tag, parts[1].to_string());
248 }
249
250 if !msg.has(tags::BEGIN_STRING) {
252 return Err(FixError::InvalidMessage("missing BeginString".to_string()));
253 }
254 if !msg.has(tags::MSG_TYPE) {
255 return Err(FixError::InvalidMessage("missing MsgType".to_string()));
256 }
257
258 Ok(msg)
259 }
260
261 #[must_use]
263 pub fn validate_checksum(&self, data: &str) -> bool {
264 if let Some(pos) = data.rfind(&format!("{}=", tags::CHECKSUM)) {
266 let body = &data[..pos];
267 let checksum_str = &data[pos + 3..data.len() - 1]; if let Ok(expected) = checksum_str.parse::<u8>() {
270 let calculated = body.bytes().fold(0u32, |acc, b| acc + b as u32) as u8;
271 return calculated == expected;
272 }
273 }
274 false
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn test_fix_message_fields() {
284 let mut msg = FixMessage::new();
285 msg.set(tags::SYMBOL, "AAPL");
286 msg.set(tags::SIDE, "1");
287
288 assert_eq!(msg.get(tags::SYMBOL), Some("AAPL"));
289 assert_eq!(msg.get(tags::SIDE), Some("1"));
290 assert!(msg.has(tags::SYMBOL));
291 assert!(!msg.has(tags::PRICE));
292 }
293
294 #[test]
295 fn test_fix_encoder() {
296 let encoder = FixEncoder::new(FixVersion::Fix44, "SENDER", "TARGET");
297 let fields = vec![
298 (tags::SYMBOL, "AAPL".to_string()),
299 (tags::SIDE, "1".to_string()),
300 ];
301 let encoded = encoder.encode("D", 1, &fields);
302
303 assert!(encoded.contains("8=FIX.4.4"));
304 assert!(encoded.contains("35=D"));
305 assert!(encoded.contains("49=SENDER"));
306 assert!(encoded.contains("56=TARGET"));
307 assert!(encoded.contains("55=AAPL"));
308 }
309
310 #[test]
311 fn test_fix_decoder() {
312 let decoder = FixDecoder::new();
313 let raw = "8=FIX.4.4\x0135=D\x0149=SENDER\x0156=TARGET\x0155=AAPL\x0110=000\x01";
314 let msg = decoder.decode(raw).unwrap();
315
316 assert_eq!(msg.get(tags::BEGIN_STRING), Some("FIX.4.4"));
317 assert_eq!(msg.msg_type(), Some("D"));
318 assert_eq!(msg.get(tags::SYMBOL), Some("AAPL"));
319 }
320}