Skip to main content

fix_codec_rs/
decoder.rs

1use memchr::memchr;
2use smallvec::SmallVec;
3
4use crate::error::FixError;
5use crate::field::{FIELD_KEY_VALUE_SEPARATOR, FIELD_SEPARATOR};
6use crate::message::Message;
7use crate::tag::{Tag, parse_tag};
8
9/// Default inline capacity: covers ~95% of FIX messages without heap spill.
10const DEFAULT_CAPACITY: usize = 32;
11
12/// A reusable FIX message decoder.
13///
14/// Owns a `SmallVec` buffer that is allocated once (at startup or first use)
15/// and reused across every `decode` call — zero allocation per message on the
16/// hot path.
17///
18/// Stores `(tag, value_start, value_end)` byte offsets rather than slices,
19/// eliminating all unsafe lifetime transmutes while preserving zero-allocation
20/// and zero-copy semantics.
21///
22/// # Example
23/// ```ignore
24/// let mut decoder = Decoder::new();
25///
26/// loop {
27///     let msg = decoder.decode(buf)?;  // zero allocation after first call
28///     process(msg);
29///     // msg dropped here — decoder buffer ready for next call
30/// }
31/// ```
32pub struct Decoder {
33    /// Stores (tag, value_start_offset, value_end_offset) per field.
34    /// clear() at the start of each decode call preserves allocated capacity —
35    /// no free/malloc on the hot path.
36    offsets: SmallVec<[(Tag, u32, u32); DEFAULT_CAPACITY]>,
37}
38
39impl Default for Decoder {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44
45impl Decoder {
46    /// Create a new decoder with a default inline capacity of 32 fields.
47    pub fn new() -> Self {
48        Self {
49            offsets: SmallVec::new(),
50        }
51    }
52
53    /// Create a new decoder pre-allocated for `capacity` fields.
54    /// Use this when messages consistently exceed 32 fields (e.g. MarketData).
55    pub fn with_capacity(capacity: usize) -> Self {
56        Self {
57            offsets: SmallVec::with_capacity(capacity),
58        }
59    }
60
61    /// Decode a raw FIX byte buffer into a `Message`.
62    ///
63    /// Clears and reuses the internal offset buffer — zero allocation per call
64    /// after the first. The returned `Message<'a>` borrows both from `self`
65    /// (the offset slice) and from `buf` (the raw bytes). Drop `Message`
66    /// before calling `decode` again.
67    ///
68    /// The sorted tag index used by [`Message::find`] is built lazily on the
69    /// first `find()` call and cached for the message lifetime. If `find()` is
70    /// never called, no sort ever happens.
71    ///
72    /// # Errors
73    /// - `FixError::IncompleteMessage` — the buffer contains a partial field
74    ///   (no `=` or no SOH delimiter found); buffer more bytes before retrying.
75    /// - `FixError::InvalidTag` — a tag contained non-digit bytes or overflowed `u32`.
76    pub fn decode<'a>(&'a mut self, buf: &'a [u8]) -> Result<Message<'a>, FixError> {
77        // clear() keeps existing capacity — no allocator call on hot path
78        self.offsets.clear();
79
80        let mut pos = 0;
81        while pos < buf.len() {
82            // SIMD scan for '=' — delimits tag from value
83            let eq_pos = memchr(FIELD_KEY_VALUE_SEPARATOR, &buf[pos..])
84                .ok_or(FixError::IncompleteMessage)?
85                + pos;
86
87            let tag = parse_tag(&buf[pos..eq_pos])?;
88
89            // SIMD scan for SOH (0x01) — delimits end of value
90            let soh_pos = memchr(FIELD_SEPARATOR, &buf[eq_pos + 1..])
91                .ok_or(FixError::IncompleteMessage)?
92                + eq_pos
93                + 1;
94
95            // Store byte offsets — plain integers, no lifetimes, no unsafe needed.
96            self.offsets
97                .push((tag, (eq_pos + 1) as u32, soh_pos as u32));
98
99            pos = soh_pos + 1;
100        }
101
102        // Both borrows are genuinely 'a: offsets from &'a mut self, buf from
103        // &'a [u8]. No transmutes, no unsafe.
104        Ok(Message::new(buf, self.offsets.as_slice()))
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::error::FixError;
112    use crate::group;
113
114    // -------------------------------------------------------------------------
115    // Group 1 — Happy path
116    // -------------------------------------------------------------------------
117
118    #[test]
119    fn happy_empty_buffer() {
120        let mut dec = Decoder::new();
121        let msg = dec.decode(b"").unwrap();
122        assert_eq!(msg.len(), 0);
123        assert!(msg.is_empty());
124    }
125
126    #[test]
127    fn happy_single_field() {
128        let mut dec = Decoder::new();
129        let msg = dec.decode(b"8=FIX.4.2\x01").unwrap();
130        assert_eq!(msg.len(), 1);
131        let f = msg.field(0);
132        assert_eq!(f.tag, 8);
133        assert_eq!(f.value, b"FIX.4.2");
134    }
135
136    #[test]
137    fn happy_multiple_fields() {
138        let mut dec = Decoder::new();
139        let msg = dec.decode(b"8=FIX.4.2\x0135=D\x0149=SENDER\x01").unwrap();
140        assert_eq!(msg.len(), 3);
141        let f0 = msg.field(0);
142        assert_eq!(f0.tag, 8);
143        assert_eq!(f0.value, b"FIX.4.2");
144        let f1 = msg.field(1);
145        assert_eq!(f1.tag, 35);
146        assert_eq!(f1.value, b"D");
147        let f2 = msg.field(2);
148        assert_eq!(f2.tag, 49);
149        assert_eq!(f2.value, b"SENDER");
150    }
151
152    #[test]
153    fn happy_empty_value() {
154        // A field whose value is an empty byte slice: "35=\x01"
155        let mut dec = Decoder::new();
156        let msg = dec.decode(b"35=\x01").unwrap();
157        assert_eq!(msg.len(), 1);
158        let f = msg.field(0);
159        assert_eq!(f.tag, 35);
160        assert_eq!(f.value, b"");
161    }
162
163    #[test]
164    fn happy_value_containing_equals() {
165        // '=' inside a value must not confuse the next field's tag scan
166        // because we only scan for '=' starting from `pos` (start of tag).
167        let mut dec = Decoder::new();
168        let msg = dec.decode(b"58=price=100\x0135=D\x01").unwrap();
169        assert_eq!(msg.len(), 2);
170        assert_eq!(msg.field(0).tag, 58);
171        assert_eq!(msg.field(0).value, b"price=100");
172        assert_eq!(msg.field(1).tag, 35);
173        assert_eq!(msg.field(1).value, b"D");
174    }
175
176    #[test]
177    fn happy_binary_value() {
178        // Values may contain arbitrary bytes (e.g. RawData tag 96)
179        let mut dec = Decoder::new();
180        let msg = dec.decode(b"95=3\x0196=\x02\x03\x04\x01").unwrap();
181        assert_eq!(msg.len(), 2);
182        assert_eq!(msg.field(1).tag, 96);
183        assert_eq!(msg.field(1).value, &[0x02u8, 0x03, 0x04]);
184    }
185
186    #[test]
187    fn happy_exactly_32_fields() {
188        // 32 fields = inline SmallVec capacity boundary, no heap spill
189        let mut dec = Decoder::new();
190        let mut buf = Vec::new();
191        for i in 1u32..=32 {
192            buf.extend_from_slice(format!("{}=v\x01", i).as_bytes());
193        }
194        let msg = dec.decode(&buf).unwrap();
195        assert_eq!(msg.len(), 32);
196        for i in 0..32 {
197            assert_eq!(msg.field(i).tag, (i + 1) as u32);
198            assert_eq!(msg.field(i).value, b"v");
199        }
200    }
201
202    #[test]
203    fn happy_33_fields_spills_to_heap() {
204        // 33 fields forces SmallVec past inline capacity — must still be correct
205        let mut dec = Decoder::new();
206        let mut buf = Vec::new();
207        for i in 1u32..=33 {
208            buf.extend_from_slice(format!("{}=v\x01", i).as_bytes());
209        }
210        let msg = dec.decode(&buf).unwrap();
211        assert_eq!(msg.len(), 33);
212        assert_eq!(msg.field(32).tag, 33);
213    }
214
215    // -------------------------------------------------------------------------
216    // Group 2 — Decoder reuse
217    // -------------------------------------------------------------------------
218
219    #[test]
220    fn reuse_decode_twice() {
221        let mut dec = Decoder::new();
222        {
223            let msg = dec.decode(b"8=FIX.4.2\x01").unwrap();
224            assert_eq!(msg.field(0).tag, 8);
225        } // msg dropped, borrow released
226        let msg2 = dec.decode(b"35=D\x01").unwrap();
227        assert_eq!(msg2.field(0).tag, 35);
228        assert_eq!(msg2.field(0).value, b"D");
229    }
230
231    #[test]
232    fn reuse_large_then_small() {
233        // After a 33-field msg causes heap spill, a 1-field msg still works
234        let mut dec = Decoder::new();
235        let mut big_buf = Vec::new();
236        for i in 1u32..=33 {
237            big_buf.extend_from_slice(format!("{}=v\x01", i).as_bytes());
238        }
239        {
240            let msg = dec.decode(&big_buf).unwrap();
241            assert_eq!(msg.len(), 33);
242        }
243        let msg2 = dec.decode(b"8=FIX.4.2\x01").unwrap();
244        assert_eq!(msg2.len(), 1);
245        assert_eq!(msg2.field(0).tag, 8);
246    }
247
248    #[test]
249    fn reuse_many_iterations_stable() {
250        let mut dec = Decoder::new();
251        let buf = b"8=FIX.4.2\x0135=D\x0149=SENDER\x01";
252        for _ in 0..1_000 {
253            let msg = dec.decode(buf).unwrap();
254            assert_eq!(msg.len(), 3);
255            assert_eq!(msg.field(0).tag, 8);
256        }
257    }
258
259    // -------------------------------------------------------------------------
260    // Group 3 — IncompleteMessage (partial TCP frame)
261    // -------------------------------------------------------------------------
262
263    #[test]
264    fn incomplete_tag_only_no_equals() {
265        let mut dec = Decoder::new();
266        assert!(matches!(
267            dec.decode(b"8").unwrap_err(),
268            FixError::IncompleteMessage
269        ));
270    }
271
272    #[test]
273    fn incomplete_tag_equals_value_no_soh() {
274        let mut dec = Decoder::new();
275        assert!(matches!(
276            dec.decode(b"8=FIX.4.2").unwrap_err(),
277            FixError::IncompleteMessage
278        ));
279    }
280
281    #[test]
282    fn incomplete_first_field_ok_second_tag_no_equals() {
283        let mut dec = Decoder::new();
284        assert!(matches!(
285            dec.decode(b"8=FIX.4.2\x0135").unwrap_err(),
286            FixError::IncompleteMessage
287        ));
288    }
289
290    #[test]
291    fn incomplete_second_field_value_no_soh() {
292        let mut dec = Decoder::new();
293        assert!(matches!(
294            dec.decode(b"8=FIX.4.2\x0135=D").unwrap_err(),
295            FixError::IncompleteMessage
296        ));
297    }
298
299    #[test]
300    fn incomplete_only_soh_byte() {
301        // b"\x01" — SOH at pos=0, no '=' found before it → IncompleteMessage
302        let mut dec = Decoder::new();
303        assert!(matches!(
304            dec.decode(b"\x01").unwrap_err(),
305            FixError::IncompleteMessage
306        ));
307    }
308
309    // -------------------------------------------------------------------------
310    // Group 4 — InvalidTag errors
311    // -------------------------------------------------------------------------
312
313    #[test]
314    fn invalid_tag_empty_tag_leading_equals() {
315        // buf starts with '=' → tag slice is empty → InvalidTag
316        let mut dec = Decoder::new();
317        assert!(matches!(
318            dec.decode(b"=val\x01").unwrap_err(),
319            FixError::InvalidTag
320        ));
321    }
322
323    #[test]
324    fn invalid_tag_non_digit_byte() {
325        let mut dec = Decoder::new();
326        assert!(matches!(
327            dec.decode(b"8X=val\x01").unwrap_err(),
328            FixError::InvalidTag
329        ));
330    }
331
332    #[test]
333    fn invalid_tag_overflow_ten_nines() {
334        // 9999999999 > u32::MAX
335        let mut dec = Decoder::new();
336        assert!(matches!(
337            dec.decode(b"9999999999=val\x01").unwrap_err(),
338            FixError::InvalidTag
339        ));
340    }
341
342    #[test]
343    fn invalid_tag_one_past_u32_max() {
344        // 4294967296 = u32::MAX + 1
345        let mut dec = Decoder::new();
346        assert!(matches!(
347            dec.decode(b"4294967296=val\x01").unwrap_err(),
348            FixError::InvalidTag
349        ));
350    }
351
352    #[test]
353    fn invalid_tag_leading_space() {
354        let mut dec = Decoder::new();
355        assert!(matches!(
356            dec.decode(b" 8=val\x01").unwrap_err(),
357            FixError::InvalidTag
358        ));
359    }
360
361    #[test]
362    fn invalid_tag_trailing_space() {
363        let mut dec = Decoder::new();
364        assert!(matches!(
365            dec.decode(b"8 =val\x01").unwrap_err(),
366            FixError::InvalidTag
367        ));
368    }
369
370    // -------------------------------------------------------------------------
371    // Group 5 — Edge cases in value scanning
372    // -------------------------------------------------------------------------
373
374    #[test]
375    fn edge_single_byte_value() {
376        let mut dec = Decoder::new();
377        let msg = dec.decode(b"8=X\x01").unwrap();
378        assert_eq!(msg.field(0).value, b"X");
379    }
380
381    #[test]
382    fn edge_value_starts_with_soh() {
383        // "8=\x01val\x01" — memchr(SOH) finds the first \x01 immediately after '=',
384        // so value = b"" and pos advances to 'v'. "val" then has no '=' → IncompleteMessage.
385        let mut dec = Decoder::new();
386        let err = dec.decode(b"8=\x01val\x01").unwrap_err();
387        assert!(matches!(err, FixError::IncompleteMessage));
388    }
389
390    #[test]
391    fn edge_value_then_bare_soh() {
392        // "8=A\x01B\x01" — first field ok (tag=8, value="A"),
393        // then "B\x01" has no '=' → IncompleteMessage.
394        let mut dec = Decoder::new();
395        let err = dec.decode(b"8=A\x01B\x01").unwrap_err();
396        assert!(matches!(err, FixError::IncompleteMessage));
397    }
398
399    #[test]
400    fn edge_back_to_back_soh() {
401        // "8=\x01\x01" — first field: value=b"", pos advances to second \x01.
402        // Second byte \x01 has no '=' → IncompleteMessage.
403        let mut dec = Decoder::new();
404        let err = dec.decode(b"8=\x01\x01").unwrap_err();
405        assert!(matches!(err, FixError::IncompleteMessage));
406    }
407
408    #[test]
409    fn edge_tag_zero() {
410        // Tag 0 is not a valid FIX tag but the decoder is not responsible for
411        // semantic validation — it should parse it as tag=0.
412        let mut dec = Decoder::new();
413        let msg = dec.decode(b"0=val\x01").unwrap();
414        assert_eq!(msg.field(0).tag, 0);
415        assert_eq!(msg.field(0).value, b"val");
416    }
417
418    #[test]
419    fn edge_tag_u32_max() {
420        // 4294967295 == u32::MAX — within range, should succeed.
421        let mut dec = Decoder::new();
422        let msg = dec.decode(b"4294967295=val\x01").unwrap();
423        assert_eq!(msg.field(0).tag, u32::MAX);
424        assert_eq!(msg.field(0).value, b"val");
425    }
426
427    // -------------------------------------------------------------------------
428    // Group 6 — pos advancement correctness
429    // -------------------------------------------------------------------------
430
431    #[test]
432    fn pos_long_value_next_field_correct() {
433        // First value is 1000 bytes; verify second field parses correctly.
434        let mut dec = Decoder::new();
435        let long_val = vec![b'A'; 1000];
436        let mut buf = Vec::new();
437        buf.extend_from_slice(b"96=");
438        buf.extend_from_slice(&long_val);
439        buf.push(0x01);
440        buf.extend_from_slice(b"35=D\x01");
441        let msg = dec.decode(&buf).unwrap();
442        assert_eq!(msg.len(), 2);
443        assert_eq!(msg.field(0).tag, 96);
444        assert_eq!(msg.field(0).value.len(), 1000);
445        assert_eq!(msg.field(1).tag, 35);
446        assert_eq!(msg.field(1).value, b"D");
447    }
448
449    #[test]
450    fn pos_equals_in_first_value_does_not_confuse_second_tag_scan() {
451        // The '=' inside the first value must not be picked up as the
452        // delimiter for the second field's tag.
453        let mut dec = Decoder::new();
454        let msg = dec.decode(b"58=a=b=c\x0135=D\x01").unwrap();
455        assert_eq!(msg.len(), 2);
456        assert_eq!(msg.field(0).tag, 58);
457        assert_eq!(msg.field(0).value, b"a=b=c");
458        assert_eq!(msg.field(1).tag, 35);
459        assert_eq!(msg.field(1).value, b"D");
460    }
461
462    #[test]
463    fn pos_message_ending_exactly_at_soh() {
464        // Last byte is SOH; pos = soh_pos + 1 == buf.len() → loop exits normally.
465        let mut dec = Decoder::new();
466        let msg = dec.decode(b"8=FIX.4.2\x0135=D\x01").unwrap();
467        assert_eq!(msg.len(), 2);
468        assert_eq!(msg.field(1).value, b"D");
469    }
470
471    // -------------------------------------------------------------------------
472    // Group 7 — with_capacity constructor
473    // -------------------------------------------------------------------------
474
475    #[test]
476    fn with_capacity_exact_fit() {
477        let mut dec = Decoder::with_capacity(4);
478        let msg = dec
479            .decode(b"8=FIX.4.2\x0135=D\x0149=A\x0156=B\x01")
480            .unwrap();
481        assert_eq!(msg.len(), 4);
482        assert_eq!(msg.field(3).tag, 56);
483        assert_eq!(msg.field(3).value, b"B");
484    }
485
486    #[test]
487    fn with_capacity_one_spills_to_heap() {
488        // Pre-allocate 1, decode 33 fields — SmallVec must spill correctly.
489        let mut dec = Decoder::with_capacity(1);
490        let mut buf = Vec::new();
491        for i in 1u32..=33 {
492            buf.extend_from_slice(format!("{}=v\x01", i).as_bytes());
493        }
494        let msg = dec.decode(&buf).unwrap();
495        assert_eq!(msg.len(), 33);
496        assert_eq!(msg.field(32).tag, 33);
497    }
498
499    // -------------------------------------------------------------------------
500    // Group 8 — Repeating groups (successful decode + group navigation)
501    // -------------------------------------------------------------------------
502
503    #[test]
504    fn group_single_misc_fee() {
505        // Allocation message with one MiscFee instance: NO_MISC_FEES=1 followed by
506        // MiscFeeAmt / MiscFeeCurr / MiscFeeType.
507        let mut dec = Decoder::new();
508        let msg = dec
509            .decode(b"8=FIX.4.2\x019=50\x0135=J\x01136=1\x01137=10.50\x01138=USD\x01139=4\x01")
510            .unwrap();
511        assert_eq!(msg.len(), 7);
512
513        let fees: Vec<_> = msg.groups(&group::MISC_FEES).collect();
514        assert_eq!(fees.len(), 1);
515        assert_eq!(
516            fees[0].find(crate::tag::MISC_FEE_AMT).unwrap().value,
517            b"10.50"
518        );
519        assert_eq!(
520            fees[0].find(crate::tag::MISC_FEE_CURR).unwrap().value,
521            b"USD"
522        );
523        assert_eq!(fees[0].find(crate::tag::MISC_FEE_TYPE).unwrap().value, b"4");
524    }
525
526    #[test]
527    fn group_multiple_misc_fees() {
528        // Two MiscFee instances — delimiter tag (137) reappearance splits them.
529        let mut dec = Decoder::new();
530        let msg = dec
531            .decode(
532                b"35=J\x01136=2\x01137=5.00\x01138=USD\x01139=1\x01137=2.50\x01138=EUR\x01139=2\x01",
533            )
534            .unwrap();
535        assert_eq!(msg.len(), 8);
536
537        let fees: Vec<_> = msg.groups(&group::MISC_FEES).collect();
538        assert_eq!(fees.len(), 2);
539
540        assert_eq!(
541            fees[0].find(crate::tag::MISC_FEE_AMT).unwrap().value,
542            b"5.00"
543        );
544        assert_eq!(
545            fees[0].find(crate::tag::MISC_FEE_CURR).unwrap().value,
546            b"USD"
547        );
548        assert_eq!(fees[0].find(crate::tag::MISC_FEE_TYPE).unwrap().value, b"1");
549
550        assert_eq!(
551            fees[1].find(crate::tag::MISC_FEE_AMT).unwrap().value,
552            b"2.50"
553        );
554        assert_eq!(
555            fees[1].find(crate::tag::MISC_FEE_CURR).unwrap().value,
556            b"EUR"
557        );
558        assert_eq!(fees[1].find(crate::tag::MISC_FEE_TYPE).unwrap().value, b"2");
559    }
560
561    #[test]
562    fn group_md_entries_bid_and_offer() {
563        // MarketDataSnapshotFullRefresh with two MDEntry instances (bid + offer).
564        // NO_MD_ENTRIES=268, delimiter=MDEntryType=269.
565        let mut dec = Decoder::new();
566        let msg = dec
567            .decode(
568                b"35=W\x0149=SENDER\x0156=TARGET\x01268=2\x01\
569                269=0\x01270=99.50\x01271=1000\x01\
570                269=1\x01270=99.75\x01271=500\x01",
571            )
572            .unwrap();
573        assert_eq!(msg.len(), 10);
574
575        let entries: Vec<_> = msg.groups(&group::MD_ENTRIES).collect();
576        assert_eq!(entries.len(), 2);
577
578        // Bid (MDEntryType=0)
579        assert_eq!(
580            entries[0].find(crate::tag::MD_ENTRY_TYPE).unwrap().value,
581            b"0"
582        );
583        assert_eq!(
584            entries[0].find(crate::tag::MD_ENTRY_PX).unwrap().value,
585            b"99.50"
586        );
587        assert_eq!(
588            entries[0].find(crate::tag::MD_ENTRY_SIZE).unwrap().value,
589            b"1000"
590        );
591
592        // Offer (MDEntryType=1)
593        assert_eq!(
594            entries[1].find(crate::tag::MD_ENTRY_TYPE).unwrap().value,
595            b"1"
596        );
597        assert_eq!(
598            entries[1].find(crate::tag::MD_ENTRY_PX).unwrap().value,
599            b"99.75"
600        );
601        assert_eq!(
602            entries[1].find(crate::tag::MD_ENTRY_SIZE).unwrap().value,
603            b"500"
604        );
605    }
606
607    #[test]
608    fn group_routing_ids_two_routes() {
609        // Header with NO_ROUTING_IDS=2; RoutingType=216 is the delimiter.
610        let mut dec = Decoder::new();
611        let msg = dec
612            .decode(b"35=D\x01215=2\x01216=1\x01217=ROUTE_A\x01216=2\x01217=ROUTE_B\x01")
613            .unwrap();
614        assert_eq!(msg.len(), 6);
615
616        let routes: Vec<_> = msg.groups(&group::ROUTING_IDS).collect();
617        assert_eq!(routes.len(), 2);
618        assert_eq!(
619            routes[0].find(crate::tag::ROUTING_TYPE).unwrap().value,
620            b"1"
621        );
622        assert_eq!(
623            routes[0].find(crate::tag::ROUTING_ID).unwrap().value,
624            b"ROUTE_A"
625        );
626        assert_eq!(
627            routes[1].find(crate::tag::ROUTING_TYPE).unwrap().value,
628            b"2"
629        );
630        assert_eq!(
631            routes[1].find(crate::tag::ROUTING_ID).unwrap().value,
632            b"ROUTE_B"
633        );
634    }
635
636    #[test]
637    fn group_count_zero_yields_no_instances() {
638        // NO_MISC_FEES=0 — iterator must yield nothing even though count tag present.
639        let mut dec = Decoder::new();
640        let msg = dec.decode(b"35=J\x01136=0\x0158=no fees\x01").unwrap();
641        assert_eq!(msg.len(), 3);
642        assert_eq!(msg.groups(&group::MISC_FEES).count(), 0);
643    }
644
645    #[test]
646    fn group_count_tag_absent_yields_no_instances() {
647        // Message has no NO_MISC_FEES tag at all.
648        let mut dec = Decoder::new();
649        let msg = dec.decode(b"8=FIX.4.2\x0135=D\x0149=SENDER\x01").unwrap();
650        assert_eq!(msg.groups(&group::MISC_FEES).count(), 0);
651    }
652
653    #[test]
654    fn group_fields_after_group_still_accessible() {
655        // Fields that follow a group in the flat message must remain accessible
656        // via Message::field() / Message::find() as usual.
657        let mut dec = Decoder::new();
658        let msg = dec
659            .decode(b"35=J\x01136=1\x01137=3.00\x01138=USD\x01139=1\x0110=200\x01")
660            .unwrap();
661        assert_eq!(msg.len(), 6);
662
663        // Group navigation works.
664        let fee = msg.groups(&group::MISC_FEES).next().unwrap();
665        assert_eq!(fee.find(crate::tag::MISC_FEE_AMT).unwrap().value, b"3.00");
666
667        // CheckSum field after the group is accessible normally.
668        assert_eq!(msg.find(crate::tag::CHECK_SUM).unwrap().value, b"200");
669    }
670
671    // -------------------------------------------------------------------------
672    // Group 9 — all_groups()
673    // -------------------------------------------------------------------------
674
675    #[test]
676    fn all_groups_empty_message_yields_nothing() {
677        // No fields at all — no groups present.
678        let mut dec = Decoder::new();
679        let msg = dec.decode(b"").unwrap();
680        assert_eq!(msg.all_groups().count(), 0);
681    }
682
683    #[test]
684    fn all_groups_no_group_tags_yields_nothing() {
685        // Plain message with no NO_* tags.
686        let mut dec = Decoder::new();
687        let msg = dec.decode(b"8=FIX.4.2\x0135=D\x0149=SENDER\x01").unwrap();
688        assert_eq!(msg.all_groups().count(), 0);
689    }
690
691    #[test]
692    fn all_groups_single_group_present() {
693        // Message contains only NO_MISC_FEES — all_groups must yield exactly one entry.
694        let mut dec = Decoder::new();
695        let msg = dec
696            .decode(b"35=J\x01136=1\x01137=7.00\x01138=USD\x01139=2\x01")
697            .unwrap();
698
699        let mut iter = msg.all_groups();
700        let (spec, mut instances) = iter.next().expect("expected one group");
701        assert_eq!(spec.count_tag, crate::tag::NO_MISC_FEES);
702
703        let g = instances.next().unwrap();
704        assert_eq!(g.find(crate::tag::MISC_FEE_AMT).unwrap().value, b"7.00");
705        assert_eq!(g.find(crate::tag::MISC_FEE_CURR).unwrap().value, b"USD");
706        assert!(iter.next().is_none());
707    }
708
709    #[test]
710    fn all_groups_two_different_groups_present() {
711        // Message contains both NO_MISC_FEES and NO_ROUTING_IDS.
712        let mut dec = Decoder::new();
713        let msg = dec
714            .decode(
715                b"35=D\x01215=2\x01216=1\x01217=ROUTE_A\x01216=2\x01217=ROUTE_B\x01\
716                  136=1\x01137=1.00\x01138=USD\x01139=3\x01",
717            )
718            .unwrap();
719
720        let found: Vec<_> = msg.all_groups().map(|(spec, _)| spec.count_tag).collect();
721        // Both group count tags must appear, in FIX42_GROUPS order.
722        assert!(found.contains(&crate::tag::NO_MISC_FEES));
723        assert!(found.contains(&crate::tag::NO_ROUTING_IDS));
724        assert_eq!(found.len(), 2);
725    }
726
727    #[test]
728    fn all_groups_count_zero_skipped() {
729        // NO_MISC_FEES=0 must not appear in all_groups output.
730        let mut dec = Decoder::new();
731        let msg = dec.decode(b"35=J\x01136=0\x01").unwrap();
732        assert_eq!(msg.all_groups().count(), 0);
733    }
734
735    #[test]
736    fn all_groups_instances_are_correct() {
737        // Verify that instances returned through all_groups() have the right field values.
738        let mut dec = Decoder::new();
739        let msg = dec
740            .decode(b"35=W\x01268=2\x01269=0\x01270=50.00\x01269=1\x01270=50.25\x01")
741            .unwrap();
742
743        let mut all = msg.all_groups();
744        let (spec, instances) = all.next().expect("expected MD_ENTRIES group");
745        assert_eq!(spec.count_tag, crate::tag::NO_MD_ENTRIES);
746
747        let entries: Vec<_> = instances.collect();
748        assert_eq!(entries.len(), 2);
749        assert_eq!(
750            entries[0].find(crate::tag::MD_ENTRY_TYPE).unwrap().value,
751            b"0"
752        );
753        assert_eq!(
754            entries[0].find(crate::tag::MD_ENTRY_PX).unwrap().value,
755            b"50.00"
756        );
757        assert_eq!(
758            entries[1].find(crate::tag::MD_ENTRY_TYPE).unwrap().value,
759            b"1"
760        );
761        assert_eq!(
762            entries[1].find(crate::tag::MD_ENTRY_PX).unwrap().value,
763            b"50.25"
764        );
765
766        assert!(all.next().is_none());
767    }
768
769    // -------------------------------------------------------------------------
770    // Group 10 — validate_body_length() and validate_checksum()
771    // -------------------------------------------------------------------------
772    //
773    // All expected checksums and body lengths are pre-computed and verified with:
774    //   sum(bytes before "10=") % 256  and  len(bytes between "9=…\x01" and "10=")
775
776    #[test]
777    fn validate_body_length_correct() {
778        // "8=FIX.4.2\x019=5\x0135=D\x0110=181\x01"
779        // Body = "35=D\x01" = 5 bytes. Declared 9=5. Should pass.
780        let mut dec = Decoder::new();
781        let msg = dec
782            .decode(b"8=FIX.4.2\x019=5\x0135=D\x0110=181\x01")
783            .unwrap();
784        assert!(msg.validate_body_length().is_ok());
785    }
786
787    #[test]
788    fn validate_body_length_wrong_value() {
789        // Declared 9=99 but actual body is 5 bytes. Should fail.
790        let mut dec = Decoder::new();
791        let msg = dec
792            .decode(b"8=FIX.4.2\x019=99\x0135=D\x0110=000\x01")
793            .unwrap();
794        assert!(matches!(
795            msg.validate_body_length().unwrap_err(),
796            FixError::InvalidBodyLength
797        ));
798    }
799
800    #[test]
801    fn validate_body_length_multi_field_body() {
802        // "8=FIX.4.2\x019=25\x0135=D\x0149=SENDER\x0156=TARGET\x0110=195\x01"
803        // Body = "35=D\x0149=SENDER\x0156=TARGET\x01" = 25 bytes. Declared 9=25.
804        let mut dec = Decoder::new();
805        let msg = dec
806            .decode(b"8=FIX.4.2\x019=25\x0135=D\x0149=SENDER\x0156=TARGET\x0110=195\x01")
807            .unwrap();
808        assert!(msg.validate_body_length().is_ok());
809    }
810
811    #[test]
812    fn validate_body_length_tag9_missing() {
813        // Message with fewer than 3 fields — no room for tag 8, 9, and 10.
814        let mut dec = Decoder::new();
815        let msg = dec.decode(b"8=FIX.4.2\x0135=D\x01").unwrap();
816        assert!(matches!(
817            msg.validate_body_length().unwrap_err(),
818            FixError::InvalidBodyLength
819        ));
820    }
821
822    #[test]
823    fn validate_body_length_tag9_not_second_field() {
824        // Tag 9 is not in position 1 — invalid message structure.
825        let mut dec = Decoder::new();
826        let msg = dec
827            .decode(b"8=FIX.4.2\x0135=D\x019=5\x0110=000\x01")
828            .unwrap();
829        assert!(matches!(
830            msg.validate_body_length().unwrap_err(),
831            FixError::InvalidBodyLength
832        ));
833    }
834
835    #[test]
836    fn validate_body_length_tag10_not_last_field() {
837        // Tag 10 is not the last field — invalid message structure.
838        let mut dec = Decoder::new();
839        let msg = dec
840            .decode(b"8=FIX.4.2\x019=5\x0110=000\x0135=D\x01")
841            .unwrap();
842        assert!(matches!(
843            msg.validate_body_length().unwrap_err(),
844            FixError::InvalidBodyLength
845        ));
846    }
847
848    #[test]
849    fn validate_checksum_correct() {
850        // "8=FIX.4.2\x019=5\x0135=D\x0110=181\x01"
851        // sum("8=FIX.4.2\x019=5\x0135=D\x01") % 256 = 181
852        let mut dec = Decoder::new();
853        let msg = dec
854            .decode(b"8=FIX.4.2\x019=5\x0135=D\x0110=181\x01")
855            .unwrap();
856        assert!(msg.validate_checksum().is_ok());
857    }
858
859    #[test]
860    fn validate_checksum_wrong_value() {
861        // Correct message bytes but checksum declared as 000 instead of 181.
862        let mut dec = Decoder::new();
863        let msg = dec
864            .decode(b"8=FIX.4.2\x019=5\x0135=D\x0110=000\x01")
865            .unwrap();
866        assert!(matches!(
867            msg.validate_checksum().unwrap_err(),
868            FixError::InvalidCheckSum
869        ));
870    }
871
872    #[test]
873    fn validate_checksum_multi_field_body() {
874        // "8=FIX.4.2\x019=25\x0135=D\x0149=SENDER\x0156=TARGET\x0110=195\x01"
875        // sum of bytes before "10=" = 195
876        let mut dec = Decoder::new();
877        let msg = dec
878            .decode(b"8=FIX.4.2\x019=25\x0135=D\x0149=SENDER\x0156=TARGET\x0110=195\x01")
879            .unwrap();
880        assert!(msg.validate_checksum().is_ok());
881    }
882
883    #[test]
884    fn validate_checksum_tag10_missing() {
885        // No tag 10 as last field — should fail.
886        let mut dec = Decoder::new();
887        let msg = dec.decode(b"8=FIX.4.2\x0135=D\x01").unwrap();
888        assert!(matches!(
889            msg.validate_checksum().unwrap_err(),
890            FixError::InvalidCheckSum
891        ));
892    }
893
894    #[test]
895    fn validate_checksum_tag10_not_last_field() {
896        // Tag 10 is not the last field — invalid structure.
897        let mut dec = Decoder::new();
898        let msg = dec.decode(b"8=FIX.4.2\x0110=181\x0135=D\x01").unwrap();
899        assert!(matches!(
900            msg.validate_checksum().unwrap_err(),
901            FixError::InvalidCheckSum
902        ));
903    }
904
905    #[test]
906    fn validate_both_correct_together() {
907        // Both validations pass on the same well-formed message.
908        let mut dec = Decoder::new();
909        let msg = dec
910            .decode(b"8=FIX.4.2\x019=25\x0135=D\x0149=SENDER\x0156=TARGET\x0110=195\x01")
911            .unwrap();
912        assert!(msg.validate_body_length().is_ok());
913        assert!(msg.validate_checksum().is_ok());
914    }
915}