1pub 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 scram;
23pub mod stream;
24pub mod ws_gate;
25
26pub use builder::{
27 build_bulk_insert_binary_frame, build_bulk_insert_frame, build_bye_frame, build_delete_frame,
28 build_dispatch_reply_frame, build_error_frame, build_error_frame_lossy, build_get_frame,
29 build_ping_frame, build_query_frame, build_query_with_params_frame, build_reply_frame,
30 build_request_frame, rewrap_length_prefixed_handler_response, BuildError, FrameBuilder,
31};
32pub use bulk_binary::{
33 decode_bulk_binary_payload, encode_bulk_binary_payload, BulkBinaryError, BulkBinaryFlavor,
34 BulkBinaryPayload,
35};
36pub use bulk_json::{
37 decode_bulk_json_payload, encode_bulk_json_payload, BulkJsonError, BulkJsonPayload,
38};
39pub use bulk_stream::{
40 decode_bulk_stream_rows_payload, decode_bulk_stream_start_payload,
41 encode_bulk_stream_rows_payload, encode_bulk_stream_start_payload, BulkStreamError,
42 BulkStreamRowsPayload, BulkStreamStartPayload,
43};
44pub use codec::{
45 decode_frame, decode_frame_parts, encode_frame, frame_len_from_header, FrameError,
46};
47pub use cursor::{
48 decode_close_cursor_payload, decode_declare_cursor_payload, decode_fetch_payload,
49 encode_close_cursor_payload, encode_cursor_batch_payload, encode_cursor_ok_payload,
50 encode_declare_cursor_payload, encode_fetch_payload, CloseCursorPayload, CursorPayloadError,
51 DeclareCursorPayload, FetchPayload,
52};
53pub use frame::{
54 Flags, Frame, MessageClass, MessageDirection, MessageKind, FRAME_HEADER_SIZE, MAX_FRAME_SIZE,
55};
56pub use handshake::{
57 build_auth_fail_frame, build_auth_fail_payload, build_auth_ok_frame_from_payload,
58 build_auth_ok_payload, build_auth_response_anonymous_payload,
59 build_auth_response_bearer_payload, build_auth_response_frame,
60 build_auth_response_oauth_jwt_payload, build_client_hello_frame, build_client_hello_payload,
61 build_hello_ack, build_hello_ack_frame, build_hello_payload, choose_hello_minor_version,
62 expect_auth_response_payload, AuthFail, AuthOk, AuthResponseKindError, Hello, HelloAck,
63 SUPPORTED_METHODS,
64};
65pub use io::{
66 drain_next_frame, frame_to_bytes, read_frame_async, write_frame_async, RedWireIoError,
67};
68pub use operations::{
69 decode_bulk_ok_count_payload, decode_bulk_ok_payload, decode_delete_ok_affected,
70 decode_delete_payload, decode_error_payload, decode_get_payload, decode_get_result_payload,
71 decode_insert_dispatch_payload, decode_query_result_payload, decode_text_payload,
72 encode_bulk_insert_payload, encode_bulk_ok_count_payload, encode_bulk_ok_payload,
73 encode_bulk_ok_payload_from_json_id_literals, encode_bulk_ok_payload_from_json_ids_bytes,
74 encode_delete_ok_payload, encode_get_result_payload, encode_insert_payload, encode_key_payload,
75 encode_query_result_summary_payload, expect_bulk_ok_or_error, expect_delete_ok_or_error,
76 expect_pong_reply, expect_result_or_error, BulkOkPayload, InsertDispatchPayload, KeyPayload,
77 OperationPayloadError, OperationReplyError,
78};
79pub use prepared::{
80 decode_deallocate_payload, decode_execute_prepared_payload, decode_prepare_payload,
81 encode_deallocate_payload, encode_execute_prepared_payload, encode_prepare_payload,
82 encode_prepared_ok_payload, DeallocatePayload, ExecutePreparedPayload, PreparePayload,
83 PreparedOkPayload, PreparedPayloadError,
84};
85pub use queue::{
86 build_event_push_payload, build_event_push_payload_from_json_bytes,
87 build_queue_event_push_frame_from_json_bytes, build_queue_wait_error_frame,
88 build_queue_wait_error_payload, build_queue_wait_open_frame, build_queue_wait_open_payload,
89 build_queue_wait_timeout_frame, build_queue_wait_timeout_payload, parse_queue_wait_open,
90 QueueWaitOpenRequest, QueueWaitParseError, WAIT_CANCELLED_CODE, WAIT_EXCEEDS_CAP_CODE,
91 WAIT_FAILED_CODE,
92};
93pub use stream::{
94 build_input_stream_end_frame, build_input_stream_end_payload, build_input_stream_error_frame,
95 build_input_stream_error_payload, build_open_ack_frame, build_open_ack_payload,
96 build_open_stream_frame, build_open_stream_payload, build_stream_chunk_frame_from_json_bytes,
97 build_stream_chunk_payload, build_stream_chunk_payload_from_json_bytes, build_stream_end_frame,
98 build_stream_end_payload, build_stream_error_frame, build_stream_error_payload,
99 open_stream_is_input, parse_input_chunk, parse_input_chunk_json, parse_open_input,
100 parse_open_stream, parse_stream_cancel, ChunkParseError, InputChunk, InputChunkJson,
101 OpenInputParseError, OpenInputRequest, OpenStreamParseError, OpenStreamRequest,
102 StreamCancelRequest,
103};
104pub use ws_gate::{evaluate_ws_upgrade, WsUpgradeRefusal};
105
106pub const REDWIRE_MAGIC: u8 = 0xFE;
110
111pub const MAX_KNOWN_MINOR_VERSION: u8 = 0x01;
115
116pub const DEFAULT_REDWIRE_PORT: u16 = 5050;
118
119pub const REDWIRE_WS_SUBPROTOCOL: &str = "reddb.redwire.v1";
125
126pub const REDWIRE_WS_PATH: &str = "/redwire";
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum StartupError {
132 BadMagic { got: u8 },
133 UnsupportedMinor { got: u8, max: u8 },
134}
135
136impl std::fmt::Display for StartupError {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 match self {
139 Self::BadMagic { got } => {
140 write!(
141 f,
142 "redwire: client did not present magic byte (got 0x{got:02x})"
143 )
144 }
145 Self::UnsupportedMinor { got, max } => {
146 write!(
147 f,
148 "redwire: unsupported minor version {got}; max supported is {max}"
149 )
150 }
151 }
152 }
153}
154
155impl std::error::Error for StartupError {}
156
157pub fn client_preface(minor: u8) -> [u8; 2] {
158 [REDWIRE_MAGIC, minor]
159}
160
161pub fn supported_client_preface() -> [u8; 2] {
162 client_preface(MAX_KNOWN_MINOR_VERSION)
163}
164
165pub fn validate_startup_magic(got: u8) -> Result<(), StartupError> {
166 if got == REDWIRE_MAGIC {
167 Ok(())
168 } else {
169 Err(StartupError::BadMagic { got })
170 }
171}
172
173pub fn validate_minor_version(got: u8) -> Result<(), StartupError> {
174 if got <= MAX_KNOWN_MINOR_VERSION {
175 Ok(())
176 } else {
177 Err(StartupError::UnsupportedMinor {
178 got,
179 max: MAX_KNOWN_MINOR_VERSION,
180 })
181 }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188pub enum RedWireMagicMatch {
189 Pending,
191 Match,
193 NoMatch,
195}
196
197pub fn redwire_magic_match(peek: &[u8]) -> RedWireMagicMatch {
202 match peek.first() {
203 None => RedWireMagicMatch::Pending,
204 Some(&first) if first == REDWIRE_MAGIC => RedWireMagicMatch::Match,
205 Some(_) => RedWireMagicMatch::NoMatch,
206 }
207}
208
209#[cfg(test)]
210mod startup_tests {
211 use super::*;
212
213 #[test]
214 fn preface_uses_magic_and_supported_minor() {
215 assert_eq!(supported_client_preface(), [0xfe, MAX_KNOWN_MINOR_VERSION]);
216 }
217
218 #[test]
219 fn startup_validation_rejects_bad_magic_and_future_minor() {
220 assert_eq!(validate_startup_magic(REDWIRE_MAGIC), Ok(()));
221 assert!(matches!(
222 validate_startup_magic(0),
223 Err(StartupError::BadMagic { got: 0 })
224 ));
225 assert_eq!(validate_minor_version(MAX_KNOWN_MINOR_VERSION), Ok(()));
226 assert!(matches!(
227 validate_minor_version(MAX_KNOWN_MINOR_VERSION.saturating_add(1)),
228 Err(StartupError::UnsupportedMinor { .. })
229 ));
230 }
231
232 #[test]
233 fn magic_match_classifies_peek_buffer() {
234 assert_eq!(redwire_magic_match(&[]), RedWireMagicMatch::Pending);
236 assert_eq!(
238 redwire_magic_match(&[REDWIRE_MAGIC]),
239 RedWireMagicMatch::Match
240 );
241 assert_eq!(
242 redwire_magic_match(&[0xFE, 0x01, 0x10]),
243 RedWireMagicMatch::Match
244 );
245 assert_eq!(redwire_magic_match(b"GET "), RedWireMagicMatch::NoMatch);
247 assert_eq!(redwire_magic_match(&[0x10]), RedWireMagicMatch::NoMatch);
248 }
249}