easyfix_messages/serializer.rs
1use std::io::Write;
2
3use tracing::warn;
4
5use crate::fields::basic_types::*;
6
7// TODO: This should be parametrizable and also used in parser to cut too big messages.
8const MAX_MSG_SIZE: usize = 4096;
9const MAX_BODY_LEN_DIGITS: usize = if MAX_MSG_SIZE < 10000 {
10 4
11} else if MAX_MSG_SIZE < 100000 {
12 5
13} else if MAX_MSG_SIZE < 1000000 {
14 6
15} else if MAX_MSG_SIZE < 10000000 {
16 7
17} else if MAX_MSG_SIZE < 100000000 {
18 8
19} else {
20 panic!("MAX_MSG_SIZE too big");
21};
22
23// TODO: SerializeError: Empty Vec/Group, `0` on SeqNum,TagNum,NumInGroup,Length
24
25impl Default for Serializer {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31pub struct Serializer {
32 output: Vec<u8>,
33 body_start_idx: usize,
34 current_tag_num: TagNum,
35}
36
37impl Serializer {
38 pub fn new() -> Serializer {
39 Serializer {
40 // Allocate for max message size, to prevent vector reallocation.
41 output: Vec::with_capacity(MAX_MSG_SIZE),
42 body_start_idx: 0,
43 current_tag_num: 0,
44 }
45 }
46
47 pub fn output_mut(&mut self) -> &mut Vec<u8> {
48 &mut self.output
49 }
50
51 pub fn take(self) -> Vec<u8> {
52 self.output
53 }
54
55 pub fn serialize_body_len(&mut self) {
56 const BODY_LEN_PLACEHOLDER: &[u8] = match MAX_BODY_LEN_DIGITS {
57 4 => b"9=0000\x01",
58 5 => b"9=00000\x01",
59 _ => panic!("unexpected count of maximum body length digits"),
60 };
61 self.output.extend_from_slice(BODY_LEN_PLACEHOLDER);
62 self.body_start_idx = self.output.len();
63 }
64
65 // TODO: add test cases for body len and checksum verification
66 pub fn serialize_checksum(&mut self) {
67 let mut buffer = itoa::Buffer::new();
68
69 let body = &self.output[self.body_start_idx..];
70 let body_len = body.len();
71 let body_len_slice = buffer.format(body_len).as_bytes();
72
73 self.output[self.body_start_idx - body_len_slice.len() - 1..self.body_start_idx - 1]
74 .copy_from_slice(body_len_slice);
75
76 let checksum = self
77 .output
78 .iter()
79 .fold(0u8, |acc, &byte| u8::wrapping_add(acc, byte));
80
81 self.output.extend_from_slice(b"10=");
82 if checksum < 10 {
83 self.output.extend_from_slice(b"00");
84 } else if checksum < 100 {
85 self.output.extend_from_slice(b"0");
86 }
87 self.output
88 .extend_from_slice(buffer.format(checksum).as_bytes());
89 self.output.push(b'\x01');
90 }
91
92 /// Serialize sequence of character digits without commas or decimals.
93 /// Value must be positive and may not contain leading zeros.
94 pub fn serialize_tag_num(&mut self, tag_num: &TagNum) {
95 self.current_tag_num = *tag_num;
96 let mut buffer = itoa::Buffer::new();
97 self.output
98 .extend_from_slice(buffer.format(*tag_num).as_bytes());
99 }
100
101 /// Serialize sequence of character digits without commas or decimals
102 /// and optional sign character (characters “-” and “0” – “9” ).
103 /// The sign character utilizes one octet (i.e., positive int is “99999”
104 /// while negative int is “-99999”).
105 pub fn serialize_int(&mut self, int: &Int) {
106 let mut buffer = itoa::Buffer::new();
107 self.output
108 .extend_from_slice(buffer.format(*int).as_bytes());
109 }
110
111 /// Serialize sequence of character digits without commas or decimals.
112 /// Value must be positive.
113 pub fn serialize_seq_num(&mut self, seq_num: &SeqNum) {
114 let mut buffer = itoa::Buffer::new();
115 self.output
116 .extend_from_slice(buffer.format(*seq_num).as_bytes());
117 }
118
119 /// Serialize sequence of character digits without commas or decimals.
120 /// Value must be positive.
121 pub fn serialize_num_in_group(&mut self, num_in_group: &NumInGroup) {
122 if *num_in_group == 0 {
123 warn!("empty Group (tag={})", self.current_tag_num);
124 }
125 let mut buffer = itoa::Buffer::new();
126 self.output
127 .extend_from_slice(buffer.format(*num_in_group).as_bytes());
128 }
129
130 /// Serialize sequence of character digits without commas or decimals
131 /// (values 1 to 31).
132 pub fn serialize_day_of_month(&mut self, day_of_month: DayOfMonth) {
133 let mut buffer = itoa::Buffer::new();
134 self.output
135 .extend_from_slice(buffer.format(day_of_month).as_bytes());
136 }
137
138 /// Serialize sequence of character digits with optional decimal point
139 /// and sign character (characters “-”, “0” – “9” and “.”);
140 /// the absence of the decimal point within the string will be interpreted
141 /// as the float representation of an integer value. Note that float values
142 /// may contain leading zeros (e.g. “00023.23” = “23.23”) and may contain
143 /// or omit trailing zeros after the decimal point
144 /// (e.g. “23.0” = “23.0000” = “23” = “23.”).
145 ///
146 /// All float fields must accommodate up to fifteen significant digits.
147 /// The number of decimal places used should be a factor of business/market
148 /// needs and mutual agreement between counterparties.
149 pub fn serialize_float(&mut self, float: &Float) {
150 self.output.extend_from_slice(float.to_string().as_bytes())
151 }
152
153 pub fn serialize_qty(&mut self, qty: &Qty) {
154 self.serialize_float(qty)
155 }
156
157 pub fn serialize_price(&mut self, price: &Price) {
158 self.serialize_float(price)
159 }
160
161 pub fn serialize_price_offset(&mut self, price_offset: &PriceOffset) {
162 self.serialize_float(price_offset)
163 }
164
165 pub fn serialize_amt(&mut self, amt: &Amt) {
166 self.serialize_float(amt)
167 }
168
169 pub fn serialize_percentage(&mut self, percentage: &Percentage) {
170 self.serialize_float(percentage)
171 }
172
173 pub fn serialize_boolean(&mut self, boolean: &Boolean) {
174 if *boolean {
175 self.output.extend_from_slice(b"Y");
176 } else {
177 self.output.extend_from_slice(b"N");
178 }
179 }
180
181 fn validate_char(&self, c: &Char) {
182 if matches!(c, 0x00..=0x1f) {
183 warn!(
184 "wrong Char value - special character ({:#04x}) (tag={})",
185 c, self.current_tag_num
186 );
187 }
188 if matches!(c, 0x80..=0xff) {
189 warn!(
190 "value of Char value - outside ASCII range ({:#04x}) (tag={})",
191 c, self.current_tag_num
192 );
193 }
194 }
195
196 /// Use any ASCII character except control characters.
197 pub fn serialize_char(&mut self, c: &Char) {
198 self.validate_char(c);
199 self.output.push(*c);
200 }
201
202 /// Serialize string containing one or more space-delimited single
203 /// character values, e.g. “2 A F”.
204 pub fn serialize_multiple_char_value(&mut self, mcv: &MultipleCharValue) {
205 if mcv.is_empty() {
206 warn!("empty MutlipleCharValue (tag={})", self.current_tag_num);
207 }
208 for c in mcv {
209 self.validate_char(c);
210 self.output.push(*c);
211 self.output.push(b' ');
212 }
213 self.output.pop();
214 }
215
216 /// Serialize alphanumeric free-format strings can include any character
217 /// except control characters.
218 pub fn serialize_string(&mut self, input: &FixStr) {
219 if input.is_empty() {
220 warn!("empty String (tag={})", self.current_tag_num);
221 }
222 self.output.extend_from_slice(input.as_bytes());
223 }
224
225 /// Serialize string containing one or more space-delimited multiple
226 /// character values, e.g. “AV AN A”.
227 pub fn serialize_multiple_string_value(&mut self, input: &MultipleStringValue) {
228 if input.is_empty() {
229 warn!("empty MultipleStringValue (tag={})", self.current_tag_num);
230 }
231 for s in input {
232 if s.is_empty() {
233 warn!(
234 "empty MultipleStringValue element (tag={})",
235 self.current_tag_num
236 );
237 }
238 self.output.extend_from_slice(s.as_bytes());
239 self.output.push(b' ');
240 }
241 self.output.pop();
242 }
243
244 /// Serialize ISO 3166-1:2013 Codes for the representation of names of
245 /// countries and their subdivision (2-character code).
246 pub fn serialize_country(&mut self, country: &Country) {
247 self.output.extend_from_slice(country.to_bytes());
248 }
249
250 /// Serialize ISO 4217:2015 Codes for the representation of currencies
251 /// and funds (3-character code).
252 pub fn serialize_currency(&mut self, currency: &Currency) {
253 self.output.extend_from_slice(currency.to_bytes());
254 }
255
256 /// Serialize ISO 10383:2012 Securities and related financial instruments
257 /// – Codes for exchanges and market identification (MIC)
258 /// (4-character code).
259 pub fn serialize_exchange(&mut self, exchange: &Exchange) {
260 self.output.extend_from_slice(exchange);
261 }
262
263 /// Serialize string representing month of a year.
264 /// An optional day of the month can be appended or an optional week code.
265 ///
266 /// # Valid formats:
267 /// YYYYMM
268 /// YYYYMMDD
269 /// YYYYMMWW
270 ///
271 /// # Valid values:
272 /// YYYY = 0000-9999; MM = 01-12; DD = 01-31;
273 /// WW = w1, w2, w3, w4, w5.
274 pub fn serialize_month_year(&mut self, input: &MonthYear) {
275 self.output.extend_from_slice(input);
276 }
277
278 /// Serialize ISO 639-1:2002 Codes for the representation of names
279 /// of languages (2-character code).
280 pub fn serialize_language(&mut self, input: &Language) {
281 self.output.extend_from_slice(input);
282 }
283
284 /// Serialize string representing time/date combination represented
285 /// in UTC (Universal Time Coordinated) in either YYYYMMDD-HH:MM:SS
286 /// (whole seconds) or YYYYMMDD-HH:MM:SS.sss* format, colons, dash,
287 /// and period required.
288 ///
289 /// # Valid values:
290 /// - YYYY = 0000-9999,
291 /// - MM = 01-12,
292 /// - DD = 01-31,
293 /// - HH = 00-23,
294 /// - MM = 0059,
295 /// - SS = 00-60 (60 only if UTC leap second),
296 /// - sss* fractions of seconds. The fractions of seconds may be empty when
297 /// no fractions of seconds are conveyed (in such a case the period
298 /// is not conveyed), it may include 3 digits to convey
299 /// milliseconds, 6 digits to convey microseconds, 9 digits
300 /// to convey nanoseconds, 12 digits to convey picoseconds;
301 pub fn serialize_utc_timestamp(&mut self, input: &UtcTimestamp) {
302 write!(self.output, "{}", input.format_precisely())
303 .expect("UtcTimestamp serialization failed")
304 }
305
306 /// Serialize string representing time-only represented in UTC
307 /// (Universal Time Coordinated) in either HH:MM:SS (whole seconds)
308 /// or HH:MM:SS.sss* (milliseconds) format, colons, and period required.
309 ///
310 /// This special-purpose field is paired with UTCDateOnly to form a proper
311 /// UTCTimestamp for bandwidth-sensitive messages.
312 ///
313 /// # Valid values:
314 /// - HH = 00-23,
315 /// - MM = 00-59,
316 /// - SS = 00-60 (60 only if UTC leap second),
317 /// - sss* fractions of seconds. The fractions of seconds may be empty when
318 /// no fractions of seconds are conveyed (in such a case the period
319 /// is not conveyed), it may include 3 digits to convey
320 /// milliseconds, 6 digits to convey microseconds, 9 digits
321 /// to convey nanoseconds, 12 digits to convey picoseconds;
322 /// // TODO: set precision!
323 pub fn serialize_utc_time_only(&mut self, input: &UtcTimeOnly) {
324 write!(self.output, "{}", input.format("%H:%M:%S.%f"))
325 .expect("UtcTimeOnly serialization failed")
326 }
327
328 /// Serialize date represented in UTC (Universal Time Coordinated)
329 /// in YYYYMMDD format.
330 ///
331 /// # Valid values:
332 /// - YYYY = 0000-9999,
333 /// - MM = 01-12,
334 /// - DD = 01-31.
335 pub fn serialize_utc_date_only(&mut self, input: &UtcDateOnly) {
336 write!(self.output, "{}", input.format("%Y%m%d")).expect("UtcDateOnly serialization failed")
337 }
338
339 /// Serialize time local to a market center. Used where offset to UTC
340 /// varies throughout the year and the defining market center is identified
341 /// in a corresponding field.
342 ///
343 /// Format is HH:MM:SS where:
344 /// - HH = 00-23 hours,
345 /// - MM = 00-59 minutes,
346 /// - SS = 00-59 seconds.
347 ///
348 /// In general only the hour token is non-zero.
349 pub fn serialize_local_mkt_time(&mut self, input: &LocalMktTime) {
350 write!(self.output, "{}", input.format("%H:%M:%S"))
351 .expect("LocalMktTime serialization failed")
352 }
353
354 /// Serialize date of local market (as opposed to UTC) in YYYYMMDD
355 /// format.
356 ///
357 /// # Valid values:
358 /// - YYYY = 0000-9999,
359 /// - MM = 01-12,
360 /// - DD = 01-31.
361 pub fn serialize_local_mkt_date(&mut self, input: &LocalMktDate) {
362 write!(self.output, "{}", input.format("%Y%m%d"))
363 .expect("LocalMktDate serialization failed")
364 }
365
366 /// Serialize string representing a time/date combination representing
367 /// local time with an offset to UTC to allow identification of local time
368 /// and time zone offset of that time.
369 ///
370 /// The representation is based on ISO 8601.
371 ///
372 /// Format is `YYYYMMDD-HH:MM:SS.sss*[Z | [ + | – hh[:mm]]]` where:
373 /// - YYYY = 0000 to 9999,
374 /// - MM = 01-12,
375 /// - DD = 01-31 HH = 00-23 hours,
376 /// - MM = 00-59 minutes,
377 /// - SS = 00-59 seconds,
378 /// - hh = 01-12 offset hours,
379 /// - mm = 00-59 offset minutes,
380 /// - sss* fractions of seconds. The fractions of seconds may be empty when
381 /// no fractions of seconds are conveyed (in such a case the period
382 /// is not conveyed), it may include 3 digits to convey
383 /// milliseconds, 6 digits to convey microseconds, 9 digits
384 /// to convey nanoseconds, 12 digits to convey picoseconds;
385 pub fn serialize_tz_timestamp(&mut self, input: &TzTimestamp) {
386 self.output.extend_from_slice(input)
387 }
388
389 /// Serialize time of day with timezone. Time represented based on
390 /// ISO 8601. This is the time with a UTC offset to allow identification of
391 /// local time and time zone of that time.
392 ///
393 /// Format is `HH:MM[:SS][Z | [ + | – hh[:mm]]]` where:
394 /// - HH = 00-23 hours,
395 /// - MM = 00-59 minutes,
396 /// - SS = 00-59 seconds,
397 /// - hh = 01-12 offset hours,
398 /// - mm = 00-59 offset minutes.
399 pub fn serialize_tz_timeonly(&mut self, input: &TzTimeOnly) {
400 self.output.extend_from_slice(input)
401 }
402
403 /// Serialize sequence of character digits without commas or decimals.
404 pub fn serialize_length(&mut self, length: &Length) {
405 let mut buffer = itoa::Buffer::new();
406 self.output
407 .extend_from_slice(buffer.format(*length).as_bytes());
408 }
409
410 /// Serialize raw data with no format or content restrictions,
411 /// or a character string encoded as specified by MessageEncoding(347).
412 pub fn serialize_data(&mut self, data: &Data) {
413 if data.is_empty() {
414 warn!("empty Data (tag={})", self.current_tag_num);
415 }
416 self.output.extend_from_slice(data);
417 }
418
419 /// Serialize XML document.
420 pub fn serialize_xml(&mut self, xml_data: &XmlData) {
421 if xml_data.is_empty() {
422 warn!("empty XmlData (tag={})", self.current_tag_num);
423 }
424 self.output.extend_from_slice(xml_data);
425 }
426
427 // fn serialize_tenor(input: &[u8]) -> Result<Tenor, RejectReason>;
428
429 pub fn serialize_enum<T>(&mut self, value: &T)
430 where
431 T: Copy + Into<&'static [u8]>,
432 {
433 self.output.extend_from_slice((*value).into());
434 }
435
436 pub fn serialize_enum_collection<T>(&mut self, values: &[T])
437 where
438 T: Copy + Into<&'static [u8]>,
439 {
440 if values.is_empty() {
441 warn!("empty enum collection (tag={})", self.current_tag_num);
442 }
443 for value in values {
444 self.output.extend_from_slice((*value).into());
445 self.output.push(b' ');
446 }
447 // Drop last space
448 self.output.pop();
449 }
450}