1pub const SYMBOL_CSTR_LEN_V1: usize = 22;
5pub const SYMBOL_CSTR_LEN_V2: usize = 71;
7pub const SYMBOL_CSTR_LEN_V3: usize = SYMBOL_CSTR_LEN_V2;
9pub(crate) const METADATA_RESERVED_LEN_V1: usize = 47;
10
11pub const fn version_symbol_cstr_len(version: u8) -> usize {
13 if version < 2 {
14 SYMBOL_CSTR_LEN_V1
15 } else {
16 SYMBOL_CSTR_LEN_V2
17 }
18}
19pub use crate::record::ErrorMsg as ErrorMsgV2;
20pub use crate::record::InstrumentDefMsg as InstrumentDefMsgV2;
21pub use crate::record::SymbolMappingMsg as SymbolMappingMsgV2;
22pub use crate::record::SystemMsg as SystemMsgV2;
23
24use std::os::raw::c_char;
25
26#[cfg(not(feature = "python"))]
29use dbn_macros::MockPyo3;
30
31use crate::{
32 macros::{dbn_record, CsvSerialize, JsonSerialize},
33 record::{transmute_header_bytes, transmute_record_bytes},
34 rtype, HasRType, RecordHeader, RecordRef, SecurityUpdateAction, UserDefinedInstrument,
35 VersionUpgradePolicy, WithTsOut, DBN_VERSION,
36};
37
38pub unsafe fn decode_record_ref<'a>(
52 version: u8,
53 upgrade_policy: VersionUpgradePolicy,
54 ts_out: bool,
55 compat_buffer: &'a mut [u8; crate::MAX_RECORD_LEN],
56 input: &'a [u8],
57) -> RecordRef<'a> {
58 match (version, upgrade_policy) {
59 (1, VersionUpgradePolicy::UpgradeToV2) => {
60 let header = transmute_header_bytes(input).unwrap();
61 match header.rtype {
62 rtype::INSTRUMENT_DEF => {
63 return upgrade_record::<InstrumentDefMsgV1, InstrumentDefMsgV2>(
64 ts_out,
65 compat_buffer,
66 input,
67 );
68 }
69 rtype::SYMBOL_MAPPING => {
70 return upgrade_record::<SymbolMappingMsgV1, SymbolMappingMsgV2>(
71 ts_out,
72 compat_buffer,
73 input,
74 );
75 }
76 rtype::ERROR => {
77 return upgrade_record::<ErrorMsgV1, ErrorMsgV2>(ts_out, compat_buffer, input);
78 }
79 rtype::SYSTEM => {
80 return upgrade_record::<SystemMsgV1, SystemMsgV2>(
81 ts_out,
82 compat_buffer,
83 input,
84 );
85 }
86 _ => (),
87 }
88 }
89 (2, VersionUpgradePolicy::UpgradeToV2) => {}
90 (..=DBN_VERSION, VersionUpgradePolicy::AsIs) => {}
91 _ => panic!("Unsupported version {version}"),
92 }
93 RecordRef::new(input)
94}
95
96pub(crate) unsafe fn choose_record_ref<'a>(
97 version: u8,
98 upgrade_policy: VersionUpgradePolicy,
99 buffer: &'a [u8],
100 compat_buffer: &'a [u8],
101) -> RecordRef<'a> {
102 if version == 1 && upgrade_policy == VersionUpgradePolicy::UpgradeToV2 {
103 let header = transmute_header_bytes(buffer).unwrap();
104 match header.rtype {
105 rtype::INSTRUMENT_DEF | rtype::SYMBOL_MAPPING | rtype::ERROR | rtype::SYSTEM => {
106 return RecordRef::new(compat_buffer);
107 }
108 _ => (),
109 }
110 }
111 RecordRef::new(buffer)
112}
113
114unsafe fn upgrade_record<'a, T, U>(
115 ts_out: bool,
116 compat_buffer: &'a mut [u8; crate::MAX_RECORD_LEN],
117 input: &'a [u8],
118) -> RecordRef<'a>
119where
120 T: HasRType,
121 U: AsRef<[u8]> + HasRType + for<'b> From<&'b T>,
122{
123 if ts_out {
124 let rec = transmute_record_bytes::<WithTsOut<T>>(input).unwrap();
125 let upgraded = WithTsOut::new(U::from(&rec.rec), rec.ts_out);
126 compat_buffer[..upgraded.as_ref().len()].copy_from_slice(upgraded.as_ref());
127 } else {
128 let upgraded = U::from(transmute_record_bytes::<T>(input).unwrap());
129 compat_buffer[..upgraded.as_ref().len()].copy_from_slice(upgraded.as_ref());
130 }
131 RecordRef::new(compat_buffer)
132}
133
134pub trait SymbolMappingRec: HasRType {
136 fn stype_in_symbol(&self) -> crate::Result<&str>;
141
142 fn stype_out_symbol(&self) -> crate::Result<&str>;
147
148 fn start_ts(&self) -> Option<time::OffsetDateTime>;
151
152 fn end_ts(&self) -> Option<time::OffsetDateTime>;
155}
156
157pub trait InstrumentDefRec: HasRType {
159 fn raw_symbol(&self) -> crate::Result<&str>;
164
165 fn asset(&self) -> crate::Result<&str>;
170
171 fn security_type(&self) -> crate::Result<&str>;
177
178 fn security_update_action(&self) -> crate::Result<SecurityUpdateAction>;
185
186 fn channel_id(&self) -> u16;
189}
190
191#[repr(C)]
196#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
197#[cfg_attr(feature = "trivial_copy", derive(Copy))]
198#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
199#[cfg_attr(
200 feature = "python",
201 pyo3::pyclass(dict, module = "databento_dbn"),
202 derive(crate::macros::PyFieldDesc)
203)]
204#[cfg_attr(not(feature = "python"), derive(MockPyo3))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
206#[dbn_record(rtype::INSTRUMENT_DEF)]
207pub struct InstrumentDefMsgV1 {
208 #[pyo3(get, set)]
210 pub hd: RecordHeader,
211 #[dbn(encode_order(0), index_ts, unix_nanos)]
214 #[pyo3(get, set)]
215 pub ts_recv: u64,
216 #[dbn(fixed_price)]
219 #[pyo3(get, set)]
220 pub min_price_increment: i64,
221 #[dbn(fixed_price)]
223 #[pyo3(get, set)]
224 pub display_factor: i64,
225 #[dbn(unix_nanos)]
231 #[pyo3(get, set)]
232 pub expiration: u64,
233 #[dbn(unix_nanos)]
239 #[pyo3(get, set)]
240 pub activation: u64,
241 #[dbn(fixed_price)]
244 #[pyo3(get, set)]
245 pub high_limit_price: i64,
246 #[dbn(fixed_price)]
249 #[pyo3(get, set)]
250 pub low_limit_price: i64,
251 #[dbn(fixed_price)]
254 #[pyo3(get, set)]
255 pub max_price_variation: i64,
256 #[dbn(fixed_price)]
258 #[pyo3(get, set)]
259 pub trading_reference_price: i64,
260 #[dbn(fixed_price)]
262 #[pyo3(get, set)]
263 pub unit_of_measure_qty: i64,
264 #[dbn(fixed_price)]
267 #[pyo3(get, set)]
268 pub min_price_increment_amount: i64,
269 #[dbn(fixed_price)]
272 #[pyo3(get, set)]
273 pub price_ratio: i64,
274 #[pyo3(get, set)]
276 pub inst_attrib_value: i32,
277 #[pyo3(get, set)]
279 pub underlying_id: u32,
280 #[pyo3(get, set)]
282 pub raw_instrument_id: u32,
283 #[pyo3(get, set)]
285 pub market_depth_implied: i32,
286 #[pyo3(get, set)]
288 pub market_depth: i32,
289 #[pyo3(get, set)]
291 pub market_segment_id: u32,
292 #[pyo3(get, set)]
294 pub max_trade_vol: u32,
295 #[pyo3(get, set)]
297 pub min_lot_size: i32,
298 #[pyo3(get, set)]
300 pub min_lot_size_block: i32,
301 #[pyo3(get, set)]
304 pub min_lot_size_round_lot: i32,
305 #[pyo3(get, set)]
307 pub min_trade_vol: u32,
308 #[doc(hidden)]
309 pub _reserved2: [u8; 4],
310 #[pyo3(get, set)]
312 pub contract_multiplier: i32,
313 #[pyo3(get, set)]
316 pub decay_quantity: i32,
317 #[pyo3(get, set)]
319 pub original_contract_size: i32,
320 #[doc(hidden)]
321 pub _reserved3: [u8; 4],
322 #[pyo3(get, set)]
325 pub trading_reference_date: u16,
326 #[pyo3(get, set)]
328 pub appl_id: i16,
329 #[pyo3(get, set)]
331 pub maturity_year: u16,
332 #[pyo3(get, set)]
334 pub decay_start_date: u16,
335 #[pyo3(get, set)]
337 pub channel_id: u16,
338 #[dbn(fmt_method)]
340 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
341 pub currency: [c_char; 4],
342 #[dbn(fmt_method)]
344 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
345 pub settl_currency: [c_char; 4],
346 #[dbn(fmt_method)]
348 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
349 pub secsubtype: [c_char; 6],
350 #[dbn(encode_order(2), fmt_method)]
352 pub raw_symbol: [c_char; SYMBOL_CSTR_LEN_V1],
353 #[dbn(fmt_method)]
355 pub group: [c_char; 21],
356 #[dbn(fmt_method)]
358 pub exchange: [c_char; 5],
359 #[dbn(fmt_method)]
361 pub asset: [c_char; 7],
362 #[dbn(fmt_method)]
364 pub cfi: [c_char; 7],
365 #[dbn(fmt_method)]
367 pub security_type: [c_char; 7],
368 #[dbn(fmt_method)]
370 pub unit_of_measure: [c_char; 31],
371 #[dbn(fmt_method)]
373 pub underlying: [c_char; 21],
374 #[dbn(fmt_method)]
376 pub strike_price_currency: [c_char; 4],
377 #[dbn(c_char, encode_order(4))]
379 #[pyo3(set)]
380 pub instrument_class: c_char,
381 #[doc(hidden)]
382 pub _reserved4: [u8; 2],
383 #[dbn(fixed_price)]
386 #[pyo3(get, set)]
387 pub strike_price: i64,
388 #[doc(hidden)]
389 pub _reserved5: [u8; 6],
390 #[dbn(c_char)]
392 #[pyo3(set)]
393 pub match_algorithm: c_char,
394 #[pyo3(get, set)]
396 pub md_security_trading_status: u8,
397 #[pyo3(get, set)]
399 pub main_fraction: u8,
400 #[pyo3(get, set)]
402 pub price_display_format: u8,
403 #[pyo3(get, set)]
405 pub settl_price_type: u8,
406 #[pyo3(get, set)]
408 pub sub_fraction: u8,
409 #[pyo3(get, set)]
411 pub underlying_product: u8,
412 #[dbn(encode_order(3))]
414 #[pyo3(set)]
415 pub security_update_action: SecurityUpdateAction,
416 #[pyo3(get, set)]
418 pub maturity_month: u8,
419 #[pyo3(get, set)]
421 pub maturity_day: u8,
422 #[pyo3(get, set)]
424 pub maturity_week: u8,
425 #[pyo3(set)]
427 pub user_defined_instrument: UserDefinedInstrument,
428 #[pyo3(get, set)]
430 pub contract_multiplier_unit: i8,
431 #[pyo3(get, set)]
433 pub flow_schedule_type: i8,
434 #[pyo3(get, set)]
436 pub tick_rule: u8,
437 #[doc(hidden)]
439 pub _dummy: [u8; 3],
440}
441
442#[repr(C)]
445#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
446#[cfg_attr(feature = "trivial_copy", derive(Copy))]
447#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
448#[cfg_attr(
449 feature = "python",
450 pyo3::pyclass(dict, module = "databento_dbn"),
451 derive(crate::macros::PyFieldDesc)
452)]
453#[cfg_attr(not(feature = "python"), derive(MockPyo3))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
455#[dbn_record(rtype::ERROR)]
456pub struct ErrorMsgV1 {
457 #[pyo3(get, set)]
459 pub hd: RecordHeader,
460 #[dbn(fmt_method)]
462 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
463 pub err: [c_char; 64],
464}
465
466#[repr(C)]
469#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
470#[cfg_attr(feature = "trivial_copy", derive(Copy))]
471#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
472#[cfg_attr(
473 feature = "python",
474 pyo3::pyclass(dict, module = "databento_dbn"),
475 derive(crate::macros::PyFieldDesc)
476)]
477#[cfg_attr(not(feature = "python"), derive(MockPyo3))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
479#[dbn_record(rtype::SYMBOL_MAPPING)]
480pub struct SymbolMappingMsgV1 {
481 #[pyo3(get, set)]
483 pub hd: RecordHeader,
484 #[dbn(fmt_method)]
486 pub stype_in_symbol: [c_char; SYMBOL_CSTR_LEN_V1],
487 #[dbn(fmt_method)]
489 pub stype_out_symbol: [c_char; SYMBOL_CSTR_LEN_V1],
490 #[doc(hidden)]
492 pub _dummy: [u8; 4],
493 #[dbn(unix_nanos)]
496 #[pyo3(get, set)]
497 pub start_ts: u64,
498 #[dbn(unix_nanos)]
501 #[pyo3(get, set)]
502 pub end_ts: u64,
503}
504
505#[repr(C)]
508#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
509#[cfg_attr(feature = "trivial_copy", derive(Copy))]
510#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
511#[cfg_attr(
512 feature = "python",
513 pyo3::pyclass(dict, module = "databento_dbn"),
514 derive(crate::macros::PyFieldDesc)
515)]
516#[cfg_attr(not(feature = "python"), derive(MockPyo3))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
518#[dbn_record(rtype::SYSTEM)]
519pub struct SystemMsgV1 {
520 #[pyo3(get, set)]
522 pub hd: RecordHeader,
523 #[dbn(fmt_method)]
525 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
526 pub msg: [c_char; 64],
527}
528
529#[repr(C)]
532#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
533#[cfg_attr(feature = "trivial_copy", derive(Copy))]
534#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
535#[cfg_attr(
536 feature = "python",
537 pyo3::pyclass(dict, module = "databento_dbn"),
538 derive(crate::macros::PyFieldDesc)
539)]
540#[cfg_attr(not(feature = "python"), derive(MockPyo3))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
542#[dbn_record(rtype::INSTRUMENT_DEF)]
543pub struct InstrumentDefMsgV3 {
544 #[pyo3(get, set)]
546 pub hd: RecordHeader,
547 #[dbn(encode_order(0), index_ts, unix_nanos)]
550 #[pyo3(get, set)]
551 pub ts_recv: u64,
552 #[dbn(fixed_price)]
555 #[pyo3(get, set)]
556 pub min_price_increment: i64,
557 #[dbn(fixed_price)]
560 #[pyo3(get, set)]
561 pub display_factor: i64,
562 #[dbn(unix_nanos)]
568 #[pyo3(get, set)]
569 pub expiration: u64,
570 #[dbn(unix_nanos)]
576 #[pyo3(get, set)]
577 pub activation: u64,
578 #[dbn(fixed_price)]
581 #[pyo3(get, set)]
582 pub high_limit_price: i64,
583 #[dbn(fixed_price)]
586 #[pyo3(get, set)]
587 pub low_limit_price: i64,
588 #[dbn(fixed_price)]
591 #[pyo3(get, set)]
592 pub max_price_variation: i64,
593 #[dbn(fixed_price)]
596 #[pyo3(get, set)]
597 pub unit_of_measure_qty: i64,
598 #[dbn(fixed_price)]
601 #[pyo3(get, set)]
602 pub min_price_increment_amount: i64,
603 #[dbn(fixed_price)]
606 #[pyo3(get, set)]
607 pub price_ratio: i64,
608 #[dbn(fixed_price, encode_order(54))]
611 #[pyo3(get, set)]
612 pub strike_price: i64,
613 #[dbn(encode_order(20))]
615 #[pyo3(get, set)]
616 pub raw_instrument_id: u64,
617 #[dbn(fixed_price, encode_order(165))]
619 #[pyo3(get, set)]
620 pub leg_price: i64,
621 #[dbn(fixed_price, encode_order(166))]
623 #[pyo3(get, set)]
624 pub leg_delta: i64,
625 #[dbn(fmt_binary)]
627 #[pyo3(get, set)]
628 pub inst_attrib_value: i32,
629 #[pyo3(get, set)]
631 pub underlying_id: u32,
632 #[pyo3(get, set)]
634 pub market_depth_implied: i32,
635 #[pyo3(get, set)]
637 pub market_depth: i32,
638 #[pyo3(get, set)]
640 pub market_segment_id: u32,
641 #[pyo3(get, set)]
643 pub max_trade_vol: u32,
644 #[pyo3(get, set)]
646 pub min_lot_size: i32,
647 #[pyo3(get, set)]
649 pub min_lot_size_block: i32,
650 #[pyo3(get, set)]
653 pub min_lot_size_round_lot: i32,
654 #[pyo3(get, set)]
656 pub min_trade_vol: u32,
657 #[pyo3(get, set)]
659 pub contract_multiplier: i32,
660 #[pyo3(get, set)]
663 pub decay_quantity: i32,
664 #[pyo3(get, set)]
666 pub original_contract_size: i32,
667 #[dbn(encode_order(160))]
669 #[pyo3(get, set)]
670 pub leg_instrument_id: u32,
671 #[dbn(encode_order(167))]
673 #[pyo3(get, set)]
674 pub leg_ratio_price_numerator: i32,
675 #[dbn(encode_order(168))]
677 #[pyo3(get, set)]
678 pub leg_ratio_price_denominator: i32,
679 #[dbn(encode_order(169))]
681 #[pyo3(get, set)]
682 pub leg_ratio_qty_numerator: i32,
683 #[dbn(encode_order(170))]
685 #[pyo3(get, set)]
686 pub leg_ratio_qty_denominator: i32,
687 #[dbn(encode_order(171))]
689 #[pyo3(get, set)]
690 pub leg_underlying_id: u32,
691 #[pyo3(get, set)]
693 pub appl_id: i16,
694 #[pyo3(get, set)]
696 pub maturity_year: u16,
697 #[pyo3(get, set)]
699 pub decay_start_date: u16,
700 #[pyo3(get, set)]
702 pub channel_id: u16,
703 #[dbn(encode_order(158))]
705 #[pyo3(get, set)]
706 pub leg_count: u16,
707 #[dbn(encode_order(159))]
709 #[pyo3(get, set)]
710 pub leg_index: u16,
711 #[dbn(fmt_method)]
713 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
714 pub currency: [c_char; 4],
715 #[dbn(fmt_method)]
717 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
718 pub settl_currency: [c_char; 4],
719 #[dbn(fmt_method)]
721 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
722 pub secsubtype: [c_char; 6],
723 #[dbn(encode_order(2), fmt_method)]
725 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
726 pub raw_symbol: [c_char; SYMBOL_CSTR_LEN_V3],
727 #[dbn(fmt_method)]
729 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
730 pub group: [c_char; 21],
731 #[dbn(fmt_method)]
733 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
734 pub exchange: [c_char; 5],
735 #[dbn(fmt_method)]
737 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
738 pub asset: [c_char; 7],
739 #[dbn(fmt_method)]
741 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
742 pub cfi: [c_char; 7],
743 #[dbn(fmt_method)]
745 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
746 pub security_type: [c_char; 7],
747 #[dbn(fmt_method)]
749 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
750 pub unit_of_measure: [c_char; 31],
751 #[dbn(fmt_method)]
753 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
754 pub underlying: [c_char; 21],
755 #[dbn(fmt_method)]
757 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
758 pub strike_price_currency: [c_char; 4],
759 #[dbn(encode_order(161), fmt_method)]
761 #[cfg_attr(feature = "serde", serde(with = "crate::record::cstr_serde"))]
762 #[pyo3(get)]
763 pub leg_raw_symbol: [c_char; SYMBOL_CSTR_LEN_V3],
764 #[dbn(c_char, encode_order(4))]
766 #[pyo3(set)]
767 pub instrument_class: c_char,
768 #[dbn(c_char)]
770 #[pyo3(set)]
771 pub match_algorithm: c_char,
772 #[pyo3(get, set)]
774 pub main_fraction: u8,
775 #[pyo3(get, set)]
777 pub price_display_format: u8,
778 #[pyo3(get, set)]
780 pub sub_fraction: u8,
781 #[pyo3(get, set)]
783 pub underlying_product: u8,
784 #[dbn(c_char, encode_order(3))]
786 pub security_update_action: c_char,
787 #[pyo3(get, set)]
789 pub maturity_month: u8,
790 #[pyo3(get, set)]
792 pub maturity_day: u8,
793 #[pyo3(get, set)]
795 pub maturity_week: u8,
796 #[dbn(c_char)]
798 pub user_defined_instrument: c_char,
799 #[pyo3(get, set)]
801 pub contract_multiplier_unit: i8,
802 #[pyo3(get, set)]
804 pub flow_schedule_type: i8,
805 #[pyo3(get, set)]
807 pub tick_rule: u8,
808 #[dbn(c_char, encode_order(163))]
810 pub leg_instrument_class: c_char,
811 #[dbn(c_char, encode_order(164))]
813 pub leg_side: c_char,
814 #[doc(hidden)]
816 #[cfg_attr(feature = "serde", serde(skip))]
817 pub _reserved: [u8; 21],
818}
819
820#[cfg(test)]
821mod tests {
822 use std::ffi::c_char;
823
824 use time::OffsetDateTime;
825
826 use crate::{Mbp1Msg, Record, MAX_RECORD_LEN};
827
828 use super::*;
829
830 #[cfg(feature = "python")]
831 #[test]
832 fn test_strike_price_order_didnt_change() {
833 use crate::python::PyFieldDesc;
834
835 assert_eq!(
836 InstrumentDefMsgV1::ordered_fields(""),
837 InstrumentDefMsgV2::ordered_fields("")
838 );
839 }
840
841 #[test]
842 fn upgrade_symbol_mapping_ts_out() -> crate::Result<()> {
843 let orig = WithTsOut::new(
844 SymbolMappingMsgV1::new(1, 2, "ES.c.0", "ESH4", 0, 0)?,
845 OffsetDateTime::now_utc().unix_timestamp_nanos() as u64,
846 );
847 let mut compat_buffer = [0; MAX_RECORD_LEN];
848 let res = unsafe {
849 decode_record_ref(
850 1,
851 VersionUpgradePolicy::UpgradeToV2,
852 true,
853 &mut compat_buffer,
854 orig.as_ref(),
855 )
856 };
857 let upgraded = res.get::<WithTsOut<SymbolMappingMsgV2>>().unwrap();
858 assert_eq!(orig.ts_out, upgraded.ts_out);
859 assert_eq!(orig.rec.stype_in_symbol()?, upgraded.rec.stype_in_symbol()?);
860 assert_eq!(
861 orig.rec.stype_out_symbol()?,
862 upgraded.rec.stype_out_symbol()?
863 );
864 assert_eq!(upgraded.record_size(), std::mem::size_of_val(upgraded));
865 assert!(std::ptr::addr_eq(upgraded.header(), compat_buffer.as_ptr()));
867 Ok(())
868 }
869
870 #[test]
871 fn upgrade_mbp1_ts_out() -> crate::Result<()> {
872 let rec = Mbp1Msg {
873 price: 1_250_000_000,
874 side: b'A' as c_char,
875 ..Mbp1Msg::default()
876 };
877 let orig = WithTsOut::new(rec, OffsetDateTime::now_utc().unix_timestamp_nanos() as u64);
878 let mut compat_buffer = [0; MAX_RECORD_LEN];
879 let res = unsafe {
880 decode_record_ref(
881 1,
882 VersionUpgradePolicy::UpgradeToV2,
883 true,
884 &mut compat_buffer,
885 orig.as_ref(),
886 )
887 };
888 let upgraded = res.get::<WithTsOut<Mbp1Msg>>().unwrap();
889 assert!(std::ptr::eq(orig.header(), upgraded.header()));
891 Ok(())
892 }
893}