Skip to main content

truffle_core/envelope/
codec.rs

1//! Envelope codecs — pluggable serialization for [`Envelope`].
2//!
3//! The default codec is [`JsonCodec`], which produces human-readable JSON.
4//! Future codecs (MessagePack, Protobuf) can be added by implementing the
5//! [`EnvelopeCodec`] trait.
6
7use super::{Envelope, EnvelopeError};
8
9// ---------------------------------------------------------------------------
10// EnvelopeCodec trait
11// ---------------------------------------------------------------------------
12
13/// A codec that serializes and deserializes [`Envelope`]s.
14///
15/// Implementations must be `Send + Sync` so they can be shared across
16/// async tasks behind an `Arc`.
17///
18/// # Default implementation
19///
20/// [`JsonCodec`] — produces compact JSON. Suitable for debugging and
21/// low-to-medium throughput applications.
22pub trait EnvelopeCodec: Send + Sync {
23    /// Encode an envelope into a byte buffer.
24    fn encode(&self, envelope: &Envelope) -> Result<Vec<u8>, EnvelopeError>;
25
26    /// Decode an envelope from a byte buffer.
27    fn decode(&self, data: &[u8]) -> Result<Envelope, EnvelopeError>;
28}
29
30// ---------------------------------------------------------------------------
31// JsonCodec
32// ---------------------------------------------------------------------------
33
34/// JSON envelope codec (the default).
35///
36/// Produces compact (non-pretty-printed) JSON. Unknown fields in the input
37/// are silently ignored during deserialization for forward compatibility.
38#[derive(Debug, Clone, Default)]
39pub struct JsonCodec;
40
41impl EnvelopeCodec for JsonCodec {
42    fn encode(&self, envelope: &Envelope) -> Result<Vec<u8>, EnvelopeError> {
43        serde_json::to_vec(envelope).map_err(EnvelopeError::Serialization)
44    }
45
46    fn decode(&self, data: &[u8]) -> Result<Envelope, EnvelopeError> {
47        serde_json::from_slice(data).map_err(EnvelopeError::Deserialization)
48    }
49}
50
51// ---------------------------------------------------------------------------
52// Tests
53// ---------------------------------------------------------------------------
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use serde_json::json;
59
60    #[test]
61    fn test_json_codec_encode_decode_roundtrip() {
62        let codec = JsonCodec;
63
64        let envelope = Envelope::new("chat", "message", json!({"text": "hello world"}));
65        let encoded = codec.encode(&envelope).expect("encode");
66        let decoded = codec.decode(&encoded).expect("decode");
67
68        assert_eq!(decoded.namespace, "chat");
69        assert_eq!(decoded.msg_type, "message");
70        assert_eq!(decoded.payload["text"], "hello world");
71    }
72
73    #[test]
74    fn test_json_codec_preserves_all_fields() {
75        let codec = JsonCodec;
76
77        let envelope = Envelope {
78            namespace: "ft".into(),
79            msg_type: "offer".into(),
80            payload: json!({"file": "data.bin"}),
81            timestamp: Some(1711234567890),
82            from: Some("peer-abc".into()),
83        };
84
85        let encoded = codec.encode(&envelope).expect("encode");
86        let decoded = codec.decode(&encoded).expect("decode");
87
88        assert_eq!(decoded.namespace, "ft");
89        assert_eq!(decoded.msg_type, "offer");
90        assert_eq!(decoded.payload["file"], "data.bin");
91        assert_eq!(decoded.timestamp, Some(1711234567890));
92        assert_eq!(decoded.from.as_deref(), Some("peer-abc"));
93    }
94
95    #[test]
96    fn test_json_codec_unknown_fields_forward_compat() {
97        let codec = JsonCodec;
98
99        // JSON with extra fields a future version might add.
100        let json_bytes = br#"{
101            "namespace": "v2",
102            "msg_type": "new_feature",
103            "payload": {},
104            "priority": "high",
105            "trace_id": "abc-123"
106        }"#;
107
108        let decoded = codec
109            .decode(json_bytes)
110            .expect("should decode with unknown fields");
111        assert_eq!(decoded.namespace, "v2");
112        assert_eq!(decoded.msg_type, "new_feature");
113    }
114
115    #[test]
116    fn test_json_codec_decode_error() {
117        let codec = JsonCodec;
118        let result = codec.decode(b"{{invalid");
119        assert!(result.is_err());
120    }
121
122    #[test]
123    fn test_json_codec_empty_payload() {
124        let codec = JsonCodec;
125
126        let envelope = Envelope::new("ping", "request", json!(null));
127        let encoded = codec.encode(&envelope).expect("encode");
128        let decoded = codec.decode(&encoded).expect("decode");
129
130        assert!(decoded.payload.is_null());
131    }
132
133    #[test]
134    fn test_json_codec_is_send_sync() {
135        fn assert_send_sync<T: Send + Sync>() {}
136        assert_send_sync::<JsonCodec>();
137    }
138
139    #[test]
140    fn test_json_codec_trait_object() {
141        // Verify EnvelopeCodec can be used as a trait object behind Arc.
142        let codec: std::sync::Arc<dyn EnvelopeCodec> = std::sync::Arc::new(JsonCodec);
143        let envelope = Envelope::new("test", "msg", json!("data"));
144        let encoded = codec.encode(&envelope).expect("encode");
145        let decoded = codec.decode(&encoded).expect("decode");
146        assert_eq!(decoded.namespace, "test");
147    }
148
149    // ── Property tests (review: protocol fuzzing) ───────────────────────
150
151    use proptest::prelude::*;
152
153    proptest! {
154        /// Arbitrary bytes must never panic the decoder — the worst
155        /// acceptable outcome is a clean Deserialization error.
156        #[test]
157        fn decode_never_panics_on_arbitrary_bytes(
158            data in proptest::collection::vec(any::<u8>(), 0..2048)
159        ) {
160            let _ = JsonCodec.decode(&data);
161        }
162
163        /// Valid envelopes roundtrip losslessly whatever the field contents
164        /// (unicode, quotes, control characters, ...).
165        #[test]
166        fn roundtrip_arbitrary_fields(
167            ns in ".{0,64}",
168            mt in ".{0,64}",
169            text in ".{0,256}"
170        ) {
171            let envelope = Envelope::new(&ns, &mt, json!({ "text": text.clone() }));
172            let decoded = JsonCodec
173                .decode(&JsonCodec.encode(&envelope).unwrap())
174                .unwrap();
175            prop_assert_eq!(decoded.namespace, ns);
176            prop_assert_eq!(decoded.msg_type, mt);
177            prop_assert_eq!(&decoded.payload["text"], &serde_json::Value::from(text));
178        }
179    }
180}