Skip to main content

microsandbox_agent_client/
message.rs

1//! Outbound agent message builders.
2//!
3//! These types represent the semantic message before the client assigns a
4//! correlation ID and writes a transport packet.
5
6use microsandbox_protocol::codec::RawFrame;
7use microsandbox_protocol::message::{Message, MessageType};
8use serde::Serialize;
9
10use crate::{AgentClientError, AgentClientResult};
11
12//--------------------------------------------------------------------------------------------------
13// Types
14//--------------------------------------------------------------------------------------------------
15
16/// A message described by protocol type and a native serializable payload.
17///
18/// Use this form when Rust should CBOR-encode the payload. The payload type must
19/// serialize to the shape expected by `microsandbox-protocol` for
20/// [`message_type`](Self::message_type).
21#[derive(Debug, Clone)]
22pub struct TypedMessage<T> {
23    /// Protocol message type.
24    pub message_type: MessageType,
25    /// Native payload to CBOR-encode.
26    pub payload: T,
27}
28
29/// A message described by protocol type and already-CBOR-encoded payload bytes.
30///
31/// The payload is only the message-type payload, not the outer protocol
32/// envelope and not the length-prefixed transport packet. The client still
33/// builds `{ v, t, p }`, derives frame flags, and assigns the correlation ID.
34#[derive(Debug, Clone)]
35pub struct EncodedMessage {
36    /// Protocol message type.
37    pub message_type: MessageType,
38    /// CBOR-encoded payload bytes.
39    pub payload: Vec<u8>,
40}
41
42/// Fully encoded outbound message body, before correlation ID assignment.
43///
44/// This is the CBOR protocol envelope body that belongs after the binary frame
45/// header. It is mostly useful for transport adapters, tests, and language
46/// bindings.
47#[derive(Debug, Clone)]
48pub struct OutboundMessage {
49    /// Protocol message type.
50    pub message_type: MessageType,
51    /// Frame flags derived from the message type.
52    pub flags: u8,
53    /// CBOR-encoded protocol envelope body.
54    pub body: Vec<u8>,
55}
56
57/// Converts a typed or encoded message into an outbound protocol body.
58///
59/// Implementations must fail before sending when the negotiated peer generation
60/// is too old for the selected message type.
61pub trait IntoOutboundMessage {
62    /// Encode the outbound message for a connection.
63    ///
64    /// `protocol_version` is the generation written into the envelope.
65    /// `negotiated_version` is the capability gate used before encoding.
66    fn into_outbound_message(
67        self,
68        protocol_version: u8,
69        negotiated_version: u8,
70    ) -> AgentClientResult<OutboundMessage>;
71}
72
73//--------------------------------------------------------------------------------------------------
74// Methods
75//--------------------------------------------------------------------------------------------------
76
77impl<T> TypedMessage<T> {
78    /// Create a typed message from a protocol type and native payload.
79    pub fn new(message_type: MessageType, payload: T) -> Self {
80        Self {
81            message_type,
82            payload,
83        }
84    }
85}
86
87impl EncodedMessage {
88    /// Create an encoded message from a protocol type and CBOR payload bytes.
89    pub fn new(message_type: MessageType, payload: impl Into<Vec<u8>>) -> Self {
90        Self {
91            message_type,
92            payload: payload.into(),
93        }
94    }
95}
96
97impl OutboundMessage {
98    /// Convert the outbound message into a raw frame with an assigned ID.
99    pub fn into_raw_frame(self, id: u32) -> RawFrame {
100        RawFrame {
101            id,
102            flags: self.flags,
103            body: self.body,
104        }
105    }
106}
107
108//--------------------------------------------------------------------------------------------------
109// Trait Implementations
110//--------------------------------------------------------------------------------------------------
111
112impl<T> IntoOutboundMessage for TypedMessage<T>
113where
114    T: Serialize,
115{
116    fn into_outbound_message(
117        self,
118        protocol_version: u8,
119        negotiated_version: u8,
120    ) -> AgentClientResult<OutboundMessage> {
121        let mut payload = Vec::new();
122        ciborium::into_writer(&self.payload, &mut payload)
123            .map_err(|error| AgentClientError::Cbor(error.to_string()))?;
124        EncodedMessage::new(self.message_type, payload)
125            .into_outbound_message(protocol_version, negotiated_version)
126    }
127}
128
129impl IntoOutboundMessage for EncodedMessage {
130    fn into_outbound_message(
131        self,
132        protocol_version: u8,
133        negotiated_version: u8,
134    ) -> AgentClientResult<OutboundMessage> {
135        if !self.message_type.is_available_at(negotiated_version) {
136            return Err(AgentClientError::UnsupportedOperation {
137                msg_type: self.message_type.as_str(),
138                needs: self.message_type.min_protocol_version(),
139                peer: negotiated_version,
140            });
141        }
142
143        let flags = self.message_type.flags();
144        let message = Message {
145            v: protocol_version,
146            t: self.message_type,
147            id: 0,
148            flags,
149            p: self.payload,
150        };
151        let mut body = Vec::new();
152        ciborium::into_writer(&message, &mut body)
153            .map_err(|error| AgentClientError::Cbor(error.to_string()))?;
154
155        Ok(OutboundMessage {
156            message_type: self.message_type,
157            flags,
158            body,
159        })
160    }
161}