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