1#![forbid(unsafe_code)]
32
33use std::{error::Error, fmt, path::PathBuf};
34
35use serde::{Deserialize, Serialize};
36
37pub mod frame;
38pub mod manifest;
39pub mod session;
40pub mod tool_call;
41
42pub mod error_codes {
47 pub const UNKNOWN_MODULE: &str = "unknown_module";
48 pub const MODULE_REMOVED: &str = "module_removed";
49 pub const MODULE_RELOADING: &str = "module_reloading";
50 pub const MODULE_WARMING: &str = "module_warming";
51 pub const TARGET_UNAVAILABLE: &str = "target_unavailable";
52 pub const MODULE_TIMEOUT: &str = "module_timeout";
53
54 pub fn is_retryable_route_open(code: &str) -> bool {
67 matches!(
68 code,
69 UNKNOWN_MODULE
70 | MODULE_RELOADING
71 | MODULE_WARMING
72 | TARGET_UNAVAILABLE
73 | MODULE_TIMEOUT
74 )
75 }
76}
77
78pub use frame::{Frame, FrameBuildError};
79
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
101pub struct BindIdentity {
102 pub project_root: PathBuf,
103 pub harness: String,
104 pub session: String,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
109#[serde(tag = "kind", rename_all = "snake_case")]
110pub enum Principal {
111 Reserved { module_id: String },
113 Direct,
115 Unverified,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
132#[serde(tag = "kind", rename_all = "snake_case")]
133pub enum RouteTarget {
134 ToolProvider {
135 module_id: String,
136 },
137 ManagementSurface {
138 module_id: String,
139 },
140 InternalService {
141 module_id: String,
142 service_id: String,
143 },
144}
145
146pub const PROTOCOL_VERSION: u8 = 2;
148
149pub const SUBC_PROTOCOL_CRATE_VERSION: &str = env!("CARGO_PKG_VERSION");
158
159pub const MIN_SUPPORTED_VERSION: u8 = 2;
161
162pub const SUBC_MODULE_ID_ENV: &str = "SUBC_MODULE_ID";
165
166pub const SUBC_LAUNCH_NONCE_ENV: &str = "SUBC_LAUNCH_NONCE";
171
172pub const HEADER_LEN: usize = 21;
174
175pub const FROZEN_PREFIX_LEN: usize = 5;
179
180pub const MAX_FRAME_BODY_LEN: u32 = 64 * 1024 * 1024;
186
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
196pub struct ErrorBody {
197 pub code: String,
198 pub message: String,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
200 pub detail: Option<serde_json::Value>,
201}
202
203impl ErrorBody {
204 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
206 Self {
207 code: code.into(),
208 message: message.into(),
209 detail: None,
210 }
211 }
212
213 pub fn with_detail(mut self, detail: serde_json::Value) -> Self {
215 self.detail = Some(detail);
216 self
217 }
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
222pub struct ModuleHelloBody {
223 pub manifest: manifest::ModuleManifest,
224 pub protocol_ver: u8,
225 #[serde(default)]
226 pub control_ops: Option<Vec<String>>,
227 #[serde(default, skip_serializing_if = "Option::is_none")]
235 pub launch_nonce: Option<String>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
240pub struct ModuleHelloAckBody {
241 pub negotiated_ver: u8,
242 pub subc_ops: Vec<String>,
243 pub subc_capabilities: Vec<String>,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
251 pub storage: Option<serde_json::Value>,
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259#[repr(u8)]
260pub enum FrameType {
261 Request = 0,
262 Response = 1,
263 Push = 2,
264 StreamData = 3,
265 StreamEnd = 4,
266 Error = 5,
267 Cancel = 6,
268 Ping = 7,
269 Pong = 8,
270 Hello = 9,
271 HelloAck = 10,
272 Goodbye = 11,
273}
274
275impl FrameType {
276 pub fn from_u8(b: u8) -> Option<Self> {
278 Some(match b {
279 0 => Self::Request,
280 1 => Self::Response,
281 2 => Self::Push,
282 3 => Self::StreamData,
283 4 => Self::StreamEnd,
284 5 => Self::Error,
285 6 => Self::Cancel,
286 7 => Self::Ping,
287 8 => Self::Pong,
288 9 => Self::Hello,
289 10 => Self::HelloAck,
290 11 => Self::Goodbye,
291 _ => return None,
292 })
293 }
294
295 pub fn is_pure_header(self) -> bool {
296 matches!(self, Self::Cancel | Self::Ping | Self::Pong | Self::Goodbye)
297 }
298}
299
300#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303#[repr(u8)]
304pub enum Priority {
305 Passive = 0,
306 Interactive = 1,
307 Background = 2,
308}
309
310impl Priority {
311 fn from_bits(bits: u8) -> Option<Self> {
312 Some(match bits {
313 0 => Self::Passive,
314 1 => Self::Interactive,
315 2 => Self::Background,
316 _ => return None,
317 })
318 }
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq)]
323#[repr(u8)]
324pub enum AdmissionClass {
325 Normal = 0,
326 Expedite = 1,
327 Sheddable = 2,
328}
329
330impl AdmissionClass {
331 fn from_bits(bits: u8) -> Option<Self> {
332 Some(match bits {
333 0 => Self::Normal,
334 1 => Self::Expedite,
335 2 => Self::Sheddable,
336 _ => return None,
337 })
338 }
339}
340
341const FLAG_BINARY: u8 = 0b0000_0001; const FLAG_PRIORITY_MASK: u8 = 0b0000_0110; const FLAG_PRIORITY_SHIFT: u8 = 1;
344const FLAG_LAST: u8 = 0b0000_1000; const FLAG_ADMISSION_MASK: u8 = 0b0011_0000; const FLAG_ADMISSION_SHIFT: u8 = 4;
347pub const FLAG_DAEMON_ORIGIN: u8 = 0b0100_0000;
348const FLAG_RESERVED_MASK: u8 = 0b1000_0000; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
352pub struct Flags(pub u8);
353
354impl Flags {
355 pub fn new(binary: bool, priority: Priority, last: bool) -> Self {
357 let mut b = 0u8;
358 if binary {
359 b |= FLAG_BINARY;
360 }
361 b |= (priority as u8) << FLAG_PRIORITY_SHIFT;
362 if last {
363 b |= FLAG_LAST;
364 }
365 Flags(b)
366 }
367
368 pub fn with_admission_class(mut self, admission_class: AdmissionClass) -> Self {
370 self.0 =
371 (self.0 & !FLAG_ADMISSION_MASK) | ((admission_class as u8) << FLAG_ADMISSION_SHIFT);
372 self
373 }
374
375 pub fn is_binary(self) -> bool {
377 self.0 & FLAG_BINARY != 0
378 }
379
380 pub fn is_last(self) -> bool {
382 self.0 & FLAG_LAST != 0
383 }
384
385 pub fn priority(self) -> Option<Priority> {
387 Priority::from_bits((self.0 & FLAG_PRIORITY_MASK) >> FLAG_PRIORITY_SHIFT)
388 }
389
390 pub fn admission_class(self) -> Option<AdmissionClass> {
392 AdmissionClass::from_bits((self.0 & FLAG_ADMISSION_MASK) >> FLAG_ADMISSION_SHIFT)
393 }
394
395 pub fn has_reserved_bits(self) -> bool {
397 self.0 & FLAG_RESERVED_MASK != 0
398 }
399
400 pub fn is_daemon_origin(self) -> bool {
402 self.0 & FLAG_DAEMON_ORIGIN != 0
403 }
404
405 pub fn with_daemon_origin(mut self) -> Self {
407 self.0 |= FLAG_DAEMON_ORIGIN;
408 self
409 }
410
411 pub fn without_daemon_origin(self) -> Self {
413 Self(self.0 & !FLAG_DAEMON_ORIGIN)
414 }
415}
416
417#[derive(Debug, Clone, Copy, PartialEq, Eq)]
419pub struct EnvelopeHeader {
420 pub len: u32,
422 pub ver: u8,
424 pub ty: FrameType,
426 pub flags: Flags,
428 pub channel: u16,
430 pub epoch: u32,
432 pub corr: u64,
434}
435
436impl EnvelopeHeader {
437 pub fn encode(&self) -> [u8; HEADER_LEN] {
439 let mut buf = [0u8; HEADER_LEN];
440 buf[0..4].copy_from_slice(&self.len.to_le_bytes());
441 buf[4] = self.ver;
442 buf[5] = self.ty as u8;
443 buf[6] = self.flags.0;
444 buf[7..9].copy_from_slice(&self.channel.to_le_bytes());
445 buf[9..13].copy_from_slice(&self.epoch.to_le_bytes());
446 buf[13..21].copy_from_slice(&self.corr.to_le_bytes());
447 buf
448 }
449}
450
451#[derive(Debug, Clone, Copy, PartialEq, Eq)]
453pub enum DecodeError {
454 TooShortForPrefix { have: usize },
456 UnsupportedVersion { ver: u8 },
458 TooShortForHeader { have: usize, need: usize },
460 UnknownFrameType { byte: u8 },
462 ReservedFlagBits { flags: u8 },
464 ReservedPriorityBits { flags: u8 },
466 ReservedAdmissionClass { flags: u8 },
468 SheddableIllegalFrameType { ty: FrameType, flags: u8 },
470 NonzeroEpochOnControlChannel { epoch: u32 },
472 PureHeaderFrameWithBody { ty: FrameType, len: u32 },
474}
475
476impl fmt::Display for DecodeError {
477 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
478 match self {
479 Self::TooShortForPrefix { have } => {
480 write!(f, "header shorter than frozen prefix: have {have} bytes")
481 }
482 Self::UnsupportedVersion { ver } => write!(f, "unsupported envelope version {ver}"),
483 Self::TooShortForHeader { have, need } => {
484 write!(
485 f,
486 "header too short for version: have {have} bytes, need {need}"
487 )
488 }
489 Self::UnknownFrameType { byte } => write!(f, "unknown frame type byte {byte}"),
490 Self::ReservedFlagBits { flags } => {
491 write!(f, "reserved flag bits set in flags 0b{flags:08b}")
492 }
493 Self::ReservedPriorityBits { flags } => {
494 write!(f, "reserved priority bits set in flags 0b{flags:08b}")
495 }
496 Self::ReservedAdmissionClass { flags } => {
497 write!(f, "reserved admission class set in flags 0b{flags:08b}")
498 }
499 Self::SheddableIllegalFrameType { ty, flags } => write!(
500 f,
501 "SHEDDABLE admission class is illegal on {ty:?} in flags 0b{flags:08b}"
502 ),
503 Self::NonzeroEpochOnControlChannel { epoch } => {
504 write!(f, "control channel carried nonzero epoch {epoch}")
505 }
506 Self::PureHeaderFrameWithBody { ty, len } => {
507 write!(
508 f,
509 "pure-header frame {ty:?} declared non-zero body length {len}"
510 )
511 }
512 }
513 }
514}
515
516impl Error for DecodeError {}
517
518fn header_len_for_version(ver: u8) -> Option<usize> {
521 match ver {
522 PROTOCOL_VERSION => Some(HEADER_LEN),
523 _ => None,
524 }
525}
526
527pub fn decode_header(bytes: &[u8]) -> Result<EnvelopeHeader, DecodeError> {
535 if bytes.len() < FROZEN_PREFIX_LEN {
536 return Err(DecodeError::TooShortForPrefix { have: bytes.len() });
537 }
538 let ver = bytes[4];
539 let need = header_len_for_version(ver).ok_or(DecodeError::UnsupportedVersion { ver })?;
540 if bytes.len() < need {
541 return Err(DecodeError::TooShortForHeader {
542 have: bytes.len(),
543 need,
544 });
545 }
546
547 let len = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
548 let ty =
549 FrameType::from_u8(bytes[5]).ok_or(DecodeError::UnknownFrameType { byte: bytes[5] })?;
550 let flags = Flags(bytes[6]);
551 if flags.has_reserved_bits() {
552 return Err(DecodeError::ReservedFlagBits { flags: bytes[6] });
553 }
554 if flags.priority().is_none() {
555 return Err(DecodeError::ReservedPriorityBits { flags: bytes[6] });
556 }
557 let admission_class = flags
558 .admission_class()
559 .ok_or(DecodeError::ReservedAdmissionClass { flags: bytes[6] })?;
560 if admission_class == AdmissionClass::Sheddable
561 && !matches!(ty, FrameType::Push | FrameType::StreamData)
562 {
563 return Err(DecodeError::SheddableIllegalFrameType {
564 ty,
565 flags: bytes[6],
566 });
567 }
568 if ty.is_pure_header() && len != 0 {
569 return Err(DecodeError::PureHeaderFrameWithBody { ty, len });
570 }
571 let channel = u16::from_le_bytes([bytes[7], bytes[8]]);
572 let epoch = u32::from_le_bytes([bytes[9], bytes[10], bytes[11], bytes[12]]);
573 if channel == 0 && epoch != 0 {
574 return Err(DecodeError::NonzeroEpochOnControlChannel { epoch });
575 }
576 let corr = u64::from_le_bytes([
577 bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19], bytes[20],
578 ]);
579
580 Ok(EnvelopeHeader {
581 len,
582 ver,
583 ty,
584 flags,
585 channel,
586 epoch,
587 corr,
588 })
589}
590
591#[cfg(test)]
592mod tests {
593 use super::*;
594
595 fn hdr(len: u32, ty: FrameType, flags: Flags, channel: u16, corr: u64) -> EnvelopeHeader {
596 hdr_with_epoch(len, ty, flags, channel, u32::from(channel != 0), corr)
597 }
598
599 fn hdr_with_epoch(
600 len: u32,
601 ty: FrameType,
602 flags: Flags,
603 channel: u16,
604 epoch: u32,
605 corr: u64,
606 ) -> EnvelopeHeader {
607 EnvelopeHeader {
608 len,
609 ver: PROTOCOL_VERSION,
610 ty,
611 flags,
612 channel,
613 epoch,
614 corr,
615 }
616 }
617
618 #[test]
619 fn bind_identity_round_trips_json() {
620 let identity = BindIdentity {
621 project_root: PathBuf::from("/tmp/project"),
622 harness: "opencode".to_string(),
623 session: "session-1".to_string(),
624 };
625
626 let encoded = serde_json::to_vec(&identity).unwrap();
627 let decoded: BindIdentity = serde_json::from_slice(&encoded).unwrap();
628
629 assert_eq!(decoded, identity);
630 }
631
632 #[test]
633 fn route_target_variants_round_trip_json() {
634 let targets = [
635 RouteTarget::ToolProvider {
636 module_id: "aft".to_string(),
637 },
638 RouteTarget::ManagementSurface {
639 module_id: "memory".to_string(),
640 },
641 RouteTarget::InternalService {
642 module_id: "bus".to_string(),
643 service_id: "dm".to_string(),
644 },
645 ];
646
647 for target in targets {
648 let encoded = serde_json::to_vec(&target).unwrap();
649 let decoded: RouteTarget = serde_json::from_slice(&encoded).unwrap();
650 assert_eq!(decoded, target);
651 }
652 }
653
654 #[test]
655 fn error_body_round_trips_json() {
656 let body = ErrorBody {
657 code: "config_divergence".to_string(),
658 message: "active config differs".to_string(),
659 detail: None,
660 };
661
662 let encoded = serde_json::to_vec(&body).unwrap();
663 let decoded: ErrorBody = serde_json::from_slice(&encoded).unwrap();
664
665 assert_eq!(decoded, body);
666 }
667
668 #[test]
669 fn round_trip_request() {
670 let h = hdr(
671 1234,
672 FrameType::Request,
673 Flags::new(false, Priority::Interactive, false),
674 42,
675 0xDEAD_BEEF_0000_0001,
676 );
677 let decoded = decode_header(&h.encode()).unwrap();
678 assert_eq!(h, decoded);
679 }
680
681 #[test]
682 fn round_trip_all_frame_types() {
683 for b in 0u8..=11 {
684 let ty = FrameType::from_u8(b).unwrap();
685 let h = hdr(0, ty, Flags::new(false, Priority::Passive, false), 0, 0);
686 assert_eq!(decode_header(&h.encode()).unwrap().ty, ty);
687 }
688 }
689
690 #[test]
691 fn pure_header_frame_has_zero_len() {
692 let h = hdr(
694 0,
695 FrameType::Cancel,
696 Flags::new(false, Priority::Passive, false),
697 7,
698 99,
699 );
700 let d = decode_header(&h.encode()).unwrap();
701 assert_eq!(d.len, 0);
702 assert_eq!(d.corr, 99);
703 }
704
705 #[test]
706 fn flags_round_trip() {
707 let f = Flags::new(true, Priority::Background, true)
708 .with_admission_class(AdmissionClass::Expedite);
709 assert!(f.is_binary());
710 assert!(f.is_last());
711 assert_eq!(f.priority(), Some(Priority::Background));
712 assert_eq!(f.admission_class(), Some(AdmissionClass::Expedite));
713 let h = hdr(8, FrameType::StreamData, f, 1, 1);
714 assert_eq!(decode_header(&h.encode()).unwrap().flags, f);
715 }
716
717 #[test]
718 fn daemon_origin_flags_decode_and_round_trip() {
719 let old = hdr(0, FrameType::Error, Flags(0), 7, 1);
720 let old_decoded = decode_header(&old.encode()).unwrap();
721 assert!(!old_decoded.flags.is_daemon_origin());
722
723 let daemon = hdr(0, FrameType::Error, Flags(0).with_daemon_origin(), 7, 1);
724 let daemon_decoded = decode_header(&daemon.encode()).unwrap();
725 assert!(daemon_decoded.flags.is_daemon_origin());
726 assert_eq!(daemon_decoded.flags.without_daemon_origin(), Flags(0));
727 assert!(Flags(0).with_daemon_origin().is_daemon_origin());
728 }
729
730 #[test]
731 fn little_endian_and_frozen_prefix_layout() {
732 let h = hdr_with_epoch(
733 0x0403_0201,
734 FrameType::Request,
735 Flags(0),
736 0x0605,
737 0x0a09_0807,
738 0x1211_100f_0e0d_0c0b,
739 );
740 let buf = h.encode();
741 assert_eq!(&buf[0..4], &[1, 2, 3, 4]);
742 assert_eq!(buf[4], PROTOCOL_VERSION);
743 assert_eq!(&buf[7..9], &[5, 6]);
744 assert_eq!(&buf[9..13], &[7, 8, 9, 10]);
745 assert_eq!(&buf[13..21], &[11, 12, 13, 14, 15, 16, 17, 18]);
746 assert_eq!(buf.len(), HEADER_LEN);
747 }
748
749 #[test]
750 fn reject_too_short_for_prefix() {
751 assert_eq!(
752 decode_header(&[0, 0, 0, 0]),
753 Err(DecodeError::TooShortForPrefix { have: 4 })
754 );
755 }
756
757 #[test]
758 fn reject_too_short_for_header() {
759 let mut b = [0u8; 10];
761 b[4] = PROTOCOL_VERSION;
762 assert_eq!(
763 decode_header(&b),
764 Err(DecodeError::TooShortForHeader {
765 have: 10,
766 need: HEADER_LEN
767 })
768 );
769 }
770
771 #[test]
772 fn reject_unsupported_version() {
773 let mut b = [0u8; HEADER_LEN];
774 b[4] = 1;
775 assert_eq!(
776 decode_header(&b),
777 Err(DecodeError::UnsupportedVersion { ver: 1 })
778 );
779 }
780
781 #[test]
782 fn reject_unknown_frame_type() {
783 let mut b = [0u8; HEADER_LEN];
784 b[4] = PROTOCOL_VERSION;
785 b[5] = 99;
786 assert_eq!(
787 decode_header(&b),
788 Err(DecodeError::UnknownFrameType { byte: 99 })
789 );
790 }
791
792 #[test]
793 fn reject_reserved_flag_bits() {
794 let mut b = [0u8; HEADER_LEN];
795 b[4] = PROTOCOL_VERSION;
796 b[5] = FrameType::Request as u8;
797 b[6] = 0b1000_0000; assert_eq!(
799 decode_header(&b),
800 Err(DecodeError::ReservedFlagBits { flags: 0b1000_0000 })
801 );
802 }
803
804 #[test]
805 fn reject_reserved_priority_bits() {
806 let mut b = [0u8; HEADER_LEN];
807 b[4] = PROTOCOL_VERSION;
808 b[5] = FrameType::Request as u8;
809 b[6] = 0b0000_0110; assert_eq!(
811 decode_header(&b),
812 Err(DecodeError::ReservedPriorityBits { flags: 0b0000_0110 })
813 );
814 }
815
816 #[test]
817 fn reject_pure_header_frame_with_body_len() {
818 let h = hdr(
819 1,
820 FrameType::Ping,
821 Flags::new(false, Priority::Passive, false),
822 0,
823 1,
824 );
825 assert_eq!(
826 decode_header(&h.encode()),
827 Err(DecodeError::PureHeaderFrameWithBody {
828 ty: FrameType::Ping,
829 len: 1
830 })
831 );
832 }
833
834 #[test]
835 fn epoch_boundaries_round_trip() {
836 for (channel, epoch) in [(0, 0), (1, 1), (u16::MAX, u32::MAX)] {
837 let h = hdr_with_epoch(
838 0,
839 FrameType::Request,
840 Flags::new(false, Priority::Passive, false),
841 channel,
842 epoch,
843 9,
844 );
845 assert_eq!(decode_header(&h.encode()).unwrap(), h);
846 }
847 }
848
849 #[test]
850 fn admission_classes_accept_three_values_and_reject_reserved_value() {
851 for (ty, admission_class) in [
852 (FrameType::Request, AdmissionClass::Normal),
853 (FrameType::Request, AdmissionClass::Expedite),
854 (FrameType::Push, AdmissionClass::Sheddable),
855 (FrameType::StreamData, AdmissionClass::Sheddable),
856 ] {
857 let flags = Flags::new(false, Priority::Interactive, false)
858 .with_admission_class(admission_class);
859 let h = hdr(0, ty, flags, 1, 2);
860 assert_eq!(decode_header(&h.encode()).unwrap().flags, flags);
861 }
862
863 let mut h = hdr(
864 0,
865 FrameType::Push,
866 Flags::new(false, Priority::Passive, false),
867 1,
868 2,
869 )
870 .encode();
871 h[6] |= 0b0011_0000;
872 assert_eq!(
873 decode_header(&h),
874 Err(DecodeError::ReservedAdmissionClass { flags: h[6] })
875 );
876 }
877
878 #[test]
879 fn sheddable_rejected_on_every_illegal_frame_type() {
880 let flags = Flags::new(false, Priority::Passive, false)
881 .with_admission_class(AdmissionClass::Sheddable);
882 for ty in [
883 FrameType::Request,
884 FrameType::Response,
885 FrameType::StreamEnd,
886 FrameType::Error,
887 FrameType::Cancel,
888 FrameType::Ping,
889 FrameType::Pong,
890 FrameType::Hello,
891 FrameType::HelloAck,
892 FrameType::Goodbye,
893 ] {
894 let h = hdr(0, ty, flags, 1, 2);
895 assert_eq!(
896 decode_header(&h.encode()),
897 Err(DecodeError::SheddableIllegalFrameType { ty, flags: flags.0 })
898 );
899 }
900 }
901
902 #[test]
903 fn nonzero_epoch_on_control_channel_is_rejected() {
904 let h = hdr_with_epoch(
905 0,
906 FrameType::Request,
907 Flags::new(false, Priority::Passive, false),
908 0,
909 u32::MAX,
910 2,
911 );
912 assert_eq!(
913 decode_header(&h.encode()),
914 Err(DecodeError::NonzeroEpochOnControlChannel { epoch: u32::MAX })
915 );
916 }
917}