1pub const FRAME_HEADER_SIZE: usize = 16;
20pub const MAX_FRAME_SIZE: u32 = 16 * 1024 * 1024;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct Frame {
24 pub kind: MessageKind,
25 pub flags: Flags,
26 pub stream_id: u16,
27 pub correlation_id: u64,
28 pub payload: Vec<u8>,
29}
30
31impl Frame {
32 pub fn new(kind: MessageKind, correlation_id: u64, payload: Vec<u8>) -> Self {
33 Self {
34 kind,
35 flags: Flags::empty(),
36 stream_id: 0,
37 correlation_id,
38 payload,
39 }
40 }
41
42 pub fn with_stream(mut self, stream_id: u16) -> Self {
43 self.stream_id = stream_id;
44 self
45 }
46
47 pub fn with_flags(mut self, flags: Flags) -> Self {
48 self.flags = flags;
49 self
50 }
51
52 pub fn encoded_len(&self) -> u32 {
53 (FRAME_HEADER_SIZE + self.payload.len()) as u32
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60#[repr(u8)]
61pub enum MessageKind {
62 Query = 0x01,
64 Result = 0x02,
65 Error = 0x03,
66 BulkInsert = 0x04,
67 BulkOk = 0x05,
68 BulkInsertBinary = 0x06,
69 QueryBinary = 0x07,
70 BulkInsertPrevalidated = 0x08,
71 BulkStreamStart = 0x09,
72 BulkStreamRows = 0x0A,
73 BulkStreamCommit = 0x0B,
74 BulkStreamAck = 0x0C,
75 Prepare = 0x0D,
76 PreparedOk = 0x0E,
77 ExecutePrepared = 0x0F,
78
79 Hello = 0x10,
81 HelloAck = 0x11,
82 AuthRequest = 0x12,
83 AuthResponse = 0x13,
84 AuthOk = 0x14,
85 AuthFail = 0x15,
86 Bye = 0x16,
87 Ping = 0x17,
88 Pong = 0x18,
89 Get = 0x19,
90 Delete = 0x1A,
91 DeleteOk = 0x1B,
92
93 Cancel = 0x20,
95 Compress = 0x21,
96 SetSession = 0x22,
97 Notice = 0x23,
98
99 RowDescription = 0x24,
101 StreamEnd = 0x25,
102
103 VectorSearch = 0x26,
105 GraphTraverse = 0x27,
106 QueryWithParams = 0x28,
107
108 OpenStream = 0x29,
112 OpenAck = 0x2A,
113 StreamChunk = 0x2B,
114 StreamError = 0x2C,
115 StreamCancel = 0x2D,
116
117 QueueWaitOpen = 0x2E,
125 QueueEventPush = 0x2F,
126 QueueWaitTimeout = 0x30,
131 MovedRedirect = 0x31,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub enum MessageClass {
146 DataPlane,
147 Handshake,
148 ControlPlane,
149 Streamed,
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum MessageDirection {
161 ClientToServer,
162 ServerToClient,
163 Both,
164}
165
166impl MessageKind {
167 pub fn class(&self) -> MessageClass {
169 match self {
170 Self::Query
175 | Self::Result
176 | Self::Error
177 | Self::BulkInsert
178 | Self::BulkOk
179 | Self::BulkInsertBinary
180 | Self::QueryBinary
181 | Self::BulkInsertPrevalidated
182 | Self::Prepare
183 | Self::PreparedOk
184 | Self::ExecutePrepared
185 | Self::Get
186 | Self::Delete
187 | Self::DeleteOk
188 | Self::VectorSearch
189 | Self::GraphTraverse
190 | Self::QueryWithParams
191 | Self::MovedRedirect => MessageClass::DataPlane,
192
193 Self::BulkStreamStart
199 | Self::BulkStreamRows
200 | Self::BulkStreamCommit
201 | Self::BulkStreamAck
202 | Self::RowDescription
203 | Self::StreamEnd
204 | Self::OpenStream
205 | Self::OpenAck
206 | Self::StreamChunk
207 | Self::StreamError
208 | Self::StreamCancel
209 | Self::QueueWaitOpen
212 | Self::QueueEventPush
213 | Self::QueueWaitTimeout => MessageClass::Streamed,
216
217 Self::Hello
219 | Self::HelloAck
220 | Self::AuthRequest
221 | Self::AuthResponse
222 | Self::AuthOk
223 | Self::AuthFail
224 | Self::Bye
225 | Self::Ping
226 | Self::Pong => MessageClass::Handshake,
227
228 Self::Cancel | Self::Compress | Self::SetSession | Self::Notice => {
230 MessageClass::ControlPlane
231 }
232 }
233 }
234
235 pub fn allowed_flags(&self) -> Flags {
245 match self {
246 Self::Hello
249 | Self::HelloAck
250 | Self::AuthRequest
251 | Self::AuthResponse
252 | Self::AuthOk
253 | Self::AuthFail
254 | Self::Bye
255 | Self::Ping
256 | Self::Pong => Flags::MORE_FRAMES,
257
258 _ => Flags::COMPRESSED.insert(Flags::MORE_FRAMES),
260 }
261 }
262
263 pub fn is_handshake(&self) -> bool {
268 matches!(self.class(), MessageClass::Handshake)
269 }
270
271 pub fn permits_flags(&self, flags: Flags) -> bool {
277 let allowed = self.allowed_flags().bits();
278 (flags.bits() & !allowed) == 0
279 }
280
281 pub fn direction(&self) -> MessageDirection {
283 match self {
284 Self::Hello
286 | Self::AuthResponse
287 | Self::Query
288 | Self::QueryBinary
289 | Self::BulkInsert
290 | Self::BulkInsertBinary
291 | Self::BulkInsertPrevalidated
292 | Self::BulkStreamStart
293 | Self::BulkStreamRows
294 | Self::BulkStreamCommit
295 | Self::Prepare
296 | Self::ExecutePrepared
297 | Self::Get
298 | Self::Delete
299 | Self::Cancel
300 | Self::Compress
301 | Self::SetSession
302 | Self::VectorSearch
303 | Self::GraphTraverse
304 | Self::QueryWithParams
305 | Self::OpenStream
306 | Self::StreamCancel
307 | Self::QueueWaitOpen => MessageDirection::ClientToServer,
309
310 Self::StreamChunk => MessageDirection::Both,
316
317 Self::HelloAck
319 | Self::AuthRequest
320 | Self::AuthOk
321 | Self::AuthFail
322 | Self::Result
323 | Self::Error
324 | Self::BulkOk
325 | Self::BulkStreamAck
326 | Self::PreparedOk
327 | Self::DeleteOk
328 | Self::Notice
329 | Self::RowDescription
330 | Self::StreamEnd
331 | Self::OpenAck
332 | Self::StreamError
333 | Self::QueueEventPush
336 | Self::QueueWaitTimeout
337 | Self::MovedRedirect => MessageDirection::ServerToClient,
338
339 Self::Bye | Self::Ping | Self::Pong => MessageDirection::Both,
343 }
344 }
345
346 pub fn from_u8(byte: u8) -> Option<Self> {
347 match byte {
348 0x01 => Some(Self::Query),
349 0x02 => Some(Self::Result),
350 0x03 => Some(Self::Error),
351 0x04 => Some(Self::BulkInsert),
352 0x05 => Some(Self::BulkOk),
353 0x06 => Some(Self::BulkInsertBinary),
354 0x07 => Some(Self::QueryBinary),
355 0x08 => Some(Self::BulkInsertPrevalidated),
356 0x09 => Some(Self::BulkStreamStart),
357 0x0A => Some(Self::BulkStreamRows),
358 0x0B => Some(Self::BulkStreamCommit),
359 0x0C => Some(Self::BulkStreamAck),
360 0x0D => Some(Self::Prepare),
361 0x0E => Some(Self::PreparedOk),
362 0x0F => Some(Self::ExecutePrepared),
363 0x10 => Some(Self::Hello),
364 0x11 => Some(Self::HelloAck),
365 0x12 => Some(Self::AuthRequest),
366 0x13 => Some(Self::AuthResponse),
367 0x14 => Some(Self::AuthOk),
368 0x15 => Some(Self::AuthFail),
369 0x16 => Some(Self::Bye),
370 0x17 => Some(Self::Ping),
371 0x18 => Some(Self::Pong),
372 0x19 => Some(Self::Get),
373 0x1A => Some(Self::Delete),
374 0x1B => Some(Self::DeleteOk),
375 0x20 => Some(Self::Cancel),
376 0x21 => Some(Self::Compress),
377 0x22 => Some(Self::SetSession),
378 0x23 => Some(Self::Notice),
379 0x24 => Some(Self::RowDescription),
380 0x25 => Some(Self::StreamEnd),
381 0x26 => Some(Self::VectorSearch),
382 0x27 => Some(Self::GraphTraverse),
383 0x28 => Some(Self::QueryWithParams),
384 0x29 => Some(Self::OpenStream),
385 0x2A => Some(Self::OpenAck),
386 0x2B => Some(Self::StreamChunk),
387 0x2C => Some(Self::StreamError),
388 0x2D => Some(Self::StreamCancel),
389 0x2E => Some(Self::QueueWaitOpen),
390 0x2F => Some(Self::QueueEventPush),
391 0x30 => Some(Self::QueueWaitTimeout),
392 0x31 => Some(Self::MovedRedirect),
393 _ => None,
394 }
395 }
396}
397
398#[derive(Debug, Clone, Copy, PartialEq, Eq)]
399pub struct Flags(u8);
400
401impl Flags {
402 pub const COMPRESSED: Self = Self(0b0000_0001);
403 pub const MORE_FRAMES: Self = Self(0b0000_0010);
404
405 pub const fn empty() -> Self {
406 Self(0)
407 }
408
409 pub const fn bits(self) -> u8 {
410 self.0
411 }
412
413 pub const fn from_bits(bits: u8) -> Self {
414 Self(bits)
415 }
416
417 pub const fn contains(self, other: Self) -> bool {
418 (self.0 & other.0) == other.0
419 }
420
421 pub const fn insert(self, other: Self) -> Self {
422 Self(self.0 | other.0)
423 }
424}
425
426impl std::ops::BitOr for Flags {
427 type Output = Self;
428 fn bitor(self, rhs: Self) -> Self {
429 self.insert(rhs)
430 }
431}
432
433#[cfg(test)]
434mod catalog_tests {
435 use super::*;
436
437 const ALL_KINDS: &[MessageKind] = &[
441 MessageKind::Query,
442 MessageKind::Result,
443 MessageKind::Error,
444 MessageKind::BulkInsert,
445 MessageKind::BulkOk,
446 MessageKind::BulkInsertBinary,
447 MessageKind::QueryBinary,
448 MessageKind::BulkInsertPrevalidated,
449 MessageKind::BulkStreamStart,
450 MessageKind::BulkStreamRows,
451 MessageKind::BulkStreamCommit,
452 MessageKind::BulkStreamAck,
453 MessageKind::Prepare,
454 MessageKind::PreparedOk,
455 MessageKind::ExecutePrepared,
456 MessageKind::Hello,
457 MessageKind::HelloAck,
458 MessageKind::AuthRequest,
459 MessageKind::AuthResponse,
460 MessageKind::AuthOk,
461 MessageKind::AuthFail,
462 MessageKind::Bye,
463 MessageKind::Ping,
464 MessageKind::Pong,
465 MessageKind::Get,
466 MessageKind::Delete,
467 MessageKind::DeleteOk,
468 MessageKind::Cancel,
469 MessageKind::Compress,
470 MessageKind::SetSession,
471 MessageKind::Notice,
472 MessageKind::RowDescription,
473 MessageKind::StreamEnd,
474 MessageKind::VectorSearch,
475 MessageKind::GraphTraverse,
476 MessageKind::QueryWithParams,
477 MessageKind::OpenStream,
478 MessageKind::OpenAck,
479 MessageKind::StreamChunk,
480 MessageKind::StreamError,
481 MessageKind::StreamCancel,
482 MessageKind::QueueWaitOpen,
483 MessageKind::QueueEventPush,
484 MessageKind::QueueWaitTimeout,
485 MessageKind::MovedRedirect,
486 ];
487
488 #[test]
489 fn class_matrix_is_pinned() {
490 assert_eq!(MessageKind::Hello.class(), MessageClass::Handshake);
493 assert_eq!(MessageKind::HelloAck.class(), MessageClass::Handshake);
494 assert_eq!(MessageKind::AuthRequest.class(), MessageClass::Handshake);
495 assert_eq!(MessageKind::AuthResponse.class(), MessageClass::Handshake);
496 assert_eq!(MessageKind::AuthOk.class(), MessageClass::Handshake);
497 assert_eq!(MessageKind::AuthFail.class(), MessageClass::Handshake);
498 assert_eq!(MessageKind::Bye.class(), MessageClass::Handshake);
499 assert_eq!(MessageKind::Ping.class(), MessageClass::Handshake);
500 assert_eq!(MessageKind::Pong.class(), MessageClass::Handshake);
501
502 assert_eq!(MessageKind::Query.class(), MessageClass::DataPlane);
504 assert_eq!(MessageKind::Result.class(), MessageClass::DataPlane);
505 assert_eq!(MessageKind::BulkInsert.class(), MessageClass::DataPlane);
506 assert_eq!(MessageKind::Get.class(), MessageClass::DataPlane);
507 assert_eq!(MessageKind::Delete.class(), MessageClass::DataPlane);
508 assert_eq!(MessageKind::DeleteOk.class(), MessageClass::DataPlane);
509 assert_eq!(MessageKind::VectorSearch.class(), MessageClass::DataPlane);
510 assert_eq!(MessageKind::GraphTraverse.class(), MessageClass::DataPlane);
511 assert_eq!(
512 MessageKind::QueryWithParams.class(),
513 MessageClass::DataPlane
514 );
515 assert_eq!(MessageKind::MovedRedirect.class(), MessageClass::DataPlane);
516
517 assert_eq!(MessageKind::BulkStreamStart.class(), MessageClass::Streamed);
519 assert_eq!(MessageKind::BulkStreamRows.class(), MessageClass::Streamed);
520 assert_eq!(
521 MessageKind::BulkStreamCommit.class(),
522 MessageClass::Streamed
523 );
524 assert_eq!(MessageKind::BulkStreamAck.class(), MessageClass::Streamed);
525 assert_eq!(MessageKind::RowDescription.class(), MessageClass::Streamed);
526 assert_eq!(MessageKind::StreamEnd.class(), MessageClass::Streamed);
527
528 assert_eq!(MessageKind::OpenStream.class(), MessageClass::Streamed);
532 assert_eq!(MessageKind::OpenAck.class(), MessageClass::Streamed);
533 assert_eq!(MessageKind::StreamChunk.class(), MessageClass::Streamed);
534 assert_eq!(MessageKind::StreamError.class(), MessageClass::Streamed);
535 assert_eq!(MessageKind::StreamCancel.class(), MessageClass::Streamed);
536
537 assert_eq!(MessageKind::QueueWaitOpen.class(), MessageClass::Streamed);
540 assert_eq!(MessageKind::QueueEventPush.class(), MessageClass::Streamed);
541 assert_eq!(
543 MessageKind::QueueWaitTimeout.class(),
544 MessageClass::Streamed
545 );
546
547 assert_eq!(MessageKind::Cancel.class(), MessageClass::ControlPlane);
549 assert_eq!(MessageKind::Compress.class(), MessageClass::ControlPlane);
550 assert_eq!(MessageKind::SetSession.class(), MessageClass::ControlPlane);
551 assert_eq!(MessageKind::Notice.class(), MessageClass::ControlPlane);
552
553 for k in ALL_KINDS {
555 let _ = k.class();
556 }
557 }
558
559 #[test]
560 fn allowed_flags_matrix_is_pinned() {
561 let handshake = [
565 MessageKind::Hello,
566 MessageKind::HelloAck,
567 MessageKind::AuthRequest,
568 MessageKind::AuthResponse,
569 MessageKind::AuthOk,
570 MessageKind::AuthFail,
571 MessageKind::Bye,
572 MessageKind::Ping,
573 MessageKind::Pong,
574 ];
575 for k in handshake {
576 let f = k.allowed_flags();
577 assert!(
578 f.contains(Flags::MORE_FRAMES),
579 "{k:?} must allow MORE_FRAMES"
580 );
581 assert!(
582 !f.contains(Flags::COMPRESSED),
583 "{k:?} must NOT allow COMPRESSED today"
584 );
585 }
586
587 for k in ALL_KINDS {
589 if handshake.contains(k) {
590 continue;
591 }
592 let f = k.allowed_flags();
593 assert!(
594 f.contains(Flags::MORE_FRAMES),
595 "{k:?} must allow MORE_FRAMES"
596 );
597 assert!(f.contains(Flags::COMPRESSED), "{k:?} must allow COMPRESSED");
598 }
599 }
600
601 #[test]
602 fn every_kind_has_unique_byte_value() {
603 let mut seen = std::collections::HashSet::new();
606 for k in ALL_KINDS {
607 let byte = *k as u8;
608 assert!(
609 seen.insert(byte),
610 "byte 0x{byte:02x} reused by {k:?}; catalog has a duplicate"
611 );
612 }
613 }
614
615 #[test]
616 fn from_u8_round_trips_for_every_kind() {
617 for k in ALL_KINDS {
618 let byte = *k as u8;
619 let decoded = MessageKind::from_u8(byte).unwrap_or_else(|| {
620 panic!("from_u8 returned None for catalog entry {k:?} (0x{byte:02x})")
621 });
622 assert_eq!(
623 decoded, *k,
624 "from_u8(0x{byte:02x}) must round-trip back to {k:?}"
625 );
626 }
627 }
628
629 #[test]
630 fn permits_flags_matches_allowed_flags() {
631 assert!(MessageKind::Ping.permits_flags(Flags::MORE_FRAMES));
633 assert!(MessageKind::Ping.permits_flags(Flags::empty()));
634 assert!(!MessageKind::Ping.permits_flags(Flags::COMPRESSED));
635 assert!(!MessageKind::Ping.permits_flags(Flags::COMPRESSED | Flags::MORE_FRAMES));
636
637 assert!(MessageKind::BulkStreamRows.permits_flags(Flags::MORE_FRAMES));
641 assert!(MessageKind::BulkStreamRows.permits_flags(Flags::COMPRESSED));
642 assert!(MessageKind::RowDescription.permits_flags(Flags::MORE_FRAMES));
643 assert!(MessageKind::StreamEnd.permits_flags(Flags::MORE_FRAMES));
644 }
645
646 #[test]
647 fn direction_matrix_is_pinned() {
648 for k in [
650 MessageKind::Hello,
651 MessageKind::AuthResponse,
652 MessageKind::Query,
653 MessageKind::QueryBinary,
654 MessageKind::BulkInsert,
655 MessageKind::BulkInsertBinary,
656 MessageKind::BulkInsertPrevalidated,
657 MessageKind::BulkStreamStart,
658 MessageKind::BulkStreamRows,
659 MessageKind::BulkStreamCommit,
660 MessageKind::Prepare,
661 MessageKind::ExecutePrepared,
662 MessageKind::Get,
663 MessageKind::Delete,
664 MessageKind::Cancel,
665 MessageKind::Compress,
666 MessageKind::SetSession,
667 MessageKind::VectorSearch,
668 MessageKind::GraphTraverse,
669 MessageKind::QueryWithParams,
670 MessageKind::OpenStream,
671 MessageKind::StreamCancel,
672 MessageKind::QueueWaitOpen,
673 ] {
674 assert_eq!(
675 k.direction(),
676 MessageDirection::ClientToServer,
677 "{k:?} should be client-originated"
678 );
679 }
680
681 for k in [
683 MessageKind::HelloAck,
684 MessageKind::AuthRequest,
685 MessageKind::AuthOk,
686 MessageKind::AuthFail,
687 MessageKind::Result,
688 MessageKind::Error,
689 MessageKind::BulkOk,
690 MessageKind::BulkStreamAck,
691 MessageKind::PreparedOk,
692 MessageKind::DeleteOk,
693 MessageKind::Notice,
694 MessageKind::RowDescription,
695 MessageKind::StreamEnd,
696 MessageKind::OpenAck,
697 MessageKind::StreamError,
698 MessageKind::QueueEventPush,
699 MessageKind::QueueWaitTimeout,
700 ] {
701 assert_eq!(
702 k.direction(),
703 MessageDirection::ServerToClient,
704 "{k:?} should be server-originated"
705 );
706 }
707
708 for k in [
712 MessageKind::Bye,
713 MessageKind::Ping,
714 MessageKind::Pong,
715 MessageKind::StreamChunk,
716 ] {
717 assert_eq!(
718 k.direction(),
719 MessageDirection::Both,
720 "{k:?} should be symmetric"
721 );
722 }
723 }
724}