dynamo_runtime/transports/event_plane/
codec.rs1use anyhow::{Result, anyhow, bail};
7use bytes::Bytes;
8use serde::{Deserialize, Serialize, de::DeserializeOwned};
9
10use super::EventEnvelope;
11
12#[derive(Debug, Clone, Copy)]
16pub enum Codec {
17 Msgpack(MsgpackCodec),
18}
19
20impl Default for Codec {
21 fn default() -> Self {
22 Codec::Msgpack(MsgpackCodec)
23 }
24}
25
26impl Codec {
27 pub fn encode_envelope(&self, envelope: &EventEnvelope) -> Result<Bytes> {
29 match self {
30 Codec::Msgpack(c) => c.encode_envelope(envelope),
31 }
32 }
33
34 pub fn encode_envelope_parts(
36 &self,
37 publisher_id: u64,
38 sequence: u64,
39 published_at: u64,
40 topic: &str,
41 payload: &[u8],
42 ) -> Result<Bytes> {
43 match self {
44 Codec::Msgpack(c) => {
45 c.encode_envelope_parts(publisher_id, sequence, published_at, topic, payload)
46 }
47 }
48 }
49
50 pub fn decode_envelope(&self, bytes: &Bytes) -> Result<EventEnvelope> {
52 match self {
53 Codec::Msgpack(c) => c.decode_envelope(bytes),
54 }
55 }
56
57 pub(crate) fn decode_envelope_identity(&self, bytes: &Bytes) -> Result<(u64, u64)> {
59 match self {
60 Codec::Msgpack(c) => c.decode_envelope_identity(bytes),
61 }
62 }
63
64 pub fn encode_payload<T: Serialize>(&self, payload: &T) -> Result<Bytes> {
66 match self {
67 Codec::Msgpack(c) => c.encode_payload(payload),
68 }
69 }
70
71 pub fn decode_payload<T: DeserializeOwned>(&self, bytes: &Bytes) -> Result<T> {
73 match self {
74 Codec::Msgpack(c) => c.decode_payload(bytes),
75 }
76 }
77
78 pub fn name(&self) -> &'static str {
80 match self {
81 Codec::Msgpack(c) => c.name(),
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy, Default)]
87pub struct MsgpackCodec;
88
89#[derive(Deserialize, Serialize)]
90struct BorrowedEventEnvelope<'a> {
91 publisher_id: u64,
92 sequence: u64,
93 published_at: u64,
94 #[serde(borrow)]
95 topic: &'a str,
96 #[serde(borrow, serialize_with = "serialize_bytes")]
97 payload: &'a [u8],
98}
99
100fn serialize_bytes<S>(bytes: &&[u8], serializer: S) -> std::result::Result<S::Ok, S::Error>
101where
102 S: serde::Serializer,
103{
104 serializer.serialize_bytes(bytes)
105}
106
107impl MsgpackCodec {
108 pub fn encode_envelope(&self, envelope: &EventEnvelope) -> Result<Bytes> {
109 Ok(Bytes::from(rmp_serde::to_vec_named(envelope)?))
110 }
111
112 pub fn encode_envelope_parts(
113 &self,
114 publisher_id: u64,
115 sequence: u64,
116 published_at: u64,
117 topic: &str,
118 payload: &[u8],
119 ) -> Result<Bytes> {
120 let envelope = BorrowedEventEnvelope {
121 publisher_id,
122 sequence,
123 published_at,
124 topic,
125 payload,
126 };
127 Ok(Bytes::from(rmp_serde::to_vec_named(&envelope)?))
128 }
129
130 pub fn decode_envelope(&self, bytes: &Bytes) -> Result<EventEnvelope> {
131 let envelope: BorrowedEventEnvelope<'_> = rmp_serde::from_slice(bytes)?;
132 let payload_start = (envelope.payload.as_ptr() as usize)
133 .checked_sub(bytes.as_ptr() as usize)
134 .ok_or_else(|| {
135 anyhow!("MessagePack envelope payload is not borrowed from its frame")
136 })?;
137 let payload_end = payload_start
138 .checked_add(envelope.payload.len())
139 .ok_or_else(|| anyhow!("MessagePack envelope payload range overflowed"))?;
140
141 if payload_end > bytes.len() {
142 bail!("MessagePack envelope payload exceeds its frame");
143 }
144
145 Ok(EventEnvelope {
146 publisher_id: envelope.publisher_id,
147 sequence: envelope.sequence,
148 published_at: envelope.published_at,
149 topic: envelope.topic.to_owned(),
150 payload: bytes.slice(payload_start..payload_end),
151 })
152 }
153
154 pub(crate) fn decode_envelope_identity(&self, bytes: &Bytes) -> Result<(u64, u64)> {
155 let envelope: BorrowedEventEnvelope<'_> = rmp_serde::from_slice(bytes)?;
156 Ok((envelope.publisher_id, envelope.sequence))
157 }
158
159 pub fn encode_payload<T: Serialize>(&self, payload: &T) -> Result<Bytes> {
160 Ok(Bytes::from(rmp_serde::to_vec_named(payload)?))
161 }
162
163 pub fn decode_payload<T: DeserializeOwned>(&self, bytes: &Bytes) -> Result<T> {
164 Ok(rmp_serde::from_slice(bytes)?)
165 }
166
167 pub fn name(&self) -> &'static str {
168 "msgpack"
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize)]
177 struct TestEvent {
178 worker_id: u64,
179 message: String,
180 }
181
182 #[test]
183 fn test_msgpack_codec_envelope_roundtrip() {
184 let codec = MsgpackCodec;
185
186 let envelope = EventEnvelope {
187 publisher_id: 12345,
188 sequence: 42,
189 published_at: 1700000000000,
190 topic: "test-topic".to_string(),
191 payload: Bytes::from("test payload"),
192 };
193
194 let encoded = codec.encode_envelope(&envelope).unwrap();
195 let decoded = codec.decode_envelope(&encoded).unwrap();
196
197 assert_eq!(decoded.publisher_id, envelope.publisher_id);
198 assert_eq!(decoded.sequence, envelope.sequence);
199 assert_eq!(decoded.published_at, envelope.published_at);
200 assert_eq!(decoded.topic, envelope.topic);
201 assert_eq!(decoded.payload, envelope.payload);
202
203 let frame_start = encoded.as_ptr() as usize;
204 let frame_end = frame_start + encoded.len();
205 let payload_start = decoded.payload.as_ptr() as usize;
206 let payload_end = payload_start + decoded.payload.len();
207 assert!(
208 (frame_start..=frame_end).contains(&payload_start) && payload_end <= frame_end,
209 "decoded payload must share the encoded frame's backing storage"
210 );
211 }
212
213 #[test]
214 fn test_msgpack_codec_borrowed_envelope_roundtrip() {
215 let codec = MsgpackCodec;
216 let payload = b"borrowed payload";
217
218 let borrowed = codec
219 .encode_envelope_parts(12345, 42, 1700000000000, "test-topic", payload)
220 .unwrap();
221 let owned = codec
222 .encode_envelope(&EventEnvelope {
223 publisher_id: 12345,
224 sequence: 42,
225 published_at: 1700000000000,
226 topic: "test-topic".to_string(),
227 payload: Bytes::from_static(payload),
228 })
229 .unwrap();
230 assert_eq!(borrowed, owned);
231
232 let decoded = codec.decode_envelope(&borrowed).unwrap();
233
234 assert_eq!(decoded.publisher_id, 12345);
235 assert_eq!(decoded.sequence, 42);
236 assert_eq!(decoded.published_at, 1700000000000);
237 assert_eq!(decoded.topic, "test-topic");
238 assert_eq!(decoded.payload.as_ref(), payload);
239 }
240
241 #[test]
242 fn test_msgpack_codec_envelope_identity_decode() {
243 let codec = MsgpackCodec;
244 let encoded = codec
245 .encode_envelope_parts(12345, 42, 1700000000000, "test-topic", b"payload")
246 .unwrap();
247
248 assert_eq!(
249 codec.decode_envelope_identity(&encoded).unwrap(),
250 (12345, 42)
251 );
252 }
253
254 #[test]
255 fn test_msgpack_codec_rejects_malformed_envelope() {
256 let codec = MsgpackCodec;
257
258 assert!(codec.decode_envelope(&Bytes::from_static(&[0xc1])).is_err());
259 assert!(
260 codec
261 .decode_envelope_identity(&Bytes::from_static(&[0xc1]))
262 .is_err()
263 );
264 }
265
266 #[test]
267 fn test_msgpack_codec_payload_roundtrip() {
268 let codec = MsgpackCodec;
269
270 let event = TestEvent {
271 worker_id: 123,
272 message: "hello world".to_string(),
273 };
274
275 let encoded = codec.encode_payload(&event).unwrap();
276 let decoded: TestEvent = codec.decode_payload(&encoded).unwrap();
277
278 assert_eq!(decoded, event);
279 }
280}