1use serde::{Deserialize, Serialize, de::DeserializeOwned};
4
5use crate::error::ProtocolResult;
6
7pub const PROTOCOL_VERSION: u8 = 10;
13
14pub const FLAG_TERMINAL: u8 = 0b0000_0001;
18
19pub const FLAG_SESSION_START: u8 = 0b0000_0010;
23
24pub const FLAG_SHUTDOWN: u8 = 0b0000_0100;
29
30pub const FLAG_BULK: u8 = 0b0000_1000;
32
33pub const FRAME_HEADER_SIZE: usize = 5;
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct Message {
51 pub v: u8,
59
60 pub t: MessageType,
62
63 #[serde(skip)]
68 pub id: u32,
69
70 #[serde(skip)]
74 pub flags: u8,
75
76 #[serde(with = "serde_bytes")]
78 pub p: Vec<u8>,
79}
80
81#[derive(
89 Debug,
90 Clone,
91 Copy,
92 PartialEq,
93 Eq,
94 Hash,
95 strum::IntoStaticStr,
96 strum::AsRefStr,
97 strum::EnumString,
98 strum::EnumIter,
99)]
100pub enum MessageType {
101 #[strum(serialize = "core.ready")]
103 Ready,
104
105 #[strum(serialize = "core.init.resolved")]
107 InitResolved,
108
109 #[strum(serialize = "core.init.ack")]
111 InitAck,
112
113 #[strum(serialize = "core.shutdown")]
115 Shutdown,
116
117 #[strum(serialize = "core.relay.client.disconnected")]
119 RelayClientDisconnected,
120
121 #[strum(serialize = "core.clock.sync")]
123 ClockSync,
124
125 #[strum(serialize = "core.ping")]
127 Ping,
128
129 #[strum(serialize = "core.pong")]
131 Pong,
132
133 #[strum(serialize = "core.touch")]
135 Touch,
136
137 #[strum(serialize = "core.touched")]
139 Touched,
140
141 #[strum(serialize = "core.workload.freeze")]
143 WorkloadFreeze,
144
145 #[strum(serialize = "core.workload.frozen")]
147 WorkloadFrozen,
148
149 #[strum(serialize = "core.workload.thaw")]
151 WorkloadThaw,
152
153 #[strum(serialize = "core.workload.thawed")]
155 WorkloadThawed,
156
157 #[strum(serialize = "core.workload.transport.credit")]
159 WorkloadTransportCredit,
160
161 #[strum(serialize = "core.root_disk.prepare")]
163 RootDiskPrepare,
164
165 #[strum(serialize = "core.root_disk.grow")]
167 RootDiskGrow,
168
169 #[strum(serialize = "core.root_disk.state")]
171 RootDiskState,
172
173 #[strum(serialize = "core.error")]
175 CoreError,
176
177 #[strum(serialize = "core.bulk.accepted")]
179 BulkAccepted,
180
181 #[strum(serialize = "core.bulk.credit")]
183 BulkCredit,
184
185 #[strum(serialize = "core.bulk.finish")]
187 BulkFinish,
188
189 #[strum(serialize = "core.bulk.cancel")]
191 BulkCancel,
192
193 #[strum(serialize = "core.exec.request")]
195 ExecRequest,
196
197 #[strum(serialize = "core.exec.started")]
199 ExecStarted,
200
201 #[strum(serialize = "core.exec.stdin")]
203 ExecStdin,
204
205 #[strum(serialize = "core.exec.stdin.error")]
210 ExecStdinError,
211
212 #[strum(serialize = "core.exec.stdout")]
214 ExecStdout,
215
216 #[strum(serialize = "core.exec.stderr")]
218 ExecStderr,
219
220 #[strum(serialize = "core.exec.exited")]
222 ExecExited,
223
224 #[strum(serialize = "core.exec.failed")]
228 ExecFailed,
229
230 #[strum(serialize = "core.exec.resize")]
232 ExecResize,
233
234 #[strum(serialize = "core.exec.signal")]
236 ExecSignal,
237
238 #[strum(serialize = "core.exec.output.ack")]
240 ExecOutputAck,
241
242 #[strum(serialize = "core.exec.stdin.ack")]
244 ExecStdinAck,
245
246 #[strum(serialize = "core.fs.request")]
248 FsRequest,
249
250 #[strum(serialize = "core.fs.response")]
252 FsResponse,
253
254 #[strum(serialize = "core.fs.data")]
256 FsData,
257
258 #[strum(serialize = "core.tcp.connect")]
260 TcpConnect,
261
262 #[strum(serialize = "core.tcp.connected")]
264 TcpConnected,
265
266 #[strum(serialize = "core.tcp.data")]
268 TcpData,
269
270 #[strum(serialize = "core.tcp.eof")]
272 TcpEof,
273
274 #[strum(serialize = "core.tcp.close")]
276 TcpClose,
277
278 #[strum(serialize = "core.tcp.closed")]
280 TcpClosed,
281
282 #[strum(serialize = "core.tcp.failed")]
284 TcpFailed,
285
286 #[strum(serialize = "core.bootstrap")]
288 Bootstrap,
289}
290
291impl Message {
296 pub fn new(t: MessageType, id: u32, p: Vec<u8>) -> Self {
298 let flags = t.flags();
299 Self {
300 v: PROTOCOL_VERSION,
301 t,
302 id,
303 flags,
304 p,
305 }
306 }
307
308 pub fn with_payload<T: Serialize>(
310 t: MessageType,
311 id: u32,
312 payload: &T,
313 ) -> ProtocolResult<Self> {
314 let mut p = Vec::new();
315 ciborium::into_writer(payload, &mut p)?;
316 let flags = t.flags();
317 Ok(Self {
318 v: PROTOCOL_VERSION,
319 t,
320 id,
321 flags,
322 p,
323 })
324 }
325
326 pub fn payload<T: DeserializeOwned>(&self) -> ProtocolResult<T> {
328 Ok(ciborium::from_reader(&self.p[..])?)
329 }
330}
331
332impl MessageType {
333 pub fn is_exec_control(self) -> bool {
335 matches!(
336 self,
337 Self::ExecOutputAck | Self::ExecStdinAck | Self::ExecSignal | Self::ExecResize
338 )
339 }
340
341 pub fn uses_workload_data_credit(self) -> bool {
347 matches!(
348 self,
349 Self::ExecStdin | Self::FsData | Self::TcpData | Self::TcpEof
350 )
351 }
352
353 pub fn flags(&self) -> u8 {
355 match self {
356 Self::Pong
357 | Self::Touched
358 | Self::WorkloadFrozen
359 | Self::WorkloadThawed
360 | Self::RootDiskState
361 | Self::CoreError
362 | Self::ExecExited
363 | Self::ExecFailed
364 | Self::FsResponse
365 | Self::TcpClosed
366 | Self::TcpFailed => FLAG_TERMINAL,
367 Self::ExecRequest | Self::FsRequest | Self::TcpConnect => FLAG_SESSION_START,
368 Self::Shutdown => FLAG_SHUTDOWN,
369 _ => 0,
370 }
371 }
372
373 pub fn min_protocol_version(&self) -> u8 {
394 match self {
395 Self::Ready
396 | Self::InitResolved
397 | Self::InitAck
398 | Self::Shutdown
399 | Self::RelayClientDisconnected
400 | Self::ClockSync
401 | Self::ExecRequest
402 | Self::ExecStarted
403 | Self::ExecStdin
404 | Self::ExecStdinError
405 | Self::ExecStdout
406 | Self::ExecStderr
407 | Self::ExecExited
408 | Self::ExecFailed
409 | Self::ExecResize
410 | Self::ExecSignal => 1,
411 Self::ExecOutputAck | Self::ExecStdinAck => 10,
412 Self::FsRequest | Self::FsResponse | Self::FsData => 2,
413 Self::CoreError => 5,
414 Self::Ping | Self::Pong | Self::Touch | Self::Touched => 6,
415 Self::Bootstrap => 7,
416 Self::WorkloadFreeze
417 | Self::WorkloadFrozen
418 | Self::WorkloadThaw
419 | Self::WorkloadThawed
420 | Self::WorkloadTransportCredit => 9,
421 Self::RootDiskPrepare | Self::RootDiskGrow | Self::RootDiskState => 9,
422 Self::BulkAccepted | Self::BulkCredit | Self::BulkFinish | Self::BulkCancel => 8,
423 Self::TcpConnect
424 | Self::TcpConnected
425 | Self::TcpData
426 | Self::TcpEof
427 | Self::TcpClose
428 | Self::TcpClosed
429 | Self::TcpFailed => 4,
430 }
431 }
432
433 pub fn is_available_at(&self, peer_generation: u8) -> bool {
442 self.min_protocol_version() <= peer_generation
443 }
444
445 pub fn as_str(&self) -> &'static str {
450 (*self).into()
451 }
452
453 pub fn from_wire_str(s: &str) -> Option<Self> {
456 s.parse().ok()
457 }
458}
459
460impl Serialize for MessageType {
465 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
466 where
467 S: serde::Serializer,
468 {
469 serializer.serialize_str(self.as_str())
470 }
471}
472
473impl<'de> Deserialize<'de> for MessageType {
474 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
475 where
476 D: serde::Deserializer<'de>,
477 {
478 let s = String::deserialize(deserializer)?;
479 Self::from_wire_str(&s)
480 .ok_or_else(|| serde::de::Error::custom(format!("unknown message type: {s}")))
481 }
482}
483
484#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn retained_payload_and_eof_use_data_credit_without_changing_frame_flags() {
494 for message in [
495 MessageType::ExecStdin,
496 MessageType::FsData,
497 MessageType::TcpData,
498 MessageType::TcpEof,
499 ] {
500 assert!(message.uses_workload_data_credit());
501 assert_eq!(
502 message.flags(),
503 0,
504 "logical admission must not change the wire header"
505 );
506 }
507 for message in [
508 MessageType::ExecRequest,
509 MessageType::Ping,
510 MessageType::FsRequest,
511 MessageType::TcpConnect,
512 MessageType::ExecSignal,
513 MessageType::BulkFinish,
514 MessageType::BulkCancel,
515 MessageType::RelayClientDisconnected,
516 ] {
517 assert!(!message.uses_workload_data_credit());
518 }
519 }
520
521 #[test]
522 fn test_message_type_roundtrip() {
523 let types = [
524 (MessageType::Bootstrap, "core.bootstrap"),
525 (MessageType::Ready, "core.ready"),
526 (MessageType::InitResolved, "core.init.resolved"),
527 (MessageType::InitAck, "core.init.ack"),
528 (MessageType::Shutdown, "core.shutdown"),
529 (
530 MessageType::RelayClientDisconnected,
531 "core.relay.client.disconnected",
532 ),
533 (MessageType::ClockSync, "core.clock.sync"),
534 (MessageType::Ping, "core.ping"),
535 (MessageType::Pong, "core.pong"),
536 (MessageType::Touch, "core.touch"),
537 (MessageType::Touched, "core.touched"),
538 (MessageType::WorkloadFreeze, "core.workload.freeze"),
539 (MessageType::WorkloadFrozen, "core.workload.frozen"),
540 (MessageType::WorkloadThaw, "core.workload.thaw"),
541 (MessageType::WorkloadThawed, "core.workload.thawed"),
542 (
543 MessageType::WorkloadTransportCredit,
544 "core.workload.transport.credit",
545 ),
546 (MessageType::CoreError, "core.error"),
547 (MessageType::BulkAccepted, "core.bulk.accepted"),
548 (MessageType::BulkCredit, "core.bulk.credit"),
549 (MessageType::BulkFinish, "core.bulk.finish"),
550 (MessageType::BulkCancel, "core.bulk.cancel"),
551 (MessageType::ExecRequest, "core.exec.request"),
552 (MessageType::ExecStarted, "core.exec.started"),
553 (MessageType::ExecStdin, "core.exec.stdin"),
554 (MessageType::ExecStdinError, "core.exec.stdin.error"),
555 (MessageType::ExecStdout, "core.exec.stdout"),
556 (MessageType::ExecStderr, "core.exec.stderr"),
557 (MessageType::ExecExited, "core.exec.exited"),
558 (MessageType::ExecFailed, "core.exec.failed"),
559 (MessageType::ExecResize, "core.exec.resize"),
560 (MessageType::ExecSignal, "core.exec.signal"),
561 (MessageType::FsRequest, "core.fs.request"),
562 (MessageType::FsResponse, "core.fs.response"),
563 (MessageType::FsData, "core.fs.data"),
564 (MessageType::TcpConnect, "core.tcp.connect"),
565 (MessageType::TcpConnected, "core.tcp.connected"),
566 (MessageType::TcpData, "core.tcp.data"),
567 (MessageType::TcpEof, "core.tcp.eof"),
568 (MessageType::TcpClose, "core.tcp.close"),
569 (MessageType::TcpClosed, "core.tcp.closed"),
570 (MessageType::TcpFailed, "core.tcp.failed"),
571 ];
572
573 for (mt, expected_str) in &types {
574 assert_eq!(mt.as_str(), *expected_str);
575 assert_eq!(MessageType::from_wire_str(expected_str).unwrap(), *mt);
576 }
577 }
578
579 #[test]
580 fn test_message_type_serde_roundtrip() {
581 let types = [
582 MessageType::Bootstrap,
583 MessageType::Ready,
584 MessageType::InitResolved,
585 MessageType::InitAck,
586 MessageType::Shutdown,
587 MessageType::RelayClientDisconnected,
588 MessageType::ClockSync,
589 MessageType::Ping,
590 MessageType::Pong,
591 MessageType::Touch,
592 MessageType::Touched,
593 MessageType::WorkloadFreeze,
594 MessageType::WorkloadFrozen,
595 MessageType::WorkloadThaw,
596 MessageType::WorkloadThawed,
597 MessageType::WorkloadTransportCredit,
598 MessageType::CoreError,
599 MessageType::BulkAccepted,
600 MessageType::BulkCredit,
601 MessageType::BulkFinish,
602 MessageType::BulkCancel,
603 MessageType::ExecRequest,
604 MessageType::ExecStarted,
605 MessageType::ExecStdin,
606 MessageType::ExecStdinError,
607 MessageType::ExecStdout,
608 MessageType::ExecStderr,
609 MessageType::ExecExited,
610 MessageType::ExecFailed,
611 MessageType::ExecResize,
612 MessageType::ExecSignal,
613 MessageType::FsRequest,
614 MessageType::FsResponse,
615 MessageType::FsData,
616 MessageType::TcpConnect,
617 MessageType::TcpConnected,
618 MessageType::TcpData,
619 MessageType::TcpEof,
620 MessageType::TcpClose,
621 MessageType::TcpClosed,
622 MessageType::TcpFailed,
623 ];
624
625 for mt in &types {
626 let mut buf = Vec::new();
627 ciborium::into_writer(mt, &mut buf).unwrap();
628 let decoded: MessageType = ciborium::from_reader(&buf[..]).unwrap();
629 assert_eq!(&decoded, mt);
630 }
631 }
632
633 #[test]
634 fn test_unknown_message_type() {
635 assert!(MessageType::from_wire_str("core.unknown").is_none());
636 }
637
638 #[test]
639 fn test_message_with_payload_roundtrip() {
640 use crate::exec::ExecExited;
641
642 let msg =
643 Message::with_payload(MessageType::ExecExited, 7, &ExecExited { code: 42 }).unwrap();
644
645 assert_eq!(msg.t, MessageType::ExecExited);
646 assert_eq!(msg.id, 7);
647 assert_eq!(msg.flags, FLAG_TERMINAL);
648
649 let payload: ExecExited = msg.payload().unwrap();
650 assert_eq!(payload.code, 42);
651 }
652
653 #[test]
654 fn test_message_type_flags() {
655 assert_eq!(MessageType::ExecExited.flags(), FLAG_TERMINAL);
656 assert_eq!(MessageType::ExecFailed.flags(), FLAG_TERMINAL);
657 assert_eq!(MessageType::FsResponse.flags(), FLAG_TERMINAL);
658 assert_eq!(MessageType::TcpClosed.flags(), FLAG_TERMINAL);
659 assert_eq!(MessageType::TcpFailed.flags(), FLAG_TERMINAL);
660 assert_eq!(MessageType::Pong.flags(), FLAG_TERMINAL);
661 assert_eq!(MessageType::Touched.flags(), FLAG_TERMINAL);
662 assert_eq!(MessageType::WorkloadFrozen.flags(), FLAG_TERMINAL);
663 assert_eq!(MessageType::WorkloadThawed.flags(), FLAG_TERMINAL);
664 assert_eq!(MessageType::ExecRequest.flags(), FLAG_SESSION_START);
665 assert_eq!(MessageType::FsRequest.flags(), FLAG_SESSION_START);
666 assert_eq!(MessageType::TcpConnect.flags(), FLAG_SESSION_START);
667 assert_eq!(MessageType::Ready.flags(), 0);
668 assert_eq!(MessageType::Bootstrap.flags(), 0);
669 assert_eq!(MessageType::InitResolved.flags(), 0);
670 assert_eq!(MessageType::InitAck.flags(), 0);
671 assert_eq!(MessageType::Shutdown.flags(), FLAG_SHUTDOWN);
672 assert_eq!(MessageType::ClockSync.flags(), 0);
673 assert_eq!(MessageType::Ping.flags(), 0);
674 assert_eq!(MessageType::Touch.flags(), 0);
675 assert_eq!(MessageType::WorkloadFreeze.flags(), 0);
676 assert_eq!(MessageType::WorkloadThaw.flags(), 0);
677 assert_eq!(MessageType::BulkAccepted.flags(), 0);
678 assert_eq!(MessageType::BulkCredit.flags(), 0);
679 assert_eq!(MessageType::BulkFinish.flags(), 0);
680 assert_eq!(MessageType::BulkCancel.flags(), 0);
681 assert_eq!(MessageType::ExecStarted.flags(), 0);
682 assert_eq!(MessageType::ExecStdin.flags(), 0);
683 assert_eq!(MessageType::ExecStdout.flags(), 0);
684 assert_eq!(MessageType::ExecStderr.flags(), 0);
685 assert_eq!(MessageType::ExecResize.flags(), 0);
686 assert_eq!(MessageType::ExecSignal.flags(), 0);
687 assert_eq!(MessageType::FsData.flags(), 0);
688 assert_eq!(MessageType::TcpConnected.flags(), 0);
689 assert_eq!(MessageType::TcpData.flags(), 0);
690 assert_eq!(MessageType::TcpEof.flags(), 0);
691 assert_eq!(MessageType::TcpClose.flags(), 0);
692 }
693
694 #[test]
695 fn test_additive_fields_keep_old_and_new_compatible() {
696 use serde::{Deserialize, Serialize};
699
700 #[derive(Serialize, Deserialize)]
702 struct Old {
703 a: u32,
704 b: u32,
705 }
706
707 #[derive(Serialize, Deserialize, Debug, PartialEq)]
709 struct New {
710 a: u32,
711 b: u32,
712 #[serde(default)]
713 c: u32,
714 }
715
716 let mut new_bytes = Vec::new();
718 ciborium::into_writer(&New { a: 1, b: 2, c: 3 }, &mut new_bytes).unwrap();
719 let as_old: Old = ciborium::from_reader(&new_bytes[..]).unwrap();
720 assert_eq!((as_old.a, as_old.b), (1, 2));
721
722 let mut old_bytes = Vec::new();
724 ciborium::into_writer(&Old { a: 1, b: 2 }, &mut old_bytes).unwrap();
725 let as_new: New = ciborium::from_reader(&old_bytes[..]).unwrap();
726 assert_eq!(as_new, New { a: 1, b: 2, c: 0 });
727 }
728
729 #[test]
730 fn test_is_available_at() {
731 assert!(MessageType::ExecRequest.is_available_at(1));
733 assert!(MessageType::ExecRequest.is_available_at(2));
734 assert!(MessageType::ExecRequest.is_available_at(PROTOCOL_VERSION));
735 assert!(!MessageType::FsRequest.is_available_at(1));
737 assert!(MessageType::FsRequest.is_available_at(2));
738 assert!(MessageType::FsRequest.is_available_at(PROTOCOL_VERSION));
739 assert!(!MessageType::Ping.is_available_at(5));
741 assert!(MessageType::Ping.is_available_at(6));
742 assert!(MessageType::Ping.is_available_at(PROTOCOL_VERSION));
743 assert!(!MessageType::Bootstrap.is_available_at(6));
745 assert!(!MessageType::WorkloadFreeze.is_available_at(8));
747 assert!(MessageType::WorkloadFreeze.is_available_at(PROTOCOL_VERSION));
748 assert!(MessageType::Bootstrap.is_available_at(7));
749 assert!(!MessageType::BulkAccepted.is_available_at(7));
752 assert!(MessageType::BulkAccepted.is_available_at(8));
753 }
754
755 #[test]
756 fn test_min_protocol_version_per_type() {
757 let baseline = [
760 MessageType::Ready,
761 MessageType::InitResolved,
762 MessageType::InitAck,
763 MessageType::Shutdown,
764 MessageType::RelayClientDisconnected,
765 MessageType::ClockSync,
766 MessageType::ExecRequest,
767 MessageType::ExecStarted,
768 MessageType::ExecStdin,
769 MessageType::ExecStdinError,
770 MessageType::ExecStdout,
771 MessageType::ExecStderr,
772 MessageType::ExecExited,
773 MessageType::ExecFailed,
774 MessageType::ExecResize,
775 MessageType::ExecSignal,
776 ];
777 for mt in &baseline {
778 assert_eq!(mt.min_protocol_version(), 1, "{mt:?} should be v1 baseline");
779 }
780
781 for mt in [
784 MessageType::FsRequest,
785 MessageType::FsResponse,
786 MessageType::FsData,
787 ] {
788 assert_eq!(mt.min_protocol_version(), 2, "{mt:?} should require gen 2");
789 }
790
791 for mt in [
792 MessageType::Ping,
793 MessageType::Pong,
794 MessageType::Touch,
795 MessageType::Touched,
796 ] {
797 assert_eq!(mt.min_protocol_version(), 6, "{mt:?} should require gen 6");
798 }
799
800 assert_eq!(MessageType::Bootstrap.min_protocol_version(), 7);
801 for mt in [
802 MessageType::BulkAccepted,
803 MessageType::BulkCredit,
804 MessageType::BulkFinish,
805 MessageType::BulkCancel,
806 ] {
807 assert_eq!(mt.min_protocol_version(), 8, "{mt:?} should require gen 8");
808 }
809
810 for mt in [
811 MessageType::WorkloadFreeze,
812 MessageType::WorkloadFrozen,
813 MessageType::WorkloadThaw,
814 MessageType::WorkloadThawed,
815 MessageType::WorkloadTransportCredit,
816 ] {
817 assert_eq!(mt.min_protocol_version(), 9, "{mt:?} should require gen 9");
818 }
819
820 assert!(MessageType::FsRequest.min_protocol_version() <= PROTOCOL_VERSION);
822 }
823
824 #[test]
825 fn test_message_new_computes_flags() {
826 let msg = Message::new(MessageType::ExecRequest, 1, Vec::new());
827 assert_eq!(msg.flags, FLAG_SESSION_START);
828
829 let msg = Message::new(MessageType::ExecStdout, 1, Vec::new());
830 assert_eq!(msg.flags, 0);
831 }
832}