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_RESULT_ROWS: u8 = 0x07;
14const MSG_RESULT_SCALAR: u8 = 0x08;
15const MSG_RESULT_OK: u8 = 0x09;
16const MSG_ERROR: u8 = 0x0A;
17const MSG_RESULT_MSG: u8 = 0x0B;
18const MSG_DISCONNECT: u8 = 0x10;
19const MSG_PING: u8 = 0x11;
20const MSG_PONG: u8 = 0x12;
21
22const MAX_PAYLOAD_SIZE: usize = 64 * 1024 * 1024;
24
25const MAX_CONNECT_PAYLOAD_SIZE: usize = 4096;
28
29const MAX_COLUMNS: usize = 4096;
31
32const MAX_ROWS: usize = 10_000_000;
34
35const MAX_PARAMS: usize = 4096;
37
38#[derive(Debug, Clone, PartialEq)]
44pub enum WireParam {
45 Null,
46 Int(i64),
47 Float(f64),
48 Bool(bool),
49 Str(String),
50}
51
52#[derive(Debug, Clone)]
53pub enum Message {
54 Connect {
55 db_name: String,
56 password: Option<Zeroizing<String>>,
61 username: Option<String>,
65 },
66 ConnectOk {
67 version: String,
68 },
69 Query {
70 query: String,
71 },
72 QuerySql {
74 query: String,
75 },
76 QueryWithParams {
78 query: String,
79 params: Vec<WireParam>,
80 },
81 ResultRows {
82 columns: Vec<String>,
83 rows: Vec<Vec<String>>,
84 },
85 ResultScalar {
86 value: String,
87 },
88 ResultOk {
89 affected: u64,
90 },
91 ResultMessage {
93 message: String,
94 },
95 Error {
96 message: String,
97 },
98 Disconnect,
99 Ping,
100 Pong,
101}
102
103impl Message {
104 pub fn encode(&self) -> Vec<u8> {
106 let (msg_type, payload) = match self {
107 Message::Connect {
108 db_name,
109 password,
110 username,
111 } => {
112 let mut buf = encode_string(db_name);
113 match password {
115 Some(p) => buf.extend_from_slice(&encode_string(p)),
116 None => buf.extend_from_slice(&0u32.to_le_bytes()),
117 }
118 match username {
121 Some(u) => buf.extend_from_slice(&encode_string(u)),
122 None => buf.extend_from_slice(&0u32.to_le_bytes()),
123 }
124 (MSG_CONNECT, buf)
125 }
126 Message::ConnectOk { version } => (MSG_CONNECT_OK, encode_string(version)),
127 Message::Query { query } => (MSG_QUERY, encode_string(query)),
128 Message::QuerySql { query } => (MSG_QUERY_SQL, encode_string(query)),
129 Message::QueryWithParams { query, params } => {
130 let mut buf = encode_string(query);
131 buf.extend_from_slice(&(params.len() as u16).to_le_bytes());
132 for p in params {
133 match p {
134 WireParam::Null => buf.push(0),
135 WireParam::Int(v) => {
136 buf.push(1);
137 buf.extend_from_slice(&v.to_le_bytes());
138 }
139 WireParam::Float(v) => {
140 buf.push(2);
141 buf.extend_from_slice(&v.to_le_bytes());
142 }
143 WireParam::Bool(v) => {
144 buf.push(3);
145 buf.push(if *v { 1 } else { 0 });
146 }
147 WireParam::Str(s) => {
148 buf.push(4);
149 buf.extend_from_slice(&encode_string(s));
150 }
151 }
152 }
153 (MSG_QUERY_PARAMS, buf)
154 }
155 Message::ResultRows { columns, rows } => {
156 let mut buf = Vec::new();
157 buf.extend_from_slice(&(columns.len() as u16).to_le_bytes());
158 for col in columns {
159 buf.extend_from_slice(&encode_string(col));
160 }
161 buf.extend_from_slice(&(rows.len() as u32).to_le_bytes());
162 for row in rows {
163 for val in row {
164 buf.extend_from_slice(&encode_string(val));
165 }
166 }
167 (MSG_RESULT_ROWS, buf)
168 }
169 Message::ResultScalar { value } => (MSG_RESULT_SCALAR, encode_string(value)),
170 Message::ResultOk { affected } => (MSG_RESULT_OK, affected.to_le_bytes().to_vec()),
171 Message::ResultMessage { message } => (MSG_RESULT_MSG, encode_string(message)),
172 Message::Error { message } => (MSG_ERROR, encode_string(message)),
173 Message::Disconnect => (MSG_DISCONNECT, Vec::new()),
174 Message::Ping => (MSG_PING, Vec::new()),
175 Message::Pong => (MSG_PONG, Vec::new()),
176 };
177
178 let mut frame = Vec::with_capacity(6 + payload.len());
179 frame.push(msg_type);
180 frame.push(0); frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
182 frame.extend_from_slice(&payload);
183 frame
184 }
185
186 pub fn decode(data: &[u8]) -> Result<Message, String> {
188 if data.len() < 6 {
189 return Err("frame too short".into());
190 }
191 let msg_type = data[0];
192 let _flags = data[1];
193 let len_bytes: [u8; 4] = data[2..6]
194 .try_into()
195 .map_err(|_| "invalid header length field".to_string())?;
196 let payload_len = u32::from_le_bytes(len_bytes) as usize;
197 if 6 + payload_len > data.len() {
198 return Err("payload length exceeds frame".into());
199 }
200 let payload = &data[6..6 + payload_len];
201
202 match msg_type {
203 MSG_CONNECT => {
204 let mut pos = 0;
205 let db_name = decode_string(payload, &mut pos)?;
206 let password = if pos < payload.len() {
209 let p = Zeroizing::new(decode_string(payload, &mut pos)?);
212 if p.is_empty() {
213 None
214 } else {
215 Some(p)
216 }
217 } else {
218 None
219 };
220 let username = if pos < payload.len() {
223 let u = decode_string(payload, &mut pos)?;
224 if u.is_empty() {
225 None
226 } else {
227 Some(u)
228 }
229 } else {
230 None
231 };
232 Ok(Message::Connect {
233 db_name,
234 password,
235 username,
236 })
237 }
238 MSG_CONNECT_OK => {
239 let version = decode_string(payload, &mut 0)?;
240 Ok(Message::ConnectOk { version })
241 }
242 MSG_QUERY => {
243 let query = decode_string(payload, &mut 0)?;
244 Ok(Message::Query { query })
245 }
246 MSG_QUERY_SQL => {
247 let query = decode_string(payload, &mut 0)?;
248 Ok(Message::QuerySql { query })
249 }
250 MSG_QUERY_PARAMS => {
251 let mut pos = 0;
252 let query = decode_string(payload, &mut pos)?;
253 if pos + 2 > payload.len() {
254 return Err("truncated param count".into());
255 }
256 let count_bytes: [u8; 2] = payload[pos..pos + 2]
257 .try_into()
258 .map_err(|_| "invalid param count bytes".to_string())?;
259 let count = u16::from_le_bytes(count_bytes) as usize;
260 pos += 2;
261 if count > MAX_PARAMS {
262 return Err("too many parameters".into());
263 }
264 let mut params = Vec::with_capacity(count);
265 for _ in 0..count {
266 if pos >= payload.len() {
267 return Err("truncated param tag".into());
268 }
269 let tag = payload[pos];
270 pos += 1;
271 let p = match tag {
272 0 => WireParam::Null,
273 1 => {
274 if pos + 8 > payload.len() {
275 return Err("truncated int param".into());
276 }
277 let b: [u8; 8] = payload[pos..pos + 8]
278 .try_into()
279 .map_err(|_| "invalid int param bytes".to_string())?;
280 pos += 8;
281 WireParam::Int(i64::from_le_bytes(b))
282 }
283 2 => {
284 if pos + 8 > payload.len() {
285 return Err("truncated float param".into());
286 }
287 let b: [u8; 8] = payload[pos..pos + 8]
288 .try_into()
289 .map_err(|_| "invalid float param bytes".to_string())?;
290 pos += 8;
291 WireParam::Float(f64::from_le_bytes(b))
292 }
293 3 => {
294 if pos + 1 > payload.len() {
295 return Err("truncated bool param".into());
296 }
297 let v = payload[pos] != 0;
298 pos += 1;
299 WireParam::Bool(v)
300 }
301 4 => WireParam::Str(decode_string(payload, &mut pos)?),
302 other => return Err(format!("unknown param tag: {other}")),
303 };
304 params.push(p);
305 }
306 Ok(Message::QueryWithParams { query, params })
307 }
308 MSG_RESULT_ROWS => {
309 let mut pos = 0;
310 if pos + 2 > payload.len() {
311 return Err("truncated column count".into());
312 }
313 let col_bytes: [u8; 2] = payload[pos..pos + 2]
314 .try_into()
315 .map_err(|_| "invalid column count bytes".to_string())?;
316 let col_count = u16::from_le_bytes(col_bytes) as usize;
317 pos += 2;
318 if col_count > MAX_COLUMNS {
319 return Err("too many columns".into());
320 }
321 let mut columns = Vec::with_capacity(col_count);
322 for _ in 0..col_count {
323 columns.push(decode_string(payload, &mut pos)?);
324 }
325 if pos + 4 > payload.len() {
326 return Err("truncated row count".into());
327 }
328 let row_bytes: [u8; 4] = payload[pos..pos + 4]
329 .try_into()
330 .map_err(|_| "invalid row count bytes".to_string())?;
331 let row_count = u32::from_le_bytes(row_bytes) as usize;
332 pos += 4;
333 if row_count > MAX_ROWS {
334 return Err("too many rows".into());
335 }
336 let mut rows = Vec::with_capacity(row_count);
337 for _ in 0..row_count {
338 let mut row = Vec::with_capacity(col_count);
339 for _ in 0..col_count {
340 row.push(decode_string(payload, &mut pos)?);
341 }
342 rows.push(row);
343 }
344 Ok(Message::ResultRows { columns, rows })
345 }
346 MSG_RESULT_SCALAR => {
347 let value = decode_string(payload, &mut 0)?;
348 Ok(Message::ResultScalar { value })
349 }
350 MSG_RESULT_OK => {
351 if payload.len() < 8 {
352 return Err("truncated result ok payload".into());
353 }
354 let aff_bytes: [u8; 8] = payload[0..8]
355 .try_into()
356 .map_err(|_| "invalid affected count bytes".to_string())?;
357 let affected = u64::from_le_bytes(aff_bytes);
358 Ok(Message::ResultOk { affected })
359 }
360 MSG_RESULT_MSG => {
361 let message = decode_string(payload, &mut 0)?;
362 Ok(Message::ResultMessage { message })
363 }
364 MSG_ERROR => {
365 let message = decode_string(payload, &mut 0)?;
366 Ok(Message::Error { message })
367 }
368 MSG_DISCONNECT => Ok(Message::Disconnect),
369 MSG_PING => Ok(Message::Ping),
370 MSG_PONG => Ok(Message::Pong),
371 _ => Err(format!("unknown message type: {msg_type:#x}")),
372 }
373 }
374
375 pub async fn write_to<W: AsyncWriteExt + Unpin>(&self, writer: &mut W) -> std::io::Result<()> {
377 let bytes = self.encode();
378 writer.write_all(&bytes).await
379 }
380
381 pub async fn read_from<R: AsyncReadExt + Unpin>(
383 reader: &mut R,
384 ) -> std::io::Result<Option<Message>> {
385 Self::read_from_with_limit(reader, MAX_PAYLOAD_SIZE).await
386 }
387
388 pub async fn read_from_preauth<R: AsyncReadExt + Unpin>(
392 reader: &mut R,
393 ) -> std::io::Result<Option<Message>> {
394 Self::read_from_with_limit(reader, MAX_CONNECT_PAYLOAD_SIZE).await
395 }
396
397 async fn read_from_with_limit<R: AsyncReadExt + Unpin>(
399 reader: &mut R,
400 max_payload: usize,
401 ) -> std::io::Result<Option<Message>> {
402 let mut header = [0u8; 6];
403 match reader.read_exact(&mut header).await {
404 Ok(_) => {}
405 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
406 Err(e) => return Err(e),
407 }
408 let len_bytes: [u8; 4] = header[2..6].try_into().map_err(|_| {
409 std::io::Error::new(
410 std::io::ErrorKind::InvalidData,
411 "invalid header length field",
412 )
413 })?;
414 let payload_len = u32::from_le_bytes(len_bytes) as usize;
415 if payload_len > max_payload {
416 return Err(std::io::Error::new(
417 std::io::ErrorKind::InvalidData,
418 format!("payload too large: {payload_len} bytes (max {max_payload})"),
419 ));
420 }
421 let mut payload = vec![0u8; payload_len];
422 if payload_len > 0 {
423 reader.read_exact(&mut payload).await?;
424 }
425
426 let mut full = Vec::with_capacity(6 + payload_len);
427 full.extend_from_slice(&header);
428 full.extend_from_slice(&payload);
429
430 Message::decode(&full)
431 .map(Some)
432 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
433 }
434}
435
436fn encode_string(s: &str) -> Vec<u8> {
437 let mut buf = Vec::with_capacity(4 + s.len());
438 buf.extend_from_slice(&(s.len() as u32).to_le_bytes());
439 buf.extend_from_slice(s.as_bytes());
440 buf
441}
442
443fn decode_string(data: &[u8], pos: &mut usize) -> Result<String, String> {
444 if *pos + 4 > data.len() {
445 return Err("truncated string length".into());
446 }
447 let len_bytes: [u8; 4] = data[*pos..*pos + 4]
448 .try_into()
449 .map_err(|_| "invalid string length bytes".to_string())?;
450 let len = u32::from_le_bytes(len_bytes) as usize;
451 *pos += 4;
452 if *pos + len > data.len() {
453 return Err("truncated string data".into());
454 }
455 let s = String::from_utf8_lossy(&data[*pos..*pos + len]).into_owned();
456 *pos += len;
457 Ok(s)
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463
464 #[test]
465 fn test_encode_decode_query() {
466 let msg = Message::Query {
467 query: "User filter .age > 30".into(),
468 };
469 let bytes = msg.encode();
470 let decoded = Message::decode(&bytes).unwrap();
471 match decoded {
472 Message::Query { query } => assert_eq!(query, "User filter .age > 30"),
473 _ => panic!("expected Query"),
474 }
475 }
476
477 #[test]
478 fn test_encode_decode_connect_with_username() {
479 let msg = Message::Connect {
480 db_name: "mydb".into(),
481 password: Some(Zeroizing::new("secret".into())),
482 username: Some("alice".into()),
483 };
484 let bytes = msg.encode();
485 match Message::decode(&bytes).unwrap() {
486 Message::Connect {
487 db_name,
488 password,
489 username,
490 } => {
491 assert_eq!(db_name, "mydb");
492 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("secret"));
493 assert_eq!(username.as_deref(), Some("alice"));
494 }
495 other => panic!("expected Connect, got {other:?}"),
496 }
497 }
498
499 #[test]
500 fn test_encode_decode_connect_without_username() {
501 let msg = Message::Connect {
503 db_name: "mydb".into(),
504 password: Some(Zeroizing::new("secret".into())),
505 username: None,
506 };
507 let bytes = msg.encode();
508 match Message::decode(&bytes).unwrap() {
509 Message::Connect {
510 db_name,
511 password,
512 username,
513 } => {
514 assert_eq!(db_name, "mydb");
515 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("secret"));
516 assert_eq!(username, None);
517 }
518 other => panic!("expected Connect, got {other:?}"),
519 }
520 }
521
522 #[test]
523 fn test_decode_old_client_connect_db_and_password_only() {
524 let mut payload = encode_string("mydb");
527 payload.extend_from_slice(&encode_string("pw"));
528 let mut frame = vec![MSG_CONNECT, 0];
529 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
530 frame.extend_from_slice(&payload);
531 match Message::decode(&frame).unwrap() {
532 Message::Connect {
533 db_name,
534 password,
535 username,
536 } => {
537 assert_eq!(db_name, "mydb");
538 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("pw"));
539 assert_eq!(username, None, "old-client frame must yield username=None");
540 }
541 other => panic!("expected Connect, got {other:?}"),
542 }
543 }
544
545 #[test]
546 fn test_decode_old_client_connect_db_only() {
547 let payload = encode_string("mydb");
549 let mut frame = vec![MSG_CONNECT, 0];
550 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
551 frame.extend_from_slice(&payload);
552 match Message::decode(&frame).unwrap() {
553 Message::Connect {
554 db_name,
555 password,
556 username,
557 } => {
558 assert_eq!(db_name, "mydb");
559 assert_eq!(password, None);
560 assert_eq!(username, None);
561 }
562 other => panic!("expected Connect, got {other:?}"),
563 }
564 }
565
566 #[test]
567 fn test_encode_decode_result_rows() {
568 let msg = Message::ResultRows {
569 columns: vec!["name".into(), "age".into()],
570 rows: vec![
571 vec!["Alice".into(), "30".into()],
572 vec!["Bob".into(), "25".into()],
573 ],
574 };
575 let bytes = msg.encode();
576 let decoded = Message::decode(&bytes).unwrap();
577 match decoded {
578 Message::ResultRows { columns, rows } => {
579 assert_eq!(columns, vec!["name", "age"]);
580 assert_eq!(rows.len(), 2);
581 }
582 _ => panic!("expected ResultRows"),
583 }
584 }
585
586 #[test]
587 fn test_encode_decode_result_message() {
588 let msg = Message::ResultMessage {
589 message: "type User created".into(),
590 };
591 let bytes = msg.encode();
592 let decoded = Message::decode(&bytes).unwrap();
593 match decoded {
594 Message::ResultMessage { message } => assert_eq!(message, "type User created"),
595 _ => panic!("expected ResultMessage"),
596 }
597 }
598
599 #[test]
600 fn test_encode_decode_error() {
601 let msg = Message::Error {
602 message: "table not found".into(),
603 };
604 let bytes = msg.encode();
605 let decoded = Message::decode(&bytes).unwrap();
606 match decoded {
607 Message::Error { message } => assert_eq!(message, "table not found"),
608 _ => panic!("expected Error"),
609 }
610 }
611
612 #[test]
613 fn test_encode_decode_query_sql() {
614 let msg = Message::QuerySql {
615 query: "SELECT * FROM User".into(),
616 };
617 let decoded = Message::decode(&msg.encode()).unwrap();
618 match decoded {
619 Message::QuerySql { query } => assert_eq!(query, "SELECT * FROM User"),
620 other => panic!("expected QuerySql, got {other:?}"),
621 }
622 }
623
624 #[test]
625 fn test_encode_decode_query_with_params() {
626 let msg = Message::QueryWithParams {
627 query: "insert User { name := $1, age := $2, ok := $3, note := $4 }".into(),
628 params: vec![
629 WireParam::Str(r#"a"b\c; drop User"#.into()),
630 WireParam::Int(-7),
631 WireParam::Bool(true),
632 WireParam::Null,
633 ],
634 };
635 let bytes = msg.encode();
636 assert_eq!(bytes[0], 0x04);
638 match Message::decode(&bytes).unwrap() {
639 Message::QueryWithParams { query, params } => {
640 assert!(query.contains("$1"));
641 assert_eq!(params.len(), 4);
642 assert!(matches!(¶ms[0], WireParam::Str(s) if s == r#"a"b\c; drop User"#));
643 assert!(matches!(¶ms[1], WireParam::Int(-7)));
644 assert!(matches!(¶ms[2], WireParam::Bool(true)));
645 assert!(matches!(¶ms[3], WireParam::Null));
646 }
647 other => panic!("expected QueryWithParams, got {other:?}"),
648 }
649 }
650
651 #[test]
652 fn test_query_with_params_float_round_trip() {
653 let msg = Message::QueryWithParams {
654 query: "T filter .f = $1".into(),
655 params: vec![WireParam::Float(2.5)],
656 };
657 match Message::decode(&msg.encode()).unwrap() {
658 Message::QueryWithParams { params, .. } => {
659 assert!(matches!(¶ms[0], WireParam::Float(f) if (*f - 2.5).abs() < 1e-12));
660 }
661 other => panic!("expected QueryWithParams, got {other:?}"),
662 }
663 }
664
665 #[test]
666 fn test_decode_garbage_never_panics() {
667 let cases: Vec<Vec<u8>> = vec![
671 vec![], vec![0x03], vec![0x03, 0x00, 0x00, 0x00, 0x00], vec![0xFF, 0x00, 0x00, 0x00, 0x00, 0x00], vec![0x03, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
677 vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
679 vec![0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF],
681 vec![0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03],
683 vec![0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
685 vec![
687 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, ],
691 vec![
693 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x02, 0x03,
698 ],
699 vec![
701 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, ],
707 vec![
709 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x63, ],
714 ];
715 for bytes in cases {
716 let result = Message::decode(&bytes);
717 assert!(
718 result.is_err(),
719 "expected Err for malformed input {bytes:?}, got {result:?}"
720 );
721 }
722 }
723
724 #[test]
725 fn test_decode_arbitrary_prefixes_never_panic() {
726 let valid = Message::ResultRows {
729 columns: vec!["a".into(), "b".into()],
730 rows: vec![vec!["1".into(), "2".into()]],
731 }
732 .encode();
733 for end in 0..valid.len() {
734 let _ = Message::decode(&valid[..end]);
736 }
737 for i in 0..valid.len() {
739 let mut m = valid.clone();
740 m[i] = 0xFF;
741 let _ = Message::decode(&m);
742 }
743 }
744
745 #[test]
746 fn test_frame_length() {
747 let msg = Message::Query {
748 query: "User".into(),
749 };
750 let bytes = msg.encode();
751 assert!(bytes.len() >= 6);
752 let payload_len = u32::from_le_bytes(bytes[2..6].try_into().unwrap()) as usize;
753 assert_eq!(bytes.len(), 6 + payload_len);
754 }
755}