1use std::sync::atomic::{AtomicU32, Ordering};
42
43use dashmap::{DashMap, DashSet, mapref::entry::Entry};
44use nautilus_model::identifiers::ClientOrderId;
45use thiserror::Error;
46
47pub const DYDX_BASE_EPOCH: i64 = 1577836800;
50
51pub const DEFAULT_RUST_CLIENT_METADATA: u32 = 4;
54
55pub const MAX_SAFE_CLIENT_ID: u32 = u32::MAX - 1000;
58
59const TRADER_SHIFT: u32 = 22; const STRATEGY_SHIFT: u32 = 12; const COUNT_MASK: u32 = 0xFFF; const TRADER_MASK: u32 = 0x3FF; const STRATEGY_MASK: u32 = 0x3FF; const SEQUENTIAL_METADATA_MARKER: u32 = u32::MAX;
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct EncodedClientOrderId {
78 pub client_id: u32,
80 pub client_metadata: u32,
82}
83
84#[derive(Debug, Clone, Error)]
86pub enum EncoderError {
87 #[error(
89 "Client order ID counter overflow: current value {0} exceeds safe limit {MAX_SAFE_CLIENT_ID}"
90 )]
91 CounterOverflow(u32),
92
93 #[error("Failed to parse O-format ClientOrderId: {0}")]
95 ParseError(String),
96
97 #[error("Value overflow in encoding: {0}")]
99 ValueOverflow(String),
100}
101
102#[derive(Debug)]
114pub struct ClientOrderIdEncoder {
115 forward: DashMap<ClientOrderId, EncodedClientOrderId>,
117 reverse: DashMap<(u32, u32), ClientOrderId>,
119 next_id: AtomicU32,
121
122 known_client_ids: DashSet<u32>,
131}
132
133impl Default for ClientOrderIdEncoder {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139impl ClientOrderIdEncoder {
140 #[must_use]
142 pub fn new() -> Self {
143 Self {
144 forward: DashMap::new(),
145 reverse: DashMap::new(),
146 next_id: AtomicU32::new(1),
147 known_client_ids: DashSet::new(),
148 }
149 }
150
151 pub fn register_known_client_id(&self, client_id: u32) {
157 self.known_client_ids.insert(client_id);
158 }
159
160 pub fn encode(&self, id: ClientOrderId) -> Result<EncodedClientOrderId, EncoderError> {
174 if let Some(existing) = self.forward.get(&id) {
176 let encoded = *existing.value();
177 return Ok(encoded);
178 }
179
180 let id_str = id.as_str();
181
182 if let Ok(numeric_id) = id_str.parse::<u32>() {
184 let encoded = EncodedClientOrderId {
185 client_id: numeric_id,
186 client_metadata: DEFAULT_RUST_CLIENT_METADATA,
187 };
188 self.forward.insert(id, encoded);
190 self.reverse
191 .insert((encoded.client_id, encoded.client_metadata), id);
192 return Ok(encoded);
193 }
194
195 if id_str.starts_with("O-") {
197 match self.encode_o_format(id_str) {
198 Ok(encoded) => {
199 if self.known_client_ids.contains(&encoded.client_id) {
203 log::warn!(
204 "[ENCODER] client_id {} for '{id}' collides with \
205 reconciled order, falling back to sequential",
206 encoded.client_id,
207 );
208 } else {
209 self.reverse
211 .insert((encoded.client_id, encoded.client_metadata), id);
212 return Ok(encoded);
213 }
214 }
215 Err(e) => {
216 log::warn!(
217 "[ENCODER] O-format parse failed for '{id}': {e}, falling back to sequential",
218 );
219 }
221 }
222 }
223
224 self.allocate_sequential(id)
226 }
227
228 fn encode_o_format(&self, id_str: &str) -> Result<EncodedClientOrderId, EncoderError> {
229 let parts: Vec<&str> = id_str.split('-').collect();
231 if parts.len() != 6 || parts[0] != "O" {
232 return Err(EncoderError::ParseError(format!(
233 "Expected O-YYYYMMDD-HHMMSS-TTT-SSS-CCC, received: {id_str}",
234 )));
235 }
236
237 let date_str = parts[1]; let time_str = parts[2]; let trader_str = parts[3]; let strategy_str = parts[4]; let count_str = parts[5]; if date_str.len() != 8 || time_str.len() != 6 {
245 return Err(EncoderError::ParseError(format!(
246 "Invalid date/time format in: {id_str}"
247 )));
248 }
249
250 let year: i32 = date_str[0..4]
252 .parse()
253 .map_err(|_| EncoderError::ParseError(format!("Invalid year in: {id_str}")))?;
254 let month: u32 = date_str[4..6]
255 .parse()
256 .map_err(|_| EncoderError::ParseError(format!("Invalid month in: {id_str}")))?;
257 let day: u32 = date_str[6..8]
258 .parse()
259 .map_err(|_| EncoderError::ParseError(format!("Invalid day in: {id_str}")))?;
260 let hour: u32 = time_str[0..2]
261 .parse()
262 .map_err(|_| EncoderError::ParseError(format!("Invalid hour in: {id_str}")))?;
263 let minute: u32 = time_str[2..4]
264 .parse()
265 .map_err(|_| EncoderError::ParseError(format!("Invalid minute in: {id_str}")))?;
266 let second: u32 = time_str[4..6]
267 .parse()
268 .map_err(|_| EncoderError::ParseError(format!("Invalid second in: {id_str}")))?;
269
270 let trader: u32 = trader_str
272 .parse()
273 .map_err(|_| EncoderError::ParseError(format!("Invalid trader in: {id_str}")))?;
274 let strategy: u32 = strategy_str
275 .parse()
276 .map_err(|_| EncoderError::ParseError(format!("Invalid strategy in: {id_str}")))?;
277 let count: u32 = count_str
278 .parse()
279 .map_err(|_| EncoderError::ParseError(format!("Invalid count in: {id_str}")))?;
280
281 if trader > TRADER_MASK {
283 return Err(EncoderError::ValueOverflow(format!(
284 "Trader tag {trader} exceeds max {TRADER_MASK}"
285 )));
286 }
287
288 if strategy > STRATEGY_MASK {
289 return Err(EncoderError::ValueOverflow(format!(
290 "Strategy tag {strategy} exceeds max {STRATEGY_MASK}"
291 )));
292 }
293
294 if count > COUNT_MASK {
295 return Err(EncoderError::ValueOverflow(format!(
296 "Count {count} exceeds max {COUNT_MASK}"
297 )));
298 }
299
300 let dt = chrono::NaiveDate::from_ymd_opt(year, month, day)
302 .and_then(|d| d.and_hms_opt(hour, minute, second))
303 .ok_or_else(|| EncoderError::ParseError(format!("Invalid datetime in: {id_str}")))?;
304
305 let timestamp = dt.and_utc().timestamp();
306
307 let seconds_since_epoch = timestamp - DYDX_BASE_EPOCH;
309 if seconds_since_epoch < 0 {
310 return Err(EncoderError::ValueOverflow(format!(
311 "Timestamp {timestamp} is before base epoch {DYDX_BASE_EPOCH}"
312 )));
313 }
314
315 let client_id =
322 (trader << TRADER_SHIFT) | (strategy << STRATEGY_SHIFT) | (count & COUNT_MASK);
323 let client_metadata = seconds_since_epoch as u32;
324
325 Ok(EncodedClientOrderId {
326 client_id,
327 client_metadata,
328 })
329 }
330
331 fn allocate_sequential(&self, id: ClientOrderId) -> Result<EncodedClientOrderId, EncoderError> {
332 let current = self.next_id.load(Ordering::Relaxed);
334 if current >= MAX_SAFE_CLIENT_ID {
335 log::error!(
336 "[ENCODER] allocate_sequential() OVERFLOW: counter {current} >= MAX_SAFE {MAX_SAFE_CLIENT_ID}"
337 );
338 return Err(EncoderError::CounterOverflow(current));
339 }
340
341 match self.forward.entry(id) {
343 Entry::Occupied(entry) => {
344 let encoded = *entry.get();
345 Ok(encoded)
346 }
347 Entry::Vacant(vacant) => {
348 let mut counter = self.next_id.fetch_add(1, Ordering::Relaxed);
351 while self.known_client_ids.contains(&counter) {
352 counter = self.next_id.fetch_add(1, Ordering::Relaxed);
353 }
354
355 if counter >= MAX_SAFE_CLIENT_ID {
356 return Err(EncoderError::CounterOverflow(counter));
357 }
358
359 let encoded = EncodedClientOrderId {
362 client_id: counter,
363 client_metadata: SEQUENTIAL_METADATA_MARKER,
364 };
365 vacant.insert(encoded);
366 self.reverse
367 .insert((encoded.client_id, encoded.client_metadata), id);
368 Ok(encoded)
369 }
370 }
371 }
372
373 #[must_use]
383 pub fn decode(&self, client_id: u32, client_metadata: u32) -> Option<ClientOrderId> {
384 if client_metadata == DEFAULT_RUST_CLIENT_METADATA {
386 let id = ClientOrderId::from(client_id.to_string().as_str());
387 return Some(id);
388 }
389
390 if client_metadata == SEQUENTIAL_METADATA_MARKER {
392 let result = self
393 .reverse
394 .get(&(client_id, client_metadata))
395 .map(|r| *r.value());
396 return result;
397 }
398
399 self.decode_o_format(client_id, client_metadata)
401 }
402
403 #[must_use]
408 pub fn decode_if_known(&self, client_id: u32, client_metadata: u32) -> Option<ClientOrderId> {
409 if let Some(entry) = self.reverse.get(&(client_id, client_metadata)) {
411 return Some(*entry.value());
412 }
413
414 if client_metadata == SEQUENTIAL_METADATA_MARKER {
416 return None;
417 }
418
419 if client_metadata == DEFAULT_RUST_CLIENT_METADATA {
421 return Some(ClientOrderId::from(client_id.to_string().as_str()));
422 }
423
424 self.decode_o_format(client_id, client_metadata)
426 }
427
428 fn decode_o_format(&self, client_id: u32, client_metadata: u32) -> Option<ClientOrderId> {
429 let trader = (client_id >> TRADER_SHIFT) & TRADER_MASK;
431 let strategy = (client_id >> STRATEGY_SHIFT) & STRATEGY_MASK;
432 let count = client_id & COUNT_MASK;
433
434 let timestamp = (client_metadata as i64) + DYDX_BASE_EPOCH;
436
437 let dt = chrono::DateTime::from_timestamp(timestamp, 0)?;
439
440 let id_str = format!(
442 "O-{:04}{:02}{:02}-{:02}{:02}{:02}-{:03}-{:03}-{}",
443 dt.year(),
444 dt.month(),
445 dt.day(),
446 dt.hour(),
447 dt.minute(),
448 dt.second(),
449 trader,
450 strategy,
451 count
452 );
453
454 let id = ClientOrderId::from(id_str.as_str());
455 Some(id)
456 }
457
458 #[must_use]
463 pub fn get(&self, id: &ClientOrderId) -> Option<EncodedClientOrderId> {
464 if let Some(entry) = self.forward.get(id) {
466 return Some(*entry.value());
467 }
468
469 let id_str = id.as_str();
470
471 if let Ok(numeric_id) = id_str.parse::<u32>() {
473 return Some(EncodedClientOrderId {
474 client_id: numeric_id,
475 client_metadata: DEFAULT_RUST_CLIENT_METADATA,
476 });
477 }
478
479 if id_str.starts_with("O-")
481 && let Ok(encoded) = self.encode_o_format(id_str)
482 {
483 return Some(encoded);
484 }
485
486 None
487 }
488
489 pub fn remove(&self, client_id: u32, client_metadata: u32) -> Option<ClientOrderId> {
493 if let Some((_, client_order_id)) = self.reverse.remove(&(client_id, client_metadata)) {
494 self.forward.remove(&client_order_id);
495 return Some(client_order_id);
496 }
497 None
498 }
499
500 pub fn remove_by_client_id(&self, client_id: u32) -> Option<ClientOrderId> {
503 if let result @ Some(_) = self.remove(client_id, DEFAULT_RUST_CLIENT_METADATA) {
505 return result;
506 }
507
508 let key_to_remove = self
510 .reverse
511 .iter()
512 .find(|r| r.key().0 == client_id)
513 .map(|r| *r.key());
514
515 if let Some((cid, meta)) = key_to_remove {
516 return self.remove(cid, meta);
517 }
518
519 None
520 }
521
522 #[must_use]
524 pub fn current_counter(&self) -> u32 {
525 self.next_id.load(Ordering::Relaxed)
526 }
527
528 #[must_use]
530 pub fn len(&self) -> usize {
531 self.forward.len()
532 }
533
534 #[must_use]
536 pub fn is_empty(&self) -> bool {
537 self.forward.is_empty()
538 }
539}
540
541use chrono::{Datelike, Timelike};
543
544#[cfg(test)]
545mod tests {
546 use rstest::rstest;
547
548 use super::*;
549
550 #[rstest]
551 fn test_encode_numeric_id() {
552 let encoder = ClientOrderIdEncoder::new();
553 let id = ClientOrderId::from("12345");
554
555 let result = encoder.encode(id);
556 assert!(result.is_ok());
557 let encoded = result.unwrap();
558 assert_eq!(encoded.client_id, 12345);
559 assert_eq!(encoded.client_metadata, DEFAULT_RUST_CLIENT_METADATA);
560 }
561
562 #[rstest]
563 fn test_encode_o_format() {
564 let encoder = ClientOrderIdEncoder::new();
565 let id = ClientOrderId::from("O-20260131-174827-001-001-1");
566
567 let result = encoder.encode(id);
568 assert!(result.is_ok());
569 let encoded = result.unwrap();
570
571 let expected_client_id = (1 << TRADER_SHIFT) | (1 << STRATEGY_SHIFT) | 1;
577 assert_eq!(encoded.client_id, expected_client_id);
578
579 let expected_timestamp = chrono::NaiveDate::from_ymd_opt(2026, 1, 31)
582 .unwrap()
583 .and_hms_opt(17, 48, 27)
584 .unwrap()
585 .and_utc()
586 .timestamp();
587 let expected_metadata = (expected_timestamp - DYDX_BASE_EPOCH) as u32;
588 assert_eq!(encoded.client_metadata, expected_metadata);
589 }
590
591 #[rstest]
592 fn test_roundtrip_o_format() {
593 let encoder = ClientOrderIdEncoder::new();
594 let id = ClientOrderId::from("O-20260131-174827-001-001-1");
595
596 let encoded = encoder.encode(id).unwrap();
597 let decoded = encoder.decode(encoded.client_id, encoded.client_metadata);
598
599 assert_eq!(decoded, Some(id));
600 }
601
602 #[rstest]
603 fn test_roundtrip_o_format_various() {
604 let encoder = ClientOrderIdEncoder::new();
605 let test_cases = vec![
606 "O-20260131-000000-001-001-1",
607 "O-20260131-235959-999-999-4095",
608 "O-20200101-000000-000-000-0",
609 "O-20251215-123456-123-456-789",
610 ];
611
612 for id_str in test_cases {
613 let id = ClientOrderId::from(id_str);
614 let encoded = encoder.encode(id).unwrap();
615 let decoded = encoder.decode(encoded.client_id, encoded.client_metadata);
616 assert_eq!(decoded, Some(id), "Roundtrip failed for {id_str}");
617 }
618 }
619
620 #[rstest]
621 fn test_roundtrip_numeric() {
622 let encoder = ClientOrderIdEncoder::new();
623 let id = ClientOrderId::from("12345");
624
625 let encoded = encoder.encode(id).unwrap();
626 let decoded = encoder.decode(encoded.client_id, encoded.client_metadata);
627
628 assert_eq!(decoded, Some(id));
629 }
630
631 #[rstest]
632 fn test_encode_non_standard_uses_sequential() {
633 let encoder = ClientOrderIdEncoder::new();
634 let id = ClientOrderId::from("custom-order-id");
635
636 let result = encoder.encode(id);
637 assert!(result.is_ok());
638 let encoded = result.unwrap();
639
640 assert_eq!(
642 encoded.client_metadata, SEQUENTIAL_METADATA_MARKER,
643 "Expected client_metadata == SEQUENTIAL_METADATA_MARKER"
644 );
645 }
646
647 #[rstest]
648 fn test_roundtrip_sequential() {
649 let encoder = ClientOrderIdEncoder::new();
650 let id = ClientOrderId::from("custom-order-id");
651
652 let encoded = encoder.encode(id).unwrap();
653 let decoded = encoder.decode(encoded.client_id, encoded.client_metadata);
654
655 assert_eq!(decoded, Some(id));
656 }
657
658 #[rstest]
659 fn test_sequential_lost_after_restart() {
660 let encoder1 = ClientOrderIdEncoder::new();
662 let id = ClientOrderId::from("custom-order-id");
663
664 let encoded = encoder1.encode(id).unwrap();
665
666 let encoder2 = ClientOrderIdEncoder::new();
668 let decoded = encoder2.decode(encoded.client_id, encoded.client_metadata);
669
670 assert!(decoded.is_none());
672 }
673
674 #[rstest]
675 fn test_o_format_survives_restart() {
676 let encoder1 = ClientOrderIdEncoder::new();
677 let id = ClientOrderId::from("O-20260131-174827-001-001-1");
678
679 let encoded = encoder1.encode(id).unwrap();
680
681 let encoder2 = ClientOrderIdEncoder::new();
683 let decoded = encoder2.decode(encoded.client_id, encoded.client_metadata);
684
685 assert_eq!(decoded, Some(id));
687 }
688
689 #[rstest]
690 fn test_get_without_encode() {
691 let encoder = ClientOrderIdEncoder::new();
692
693 let numeric_id = ClientOrderId::from("12345");
695 let actual = encoder.get(&numeric_id);
696 assert_eq!(
697 actual,
698 Some(EncodedClientOrderId {
699 client_id: 12345,
700 client_metadata: DEFAULT_RUST_CLIENT_METADATA
701 })
702 );
703
704 let o_id = ClientOrderId::from("O-20260131-174827-001-001-1");
706 let actual = encoder.get(&o_id);
707 assert!(actual.is_some());
708
709 let custom_id = ClientOrderId::from("custom");
711 let actual = encoder.get(&custom_id);
712 assert!(actual.is_none());
713 }
714
715 #[rstest]
716 fn test_remove_sequential() {
717 let encoder = ClientOrderIdEncoder::new();
718 let id = ClientOrderId::from("custom-order-id");
719
720 let encoded = encoder.encode(id).unwrap();
721 assert_eq!(encoder.len(), 1);
722
723 let removed = encoder.remove(encoded.client_id, encoded.client_metadata);
724 assert_eq!(removed, Some(id));
725 assert_eq!(encoder.len(), 0);
726 }
727
728 #[rstest]
729 fn test_max_values_o_format() {
730 let encoder = ClientOrderIdEncoder::new();
731 let id = ClientOrderId::from("O-20260131-235959-999-999-4095");
733
734 let result = encoder.encode(id);
735 assert!(result.is_ok());
736
737 let encoded = result.unwrap();
738 let decoded = encoder.decode(encoded.client_id, encoded.client_metadata);
739 assert_eq!(decoded, Some(id));
740 }
741
742 #[rstest]
743 fn test_overflow_trader_tag() {
744 let encoder = ClientOrderIdEncoder::new();
745 let id = ClientOrderId::from("O-20260131-174827-1024-001-1");
747
748 let result = encoder.encode(id);
749 assert!(result.is_ok());
751 assert_eq!(
752 result.unwrap().client_metadata,
753 SEQUENTIAL_METADATA_MARKER,
754 "Overflow should fall back to sequential allocation"
755 );
756 }
757
758 #[rstest]
759 fn test_date_before_base_epoch_falls_back_to_sequential() {
760 let encoder = ClientOrderIdEncoder::new();
761 let id = ClientOrderId::from("O-20191231-235959-001-001-1");
763
764 let result = encoder.encode(id);
765 assert!(result.is_ok());
767 let encoded = result.unwrap();
768 assert_eq!(
769 encoded.client_metadata, SEQUENTIAL_METADATA_MARKER,
770 "Pre-2020 dates should fall back to sequential allocation"
771 );
772
773 let decoded = encoder.decode(encoded.client_id, encoded.client_metadata);
775 assert_eq!(decoded, Some(id));
776 }
777
778 #[rstest]
779 fn test_encode_same_id_returns_same_value() {
780 let encoder = ClientOrderIdEncoder::new();
781 let id = ClientOrderId::from("O-20260131-174827-001-001-1");
782
783 let first = encoder.encode(id).unwrap();
784 let second = encoder.encode(id).unwrap();
785
786 assert_eq!(first, second);
787 }
788
789 #[rstest]
790 fn test_same_second_different_count_has_unique_client_ids() {
791 let encoder = ClientOrderIdEncoder::new();
794
795 let id1 = ClientOrderId::from("O-20260201-084653-001-001-1");
797 let id2 = ClientOrderId::from("O-20260201-084653-001-001-2");
798
799 let encoded1 = encoder.encode(id1).unwrap();
800 let encoded2 = encoder.encode(id2).unwrap();
801
802 assert_ne!(
804 encoded1.client_id, encoded2.client_id,
805 "Orders in the same second must have different client_ids for dYdX"
806 );
807
808 assert_eq!(encoded1.client_metadata, encoded2.client_metadata);
810
811 assert_eq!(
813 encoder.decode(encoded1.client_id, encoded1.client_metadata),
814 Some(id1)
815 );
816 assert_eq!(
817 encoder.decode(encoded2.client_id, encoded2.client_metadata),
818 Some(id2)
819 );
820 }
821
822 #[rstest]
823 fn test_encode_different_ids_returns_different_values() {
824 let encoder = ClientOrderIdEncoder::new();
825 let id1 = ClientOrderId::from("O-20260131-174827-001-001-1");
826 let id2 = ClientOrderId::from("O-20260131-174828-001-001-2");
827
828 let result1 = encoder.encode(id1).unwrap();
829 let result2 = encoder.encode(id2).unwrap();
830
831 assert_ne!(result1, result2);
832 }
833
834 #[rstest]
835 fn test_current_counter() {
836 let encoder = ClientOrderIdEncoder::new();
837 assert_eq!(encoder.current_counter(), 1);
838
839 encoder.encode(ClientOrderId::from("custom-1")).unwrap();
840 assert_eq!(encoder.current_counter(), 2);
841
842 encoder.encode(ClientOrderId::from("custom-2")).unwrap();
843 assert_eq!(encoder.current_counter(), 3);
844
845 encoder
847 .encode(ClientOrderId::from("O-20260131-174827-001-001-1"))
848 .unwrap();
849 assert_eq!(encoder.current_counter(), 3);
850 }
851
852 #[rstest]
853 fn test_is_empty() {
854 let encoder = ClientOrderIdEncoder::new();
855 assert!(encoder.is_empty());
856
857 encoder.encode(ClientOrderId::from("custom")).unwrap();
858 assert!(!encoder.is_empty());
859 }
860
861 #[rstest]
862 fn test_o_format_collision_falls_back_to_sequential() {
863 let encoder = ClientOrderIdEncoder::new();
864 let id = ClientOrderId::from("O-20260220-031943-001-000-51");
865
866 let colliding_client_id = (1 << TRADER_SHIFT) | (0 << STRATEGY_SHIFT) | 51;
868
869 encoder.register_known_client_id(colliding_client_id);
870 let encoded = encoder.encode(id).unwrap();
871 assert_eq!(
872 encoded.client_metadata, SEQUENTIAL_METADATA_MARKER,
873 "Collision should fall back to sequential allocation"
874 );
875 assert_ne!(encoded.client_id, colliding_client_id);
876
877 let decoded = encoder.decode_o_format(colliding_client_id, {
879 let dt = chrono::NaiveDate::from_ymd_opt(2026, 2, 20)
880 .unwrap()
881 .and_hms_opt(3, 19, 43)
882 .unwrap()
883 .and_utc()
884 .timestamp();
885 (dt - DYDX_BASE_EPOCH) as u32
886 });
887 assert_eq!(decoded, Some(id));
888 }
889
890 #[rstest]
891 fn test_sequential_skips_known_client_ids() {
892 let encoder = ClientOrderIdEncoder::new();
893
894 encoder.register_known_client_id(1);
895 encoder.register_known_client_id(2);
896
897 let encoded = encoder.encode(ClientOrderId::from("custom-order")).unwrap();
898 assert_eq!(encoded.client_id, 3);
899 assert_eq!(encoded.client_metadata, SEQUENTIAL_METADATA_MARKER);
900 }
901
902 #[rstest]
903 fn test_sequential_overflow_after_skipping_known_ids() {
904 let encoder = ClientOrderIdEncoder::new();
905
906 let near_limit = MAX_SAFE_CLIENT_ID - 1;
907 encoder.next_id.store(near_limit, Ordering::Relaxed);
908
909 encoder.register_known_client_id(near_limit);
911
912 let result = encoder.encode(ClientOrderId::from("overflow-order"));
913 assert!(
914 matches!(result, Err(EncoderError::CounterOverflow(_))),
915 "Expected CounterOverflow after skipping past MAX_SAFE_CLIENT_ID"
916 );
917 }
918}