1use crate::digest::sha256_digest;
9use serde::de::DeserializeOwned;
10use serde::{Deserialize, Serialize};
11use serde_json::value::RawValue;
12use thiserror::Error;
13
14#[derive(Debug, Deserialize)]
21pub struct EnvelopeProbe {
22 pub kind: String,
24 pub format_version: u32,
26}
27
28#[derive(Debug, Error)]
32#[non_exhaustive]
33pub enum EnvelopeCodecError {
34 #[error("failed to encode envelope payload: {0}")]
36 PayloadEncode(String),
37 #[error("failed to encode envelope document: {0}")]
39 EnvelopeEncode(String),
40 #[error("failed to decode envelope document: {0}")]
42 EnvelopeDecode(String),
43 #[error("failed to decode envelope payload: {0}")]
45 PayloadDecode(String),
46 #[error("failed to compress envelope: {0}")]
48 Compress(String),
49 #[error("failed to decompress envelope: {0}")]
51 Decompress(String),
52 #[error("decompressed WAL document exceeds the {max_bytes}-byte limit")]
54 WalSegmentTooLarge {
55 max_bytes: usize,
57 },
58 #[error(
60 "invalid wal inline content in commit `{seq}` for `content_id` `{content_id}`: {reason}"
61 )]
62 InvalidWalInlineContent {
63 seq: crate::ChangeSeq,
65 content_id: crate::ContentId,
67 reason: &'static str,
69 },
70 #[error("unknown envelope kind `{found}`")]
72 UnknownKind {
73 found: String,
75 },
76 #[error("envelope kind mismatch: expected `{expected}`, found `{found}`")]
78 KindMismatch {
79 expected: String,
81 found: String,
83 },
84 #[error(
86 "unsupported `{kind}` envelope format version `{found}`: \
87 this build supports `{supported}`"
88 )]
89 UnsupportedFormatVersion {
90 kind: String,
92 found: u32,
94 supported: u32,
96 },
97 #[error("envelope payload checksum mismatch: expected `{expected}`, actual `{actual}`")]
99 ChecksumMismatch {
100 expected: String,
102 actual: String,
104 },
105}
106
107pub fn verify_kind(expected: &str, found: &str) -> Result<(), EnvelopeCodecError> {
109 if found != expected {
110 return Err(EnvelopeCodecError::KindMismatch {
111 expected: expected.to_owned(),
112 found: found.to_owned(),
113 });
114 }
115 Ok(())
116}
117
118pub fn verify_version(kind: &str, found: u32, supported: u32) -> Result<(), EnvelopeCodecError> {
121 if found != supported {
122 return Err(EnvelopeCodecError::UnsupportedFormatVersion {
123 kind: kind.to_owned(),
124 found,
125 supported,
126 });
127 }
128 Ok(())
129}
130
131pub fn verify_payload_checksum(
133 expected: &str,
134 payload_bytes: &[u8],
135) -> Result<(), EnvelopeCodecError> {
136 let actual = sha256_digest(payload_bytes);
137 if actual != expected {
138 return Err(EnvelopeCodecError::ChecksumMismatch {
139 expected: expected.to_owned(),
140 actual,
141 });
142 }
143 Ok(())
144}
145
146#[derive(Serialize, Deserialize)]
151#[serde(deny_unknown_fields)]
152struct JsonEnvelopeDocument {
153 kind: String,
154 format_version: u32,
155 payload_checksum: String,
156 payload: Box<RawValue>,
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct VerifiedEnvelope<T> {
167 pub(crate) payload_checksum: String,
168 pub(crate) payload: T,
169}
170
171impl<T> VerifiedEnvelope<T> {
172 pub fn payload_checksum(&self) -> &str {
174 &self.payload_checksum
175 }
176 pub fn payload(&self) -> &T {
178 &self.payload
179 }
180 pub fn into_payload(self) -> T {
182 self.payload
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct EncodedEnvelope<T> {
192 pub(crate) envelope: VerifiedEnvelope<T>,
193 pub(crate) bytes: Vec<u8>,
194 pub(crate) document_len: usize,
195}
196
197impl<T> EncodedEnvelope<T> {
198 pub fn document_len(&self) -> usize {
200 self.document_len
201 }
202
203 pub fn envelope(&self) -> &VerifiedEnvelope<T> {
205 &self.envelope
206 }
207 pub fn as_bytes(&self) -> &[u8] {
209 &self.bytes
210 }
211 pub fn into_bytes(self) -> Vec<u8> {
213 self.bytes
214 }
215 pub fn into_envelope(self) -> VerifiedEnvelope<T> {
217 self.envelope
218 }
219 pub fn into_parts(self) -> (VerifiedEnvelope<T>, Vec<u8>) {
221 (self.envelope, self.bytes)
222 }
223}
224
225pub fn encode_json_envelope<T: Serialize>(
227 kind: &str,
228 format_version: u32,
229 payload: T,
230) -> Result<EncodedEnvelope<T>, EnvelopeCodecError> {
231 let payload_json = serde_json::to_string(&payload)
232 .map_err(|err| EnvelopeCodecError::PayloadEncode(err.to_string()))?;
233 let payload_checksum = sha256_digest(payload_json.as_bytes());
234 let document = JsonEnvelopeDocument {
235 kind: kind.to_owned(),
236 format_version,
237 payload_checksum: payload_checksum.clone(),
238 payload: RawValue::from_string(payload_json)
239 .map_err(|err| EnvelopeCodecError::PayloadEncode(err.to_string()))?,
240 };
241 let bytes = serde_json::to_vec(&document)
242 .map_err(|err| EnvelopeCodecError::EnvelopeEncode(err.to_string()))?;
243 Ok(EncodedEnvelope {
244 envelope: VerifiedEnvelope {
245 payload_checksum,
246 payload,
247 },
248 document_len: bytes.len(),
249 bytes,
250 })
251}
252
253pub fn decode_json_envelope<T: DeserializeOwned>(
260 bytes: &[u8],
261 supported_version: u32,
262 classify_kind: impl FnOnce(&str) -> Result<(), EnvelopeCodecError>,
263) -> Result<VerifiedEnvelope<T>, EnvelopeCodecError> {
264 let probe: EnvelopeProbe = serde_json::from_slice(bytes)
265 .map_err(|err| EnvelopeCodecError::EnvelopeDecode(err.to_string()))?;
266 classify_kind(&probe.kind)?;
267 verify_version(&probe.kind, probe.format_version, supported_version)?;
268 let document: JsonEnvelopeDocument = serde_json::from_slice(bytes)
269 .map_err(|err| EnvelopeCodecError::EnvelopeDecode(err.to_string()))?;
270 decode_json_envelope_payload(document)
271}
272
273fn decode_json_envelope_payload<T: DeserializeOwned>(
274 document: JsonEnvelopeDocument,
275) -> Result<VerifiedEnvelope<T>, EnvelopeCodecError> {
276 verify_payload_checksum(
277 &document.payload_checksum,
278 document.payload.get().as_bytes(),
279 )?;
280 let payload: T = serde_json::from_str(document.payload.get())
281 .map_err(|err| EnvelopeCodecError::PayloadDecode(err.to_string()))?;
282
283 Ok(VerifiedEnvelope {
284 payload_checksum: document.payload_checksum,
285 payload,
286 })
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use std::cell::Cell;
293
294 #[test]
295 fn encoding_serializes_the_payload_once_and_checksums_those_bytes() {
296 struct CountedPayload<'a>(&'a Cell<usize>);
297 impl Serialize for CountedPayload<'_> {
298 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
299 self.0.set(self.0.get() + 1);
300 serializer.serialize_u64(42)
301 }
302 }
303 let calls = Cell::new(0);
304 let encoded = encode_json_envelope("test", 1, CountedPayload(&calls)).expect("encode");
305 let document: JsonEnvelopeDocument =
306 serde_json::from_slice(encoded.as_bytes()).expect("document");
307 assert_eq!(calls.get(), 1);
308 assert_eq!(document.payload.get(), "42");
309 assert_eq!(encoded.envelope().payload_checksum(), sha256_digest(b"42"));
310 assert_eq!(
311 document.payload_checksum,
312 encoded.envelope().payload_checksum()
313 );
314 }
315
316 #[test]
317 fn decoding_checks_the_stored_payload_including_noncanonical_whitespace() {
318 let payload = r#"{ "value" : 42 }"#;
319 let checksum = sha256_digest(payload.as_bytes());
320 let bytes = format!(
321 r#"{{"kind":"test","format_version":1,"payload_checksum":"{checksum}","payload":{payload}}}"#
322 );
323 let decoded: VerifiedEnvelope<serde_json::Value> =
324 decode_json_envelope(bytes.as_bytes(), 1, |kind| verify_kind("test", kind))
325 .expect("decode exact stored bytes");
326 assert_eq!(decoded.payload_checksum(), checksum);
327 let successor =
328 encode_json_envelope("test", 1, decoded.into_payload()).expect("canonical encoding");
329 assert_ne!(successor.envelope().payload_checksum(), checksum);
330 let reread: VerifiedEnvelope<serde_json::Value> =
331 decode_json_envelope(successor.as_bytes(), 1, |kind| verify_kind("test", kind))
332 .expect("decode canonical bytes");
333 assert_eq!(&reread, successor.envelope());
334 }
335}