1use super::frame::{Flags, Frame, MessageKind, FRAME_HEADER_SIZE, MAX_FRAME_SIZE};
6
7pub const MAX_DECOMPRESSED_PAYLOAD: usize = 4 * (MAX_FRAME_SIZE as usize);
11
12fn decompress_bounded(on_wire: &[u8], payload_len: usize) -> Result<Vec<u8>, FrameError> {
13 use std::io::Read;
14 let limit = MAX_DECOMPRESSED_PAYLOAD as u64;
15 let unparseable = |e: std::io::Error| FrameError::PayloadTruncated {
16 expected: payload_len as u32,
20 available: e.to_string().len() as u32,
21 };
22 let decoder = zstd::stream::Decoder::new(on_wire).map_err(unparseable)?;
23 let mut plain = Vec::new();
24 decoder
27 .take(limit + 1)
28 .read_to_end(&mut plain)
29 .map_err(unparseable)?;
30 if plain.len() as u64 > limit {
31 return Err(FrameError::DecompressedTooLarge {
32 limit: MAX_DECOMPRESSED_PAYLOAD as u32,
33 });
34 }
35 Ok(plain)
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum FrameError {
40 Truncated,
41 InvalidLength(u32),
42 PayloadTruncated {
43 expected: u32,
44 available: u32,
45 },
46 UnknownKind(u8),
47 UnknownFlags(u8),
48 DecompressedTooLarge {
54 limit: u32,
55 },
56 FlagsNotAllowedForKind {
57 kind: u8,
58 flags: u8,
59 },
60}
61
62impl std::fmt::Display for FrameError {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 Self::Truncated => write!(f, "frame header truncated (< 16 bytes)"),
66 Self::InvalidLength(n) => write!(f, "frame length field invalid: {n}"),
67 Self::PayloadTruncated {
68 expected,
69 available,
70 } => write!(
71 f,
72 "frame payload truncated: expected {expected} bytes, got {available}"
73 ),
74 Self::UnknownKind(byte) => write!(f, "unknown message kind 0x{byte:02x}"),
75 Self::UnknownFlags(byte) => write!(f, "unknown flag bits 0x{byte:02x}"),
76 Self::DecompressedTooLarge { limit } => {
77 write!(f, "compressed payload inflates past the {limit}-byte limit")
78 }
79 Self::FlagsNotAllowedForKind { kind, flags } => write!(
80 f,
81 "flag bits 0x{flags:02x} not allowed on kind 0x{kind:02x}"
82 ),
83 }
84 }
85}
86
87impl std::error::Error for FrameError {}
88
89pub fn frame_len_from_header(header: &[u8; FRAME_HEADER_SIZE]) -> Result<usize, FrameError> {
90 let length = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
91 if length < FRAME_HEADER_SIZE as u32 || length > MAX_FRAME_SIZE {
92 return Err(FrameError::InvalidLength(length));
93 }
94 Ok(length as usize)
95}
96
97pub fn decode_frame_parts(
98 header: &[u8; FRAME_HEADER_SIZE],
99 payload: &[u8],
100) -> Result<Frame, FrameError> {
101 let length = frame_len_from_header(header)?;
102 let expected_payload_len = length - FRAME_HEADER_SIZE;
103 if payload.len() < expected_payload_len {
104 return Err(FrameError::PayloadTruncated {
105 expected: expected_payload_len as u32,
106 available: payload.len() as u32,
107 });
108 }
109
110 let mut bytes = Vec::with_capacity(length);
111 bytes.extend_from_slice(header);
112 bytes.extend_from_slice(&payload[..expected_payload_len]);
113 decode_frame(&bytes).map(|(frame, _)| frame)
114}
115
116pub fn encode_frame(frame: &Frame) -> Vec<u8> {
117 if frame.flags.contains(Flags::COMPRESSED) {
122 return encode_compressed(frame);
123 }
124 let total = frame.encoded_len() as usize;
125 let mut buf = Vec::with_capacity(total);
126 buf.extend_from_slice(&frame.encoded_len().to_le_bytes());
127 buf.push(frame.kind as u8);
128 buf.push(frame.flags.bits());
129 buf.extend_from_slice(&frame.stream_id.to_le_bytes());
130 buf.extend_from_slice(&frame.correlation_id.to_le_bytes());
131 buf.extend_from_slice(&frame.payload);
132 buf
133}
134
135fn encode_compressed(frame: &Frame) -> Vec<u8> {
136 let level = std::env::var("RED_REDWIRE_ZSTD_LEVEL")
140 .ok()
141 .and_then(|s| s.parse::<i32>().ok())
142 .unwrap_or(1);
143 let compressed = match zstd::stream::encode_all(frame.payload.as_slice(), level) {
144 Ok(buf) => buf,
145 Err(_) => {
146 let mut clone = frame.clone();
151 clone.flags = Flags::from_bits(clone.flags.bits() & !Flags::COMPRESSED.bits());
152 return encode_frame(&clone);
153 }
154 };
155 let total = (FRAME_HEADER_SIZE + compressed.len()) as u32;
156 let mut buf = Vec::with_capacity(total as usize);
157 buf.extend_from_slice(&total.to_le_bytes());
158 buf.push(frame.kind as u8);
159 buf.push(frame.flags.bits());
160 buf.extend_from_slice(&frame.stream_id.to_le_bytes());
161 buf.extend_from_slice(&frame.correlation_id.to_le_bytes());
162 buf.extend_from_slice(&compressed);
163 buf
164}
165
166pub fn decode_frame(bytes: &[u8]) -> Result<(Frame, usize), FrameError> {
167 if bytes.len() < FRAME_HEADER_SIZE {
168 return Err(FrameError::Truncated);
169 }
170 let mut header = [0u8; FRAME_HEADER_SIZE];
171 header.copy_from_slice(&bytes[..FRAME_HEADER_SIZE]);
172 let length = frame_len_from_header(&header)? as u32;
173 if (bytes.len() as u32) < length {
174 return Err(FrameError::PayloadTruncated {
175 expected: length,
176 available: bytes.len() as u32,
177 });
178 }
179 let kind = MessageKind::from_u8(bytes[4]).ok_or(FrameError::UnknownKind(bytes[4]))?;
180 let flag_bits = bytes[5];
181 const KNOWN_FLAGS: u8 = 0b0000_0011;
182 if flag_bits & !KNOWN_FLAGS != 0 {
183 return Err(FrameError::UnknownFlags(flag_bits));
184 }
185 let flags = Flags::from_bits(flag_bits);
186 if !kind.permits_flags(flags) {
191 return Err(FrameError::FlagsNotAllowedForKind {
192 kind: bytes[4],
193 flags: flag_bits,
194 });
195 }
196 let stream_id = u16::from_le_bytes([bytes[6], bytes[7]]);
197 let correlation_id = u64::from_le_bytes([
198 bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
199 ]);
200 let payload_len = (length as usize) - FRAME_HEADER_SIZE;
201 let on_wire = &bytes[FRAME_HEADER_SIZE..FRAME_HEADER_SIZE + payload_len];
202 let payload = if flags.contains(Flags::COMPRESSED) {
203 decompress_bounded(on_wire, payload_len)?
209 } else {
210 on_wire.to_vec()
211 };
212 Ok((
213 Frame {
214 kind,
215 flags,
218 stream_id,
219 correlation_id,
220 payload,
221 },
222 length as usize,
223 ))
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 fn round_trip(frame: Frame) {
231 let bytes = encode_frame(&frame);
232 let (decoded, consumed) = decode_frame(&bytes).expect("decode");
233 assert_eq!(consumed, bytes.len());
234 assert_eq!(decoded, frame);
235 }
236
237 #[test]
238 fn round_trip_empty_payload() {
239 round_trip(Frame::new(MessageKind::Ping, 1, vec![]));
240 }
241
242 #[test]
243 fn frame_len_from_header_validates_bounds() {
244 let mut header = [0u8; FRAME_HEADER_SIZE];
245 header[..4].copy_from_slice(&(FRAME_HEADER_SIZE as u32).to_le_bytes());
246 assert_eq!(frame_len_from_header(&header).unwrap(), FRAME_HEADER_SIZE);
247
248 header[..4].copy_from_slice(&15u32.to_le_bytes());
249 assert_eq!(
250 frame_len_from_header(&header),
251 Err(FrameError::InvalidLength(15))
252 );
253
254 header[..4].copy_from_slice(&(MAX_FRAME_SIZE + 1).to_le_bytes());
255 assert_eq!(
256 frame_len_from_header(&header),
257 Err(FrameError::InvalidLength(MAX_FRAME_SIZE + 1))
258 );
259 }
260
261 #[test]
262 fn round_trip_with_payload() {
263 round_trip(Frame::new(MessageKind::Query, 42, b"SELECT 1".to_vec()));
264 }
265
266 #[test]
267 fn decode_frame_parts_matches_full_buffer_decode() {
268 let frame = Frame::new(MessageKind::Result, 42, br#"{"ok":true}"#.to_vec());
269 let bytes = encode_frame(&frame);
270 let mut header = [0u8; FRAME_HEADER_SIZE];
271 header.copy_from_slice(&bytes[..FRAME_HEADER_SIZE]);
272 let payload = &bytes[FRAME_HEADER_SIZE..];
273
274 let decoded = decode_frame_parts(&header, payload).expect("decode parts");
275 assert_eq!(decoded, frame);
276 }
277
278 #[test]
279 fn round_trip_with_stream_and_flags() {
280 let frame = Frame::new(MessageKind::Result, 999, vec![0xab; 256])
281 .with_stream(7)
282 .with_flags(Flags::COMPRESSED | Flags::MORE_FRAMES);
283 round_trip(frame);
284 }
285
286 #[test]
287 fn truncated_header_rejected() {
288 assert_eq!(decode_frame(&[]), Err(FrameError::Truncated));
289 assert_eq!(decode_frame(&[0; 15]), Err(FrameError::Truncated));
290 }
291
292 #[test]
293 fn length_below_header_rejected() {
294 let mut bytes = vec![0u8; 16];
295 bytes[..4].copy_from_slice(&15u32.to_le_bytes());
296 assert!(matches!(
297 decode_frame(&bytes),
298 Err(FrameError::InvalidLength(15))
299 ));
300 }
301
302 #[test]
303 fn unknown_kind_rejected() {
304 let mut bytes = vec![0u8; 16];
305 bytes[..4].copy_from_slice(&16u32.to_le_bytes());
306 bytes[4] = 0xff;
307 assert_eq!(decode_frame(&bytes), Err(FrameError::UnknownKind(0xff)));
308 }
309
310 #[test]
311 fn unknown_flag_bits_rejected() {
312 let mut bytes = vec![0u8; 16];
313 bytes[..4].copy_from_slice(&16u32.to_le_bytes());
314 bytes[4] = MessageKind::Ping as u8;
315 bytes[5] = 0b1000_0000;
316 assert!(matches!(
317 decode_frame(&bytes),
318 Err(FrameError::UnknownFlags(_))
319 ));
320 }
321
322 #[test]
323 fn flags_not_allowed_for_kind_rejected() {
324 let mut bytes = vec![0u8; 16];
328 bytes[..4].copy_from_slice(&16u32.to_le_bytes());
329 bytes[4] = MessageKind::Ping as u8;
330 bytes[5] = Flags::COMPRESSED.bits();
331 match decode_frame(&bytes) {
332 Err(FrameError::FlagsNotAllowedForKind { kind, flags }) => {
333 assert_eq!(kind, MessageKind::Ping as u8);
334 assert_eq!(flags, Flags::COMPRESSED.bits());
335 }
336 other => panic!("expected FlagsNotAllowedForKind, got {other:?}"),
337 }
338 }
339
340 #[test]
341 fn streaming_decode_two_frames_back_to_back() {
342 let f1 = Frame::new(MessageKind::Query, 1, b"a".to_vec());
343 let f2 = Frame::new(MessageKind::Query, 2, b"b".to_vec());
344 let mut buf = encode_frame(&f1);
345 buf.extend(encode_frame(&f2));
346 let (got1, n1) = decode_frame(&buf).unwrap();
347 let (got2, _n2) = decode_frame(&buf[n1..]).unwrap();
348 assert_eq!(got1, f1);
349 assert_eq!(got2, f2);
350 }
351
352 #[test]
353 fn compressed_round_trip_recovers_plaintext() {
354 let payload = b"abcabcabcabc".repeat(100);
356 let frame =
357 Frame::new(MessageKind::Result, 7, payload.clone()).with_flags(Flags::COMPRESSED);
358 let bytes = encode_frame(&frame);
359 assert!(
361 bytes.len() < FRAME_HEADER_SIZE + payload.len(),
362 "compressed frame ({}) must be smaller than plaintext payload ({})",
363 bytes.len(),
364 payload.len(),
365 );
366 let (decoded, _) = decode_frame(&bytes).expect("decode compressed");
367 assert_eq!(decoded.payload, payload);
368 assert!(decoded.flags.contains(Flags::COMPRESSED));
369 }
370
371 #[test]
372 fn output_stream_lifecycle_envelopes_round_trip() {
373 let open = Frame::new(
379 MessageKind::OpenStream,
380 10,
381 br#"{"sql":"SELECT 1","opts":{}}"#.to_vec(),
382 )
383 .with_stream(7);
384 round_trip(open.clone());
385 assert_eq!(encode_frame(&open)[4], 0x29);
386
387 let ack = Frame::new(
388 MessageKind::OpenAck,
389 10,
390 br#"{"lease_handle":"42","resumable":false,"snapshot_lsn":1234}"#.to_vec(),
391 )
392 .with_stream(7);
393 round_trip(ack.clone());
394 assert_eq!(encode_frame(&ack)[4], 0x2A);
395
396 let chunk = Frame::new(
397 MessageKind::StreamChunk,
398 10,
399 br#"{"seq":0,"rows":[{"a":1}],"terminal":false}"#.to_vec(),
400 )
401 .with_stream(7);
402 round_trip(chunk.clone());
403 assert_eq!(encode_frame(&chunk)[4], 0x2B);
404
405 let serr = Frame::new(
406 MessageKind::StreamError,
407 10,
408 br#"{"code":"unknown_stream","message":"x"}"#.to_vec(),
409 )
410 .with_stream(7);
411 round_trip(serr.clone());
412 assert_eq!(encode_frame(&serr)[4], 0x2C);
413
414 let end = Frame::new(
415 MessageKind::StreamEnd,
416 10,
417 br#"{"stats":{"row_count":1}}"#.to_vec(),
418 )
419 .with_stream(7);
420 round_trip(end.clone());
421 assert_eq!(encode_frame(&end)[4], 0x25);
422
423 let cancel = Frame::new(
424 MessageKind::StreamCancel,
425 10,
426 br#"{"reason":"client-abort"}"#.to_vec(),
427 )
428 .with_stream(7);
429 round_trip(cancel.clone());
430 assert_eq!(encode_frame(&cancel)[4], 0x2D);
431 }
432
433 #[test]
434 fn input_stream_envelopes_round_trip() {
435 let open_in = Frame::new(
444 MessageKind::OpenStream,
445 20,
446 br#"{"direction":"in","target":"t","columns":["id","name"]}"#.to_vec(),
447 )
448 .with_stream(5);
449 round_trip(open_in.clone());
450 assert_eq!(encode_frame(&open_in)[4], 0x29);
451
452 let chunk_in = Frame::new(
456 MessageKind::StreamChunk,
457 20,
458 br#"{"seq":0,"rows":[{"id":1,"name":"a"}],"terminal":false}"#.to_vec(),
459 )
460 .with_stream(5);
461 round_trip(chunk_in.clone());
462 assert_eq!(encode_frame(&chunk_in)[4], 0x2B);
463
464 let chunk_terminal = Frame::new(
466 MessageKind::StreamChunk,
467 20,
468 br#"{"seq":2,"rows":[],"terminal":true}"#.to_vec(),
469 )
470 .with_stream(5);
471 round_trip(chunk_terminal.clone());
472 assert_eq!(encode_frame(&chunk_terminal)[4], 0x2B);
473
474 let end = Frame::new(
476 MessageKind::StreamEnd,
477 20,
478 br#"{"stats":{"row_count":3,"chunk_count":2,"committed_rid":42,"snapshot_lsn":40,"cancelled":false}}"#.to_vec(),
479 )
480 .with_stream(5);
481 round_trip(end.clone());
482 assert_eq!(encode_frame(&end)[4], 0x25);
483
484 let serr = Frame::new(
486 MessageKind::StreamError,
487 20,
488 br#"{"code":"invalid_row","message":"x","chunk_seq":1,"recoverable_rid":41}"#.to_vec(),
489 )
490 .with_stream(5);
491 round_trip(serr.clone());
492 assert_eq!(encode_frame(&serr)[4], 0x2C);
493 }
494
495 #[test]
496 fn queue_wait_envelopes_round_trip() {
497 let open = Frame::new(
503 MessageKind::QueueWaitOpen,
504 10,
505 br#"{"queue":"jobs","consumer":"w1","count":1,"wait_ms":5000}"#.to_vec(),
506 )
507 .with_stream(3);
508 round_trip(open.clone());
509 assert_eq!(encode_frame(&open)[4], 0x2E);
510
511 let push = Frame::new(
512 MessageKind::QueueEventPush,
513 10,
514 br#"{"message_id":"42","payload":{"hello":"world"},"consumer":"w1","delivery_count":1}"#
515 .to_vec(),
516 )
517 .with_stream(3);
518 round_trip(push.clone());
519 assert_eq!(encode_frame(&push)[4], 0x2F);
520 }
521
522 #[test]
523 fn uncompressed_frame_decodes_unchanged_when_flag_unset() {
524 let payload = b"hello world".to_vec();
525 let frame = Frame::new(MessageKind::Result, 1, payload.clone());
526 let bytes = encode_frame(&frame);
527 let (decoded, _) = decode_frame(&bytes).unwrap();
528 assert_eq!(decoded.payload, payload);
529 assert!(!decoded.flags.contains(Flags::COMPRESSED));
530 }
531
532 #[test]
533 fn compressed_payload_that_inflates_past_the_cap_is_refused() {
534 let plain = vec![0u8; MAX_DECOMPRESSED_PAYLOAD + 1024];
538 let compressed = zstd::stream::encode_all(plain.as_slice(), 1).expect("compress");
539 assert!(
540 compressed.len() < MAX_FRAME_SIZE as usize,
541 "the bomb must fit in one legal frame to be a realistic attack"
542 );
543
544 let total = (FRAME_HEADER_SIZE + compressed.len()) as u32;
545 let mut bytes = Vec::with_capacity(total as usize);
546 bytes.extend_from_slice(&total.to_le_bytes());
547 bytes.push(MessageKind::Query as u8);
548 bytes.push(Flags::COMPRESSED.bits());
549 bytes.extend_from_slice(&0u16.to_le_bytes());
550 bytes.extend_from_slice(&0u64.to_le_bytes());
551 bytes.extend_from_slice(&compressed);
552
553 match decode_frame(&bytes) {
554 Err(FrameError::DecompressedTooLarge { limit }) => {
555 assert_eq!(limit as usize, MAX_DECOMPRESSED_PAYLOAD);
556 }
557 other => panic!("expected DecompressedTooLarge, got {other:?}"),
558 }
559 }
560}