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_RESULT_ROWS: u8 = 0x07;
8const MSG_RESULT_SCALAR: u8 = 0x08;
9const MSG_RESULT_OK: u8 = 0x09;
10const MSG_ERROR: u8 = 0x0A;
11const MSG_RESULT_MSG: u8 = 0x0B;
12const MSG_DISCONNECT: u8 = 0x10;
13const MSG_PING: u8 = 0x11;
14const MSG_PONG: u8 = 0x12;
15
16const MAX_PAYLOAD_SIZE: usize = 64 * 1024 * 1024;
18
19const MAX_CONNECT_PAYLOAD_SIZE: usize = 4096;
22
23const MAX_COLUMNS: usize = 4096;
25
26const MAX_ROWS: usize = 10_000_000;
28
29#[derive(Debug, Clone)]
30pub enum Message {
31 Connect {
32 db_name: String,
33 password: Option<Zeroizing<String>>,
38 username: Option<String>,
42 },
43 ConnectOk {
44 version: String,
45 },
46 Query {
47 query: String,
48 },
49 ResultRows {
50 columns: Vec<String>,
51 rows: Vec<Vec<String>>,
52 },
53 ResultScalar {
54 value: String,
55 },
56 ResultOk {
57 affected: u64,
58 },
59 ResultMessage {
61 message: String,
62 },
63 Error {
64 message: String,
65 },
66 Disconnect,
67 Ping,
68 Pong,
69}
70
71impl Message {
72 pub fn encode(&self) -> Vec<u8> {
74 let (msg_type, payload) = match self {
75 Message::Connect {
76 db_name,
77 password,
78 username,
79 } => {
80 let mut buf = encode_string(db_name);
81 match password {
83 Some(p) => buf.extend_from_slice(&encode_string(p)),
84 None => buf.extend_from_slice(&0u32.to_le_bytes()),
85 }
86 match username {
89 Some(u) => buf.extend_from_slice(&encode_string(u)),
90 None => buf.extend_from_slice(&0u32.to_le_bytes()),
91 }
92 (MSG_CONNECT, buf)
93 }
94 Message::ConnectOk { version } => (MSG_CONNECT_OK, encode_string(version)),
95 Message::Query { query } => (MSG_QUERY, encode_string(query)),
96 Message::ResultRows { columns, rows } => {
97 let mut buf = Vec::new();
98 buf.extend_from_slice(&(columns.len() as u16).to_le_bytes());
99 for col in columns {
100 buf.extend_from_slice(&encode_string(col));
101 }
102 buf.extend_from_slice(&(rows.len() as u32).to_le_bytes());
103 for row in rows {
104 for val in row {
105 buf.extend_from_slice(&encode_string(val));
106 }
107 }
108 (MSG_RESULT_ROWS, buf)
109 }
110 Message::ResultScalar { value } => (MSG_RESULT_SCALAR, encode_string(value)),
111 Message::ResultOk { affected } => (MSG_RESULT_OK, affected.to_le_bytes().to_vec()),
112 Message::ResultMessage { message } => (MSG_RESULT_MSG, encode_string(message)),
113 Message::Error { message } => (MSG_ERROR, encode_string(message)),
114 Message::Disconnect => (MSG_DISCONNECT, Vec::new()),
115 Message::Ping => (MSG_PING, Vec::new()),
116 Message::Pong => (MSG_PONG, Vec::new()),
117 };
118
119 let mut frame = Vec::with_capacity(6 + payload.len());
120 frame.push(msg_type);
121 frame.push(0); frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
123 frame.extend_from_slice(&payload);
124 frame
125 }
126
127 pub fn decode(data: &[u8]) -> Result<Message, String> {
129 if data.len() < 6 {
130 return Err("frame too short".into());
131 }
132 let msg_type = data[0];
133 let _flags = data[1];
134 let len_bytes: [u8; 4] = data[2..6]
135 .try_into()
136 .map_err(|_| "invalid header length field".to_string())?;
137 let payload_len = u32::from_le_bytes(len_bytes) as usize;
138 if 6 + payload_len > data.len() {
139 return Err("payload length exceeds frame".into());
140 }
141 let payload = &data[6..6 + payload_len];
142
143 match msg_type {
144 MSG_CONNECT => {
145 let mut pos = 0;
146 let db_name = decode_string(payload, &mut pos)?;
147 let password = if pos < payload.len() {
150 let p = Zeroizing::new(decode_string(payload, &mut pos)?);
153 if p.is_empty() {
154 None
155 } else {
156 Some(p)
157 }
158 } else {
159 None
160 };
161 let username = if pos < payload.len() {
164 let u = decode_string(payload, &mut pos)?;
165 if u.is_empty() {
166 None
167 } else {
168 Some(u)
169 }
170 } else {
171 None
172 };
173 Ok(Message::Connect {
174 db_name,
175 password,
176 username,
177 })
178 }
179 MSG_CONNECT_OK => {
180 let version = decode_string(payload, &mut 0)?;
181 Ok(Message::ConnectOk { version })
182 }
183 MSG_QUERY => {
184 let query = decode_string(payload, &mut 0)?;
185 Ok(Message::Query { query })
186 }
187 MSG_RESULT_ROWS => {
188 let mut pos = 0;
189 if pos + 2 > payload.len() {
190 return Err("truncated column count".into());
191 }
192 let col_bytes: [u8; 2] = payload[pos..pos + 2]
193 .try_into()
194 .map_err(|_| "invalid column count bytes".to_string())?;
195 let col_count = u16::from_le_bytes(col_bytes) as usize;
196 pos += 2;
197 if col_count > MAX_COLUMNS {
198 return Err("too many columns".into());
199 }
200 let mut columns = Vec::with_capacity(col_count);
201 for _ in 0..col_count {
202 columns.push(decode_string(payload, &mut pos)?);
203 }
204 if pos + 4 > payload.len() {
205 return Err("truncated row count".into());
206 }
207 let row_bytes: [u8; 4] = payload[pos..pos + 4]
208 .try_into()
209 .map_err(|_| "invalid row count bytes".to_string())?;
210 let row_count = u32::from_le_bytes(row_bytes) as usize;
211 pos += 4;
212 if row_count > MAX_ROWS {
213 return Err("too many rows".into());
214 }
215 let mut rows = Vec::with_capacity(row_count);
216 for _ in 0..row_count {
217 let mut row = Vec::with_capacity(col_count);
218 for _ in 0..col_count {
219 row.push(decode_string(payload, &mut pos)?);
220 }
221 rows.push(row);
222 }
223 Ok(Message::ResultRows { columns, rows })
224 }
225 MSG_RESULT_SCALAR => {
226 let value = decode_string(payload, &mut 0)?;
227 Ok(Message::ResultScalar { value })
228 }
229 MSG_RESULT_OK => {
230 if payload.len() < 8 {
231 return Err("truncated result ok payload".into());
232 }
233 let aff_bytes: [u8; 8] = payload[0..8]
234 .try_into()
235 .map_err(|_| "invalid affected count bytes".to_string())?;
236 let affected = u64::from_le_bytes(aff_bytes);
237 Ok(Message::ResultOk { affected })
238 }
239 MSG_RESULT_MSG => {
240 let message = decode_string(payload, &mut 0)?;
241 Ok(Message::ResultMessage { message })
242 }
243 MSG_ERROR => {
244 let message = decode_string(payload, &mut 0)?;
245 Ok(Message::Error { message })
246 }
247 MSG_DISCONNECT => Ok(Message::Disconnect),
248 MSG_PING => Ok(Message::Ping),
249 MSG_PONG => Ok(Message::Pong),
250 _ => Err(format!("unknown message type: {msg_type:#x}")),
251 }
252 }
253
254 pub async fn write_to<W: AsyncWriteExt + Unpin>(&self, writer: &mut W) -> std::io::Result<()> {
256 let bytes = self.encode();
257 writer.write_all(&bytes).await
258 }
259
260 pub async fn read_from<R: AsyncReadExt + Unpin>(
262 reader: &mut R,
263 ) -> std::io::Result<Option<Message>> {
264 Self::read_from_with_limit(reader, MAX_PAYLOAD_SIZE).await
265 }
266
267 pub async fn read_from_preauth<R: AsyncReadExt + Unpin>(
271 reader: &mut R,
272 ) -> std::io::Result<Option<Message>> {
273 Self::read_from_with_limit(reader, MAX_CONNECT_PAYLOAD_SIZE).await
274 }
275
276 async fn read_from_with_limit<R: AsyncReadExt + Unpin>(
278 reader: &mut R,
279 max_payload: usize,
280 ) -> std::io::Result<Option<Message>> {
281 let mut header = [0u8; 6];
282 match reader.read_exact(&mut header).await {
283 Ok(_) => {}
284 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
285 Err(e) => return Err(e),
286 }
287 let len_bytes: [u8; 4] = header[2..6].try_into().map_err(|_| {
288 std::io::Error::new(
289 std::io::ErrorKind::InvalidData,
290 "invalid header length field",
291 )
292 })?;
293 let payload_len = u32::from_le_bytes(len_bytes) as usize;
294 if payload_len > max_payload {
295 return Err(std::io::Error::new(
296 std::io::ErrorKind::InvalidData,
297 format!("payload too large: {payload_len} bytes (max {max_payload})"),
298 ));
299 }
300 let mut payload = vec![0u8; payload_len];
301 if payload_len > 0 {
302 reader.read_exact(&mut payload).await?;
303 }
304
305 let mut full = Vec::with_capacity(6 + payload_len);
306 full.extend_from_slice(&header);
307 full.extend_from_slice(&payload);
308
309 Message::decode(&full)
310 .map(Some)
311 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
312 }
313}
314
315fn encode_string(s: &str) -> Vec<u8> {
316 let mut buf = Vec::with_capacity(4 + s.len());
317 buf.extend_from_slice(&(s.len() as u32).to_le_bytes());
318 buf.extend_from_slice(s.as_bytes());
319 buf
320}
321
322fn decode_string(data: &[u8], pos: &mut usize) -> Result<String, String> {
323 if *pos + 4 > data.len() {
324 return Err("truncated string length".into());
325 }
326 let len_bytes: [u8; 4] = data[*pos..*pos + 4]
327 .try_into()
328 .map_err(|_| "invalid string length bytes".to_string())?;
329 let len = u32::from_le_bytes(len_bytes) as usize;
330 *pos += 4;
331 if *pos + len > data.len() {
332 return Err("truncated string data".into());
333 }
334 let s = String::from_utf8_lossy(&data[*pos..*pos + len]).into_owned();
335 *pos += len;
336 Ok(s)
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn test_encode_decode_query() {
345 let msg = Message::Query {
346 query: "User filter .age > 30".into(),
347 };
348 let bytes = msg.encode();
349 let decoded = Message::decode(&bytes).unwrap();
350 match decoded {
351 Message::Query { query } => assert_eq!(query, "User filter .age > 30"),
352 _ => panic!("expected Query"),
353 }
354 }
355
356 #[test]
357 fn test_encode_decode_connect_with_username() {
358 let msg = Message::Connect {
359 db_name: "mydb".into(),
360 password: Some(Zeroizing::new("secret".into())),
361 username: Some("alice".into()),
362 };
363 let bytes = msg.encode();
364 match Message::decode(&bytes).unwrap() {
365 Message::Connect {
366 db_name,
367 password,
368 username,
369 } => {
370 assert_eq!(db_name, "mydb");
371 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("secret"));
372 assert_eq!(username.as_deref(), Some("alice"));
373 }
374 other => panic!("expected Connect, got {other:?}"),
375 }
376 }
377
378 #[test]
379 fn test_encode_decode_connect_without_username() {
380 let msg = Message::Connect {
382 db_name: "mydb".into(),
383 password: Some(Zeroizing::new("secret".into())),
384 username: None,
385 };
386 let bytes = msg.encode();
387 match Message::decode(&bytes).unwrap() {
388 Message::Connect {
389 db_name,
390 password,
391 username,
392 } => {
393 assert_eq!(db_name, "mydb");
394 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("secret"));
395 assert_eq!(username, None);
396 }
397 other => panic!("expected Connect, got {other:?}"),
398 }
399 }
400
401 #[test]
402 fn test_decode_old_client_connect_db_and_password_only() {
403 let mut payload = encode_string("mydb");
406 payload.extend_from_slice(&encode_string("pw"));
407 let mut frame = vec![MSG_CONNECT, 0];
408 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
409 frame.extend_from_slice(&payload);
410 match Message::decode(&frame).unwrap() {
411 Message::Connect {
412 db_name,
413 password,
414 username,
415 } => {
416 assert_eq!(db_name, "mydb");
417 assert_eq!(password.as_deref().map(|s| s.as_str()), Some("pw"));
418 assert_eq!(username, None, "old-client frame must yield username=None");
419 }
420 other => panic!("expected Connect, got {other:?}"),
421 }
422 }
423
424 #[test]
425 fn test_decode_old_client_connect_db_only() {
426 let payload = encode_string("mydb");
428 let mut frame = vec![MSG_CONNECT, 0];
429 frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
430 frame.extend_from_slice(&payload);
431 match Message::decode(&frame).unwrap() {
432 Message::Connect {
433 db_name,
434 password,
435 username,
436 } => {
437 assert_eq!(db_name, "mydb");
438 assert_eq!(password, None);
439 assert_eq!(username, None);
440 }
441 other => panic!("expected Connect, got {other:?}"),
442 }
443 }
444
445 #[test]
446 fn test_encode_decode_result_rows() {
447 let msg = Message::ResultRows {
448 columns: vec!["name".into(), "age".into()],
449 rows: vec![
450 vec!["Alice".into(), "30".into()],
451 vec!["Bob".into(), "25".into()],
452 ],
453 };
454 let bytes = msg.encode();
455 let decoded = Message::decode(&bytes).unwrap();
456 match decoded {
457 Message::ResultRows { columns, rows } => {
458 assert_eq!(columns, vec!["name", "age"]);
459 assert_eq!(rows.len(), 2);
460 }
461 _ => panic!("expected ResultRows"),
462 }
463 }
464
465 #[test]
466 fn test_encode_decode_result_message() {
467 let msg = Message::ResultMessage {
468 message: "type User created".into(),
469 };
470 let bytes = msg.encode();
471 let decoded = Message::decode(&bytes).unwrap();
472 match decoded {
473 Message::ResultMessage { message } => assert_eq!(message, "type User created"),
474 _ => panic!("expected ResultMessage"),
475 }
476 }
477
478 #[test]
479 fn test_encode_decode_error() {
480 let msg = Message::Error {
481 message: "table not found".into(),
482 };
483 let bytes = msg.encode();
484 let decoded = Message::decode(&bytes).unwrap();
485 match decoded {
486 Message::Error { message } => assert_eq!(message, "table not found"),
487 _ => panic!("expected Error"),
488 }
489 }
490
491 #[test]
492 fn test_decode_garbage_never_panics() {
493 let cases: Vec<Vec<u8>> = vec![
497 vec![], vec![0x03], vec![0x03, 0x00, 0x00, 0x00, 0x00], vec![0xFF, 0x00, 0x00, 0x00, 0x00, 0x00], vec![0x03, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
503 vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
505 vec![0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF],
507 vec![0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03],
509 ];
510 for bytes in cases {
511 let result = Message::decode(&bytes);
512 assert!(
513 result.is_err(),
514 "expected Err for malformed input {bytes:?}, got {result:?}"
515 );
516 }
517 }
518
519 #[test]
520 fn test_decode_arbitrary_prefixes_never_panic() {
521 let valid = Message::ResultRows {
524 columns: vec!["a".into(), "b".into()],
525 rows: vec![vec!["1".into(), "2".into()]],
526 }
527 .encode();
528 for end in 0..valid.len() {
529 let _ = Message::decode(&valid[..end]);
531 }
532 for i in 0..valid.len() {
534 let mut m = valid.clone();
535 m[i] = 0xFF;
536 let _ = Message::decode(&m);
537 }
538 }
539
540 #[test]
541 fn test_frame_length() {
542 let msg = Message::Query {
543 query: "User".into(),
544 };
545 let bytes = msg.encode();
546 assert!(bytes.len() >= 6);
547 let payload_len = u32::from_le_bytes(bytes[2..6].try_into().unwrap()) as usize;
548 assert_eq!(bytes.len(), 6 + payload_len);
549 }
550}