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}