1use crate::fix::{DayOfMonth, FixedPrice};
2use chrono::{DateTime, Datelike, Timelike, Utc};
3
4pub const SOH: u8 = 0x01;
6const MAX_BODY_LEN_DIGITS: usize = 20;
9
10#[macro_export]
20macro_rules! build_fix {
21 ($builder:expr, $seq_out:expr, $dt:expr, $msg_type:expr $(,)?) => {{
22 $builder
23 .begin_with(&$seq_out, &$dt, &$msg_type)
24 .finish()
25 }};
26 ($builder:expr, $seq_out:expr, $dt:expr, $msg_type:expr $(, $($rest:tt)+)?) => {{
27 let msg = $builder.begin_with(&$seq_out, &$dt, &$msg_type);
28 $crate::build_fix!(@fields msg $(, $($rest)+)?).finish()
29 }};
30 (@fields $msg:expr) => { $msg };
31 (@fields $msg:expr, ) => { $msg };
32 (@fields $msg:expr, ? $tag:expr => $val:expr ,) => {
33 $msg.try_field_ref($tag as u32, &$val)?
34 };
35 (@fields $msg:expr, $tag:expr => $val:expr ,) => {
36 $msg.field_ref($tag as u32, &$val)
37 };
38 (@fields $msg:expr, @$val:expr ,) => {
39 $msg.field_tagged_ref(&$val)
40 };
41 (@fields $msg:expr, $tag:expr, $val:expr ,) => {
42 $msg.field_ref($tag as u32, &$val)
43 };
44 (@fields $msg:expr, ? $tag:expr => $val:expr $(, $($rest:tt)+)?) => {
45 $crate::build_fix!(@fields $msg.try_field_ref($tag as u32, &$val)? $(, $($rest)+)?)
46 };
47 (@fields $msg:expr, $tag:expr => $val:expr $(, $($rest:tt)+)?) => {
48 $crate::build_fix!(@fields $msg.field_ref($tag as u32, &$val) $(, $($rest)+)?)
49 };
50 (@fields $msg:expr, @$val:expr $(, $($rest:tt)+)?) => {
51 $crate::build_fix!(@fields $msg.field_tagged_ref(&$val) $(, $($rest)+)?)
52 };
53 (@fields $msg:expr, $tag:expr, $val:expr $(, $($rest:tt)+)?) => {
54 $crate::build_fix!(@fields $msg.field_ref($tag as u32, &$val) $(, $($rest)+)?)
55 };
56}
57
58pub trait FixValue {
60 fn encode(&self, out: &mut Vec<u8>);
62}
63
64pub trait FixTaggedValue: FixValue {
66 const TAG: u32;
68}
69
70pub trait TryFixValue {
74 type Error;
76 fn try_encode(&self, out: &mut Vec<u8>) -> Result<(), Self::Error>;
78}
79
80impl TryFixValue for f64 {
81 type Error = FixError;
82
83 #[inline]
95 fn try_encode(&self, out: &mut Vec<u8>) -> Result<(), Self::Error> {
96 if encode_f64_checked(*self, out) {
97 Ok(())
98 } else {
99 Err(FixError::invalid_value(0))
101 }
102 }
103}
104
105pub trait FixSeqNum: FixValue {}
107
108impl FixSeqNum for u32 {}
110impl FixSeqNum for u64 {}
111impl FixSeqNum for usize {}
112impl FixSeqNum for i64 {}
113
114pub trait FixSendingTime {
116 fn encode_sending_time(&self, out: &mut Vec<u8>);
118}
119
120impl FixSendingTime for DateTime<Utc> {
122 #[inline]
123 fn encode_sending_time(&self, out: &mut Vec<u8>) {
124 encode_timestamp_utc(self, out);
125 }
126}
127
128pub struct SendingTimeStr<'a>(pub &'a str);
130
131impl FixSendingTime for SendingTimeStr<'_> {
132 #[inline]
133 fn encode_sending_time(&self, out: &mut Vec<u8>) {
134 out.extend_from_slice(self.0.as_bytes());
135 }
136}
137
138pub struct SendingTimeBytes<'a>(pub &'a [u8]);
140
141impl FixSendingTime for SendingTimeBytes<'_> {
142 #[inline]
143 fn encode_sending_time(&self, out: &mut Vec<u8>) {
144 out.extend_from_slice(self.0);
145 }
146}
147
148pub trait AsFixStr {
150 fn as_fix_str(&self) -> &'static str;
152}
153
154impl<T: AsFixStr> FixValue for T {
155 #[inline]
156 fn encode(&self, out: &mut Vec<u8>) {
157 out.extend_from_slice(self.as_fix_str().as_bytes());
158 }
159}
160
161impl FixValue for str {
164 #[inline]
165 fn encode(&self, out: &mut Vec<u8>) {
166 out.extend_from_slice(self.as_bytes());
167 }
168}
169
170impl FixValue for String {
171 #[inline]
172 fn encode(&self, out: &mut Vec<u8>) {
173 out.extend_from_slice(self.as_bytes());
174 }
175}
176
177impl FixValue for [u8] {
178 #[inline]
179 fn encode(&self, out: &mut Vec<u8>) {
180 out.extend_from_slice(self);
181 }
182}
183
184impl FixValue for bool {
185 #[inline]
186 fn encode(&self, out: &mut Vec<u8>) {
187 out.push(if *self { b'Y' } else { b'N' });
188 }
189}
190
191impl FixValue for u8 {
192 #[inline]
193 fn encode(&self, out: &mut Vec<u8>) {
194 push_u64_ascii(out, *self as u64);
195 }
196}
197
198impl FixValue for u32 {
199 #[inline]
200 fn encode(&self, out: &mut Vec<u8>) {
201 push_u64_ascii(out, *self as u64);
202 }
203}
204impl FixValue for u64 {
205 #[inline]
206 fn encode(&self, out: &mut Vec<u8>) {
207 push_u64_ascii(out, *self);
208 }
209}
210impl FixValue for usize {
211 #[inline]
212 fn encode(&self, out: &mut Vec<u8>) {
213 push_u64_ascii(out, *self as u64);
214 }
215}
216impl FixValue for i64 {
217 #[inline]
218 fn encode(&self, out: &mut Vec<u8>) {
219 push_i64_ascii(out, *self);
220 }
221}
222impl FixValue for i32 {
223 #[inline]
224 fn encode(&self, out: &mut Vec<u8>) {
225 (*self as i64).encode(out)
226 }
227}
228
229const DIGITS_U16: [u16; 100] = digits_00_99_u16();
230
231#[inline]
238fn encode_f64_checked(mut value: f64, out: &mut Vec<u8>) -> bool {
239 let start = out.len();
240 if !value.is_finite() {
241 return false;
242 }
243 if value < 0.0 {
244 out.push(b'-');
245 value = -value;
246 }
247 let whole = value as u64;
248 let len = out.len();
249 push_u64_ascii(out, whole);
250 let wd = if whole != 0 {
251 (out.len() - len) as u32
252 } else {
253 0
254 };
255
256 if wd < 15 {
257 let mut factor = 10u64.pow(15 - wd);
258 let fraction = value - whole as f64;
259
260 let scaled = fraction * (factor as f64);
262 let mut fraction = scaled.round();
263 if fraction <= 0.0 {
264 fraction = 0.0;
265 }
266 let mut fraction = fraction as u64;
267
268 if fraction == factor {
269 if let Some(next) = whole.checked_add(1) {
270 out.truncate(len); push_u64_ascii(out, next);
272 }
273 return true;
275 }
276
277 if fraction > 0 {
278 out.push(b'.');
279 while fraction > 0 {
280 let n: usize; if factor >= 100 {
283 factor /= 100;
284 n = (fraction / factor) as usize;
285 fraction %= factor;
286 } else if factor == 10 {
287 n = (fraction as usize) * 10;
288 fraction = 0;
289 } else {
290 n = fraction as usize;
291 fraction = 0;
292 }
293 let pair = DIGITS_U16[n];
294 let [tens, ones] = pair.to_ne_bytes();
295 if fraction > 0 || ones != b'0' {
296 push_2_u16(out, pair);
297 } else {
298 out.push(tens);
299 }
300 }
301 }
302 }
303
304 out.len() != start
306}
307
308impl FixValue for DateTime<Utc> {
310 #[inline]
311 fn encode(&self, out: &mut Vec<u8>) {
312 encode_timestamp_utc(self, out);
313 }
314}
315
316impl FixValue for DayOfMonth {
317 #[inline]
318 fn encode(&self, out: &mut Vec<u8>) {
319 self.0.encode(out);
320 }
321}
322
323impl<const W: u32, const F: u32> FixValue for FixedPrice<W, F> {
324 #[inline]
325 fn encode(&self, out: &mut Vec<u8>) {
326 let mut raw = self.raw();
327 if raw < 0 {
328 out.push(b'-');
329 raw = raw.wrapping_neg();
330 }
331
332 let scale_digits = F as usize;
333 if scale_digits == 0 {
334 push_u64_ascii(out, raw as u64);
335 return;
336 }
337 if scale_digits > 18 {
338 out.extend_from_slice(self.to_string().as_bytes());
339 return;
340 }
341
342 let scale = 10u64.pow(F);
343 let abs = raw as u64;
344 let int_part = abs / scale;
345 let frac_part = abs % scale;
346
347 push_u64_ascii(out, int_part);
348 if frac_part == 0 {
349 return;
350 }
351
352 out.push(b'.');
353 let mut frac_buf = [b'0'; 18];
354 let mut x = frac_part;
355 for i in (0..scale_digits).rev() {
356 frac_buf[i] = b'0' + (x % 10) as u8;
357 x /= 10;
358 }
359 let mut end = scale_digits;
360 while end > 0 && frac_buf[end - 1] == b'0' {
361 end -= 1;
362 }
363 out.extend_from_slice(&frac_buf[..end]);
364 }
365}
366
367#[inline]
370fn push_2_u16(out: &mut Vec<u8>, pair: u16) {
371 out.reserve(2);
372 let start = out.len();
373
374 let spare = out.spare_capacity_mut();
376 let dst = &mut spare[..2];
377
378 let bytes = pair.to_ne_bytes();
379 dst[0].write(bytes[0]);
380 dst[1].write(bytes[1]);
381
382 unsafe { out.set_len(start + 2) };
383}
384
385#[inline]
386fn push_2(out: &mut Vec<u8>, n: u8) {
387 push_2_u16(out, DIGITS_U16[n as usize]);
388}
389
390#[inline]
391fn push_3(out: &mut Vec<u8>, n: u16) {
392 out.push(b'0' + (n / 100) as u8);
393 push_2_u16(out, DIGITS_U16[(n % 100) as usize]);
394}
395
396#[inline]
397fn push_4(out: &mut Vec<u8>, n: u32) {
398 debug_assert!(n < 10_000);
399 push_2_u16(out, DIGITS_U16[(n / 100) as usize]);
400 push_2_u16(out, DIGITS_U16[(n % 100) as usize]);
401}
402
403#[inline]
404fn push_i64_ascii(out: &mut Vec<u8>, value: i64) {
405 if value < 0 {
406 out.push(b'-');
407 push_u64_ascii(out, value.wrapping_neg() as u64);
409 } else {
410 push_u64_ascii(out, value as u64);
411 }
412}
413
414fn push_u64_ascii(out: &mut Vec<u8>, mut value: u64) {
415 let len = num_digits(value);
416 let start = out.len();
417
418 out.reserve(len);
419 let spare = out.spare_capacity_mut();
420 debug_assert!(spare.len() >= len);
421
422 let mut i = len;
423
424 while value >= 100 {
425 let idx = (value % 100) as usize;
426 i -= 2;
427 let bytes = DIGITS_U16[idx].to_ne_bytes();
428 spare[i].write(bytes[0]);
429 spare[i + 1].write(bytes[1]);
430 value /= 100;
431 }
432
433 if value < 10 {
434 i -= 1;
435 spare[i].write(b'0' + value as u8);
436 } else {
437 let idx = value as usize;
438 i -= 2;
439 let bytes = DIGITS_U16[idx].to_ne_bytes();
440 spare[i].write(bytes[0]);
441 spare[i + 1].write(bytes[1]);
442 }
443
444 debug_assert_eq!(i, 0);
445 unsafe { out.set_len(start + len) };
446}
447
448#[inline]
449fn write_usize_ascii(dst: &mut [u8], mut n: usize) -> usize {
450 let mut tmp = [0u8; 20];
451 let mut i = tmp.len();
452 loop {
453 let digit = (n % 10) as u8;
454 i -= 1;
455 tmp[i] = b'0' + digit;
456 n /= 10;
457 if n == 0 {
458 break;
459 }
460 }
461 let len = tmp.len() - i;
462 dst[..len].copy_from_slice(&tmp[i..]);
463 len
464}
465
466#[inline]
467fn encode_timestamp_utc(dt: &DateTime<Utc>, out: &mut Vec<u8>) {
468 push_4(out, dt.year() as u32);
469 push_2(out, dt.month() as u8);
470 push_2(out, dt.day() as u8);
471 out.push(b'-');
472 push_2(out, dt.hour() as u8);
473 out.push(b':');
474 push_2(out, dt.minute() as u8);
475 out.push(b':');
476 push_2(out, dt.second() as u8);
477 out.push(b'.');
478 push_3(out, dt.timestamp_subsec_millis() as u16);
479}
480
481#[inline]
482fn push_checksum_3(out: &mut Vec<u8>, cksum: u8) {
483 out.push(b'0' + (cksum / 100));
484 out.push(b'0' + ((cksum / 10) % 10));
485 out.push(b'0' + (cksum % 10));
486}
487
488#[must_use = "Call .finish() to finalize the message (writes 8/9 and 10= checksum)"]
490pub struct FixMsg<'a> {
491 b: &'a mut FixBuilder,
492}
493
494impl<'a> FixMsg<'a> {
495 #[inline]
497 pub fn field<V: FixValue>(self, tag: u32, value: V) -> Self {
498 kv(&mut self.b.buf, tag, &value);
499 self
500 }
501
502 #[inline]
504 pub fn field_ref<V: FixValue + ?Sized>(self, tag: u32, value: &V) -> Self {
505 kv(&mut self.b.buf, tag, value);
506 self
507 }
508
509 #[inline]
511 pub fn field_tagged<V: FixTaggedValue>(self, value: V) -> Self {
512 kv(&mut self.b.buf, V::TAG, &value);
513 self
514 }
515
516 #[inline]
518 pub fn field_tagged_ref<V: FixTaggedValue + ?Sized>(self, value: &V) -> Self {
519 kv(&mut self.b.buf, V::TAG, value);
520 self
521 }
522
523 #[inline]
525 pub fn str(self, tag: u32, s: &str) -> Self {
526 kv(&mut self.b.buf, tag, s);
527 self
528 }
529
530 #[inline]
532 pub fn bytes(self, tag: u32, b: &[u8]) -> Self {
533 kv(&mut self.b.buf, tag, b);
534 self
535 }
536
537 #[inline]
539 pub fn try_field<V>(self, tag: u32, value: V) -> Result<Self, FixError>
540 where
541 V: TryFixValue<Error = FixError>,
542 {
543 try_kv(&mut self.b.buf, tag, &value)?;
544 Ok(self)
545 }
546
547 #[inline]
549 pub fn try_field_ref<V>(self, tag: u32, value: &V) -> Result<Self, FixError>
550 where
551 V: TryFixValue<Error = FixError> + ?Sized,
552 {
553 try_kv(&mut self.b.buf, tag, value)?;
554 Ok(self)
555 }
556
557 #[inline]
559 pub fn try_field_tagged<V>(self, value: V) -> Result<Self, FixError>
560 where
561 V: FixTaggedValue + TryFixValue<Error = FixError>,
562 {
563 try_kv(&mut self.b.buf, V::TAG, &value)?;
564 Ok(self)
565 }
566
567 #[inline]
569 pub fn try_field_tagged_ref<V>(self, value: &V) -> Result<Self, FixError>
570 where
571 V: FixTaggedValue + TryFixValue<Error = FixError> + ?Sized,
572 {
573 try_kv(&mut self.b.buf, V::TAG, value)?;
574 Ok(self)
575 }
576
577 #[inline]
579 pub fn fields(self, f: impl FnOnce(&mut FixMsgWriter<'_>)) -> Self {
580 let mut w = FixMsgWriter {
581 buf: &mut self.b.buf,
582 };
583 f(&mut w);
584 self
585 }
586
587 #[inline]
589 pub fn try_fields<F>(self, f: F) -> Result<Self, FixError>
590 where
591 F: FnOnce(&mut FixMsgWriter<'_>) -> Result<(), FixError>,
592 {
593 let mut w = FixMsgWriter {
594 buf: &mut self.b.buf,
595 };
596 f(&mut w)?;
597 Ok(self)
598 }
599
600 #[inline]
602 pub fn finish(self) -> &'a [u8] {
603 self.b.finish()
604 }
605}
606
607pub struct FixMsgWriter<'a> {
609 buf: &'a mut Vec<u8>,
610}
611
612impl<'a> FixMsgWriter<'a> {
613 #[inline]
615 pub fn field<V: FixValue>(&mut self, tag: u32, value: V) {
616 kv(self.buf, tag, &value);
617 }
618
619 #[inline]
621 pub fn field_ref<V: FixValue + ?Sized>(&mut self, tag: u32, v: &V) {
622 kv(self.buf, tag, v);
623 }
624
625 #[inline]
627 pub fn field_tagged<V: FixTaggedValue>(&mut self, value: V) {
628 kv(self.buf, V::TAG, &value);
629 }
630
631 #[inline]
633 pub fn field_tagged_ref<V: FixTaggedValue + ?Sized>(&mut self, value: &V) {
634 kv(self.buf, V::TAG, value);
635 }
636
637 #[inline]
639 pub fn try_field<V>(&mut self, tag: u32, v: V) -> Result<(), FixError>
640 where
641 V: TryFixValue<Error = FixError>,
642 {
643 try_kv(self.buf, tag, &v)
644 }
645
646 #[inline]
648 pub fn try_field_ref<V>(&mut self, tag: u32, v: &V) -> Result<(), FixError>
649 where
650 V: TryFixValue<Error = FixError> + ?Sized,
651 {
652 try_kv(self.buf, tag, v)
653 }
654
655 #[inline]
657 pub fn try_field_tagged<V>(&mut self, value: V) -> Result<(), FixError>
658 where
659 V: FixTaggedValue + TryFixValue<Error = FixError>,
660 {
661 try_kv(self.buf, V::TAG, &value)
662 }
663
664 #[inline]
666 pub fn try_field_tagged_ref<V>(&mut self, value: &V) -> Result<(), FixError>
667 where
668 V: FixTaggedValue + TryFixValue<Error = FixError> + ?Sized,
669 {
670 try_kv(self.buf, V::TAG, value)
671 }
672
673 #[inline]
675 pub fn str(&mut self, tag: u32, s: &str) {
676 kv(self.buf, tag, s);
677 }
678 #[inline]
680 pub fn bytes(&mut self, tag: u32, b: &[u8]) {
681 kv(self.buf, tag, b);
682 }
683}
684
685pub struct FixBuilder {
687 sender: Vec<u8>,
688 target: Vec<u8>,
689 buf: Vec<u8>,
690 fix_version: Vec<u8>,
691 prefix_space: usize,
693}
694
695impl FixBuilder {
696 pub fn new(
698 fix_version: impl Into<String>,
699 sender: impl Into<String>,
700 target: impl Into<String>,
701 ) -> Self {
702 Self::with_capacity(fix_version, sender, target, 1024)
703 }
704
705 pub fn with_capacity(
707 fix_version: impl Into<String>,
708 sender: impl Into<String>,
709 target: impl Into<String>,
710 capacity: usize,
711 ) -> Self {
712 let fix_version = fix_version.into().into_bytes();
713 let prefix_space = 2 + fix_version.len() + 1 + 2 + MAX_BODY_LEN_DIGITS + 1;
715 Self {
716 fix_version,
717 sender: sender.into().into_bytes(),
718 target: target.into().into_bytes(),
719 buf: Vec::with_capacity(capacity.max(prefix_space + 64)),
720 prefix_space,
721 }
722 }
723
724 pub fn begin_with<MT, SEQ, TS>(&mut self, seq_out: &SEQ, dt: &TS, msg_type: &MT) -> FixMsg<'_>
726 where
727 MT: FixValue + ?Sized,
728 SEQ: FixSeqNum + ?Sized,
729 TS: FixSendingTime + ?Sized,
730 {
731 self.buf.clear();
732 self.buf.resize(self.prefix_space, 0);
733
734 let buf = &mut self.buf;
735 kv(buf, 35, msg_type);
736 kv(buf, 34, seq_out);
737 kv_bytes(buf, 49, &self.sender);
738 kv_bytes(buf, 56, &self.target);
739
740 push_u64_ascii(buf, 52);
741 buf.push(b'=');
742 dt.encode_sending_time(buf);
743 buf.push(SOH);
744 FixMsg { b: self }
745 }
746
747 pub fn finish(&mut self) -> &[u8] {
749 let body_start = self.prefix_space;
750 let body_end = self.buf.len();
751 debug_assert!(body_end >= body_start);
752
753 let body_len = body_end - body_start;
754
755 let header_len = 2 + self.fix_version.len() + 1 + 2 + num_digits(body_len) + 1;
757 debug_assert!(header_len <= self.prefix_space);
760
761 let header_start = body_start - header_len;
762
763 {
765 let header = &mut self.buf[header_start..body_start];
766 let mut i = 0;
767
768 header[i] = b'8';
769 header[i + 1] = b'=';
770 i += 2;
771
772 header[i..i + self.fix_version.len()].copy_from_slice(&self.fix_version);
773 i += self.fix_version.len();
774
775 header[i] = SOH;
776 i += 1;
777
778 header[i] = b'9';
779 header[i + 1] = b'=';
780 i += 2;
781
782 i += write_usize_ascii(&mut header[i..], body_len);
783
784 header[i] = SOH;
785 i += 1;
786
787 debug_assert_eq!(i, header_len);
788 }
789
790 let mut sum: u32 = 0;
792 for &b in &self.buf[header_start..body_end] {
793 sum += b as u32;
794 }
795 let cksum = (sum % 256) as u8;
796
797 self.buf.extend_from_slice(b"10=");
799 push_checksum_3(&mut self.buf, cksum);
800 self.buf.push(SOH);
801
802 &self.buf[header_start..]
803 }
804}
805
806#[inline]
808fn kv<V: FixValue + ?Sized>(buf: &mut Vec<u8>, tag: u32, value: &V) {
809 push_u64_ascii(buf, tag as u64);
810 buf.push(b'=');
811 value.encode(buf);
812 buf.push(SOH);
813}
814
815#[inline]
816fn try_kv<V: TryFixValue<Error = FixError> + ?Sized>(
817 buf: &mut Vec<u8>,
818 tag: u32,
819 value: &V,
820) -> Result<(), FixError> {
821 let start = buf.len();
822 push_u64_ascii(buf, tag as u64);
823 buf.push(b'=');
824
825 if let Err(e) = value.try_encode(buf) {
826 buf.truncate(start);
827
828 return Err(match e {
830 FixError::InvalidValue { tag: 0, ctx } => FixError::InvalidValue { tag, ctx },
831 other => other,
832 });
833 }
834
835 buf.push(SOH);
836 Ok(())
837}
838
839#[inline]
840fn kv_bytes(buf: &mut Vec<u8>, tag: u32, bytes: &[u8]) {
841 push_u64_ascii(buf, tag as u64);
842 buf.push(b'=');
843 buf.extend_from_slice(bytes);
844 buf.push(SOH);
845}
846
847const fn digits_00_99_u16() -> [u16; 100] {
848 let mut out = [0u16; 100];
849 let mut n: usize = 0;
850 while n < 100 {
851 let tens = b'0' + (n as u8 / 10);
852 let ones = b'0' + (n as u8 % 10);
853 out[n] = u16::from_ne_bytes([tens, ones]);
854 n += 1;
855 }
856 out
857}
858
859use crate::FixError;
860use core::ops::DivAssign;
861
862trait UnsignedDigits: Copy + PartialOrd + From<u8> + DivAssign<Self> {}
863
864impl UnsignedDigits for u32 {}
865impl UnsignedDigits for u64 {}
866impl UnsignedDigits for usize {}
867
868#[inline]
869fn num_digits<T>(mut value: T) -> usize
870where
871 T: UnsignedDigits + From<u16>,
872{
873 let ten: T = 10u16.into();
874 let hundred: T = 100u16.into();
875 let thousand: T = 1000u16.into();
876 let ten_thousand: T = 10_000u16.into();
877
878 let mut len = 0usize;
879
880 while value >= ten_thousand {
881 value /= ten_thousand;
882 len += 4;
883 }
884
885 if value < hundred {
886 len += if value < ten { 1 } else { 2 };
887 } else {
888 len += if value < thousand { 3 } else { 4 };
889 }
890
891 len
892}
893
894#[cfg(test)]
895mod tests {
896 use super::*;
897 use crate::enums::{
898 HandlInst as FixHandlInst, MsgType as FixMsgType, OrdType as FixOrdType, Side as FixSide,
899 };
900 use crate::fix::{DayOfMonth as FixDayOfMonth, Price as FixPrice};
901 use crate::tags;
902 use chrono::{TimeZone, Timelike};
903
904 fn encode_f64(value: f64) -> String {
907 let mut out = Vec::new();
908 <f64 as TryFixValue>::try_encode(&value, &mut out).expect("test value is finite");
909 String::from_utf8(out).expect("f64 encoding should be ASCII")
910 }
911
912 #[derive(Copy, Clone, Debug)]
913 enum TestMsgType {
914 NewOrderSingle,
915 }
916 impl AsFixStr for TestMsgType {
917 fn as_fix_str(&self) -> &'static str {
918 match self {
919 TestMsgType::NewOrderSingle => "D",
920 }
921 }
922 }
923
924 #[derive(Copy, Clone, Debug)]
925 enum HandlInst {
926 Automated,
927 }
928 impl AsFixStr for HandlInst {
929 fn as_fix_str(&self) -> &'static str {
930 match self {
931 HandlInst::Automated => "1",
932 }
933 }
934 }
935 impl FixTaggedValue for HandlInst {
936 const TAG: u32 = tags::HANDL_INST;
937 }
938
939 #[derive(Copy, Clone, Debug)]
940 enum OrdType {
941 Limit,
942 }
943 impl AsFixStr for OrdType {
944 fn as_fix_str(&self) -> &'static str {
945 match self {
946 OrdType::Limit => "2",
947 }
948 }
949 }
950 impl FixTaggedValue for OrdType {
951 const TAG: u32 = tags::ORD_TYPE;
952 }
953
954 #[derive(Copy, Clone, Debug)]
955 struct ClientOrderId(u64);
956 impl FixValue for ClientOrderId {
957 fn encode(&self, out: &mut Vec<u8>) {
958 push_u64_ascii(out, self.0);
959 }
960 }
961
962 #[derive(Copy, Clone, Debug)]
964 struct Price {
965 mantissa: i64,
966 scale: u8,
967 }
968 impl FixValue for Price {
969 fn encode(&self, out: &mut Vec<u8>) {
970 let mut m = self.mantissa;
971 if m < 0 {
972 out.push(b'-');
973 m = -m;
974 }
975 let scale = self.scale as usize;
976 if scale == 0 {
977 push_u64_ascii(out, m as u64);
978 return;
979 }
980
981 const POW10: [u64; 19] = [
982 1,
983 10,
984 100,
985 1_000,
986 10_000,
987 100_000,
988 1_000_000,
989 10_000_000,
990 100_000_000,
991 1_000_000_000,
992 10_000_000_000,
993 100_000_000_000,
994 1_000_000_000_000,
995 10_000_000_000_000,
996 100_000_000_000_000,
997 1_000_000_000_000_000,
998 10_000_000_000_000_000,
999 100_000_000_000_000_000,
1000 1_000_000_000_000_000_000,
1001 ];
1002
1003 let div = POW10[scale];
1004 let um = m as u64;
1005 let int_part = um / div;
1006 let frac_part = um % div;
1007
1008 push_u64_ascii(out, int_part);
1009 out.push(b'.');
1010
1011 let mut tmp = [b'0'; 18];
1012 let mut x = frac_part;
1013 for i in (0..scale).rev() {
1014 tmp[i] = b'0' + (x % 10) as u8;
1015 x /= 10;
1016 }
1017 out.extend_from_slice(&tmp[..scale]);
1018 }
1019 }
1020
1021 fn find_field(msg: &[u8], tag: u32) -> Option<&[u8]> {
1024 let tag_s = tag.to_string();
1025 let tag_b = tag_s.as_bytes();
1026 for part in msg.split(|&b| b == SOH) {
1027 if part.is_empty() {
1028 continue;
1029 }
1030 let Some(eq) = part.iter().position(|&b| b == b'=') else {
1031 continue;
1032 };
1033 if &part[..eq] == tag_b {
1034 return Some(&part[eq + 1..]);
1035 }
1036 }
1037 None
1038 }
1039
1040 fn parse_u32_ascii(bytes: &[u8]) -> u32 {
1041 let mut v: u32 = 0;
1042 for &b in bytes {
1043 assert!(b.is_ascii_digit());
1044 v = v * 10 + (b - b'0') as u32;
1045 }
1046 v
1047 }
1048
1049 fn locate_body_bounds(msg: &[u8]) -> (usize, usize) {
1050 let mut body_start = None;
1052
1053 let mut idx = 0usize;
1054 for part in msg.split(|&b| b == SOH) {
1055 let part_len = part.len();
1056 if part_len == 0 {
1057 idx += 1;
1058 continue;
1059 }
1060 if let Some(eq) = part.iter().position(|&b| b == b'=')
1061 && &part[..eq] == b"9"
1062 {
1063 body_start = Some(idx + part_len + 1);
1064 break;
1065 }
1066 idx += part_len + 1;
1067 }
1068 let body_start = body_start.expect("tag 9 not found");
1069
1070 let mut checksum_tag_start = None;
1072 let mut idx2 = 0usize;
1073 for part in msg.split(|&b| b == SOH) {
1074 let part_len = part.len();
1075 if part_len == 0 {
1076 idx2 += 1;
1077 continue;
1078 }
1079 if part.starts_with(b"10=") {
1080 checksum_tag_start = Some(idx2);
1081 }
1082 idx2 += part_len + 1;
1083 }
1084 let checksum_tag_start = checksum_tag_start.expect("tag 10 not found");
1085
1086 (body_start, checksum_tag_start)
1087 }
1088
1089 fn verify_body_length(msg: &[u8]) {
1090 let body_len = parse_u32_ascii(find_field(msg, 9).expect("missing 9"));
1091 let (body_start, checksum_tag_start) = locate_body_bounds(msg);
1092 let actual = checksum_tag_start - body_start;
1093 assert_eq!(body_len as usize, actual, "BodyLength mismatch");
1094 }
1095
1096 fn verify_checksum(msg: &[u8]) {
1097 let cksum = parse_u32_ascii(find_field(msg, 10).expect("missing 10"));
1098 let (_body_start, checksum_tag_start) = locate_body_bounds(msg);
1099
1100 let sum: u32 = msg[..checksum_tag_start].iter().map(|&b| b as u32).sum();
1101 let expected = sum % 256;
1102 assert_eq!(cksum, expected, "CheckSum mismatch");
1103 }
1104
1105 fn fixed_dt() -> DateTime<Utc> {
1106 Utc.with_ymd_and_hms(2025, 1, 2, 3, 4, 5)
1107 .unwrap()
1108 .with_nanosecond(678_000_000)
1109 .unwrap()
1110 }
1111
1112 #[derive(Debug, fixlite_derive::FixDeserialize)]
1113 struct RoundTripMessage<'a> {
1114 #[fix(tag = 8)]
1115 begin_string: &'a str,
1116 #[fix(tag = 9)]
1117 body_length: u32,
1118 #[fix(tag = 35)]
1119 msg_type: FixMsgType,
1120 #[fix(tag = 49)]
1121 sender_comp_id: &'a str,
1122 #[fix(tag = 56)]
1123 target_comp_id: &'a str,
1124 #[fix(tag = 34)]
1125 msg_seq_num: u64,
1126 #[fix(tag = 52)]
1127 sending_time: DateTime<Utc>,
1128 #[fix(tag = 21)]
1129 handl_inst: FixHandlInst,
1130 #[fix(tag = 40)]
1131 ord_type: FixOrdType,
1132 #[fix(tag = 44)]
1133 price: FixPrice,
1134 #[fix(tag = 205)]
1135 maturity_day: FixDayOfMonth,
1136 #[fix(tag = 10)]
1137 checksum: u8,
1138 }
1139
1140 #[test]
1143 fn f64_encode_handles_integers_and_fractions() {
1144 assert_eq!(encode_f64(0.0), "0");
1145 assert_eq!(encode_f64(42.0), "42");
1146 assert_eq!(encode_f64(1.5), "1.5");
1147 assert_eq!(encode_f64(1.25), "1.25");
1148 assert_eq!(encode_f64(1.234375), "1.234375");
1149 }
1150
1151 #[test]
1152 fn f64_encode_handles_leading_fractional_zeros_and_signs() {
1153 assert_eq!(encode_f64(0.001953125), "0.001953125");
1154 assert_eq!(encode_f64(-0.5), "-0.5");
1155 assert_eq!(encode_f64(-1.25), "-1.25");
1156 }
1157
1158 #[test]
1159 fn f64_encode_handles_simple_decimals() {
1160 assert_eq!(encode_f64(0.1), "0.1");
1161 assert_eq!(encode_f64(0.01), "0.01");
1162 assert_eq!(encode_f64(10.01), "10.01");
1163 }
1164
1165 #[test]
1166 fn f64_encode_rounds_and_carries_whole() {
1167 assert_eq!(encode_f64(99_999_999_999_999.97), "100000000000000");
1168 }
1169
1170 #[test]
1171 fn begin_with_finish_produces_valid_header_length_and_checksum() {
1172 let mut b = FixBuilder::new("FIX.4.2", "S", "T");
1173
1174 let dt = fixed_dt();
1175 let seq = 7u32;
1176 let mt = TestMsgType::NewOrderSingle;
1177
1178 let msg = b
1179 .begin_with(&seq, &dt, &mt)
1180 .fields(|m| {
1181 m.field(11, ClientOrderId(123));
1182 m.field(21, HandlInst::Automated);
1183 m.field(40, OrdType::Limit);
1184 })
1185 .finish();
1186
1187 assert!(msg.starts_with(b"8=FIX.4.2\x01"), "Missing BeginString");
1188 assert_eq!(find_field(msg, 35).unwrap(), b"D");
1189 assert_eq!(find_field(msg, 34).unwrap(), b"7");
1190 assert_eq!(find_field(msg, 49).unwrap(), b"S");
1191 assert_eq!(find_field(msg, 56).unwrap(), b"T");
1192
1193 verify_body_length(msg);
1194 verify_checksum(msg);
1195 }
1196
1197 #[test]
1198 fn custom_types_are_encoded_correctly() {
1199 let mut b = FixBuilder::new("FIX.4.2", "SENDER", "TARGET");
1200
1201 let dt = fixed_dt();
1202 let seq = 1u32;
1203 let mt = TestMsgType::NewOrderSingle;
1204
1205 let cl = ClientOrderId(999_001);
1206 let px = Price {
1207 mantissa: 12345,
1208 scale: 2,
1209 };
1210
1211 let msg = b
1212 .begin_with(&seq, &dt, &mt)
1213 .field(tags::CL_ORD_ID, cl)
1214 .field_tagged(HandlInst::Automated)
1215 .field_tagged(OrdType::Limit)
1216 .field(44, px)
1217 .finish();
1218
1219 assert_eq!(find_field(msg, 11).unwrap(), b"999001");
1220 assert_eq!(find_field(msg, 21).unwrap(), b"1");
1221 assert_eq!(find_field(msg, 40).unwrap(), b"2");
1222 assert_eq!(find_field(msg, 44).unwrap(), b"123.45");
1223
1224 verify_body_length(msg);
1225 verify_checksum(msg);
1226 }
1227
1228 #[test]
1229 fn builder_reuse_does_not_leak_previous_fields() {
1230 let mut b = FixBuilder::new("FIX.4.2", "S", "T");
1231
1232 let dt = fixed_dt();
1233 let mt = TestMsgType::NewOrderSingle;
1234
1235 let seq1 = 1u32;
1236 let msg1 = b.begin_with(&seq1, &dt, &mt).str(9999, "LEAKME").finish();
1237
1238 assert!(find_field(msg1, 9999).is_some());
1239
1240 let seq2 = 2u32;
1241 let msg2 = b
1242 .begin_with(&seq2, &dt, &mt)
1243 .field(11, ClientOrderId(1))
1244 .finish();
1245
1246 assert!(
1247 find_field(msg2, 9999).is_none(),
1248 "Field leaked across messages"
1249 );
1250
1251 verify_body_length(msg2);
1252 verify_checksum(msg2);
1253 }
1254
1255 #[test]
1256 fn macro_build_fix_builds_message_and_validates_checksum_and_length() {
1257 let mut builder = FixBuilder::new("FIX.4.2", "S", "T");
1258
1259 let cl = ClientOrderId(77);
1260 let dt = fixed_dt();
1261
1262 let fix_message = build_fix!(
1263 builder,
1264 42u32,
1265 dt,
1266 TestMsgType::NewOrderSingle,
1267 tags::CL_ORD_ID => cl,
1268 @HandlInst::Automated,
1269 @OrdType::Limit,
1270 );
1271
1272 assert!(fix_message.starts_with(b"8=FIX.4.2\x01"));
1273 assert_eq!(find_field(fix_message, 35).unwrap(), b"D");
1274 assert_eq!(find_field(fix_message, 34).unwrap(), b"42");
1275 assert_eq!(find_field(fix_message, 11).unwrap(), b"77");
1276
1277 verify_body_length(fix_message);
1278 verify_checksum(fix_message);
1279 }
1280
1281 #[test]
1282 fn macro_build_fix_accepts_enum_references() {
1283 let mut builder = FixBuilder::new("FIX.4.2", "S", "T");
1284
1285 let dt = fixed_dt();
1286 let seq = 7u32;
1287 let fix_message = build_fix!(
1288 builder,
1289 seq,
1290 dt,
1291 FixMsgType::NewOrderSingle,
1292 @FixSide::Buy,
1293 @FixOrdType::Limit,
1294 );
1295
1296 assert_eq!(find_field(fix_message, 35).unwrap(), b"D");
1297 assert_eq!(find_field(fix_message, 34).unwrap(), b"7");
1298 assert_eq!(find_field(fix_message, 54).unwrap(), b"1");
1299 assert_eq!(find_field(fix_message, 40).unwrap(), b"2");
1300
1301 verify_body_length(fix_message);
1302 verify_checksum(fix_message);
1303 }
1304
1305 #[test]
1306 fn macro_build_fix_supports_arrow_and_tagged_values() {
1307 let mut builder = FixBuilder::new("FIX.4.2", "S", "T");
1308
1309 let dt = fixed_dt();
1310 let fix_message = build_fix!(
1311 builder,
1312 3u32,
1313 dt,
1314 FixMsgType::NewOrderSingle,
1315 tags::SIDE => FixSide::Buy,
1316 tags::ORD_TYPE => FixOrdType::Limit,
1317 @FixHandlInst::Automated,
1318 );
1319
1320 assert_eq!(find_field(fix_message, 35).unwrap(), b"D");
1321 assert_eq!(find_field(fix_message, 34).unwrap(), b"3");
1322 assert_eq!(find_field(fix_message, tags::SIDE).unwrap(), b"1");
1323 assert_eq!(find_field(fix_message, tags::ORD_TYPE).unwrap(), b"2");
1324 assert_eq!(find_field(fix_message, tags::HANDL_INST).unwrap(), b"1");
1325
1326 verify_body_length(fix_message);
1327 verify_checksum(fix_message);
1328 }
1329
1330 #[test]
1331 fn field_tagged_uses_enum_tag_constants() {
1332 let mut b = FixBuilder::new("FIX.4.2", "S", "T");
1333
1334 let dt = fixed_dt();
1335 let seq = 9u32;
1336 let mt = FixMsgType::NewOrderSingle;
1337 let side = FixSide::Buy;
1338 let ord_type = FixOrdType::Limit;
1339
1340 let msg = b
1341 .begin_with(&seq, &dt, &mt)
1342 .field_tagged_ref(&side)
1343 .field_tagged(ord_type)
1344 .finish();
1345
1346 assert_eq!(find_field(msg, tags::SIDE).unwrap(), b"1");
1347 assert_eq!(find_field(msg, tags::ORD_TYPE).unwrap(), b"2");
1348
1349 verify_body_length(msg);
1350 verify_checksum(msg);
1351 }
1352
1353 #[test]
1354 fn builder_round_trip_with_fix_types() {
1355 let mut b = FixBuilder::new("FIX.4.2", "SENDER", "TARGET");
1356
1357 let dt = fixed_dt();
1358 let seq = 42;
1359 let price: FixPrice = "123.4500".parse().unwrap();
1360 let day = FixDayOfMonth(7);
1361
1362 let msg = b
1363 .begin_with(&seq, &dt, &FixMsgType::NewOrderSingle)
1364 .field(21, FixHandlInst::Automated)
1365 .field(40, FixOrdType::Limit)
1366 .field(44, price)
1367 .field(205, day)
1368 .finish();
1369
1370 let parsed = <RoundTripMessage as crate::FixDeserialize>::from_fix(msg).unwrap();
1371 assert_eq!(parsed.begin_string, "FIX.4.2");
1372 assert_eq!(parsed.msg_type, FixMsgType::NewOrderSingle);
1373 assert_eq!(parsed.sender_comp_id, "SENDER");
1374 assert_eq!(parsed.target_comp_id, "TARGET");
1375 assert_eq!(parsed.msg_seq_num, seq);
1376 assert_eq!(parsed.sending_time, dt);
1377 assert_eq!(parsed.handl_inst, FixHandlInst::Automated);
1378 assert_eq!(parsed.ord_type, FixOrdType::Limit);
1379 assert_eq!(parsed.price, price);
1380 assert_eq!(parsed.maturity_day, day);
1381 assert_eq!(
1382 parsed.body_length,
1383 parse_u32_ascii(find_field(msg, 9).unwrap())
1384 );
1385 assert_eq!(
1386 parsed.checksum as u32,
1387 parse_u32_ascii(find_field(msg, 10).unwrap())
1388 );
1389
1390 verify_body_length(msg);
1391 verify_checksum(msg);
1392 }
1393
1394 #[test]
1395 fn fields_closure_is_nicer_for_conditionals_and_loops() {
1396 let mut b = FixBuilder::new("FIX.4.2", "S", "T");
1397
1398 let dt = fixed_dt();
1399 let seq = 100u32;
1400 let mt = TestMsgType::NewOrderSingle;
1401
1402 let cl_ord_id = Some(ClientOrderId(777));
1404 let account: Option<&str> = None;
1405
1406 let extras: &[(u32, &str)] = &[
1408 (58, "hello"), (100, "XNAS"), (110, "1"), ];
1412
1413 let msg = b
1414 .begin_with(&seq, &dt, &mt)
1415 .fields(|m| {
1420 m.field(21, HandlInst::Automated);
1422 m.field(40, OrdType::Limit);
1423
1424 if let Some(cl) = cl_ord_id {
1426 m.field(11, cl);
1427 }
1428 if let Some(acct) = account {
1429 m.str(1, acct); }
1431
1432 for &(tag, val) in extras {
1434 m.str(tag, val);
1435 }
1436 })
1437 .finish();
1438
1439 assert_eq!(find_field(msg, 34).unwrap(), b"100");
1441 assert_eq!(find_field(msg, 11).unwrap(), b"777");
1442 assert_eq!(find_field(msg, 58).unwrap(), b"hello");
1443 assert_eq!(find_field(msg, 100).unwrap(), b"XNAS");
1444
1445 verify_body_length(msg);
1446 verify_checksum(msg);
1447 }
1448
1449 #[test]
1450 fn long_fix_version_does_not_overflow_prefix() {
1451 let mut b = FixBuilder::new("FIXT.1.1", "S", "T");
1456 let dt = fixed_dt();
1457 let seq = 1u32;
1458
1459 let big_text: String = "x".repeat(5000);
1461 let msg = b
1462 .begin_with(&seq, &dt, &FixMsgType::NewOrderSingle)
1463 .field_ref(58, &big_text)
1464 .finish();
1465
1466 assert!(msg.starts_with(b"8=FIXT.1.1\x01"));
1467 verify_body_length(msg);
1468 verify_checksum(msg);
1469 }
1470
1471 #[test]
1472 fn macro_build_fix_supports_fallible_arrow_for_f64() {
1473 fn build_msg(builder: &mut FixBuilder) -> Result<&[u8], FixError> {
1476 let dt = fixed_dt();
1477 let price = 150.25_f64;
1478 let qty = 100.0_f64;
1479 Ok(build_fix!(
1480 builder,
1481 1u32,
1482 dt,
1483 FixMsgType::NewOrderSingle,
1484 ?tags::PRICE => price,
1485 ?tags::ORDER_QTY => qty,
1486 @FixSide::Buy,
1487 ))
1488 }
1489
1490 let mut builder = FixBuilder::new("FIX.4.2", "S", "T");
1491 let msg = build_msg(&mut builder).unwrap();
1492
1493 assert_eq!(find_field(msg, tags::PRICE).unwrap(), b"150.25");
1494 assert_eq!(find_field(msg, tags::ORDER_QTY).unwrap(), b"100");
1495 assert_eq!(find_field(msg, tags::SIDE).unwrap(), b"1");
1496 verify_body_length(msg);
1497 verify_checksum(msg);
1498 }
1499
1500 #[test]
1501 fn macro_build_fix_propagates_f64_error_for_nan() {
1502 fn build_msg(builder: &mut FixBuilder) -> Result<&[u8], FixError> {
1503 let dt = fixed_dt();
1504 Ok(build_fix!(
1505 builder,
1506 1u32,
1507 dt,
1508 FixMsgType::NewOrderSingle,
1509 ?tags::PRICE => f64::NAN,
1510 ))
1511 }
1512
1513 let mut builder = FixBuilder::new("FIX.4.2", "S", "T");
1514 let err = build_msg(&mut builder).unwrap_err();
1515 assert!(matches!(
1516 err,
1517 FixError::InvalidValue {
1518 tag: tags::PRICE,
1519 ..
1520 }
1521 ));
1522 }
1523
1524 #[test]
1525 fn macro_build_fix_accepts_string_via_owned_string() {
1526 let mut builder = FixBuilder::new("FIX.4.2", "S", "T");
1528 let dt = fixed_dt();
1529 let cl_ord_id = String::from("ABC123");
1530 let msg = build_fix!(
1531 builder, 1u32, dt, FixMsgType::NewOrderSingle,
1532 tags::CL_ORD_ID => cl_ord_id,
1533 @FixSide::Buy,
1534 );
1535 assert_eq!(find_field(msg, tags::CL_ORD_ID).unwrap(), b"ABC123");
1536 }
1537
1538 #[test]
1539 fn macro_build_fix_accepts_string_via_explicit_deref() {
1540 let mut builder = FixBuilder::new("FIX.4.2", "S", "T");
1544 let dt = fixed_dt();
1545 let cl_ord_id: &str = "ABC123";
1546 let msg = build_fix!(
1547 builder, 1u32, dt, FixMsgType::NewOrderSingle,
1548 tags::CL_ORD_ID => *cl_ord_id,
1549 @FixSide::Buy,
1550 );
1551 assert_eq!(find_field(msg, tags::CL_ORD_ID).unwrap(), b"ABC123");
1552 }
1553
1554 #[test]
1555 fn try_field_f64_rejects_nan() {
1556 let mut b = FixBuilder::new("FIX.4.2", "S", "T");
1557 let dt = fixed_dt();
1558 let seq = 1u32;
1559 let mt = TestMsgType::NewOrderSingle;
1560
1561 let err = match b.begin_with(&seq, &dt, &mt).try_field_ref(44, &f64::NAN) {
1562 Ok(_) => panic!("expected error"),
1563 Err(e) => e,
1564 };
1565
1566 assert!(matches!(err, FixError::InvalidValue { tag: 44, .. }));
1567 }
1568}