1use nautilus_model::identifiers::ClientOrderId;
108
109const BASE62_CHARS: &[u8; 62] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
111
112const BASE62_DECODE: [u8; 128] = {
115 let mut table = [0xFFu8; 128];
116 let mut i = 0u8;
117 while i < 62 {
118 table[BASE62_CHARS[i as usize] as usize] = i;
119 i += 1;
120 }
121 table
122};
123
124const O_FORMAT_EPOCH: i64 = 1_577_836_800;
126
127const O_FORMAT_B62_LEN: usize = 13;
129
130const UUID_B62_LEN: usize = 22;
132
133const MAX_CLIENT_ORDER_ID_LEN: usize = 36;
135
136const SIGNAL_O_HYPHENS: u8 = b'T';
137const SIGNAL_O_NO_HYPHENS: u8 = b't';
138const SIGNAL_UUID_HYPHENS: u8 = b'U';
139const SIGNAL_UUID_NO_HYPHENS: u8 = b'u';
140const SIGNAL_RAW: u8 = b'R';
141
142#[must_use]
144fn broker_prefix(broker_id: &str) -> String {
145 format!("x-{broker_id}-")
146}
147
148#[must_use]
153pub fn encode_broker_id(client_order_id: &ClientOrderId, broker_id: &str) -> String {
154 let id_str = client_order_id.as_str();
155 let prefix = broker_prefix(broker_id);
156 let budget = MAX_CLIENT_ORDER_ID_LEN - prefix.len();
157
158 if let Some((packed, has_hyphens)) = pack_o_format(id_str) {
159 let signal = if has_hyphens {
160 SIGNAL_O_HYPHENS
161 } else {
162 SIGNAL_O_NO_HYPHENS
163 };
164 let b62 = encode_base62::<O_FORMAT_B62_LEN>(packed);
165 return build_encoded(&prefix, signal, &b62);
166 }
167
168 if let Some((value, has_hyphens)) = parse_uuid_hex(id_str) {
169 let signal = if has_hyphens {
170 SIGNAL_UUID_HYPHENS
171 } else {
172 SIGNAL_UUID_NO_HYPHENS
173 };
174 let b62 = encode_base62::<UUID_B62_LEN>(value);
175 return build_encoded(&prefix, signal, &b62);
176 }
177
178 if id_str.len() < budget {
179 let mut result = String::with_capacity(prefix.len() + 1 + id_str.len());
180 result.push_str(&prefix);
181 result.push(SIGNAL_RAW as char);
182 result.push_str(id_str);
183 return result;
184 }
185
186 log::warn!(
187 "ClientOrderId '{id_str}' ({} chars) exceeds broker ID encoding budget ({budget} chars), sending without prefix",
188 id_str.len(),
189 );
190 id_str.to_string()
191}
192
193#[must_use]
199pub fn decode_broker_id(encoded: &str, broker_id: &str) -> String {
200 let prefix = broker_prefix(broker_id);
201 let Some(payload) = encoded.strip_prefix(&prefix) else {
202 return encoded.to_string();
203 };
204
205 if payload.is_empty() {
206 return encoded.to_string();
207 }
208
209 let signal = payload.as_bytes()[0];
210 let data = &payload[1..];
211
212 match signal {
213 SIGNAL_O_HYPHENS => unpack_o_format(data, true),
214 SIGNAL_O_NO_HYPHENS => unpack_o_format(data, false),
215 SIGNAL_UUID_HYPHENS => format_uuid(data, true),
216 SIGNAL_UUID_NO_HYPHENS => format_uuid(data, false),
217 SIGNAL_RAW => data.to_string(),
218 _ => {
219 log::warn!("Unknown broker ID signal byte '{signal}', returning raw");
220 encoded.to_string()
221 }
222 }
223}
224
225fn build_encoded(prefix: &str, signal: u8, b62: &[u8]) -> String {
226 let mut result = String::with_capacity(prefix.len() + 1 + b62.len());
227 result.push_str(prefix);
228 result.push(signal as char);
229 result.push_str(std::str::from_utf8(b62).expect("base62 is valid UTF-8"));
231 result
232}
233
234fn encode_base62<const N: usize>(mut value: u128) -> [u8; N] {
235 let mut buf = [b'0'; N];
236 for i in (0..N).rev() {
237 buf[i] = BASE62_CHARS[(value % 62) as usize];
238 value /= 62;
239 }
240 buf
241}
242
243fn decode_base62(encoded: &[u8]) -> u128 {
244 let mut value: u128 = 0;
245
246 for &byte in encoded {
247 let digit = BASE62_DECODE[byte as usize & 0x7F];
248
249 if digit == 0xFF {
250 log::warn!("Invalid base62 character: {byte}");
251 return 0;
252 }
253 value = value * 62 + digit as u128;
254 }
255 value
256}
257
258fn parse_digits(bytes: &[u8]) -> Option<u32> {
259 let mut n: u32 = 0;
260
261 for &b in bytes {
262 if !b.is_ascii_digit() {
263 return None;
264 }
265 n = n * 10 + (b - b'0') as u32;
266 }
267 Some(n)
268}
269
270fn pack_o_format(id_str: &str) -> Option<(u128, bool)> {
271 let b = id_str.as_bytes();
272
273 if b.first() != Some(&b'O') {
274 return None;
275 }
276
277 let (year, month, day, hour, minute, second, trader, strategy, count, has_hyphens) =
278 if b.get(1) == Some(&b'-') {
279 if b.len() < 23 || b[10] != b'-' || b[17] != b'-' {
282 return None;
283 }
284 let h4 = memchr_byte(b'-', &b[18..])?;
285 let trader_end = 18 + h4;
286 let h5 = memchr_byte(b'-', &b[trader_end + 1..])?;
287 let strategy_end = trader_end + 1 + h5;
288
289 (
290 parse_digits(&b[2..6])?,
291 parse_digits(&b[6..8])?,
292 parse_digits(&b[8..10])?,
293 parse_digits(&b[11..13])?,
294 parse_digits(&b[13..15])?,
295 parse_digits(&b[15..17])?,
296 parse_digits(&b[18..trader_end])?,
297 parse_digits(&b[trader_end + 1..strategy_end])?,
298 parse_digits(&b[strategy_end + 1..])?,
299 true,
300 )
301 } else {
302 if b.len() < 22 {
304 return None;
305 }
306 (
307 parse_digits(&b[1..5])?,
308 parse_digits(&b[5..7])?,
309 parse_digits(&b[7..9])?,
310 parse_digits(&b[9..11])?,
311 parse_digits(&b[11..13])?,
312 parse_digits(&b[13..15])?,
313 parse_digits(&b[15..18])?,
314 parse_digits(&b[18..21])?,
315 parse_digits(&b[21..])?,
316 false,
317 )
318 };
319
320 if trader > 1023 || strategy > 1023 || count > 0xF_FFFF {
321 return None;
322 }
323
324 let secs_since_epoch = civil_to_epoch(year, month, day, hour, minute, second)? - O_FORMAT_EPOCH;
325
326 if secs_since_epoch < 0 {
327 return None;
328 }
329
330 let packed = (secs_since_epoch as u128) << 40
331 | (trader as u128) << 30
332 | (strategy as u128) << 20
333 | (count as u128);
334
335 Some((packed, has_hyphens))
336}
337
338fn unpack_o_format(data: &str, has_hyphens: bool) -> String {
339 let packed = decode_base62(data.as_bytes());
340
341 let count = (packed & 0xF_FFFF) as u32;
342 let strategy = ((packed >> 20) & 0x3FF) as u32;
343 let trader = ((packed >> 30) & 0x3FF) as u32;
344 let secs_since_epoch = (packed >> 40) as i64;
345
346 let timestamp = secs_since_epoch + O_FORMAT_EPOCH;
347 let Some((year, month, day, hour, minute, second)) = epoch_to_civil(timestamp) else {
348 log::warn!("Failed to decode O-format timestamp: {timestamp}");
349 return format!("DECODE_ERROR_{packed}");
350 };
351
352 if has_hyphens {
353 format!(
354 "O-{year:04}{month:02}{day:02}-{hour:02}{minute:02}{second:02}-{trader:03}-{strategy:03}-{count}",
355 )
356 } else {
357 format!(
358 "O{year:04}{month:02}{day:02}{hour:02}{minute:02}{second:02}{trader:03}{strategy:03}{count}",
359 )
360 }
361}
362
363fn parse_uuid_hex(id_str: &str) -> Option<(u128, bool)> {
364 let b = id_str.as_bytes();
365
366 if b.len() == 36 && b[8] == b'-' {
367 if b[13] != b'-' || b[18] != b'-' || b[23] != b'-' {
369 return None;
370 }
371 let mut value: u128 = 0;
372
373 for &byte in b {
374 if byte == b'-' {
375 continue;
376 }
377 let nibble = hex_digit(byte)?;
378 value = (value << 4) | nibble as u128;
379 }
380 Some((value, true))
381 } else if b.len() == 32 {
382 let mut value: u128 = 0;
383
384 for &byte in b {
385 let nibble = hex_digit(byte)?;
386 value = (value << 4) | nibble as u128;
387 }
388 Some((value, false))
389 } else {
390 None
391 }
392}
393
394fn format_uuid(data: &str, has_hyphens: bool) -> String {
395 const HEX: &[u8; 16] = b"0123456789abcdef";
396 let value = decode_base62(data.as_bytes());
397 let bytes = value.to_be_bytes();
398
399 if has_hyphens {
400 let mut buf = [0u8; 36];
401 let mut pos = 0;
402
403 for (i, &b) in bytes.iter().enumerate() {
404 if i == 4 || i == 6 || i == 8 || i == 10 {
405 buf[pos] = b'-';
406 pos += 1;
407 }
408 buf[pos] = HEX[(b >> 4) as usize];
409 buf[pos + 1] = HEX[(b & 0x0F) as usize];
410 pos += 2;
411 }
412 std::str::from_utf8(&buf)
413 .expect("hex is valid UTF-8")
414 .to_string()
415 } else {
416 let mut buf = [0u8; 32];
417 for (i, &b) in bytes.iter().enumerate() {
418 buf[i * 2] = HEX[(b >> 4) as usize];
419 buf[i * 2 + 1] = HEX[(b & 0x0F) as usize];
420 }
421 std::str::from_utf8(&buf)
422 .expect("hex is valid UTF-8")
423 .to_string()
424 }
425}
426
427fn hex_digit(byte: u8) -> Option<u8> {
428 match byte {
429 b'0'..=b'9' => Some(byte - b'0'),
430 b'a'..=b'f' => Some(byte - b'a' + 10),
431 b'A'..=b'F' => Some(byte - b'A' + 10),
432 _ => None,
433 }
434}
435
436fn memchr_byte(needle: u8, haystack: &[u8]) -> Option<usize> {
437 haystack.iter().position(|&b| b == needle)
438}
439
440fn civil_to_epoch(year: u32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> Option<i64> {
442 if !(1..=12).contains(&month) || !(1..=31).contains(&day) || hour > 23 || min > 59 || sec > 59 {
443 return None;
444 }
445 let y = if month <= 2 {
447 year as i64 - 1
448 } else {
449 year as i64
450 };
451 let era = y.div_euclid(400);
452 let yoe = y.rem_euclid(400) as u64;
453 let m = if month > 2 { month - 3 } else { month + 9 } as u64;
454 let doy = (153 * m + 2) / 5 + day as u64 - 1;
455 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
456 let days = era * 146097 + doe as i64 - 719468;
457 Some(days * 86400 + hour as i64 * 3600 + min as i64 * 60 + sec as i64)
458}
459
460fn epoch_to_civil(timestamp: i64) -> Option<(u32, u32, u32, u32, u32, u32)> {
462 if timestamp < 0 {
463 return None;
464 }
465 let secs_of_day = (timestamp % 86400) as u32;
466 let days = timestamp / 86400;
467
468 let hour = secs_of_day / 3600;
469 let minute = (secs_of_day % 3600) / 60;
470 let second = secs_of_day % 60;
471
472 let z = days + 719468;
474 let era = z.div_euclid(146097);
475 let doe = z.rem_euclid(146097) as u64;
476 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
477 let y = yoe as i64 + era * 400;
478 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
479 let mp = (5 * doy + 2) / 153;
480 let day = (doy - (153 * mp + 2) / 5 + 1) as u32;
481 let month = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
482 let year = if month <= 2 { y + 1 } else { y } as u32;
483
484 Some((year, month, day, hour, minute, second))
485}
486
487#[cfg(test)]
488mod tests {
489 use std::hint::black_box;
490
491 use rstest::rstest;
492
493 use super::{super::consts::BINANCE_NAUTILUS_SPOT_BROKER_ID, *};
494
495 const TEST_BROKER_ID: &str = BINANCE_NAUTILUS_SPOT_BROKER_ID;
496
497 #[rstest]
498 fn test_base62_roundtrip_zero() {
499 let encoded = encode_base62::<13>(0);
500 let decoded = decode_base62(&encoded);
501 assert_eq!(decoded, 0);
502 }
503
504 #[rstest]
505 fn test_base62_roundtrip_max_72_bit() {
506 let value: u128 = (1u128 << 72) - 1;
507 let encoded = encode_base62::<13>(value);
508 let decoded = decode_base62(&encoded);
509 assert_eq!(decoded, value);
510 }
511
512 #[rstest]
513 fn test_base62_roundtrip_max_128_bit() {
514 let value: u128 = u128::MAX;
515 let encoded = encode_base62::<22>(value);
516 let decoded = decode_base62(&encoded);
517 assert_eq!(decoded, value);
518 }
519
520 #[rstest]
521 #[case("O-20200101-000000-000-000-0")]
522 #[case("O-20200101-000001-001-001-1")]
523 #[case("O-20260131-174827-001-001-1")]
524 #[case("O-20260131-235959-999-999-4095")]
525 #[case("O-20251215-123456-123-456-789")]
526 #[case("O-20260305-120000-001-001-100")]
527 #[case("O-20260305-120000-001-001-99999")]
528 #[case("O-20260305-120000-001-001-1048575")]
529 fn test_roundtrip_o_format_with_hyphens(#[case] id_str: &str) {
530 let coid = ClientOrderId::from(id_str);
531 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
532
533 assert!(encoded.starts_with("x-TD67BGP9-T"), "got: {encoded}");
534 assert!(encoded.len() <= 36, "len {} > 36: {encoded}", encoded.len());
535
536 let decoded = decode_broker_id(&encoded, TEST_BROKER_ID);
537 assert_eq!(decoded, id_str);
538 }
539
540 #[rstest]
541 #[case("O202001010000000000000")]
542 #[case("O202601311748270010011")]
543 #[case("O202601312359599999994095")]
544 fn test_roundtrip_o_format_without_hyphens(#[case] id_str: &str) {
545 let coid = ClientOrderId::from(id_str);
546 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
547
548 assert!(encoded.starts_with("x-TD67BGP9-t"), "got: {encoded}");
549 assert!(encoded.len() <= 36);
550
551 let decoded = decode_broker_id(&encoded, TEST_BROKER_ID);
552 assert_eq!(decoded, id_str);
553 }
554
555 #[rstest]
556 fn test_roundtrip_uuid_with_hyphens() {
557 let id_str = "550e8400-e29b-41d4-a716-446655440000";
558 let coid = ClientOrderId::from(id_str);
559 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
560
561 assert!(encoded.starts_with("x-TD67BGP9-U"), "got: {encoded}");
562 assert!(encoded.len() <= 36, "len {} > 36: {encoded}", encoded.len());
563
564 let decoded = decode_broker_id(&encoded, TEST_BROKER_ID);
565 assert_eq!(decoded, id_str);
566 }
567
568 #[rstest]
569 fn test_roundtrip_uuid_without_hyphens() {
570 let id_str = "550e8400e29b41d4a716446655440000";
571 let coid = ClientOrderId::from(id_str);
572 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
573
574 assert!(encoded.starts_with("x-TD67BGP9-u"), "got: {encoded}");
575 assert!(encoded.len() <= 36);
576
577 let decoded = decode_broker_id(&encoded, TEST_BROKER_ID);
578 assert_eq!(decoded, id_str);
579 }
580
581 #[rstest]
582 fn test_roundtrip_uuid_all_zeros() {
583 let id_str = "00000000-0000-0000-0000-000000000000";
584 let coid = ClientOrderId::from(id_str);
585
586 let decoded = decode_broker_id(&encode_broker_id(&coid, TEST_BROKER_ID), TEST_BROKER_ID);
587 assert_eq!(decoded, id_str);
588 }
589
590 #[rstest]
591 fn test_roundtrip_uuid_all_f() {
592 let id_str = "ffffffff-ffff-ffff-ffff-ffffffffffff";
593 let coid = ClientOrderId::from(id_str);
594
595 let decoded = decode_broker_id(&encode_broker_id(&coid, TEST_BROKER_ID), TEST_BROKER_ID);
596 assert_eq!(decoded, id_str);
597 }
598
599 #[rstest]
600 fn test_raw_passthrough_short_id() {
601 let id_str = "my-order-123";
602 let coid = ClientOrderId::from(id_str);
603 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
604
605 assert!(encoded.starts_with("x-TD67BGP9-R"), "got: {encoded}");
606 assert!(encoded.len() <= 36);
607
608 let decoded = decode_broker_id(&encoded, TEST_BROKER_ID);
609 assert_eq!(decoded, id_str);
610 }
611
612 #[rstest]
613 fn test_raw_passthrough_max_length() {
614 let id_str = "abcdefghijklmnopqrstuvwx"; let coid = ClientOrderId::from(id_str);
616 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
617
618 assert_eq!(encoded.len(), 36);
619 assert!(encoded.starts_with("x-TD67BGP9-R"));
620
621 let decoded = decode_broker_id(&encoded, TEST_BROKER_ID);
622 assert_eq!(decoded, id_str);
623 }
624
625 #[rstest]
626 fn test_decode_non_prefixed_returns_as_is() {
627 let raw = "O-20260131-174827-001-001-1";
628 assert_eq!(decode_broker_id(raw, TEST_BROKER_ID), raw);
629 }
630
631 #[rstest]
632 fn test_decode_different_prefix_returns_as_is() {
633 let raw = "x-OTHERBROKER-T0000000000000";
634 assert_eq!(decode_broker_id(raw, TEST_BROKER_ID), raw);
635 }
636
637 #[rstest]
638 fn test_o_format_trader_overflow_sends_without_prefix() {
639 let id_str = "O-20260131-174827-1024-001-1";
642 let coid = ClientOrderId::from(id_str);
643 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
644 assert_eq!(encoded, id_str);
645 }
646
647 #[rstest]
648 fn test_o_format_count_overflow_sends_without_prefix() {
649 let id_str = "O-20260131-174827-001-001-1048576";
652 let coid = ClientOrderId::from(id_str);
653 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
654 assert_eq!(encoded, id_str);
655 }
656
657 #[rstest]
658 fn test_too_long_id_sends_without_prefix() {
659 let id_str = "this-is-a-very-long-order-id-that-exceeds-everything";
660 let coid = ClientOrderId::from(id_str);
661 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
662
663 assert_eq!(encoded, id_str);
664 }
665
666 #[rstest]
667 fn test_o_format_always_25_chars() {
668 let test_cases = [
669 "O-20200101-000000-000-000-0",
670 "O-20260131-235959-999-999-4095",
671 "O-20260305-120000-001-001-1048575",
672 ];
673
674 for id_str in test_cases {
675 let coid = ClientOrderId::from(id_str);
676 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
677 assert_eq!(
678 encoded.len(),
679 25,
680 "got {} for {id_str}: {encoded}",
681 encoded.len()
682 );
683 }
684 }
685
686 #[rstest]
687 fn test_uuid_always_34_chars() {
688 let id_str = "550e8400-e29b-41d4-a716-446655440000";
689 let coid = ClientOrderId::from(id_str);
690 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
691 assert_eq!(encoded.len(), 34, "got {}", encoded.len());
692 }
693
694 #[rstest]
695 fn test_broker_prefix_format() {
696 let prefix = broker_prefix(TEST_BROKER_ID);
697 assert_eq!(prefix, "x-TD67BGP9-");
698 }
699
700 #[rstest]
701 fn test_encoded_chars_are_binance_valid() {
702 let valid = |c: char| {
703 c.is_ascii_alphanumeric() || c == '.' || c == ':' || c == '/' || c == '_' || c == '-'
704 };
705
706 let ids = [
707 "O-20260131-174827-001-001-1",
708 "550e8400-e29b-41d4-a716-446655440000",
709 "short-id",
710 ];
711
712 for id_str in ids {
713 let coid = ClientOrderId::from(id_str);
714 let encoded = encode_broker_id(&coid, TEST_BROKER_ID);
715 assert!(
716 encoded.chars().all(valid),
717 "'{encoded}' contains invalid Binance characters"
718 );
719 }
720 }
721
722 #[rstest]
723 fn test_civil_time_roundtrip() {
724 let epoch = civil_to_epoch(2020, 1, 1, 0, 0, 0).unwrap();
725 assert_eq!(epoch, O_FORMAT_EPOCH);
726 let (y, m, d, h, mi, s) = epoch_to_civil(epoch).unwrap();
727 assert_eq!((y, m, d, h, mi, s), (2020, 1, 1, 0, 0, 0));
728 }
729
730 #[rstest]
731 #[case("O-20260305-120000-001-001-1")]
732 #[case("O-20260131-174827-001-001-1")]
733 #[case("O-20260305-120000-001-001-1048575")]
734 #[case("550e8400-e29b-41d4-a716-446655440000")]
735 #[case("550e8400e29b41d4a716446655440000")]
736 #[case("my-order-42")]
737 #[case("short")]
738 fn test_end_to_end_submit_and_receive(#[case] original_id: &str) {
739 let broker_id = TEST_BROKER_ID;
740 let client_order_id = ClientOrderId::from(original_id);
741
742 let encoded = encode_broker_id(&client_order_id, broker_id);
744 assert!(encoded.len() <= 36, "encoded len {} > 36", encoded.len());
745
746 let decoded = decode_broker_id(&encoded, broker_id);
748
749 assert_eq!(decoded, original_id);
751 assert_eq!(ClientOrderId::new(decoded), client_order_id);
752 }
753
754 #[rstest]
755 fn bench_encode_decode_timing() {
756 let o_coid = ClientOrderId::from("O-20260305-120000-001-001-100");
757 let uuid_coid = ClientOrderId::from("550e8400-e29b-41d4-a716-446655440000");
758 let raw_coid = ClientOrderId::from("my-order-123");
759
760 let iterations = 100_000;
761
762 let start = std::time::Instant::now();
763
764 for _ in 0..iterations {
765 black_box(encode_broker_id(black_box(&o_coid), TEST_BROKER_ID));
766 }
767 let encode_o = start.elapsed();
768
769 let o_encoded = encode_broker_id(&o_coid, TEST_BROKER_ID);
770 let start = std::time::Instant::now();
771
772 for _ in 0..iterations {
773 black_box(decode_broker_id(black_box(&o_encoded), TEST_BROKER_ID));
774 }
775 let decode_o = start.elapsed();
776
777 let start = std::time::Instant::now();
778
779 for _ in 0..iterations {
780 black_box(encode_broker_id(black_box(&uuid_coid), TEST_BROKER_ID));
781 }
782 let encode_uuid = start.elapsed();
783
784 let uuid_encoded = encode_broker_id(&uuid_coid, TEST_BROKER_ID);
785 let start = std::time::Instant::now();
786
787 for _ in 0..iterations {
788 black_box(decode_broker_id(black_box(&uuid_encoded), TEST_BROKER_ID));
789 }
790 let decode_uuid = start.elapsed();
791
792 let start = std::time::Instant::now();
793
794 for _ in 0..iterations {
795 black_box(encode_broker_id(black_box(&raw_coid), TEST_BROKER_ID));
796 }
797 let encode_raw = start.elapsed();
798
799 let raw_encoded = encode_broker_id(&raw_coid, TEST_BROKER_ID);
800 let start = std::time::Instant::now();
801
802 for _ in 0..iterations {
803 black_box(decode_broker_id(black_box(&raw_encoded), TEST_BROKER_ID));
804 }
805 let decode_raw = start.elapsed();
806
807 let passthrough = "O-20260305-120000-001-001-100";
808 let start = std::time::Instant::now();
809
810 for _ in 0..iterations {
811 black_box(decode_broker_id(black_box(passthrough), TEST_BROKER_ID));
812 }
813 let decode_pass = start.elapsed();
814
815 println!("\n--- Broker ID Encoder Performance ({iterations} iterations) ---");
816 println!(
817 "encode O-format: {:>8.1} ns/op",
818 encode_o.as_nanos() as f64 / iterations as f64
819 );
820 println!(
821 "decode O-format: {:>8.1} ns/op",
822 decode_o.as_nanos() as f64 / iterations as f64
823 );
824 println!(
825 "encode UUID: {:>8.1} ns/op",
826 encode_uuid.as_nanos() as f64 / iterations as f64
827 );
828 println!(
829 "decode UUID: {:>8.1} ns/op",
830 decode_uuid.as_nanos() as f64 / iterations as f64
831 );
832 println!(
833 "encode raw: {:>8.1} ns/op",
834 encode_raw.as_nanos() as f64 / iterations as f64
835 );
836 println!(
837 "decode raw: {:>8.1} ns/op",
838 decode_raw.as_nanos() as f64 / iterations as f64
839 );
840 println!(
841 "decode passthrough: {:>8.1} ns/op",
842 decode_pass.as_nanos() as f64 / iterations as f64
843 );
844 }
845}