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 encode_uncompressed_frame_into(frame, &mut buf);
127 buf
128}
129
130pub fn encode_uncompressed_frame_into(frame: &Frame, buf: &mut Vec<u8>) {
134 encode_uncompressed_payload_into(frame, &frame.payload, buf);
135}
136
137pub(crate) fn encode_uncompressed_payload_into(frame: &Frame, payload: &[u8], buf: &mut Vec<u8>) {
138 assert!(!frame.flags.contains(Flags::COMPRESSED));
139 let length = u32::try_from(FRAME_HEADER_SIZE + payload.len()).expect("validated frame size");
140 buf.extend_from_slice(&length.to_le_bytes());
141 buf.push(frame.kind as u8);
142 buf.push(frame.flags.bits());
143 buf.extend_from_slice(&frame.stream_id.to_le_bytes());
144 buf.extend_from_slice(&frame.correlation_id.to_le_bytes());
145 buf.extend_from_slice(payload);
146}
147
148fn encode_compressed(frame: &Frame) -> Vec<u8> {
149 let level = std::env::var("RED_REDWIRE_ZSTD_LEVEL")
153 .ok()
154 .and_then(|s| s.parse::<i32>().ok())
155 .unwrap_or(1);
156 let compressed = match zstd::stream::encode_all(frame.payload.as_slice(), level) {
157 Ok(buf) => buf,
158 Err(_) => {
159 let mut clone = frame.clone();
164 clone.flags = Flags::from_bits(clone.flags.bits() & !Flags::COMPRESSED.bits());
165 return encode_frame(&clone);
166 }
167 };
168 let total = (FRAME_HEADER_SIZE + compressed.len()) as u32;
169 let mut buf = Vec::with_capacity(total as usize);
170 buf.extend_from_slice(&total.to_le_bytes());
171 buf.push(frame.kind as u8);
172 buf.push(frame.flags.bits());
173 buf.extend_from_slice(&frame.stream_id.to_le_bytes());
174 buf.extend_from_slice(&frame.correlation_id.to_le_bytes());
175 buf.extend_from_slice(&compressed);
176 buf
177}
178
179pub fn decode_frame(bytes: &[u8]) -> Result<(Frame, usize), FrameError> {
180 if bytes.len() < FRAME_HEADER_SIZE {
181 return Err(FrameError::Truncated);
182 }
183 let mut header = [0u8; FRAME_HEADER_SIZE];
184 header.copy_from_slice(&bytes[..FRAME_HEADER_SIZE]);
185 let length = frame_len_from_header(&header)? as u32;
186 if (bytes.len() as u32) < length {
187 return Err(FrameError::PayloadTruncated {
188 expected: length,
189 available: bytes.len() as u32,
190 });
191 }
192 let kind = MessageKind::from_u8(bytes[4]).ok_or(FrameError::UnknownKind(bytes[4]))?;
193 let flag_bits = bytes[5];
194 const KNOWN_FLAGS: u8 = 0b0000_0011;
195 if flag_bits & !KNOWN_FLAGS != 0 {
196 return Err(FrameError::UnknownFlags(flag_bits));
197 }
198 let flags = Flags::from_bits(flag_bits);
199 if !kind.permits_flags(flags) {
204 return Err(FrameError::FlagsNotAllowedForKind {
205 kind: bytes[4],
206 flags: flag_bits,
207 });
208 }
209 let stream_id = u16::from_le_bytes([bytes[6], bytes[7]]);
210 let correlation_id = u64::from_le_bytes([
211 bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
212 ]);
213 let payload_len = (length as usize) - FRAME_HEADER_SIZE;
214 let on_wire = &bytes[FRAME_HEADER_SIZE..FRAME_HEADER_SIZE + payload_len];
215 let payload = if flags.contains(Flags::COMPRESSED) {
216 decompress_bounded(on_wire, payload_len)?
222 } else {
223 on_wire.to_vec()
224 };
225 Ok((
226 Frame {
227 kind,
228 flags,
231 stream_id,
232 correlation_id,
233 payload,
234 },
235 length as usize,
236 ))
237}
238
239#[cfg(test)]
240mod tests {
241 #[test]
242 fn caller_owned_encoding_matches_golden_header_and_preserves_prefix() {
243 use super::*;
244 let mut frame = Frame::new(MessageKind::Result, 0x0807060504030201, vec![0xaa, 0xbb]);
245 frame.stream_id = 0x0201;
246 let mut bytes = Vec::with_capacity(21);
247 bytes.extend_from_slice(b"pre");
248 encode_uncompressed_frame_into(&frame, &mut bytes);
249 assert_eq!(&bytes[..3], b"pre");
250 assert_eq!(
251 &bytes[3..],
252 &[
253 18,
254 0,
255 0,
256 0,
257 MessageKind::Result as u8,
258 0,
259 1,
260 2,
261 1,
262 2,
263 3,
264 4,
265 5,
266 6,
267 7,
268 8,
269 0xaa,
270 0xbb
271 ]
272 );
273 assert_eq!(bytes.capacity(), 21);
274 assert_eq!(&bytes[3..], encode_frame(&frame));
275 let (decoded, used) = decode_frame(&bytes[3..]).unwrap();
276 assert_eq!(used, 18);
277 assert_eq!(decoded.payload, frame.payload);
278 assert_eq!(decoded.stream_id, frame.stream_id);
279 assert_eq!(decoded.correlation_id, frame.correlation_id);
280 }
281
282 use super::*;
283
284 fn round_trip(frame: Frame) {
285 let bytes = encode_frame(&frame);
286 let (decoded, consumed) = decode_frame(&bytes).expect("decode");
287 assert_eq!(consumed, bytes.len());
288 assert_eq!(decoded, frame);
289 }
290
291 #[test]
292 fn round_trip_empty_payload() {
293 round_trip(Frame::new(MessageKind::Ping, 1, vec![]));
294 }
295
296 #[test]
297 fn frame_len_from_header_validates_bounds() {
298 let mut header = [0u8; FRAME_HEADER_SIZE];
299 header[..4].copy_from_slice(&(FRAME_HEADER_SIZE as u32).to_le_bytes());
300 assert_eq!(frame_len_from_header(&header).unwrap(), FRAME_HEADER_SIZE);
301
302 header[..4].copy_from_slice(&15u32.to_le_bytes());
303 assert_eq!(
304 frame_len_from_header(&header),
305 Err(FrameError::InvalidLength(15))
306 );
307
308 header[..4].copy_from_slice(&(MAX_FRAME_SIZE + 1).to_le_bytes());
309 assert_eq!(
310 frame_len_from_header(&header),
311 Err(FrameError::InvalidLength(MAX_FRAME_SIZE + 1))
312 );
313 }
314
315 #[test]
316 fn round_trip_with_payload() {
317 round_trip(Frame::new(MessageKind::Query, 42, b"SELECT 1".to_vec()));
318 }
319
320 #[test]
321 fn decode_frame_parts_matches_full_buffer_decode() {
322 let frame = Frame::new(MessageKind::Result, 42, br#"{"ok":true}"#.to_vec());
323 let bytes = encode_frame(&frame);
324 let mut header = [0u8; FRAME_HEADER_SIZE];
325 header.copy_from_slice(&bytes[..FRAME_HEADER_SIZE]);
326 let payload = &bytes[FRAME_HEADER_SIZE..];
327
328 let decoded = decode_frame_parts(&header, payload).expect("decode parts");
329 assert_eq!(decoded, frame);
330 }
331
332 #[test]
333 fn round_trip_with_stream_and_flags() {
334 let frame = Frame::new(MessageKind::Result, 999, vec![0xab; 256])
335 .with_stream(7)
336 .with_flags(Flags::COMPRESSED | Flags::MORE_FRAMES);
337 round_trip(frame);
338 }
339
340 #[test]
341 fn truncated_header_rejected() {
342 assert_eq!(decode_frame(&[]), Err(FrameError::Truncated));
343 assert_eq!(decode_frame(&[0; 15]), Err(FrameError::Truncated));
344 }
345
346 #[test]
347 fn length_below_header_rejected() {
348 let mut bytes = vec![0u8; 16];
349 bytes[..4].copy_from_slice(&15u32.to_le_bytes());
350 assert!(matches!(
351 decode_frame(&bytes),
352 Err(FrameError::InvalidLength(15))
353 ));
354 }
355
356 #[test]
357 fn unknown_kind_rejected() {
358 let mut bytes = vec![0u8; 16];
359 bytes[..4].copy_from_slice(&16u32.to_le_bytes());
360 bytes[4] = 0xff;
361 assert_eq!(decode_frame(&bytes), Err(FrameError::UnknownKind(0xff)));
362 }
363
364 #[test]
365 fn unknown_flag_bits_rejected() {
366 let mut bytes = vec![0u8; 16];
367 bytes[..4].copy_from_slice(&16u32.to_le_bytes());
368 bytes[4] = MessageKind::Ping as u8;
369 bytes[5] = 0b1000_0000;
370 assert!(matches!(
371 decode_frame(&bytes),
372 Err(FrameError::UnknownFlags(_))
373 ));
374 }
375
376 #[test]
377 fn flags_not_allowed_for_kind_rejected() {
378 let mut bytes = vec![0u8; 16];
382 bytes[..4].copy_from_slice(&16u32.to_le_bytes());
383 bytes[4] = MessageKind::Ping as u8;
384 bytes[5] = Flags::COMPRESSED.bits();
385 match decode_frame(&bytes) {
386 Err(FrameError::FlagsNotAllowedForKind { kind, flags }) => {
387 assert_eq!(kind, MessageKind::Ping as u8);
388 assert_eq!(flags, Flags::COMPRESSED.bits());
389 }
390 other => panic!("expected FlagsNotAllowedForKind, got {other:?}"),
391 }
392 }
393
394 #[test]
395 fn streaming_decode_two_frames_back_to_back() {
396 let f1 = Frame::new(MessageKind::Query, 1, b"a".to_vec());
397 let f2 = Frame::new(MessageKind::Query, 2, b"b".to_vec());
398 let mut buf = encode_frame(&f1);
399 buf.extend(encode_frame(&f2));
400 let (got1, n1) = decode_frame(&buf).unwrap();
401 let (got2, _n2) = decode_frame(&buf[n1..]).unwrap();
402 assert_eq!(got1, f1);
403 assert_eq!(got2, f2);
404 }
405
406 #[test]
407 fn compressed_round_trip_recovers_plaintext() {
408 let payload = b"abcabcabcabc".repeat(100);
410 let frame =
411 Frame::new(MessageKind::Result, 7, payload.clone()).with_flags(Flags::COMPRESSED);
412 let bytes = encode_frame(&frame);
413 assert!(
415 bytes.len() < FRAME_HEADER_SIZE + payload.len(),
416 "compressed frame ({}) must be smaller than plaintext payload ({})",
417 bytes.len(),
418 payload.len(),
419 );
420 let (decoded, _) = decode_frame(&bytes).expect("decode compressed");
421 assert_eq!(decoded.payload, payload);
422 assert!(decoded.flags.contains(Flags::COMPRESSED));
423 }
424
425 #[test]
426 fn output_stream_lifecycle_envelopes_round_trip() {
427 let open = Frame::new(
433 MessageKind::OpenStream,
434 10,
435 br#"{"sql":"SELECT 1","opts":{}}"#.to_vec(),
436 )
437 .with_stream(7);
438 round_trip(open.clone());
439 assert_eq!(encode_frame(&open)[4], 0x29);
440
441 let ack = Frame::new(
442 MessageKind::OpenAck,
443 10,
444 br#"{"lease_handle":"42","resumable":false,"snapshot_lsn":1234}"#.to_vec(),
445 )
446 .with_stream(7);
447 round_trip(ack.clone());
448 assert_eq!(encode_frame(&ack)[4], 0x2A);
449
450 let chunk = Frame::new(
451 MessageKind::StreamChunk,
452 10,
453 br#"{"seq":0,"rows":[{"a":1}],"terminal":false}"#.to_vec(),
454 )
455 .with_stream(7);
456 round_trip(chunk.clone());
457 assert_eq!(encode_frame(&chunk)[4], 0x2B);
458
459 let serr = Frame::new(
460 MessageKind::StreamError,
461 10,
462 br#"{"code":"unknown_stream","message":"x"}"#.to_vec(),
463 )
464 .with_stream(7);
465 round_trip(serr.clone());
466 assert_eq!(encode_frame(&serr)[4], 0x2C);
467
468 let end = Frame::new(
469 MessageKind::StreamEnd,
470 10,
471 br#"{"stats":{"row_count":1}}"#.to_vec(),
472 )
473 .with_stream(7);
474 round_trip(end.clone());
475 assert_eq!(encode_frame(&end)[4], 0x25);
476
477 let cancel = Frame::new(
478 MessageKind::StreamCancel,
479 10,
480 br#"{"reason":"client-abort"}"#.to_vec(),
481 )
482 .with_stream(7);
483 round_trip(cancel.clone());
484 assert_eq!(encode_frame(&cancel)[4], 0x2D);
485 }
486
487 #[test]
488 fn input_stream_envelopes_round_trip() {
489 let open_in = Frame::new(
498 MessageKind::OpenStream,
499 20,
500 br#"{"direction":"in","target":"t","columns":["id","name"]}"#.to_vec(),
501 )
502 .with_stream(5);
503 round_trip(open_in.clone());
504 assert_eq!(encode_frame(&open_in)[4], 0x29);
505
506 let chunk_in = Frame::new(
510 MessageKind::StreamChunk,
511 20,
512 br#"{"seq":0,"rows":[{"id":1,"name":"a"}],"terminal":false}"#.to_vec(),
513 )
514 .with_stream(5);
515 round_trip(chunk_in.clone());
516 assert_eq!(encode_frame(&chunk_in)[4], 0x2B);
517
518 let chunk_terminal = Frame::new(
520 MessageKind::StreamChunk,
521 20,
522 br#"{"seq":2,"rows":[],"terminal":true}"#.to_vec(),
523 )
524 .with_stream(5);
525 round_trip(chunk_terminal.clone());
526 assert_eq!(encode_frame(&chunk_terminal)[4], 0x2B);
527
528 let end = Frame::new(
530 MessageKind::StreamEnd,
531 20,
532 br#"{"stats":{"row_count":3,"chunk_count":2,"committed_rid":42,"snapshot_lsn":40,"cancelled":false}}"#.to_vec(),
533 )
534 .with_stream(5);
535 round_trip(end.clone());
536 assert_eq!(encode_frame(&end)[4], 0x25);
537
538 let serr = Frame::new(
540 MessageKind::StreamError,
541 20,
542 br#"{"code":"invalid_row","message":"x","chunk_seq":1,"recoverable_rid":41}"#.to_vec(),
543 )
544 .with_stream(5);
545 round_trip(serr.clone());
546 assert_eq!(encode_frame(&serr)[4], 0x2C);
547 }
548
549 #[test]
550 fn queue_wait_envelopes_round_trip() {
551 let open = Frame::new(
557 MessageKind::QueueWaitOpen,
558 10,
559 br#"{"queue":"jobs","consumer":"w1","count":1,"wait_ms":5000}"#.to_vec(),
560 )
561 .with_stream(3);
562 round_trip(open.clone());
563 assert_eq!(encode_frame(&open)[4], 0x2E);
564
565 let push = Frame::new(
566 MessageKind::QueueEventPush,
567 10,
568 br#"{"message_id":"42","payload":{"hello":"world"},"consumer":"w1","delivery_count":1}"#
569 .to_vec(),
570 )
571 .with_stream(3);
572 round_trip(push.clone());
573 assert_eq!(encode_frame(&push)[4], 0x2F);
574 }
575
576 #[test]
577 fn uncompressed_frame_decodes_unchanged_when_flag_unset() {
578 let payload = b"hello world".to_vec();
579 let frame = Frame::new(MessageKind::Result, 1, payload.clone());
580 let bytes = encode_frame(&frame);
581 let (decoded, _) = decode_frame(&bytes).unwrap();
582 assert_eq!(decoded.payload, payload);
583 assert!(!decoded.flags.contains(Flags::COMPRESSED));
584 }
585
586 #[test]
587 fn compressed_payload_that_inflates_past_the_cap_is_refused() {
588 let plain = vec![0u8; MAX_DECOMPRESSED_PAYLOAD + 1024];
592 let compressed = zstd::stream::encode_all(plain.as_slice(), 1).expect("compress");
593 assert!(
594 compressed.len() < MAX_FRAME_SIZE as usize,
595 "the bomb must fit in one legal frame to be a realistic attack"
596 );
597
598 let total = (FRAME_HEADER_SIZE + compressed.len()) as u32;
599 let mut bytes = Vec::with_capacity(total as usize);
600 bytes.extend_from_slice(&total.to_le_bytes());
601 bytes.push(MessageKind::Query as u8);
602 bytes.push(Flags::COMPRESSED.bits());
603 bytes.extend_from_slice(&0u16.to_le_bytes());
604 bytes.extend_from_slice(&0u64.to_le_bytes());
605 bytes.extend_from_slice(&compressed);
606
607 match decode_frame(&bytes) {
608 Err(FrameError::DecompressedTooLarge { limit }) => {
609 assert_eq!(limit as usize, MAX_DECOMPRESSED_PAYLOAD);
610 }
611 other => panic!("expected DecompressedTooLarge, got {other:?}"),
612 }
613 }
614}