1use tokio::io::{AsyncReadExt, AsyncWriteExt};
2use zeroize::Zeroizing;
3
4const MSG_CONNECT: u8 = 0x01;
5const MSG_CONNECT_OK: u8 = 0x02;
6const MSG_QUERY: u8 = 0x03;
7const MSG_QUERY_PARAMS: u8 = 0x04;
11const MSG_QUERY_SQL: u8 = 0x05;
13const MSG_SYNC_STATUS: u8 = 0x20;
16const MSG_SYNC_PULL: u8 = 0x21;
17const MSG_SYNC_ACK: u8 = 0x22;
18const MSG_SYNC_STATUS_RESULT: u8 = 0x23;
19const MSG_SYNC_PULL_RESULT: u8 = 0x24;
20const MSG_SYNC_ACK_RESULT: u8 = 0x25;
21const MSG_RESULT_ROWS: u8 = 0x07;
22const MSG_RESULT_SCALAR: u8 = 0x08;
23const MSG_RESULT_OK: u8 = 0x09;
24const MSG_ERROR: u8 = 0x0A;
25const MSG_RESULT_MSG: u8 = 0x0B;
26const MSG_DISCONNECT: u8 = 0x10;
27const MSG_PING: u8 = 0x11;
28const MSG_PONG: u8 = 0x12;
29
30const MAX_PAYLOAD_SIZE: usize = 64 * 1024 * 1024;
32
33const MAX_CONNECT_PAYLOAD_SIZE: usize = 4096;
36
37const MAX_COLUMNS: usize = 4096;
39
40const MAX_ROWS: usize = 10_000_000;
42
43const MAX_PARAMS: usize = 4096;
45
46const MAX_SYNC_UNITS: usize = 4096;
48
49const STRING_LEN_PREFIX: usize = 4; #[derive(Debug, Clone, PartialEq)]
57pub enum WireParam {
58 Null,
59 Int(i64),
60 Float(f64),
61 Bool(bool),
62 Str(String),
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum WireSyncRepairAction {
67 None,
68 Pull,
69 AwaitArchive,
70 Rebootstrap,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct WireSyncStatus {
75 pub replica_id: String,
76 pub active: bool,
77 pub last_applied_lsn: Option<u64>,
78 pub remote_lsn: u64,
79 pub servable_lsn: Option<u64>,
80 pub unarchived_lsn: Option<u64>,
81 pub lag_lsn: Option<u64>,
82 pub lag_bytes: Option<u64>,
83 pub lag_ms: Option<u64>,
84 pub stale: bool,
85 pub repair_action: WireSyncRepairAction,
86 pub last_sync_error: Option<String>,
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct WireRetainedUnit {
91 pub tx_id: u64,
92 pub record_type: u8,
93 pub lsn: u64,
94 pub data: Vec<u8>,
95}
96
97impl WireRetainedUnit {
98 pub fn encoded_len(&self) -> Result<u64, String> {
102 let data_len = u64::try_from(self.data.len())
103 .map_err(|_| "sync retained unit payload too large".to_string())?;
104 8u64.checked_add(1)
105 .and_then(|n| n.checked_add(8))
106 .and_then(|n| n.checked_add(4))
107 .and_then(|n| n.checked_add(data_len))
108 .ok_or_else(|| "sync retained unit encoded length overflow".to_string())
109 }
110}
111
112#[derive(Debug, Clone)]
113pub enum Message {
114 Connect {
115 db_name: String,
116 password: Option<Zeroizing<String>>,
121 username: Option<String>,
125 },
126 ConnectOk {
127 version: String,
128 },
129 Query {
130 query: String,
131 },
132 QuerySql {
134 query: String,
135 },
136 QueryWithParams {
138 query: String,
139 params: Vec<WireParam>,
140 },
141 SyncStatus {
143 replica_id: String,
144 },
145 SyncPull {
147 replica_id: String,
148 since_lsn: u64,
149 max_units: u32,
150 max_bytes: u64,
151 database_id: [u8; 16],
152 primary_generation: u64,
153 wal_format_version: u16,
154 catalog_version: u16,
155 segment_format_version: u16,
156 },
157 SyncAck {
159 replica_id: String,
160 applied_lsn: u64,
161 remote_lsn: u64,
162 },
163 SyncStatusResult {
164 status: WireSyncStatus,
165 },
166 SyncPullResult {
167 status: WireSyncStatus,
168 units: Vec<WireRetainedUnit>,
169 has_more: bool,
170 },
171 SyncAckResult {
172 previous_applied_lsn: u64,
173 applied_lsn: u64,
174 remote_lsn: u64,
175 advanced: bool,
176 status: WireSyncStatus,
177 },
178 ResultRows {
179 columns: Vec<String>,
180 rows: Vec<Vec<String>>,
181 },
182 ResultScalar {
183 value: String,
184 },
185 ResultOk {
186 affected: u64,
187 },
188 ResultMessage {
190 message: String,
191 },
192 Error {
193 message: String,
194 },
195 Disconnect,
196 Ping,
197 Pong,
198}
199
200impl Message {
201 pub fn encode(&self) -> Vec<u8> {
203 let (msg_type, payload) = match self {
204 Message::Connect {
205 db_name,
206 password,
207 username,
208 } => {
209 let mut buf = encode_string(db_name);
210 match password {
212 Some(p) => buf.extend_from_slice(&encode_string(p)),
213 None => buf.extend_from_slice(&0u32.to_le_bytes()),
214 }
215 match username {
218 Some(u) => buf.extend_from_slice(&encode_string(u)),
219 None => buf.extend_from_slice(&0u32.to_le_bytes()),
220 }
221 (MSG_CONNECT, buf)
222 }
223 Message::ConnectOk { version } => (MSG_CONNECT_OK, encode_string(version)),
224 Message::Query { query } => (MSG_QUERY, encode_string(query)),
225 Message::QuerySql { query } => (MSG_QUERY_SQL, encode_string(query)),
226 Message::QueryWithParams { query, params } => {
227 let mut buf = encode_string(query);
228 buf.extend_from_slice(&(params.len() as u16).to_le_bytes());
229 for p in params {
230 match p {
231 WireParam::Null => buf.push(0),
232 WireParam::Int(v) => {
233 buf.push(1);
234 buf.extend_from_slice(&v.to_le_bytes());
235 }
236 WireParam::Float(v) => {
237 buf.push(2);
238 buf.extend_from_slice(&v.to_le_bytes());
239 }
240 WireParam::Bool(v) => {
241 buf.push(3);
242 buf.push(if *v { 1 } else { 0 });
243 }
244 WireParam::Str(s) => {
245 buf.push(4);
246 buf.extend_from_slice(&encode_string(s));
247 }
248 }
249 }
250 (MSG_QUERY_PARAMS, buf)
251 }
252 Message::SyncStatus { replica_id } => (MSG_SYNC_STATUS, encode_string(replica_id)),
253 Message::SyncPull {
254 replica_id,
255 since_lsn,
256 max_units,
257 max_bytes,
258 database_id,
259 primary_generation,
260 wal_format_version,
261 catalog_version,
262 segment_format_version,
263 } => {
264 let mut buf = encode_string(replica_id);
265 buf.extend_from_slice(&since_lsn.to_le_bytes());
266 buf.extend_from_slice(&max_units.to_le_bytes());
267 buf.extend_from_slice(&max_bytes.to_le_bytes());
268 buf.extend_from_slice(database_id);
269 buf.extend_from_slice(&primary_generation.to_le_bytes());
270 buf.extend_from_slice(&wal_format_version.to_le_bytes());
271 buf.extend_from_slice(&catalog_version.to_le_bytes());
272 buf.extend_from_slice(&segment_format_version.to_le_bytes());
273 (MSG_SYNC_PULL, buf)
274 }
275 Message::SyncAck {
276 replica_id,
277 applied_lsn,
278 remote_lsn,
279 } => {
280 let mut buf = encode_string(replica_id);
281 buf.extend_from_slice(&applied_lsn.to_le_bytes());
282 buf.extend_from_slice(&remote_lsn.to_le_bytes());
283 (MSG_SYNC_ACK, buf)
284 }
285 Message::SyncStatusResult { status } => {
286 (MSG_SYNC_STATUS_RESULT, encode_sync_status(status))
287 }
288 Message::SyncPullResult {
289 status,
290 units,
291 has_more,
292 } => {
293 let mut buf = encode_sync_status(status);
294 buf.extend_from_slice(&(units.len() as u32).to_le_bytes());
295 for unit in units {
296 encode_retained_unit(&mut buf, unit);
297 }
298 buf.push(u8::from(*has_more));
299 (MSG_SYNC_PULL_RESULT, buf)
300 }
301 Message::SyncAckResult {
302 previous_applied_lsn,
303 applied_lsn,
304 remote_lsn,
305 advanced,
306 status,
307 } => {
308 let mut buf = Vec::new();
309 buf.extend_from_slice(&previous_applied_lsn.to_le_bytes());
310 buf.extend_from_slice(&applied_lsn.to_le_bytes());
311 buf.extend_from_slice(&remote_lsn.to_le_bytes());
312 buf.push(u8::from(*advanced));
313 buf.extend_from_slice(&encode_sync_status(status));
314 (MSG_SYNC_ACK_RESULT, buf)
315 }
316 Message::ResultRows { columns, rows } => {
317 let mut buf = Vec::new();
318 buf.extend_from_slice(&(columns.len() as u16).to_le_bytes());
319 for col in columns {
320 buf.extend_from_slice(&encode_string(col));
321 }
322 buf.extend_from_slice(&(rows.len() as u32).to_le_bytes());
323 for row in rows {
324 for val in row {
325 buf.extend_from_slice(&encode_string(val));
326 }
327 }
328 (MSG_RESULT_ROWS, buf)
329 }
330 Message::ResultScalar { value } => (MSG_RESULT_SCALAR, encode_string(value)),
331 Message::ResultOk { affected } => (MSG_RESULT_OK, affected.to_le_bytes().to_vec()),
332 Message::ResultMessage { message } => (MSG_RESULT_MSG, encode_string(message)),
333 Message::Error { message } => (MSG_ERROR, encode_string(message)),
334 Message::Disconnect => (MSG_DISCONNECT, Vec::new()),
335 Message::Ping => (MSG_PING, Vec::new()),
336 Message::Pong => (MSG_PONG, Vec::new()),
337 };
338
339 let mut frame = Vec::with_capacity(6 + payload.len());
340 frame.push(msg_type);
341 frame.push(0); frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
343 frame.extend_from_slice(&payload);
344 frame
345 }
346
347 pub fn decode(data: &[u8]) -> Result<Message, String> {
349 if data.len() < 6 {
350 return Err("frame too short".into());
351 }
352 let msg_type = data[0];
353 let _flags = data[1];
354 let len_bytes: [u8; 4] = data[2..6]
355 .try_into()
356 .map_err(|_| "invalid header length field".to_string())?;
357 let payload_len = u32::from_le_bytes(len_bytes) as usize;
358 if 6 + payload_len > data.len() {
359 return Err("payload length exceeds frame".into());
360 }
361 let payload = &data[6..6 + payload_len];
362
363 match msg_type {
364 MSG_CONNECT => {
365 let mut pos = 0;
366 let db_name = decode_string(payload, &mut pos)?;
367 let password = if pos < payload.len() {
370 let p = Zeroizing::new(decode_string(payload, &mut pos)?);
373 if p.is_empty() {
374 None
375 } else {
376 Some(p)
377 }
378 } else {
379 None
380 };
381 let username = if pos < payload.len() {
384 let u = decode_string(payload, &mut pos)?;
385 if u.is_empty() {
386 None
387 } else {
388 Some(u)
389 }
390 } else {
391 None
392 };
393 Ok(Message::Connect {
394 db_name,
395 password,
396 username,
397 })
398 }
399 MSG_CONNECT_OK => {
400 let version = decode_string(payload, &mut 0)?;
401 Ok(Message::ConnectOk { version })
402 }
403 MSG_QUERY => {
404 let query = decode_string(payload, &mut 0)?;
405 Ok(Message::Query { query })
406 }
407 MSG_QUERY_SQL => {
408 let query = decode_string(payload, &mut 0)?;
409 Ok(Message::QuerySql { query })
410 }
411 MSG_QUERY_PARAMS => {
412 let mut pos = 0;
413 let query = decode_string(payload, &mut pos)?;
414 if pos + 2 > payload.len() {
415 return Err("truncated param count".into());
416 }
417 let count_bytes: [u8; 2] = payload[pos..pos + 2]
418 .try_into()
419 .map_err(|_| "invalid param count bytes".to_string())?;
420 let count = u16::from_le_bytes(count_bytes) as usize;
421 pos += 2;
422 if count > MAX_PARAMS {
423 return Err("too many parameters".into());
424 }
425 let mut params = Vec::with_capacity(count.min(payload.len() - pos));
426 for _ in 0..count {
427 if pos >= payload.len() {
428 return Err("truncated param tag".into());
429 }
430 let tag = payload[pos];
431 pos += 1;
432 let p = match tag {
433 0 => WireParam::Null,
434 1 => {
435 if pos + 8 > payload.len() {
436 return Err("truncated int param".into());
437 }
438 let b: [u8; 8] = payload[pos..pos + 8]
439 .try_into()
440 .map_err(|_| "invalid int param bytes".to_string())?;
441 pos += 8;
442 WireParam::Int(i64::from_le_bytes(b))
443 }
444 2 => {
445 if pos + 8 > payload.len() {
446 return Err("truncated float param".into());
447 }
448 let b: [u8; 8] = payload[pos..pos + 8]
449 .try_into()
450 .map_err(|_| "invalid float param bytes".to_string())?;
451 pos += 8;
452 WireParam::Float(f64::from_le_bytes(b))
453 }
454 3 => {
455 if pos + 1 > payload.len() {
456 return Err("truncated bool param".into());
457 }
458 let v = payload[pos] != 0;
459 pos += 1;
460 WireParam::Bool(v)
461 }
462 4 => WireParam::Str(decode_string(payload, &mut pos)?),
463 other => return Err(format!("unknown param tag: {other}")),
464 };
465 params.push(p);
466 }
467 Ok(Message::QueryWithParams { query, params })
468 }
469 MSG_SYNC_STATUS => {
470 let replica_id = decode_string(payload, &mut 0)?;
471 Ok(Message::SyncStatus { replica_id })
472 }
473 MSG_SYNC_PULL => {
474 let mut pos = 0;
475 let replica_id = decode_string(payload, &mut pos)?;
476 let since_lsn = decode_u64(payload, &mut pos, "sync pull since LSN")?;
477 let max_units = decode_u32(payload, &mut pos, "sync pull max units")?;
478 let max_bytes = decode_u64(payload, &mut pos, "sync pull max bytes")?;
479 let database_id = decode_16_bytes(payload, &mut pos, "sync database id")?;
480 let primary_generation = decode_u64(payload, &mut pos, "sync primary generation")?;
481 let wal_format_version = decode_u16(payload, &mut pos, "sync WAL format version")?;
482 let catalog_version = decode_u16(payload, &mut pos, "sync catalog version")?;
483 let segment_format_version =
484 decode_u16(payload, &mut pos, "sync segment format version")?;
485 Ok(Message::SyncPull {
486 replica_id,
487 since_lsn,
488 max_units,
489 max_bytes,
490 database_id,
491 primary_generation,
492 wal_format_version,
493 catalog_version,
494 segment_format_version,
495 })
496 }
497 MSG_SYNC_ACK => {
498 let mut pos = 0;
499 let replica_id = decode_string(payload, &mut pos)?;
500 let applied_lsn = decode_u64(payload, &mut pos, "sync ack applied LSN")?;
501 let remote_lsn = decode_u64(payload, &mut pos, "sync ack remote LSN")?;
502 Ok(Message::SyncAck {
503 replica_id,
504 applied_lsn,
505 remote_lsn,
506 })
507 }
508 MSG_SYNC_STATUS_RESULT => {
509 let mut pos = 0;
510 let status = decode_sync_status(payload, &mut pos)?;
511 Ok(Message::SyncStatusResult { status })
512 }
513 MSG_SYNC_PULL_RESULT => {
514 let mut pos = 0;
515 let status = decode_sync_status(payload, &mut pos)?;
516 let count = decode_u32(payload, &mut pos, "sync retained unit count")? as usize;
517 if count > MAX_SYNC_UNITS {
518 return Err("too many retained units".into());
519 }
520 let mut units = Vec::with_capacity(count.min(payload.len().saturating_sub(pos)));
521 for _ in 0..count {
522 units.push(decode_retained_unit(payload, &mut pos)?);
523 }
524 let has_more = decode_bool(payload, &mut pos, "sync has_more")?;
525 Ok(Message::SyncPullResult {
526 status,
527 units,
528 has_more,
529 })
530 }
531 MSG_SYNC_ACK_RESULT => {
532 let mut pos = 0;
533 let previous_applied_lsn = decode_u64(payload, &mut pos, "previous applied LSN")?;
534 let applied_lsn = decode_u64(payload, &mut pos, "applied LSN")?;
535 let remote_lsn = decode_u64(payload, &mut pos, "remote LSN")?;
536 let advanced = decode_bool(payload, &mut pos, "sync ack advanced")?;
537 let status = decode_sync_status(payload, &mut pos)?;
538 Ok(Message::SyncAckResult {
539 previous_applied_lsn,
540 applied_lsn,
541 remote_lsn,
542 advanced,
543 status,
544 })
545 }
546 MSG_RESULT_ROWS => {
547 let mut pos = 0;
548 if pos + 2 > payload.len() {
549 return Err("truncated column count".into());
550 }
551 let col_bytes: [u8; 2] = payload[pos..pos + 2]
552 .try_into()
553 .map_err(|_| "invalid column count bytes".to_string())?;
554 let col_count = u16::from_le_bytes(col_bytes) as usize;
555 pos += 2;
556 if col_count > MAX_COLUMNS {
557 return Err("too many columns".into());
558 }
559 let mut columns =
560 Vec::with_capacity(col_count.min((payload.len() - pos) / STRING_LEN_PREFIX));
561 for _ in 0..col_count {
562 columns.push(decode_string(payload, &mut pos)?);
563 }
564 if pos + 4 > payload.len() {
565 return Err("truncated row count".into());
566 }
567 let row_bytes: [u8; 4] = payload[pos..pos + 4]
568 .try_into()
569 .map_err(|_| "invalid row count bytes".to_string())?;
570 let row_count = u32::from_le_bytes(row_bytes) as usize;
571 pos += 4;
572 if row_count > MAX_ROWS {
573 return Err("too many rows".into());
574 }
575 let max_rows = match col_count.checked_mul(STRING_LEN_PREFIX) {
581 Some(0) | None => 0,
582 Some(per_row) => (payload.len() - pos) / per_row,
583 };
584 if row_count > max_rows {
585 return Err("row count exceeds payload size".into());
586 }
587 let mut rows = Vec::with_capacity(row_count);
588 for _ in 0..row_count {
589 let mut row = Vec::with_capacity(col_count);
590 for _ in 0..col_count {
591 row.push(decode_string(payload, &mut pos)?);
592 }
593 rows.push(row);
594 }
595 Ok(Message::ResultRows { columns, rows })
596 }
597 MSG_RESULT_SCALAR => {
598 let value = decode_string(payload, &mut 0)?;
599 Ok(Message::ResultScalar { value })
600 }
601 MSG_RESULT_OK => {
602 if payload.len() < 8 {
603 return Err("truncated result ok payload".into());
604 }
605 let aff_bytes: [u8; 8] = payload[0..8]
606 .try_into()
607 .map_err(|_| "invalid affected count bytes".to_string())?;
608 let affected = u64::from_le_bytes(aff_bytes);
609 Ok(Message::ResultOk { affected })
610 }
611 MSG_RESULT_MSG => {
612 let message = decode_string(payload, &mut 0)?;
613 Ok(Message::ResultMessage { message })
614 }
615 MSG_ERROR => {
616 let message = decode_string(payload, &mut 0)?;
617 Ok(Message::Error { message })
618 }
619 MSG_DISCONNECT => Ok(Message::Disconnect),
620 MSG_PING => Ok(Message::Ping),
621 MSG_PONG => Ok(Message::Pong),
622 _ => Err(format!("unknown message type: {msg_type:#x}")),
623 }
624 }
625
626 pub async fn write_to<W: AsyncWriteExt + Unpin>(&self, writer: &mut W) -> std::io::Result<()> {
628 let bytes = self.encode();
629 writer.write_all(&bytes).await
630 }
631
632 pub async fn read_from<R: AsyncReadExt + Unpin>(
634 reader: &mut R,
635 ) -> std::io::Result<Option<Message>> {
636 Self::read_from_with_limit(reader, MAX_PAYLOAD_SIZE).await
637 }
638
639 pub async fn read_from_preauth<R: AsyncReadExt + Unpin>(
643 reader: &mut R,
644 ) -> std::io::Result<Option<Message>> {
645 Self::read_from_with_limit(reader, MAX_CONNECT_PAYLOAD_SIZE).await
646 }
647
648 async fn read_from_with_limit<R: AsyncReadExt + Unpin>(
650 reader: &mut R,
651 max_payload: usize,
652 ) -> std::io::Result<Option<Message>> {
653 let mut header = [0u8; 6];
654 match reader.read_exact(&mut header).await {
655 Ok(_) => {}
656 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
657 Err(e) => return Err(e),
658 }
659 let len_bytes: [u8; 4] = header[2..6].try_into().map_err(|_| {
660 std::io::Error::new(
661 std::io::ErrorKind::InvalidData,
662 "invalid header length field",
663 )
664 })?;
665 let payload_len = u32::from_le_bytes(len_bytes) as usize;
666 if payload_len > max_payload {
667 return Err(std::io::Error::new(
668 std::io::ErrorKind::InvalidData,
669 format!("payload too large: {payload_len} bytes (max {max_payload})"),
670 ));
671 }
672 let mut payload = vec![0u8; payload_len];
673 if payload_len > 0 {
674 reader.read_exact(&mut payload).await?;
675 }
676
677 let mut full = Vec::with_capacity(6 + payload_len);
678 full.extend_from_slice(&header);
679 full.extend_from_slice(&payload);
680
681 Message::decode(&full)
682 .map(Some)
683 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
684 }
685}
686
687fn encode_string(s: &str) -> Vec<u8> {
688 let mut buf = Vec::with_capacity(4 + s.len());
689 buf.extend_from_slice(&(s.len() as u32).to_le_bytes());
690 buf.extend_from_slice(s.as_bytes());
691 buf
692}
693
694fn decode_string(data: &[u8], pos: &mut usize) -> Result<String, String> {
695 if *pos + 4 > data.len() {
696 return Err("truncated string length".into());
697 }
698 let len_bytes: [u8; 4] = data[*pos..*pos + 4]
699 .try_into()
700 .map_err(|_| "invalid string length bytes".to_string())?;
701 let len = u32::from_le_bytes(len_bytes) as usize;
702 *pos += 4;
703 if *pos + len > data.len() {
704 return Err("truncated string data".into());
705 }
706 let s = String::from_utf8_lossy(&data[*pos..*pos + len]).into_owned();
707 *pos += len;
708 Ok(s)
709}
710
711fn encode_option_u64(out: &mut Vec<u8>, value: Option<u64>) {
712 match value {
713 Some(value) => {
714 out.push(1);
715 out.extend_from_slice(&value.to_le_bytes());
716 }
717 None => out.push(0),
718 }
719}
720
721fn decode_option_u64(data: &[u8], pos: &mut usize, label: &str) -> Result<Option<u64>, String> {
722 let present = decode_bool(data, pos, label)?;
723 if present {
724 Ok(Some(decode_u64(data, pos, label)?))
725 } else {
726 Ok(None)
727 }
728}
729
730fn encode_option_string(out: &mut Vec<u8>, value: Option<&String>) {
731 match value {
732 Some(value) => {
733 out.push(1);
734 out.extend_from_slice(&encode_string(value));
735 }
736 None => out.push(0),
737 }
738}
739
740fn decode_option_string(
741 data: &[u8],
742 pos: &mut usize,
743 label: &str,
744) -> Result<Option<String>, String> {
745 let present = decode_bool(data, pos, label)?;
746 if present {
747 Ok(Some(decode_string(data, pos)?))
748 } else {
749 Ok(None)
750 }
751}
752
753fn encode_sync_status(status: &WireSyncStatus) -> Vec<u8> {
754 let mut out = Vec::new();
755 out.extend_from_slice(&encode_string(&status.replica_id));
756 out.push(u8::from(status.active));
757 encode_option_u64(&mut out, status.last_applied_lsn);
758 out.extend_from_slice(&status.remote_lsn.to_le_bytes());
759 encode_option_u64(&mut out, status.servable_lsn);
760 encode_option_u64(&mut out, status.unarchived_lsn);
761 encode_option_u64(&mut out, status.lag_lsn);
762 encode_option_u64(&mut out, status.lag_bytes);
763 encode_option_u64(&mut out, status.lag_ms);
764 out.push(u8::from(status.stale));
765 out.push(match status.repair_action {
766 WireSyncRepairAction::None => 0,
767 WireSyncRepairAction::Pull => 1,
768 WireSyncRepairAction::AwaitArchive => 2,
769 WireSyncRepairAction::Rebootstrap => 3,
770 });
771 encode_option_string(&mut out, status.last_sync_error.as_ref());
772 out
773}
774
775fn decode_sync_status(data: &[u8], pos: &mut usize) -> Result<WireSyncStatus, String> {
776 let replica_id = decode_string(data, pos)?;
777 let active = decode_bool(data, pos, "sync status active")?;
778 let last_applied_lsn = decode_option_u64(data, pos, "sync status last applied LSN")?;
779 let remote_lsn = decode_u64(data, pos, "sync status remote LSN")?;
780 let servable_lsn = decode_option_u64(data, pos, "sync status servable LSN")?;
781 let unarchived_lsn = decode_option_u64(data, pos, "sync status unarchived LSN")?;
782 let lag_lsn = decode_option_u64(data, pos, "sync status lag LSN")?;
783 let lag_bytes = decode_option_u64(data, pos, "sync status lag bytes")?;
784 let lag_ms = decode_option_u64(data, pos, "sync status lag milliseconds")?;
785 let stale = decode_bool(data, pos, "sync status stale")?;
786 if *pos >= data.len() {
787 return Err("truncated sync repair action".into());
788 }
789 let repair_action = match data[*pos] {
790 0 => WireSyncRepairAction::None,
791 1 => WireSyncRepairAction::Pull,
792 2 => WireSyncRepairAction::AwaitArchive,
793 3 => WireSyncRepairAction::Rebootstrap,
794 other => return Err(format!("unknown sync repair action: {other}")),
795 };
796 *pos += 1;
797 let last_sync_error = decode_option_string(data, pos, "sync status last error")?;
798 Ok(WireSyncStatus {
799 replica_id,
800 active,
801 last_applied_lsn,
802 remote_lsn,
803 servable_lsn,
804 unarchived_lsn,
805 lag_lsn,
806 lag_bytes,
807 lag_ms,
808 stale,
809 repair_action,
810 last_sync_error,
811 })
812}
813
814fn encode_retained_unit(out: &mut Vec<u8>, unit: &WireRetainedUnit) {
815 out.extend_from_slice(&unit.tx_id.to_le_bytes());
816 out.push(unit.record_type);
817 out.extend_from_slice(&unit.lsn.to_le_bytes());
818 out.extend_from_slice(&(unit.data.len() as u32).to_le_bytes());
819 out.extend_from_slice(&unit.data);
820}
821
822fn decode_retained_unit(data: &[u8], pos: &mut usize) -> Result<WireRetainedUnit, String> {
823 let tx_id = decode_u64(data, pos, "sync retained unit tx id")?;
824 if *pos >= data.len() {
825 return Err("truncated sync retained unit record type".into());
826 }
827 let record_type = data[*pos];
828 *pos += 1;
829 let lsn = decode_u64(data, pos, "sync retained unit LSN")?;
830 let data = decode_bytes(data, pos, "sync retained unit payload")?;
831 Ok(WireRetainedUnit {
832 tx_id,
833 record_type,
834 lsn,
835 data,
836 })
837}
838
839fn decode_bool(data: &[u8], pos: &mut usize, label: &str) -> Result<bool, String> {
840 if *pos >= data.len() {
841 return Err(format!("truncated {label}"));
842 }
843 let raw = data[*pos];
844 *pos += 1;
845 match raw {
846 0 => Ok(false),
847 1 => Ok(true),
848 other => Err(format!("invalid boolean for {label}: {other}")),
849 }
850}
851
852fn decode_u16(data: &[u8], pos: &mut usize, label: &str) -> Result<u16, String> {
853 if *pos + 2 > data.len() {
854 return Err(format!("truncated {label}"));
855 }
856 let bytes: [u8; 2] = data[*pos..*pos + 2]
857 .try_into()
858 .map_err(|_| format!("invalid {label} bytes"))?;
859 *pos += 2;
860 Ok(u16::from_le_bytes(bytes))
861}
862
863fn decode_u32(data: &[u8], pos: &mut usize, label: &str) -> Result<u32, String> {
864 if *pos + 4 > data.len() {
865 return Err(format!("truncated {label}"));
866 }
867 let bytes: [u8; 4] = data[*pos..*pos + 4]
868 .try_into()
869 .map_err(|_| format!("invalid {label} bytes"))?;
870 *pos += 4;
871 Ok(u32::from_le_bytes(bytes))
872}
873
874fn decode_u64(data: &[u8], pos: &mut usize, label: &str) -> Result<u64, String> {
875 if *pos + 8 > data.len() {
876 return Err(format!("truncated {label}"));
877 }
878 let bytes: [u8; 8] = data[*pos..*pos + 8]
879 .try_into()
880 .map_err(|_| format!("invalid {label} bytes"))?;
881 *pos += 8;
882 Ok(u64::from_le_bytes(bytes))
883}
884
885fn decode_16_bytes(data: &[u8], pos: &mut usize, label: &str) -> Result<[u8; 16], String> {
886 if *pos + 16 > data.len() {
887 return Err(format!("truncated {label}"));
888 }
889 let mut out = [0u8; 16];
890 out.copy_from_slice(&data[*pos..*pos + 16]);
891 *pos += 16;
892 Ok(out)
893}
894
895fn decode_bytes(data: &[u8], pos: &mut usize, label: &str) -> Result<Vec<u8>, String> {
896 let len = decode_u32(data, pos, label)? as usize;
897 if *pos + len > data.len() {
898 return Err(format!("truncated {label}"));
899 }
900 let out = data[*pos..*pos + len].to_vec();
901 *pos += len;
902 Ok(out)
903}
904
905#[cfg(test)]
906mod tests {
907 use super::*;
908
909 #[test]
910 fn test_encode_decode_query() {
911 let msg = Message::Query {
912 query: "User filter .age > 30".into(),
913 };
914 let bytes = msg.encode();
915 let decoded = Message::decode(&bytes).unwrap();
916 match decoded {
917 Message::Query { query } => assert_eq!(query, "User filter .age > 30"),
918 _ => panic!("expected Query"),
919 }
920 }
921
922 #[test]
923 fn test_encode_decode_connect_with_username() {
924 let msg = Message::Connect {
925 db_name: "mydb".into(),
926 password: Some(Zeroizing::new("secret".into())),
927 username: Some("alice".into()),
928 };
929 let bytes = msg.encode();
930 match Message::decode(&bytes).unwrap() {
931 Message::Connect {
932 db_name,
933 password,
934 username,
935 } => {
936 assert_eq!(db_name, "mydb");
937 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("secret"));
938 assert_eq!(username.as_deref(), Some("alice"));
939 }
940 other => panic!("expected Connect, got {other:?}"),
941 }
942 }
943
944 #[test]
945 fn test_encode_decode_connect_without_username() {
946 let msg = Message::Connect {
948 db_name: "mydb".into(),
949 password: Some(Zeroizing::new("secret".into())),
950 username: None,
951 };
952 let bytes = msg.encode();
953 match Message::decode(&bytes).unwrap() {
954 Message::Connect {
955 db_name,
956 password,
957 username,
958 } => {
959 assert_eq!(db_name, "mydb");
960 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("secret"));
961 assert_eq!(username, None);
962 }
963 other => panic!("expected Connect, got {other:?}"),
964 }
965 }
966
967 #[test]
968 fn test_decode_old_client_connect_db_and_password_only() {
969 let mut payload = encode_string("mydb");
972 payload.extend_from_slice(&encode_string("pw"));
973 let mut frame = vec![MSG_CONNECT, 0];
974 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
975 frame.extend_from_slice(&payload);
976 match Message::decode(&frame).unwrap() {
977 Message::Connect {
978 db_name,
979 password,
980 username,
981 } => {
982 assert_eq!(db_name, "mydb");
983 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("pw"));
984 assert_eq!(username, None, "old-client frame must yield username=None");
985 }
986 other => panic!("expected Connect, got {other:?}"),
987 }
988 }
989
990 #[test]
991 fn test_decode_old_client_connect_db_only() {
992 let payload = encode_string("mydb");
994 let mut frame = vec![MSG_CONNECT, 0];
995 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
996 frame.extend_from_slice(&payload);
997 match Message::decode(&frame).unwrap() {
998 Message::Connect {
999 db_name,
1000 password,
1001 username,
1002 } => {
1003 assert_eq!(db_name, "mydb");
1004 assert_eq!(password, None);
1005 assert_eq!(username, None);
1006 }
1007 other => panic!("expected Connect, got {other:?}"),
1008 }
1009 }
1010
1011 #[test]
1012 fn test_encode_decode_result_rows() {
1013 let msg = Message::ResultRows {
1014 columns: vec!["name".into(), "age".into()],
1015 rows: vec![
1016 vec!["Alice".into(), "30".into()],
1017 vec!["Bob".into(), "25".into()],
1018 ],
1019 };
1020 let bytes = msg.encode();
1021 let decoded = Message::decode(&bytes).unwrap();
1022 match decoded {
1023 Message::ResultRows { columns, rows } => {
1024 assert_eq!(columns, vec!["name", "age"]);
1025 assert_eq!(rows.len(), 2);
1026 }
1027 _ => panic!("expected ResultRows"),
1028 }
1029 }
1030
1031 #[test]
1032 fn test_encode_decode_result_message() {
1033 let msg = Message::ResultMessage {
1034 message: "type User created".into(),
1035 };
1036 let bytes = msg.encode();
1037 let decoded = Message::decode(&bytes).unwrap();
1038 match decoded {
1039 Message::ResultMessage { message } => assert_eq!(message, "type User created"),
1040 _ => panic!("expected ResultMessage"),
1041 }
1042 }
1043
1044 #[test]
1045 fn test_encode_decode_error() {
1046 let msg = Message::Error {
1047 message: "table not found".into(),
1048 };
1049 let bytes = msg.encode();
1050 let decoded = Message::decode(&bytes).unwrap();
1051 match decoded {
1052 Message::Error { message } => assert_eq!(message, "table not found"),
1053 _ => panic!("expected Error"),
1054 }
1055 }
1056
1057 #[test]
1058 fn test_encode_decode_query_sql() {
1059 let msg = Message::QuerySql {
1060 query: "SELECT * FROM User".into(),
1061 };
1062 let decoded = Message::decode(&msg.encode()).unwrap();
1063 match decoded {
1064 Message::QuerySql { query } => assert_eq!(query, "SELECT * FROM User"),
1065 other => panic!("expected QuerySql, got {other:?}"),
1066 }
1067 }
1068
1069 #[test]
1070 fn test_encode_decode_query_with_params() {
1071 let msg = Message::QueryWithParams {
1072 query: "insert User { name := $1, age := $2, ok := $3, note := $4 }".into(),
1073 params: vec![
1074 WireParam::Str(r#"a"b\c; drop User"#.into()),
1075 WireParam::Int(-7),
1076 WireParam::Bool(true),
1077 WireParam::Null,
1078 ],
1079 };
1080 let bytes = msg.encode();
1081 assert_eq!(bytes[0], 0x04);
1083 match Message::decode(&bytes).unwrap() {
1084 Message::QueryWithParams { query, params } => {
1085 assert!(query.contains("$1"));
1086 assert_eq!(params.len(), 4);
1087 assert!(matches!(¶ms[0], WireParam::Str(s) if s == r#"a"b\c; drop User"#));
1088 assert!(matches!(¶ms[1], WireParam::Int(-7)));
1089 assert!(matches!(¶ms[2], WireParam::Bool(true)));
1090 assert!(matches!(¶ms[3], WireParam::Null));
1091 }
1092 other => panic!("expected QueryWithParams, got {other:?}"),
1093 }
1094 }
1095
1096 #[test]
1097 fn test_query_with_params_float_round_trip() {
1098 let msg = Message::QueryWithParams {
1099 query: "T filter .f = $1".into(),
1100 params: vec![WireParam::Float(2.5)],
1101 };
1102 match Message::decode(&msg.encode()).unwrap() {
1103 Message::QueryWithParams { params, .. } => {
1104 assert!(matches!(¶ms[0], WireParam::Float(f) if (*f - 2.5).abs() < 1e-12));
1105 }
1106 other => panic!("expected QueryWithParams, got {other:?}"),
1107 }
1108 }
1109
1110 fn sample_sync_status() -> WireSyncStatus {
1111 WireSyncStatus {
1112 replica_id: "replica-a".into(),
1113 active: true,
1114 last_applied_lsn: Some(7),
1115 remote_lsn: 10,
1116 servable_lsn: Some(10),
1117 unarchived_lsn: Some(0),
1118 lag_lsn: Some(3),
1119 lag_bytes: Some(2048),
1120 lag_ms: Some(5000),
1121 stale: true,
1122 repair_action: WireSyncRepairAction::Pull,
1123 last_sync_error: None,
1124 }
1125 }
1126
1127 #[test]
1128 fn test_encode_decode_sync_requests() {
1129 let database_id = *b"sync-protocol!!!";
1130 let pull = Message::SyncPull {
1131 replica_id: "replica-a".into(),
1132 since_lsn: 7,
1133 max_units: 128,
1134 max_bytes: 4096,
1135 database_id,
1136 primary_generation: 9,
1137 wal_format_version: 1,
1138 catalog_version: 2,
1139 segment_format_version: 1,
1140 };
1141 let bytes = pull.encode();
1142 assert_eq!(bytes[0], MSG_SYNC_PULL);
1143 match Message::decode(&bytes).unwrap() {
1144 Message::SyncPull {
1145 replica_id,
1146 since_lsn,
1147 max_units,
1148 max_bytes,
1149 database_id: decoded_database_id,
1150 primary_generation,
1151 wal_format_version,
1152 catalog_version,
1153 segment_format_version,
1154 } => {
1155 assert_eq!(replica_id, "replica-a");
1156 assert_eq!(since_lsn, 7);
1157 assert_eq!(max_units, 128);
1158 assert_eq!(max_bytes, 4096);
1159 assert_eq!(decoded_database_id, database_id);
1160 assert_eq!(primary_generation, 9);
1161 assert_eq!(wal_format_version, 1);
1162 assert_eq!(catalog_version, 2);
1163 assert_eq!(segment_format_version, 1);
1164 }
1165 other => panic!("expected SyncPull, got {other:?}"),
1166 }
1167
1168 let ack = Message::SyncAck {
1169 replica_id: "replica-a".into(),
1170 applied_lsn: 10,
1171 remote_lsn: 10,
1172 };
1173 match Message::decode(&ack.encode()).unwrap() {
1174 Message::SyncAck {
1175 replica_id,
1176 applied_lsn,
1177 remote_lsn,
1178 } => {
1179 assert_eq!(replica_id, "replica-a");
1180 assert_eq!(applied_lsn, 10);
1181 assert_eq!(remote_lsn, 10);
1182 }
1183 other => panic!("expected SyncAck, got {other:?}"),
1184 }
1185 }
1186
1187 #[test]
1188 fn test_encode_decode_sync_results() {
1189 let status = sample_sync_status();
1190 match Message::decode(
1191 &Message::SyncStatusResult {
1192 status: status.clone(),
1193 }
1194 .encode(),
1195 )
1196 .unwrap()
1197 {
1198 Message::SyncStatusResult { status: decoded } => assert_eq!(decoded, status),
1199 other => panic!("expected SyncStatusResult, got {other:?}"),
1200 }
1201
1202 let await_archive_status = WireSyncStatus {
1203 servable_lsn: Some(7),
1204 unarchived_lsn: Some(3),
1205 repair_action: WireSyncRepairAction::AwaitArchive,
1206 last_sync_error: Some("primary WAL is not yet archived".into()),
1207 ..status.clone()
1208 };
1209 match Message::decode(
1210 &Message::SyncStatusResult {
1211 status: await_archive_status.clone(),
1212 }
1213 .encode(),
1214 )
1215 .unwrap()
1216 {
1217 Message::SyncStatusResult { status: decoded } => {
1218 assert_eq!(decoded, await_archive_status)
1219 }
1220 other => panic!("expected AwaitArchive SyncStatusResult, got {other:?}"),
1221 }
1222
1223 let units = vec![
1224 WireRetainedUnit {
1225 tx_id: 1,
1226 record_type: 4,
1227 lsn: 8,
1228 data: vec![1, 2, 3],
1229 },
1230 WireRetainedUnit {
1231 tx_id: 1,
1232 record_type: 4,
1233 lsn: 9,
1234 data: vec![4, 5],
1235 },
1236 ];
1237 let empty_pull_len = Message::SyncPullResult {
1238 status: status.clone(),
1239 units: Vec::new(),
1240 has_more: true,
1241 }
1242 .encode()
1243 .len();
1244 let populated_pull_len = Message::SyncPullResult {
1245 status: status.clone(),
1246 units: units.clone(),
1247 has_more: true,
1248 }
1249 .encode()
1250 .len();
1251 let expected_unit_len: u64 = units
1252 .iter()
1253 .map(WireRetainedUnit::encoded_len)
1254 .collect::<Result<Vec<_>, _>>()
1255 .unwrap()
1256 .into_iter()
1257 .sum();
1258 assert_eq!(
1259 u64::try_from(populated_pull_len - empty_pull_len).unwrap(),
1260 expected_unit_len,
1261 "retained-unit encoded length must track SyncPullResult wire shape"
1262 );
1263
1264 match Message::decode(
1265 &Message::SyncPullResult {
1266 status: status.clone(),
1267 units: units.clone(),
1268 has_more: true,
1269 }
1270 .encode(),
1271 )
1272 .unwrap()
1273 {
1274 Message::SyncPullResult {
1275 status: decoded_status,
1276 units: decoded_units,
1277 has_more,
1278 } => {
1279 assert_eq!(decoded_status, status);
1280 assert_eq!(decoded_units, units);
1281 assert!(has_more);
1282 }
1283 other => panic!("expected SyncPullResult, got {other:?}"),
1284 }
1285
1286 match Message::decode(
1287 &Message::SyncAckResult {
1288 previous_applied_lsn: 7,
1289 applied_lsn: 10,
1290 remote_lsn: 10,
1291 advanced: true,
1292 status: WireSyncStatus {
1293 stale: false,
1294 repair_action: WireSyncRepairAction::None,
1295 lag_lsn: Some(0),
1296 lag_bytes: Some(0),
1297 lag_ms: Some(0),
1298 ..status
1299 },
1300 }
1301 .encode(),
1302 )
1303 .unwrap()
1304 {
1305 Message::SyncAckResult {
1306 previous_applied_lsn,
1307 applied_lsn,
1308 remote_lsn,
1309 advanced,
1310 status,
1311 } => {
1312 assert_eq!(previous_applied_lsn, 7);
1313 assert_eq!(applied_lsn, 10);
1314 assert_eq!(remote_lsn, 10);
1315 assert!(advanced);
1316 assert!(!status.stale);
1317 }
1318 other => panic!("expected SyncAckResult, got {other:?}"),
1319 }
1320 }
1321
1322 #[test]
1323 fn test_decode_garbage_never_panics() {
1324 let cases: Vec<Vec<u8>> = vec![
1328 vec![], vec![0x03], vec![0x03, 0x00, 0x00, 0x00, 0x00], vec![0xFF, 0x00, 0x00, 0x00, 0x00, 0x00], vec![0x03, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
1334 {
1336 let mut payload = encode_string("replica-a");
1337 let mut frame = vec![MSG_SYNC_PULL, 0];
1338 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
1339 frame.append(&mut payload);
1340 frame
1341 },
1342 {
1344 let mut payload = encode_sync_status(&sample_sync_status());
1345 payload.extend_from_slice(&((MAX_SYNC_UNITS as u32) + 1).to_le_bytes());
1346 let mut frame = vec![MSG_SYNC_PULL_RESULT, 0];
1347 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
1348 frame.extend_from_slice(&payload);
1349 frame
1350 },
1351 vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
1353 vec![0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF],
1355 vec![0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03],
1357 vec![0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
1359 vec![
1361 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, ],
1365 vec![
1367 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x02, 0x03,
1372 ],
1373 vec![
1375 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, ],
1381 vec![
1383 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x63, ],
1388 ];
1389 for bytes in cases {
1390 let result = Message::decode(&bytes);
1391 assert!(
1392 result.is_err(),
1393 "expected Err for malformed input {bytes:?}, got {result:?}"
1394 );
1395 }
1396 }
1397
1398 #[test]
1399 fn test_decode_arbitrary_prefixes_never_panic() {
1400 let valid = Message::ResultRows {
1403 columns: vec!["a".into(), "b".into()],
1404 rows: vec![vec!["1".into(), "2".into()]],
1405 }
1406 .encode();
1407 for end in 0..valid.len() {
1408 let _ = Message::decode(&valid[..end]);
1410 }
1411 for i in 0..valid.len() {
1413 let mut m = valid.clone();
1414 m[i] = 0xFF;
1415 let _ = Message::decode(&m);
1416 }
1417 }
1418
1419 #[test]
1420 fn test_decode_result_rows_rejects_amplified_row_count() {
1421 let mut payload = Vec::new();
1426 payload.extend_from_slice(&0u16.to_le_bytes()); payload.extend_from_slice(&10_000_000u32.to_le_bytes()); let mut frame = vec![MSG_RESULT_ROWS, 0];
1429 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
1430 frame.extend_from_slice(&payload);
1431 assert!(
1432 Message::decode(&frame).is_err(),
1433 "amplified row count must be rejected"
1434 );
1435
1436 let msg = Message::ResultRows {
1438 columns: vec!["a".into()],
1439 rows: vec![vec!["x".into()]],
1440 };
1441 match Message::decode(&msg.encode()).unwrap() {
1442 Message::ResultRows { columns, rows } => {
1443 assert_eq!(columns, vec!["a"]);
1444 assert_eq!(rows, vec![vec!["x".to_string()]]);
1445 }
1446 other => panic!("expected ResultRows, got {other:?}"),
1447 }
1448 }
1449
1450 #[test]
1451 fn test_frame_length() {
1452 let msg = Message::Query {
1453 query: "User".into(),
1454 };
1455 let bytes = msg.encode();
1456 assert!(bytes.len() >= 6);
1457 let payload_len = u32::from_le_bytes(bytes[2..6].try_into().unwrap()) as usize;
1458 assert_eq!(bytes.len(), 6 + payload_len);
1459 }
1460}