Skip to main content

reddb_wire/redwire/
mod.rs

1//! RedWire — RedDB's binary TCP/TLS wire protocol.
2//!
3//! ADR 0001 (`.red/adr/0001-redwire-tcp-protocol.md`) is the
4//! normative spec. This module owns the frame layout, message-kind
5//! discriminator, flags, encode/decode codec, and generic async
6//! frame I/O over byte streams. Server-side dispatch, auth policy,
7//! session loop, and listener accept stay in `reddb` and depend on
8//! these types.
9
10pub mod builder;
11pub mod bulk_binary;
12pub mod bulk_json;
13pub mod bulk_stream;
14pub mod codec;
15pub mod cursor;
16pub mod frame;
17pub mod handshake;
18pub mod io;
19pub mod operations;
20pub mod prepared;
21pub mod queue;
22pub mod stream;
23
24pub use builder::{
25    build_bulk_insert_binary_frame, build_bulk_insert_frame, build_bye_frame, build_delete_frame,
26    build_dispatch_reply_frame, build_error_frame, build_error_frame_lossy, build_get_frame,
27    build_ping_frame, build_query_frame, build_query_with_params_frame, build_reply_frame,
28    build_request_frame, rewrap_length_prefixed_handler_response, BuildError, FrameBuilder,
29};
30pub use bulk_binary::{
31    decode_bulk_binary_payload, encode_bulk_binary_payload, BulkBinaryError, BulkBinaryFlavor,
32    BulkBinaryPayload,
33};
34pub use bulk_json::{
35    decode_bulk_json_payload, encode_bulk_json_payload, BulkJsonError, BulkJsonPayload,
36};
37pub use bulk_stream::{
38    decode_bulk_stream_rows_payload, decode_bulk_stream_start_payload,
39    encode_bulk_stream_rows_payload, encode_bulk_stream_start_payload, BulkStreamError,
40    BulkStreamRowsPayload, BulkStreamStartPayload,
41};
42pub use codec::{
43    decode_frame, decode_frame_parts, encode_frame, frame_len_from_header, FrameError,
44};
45pub use cursor::{
46    decode_close_cursor_payload, decode_declare_cursor_payload, decode_fetch_payload,
47    encode_close_cursor_payload, encode_cursor_batch_payload, encode_cursor_ok_payload,
48    encode_declare_cursor_payload, encode_fetch_payload, CloseCursorPayload, CursorPayloadError,
49    DeclareCursorPayload, FetchPayload,
50};
51pub use frame::{
52    Flags, Frame, MessageClass, MessageDirection, MessageKind, FRAME_HEADER_SIZE, MAX_FRAME_SIZE,
53};
54pub use handshake::{
55    build_auth_fail_frame, build_auth_fail_payload, build_auth_ok_frame_from_payload,
56    build_auth_ok_payload, build_auth_response_anonymous_payload,
57    build_auth_response_bearer_payload, build_auth_response_frame,
58    build_auth_response_oauth_jwt_payload, build_client_hello_frame, build_client_hello_payload,
59    build_hello_ack, build_hello_ack_frame, build_hello_payload, choose_hello_minor_version,
60    expect_auth_response_payload, AuthFail, AuthOk, AuthResponseKindError, Hello, HelloAck,
61    SUPPORTED_METHODS,
62};
63pub use io::{read_frame_async, write_frame_async, RedWireIoError};
64pub use operations::{
65    decode_bulk_ok_count_payload, decode_bulk_ok_payload, decode_delete_ok_affected,
66    decode_delete_payload, decode_error_payload, decode_get_payload, decode_get_result_payload,
67    decode_insert_dispatch_payload, decode_query_result_payload, decode_text_payload,
68    encode_bulk_insert_payload, encode_bulk_ok_count_payload, encode_bulk_ok_payload,
69    encode_bulk_ok_payload_from_json_id_literals, encode_bulk_ok_payload_from_json_ids_bytes,
70    encode_delete_ok_payload, encode_get_result_payload, encode_insert_payload, encode_key_payload,
71    encode_query_result_summary_payload, expect_bulk_ok_or_error, expect_delete_ok_or_error,
72    expect_pong_reply, expect_result_or_error, BulkOkPayload, InsertDispatchPayload, KeyPayload,
73    OperationPayloadError, OperationReplyError,
74};
75pub use prepared::{
76    decode_deallocate_payload, decode_execute_prepared_payload, decode_prepare_payload,
77    encode_deallocate_payload, encode_execute_prepared_payload, encode_prepare_payload,
78    encode_prepared_ok_payload, DeallocatePayload, ExecutePreparedPayload, PreparePayload,
79    PreparedOkPayload, PreparedPayloadError,
80};
81pub use queue::{
82    build_event_push_payload, build_event_push_payload_from_json_bytes,
83    build_queue_event_push_frame_from_json_bytes, build_queue_wait_error_frame,
84    build_queue_wait_error_payload, build_queue_wait_open_frame, build_queue_wait_open_payload,
85    build_queue_wait_timeout_frame, build_queue_wait_timeout_payload, parse_queue_wait_open,
86    QueueWaitOpenRequest, QueueWaitParseError, WAIT_CANCELLED_CODE, WAIT_EXCEEDS_CAP_CODE,
87    WAIT_FAILED_CODE,
88};
89pub use stream::{
90    build_input_stream_end_frame, build_input_stream_end_payload, build_input_stream_error_frame,
91    build_input_stream_error_payload, build_open_ack_frame, build_open_ack_payload,
92    build_open_stream_frame, build_open_stream_payload, build_stream_chunk_frame_from_json_bytes,
93    build_stream_chunk_payload, build_stream_chunk_payload_from_json_bytes, build_stream_end_frame,
94    build_stream_end_payload, build_stream_error_frame, build_stream_error_payload,
95    open_stream_is_input, parse_input_chunk, parse_input_chunk_json, parse_open_input,
96    parse_open_stream, parse_stream_cancel, ChunkParseError, InputChunk, InputChunkJson,
97    OpenInputParseError, OpenInputRequest, OpenStreamParseError, OpenStreamRequest,
98    StreamCancelRequest,
99};
100
101/// Discriminator byte every RedWire client sends as the very first
102/// byte off the wire. The service-router detector keys off this
103/// (and so does the standalone listener path).
104pub const REDWIRE_MAGIC: u8 = 0xFE;
105
106/// Highest minor version the server supports. Wire-bumped as we
107/// add features that change the handshake; data-plane additions
108/// flow through `Hello.features` instead.
109pub const MAX_KNOWN_MINOR_VERSION: u8 = 0x01;
110
111/// Default port for the RedWire listener.
112pub const DEFAULT_REDWIRE_PORT: u16 = 5050;
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum StartupError {
116    BadMagic { got: u8 },
117    UnsupportedMinor { got: u8, max: u8 },
118}
119
120impl std::fmt::Display for StartupError {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        match self {
123            Self::BadMagic { got } => {
124                write!(
125                    f,
126                    "redwire: client did not present magic byte (got 0x{got:02x})"
127                )
128            }
129            Self::UnsupportedMinor { got, max } => {
130                write!(
131                    f,
132                    "redwire: unsupported minor version {got}; max supported is {max}"
133                )
134            }
135        }
136    }
137}
138
139impl std::error::Error for StartupError {}
140
141pub fn client_preface(minor: u8) -> [u8; 2] {
142    [REDWIRE_MAGIC, minor]
143}
144
145pub fn supported_client_preface() -> [u8; 2] {
146    client_preface(MAX_KNOWN_MINOR_VERSION)
147}
148
149pub fn validate_startup_magic(got: u8) -> Result<(), StartupError> {
150    if got == REDWIRE_MAGIC {
151        Ok(())
152    } else {
153        Err(StartupError::BadMagic { got })
154    }
155}
156
157pub fn validate_minor_version(got: u8) -> Result<(), StartupError> {
158    if got <= MAX_KNOWN_MINOR_VERSION {
159        Ok(())
160    } else {
161        Err(StartupError::UnsupportedMinor {
162            got,
163            max: MAX_KNOWN_MINOR_VERSION,
164        })
165    }
166}
167
168#[cfg(test)]
169mod startup_tests {
170    use super::*;
171
172    #[test]
173    fn preface_uses_magic_and_supported_minor() {
174        assert_eq!(supported_client_preface(), [0xfe, MAX_KNOWN_MINOR_VERSION]);
175    }
176
177    #[test]
178    fn startup_validation_rejects_bad_magic_and_future_minor() {
179        assert_eq!(validate_startup_magic(REDWIRE_MAGIC), Ok(()));
180        assert!(matches!(
181            validate_startup_magic(0),
182            Err(StartupError::BadMagic { got: 0 })
183        ));
184        assert_eq!(validate_minor_version(MAX_KNOWN_MINOR_VERSION), Ok(()));
185        assert!(matches!(
186            validate_minor_version(MAX_KNOWN_MINOR_VERSION.saturating_add(1)),
187            Err(StartupError::UnsupportedMinor { .. })
188        ));
189    }
190}