1use super::frame::{Flags, Frame, MessageKind, FRAME_HEADER_SIZE, MAX_FRAME_SIZE};
41
42#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum BuildError {
51 KindMissing,
54 PayloadTooLarge { encoded_len: usize, max: u32 },
56 FlagsNotAllowedForKind { kind: MessageKind, flags: u8 },
59}
60
61impl std::fmt::Display for BuildError {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 Self::KindMissing => write!(f, "FrameBuilder: kind() must be called before build()"),
65 Self::PayloadTooLarge { encoded_len, max } => write!(
66 f,
67 "FrameBuilder: encoded frame size {encoded_len} exceeds MAX_FRAME_SIZE ({max})"
68 ),
69 Self::FlagsNotAllowedForKind { kind, flags } => write!(
70 f,
71 "FrameBuilder: flag bits 0x{flags:02x} not allowed on kind {kind:?}"
72 ),
73 }
74 }
75}
76
77impl std::error::Error for BuildError {}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85enum Compress {
86 No,
87 Yes,
88}
89
90#[derive(Debug, Clone)]
98pub struct FrameBuilder {
99 kind: Option<MessageKind>,
100 correlation_id: u64,
101 stream_id: u16,
102 payload: Vec<u8>,
103 flags: Flags,
104 compress: Compress,
105 more_frames: bool,
109}
110
111impl FrameBuilder {
112 pub fn request(correlation_id: u64) -> Self {
115 Self::with_correlation(correlation_id)
116 }
117
118 pub fn reply_to(correlation_id: u64) -> Self {
121 Self::with_correlation(correlation_id)
122 }
123
124 pub fn unsolicited() -> Self {
127 Self::with_correlation(0)
128 }
129
130 fn with_correlation(correlation_id: u64) -> Self {
131 Self {
132 kind: None,
133 correlation_id,
134 stream_id: 0,
135 payload: Vec::new(),
136 flags: Flags::empty(),
137 compress: Compress::No,
138 more_frames: false,
139 }
140 }
141
142 pub fn kind(mut self, kind: MessageKind) -> Self {
143 self.kind = Some(kind);
144 self
145 }
146
147 pub fn payload(mut self, payload: Vec<u8>) -> Self {
148 self.payload = payload;
149 self
150 }
151
152 pub fn stream_id(mut self, stream_id: u16) -> Self {
153 self.stream_id = stream_id;
154 self
155 }
156
157 pub fn flags(mut self, flags: Flags) -> Self {
162 self.flags = flags;
163 self
164 }
165
166 pub fn more_frames(mut self, more: bool) -> Self {
171 self.more_frames = more;
172 self
173 }
174
175 pub fn compress(mut self, yes: bool) -> Self {
179 self.compress = if yes { Compress::Yes } else { Compress::No };
180 self
181 }
182
183 pub fn build(self) -> Result<Frame, BuildError> {
198 let kind = self.kind.ok_or(BuildError::KindMissing)?;
199 let encoded_len = FRAME_HEADER_SIZE + self.payload.len();
200 if encoded_len > MAX_FRAME_SIZE as usize {
201 return Err(BuildError::PayloadTooLarge {
202 encoded_len,
203 max: MAX_FRAME_SIZE,
204 });
205 }
206
207 if !kind.permits_flags(self.flags) {
208 return Err(BuildError::FlagsNotAllowedForKind {
209 kind,
210 flags: self.flags.bits(),
211 });
212 }
213
214 let mut flags = self.flags;
215 if self.more_frames {
216 flags = flags.insert(Flags::MORE_FRAMES);
217 } else {
218 flags = Flags::from_bits(flags.bits() & !Flags::MORE_FRAMES.bits());
221 }
222
223 let compressed = match self.compress {
224 Compress::No => false,
225 Compress::Yes => is_payload_compressible(&self.payload),
226 };
227 if compressed {
228 flags = flags.insert(Flags::COMPRESSED);
229 } else {
230 flags = Flags::from_bits(flags.bits() & !Flags::COMPRESSED.bits());
231 }
232
233 if !kind.permits_flags(flags) {
238 return Err(BuildError::FlagsNotAllowedForKind {
239 kind,
240 flags: flags.bits(),
241 });
242 }
243
244 Ok(Frame {
245 kind,
246 flags,
247 stream_id: self.stream_id,
248 correlation_id: self.correlation_id,
249 payload: self.payload,
250 })
251 }
252}
253
254pub fn build_reply_frame(
255 correlation_id: u64,
256 kind: MessageKind,
257 payload: Vec<u8>,
258) -> Result<Frame, BuildError> {
259 FrameBuilder::reply_to(correlation_id)
260 .kind(kind)
261 .payload(payload)
262 .build()
263}
264
265pub fn build_error_frame(correlation_id: u64, message: &str) -> Result<Frame, BuildError> {
266 build_reply_frame(
267 correlation_id,
268 MessageKind::Error,
269 message.as_bytes().to_vec(),
270 )
271}
272
273pub fn build_error_frame_lossy(correlation_id: u64, message: &str) -> Frame {
274 build_error_frame(correlation_id, message).unwrap_or_else(|_| {
275 Frame::new(
276 MessageKind::Error,
277 correlation_id,
278 b"redwire error frame too large".to_vec(),
279 )
280 })
281}
282
283pub fn build_dispatch_reply_frame(
284 correlation_id: u64,
285 kind: MessageKind,
286 payload: Vec<u8>,
287) -> Frame {
288 build_reply_frame(correlation_id, kind, payload)
289 .unwrap_or_else(|err| build_error_frame_lossy(correlation_id, &err.to_string()))
290}
291
292pub fn rewrap_length_prefixed_handler_response(raw_bytes: &[u8], correlation_id: u64) -> Frame {
299 if raw_bytes.len() < 5 {
300 return build_error_frame_lossy(
301 correlation_id,
302 "fast-path handler returned a truncated frame",
303 );
304 }
305 let kind = MessageKind::from_u8(raw_bytes[4]).unwrap_or(MessageKind::Error);
306 build_dispatch_reply_frame(correlation_id, kind, raw_bytes[5..].to_vec())
307}
308
309pub struct BorrowedLegacyReply<'a> {
312 frame: Frame,
313 payload: &'a [u8],
314}
315
316impl<'a> BorrowedLegacyReply<'a> {
317 pub fn new(raw: &'a [u8], correlation_id: u64) -> Result<Self, super::codec::FrameError> {
318 use super::codec::FrameError;
319 if raw.len() < 5 {
320 return Err(FrameError::Truncated);
321 }
322 let payload = &raw[5..];
323 let length = payload
324 .len()
325 .checked_add(FRAME_HEADER_SIZE)
326 .filter(|length| *length <= MAX_FRAME_SIZE as usize)
327 .ok_or(FrameError::InvalidLength(u32::MAX))?;
328 let kind = MessageKind::from_u8(raw[4]).unwrap_or(MessageKind::Error);
329 let frame = build_reply_frame(correlation_id, kind, Vec::new())
330 .expect("empty uncompressed reply is valid for every kind");
331 assert_eq!(length, FRAME_HEADER_SIZE + payload.len());
332 Ok(Self { frame, payload })
333 }
334
335 pub fn encoded_len(&self) -> usize {
336 FRAME_HEADER_SIZE + self.payload.len()
337 }
338
339 pub fn encode_into(&self, buffer: &mut Vec<u8>) {
340 super::codec::encode_uncompressed_payload_into(&self.frame, self.payload, buffer);
341 }
342}
343
344pub fn build_query_frame(correlation_id: u64, sql: &str) -> Result<Frame, BuildError> {
345 build_request_frame(correlation_id, MessageKind::Query, sql.as_bytes().to_vec())
346}
347
348pub fn build_query_with_params_frame(
349 correlation_id: u64,
350 payload: Vec<u8>,
351) -> Result<Frame, BuildError> {
352 build_request_frame(correlation_id, MessageKind::QueryWithParams, payload)
353}
354
355pub fn build_bulk_insert_frame(correlation_id: u64, payload: Vec<u8>) -> Result<Frame, BuildError> {
356 build_request_frame(correlation_id, MessageKind::BulkInsert, payload)
357}
358
359pub fn build_get_frame(correlation_id: u64, payload: Vec<u8>) -> Result<Frame, BuildError> {
360 build_request_frame(correlation_id, MessageKind::Get, payload)
361}
362
363pub fn build_delete_frame(correlation_id: u64, payload: Vec<u8>) -> Result<Frame, BuildError> {
364 build_request_frame(correlation_id, MessageKind::Delete, payload)
365}
366
367pub fn build_bulk_insert_binary_frame(
368 correlation_id: u64,
369 payload: Vec<u8>,
370) -> Result<Frame, BuildError> {
371 build_request_frame(correlation_id, MessageKind::BulkInsertBinary, payload)
372}
373
374pub fn build_ping_frame(correlation_id: u64) -> Result<Frame, BuildError> {
375 build_request_frame(correlation_id, MessageKind::Ping, Vec::new())
376}
377
378pub fn build_bye_frame(correlation_id: u64) -> Result<Frame, BuildError> {
379 build_request_frame(correlation_id, MessageKind::Bye, Vec::new())
380}
381
382pub fn build_request_frame(
383 correlation_id: u64,
384 kind: MessageKind,
385 payload: Vec<u8>,
386) -> Result<Frame, BuildError> {
387 FrameBuilder::request(correlation_id)
388 .kind(kind)
389 .payload(payload)
390 .build()
391}
392
393fn is_payload_compressible(payload: &[u8]) -> bool {
399 payload.len() > 32
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 #[test]
407 fn borrowed_legacy_reply_matches_existing_bridge_without_payload_allocation() {
408 for kind in [crate::legacy::MSG_RESULT, crate::legacy::MSG_ERROR, 255] {
409 for size in [0, 1, 4096, 192 * 1024] {
410 let raw = crate::legacy::build_legacy_frame(kind, &vec![b'x'; size]);
411 let reply = BorrowedLegacyReply::new(&raw, 123).unwrap();
412 assert_eq!(reply.payload.as_ptr(), raw[5..].as_ptr());
413 assert_eq!(reply.frame.payload.capacity(), 0);
414 let mut bytes = Vec::with_capacity(reply.encoded_len());
415 reply.encode_into(&mut bytes);
416 assert_eq!(bytes.len(), bytes.capacity());
417 assert_eq!(
418 bytes,
419 super::super::codec::encode_frame(&rewrap_length_prefixed_handler_response(
420 &raw, 123
421 ))
422 );
423 }
424 }
425 assert!(BorrowedLegacyReply::new(&[0; 4], 1).is_err());
426 let oversized = vec![0; MAX_FRAME_SIZE as usize];
427 assert!(BorrowedLegacyReply::new(&oversized, 1).is_err());
428 }
429
430 #[test]
431 fn reply_to_propagates_correlation_id() {
432 let frame = FrameBuilder::reply_to(0xABCD)
435 .kind(MessageKind::Result)
436 .payload(b"ok".to_vec())
437 .build()
438 .expect("build");
439 assert_eq!(frame.correlation_id, 0xABCD);
440 assert_eq!(frame.kind, MessageKind::Result);
441 assert_eq!(frame.payload, b"ok");
442 }
443
444 #[test]
445 fn request_builders_choose_client_message_kinds() {
446 let query = build_query_frame(1, "select 1").expect("query");
447 assert_eq!(query.kind, MessageKind::Query);
448 assert_eq!(query.correlation_id, 1);
449 assert_eq!(query.payload, b"select 1");
450
451 let ping = build_ping_frame(2).expect("ping");
452 assert_eq!(ping.kind, MessageKind::Ping);
453 assert!(ping.payload.is_empty());
454
455 let bye = build_bye_frame(3).expect("bye");
456 assert_eq!(bye.kind, MessageKind::Bye);
457 assert!(bye.payload.is_empty());
458 }
459
460 #[test]
461 fn unsolicited_uses_zero_correlation() {
462 let frame = FrameBuilder::unsolicited()
463 .kind(MessageKind::Notice)
464 .payload(b"server-side notice".to_vec())
465 .build()
466 .expect("build");
467 assert_eq!(frame.correlation_id, 0);
468 }
469
470 #[test]
471 fn missing_kind_rejected() {
472 let err = FrameBuilder::reply_to(1).build().unwrap_err();
473 assert_eq!(err, BuildError::KindMissing);
474 }
475
476 #[test]
477 fn more_frames_last_frame_clears_the_flag() {
478 let middle = FrameBuilder::reply_to(7)
482 .kind(MessageKind::Result)
483 .payload(vec![0; 8])
484 .more_frames(true)
485 .build()
486 .expect("build middle");
487 assert!(
488 middle.flags.contains(Flags::MORE_FRAMES),
489 "middle frame must carry MORE_FRAMES"
490 );
491
492 let last = FrameBuilder::reply_to(7)
493 .kind(MessageKind::Result)
494 .payload(vec![0; 8])
495 .more_frames(false)
496 .build()
497 .expect("build last");
498 assert!(
499 !last.flags.contains(Flags::MORE_FRAMES),
500 "last frame must clear MORE_FRAMES"
501 );
502 }
503
504 #[test]
505 fn more_frames_default_is_last_frame() {
506 let frame = FrameBuilder::reply_to(1)
510 .kind(MessageKind::Pong)
511 .build()
512 .expect("build");
513 assert!(!frame.flags.contains(Flags::MORE_FRAMES));
514 }
515
516 #[test]
517 fn payload_at_max_size_accepted() {
518 let payload = vec![0u8; (MAX_FRAME_SIZE as usize) - FRAME_HEADER_SIZE];
519 let frame = FrameBuilder::reply_to(1)
520 .kind(MessageKind::Result)
521 .payload(payload)
522 .build()
523 .expect("build at limit");
524 assert_eq!(frame.encoded_len(), MAX_FRAME_SIZE);
525 }
526
527 #[test]
528 fn payload_over_max_size_rejected() {
529 let oversize = (MAX_FRAME_SIZE as usize) - FRAME_HEADER_SIZE + 1;
533 let payload = vec![0u8; oversize];
534 let err = FrameBuilder::reply_to(1)
535 .kind(MessageKind::Result)
536 .payload(payload)
537 .build()
538 .unwrap_err();
539 match err {
540 BuildError::PayloadTooLarge { encoded_len, max } => {
541 assert_eq!(max, MAX_FRAME_SIZE);
542 assert_eq!(encoded_len, MAX_FRAME_SIZE as usize + 1);
543 }
544 other => panic!("expected PayloadTooLarge, got {other:?}"),
545 }
546 }
547
548 #[test]
549 fn compression_fallback_drops_flag_for_incompressible_payload() {
550 let frame = FrameBuilder::reply_to(1)
555 .kind(MessageKind::Result)
556 .payload(b"tiny".to_vec())
557 .compress(true)
558 .build()
559 .expect("build");
560 assert!(
561 !frame.flags.contains(Flags::COMPRESSED),
562 "incompressible payload must not carry COMPRESSED"
563 );
564 }
565
566 #[test]
567 fn compression_kept_for_compressible_payload() {
568 let payload = b"abcabcabc".repeat(16);
569 let frame = FrameBuilder::reply_to(1)
570 .kind(MessageKind::Result)
571 .payload(payload)
572 .compress(true)
573 .build()
574 .expect("build");
575 assert!(frame.flags.contains(Flags::COMPRESSED));
576 }
577
578 #[test]
579 fn flags_not_allowed_for_kind_rejected_at_build() {
580 let err = FrameBuilder::reply_to(1)
584 .kind(MessageKind::Hello)
585 .flags(Flags::COMPRESSED)
586 .build()
587 .unwrap_err();
588 match err {
589 BuildError::FlagsNotAllowedForKind { kind, flags } => {
590 assert_eq!(kind, MessageKind::Hello);
591 assert_eq!(flags, Flags::COMPRESSED.bits());
592 }
593 other => panic!("expected FlagsNotAllowedForKind, got {other:?}"),
594 }
595 }
596
597 #[test]
598 fn stream_id_propagates() {
599 let frame = FrameBuilder::reply_to(1)
600 .kind(MessageKind::Result)
601 .stream_id(0xBEEF)
602 .build()
603 .expect("build");
604 assert_eq!(frame.stream_id, 0xBEEF);
605 }
606
607 #[test]
608 fn generic_reply_builders_pin_server_frame_contracts() {
609 let reply = build_reply_frame(7, MessageKind::Pong, b"ok".to_vec()).expect("reply frame");
610 assert_eq!(reply.correlation_id, 7);
611 assert_eq!(reply.kind, MessageKind::Pong);
612 assert_eq!(reply.payload, b"ok");
613
614 let err = build_error_frame(8, "bad request").expect("error frame");
615 assert_eq!(err.kind, MessageKind::Error);
616 assert_eq!(err.correlation_id, 8);
617 assert_eq!(err.payload, b"bad request");
618
619 let dispatch = build_dispatch_reply_frame(9, MessageKind::Result, b"rows".to_vec());
620 assert_eq!(dispatch.kind, MessageKind::Result);
621 assert_eq!(dispatch.correlation_id, 9);
622 }
623
624 #[test]
625 fn rewraps_length_prefixed_handler_response() {
626 let raw = [4u8, 0, 0, 0, MessageKind::BulkOk as u8, b'o', b'k'];
627 let frame = rewrap_length_prefixed_handler_response(&raw, 12);
628 assert_eq!(frame.correlation_id, 12);
629 assert_eq!(frame.kind, MessageKind::BulkOk);
630 assert_eq!(frame.payload, b"ok");
631
632 let truncated = rewrap_length_prefixed_handler_response(&raw[..4], 13);
633 assert_eq!(truncated.kind, MessageKind::Error);
634 assert_eq!(truncated.correlation_id, 13);
635 assert_eq!(
636 truncated.payload,
637 b"fast-path handler returned a truncated frame"
638 );
639 }
640
641 #[test]
642 fn lossy_error_builder_never_panics_on_oversized_payload() {
643 let too_large = "x".repeat(MAX_FRAME_SIZE as usize);
644 let err = build_error_frame_lossy(11, &too_large);
645 assert_eq!(err.kind, MessageKind::Error);
646 assert_eq!(err.correlation_id, 11);
647 assert_eq!(err.payload, b"redwire error frame too large");
648 }
649}