1use powdb_storage::pj1::pj1_validate;
2use powdb_storage::types::{TypeId, Value};
3use tokio::io::{AsyncReadExt, AsyncWriteExt};
4use zeroize::Zeroizing;
5
6const MSG_CONNECT: u8 = 0x01;
7const MSG_CONNECT_OK: u8 = 0x02;
8const MSG_QUERY: u8 = 0x03;
9const MSG_QUERY_PARAMS: u8 = 0x04;
13const MSG_QUERY_SQL: u8 = 0x05;
15const MSG_SYNC_STATUS: u8 = 0x20;
18const MSG_SYNC_PULL: u8 = 0x21;
19const MSG_SYNC_ACK: u8 = 0x22;
20const MSG_SYNC_STATUS_RESULT: u8 = 0x23;
21const MSG_SYNC_PULL_RESULT: u8 = 0x24;
22const MSG_SYNC_ACK_RESULT: u8 = 0x25;
23const MSG_RESULT_ROWS: u8 = 0x07;
24const MSG_RESULT_SCALAR: u8 = 0x08;
25const MSG_RESULT_OK: u8 = 0x09;
26const MSG_ERROR: u8 = 0x0A;
27const MSG_RESULT_MSG: u8 = 0x0B;
28const MSG_DISCONNECT: u8 = 0x10;
29const MSG_PING: u8 = 0x11;
30const MSG_PONG: u8 = 0x12;
31pub const MSG_QUERY_NATIVE: u8 = 0x13;
34pub const MSG_QUERY_PARAMS_NATIVE: u8 = 0x14;
36pub const MSG_QUERY_SQL_NATIVE: u8 = 0x15;
38pub const MSG_RESULT_ROWS_NATIVE: u8 = 0x16;
40pub const MSG_RESULT_SCALAR_NATIVE: u8 = 0x17;
42
43const MAX_PAYLOAD_SIZE: usize = 64 * 1024 * 1024;
45
46const MAX_CONNECT_PAYLOAD_SIZE: usize = 4096;
49
50const MAX_COLUMNS: usize = 4096;
52
53const MAX_ROWS: usize = 10_000_000;
55
56const MAX_PARAMS: usize = 4096;
58
59const MAX_SYNC_UNITS: usize = 4096;
61
62const STRING_LEN_PREFIX: usize = 4; #[derive(Debug, Clone, PartialEq)]
70pub enum WireParam {
71 Null,
72 Int(i64),
73 Float(f64),
74 Bool(bool),
75 Str(String),
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum WireSyncRepairAction {
80 None,
81 Pull,
82 AwaitArchive,
83 Rebootstrap,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct WireSyncStatus {
88 pub replica_id: String,
89 pub active: bool,
90 pub last_applied_lsn: Option<u64>,
91 pub remote_lsn: u64,
92 pub servable_lsn: Option<u64>,
93 pub unarchived_lsn: Option<u64>,
94 pub lag_lsn: Option<u64>,
95 pub lag_bytes: Option<u64>,
96 pub lag_ms: Option<u64>,
97 pub stale: bool,
98 pub repair_action: WireSyncRepairAction,
99 pub last_sync_error: Option<String>,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
103pub struct WireRetainedUnit {
104 pub tx_id: u64,
105 pub record_type: u8,
106 pub lsn: u64,
107 pub data: Vec<u8>,
108}
109
110impl WireRetainedUnit {
111 pub fn encoded_len(&self) -> Result<u64, String> {
115 let data_len = u64::try_from(self.data.len())
116 .map_err(|_| "sync retained unit payload too large".to_string())?;
117 8u64.checked_add(1)
118 .and_then(|n| n.checked_add(8))
119 .and_then(|n| n.checked_add(4))
120 .and_then(|n| n.checked_add(data_len))
121 .ok_or_else(|| "sync retained unit encoded length overflow".to_string())
122 }
123}
124
125#[derive(Debug, Clone)]
126pub enum Message {
127 Connect {
128 db_name: String,
129 password: Option<Zeroizing<String>>,
134 username: Option<String>,
138 },
139 ConnectOk {
140 version: String,
141 },
142 Query {
143 query: String,
144 },
145 QuerySql {
147 query: String,
148 },
149 QueryWithParams {
151 query: String,
152 params: Vec<WireParam>,
153 },
154 QueryNative {
156 query: String,
157 },
158 QueryWithParamsNative {
160 query: String,
161 params: Vec<WireParam>,
162 },
163 QuerySqlNative {
165 query: String,
166 },
167 SyncStatus {
169 replica_id: String,
170 },
171 SyncPull {
173 replica_id: String,
174 since_lsn: u64,
175 max_units: u32,
176 max_bytes: u64,
177 database_id: [u8; 16],
178 primary_generation: u64,
179 wal_format_version: u16,
180 catalog_version: u16,
181 segment_format_version: u16,
182 },
183 SyncAck {
185 replica_id: String,
186 applied_lsn: u64,
187 remote_lsn: u64,
188 },
189 SyncStatusResult {
190 status: WireSyncStatus,
191 },
192 SyncPullResult {
193 status: WireSyncStatus,
194 units: Vec<WireRetainedUnit>,
195 has_more: bool,
196 },
197 SyncAckResult {
198 previous_applied_lsn: u64,
199 applied_lsn: u64,
200 remote_lsn: u64,
201 advanced: bool,
202 status: WireSyncStatus,
203 },
204 ResultRows {
205 columns: Vec<String>,
206 rows: Vec<Vec<String>>,
207 },
208 ResultScalar {
209 value: String,
210 },
211 ResultRowsNative {
212 columns: Vec<String>,
213 rows: Vec<Vec<Value>>,
214 },
215 ResultScalarNative {
216 value: Value,
217 },
218 ResultOk {
219 affected: u64,
220 },
221 ResultMessage {
223 message: String,
224 },
225 Error {
226 message: String,
227 },
228 Disconnect,
229 Ping,
230 Pong,
231}
232
233impl Message {
234 pub fn encode(&self) -> Vec<u8> {
236 let (msg_type, payload) = match self {
237 Message::Connect {
238 db_name,
239 password,
240 username,
241 } => {
242 let mut buf = encode_string(db_name);
243 match password {
245 Some(p) => buf.extend_from_slice(&encode_string(p)),
246 None => buf.extend_from_slice(&0u32.to_le_bytes()),
247 }
248 match username {
251 Some(u) => buf.extend_from_slice(&encode_string(u)),
252 None => buf.extend_from_slice(&0u32.to_le_bytes()),
253 }
254 (MSG_CONNECT, buf)
255 }
256 Message::ConnectOk { version } => (MSG_CONNECT_OK, encode_string(version)),
257 Message::Query { query } => (MSG_QUERY, encode_string(query)),
258 Message::QuerySql { query } => (MSG_QUERY_SQL, encode_string(query)),
259 Message::QueryWithParams { query, params } => {
260 (MSG_QUERY_PARAMS, encode_query_with_params(query, params))
261 }
262 Message::QueryNative { query } => (MSG_QUERY_NATIVE, encode_string(query)),
263 Message::QueryWithParamsNative { query, params } => (
264 MSG_QUERY_PARAMS_NATIVE,
265 encode_query_with_params(query, params),
266 ),
267 Message::QuerySqlNative { query } => (MSG_QUERY_SQL_NATIVE, encode_string(query)),
268 Message::SyncStatus { replica_id } => (MSG_SYNC_STATUS, encode_string(replica_id)),
269 Message::SyncPull {
270 replica_id,
271 since_lsn,
272 max_units,
273 max_bytes,
274 database_id,
275 primary_generation,
276 wal_format_version,
277 catalog_version,
278 segment_format_version,
279 } => {
280 let mut buf = encode_string(replica_id);
281 buf.extend_from_slice(&since_lsn.to_le_bytes());
282 buf.extend_from_slice(&max_units.to_le_bytes());
283 buf.extend_from_slice(&max_bytes.to_le_bytes());
284 buf.extend_from_slice(database_id);
285 buf.extend_from_slice(&primary_generation.to_le_bytes());
286 buf.extend_from_slice(&wal_format_version.to_le_bytes());
287 buf.extend_from_slice(&catalog_version.to_le_bytes());
288 buf.extend_from_slice(&segment_format_version.to_le_bytes());
289 (MSG_SYNC_PULL, buf)
290 }
291 Message::SyncAck {
292 replica_id,
293 applied_lsn,
294 remote_lsn,
295 } => {
296 let mut buf = encode_string(replica_id);
297 buf.extend_from_slice(&applied_lsn.to_le_bytes());
298 buf.extend_from_slice(&remote_lsn.to_le_bytes());
299 (MSG_SYNC_ACK, buf)
300 }
301 Message::SyncStatusResult { status } => {
302 (MSG_SYNC_STATUS_RESULT, encode_sync_status(status))
303 }
304 Message::SyncPullResult {
305 status,
306 units,
307 has_more,
308 } => {
309 let mut buf = encode_sync_status(status);
310 buf.extend_from_slice(&(units.len() as u32).to_le_bytes());
311 for unit in units {
312 encode_retained_unit(&mut buf, unit);
313 }
314 buf.push(u8::from(*has_more));
315 (MSG_SYNC_PULL_RESULT, buf)
316 }
317 Message::SyncAckResult {
318 previous_applied_lsn,
319 applied_lsn,
320 remote_lsn,
321 advanced,
322 status,
323 } => {
324 let mut buf = Vec::new();
325 buf.extend_from_slice(&previous_applied_lsn.to_le_bytes());
326 buf.extend_from_slice(&applied_lsn.to_le_bytes());
327 buf.extend_from_slice(&remote_lsn.to_le_bytes());
328 buf.push(u8::from(*advanced));
329 buf.extend_from_slice(&encode_sync_status(status));
330 (MSG_SYNC_ACK_RESULT, buf)
331 }
332 Message::ResultRows { columns, rows } => {
333 let mut buf = Vec::new();
334 buf.extend_from_slice(&(columns.len() as u16).to_le_bytes());
335 for col in columns {
336 buf.extend_from_slice(&encode_string(col));
337 }
338 buf.extend_from_slice(&(rows.len() as u32).to_le_bytes());
339 for row in rows {
340 for val in row {
341 buf.extend_from_slice(&encode_string(val));
342 }
343 }
344 (MSG_RESULT_ROWS, buf)
345 }
346 Message::ResultScalar { value } => (MSG_RESULT_SCALAR, encode_string(value)),
347 Message::ResultRowsNative { columns, rows } => {
348 let mut buf = Vec::new();
349 buf.extend_from_slice(&(columns.len() as u16).to_le_bytes());
350 for column in columns {
351 buf.extend_from_slice(&encode_string(column));
352 }
353 buf.extend_from_slice(&(rows.len() as u32).to_le_bytes());
354 for row in rows {
355 for value in row {
356 encode_typed_value(&mut buf, value);
357 }
358 }
359 (MSG_RESULT_ROWS_NATIVE, buf)
360 }
361 Message::ResultScalarNative { value } => {
362 let mut buf = Vec::new();
363 encode_typed_value(&mut buf, value);
364 (MSG_RESULT_SCALAR_NATIVE, buf)
365 }
366 Message::ResultOk { affected } => (MSG_RESULT_OK, affected.to_le_bytes().to_vec()),
367 Message::ResultMessage { message } => (MSG_RESULT_MSG, encode_string(message)),
368 Message::Error { message } => (MSG_ERROR, encode_string(message)),
369 Message::Disconnect => (MSG_DISCONNECT, Vec::new()),
370 Message::Ping => (MSG_PING, Vec::new()),
371 Message::Pong => (MSG_PONG, Vec::new()),
372 };
373
374 let mut frame = Vec::with_capacity(6 + payload.len());
375 frame.push(msg_type);
376 frame.push(0); frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
378 frame.extend_from_slice(&payload);
379 frame
380 }
381
382 pub fn decode(data: &[u8]) -> Result<Message, String> {
384 if data.len() < 6 {
385 return Err("frame too short".into());
386 }
387 let msg_type = data[0];
388 let _flags = data[1];
389 let len_bytes: [u8; 4] = data[2..6]
390 .try_into()
391 .map_err(|_| "invalid header length field".to_string())?;
392 let payload_len = u32::from_le_bytes(len_bytes) as usize;
393 if 6 + payload_len > data.len() {
394 return Err("payload length exceeds frame".into());
395 }
396 let payload = &data[6..6 + payload_len];
397
398 match msg_type {
399 MSG_CONNECT => {
400 let mut pos = 0;
401 let db_name = decode_string(payload, &mut pos)?;
402 let password = if pos < payload.len() {
405 let p = Zeroizing::new(decode_string(payload, &mut pos)?);
408 if p.is_empty() {
409 None
410 } else {
411 Some(p)
412 }
413 } else {
414 None
415 };
416 let username = if pos < payload.len() {
419 let u = decode_string(payload, &mut pos)?;
420 if u.is_empty() {
421 None
422 } else {
423 Some(u)
424 }
425 } else {
426 None
427 };
428 Ok(Message::Connect {
429 db_name,
430 password,
431 username,
432 })
433 }
434 MSG_CONNECT_OK => {
435 let version = decode_string(payload, &mut 0)?;
436 Ok(Message::ConnectOk { version })
437 }
438 MSG_QUERY => {
439 let query = decode_string(payload, &mut 0)?;
440 Ok(Message::Query { query })
441 }
442 MSG_QUERY_SQL => {
443 let query = decode_string(payload, &mut 0)?;
444 Ok(Message::QuerySql { query })
445 }
446 MSG_QUERY_PARAMS => {
447 let mut pos = 0;
448 let query = decode_string(payload, &mut pos)?;
449 if pos + 2 > payload.len() {
450 return Err("truncated param count".into());
451 }
452 let count_bytes: [u8; 2] = payload[pos..pos + 2]
453 .try_into()
454 .map_err(|_| "invalid param count bytes".to_string())?;
455 let count = u16::from_le_bytes(count_bytes) as usize;
456 pos += 2;
457 if count > MAX_PARAMS {
458 return Err("too many parameters".into());
459 }
460 let mut params = Vec::with_capacity(count.min(payload.len() - pos));
461 for _ in 0..count {
462 if pos >= payload.len() {
463 return Err("truncated param tag".into());
464 }
465 let tag = payload[pos];
466 pos += 1;
467 let p = match tag {
468 0 => WireParam::Null,
469 1 => {
470 if pos + 8 > payload.len() {
471 return Err("truncated int param".into());
472 }
473 let b: [u8; 8] = payload[pos..pos + 8]
474 .try_into()
475 .map_err(|_| "invalid int param bytes".to_string())?;
476 pos += 8;
477 WireParam::Int(i64::from_le_bytes(b))
478 }
479 2 => {
480 if pos + 8 > payload.len() {
481 return Err("truncated float param".into());
482 }
483 let b: [u8; 8] = payload[pos..pos + 8]
484 .try_into()
485 .map_err(|_| "invalid float param bytes".to_string())?;
486 pos += 8;
487 WireParam::Float(f64::from_le_bytes(b))
488 }
489 3 => {
490 if pos + 1 > payload.len() {
491 return Err("truncated bool param".into());
492 }
493 let v = payload[pos] != 0;
494 pos += 1;
495 WireParam::Bool(v)
496 }
497 4 => WireParam::Str(decode_string(payload, &mut pos)?),
498 other => return Err(format!("unknown param tag: {other}")),
499 };
500 params.push(p);
501 }
502 Ok(Message::QueryWithParams { query, params })
503 }
504 MSG_QUERY_NATIVE => {
505 let query = decode_exact_string(payload, "native PowQL query")?;
506 Ok(Message::QueryNative { query })
507 }
508 MSG_QUERY_PARAMS_NATIVE => {
509 let (query, params) = decode_query_with_params_exact(payload)?;
510 Ok(Message::QueryWithParamsNative { query, params })
511 }
512 MSG_QUERY_SQL_NATIVE => {
513 let query = decode_exact_string(payload, "native SQL query")?;
514 Ok(Message::QuerySqlNative { query })
515 }
516 MSG_SYNC_STATUS => {
517 let replica_id = decode_string(payload, &mut 0)?;
518 Ok(Message::SyncStatus { replica_id })
519 }
520 MSG_SYNC_PULL => {
521 let mut pos = 0;
522 let replica_id = decode_string(payload, &mut pos)?;
523 let since_lsn = decode_u64(payload, &mut pos, "sync pull since LSN")?;
524 let max_units = decode_u32(payload, &mut pos, "sync pull max units")?;
525 let max_bytes = decode_u64(payload, &mut pos, "sync pull max bytes")?;
526 let database_id = decode_16_bytes(payload, &mut pos, "sync database id")?;
527 let primary_generation = decode_u64(payload, &mut pos, "sync primary generation")?;
528 let wal_format_version = decode_u16(payload, &mut pos, "sync WAL format version")?;
529 let catalog_version = decode_u16(payload, &mut pos, "sync catalog version")?;
530 let segment_format_version =
531 decode_u16(payload, &mut pos, "sync segment format version")?;
532 Ok(Message::SyncPull {
533 replica_id,
534 since_lsn,
535 max_units,
536 max_bytes,
537 database_id,
538 primary_generation,
539 wal_format_version,
540 catalog_version,
541 segment_format_version,
542 })
543 }
544 MSG_SYNC_ACK => {
545 let mut pos = 0;
546 let replica_id = decode_string(payload, &mut pos)?;
547 let applied_lsn = decode_u64(payload, &mut pos, "sync ack applied LSN")?;
548 let remote_lsn = decode_u64(payload, &mut pos, "sync ack remote LSN")?;
549 Ok(Message::SyncAck {
550 replica_id,
551 applied_lsn,
552 remote_lsn,
553 })
554 }
555 MSG_SYNC_STATUS_RESULT => {
556 let mut pos = 0;
557 let status = decode_sync_status(payload, &mut pos)?;
558 Ok(Message::SyncStatusResult { status })
559 }
560 MSG_SYNC_PULL_RESULT => {
561 let mut pos = 0;
562 let status = decode_sync_status(payload, &mut pos)?;
563 let count = decode_u32(payload, &mut pos, "sync retained unit count")? as usize;
564 if count > MAX_SYNC_UNITS {
565 return Err("too many retained units".into());
566 }
567 let mut units = Vec::with_capacity(count.min(payload.len().saturating_sub(pos)));
568 for _ in 0..count {
569 units.push(decode_retained_unit(payload, &mut pos)?);
570 }
571 let has_more = decode_bool(payload, &mut pos, "sync has_more")?;
572 Ok(Message::SyncPullResult {
573 status,
574 units,
575 has_more,
576 })
577 }
578 MSG_SYNC_ACK_RESULT => {
579 let mut pos = 0;
580 let previous_applied_lsn = decode_u64(payload, &mut pos, "previous applied LSN")?;
581 let applied_lsn = decode_u64(payload, &mut pos, "applied LSN")?;
582 let remote_lsn = decode_u64(payload, &mut pos, "remote LSN")?;
583 let advanced = decode_bool(payload, &mut pos, "sync ack advanced")?;
584 let status = decode_sync_status(payload, &mut pos)?;
585 Ok(Message::SyncAckResult {
586 previous_applied_lsn,
587 applied_lsn,
588 remote_lsn,
589 advanced,
590 status,
591 })
592 }
593 MSG_RESULT_ROWS => {
594 let mut pos = 0;
595 if pos + 2 > payload.len() {
596 return Err("truncated column count".into());
597 }
598 let col_bytes: [u8; 2] = payload[pos..pos + 2]
599 .try_into()
600 .map_err(|_| "invalid column count bytes".to_string())?;
601 let col_count = u16::from_le_bytes(col_bytes) as usize;
602 pos += 2;
603 if col_count > MAX_COLUMNS {
604 return Err("too many columns".into());
605 }
606 let mut columns =
607 Vec::with_capacity(col_count.min((payload.len() - pos) / STRING_LEN_PREFIX));
608 for _ in 0..col_count {
609 columns.push(decode_string(payload, &mut pos)?);
610 }
611 if pos + 4 > payload.len() {
612 return Err("truncated row count".into());
613 }
614 let row_bytes: [u8; 4] = payload[pos..pos + 4]
615 .try_into()
616 .map_err(|_| "invalid row count bytes".to_string())?;
617 let row_count = u32::from_le_bytes(row_bytes) as usize;
618 pos += 4;
619 if row_count > MAX_ROWS {
620 return Err("too many rows".into());
621 }
622 let max_rows = match col_count.checked_mul(STRING_LEN_PREFIX) {
628 Some(0) | None => 0,
629 Some(per_row) => (payload.len() - pos) / per_row,
630 };
631 if row_count > max_rows {
632 return Err("row count exceeds payload size".into());
633 }
634 let mut rows = Vec::with_capacity(row_count);
635 for _ in 0..row_count {
636 let mut row = Vec::with_capacity(col_count);
637 for _ in 0..col_count {
638 row.push(decode_string(payload, &mut pos)?);
639 }
640 rows.push(row);
641 }
642 Ok(Message::ResultRows { columns, rows })
643 }
644 MSG_RESULT_SCALAR => {
645 let value = decode_string(payload, &mut 0)?;
646 Ok(Message::ResultScalar { value })
647 }
648 MSG_RESULT_ROWS_NATIVE => decode_native_rows(payload),
649 MSG_RESULT_SCALAR_NATIVE => {
650 let mut pos = 0;
651 let value = decode_typed_value(payload, &mut pos)?;
652 require_payload_end(payload, pos, "native scalar")?;
653 Ok(Message::ResultScalarNative { value })
654 }
655 MSG_RESULT_OK => {
656 if payload.len() < 8 {
657 return Err("truncated result ok payload".into());
658 }
659 let aff_bytes: [u8; 8] = payload[0..8]
660 .try_into()
661 .map_err(|_| "invalid affected count bytes".to_string())?;
662 let affected = u64::from_le_bytes(aff_bytes);
663 Ok(Message::ResultOk { affected })
664 }
665 MSG_RESULT_MSG => {
666 let message = decode_string(payload, &mut 0)?;
667 Ok(Message::ResultMessage { message })
668 }
669 MSG_ERROR => {
670 let message = decode_string(payload, &mut 0)?;
671 Ok(Message::Error { message })
672 }
673 MSG_DISCONNECT => Ok(Message::Disconnect),
674 MSG_PING => Ok(Message::Ping),
675 MSG_PONG => Ok(Message::Pong),
676 _ => Err(format!("unknown message type: {msg_type:#x}")),
677 }
678 }
679
680 pub async fn write_to<W: AsyncWriteExt + Unpin>(&self, writer: &mut W) -> std::io::Result<()> {
682 let bytes = self.encode();
683 writer.write_all(&bytes).await
684 }
685
686 pub async fn read_from<R: AsyncReadExt + Unpin>(
688 reader: &mut R,
689 ) -> std::io::Result<Option<Message>> {
690 Self::read_from_with_limit(reader, MAX_PAYLOAD_SIZE).await
691 }
692
693 pub async fn read_from_preauth<R: AsyncReadExt + Unpin>(
697 reader: &mut R,
698 ) -> std::io::Result<Option<Message>> {
699 Self::read_from_with_limit(reader, MAX_CONNECT_PAYLOAD_SIZE).await
700 }
701
702 async fn read_from_with_limit<R: AsyncReadExt + Unpin>(
704 reader: &mut R,
705 max_payload: usize,
706 ) -> std::io::Result<Option<Message>> {
707 let mut header = [0u8; 6];
708 match reader.read_exact(&mut header).await {
709 Ok(_) => {}
710 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
711 Err(e) => return Err(e),
712 }
713 let len_bytes: [u8; 4] = header[2..6].try_into().map_err(|_| {
714 std::io::Error::new(
715 std::io::ErrorKind::InvalidData,
716 "invalid header length field",
717 )
718 })?;
719 let payload_len = u32::from_le_bytes(len_bytes) as usize;
720 if payload_len > max_payload {
721 return Err(std::io::Error::new(
722 std::io::ErrorKind::InvalidData,
723 format!("payload too large: {payload_len} bytes (max {max_payload})"),
724 ));
725 }
726 let mut payload = vec![0u8; payload_len];
727 if payload_len > 0 {
728 reader.read_exact(&mut payload).await?;
729 }
730
731 let mut full = Vec::with_capacity(6 + payload_len);
732 full.extend_from_slice(&header);
733 full.extend_from_slice(&payload);
734
735 Message::decode(&full)
736 .map(Some)
737 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
738 }
739}
740
741fn encode_query_with_params(query: &str, params: &[WireParam]) -> Vec<u8> {
742 let mut buf = encode_string(query);
743 buf.extend_from_slice(&(params.len() as u16).to_le_bytes());
744 for param in params {
745 match param {
746 WireParam::Null => buf.push(0),
747 WireParam::Int(value) => {
748 buf.push(1);
749 buf.extend_from_slice(&value.to_le_bytes());
750 }
751 WireParam::Float(value) => {
752 buf.push(2);
753 buf.extend_from_slice(&value.to_le_bytes());
754 }
755 WireParam::Bool(value) => {
756 buf.push(3);
757 buf.push(u8::from(*value));
758 }
759 WireParam::Str(value) => {
760 buf.push(4);
761 buf.extend_from_slice(&encode_string(value));
762 }
763 }
764 }
765 buf
766}
767
768fn decode_query_with_params_exact(payload: &[u8]) -> Result<(String, Vec<WireParam>), String> {
769 let mut pos = 0;
770 let query = decode_string_strict(payload, &mut pos, "native query")?;
771 let count = decode_u16(payload, &mut pos, "native param count")? as usize;
772 if count > MAX_PARAMS {
773 return Err("too many parameters".into());
774 }
775 if count > payload.len().saturating_sub(pos) {
777 return Err("parameter count exceeds payload size".into());
778 }
779 let mut params = Vec::with_capacity(count);
780 for _ in 0..count {
781 if pos >= payload.len() {
782 return Err("truncated param tag".into());
783 }
784 let tag = payload[pos];
785 pos += 1;
786 params.push(match tag {
787 0 => WireParam::Null,
788 1 => {
789 let bytes = take_exact(payload, &mut pos, 8, "int param")?;
790 WireParam::Int(i64::from_le_bytes(bytes.try_into().expect("8 bytes")))
791 }
792 2 => {
793 let bytes = take_exact(payload, &mut pos, 8, "float param")?;
794 WireParam::Float(f64::from_le_bytes(bytes.try_into().expect("8 bytes")))
795 }
796 3 => WireParam::Bool(decode_bool(payload, &mut pos, "bool param")?),
797 4 => WireParam::Str(decode_string_strict(payload, &mut pos, "string param")?),
798 other => return Err(format!("unknown param tag: {other}")),
799 });
800 }
801 require_payload_end(payload, pos, "native parameterized query")?;
802 Ok((query, params))
803}
804
805fn encode_typed_value(out: &mut Vec<u8>, value: &Value) {
806 out.push(value.type_id() as u8);
807 let body_len = match value {
808 Value::Empty => 0,
809 Value::Int(_) | Value::Float(_) | Value::DateTime(_) => 8,
810 Value::Bool(_) => 1,
811 Value::Str(value) => value.len(),
812 Value::Uuid(_) => 16,
813 Value::Bytes(value) => value.len(),
814 Value::Json(value) => value.len(),
815 };
816 out.extend_from_slice(&(body_len as u32).to_le_bytes());
817 match value {
818 Value::Empty => {}
819 Value::Int(value) | Value::DateTime(value) => out.extend_from_slice(&value.to_le_bytes()),
820 Value::Float(value) => out.extend_from_slice(&value.to_le_bytes()),
821 Value::Bool(value) => out.push(u8::from(*value)),
822 Value::Str(value) => out.extend_from_slice(value.as_bytes()),
823 Value::Uuid(value) => out.extend_from_slice(value),
824 Value::Bytes(value) => out.extend_from_slice(value),
825 Value::Json(value) => out.extend_from_slice(value),
826 }
827}
828
829fn decode_typed_value(data: &[u8], pos: &mut usize) -> Result<Value, String> {
830 if *pos >= data.len() {
831 return Err("truncated typed value tag".into());
832 }
833 let raw_type = data[*pos];
834 *pos += 1;
835 let type_id =
836 TypeId::from_u8(raw_type).ok_or_else(|| format!("unknown typed value tag: {raw_type}"))?;
837 let body_len = decode_u32(data, pos, "typed value body length")? as usize;
838 let body = take_exact(data, pos, body_len, "typed value body")?;
839
840 let require_len = |expected: usize| {
841 if body_len == expected {
842 Ok(())
843 } else {
844 Err(format!(
845 "invalid {type_id:?} typed value length: expected {expected}, got {body_len}"
846 ))
847 }
848 };
849 match type_id {
850 TypeId::Empty => {
851 require_len(0)?;
852 Ok(Value::Empty)
853 }
854 TypeId::Int => {
855 require_len(8)?;
856 Ok(Value::Int(i64::from_le_bytes(
857 body.try_into().expect("validated 8-byte int"),
858 )))
859 }
860 TypeId::Float => {
861 require_len(8)?;
862 Ok(Value::Float(f64::from_le_bytes(
863 body.try_into().expect("validated 8-byte float"),
864 )))
865 }
866 TypeId::Bool => {
867 require_len(1)?;
868 match body[0] {
869 0 => Ok(Value::Bool(false)),
870 1 => Ok(Value::Bool(true)),
871 other => Err(format!("invalid typed boolean: {other}")),
872 }
873 }
874 TypeId::Str => Ok(Value::Str(
875 std::str::from_utf8(body)
876 .map_err(|error| format!("invalid UTF-8 in typed string: {error}"))?
877 .to_owned(),
878 )),
879 TypeId::DateTime => {
880 require_len(8)?;
881 Ok(Value::DateTime(i64::from_le_bytes(
882 body.try_into().expect("validated 8-byte datetime"),
883 )))
884 }
885 TypeId::Uuid => {
886 require_len(16)?;
887 Ok(Value::Uuid(
888 body.try_into().expect("validated 16-byte UUID"),
889 ))
890 }
891 TypeId::Bytes => Ok(Value::Bytes(body.to_vec())),
892 TypeId::Json => {
893 pj1_validate(body).map_err(|error| format!("invalid typed PJ1 JSON: {error}"))?;
894 Ok(Value::Json(body.into()))
895 }
896 }
897}
898
899fn decode_native_rows(payload: &[u8]) -> Result<Message, String> {
900 let mut pos = 0;
901 let col_count = decode_u16(payload, &mut pos, "native column count")? as usize;
902 if col_count > MAX_COLUMNS {
903 return Err("too many columns".into());
904 }
905 let mut columns = Vec::with_capacity(col_count);
906 for _ in 0..col_count {
907 columns.push(decode_string_strict(
908 payload,
909 &mut pos,
910 "native column name",
911 )?);
912 }
913 let row_count = decode_u32(payload, &mut pos, "native row count")? as usize;
914 if row_count > MAX_ROWS {
915 return Err("too many rows".into());
916 }
917 let minimum_row_len = col_count
919 .checked_mul(5)
920 .ok_or_else(|| "native row width overflow".to_string())?;
921 if minimum_row_len == 0 {
922 if row_count != 0 {
923 return Err("nonzero native row count with zero columns".into());
924 }
925 } else if row_count > payload.len().saturating_sub(pos) / minimum_row_len {
926 return Err("native row count exceeds payload size".into());
927 }
928
929 let mut rows = Vec::with_capacity(row_count);
930 for _ in 0..row_count {
931 let mut row = Vec::with_capacity(col_count);
932 for _ in 0..col_count {
933 row.push(decode_typed_value(payload, &mut pos)?);
934 }
935 rows.push(row);
936 }
937 require_payload_end(payload, pos, "native rows")?;
938 Ok(Message::ResultRowsNative { columns, rows })
939}
940
941fn take_exact<'a>(
942 data: &'a [u8],
943 pos: &mut usize,
944 len: usize,
945 label: &str,
946) -> Result<&'a [u8], String> {
947 let end = pos
948 .checked_add(len)
949 .ok_or_else(|| format!("{label} length overflow"))?;
950 if end > data.len() {
951 return Err(format!("truncated {label}"));
952 }
953 let bytes = &data[*pos..end];
954 *pos = end;
955 Ok(bytes)
956}
957
958fn require_payload_end(payload: &[u8], pos: usize, label: &str) -> Result<(), String> {
959 if pos == payload.len() {
960 Ok(())
961 } else {
962 Err(format!("trailing bytes in {label} payload"))
963 }
964}
965
966fn decode_exact_string(payload: &[u8], label: &str) -> Result<String, String> {
967 let mut pos = 0;
968 let value = decode_string_strict(payload, &mut pos, label)?;
969 require_payload_end(payload, pos, label)?;
970 Ok(value)
971}
972
973fn decode_string_strict(data: &[u8], pos: &mut usize, label: &str) -> Result<String, String> {
974 let len = decode_u32(data, pos, label)? as usize;
975 let bytes = take_exact(data, pos, len, label)?;
976 std::str::from_utf8(bytes)
977 .map(str::to_owned)
978 .map_err(|error| format!("invalid UTF-8 in {label}: {error}"))
979}
980
981fn encode_string(s: &str) -> Vec<u8> {
982 let mut buf = Vec::with_capacity(4 + s.len());
983 buf.extend_from_slice(&(s.len() as u32).to_le_bytes());
984 buf.extend_from_slice(s.as_bytes());
985 buf
986}
987
988fn decode_string(data: &[u8], pos: &mut usize) -> Result<String, String> {
989 if *pos + 4 > data.len() {
990 return Err("truncated string length".into());
991 }
992 let len_bytes: [u8; 4] = data[*pos..*pos + 4]
993 .try_into()
994 .map_err(|_| "invalid string length bytes".to_string())?;
995 let len = u32::from_le_bytes(len_bytes) as usize;
996 *pos += 4;
997 if *pos + len > data.len() {
998 return Err("truncated string data".into());
999 }
1000 let s = String::from_utf8_lossy(&data[*pos..*pos + len]).into_owned();
1001 *pos += len;
1002 Ok(s)
1003}
1004
1005fn encode_option_u64(out: &mut Vec<u8>, value: Option<u64>) {
1006 match value {
1007 Some(value) => {
1008 out.push(1);
1009 out.extend_from_slice(&value.to_le_bytes());
1010 }
1011 None => out.push(0),
1012 }
1013}
1014
1015fn decode_option_u64(data: &[u8], pos: &mut usize, label: &str) -> Result<Option<u64>, String> {
1016 let present = decode_bool(data, pos, label)?;
1017 if present {
1018 Ok(Some(decode_u64(data, pos, label)?))
1019 } else {
1020 Ok(None)
1021 }
1022}
1023
1024fn encode_option_string(out: &mut Vec<u8>, value: Option<&String>) {
1025 match value {
1026 Some(value) => {
1027 out.push(1);
1028 out.extend_from_slice(&encode_string(value));
1029 }
1030 None => out.push(0),
1031 }
1032}
1033
1034fn decode_option_string(
1035 data: &[u8],
1036 pos: &mut usize,
1037 label: &str,
1038) -> Result<Option<String>, String> {
1039 let present = decode_bool(data, pos, label)?;
1040 if present {
1041 Ok(Some(decode_string(data, pos)?))
1042 } else {
1043 Ok(None)
1044 }
1045}
1046
1047fn encode_sync_status(status: &WireSyncStatus) -> Vec<u8> {
1048 let mut out = Vec::new();
1049 out.extend_from_slice(&encode_string(&status.replica_id));
1050 out.push(u8::from(status.active));
1051 encode_option_u64(&mut out, status.last_applied_lsn);
1052 out.extend_from_slice(&status.remote_lsn.to_le_bytes());
1053 encode_option_u64(&mut out, status.servable_lsn);
1054 encode_option_u64(&mut out, status.unarchived_lsn);
1055 encode_option_u64(&mut out, status.lag_lsn);
1056 encode_option_u64(&mut out, status.lag_bytes);
1057 encode_option_u64(&mut out, status.lag_ms);
1058 out.push(u8::from(status.stale));
1059 out.push(match status.repair_action {
1060 WireSyncRepairAction::None => 0,
1061 WireSyncRepairAction::Pull => 1,
1062 WireSyncRepairAction::AwaitArchive => 2,
1063 WireSyncRepairAction::Rebootstrap => 3,
1064 });
1065 encode_option_string(&mut out, status.last_sync_error.as_ref());
1066 out
1067}
1068
1069fn decode_sync_status(data: &[u8], pos: &mut usize) -> Result<WireSyncStatus, String> {
1070 let replica_id = decode_string(data, pos)?;
1071 let active = decode_bool(data, pos, "sync status active")?;
1072 let last_applied_lsn = decode_option_u64(data, pos, "sync status last applied LSN")?;
1073 let remote_lsn = decode_u64(data, pos, "sync status remote LSN")?;
1074 let servable_lsn = decode_option_u64(data, pos, "sync status servable LSN")?;
1075 let unarchived_lsn = decode_option_u64(data, pos, "sync status unarchived LSN")?;
1076 let lag_lsn = decode_option_u64(data, pos, "sync status lag LSN")?;
1077 let lag_bytes = decode_option_u64(data, pos, "sync status lag bytes")?;
1078 let lag_ms = decode_option_u64(data, pos, "sync status lag milliseconds")?;
1079 let stale = decode_bool(data, pos, "sync status stale")?;
1080 if *pos >= data.len() {
1081 return Err("truncated sync repair action".into());
1082 }
1083 let repair_action = match data[*pos] {
1084 0 => WireSyncRepairAction::None,
1085 1 => WireSyncRepairAction::Pull,
1086 2 => WireSyncRepairAction::AwaitArchive,
1087 3 => WireSyncRepairAction::Rebootstrap,
1088 other => return Err(format!("unknown sync repair action: {other}")),
1089 };
1090 *pos += 1;
1091 let last_sync_error = decode_option_string(data, pos, "sync status last error")?;
1092 Ok(WireSyncStatus {
1093 replica_id,
1094 active,
1095 last_applied_lsn,
1096 remote_lsn,
1097 servable_lsn,
1098 unarchived_lsn,
1099 lag_lsn,
1100 lag_bytes,
1101 lag_ms,
1102 stale,
1103 repair_action,
1104 last_sync_error,
1105 })
1106}
1107
1108fn encode_retained_unit(out: &mut Vec<u8>, unit: &WireRetainedUnit) {
1109 out.extend_from_slice(&unit.tx_id.to_le_bytes());
1110 out.push(unit.record_type);
1111 out.extend_from_slice(&unit.lsn.to_le_bytes());
1112 out.extend_from_slice(&(unit.data.len() as u32).to_le_bytes());
1113 out.extend_from_slice(&unit.data);
1114}
1115
1116fn decode_retained_unit(data: &[u8], pos: &mut usize) -> Result<WireRetainedUnit, String> {
1117 let tx_id = decode_u64(data, pos, "sync retained unit tx id")?;
1118 if *pos >= data.len() {
1119 return Err("truncated sync retained unit record type".into());
1120 }
1121 let record_type = data[*pos];
1122 *pos += 1;
1123 let lsn = decode_u64(data, pos, "sync retained unit LSN")?;
1124 let data = decode_bytes(data, pos, "sync retained unit payload")?;
1125 Ok(WireRetainedUnit {
1126 tx_id,
1127 record_type,
1128 lsn,
1129 data,
1130 })
1131}
1132
1133fn decode_bool(data: &[u8], pos: &mut usize, label: &str) -> Result<bool, String> {
1134 if *pos >= data.len() {
1135 return Err(format!("truncated {label}"));
1136 }
1137 let raw = data[*pos];
1138 *pos += 1;
1139 match raw {
1140 0 => Ok(false),
1141 1 => Ok(true),
1142 other => Err(format!("invalid boolean for {label}: {other}")),
1143 }
1144}
1145
1146fn decode_u16(data: &[u8], pos: &mut usize, label: &str) -> Result<u16, String> {
1147 if *pos + 2 > data.len() {
1148 return Err(format!("truncated {label}"));
1149 }
1150 let bytes: [u8; 2] = data[*pos..*pos + 2]
1151 .try_into()
1152 .map_err(|_| format!("invalid {label} bytes"))?;
1153 *pos += 2;
1154 Ok(u16::from_le_bytes(bytes))
1155}
1156
1157fn decode_u32(data: &[u8], pos: &mut usize, label: &str) -> Result<u32, String> {
1158 if *pos + 4 > data.len() {
1159 return Err(format!("truncated {label}"));
1160 }
1161 let bytes: [u8; 4] = data[*pos..*pos + 4]
1162 .try_into()
1163 .map_err(|_| format!("invalid {label} bytes"))?;
1164 *pos += 4;
1165 Ok(u32::from_le_bytes(bytes))
1166}
1167
1168fn decode_u64(data: &[u8], pos: &mut usize, label: &str) -> Result<u64, String> {
1169 if *pos + 8 > data.len() {
1170 return Err(format!("truncated {label}"));
1171 }
1172 let bytes: [u8; 8] = data[*pos..*pos + 8]
1173 .try_into()
1174 .map_err(|_| format!("invalid {label} bytes"))?;
1175 *pos += 8;
1176 Ok(u64::from_le_bytes(bytes))
1177}
1178
1179fn decode_16_bytes(data: &[u8], pos: &mut usize, label: &str) -> Result<[u8; 16], String> {
1180 if *pos + 16 > data.len() {
1181 return Err(format!("truncated {label}"));
1182 }
1183 let mut out = [0u8; 16];
1184 out.copy_from_slice(&data[*pos..*pos + 16]);
1185 *pos += 16;
1186 Ok(out)
1187}
1188
1189fn decode_bytes(data: &[u8], pos: &mut usize, label: &str) -> Result<Vec<u8>, String> {
1190 let len = decode_u32(data, pos, label)? as usize;
1191 if *pos + len > data.len() {
1192 return Err(format!("truncated {label}"));
1193 }
1194 let out = data[*pos..*pos + len].to_vec();
1195 *pos += len;
1196 Ok(out)
1197}
1198
1199#[cfg(test)]
1200mod tests {
1201 use super::*;
1202
1203 #[test]
1204 fn test_encode_decode_query() {
1205 let msg = Message::Query {
1206 query: "User filter .age > 30".into(),
1207 };
1208 let bytes = msg.encode();
1209 let decoded = Message::decode(&bytes).unwrap();
1210 match decoded {
1211 Message::Query { query } => assert_eq!(query, "User filter .age > 30"),
1212 _ => panic!("expected Query"),
1213 }
1214 }
1215
1216 #[test]
1217 fn test_encode_decode_connect_with_username() {
1218 let msg = Message::Connect {
1219 db_name: "mydb".into(),
1220 password: Some(Zeroizing::new("secret".into())),
1221 username: Some("alice".into()),
1222 };
1223 let bytes = msg.encode();
1224 match Message::decode(&bytes).unwrap() {
1225 Message::Connect {
1226 db_name,
1227 password,
1228 username,
1229 } => {
1230 assert_eq!(db_name, "mydb");
1231 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("secret"));
1232 assert_eq!(username.as_deref(), Some("alice"));
1233 }
1234 other => panic!("expected Connect, got {other:?}"),
1235 }
1236 }
1237
1238 #[test]
1239 fn test_encode_decode_connect_without_username() {
1240 let msg = Message::Connect {
1242 db_name: "mydb".into(),
1243 password: Some(Zeroizing::new("secret".into())),
1244 username: None,
1245 };
1246 let bytes = msg.encode();
1247 match Message::decode(&bytes).unwrap() {
1248 Message::Connect {
1249 db_name,
1250 password,
1251 username,
1252 } => {
1253 assert_eq!(db_name, "mydb");
1254 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("secret"));
1255 assert_eq!(username, None);
1256 }
1257 other => panic!("expected Connect, got {other:?}"),
1258 }
1259 }
1260
1261 #[test]
1262 fn test_decode_old_client_connect_db_and_password_only() {
1263 let mut payload = encode_string("mydb");
1266 payload.extend_from_slice(&encode_string("pw"));
1267 let mut frame = vec![MSG_CONNECT, 0];
1268 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
1269 frame.extend_from_slice(&payload);
1270 match Message::decode(&frame).unwrap() {
1271 Message::Connect {
1272 db_name,
1273 password,
1274 username,
1275 } => {
1276 assert_eq!(db_name, "mydb");
1277 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("pw"));
1278 assert_eq!(username, None, "old-client frame must yield username=None");
1279 }
1280 other => panic!("expected Connect, got {other:?}"),
1281 }
1282 }
1283
1284 #[test]
1285 fn test_decode_old_client_connect_db_only() {
1286 let payload = encode_string("mydb");
1288 let mut frame = vec![MSG_CONNECT, 0];
1289 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
1290 frame.extend_from_slice(&payload);
1291 match Message::decode(&frame).unwrap() {
1292 Message::Connect {
1293 db_name,
1294 password,
1295 username,
1296 } => {
1297 assert_eq!(db_name, "mydb");
1298 assert_eq!(password, None);
1299 assert_eq!(username, None);
1300 }
1301 other => panic!("expected Connect, got {other:?}"),
1302 }
1303 }
1304
1305 #[test]
1306 fn test_encode_decode_result_rows() {
1307 let msg = Message::ResultRows {
1308 columns: vec!["name".into(), "age".into()],
1309 rows: vec![
1310 vec!["Alice".into(), "30".into()],
1311 vec!["Bob".into(), "25".into()],
1312 ],
1313 };
1314 let bytes = msg.encode();
1315 let decoded = Message::decode(&bytes).unwrap();
1316 match decoded {
1317 Message::ResultRows { columns, rows } => {
1318 assert_eq!(columns, vec!["name", "age"]);
1319 assert_eq!(rows.len(), 2);
1320 }
1321 _ => panic!("expected ResultRows"),
1322 }
1323 }
1324
1325 #[test]
1326 fn legacy_rows_frame_is_byte_identical() {
1327 let encoded = Message::ResultRows {
1328 columns: vec!["x".into()],
1329 rows: vec![vec!["y".into()]],
1330 }
1331 .encode();
1332 assert_eq!(
1333 encoded,
1334 vec![
1335 0x07, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, b'x', 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, b'y', ]
1341 );
1342 }
1343
1344 #[test]
1345 fn native_request_tags_and_params_round_trip() {
1346 let cases = [
1347 (
1348 MSG_QUERY_NATIVE,
1349 Message::QueryNative {
1350 query: "T { .x }".into(),
1351 },
1352 ),
1353 (
1354 MSG_QUERY_SQL_NATIVE,
1355 Message::QuerySqlNative {
1356 query: "SELECT x FROM T".into(),
1357 },
1358 ),
1359 ];
1360 for (tag, message) in cases {
1361 let encoded = message.encode();
1362 assert_eq!(encoded[0], tag);
1363 match Message::decode(&encoded).expect("native request round trip") {
1364 Message::QueryNative { query } => assert_eq!(query, "T { .x }"),
1365 Message::QuerySqlNative { query } => assert_eq!(query, "SELECT x FROM T"),
1366 other => panic!("unexpected native request: {other:?}"),
1367 }
1368 }
1369
1370 let encoded = Message::QueryWithParamsNative {
1371 query: "T filter .x = $1".into(),
1372 params: vec![WireParam::Int(7), WireParam::Bool(false)],
1373 }
1374 .encode();
1375 assert_eq!(encoded[0], MSG_QUERY_PARAMS_NATIVE);
1376 match Message::decode(&encoded).expect("native params round trip") {
1377 Message::QueryWithParamsNative { query, params } => {
1378 assert_eq!(query, "T filter .x = $1");
1379 assert_eq!(params, vec![WireParam::Int(7), WireParam::Bool(false)]);
1380 }
1381 other => panic!("unexpected native parameterized request: {other:?}"),
1382 }
1383 }
1384
1385 fn every_native_value() -> Vec<Value> {
1386 vec![
1387 Value::Empty,
1388 Value::Int(-9_007_199_254_740_993),
1389 Value::Float(2.5),
1390 Value::Bool(true),
1391 Value::Str("héllo".into()),
1392 Value::DateTime(1_723_650_123_456_789),
1393 Value::Uuid([
1394 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
1395 0xee, 0xff,
1396 ]),
1397 Value::Bytes(vec![0x00, 0x7f, 0x80, 0xff]),
1398 Value::Json(
1399 powdb_storage::pj1::parse_json_text("9007199254740993")
1400 .expect("PJ1 fixture")
1401 .into_boxed_slice(),
1402 ),
1403 ]
1404 }
1405
1406 #[test]
1407 fn native_rows_and_scalar_round_trip_every_type() {
1408 let values = every_native_value();
1409 let columns = (0..values.len()).map(|index| format!("c{index}")).collect();
1410 let encoded = Message::ResultRowsNative {
1411 columns,
1412 rows: vec![values.clone()],
1413 }
1414 .encode();
1415 assert_eq!(encoded[0], MSG_RESULT_ROWS_NATIVE);
1416 match Message::decode(&encoded).expect("native rows round trip") {
1417 Message::ResultRowsNative { columns, rows } => {
1418 assert_eq!(columns.len(), values.len());
1419 assert_eq!(rows, vec![values.clone()]);
1420 }
1421 other => panic!("unexpected native rows: {other:?}"),
1422 }
1423
1424 for value in values {
1425 let encoded = Message::ResultScalarNative {
1426 value: value.clone(),
1427 }
1428 .encode();
1429 assert_eq!(encoded[0], MSG_RESULT_SCALAR_NATIVE);
1430 match Message::decode(&encoded).expect("native scalar round trip") {
1431 Message::ResultScalarNative { value: decoded } => assert_eq!(decoded, value),
1432 other => panic!("unexpected native scalar: {other:?}"),
1433 }
1434 }
1435 }
1436
1437 #[test]
1438 fn native_mixed_row_matches_cross_client_golden() {
1439 let encoded = Message::ResultRowsNative {
1440 columns: ["e", "i", "f", "b", "s", "d", "u", "x", "j"]
1441 .into_iter()
1442 .map(str::to_owned)
1443 .collect(),
1444 rows: vec![every_native_value()],
1445 }
1446 .encode();
1447 let hex = encoded
1448 .iter()
1449 .map(|byte| format!("{byte:02x}"))
1450 .collect::<String>();
1451 assert_eq!(
1452 hex,
1453 "16009c000000090001000000650100000069010000006601000000620100000073010000006401000000750100000078010000006a0100000000000000000108000000ffffffffffffdfff02080000000000000000000440030100000001040600000068c3a96c6c6f050800000015615391a61f0600061000000000112233445566778899aabbccddeeff0704000000007f80ff0809000000030100000000002000"
1454 );
1455 }
1456
1457 fn frame(tag: u8, payload: &[u8]) -> Vec<u8> {
1458 let mut frame = vec![tag, 0];
1459 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
1460 frame.extend_from_slice(payload);
1461 frame
1462 }
1463
1464 fn typed_cell(tag: u8, body: &[u8]) -> Vec<u8> {
1465 let mut payload = vec![tag];
1466 payload.extend_from_slice(&(body.len() as u32).to_le_bytes());
1467 payload.extend_from_slice(body);
1468 payload
1469 }
1470
1471 #[test]
1472 fn native_scalar_rejects_malformed_cells() {
1473 let malformed = [
1474 typed_cell(0xff, &[]),
1475 typed_cell(TypeId::Int as u8, &[0; 7]),
1476 typed_cell(TypeId::Bool as u8, &[2]),
1477 typed_cell(TypeId::Str as u8, &[0xff]),
1478 typed_cell(TypeId::Json as u8, &[0xff]),
1479 typed_cell(TypeId::Json as u8, &[0, 0]),
1480 ];
1481 for payload in malformed {
1482 assert!(Message::decode(&frame(MSG_RESULT_SCALAR_NATIVE, &payload)).is_err());
1483 }
1484
1485 let mut trailing = typed_cell(TypeId::Empty as u8, &[]);
1486 trailing.push(0);
1487 assert!(Message::decode(&frame(MSG_RESULT_SCALAR_NATIVE, &trailing)).is_err());
1488 }
1489
1490 #[test]
1491 fn native_rows_rejects_bad_counts_and_trailing_data() {
1492 let mut payload = Vec::new();
1493 payload.extend_from_slice(&1u16.to_le_bytes());
1494 payload.extend_from_slice(&encode_string("x"));
1495 payload.extend_from_slice(&2u32.to_le_bytes());
1496 payload.extend_from_slice(&typed_cell(TypeId::Empty as u8, &[]));
1497 assert!(Message::decode(&frame(MSG_RESULT_ROWS_NATIVE, &payload)).is_err());
1498
1499 payload[7..11].copy_from_slice(&1u32.to_le_bytes());
1500 payload.extend_from_slice(&[0xaa]);
1501 assert!(Message::decode(&frame(MSG_RESULT_ROWS_NATIVE, &payload)).is_err());
1502 }
1503
1504 #[test]
1505 fn test_encode_decode_result_message() {
1506 let msg = Message::ResultMessage {
1507 message: "type User created".into(),
1508 };
1509 let bytes = msg.encode();
1510 let decoded = Message::decode(&bytes).unwrap();
1511 match decoded {
1512 Message::ResultMessage { message } => assert_eq!(message, "type User created"),
1513 _ => panic!("expected ResultMessage"),
1514 }
1515 }
1516
1517 #[test]
1518 fn test_encode_decode_error() {
1519 let msg = Message::Error {
1520 message: "table not found".into(),
1521 };
1522 let bytes = msg.encode();
1523 let decoded = Message::decode(&bytes).unwrap();
1524 match decoded {
1525 Message::Error { message } => assert_eq!(message, "table not found"),
1526 _ => panic!("expected Error"),
1527 }
1528 }
1529
1530 #[test]
1531 fn test_encode_decode_query_sql() {
1532 let msg = Message::QuerySql {
1533 query: "SELECT * FROM User".into(),
1534 };
1535 let decoded = Message::decode(&msg.encode()).unwrap();
1536 match decoded {
1537 Message::QuerySql { query } => assert_eq!(query, "SELECT * FROM User"),
1538 other => panic!("expected QuerySql, got {other:?}"),
1539 }
1540 }
1541
1542 #[test]
1543 fn test_encode_decode_query_with_params() {
1544 let msg = Message::QueryWithParams {
1545 query: "insert User { name := $1, age := $2, ok := $3, note := $4 }".into(),
1546 params: vec![
1547 WireParam::Str(r#"a"b\c; drop User"#.into()),
1548 WireParam::Int(-7),
1549 WireParam::Bool(true),
1550 WireParam::Null,
1551 ],
1552 };
1553 let bytes = msg.encode();
1554 assert_eq!(bytes[0], 0x04);
1556 match Message::decode(&bytes).unwrap() {
1557 Message::QueryWithParams { query, params } => {
1558 assert!(query.contains("$1"));
1559 assert_eq!(params.len(), 4);
1560 assert!(matches!(¶ms[0], WireParam::Str(s) if s == r#"a"b\c; drop User"#));
1561 assert!(matches!(¶ms[1], WireParam::Int(-7)));
1562 assert!(matches!(¶ms[2], WireParam::Bool(true)));
1563 assert!(matches!(¶ms[3], WireParam::Null));
1564 }
1565 other => panic!("expected QueryWithParams, got {other:?}"),
1566 }
1567 }
1568
1569 #[test]
1570 fn test_query_with_params_float_round_trip() {
1571 let msg = Message::QueryWithParams {
1572 query: "T filter .f = $1".into(),
1573 params: vec![WireParam::Float(2.5)],
1574 };
1575 match Message::decode(&msg.encode()).unwrap() {
1576 Message::QueryWithParams { params, .. } => {
1577 assert!(matches!(¶ms[0], WireParam::Float(f) if (*f - 2.5).abs() < 1e-12));
1578 }
1579 other => panic!("expected QueryWithParams, got {other:?}"),
1580 }
1581 }
1582
1583 fn sample_sync_status() -> WireSyncStatus {
1584 WireSyncStatus {
1585 replica_id: "replica-a".into(),
1586 active: true,
1587 last_applied_lsn: Some(7),
1588 remote_lsn: 10,
1589 servable_lsn: Some(10),
1590 unarchived_lsn: Some(0),
1591 lag_lsn: Some(3),
1592 lag_bytes: Some(2048),
1593 lag_ms: Some(5000),
1594 stale: true,
1595 repair_action: WireSyncRepairAction::Pull,
1596 last_sync_error: None,
1597 }
1598 }
1599
1600 #[test]
1601 fn test_encode_decode_sync_requests() {
1602 let database_id = *b"sync-protocol!!!";
1603 let pull = Message::SyncPull {
1604 replica_id: "replica-a".into(),
1605 since_lsn: 7,
1606 max_units: 128,
1607 max_bytes: 4096,
1608 database_id,
1609 primary_generation: 9,
1610 wal_format_version: 1,
1611 catalog_version: 2,
1612 segment_format_version: 1,
1613 };
1614 let bytes = pull.encode();
1615 assert_eq!(bytes[0], MSG_SYNC_PULL);
1616 match Message::decode(&bytes).unwrap() {
1617 Message::SyncPull {
1618 replica_id,
1619 since_lsn,
1620 max_units,
1621 max_bytes,
1622 database_id: decoded_database_id,
1623 primary_generation,
1624 wal_format_version,
1625 catalog_version,
1626 segment_format_version,
1627 } => {
1628 assert_eq!(replica_id, "replica-a");
1629 assert_eq!(since_lsn, 7);
1630 assert_eq!(max_units, 128);
1631 assert_eq!(max_bytes, 4096);
1632 assert_eq!(decoded_database_id, database_id);
1633 assert_eq!(primary_generation, 9);
1634 assert_eq!(wal_format_version, 1);
1635 assert_eq!(catalog_version, 2);
1636 assert_eq!(segment_format_version, 1);
1637 }
1638 other => panic!("expected SyncPull, got {other:?}"),
1639 }
1640
1641 let ack = Message::SyncAck {
1642 replica_id: "replica-a".into(),
1643 applied_lsn: 10,
1644 remote_lsn: 10,
1645 };
1646 match Message::decode(&ack.encode()).unwrap() {
1647 Message::SyncAck {
1648 replica_id,
1649 applied_lsn,
1650 remote_lsn,
1651 } => {
1652 assert_eq!(replica_id, "replica-a");
1653 assert_eq!(applied_lsn, 10);
1654 assert_eq!(remote_lsn, 10);
1655 }
1656 other => panic!("expected SyncAck, got {other:?}"),
1657 }
1658 }
1659
1660 #[test]
1661 fn test_encode_decode_sync_results() {
1662 let status = sample_sync_status();
1663 match Message::decode(
1664 &Message::SyncStatusResult {
1665 status: status.clone(),
1666 }
1667 .encode(),
1668 )
1669 .unwrap()
1670 {
1671 Message::SyncStatusResult { status: decoded } => assert_eq!(decoded, status),
1672 other => panic!("expected SyncStatusResult, got {other:?}"),
1673 }
1674
1675 let await_archive_status = WireSyncStatus {
1676 servable_lsn: Some(7),
1677 unarchived_lsn: Some(3),
1678 repair_action: WireSyncRepairAction::AwaitArchive,
1679 last_sync_error: Some("primary WAL is not yet archived".into()),
1680 ..status.clone()
1681 };
1682 match Message::decode(
1683 &Message::SyncStatusResult {
1684 status: await_archive_status.clone(),
1685 }
1686 .encode(),
1687 )
1688 .unwrap()
1689 {
1690 Message::SyncStatusResult { status: decoded } => {
1691 assert_eq!(decoded, await_archive_status)
1692 }
1693 other => panic!("expected AwaitArchive SyncStatusResult, got {other:?}"),
1694 }
1695
1696 let units = vec![
1697 WireRetainedUnit {
1698 tx_id: 1,
1699 record_type: 4,
1700 lsn: 8,
1701 data: vec![1, 2, 3],
1702 },
1703 WireRetainedUnit {
1704 tx_id: 1,
1705 record_type: 4,
1706 lsn: 9,
1707 data: vec![4, 5],
1708 },
1709 ];
1710 let empty_pull_len = Message::SyncPullResult {
1711 status: status.clone(),
1712 units: Vec::new(),
1713 has_more: true,
1714 }
1715 .encode()
1716 .len();
1717 let populated_pull_len = Message::SyncPullResult {
1718 status: status.clone(),
1719 units: units.clone(),
1720 has_more: true,
1721 }
1722 .encode()
1723 .len();
1724 let expected_unit_len: u64 = units
1725 .iter()
1726 .map(WireRetainedUnit::encoded_len)
1727 .collect::<Result<Vec<_>, _>>()
1728 .unwrap()
1729 .into_iter()
1730 .sum();
1731 assert_eq!(
1732 u64::try_from(populated_pull_len - empty_pull_len).unwrap(),
1733 expected_unit_len,
1734 "retained-unit encoded length must track SyncPullResult wire shape"
1735 );
1736
1737 match Message::decode(
1738 &Message::SyncPullResult {
1739 status: status.clone(),
1740 units: units.clone(),
1741 has_more: true,
1742 }
1743 .encode(),
1744 )
1745 .unwrap()
1746 {
1747 Message::SyncPullResult {
1748 status: decoded_status,
1749 units: decoded_units,
1750 has_more,
1751 } => {
1752 assert_eq!(decoded_status, status);
1753 assert_eq!(decoded_units, units);
1754 assert!(has_more);
1755 }
1756 other => panic!("expected SyncPullResult, got {other:?}"),
1757 }
1758
1759 match Message::decode(
1760 &Message::SyncAckResult {
1761 previous_applied_lsn: 7,
1762 applied_lsn: 10,
1763 remote_lsn: 10,
1764 advanced: true,
1765 status: WireSyncStatus {
1766 stale: false,
1767 repair_action: WireSyncRepairAction::None,
1768 lag_lsn: Some(0),
1769 lag_bytes: Some(0),
1770 lag_ms: Some(0),
1771 ..status
1772 },
1773 }
1774 .encode(),
1775 )
1776 .unwrap()
1777 {
1778 Message::SyncAckResult {
1779 previous_applied_lsn,
1780 applied_lsn,
1781 remote_lsn,
1782 advanced,
1783 status,
1784 } => {
1785 assert_eq!(previous_applied_lsn, 7);
1786 assert_eq!(applied_lsn, 10);
1787 assert_eq!(remote_lsn, 10);
1788 assert!(advanced);
1789 assert!(!status.stale);
1790 }
1791 other => panic!("expected SyncAckResult, got {other:?}"),
1792 }
1793 }
1794
1795 #[test]
1796 fn test_decode_garbage_never_panics() {
1797 let cases: Vec<Vec<u8>> = vec![
1801 vec![], vec![0x03], vec![0x03, 0x00, 0x00, 0x00, 0x00], vec![0xFF, 0x00, 0x00, 0x00, 0x00, 0x00], vec![0x03, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
1807 {
1809 let mut payload = encode_string("replica-a");
1810 let mut frame = vec![MSG_SYNC_PULL, 0];
1811 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
1812 frame.append(&mut payload);
1813 frame
1814 },
1815 {
1817 let mut payload = encode_sync_status(&sample_sync_status());
1818 payload.extend_from_slice(&((MAX_SYNC_UNITS as u32) + 1).to_le_bytes());
1819 let mut frame = vec![MSG_SYNC_PULL_RESULT, 0];
1820 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
1821 frame.extend_from_slice(&payload);
1822 frame
1823 },
1824 vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
1826 vec![0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF],
1828 vec![0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03],
1830 vec![0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
1832 vec![
1834 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, ],
1838 vec![
1840 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x02, 0x03,
1845 ],
1846 vec![
1848 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, ],
1854 vec![
1856 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x63, ],
1861 ];
1862 for bytes in cases {
1863 let result = Message::decode(&bytes);
1864 assert!(
1865 result.is_err(),
1866 "expected Err for malformed input {bytes:?}, got {result:?}"
1867 );
1868 }
1869 }
1870
1871 #[test]
1872 fn test_decode_arbitrary_prefixes_never_panic() {
1873 let valid = Message::ResultRows {
1876 columns: vec!["a".into(), "b".into()],
1877 rows: vec![vec!["1".into(), "2".into()]],
1878 }
1879 .encode();
1880 for end in 0..valid.len() {
1881 let _ = Message::decode(&valid[..end]);
1883 }
1884 for i in 0..valid.len() {
1886 let mut m = valid.clone();
1887 m[i] = 0xFF;
1888 let _ = Message::decode(&m);
1889 }
1890 }
1891
1892 #[test]
1893 fn test_decode_result_rows_rejects_amplified_row_count() {
1894 let mut payload = Vec::new();
1899 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];
1902 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
1903 frame.extend_from_slice(&payload);
1904 assert!(
1905 Message::decode(&frame).is_err(),
1906 "amplified row count must be rejected"
1907 );
1908
1909 let msg = Message::ResultRows {
1911 columns: vec!["a".into()],
1912 rows: vec![vec!["x".into()]],
1913 };
1914 match Message::decode(&msg.encode()).unwrap() {
1915 Message::ResultRows { columns, rows } => {
1916 assert_eq!(columns, vec!["a"]);
1917 assert_eq!(rows, vec![vec!["x".to_string()]]);
1918 }
1919 other => panic!("expected ResultRows, got {other:?}"),
1920 }
1921 }
1922
1923 #[test]
1924 fn test_frame_length() {
1925 let msg = Message::Query {
1926 query: "User".into(),
1927 };
1928 let bytes = msg.encode();
1929 assert!(bytes.len() >= 6);
1930 let payload_len = u32::from_le_bytes(bytes[2..6].try_into().unwrap()) as usize;
1931 assert_eq!(bytes.len(), 6 + payload_len);
1932 }
1933}