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 },
39 ConnectOk {
40 version: String,
41 },
42 Query {
43 query: String,
44 },
45 ResultRows {
46 columns: Vec<String>,
47 rows: Vec<Vec<String>>,
48 },
49 ResultScalar {
50 value: String,
51 },
52 ResultOk {
53 affected: u64,
54 },
55 ResultMessage {
57 message: String,
58 },
59 Error {
60 message: String,
61 },
62 Disconnect,
63 Ping,
64 Pong,
65}
66
67impl Message {
68 pub fn encode(&self) -> Vec<u8> {
70 let (msg_type, payload) = match self {
71 Message::Connect { db_name, password } => {
72 let mut buf = encode_string(db_name);
73 match password {
75 Some(p) => buf.extend_from_slice(&encode_string(p)),
76 None => buf.extend_from_slice(&0u32.to_le_bytes()),
77 }
78 (MSG_CONNECT, buf)
79 }
80 Message::ConnectOk { version } => (MSG_CONNECT_OK, encode_string(version)),
81 Message::Query { query } => (MSG_QUERY, encode_string(query)),
82 Message::ResultRows { columns, rows } => {
83 let mut buf = Vec::new();
84 buf.extend_from_slice(&(columns.len() as u16).to_le_bytes());
85 for col in columns {
86 buf.extend_from_slice(&encode_string(col));
87 }
88 buf.extend_from_slice(&(rows.len() as u32).to_le_bytes());
89 for row in rows {
90 for val in row {
91 buf.extend_from_slice(&encode_string(val));
92 }
93 }
94 (MSG_RESULT_ROWS, buf)
95 }
96 Message::ResultScalar { value } => (MSG_RESULT_SCALAR, encode_string(value)),
97 Message::ResultOk { affected } => (MSG_RESULT_OK, affected.to_le_bytes().to_vec()),
98 Message::ResultMessage { message } => (MSG_RESULT_MSG, encode_string(message)),
99 Message::Error { message } => (MSG_ERROR, encode_string(message)),
100 Message::Disconnect => (MSG_DISCONNECT, Vec::new()),
101 Message::Ping => (MSG_PING, Vec::new()),
102 Message::Pong => (MSG_PONG, Vec::new()),
103 };
104
105 let mut frame = Vec::with_capacity(6 + payload.len());
106 frame.push(msg_type);
107 frame.push(0); frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
109 frame.extend_from_slice(&payload);
110 frame
111 }
112
113 pub fn decode(data: &[u8]) -> Result<Message, String> {
115 if data.len() < 6 {
116 return Err("frame too short".into());
117 }
118 let msg_type = data[0];
119 let _flags = data[1];
120 let len_bytes: [u8; 4] = data[2..6]
121 .try_into()
122 .map_err(|_| "invalid header length field".to_string())?;
123 let payload_len = u32::from_le_bytes(len_bytes) as usize;
124 if 6 + payload_len > data.len() {
125 return Err("payload length exceeds frame".into());
126 }
127 let payload = &data[6..6 + payload_len];
128
129 match msg_type {
130 MSG_CONNECT => {
131 let mut pos = 0;
132 let db_name = decode_string(payload, &mut pos)?;
133 let password = if pos < payload.len() {
136 let p = Zeroizing::new(decode_string(payload, &mut pos)?);
139 if p.is_empty() {
140 None
141 } else {
142 Some(p)
143 }
144 } else {
145 None
146 };
147 Ok(Message::Connect { db_name, password })
148 }
149 MSG_CONNECT_OK => {
150 let version = decode_string(payload, &mut 0)?;
151 Ok(Message::ConnectOk { version })
152 }
153 MSG_QUERY => {
154 let query = decode_string(payload, &mut 0)?;
155 Ok(Message::Query { query })
156 }
157 MSG_RESULT_ROWS => {
158 let mut pos = 0;
159 if pos + 2 > payload.len() {
160 return Err("truncated column count".into());
161 }
162 let col_bytes: [u8; 2] = payload[pos..pos + 2]
163 .try_into()
164 .map_err(|_| "invalid column count bytes".to_string())?;
165 let col_count = u16::from_le_bytes(col_bytes) as usize;
166 pos += 2;
167 if col_count > MAX_COLUMNS {
168 return Err("too many columns".into());
169 }
170 let mut columns = Vec::with_capacity(col_count);
171 for _ in 0..col_count {
172 columns.push(decode_string(payload, &mut pos)?);
173 }
174 if pos + 4 > payload.len() {
175 return Err("truncated row count".into());
176 }
177 let row_bytes: [u8; 4] = payload[pos..pos + 4]
178 .try_into()
179 .map_err(|_| "invalid row count bytes".to_string())?;
180 let row_count = u32::from_le_bytes(row_bytes) as usize;
181 pos += 4;
182 if row_count > MAX_ROWS {
183 return Err("too many rows".into());
184 }
185 let mut rows = Vec::with_capacity(row_count);
186 for _ in 0..row_count {
187 let mut row = Vec::with_capacity(col_count);
188 for _ in 0..col_count {
189 row.push(decode_string(payload, &mut pos)?);
190 }
191 rows.push(row);
192 }
193 Ok(Message::ResultRows { columns, rows })
194 }
195 MSG_RESULT_SCALAR => {
196 let value = decode_string(payload, &mut 0)?;
197 Ok(Message::ResultScalar { value })
198 }
199 MSG_RESULT_OK => {
200 if payload.len() < 8 {
201 return Err("truncated result ok payload".into());
202 }
203 let aff_bytes: [u8; 8] = payload[0..8]
204 .try_into()
205 .map_err(|_| "invalid affected count bytes".to_string())?;
206 let affected = u64::from_le_bytes(aff_bytes);
207 Ok(Message::ResultOk { affected })
208 }
209 MSG_RESULT_MSG => {
210 let message = decode_string(payload, &mut 0)?;
211 Ok(Message::ResultMessage { message })
212 }
213 MSG_ERROR => {
214 let message = decode_string(payload, &mut 0)?;
215 Ok(Message::Error { message })
216 }
217 MSG_DISCONNECT => Ok(Message::Disconnect),
218 MSG_PING => Ok(Message::Ping),
219 MSG_PONG => Ok(Message::Pong),
220 _ => Err(format!("unknown message type: {msg_type:#x}")),
221 }
222 }
223
224 pub async fn write_to<W: AsyncWriteExt + Unpin>(&self, writer: &mut W) -> std::io::Result<()> {
226 let bytes = self.encode();
227 writer.write_all(&bytes).await
228 }
229
230 pub async fn read_from<R: AsyncReadExt + Unpin>(
232 reader: &mut R,
233 ) -> std::io::Result<Option<Message>> {
234 Self::read_from_with_limit(reader, MAX_PAYLOAD_SIZE).await
235 }
236
237 pub async fn read_from_preauth<R: AsyncReadExt + Unpin>(
241 reader: &mut R,
242 ) -> std::io::Result<Option<Message>> {
243 Self::read_from_with_limit(reader, MAX_CONNECT_PAYLOAD_SIZE).await
244 }
245
246 async fn read_from_with_limit<R: AsyncReadExt + Unpin>(
248 reader: &mut R,
249 max_payload: usize,
250 ) -> std::io::Result<Option<Message>> {
251 let mut header = [0u8; 6];
252 match reader.read_exact(&mut header).await {
253 Ok(_) => {}
254 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
255 Err(e) => return Err(e),
256 }
257 let len_bytes: [u8; 4] = header[2..6].try_into().map_err(|_| {
258 std::io::Error::new(
259 std::io::ErrorKind::InvalidData,
260 "invalid header length field",
261 )
262 })?;
263 let payload_len = u32::from_le_bytes(len_bytes) as usize;
264 if payload_len > max_payload {
265 return Err(std::io::Error::new(
266 std::io::ErrorKind::InvalidData,
267 format!("payload too large: {payload_len} bytes (max {max_payload})"),
268 ));
269 }
270 let mut payload = vec![0u8; payload_len];
271 if payload_len > 0 {
272 reader.read_exact(&mut payload).await?;
273 }
274
275 let mut full = Vec::with_capacity(6 + payload_len);
276 full.extend_from_slice(&header);
277 full.extend_from_slice(&payload);
278
279 Message::decode(&full)
280 .map(Some)
281 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
282 }
283}
284
285fn encode_string(s: &str) -> Vec<u8> {
286 let mut buf = Vec::with_capacity(4 + s.len());
287 buf.extend_from_slice(&(s.len() as u32).to_le_bytes());
288 buf.extend_from_slice(s.as_bytes());
289 buf
290}
291
292fn decode_string(data: &[u8], pos: &mut usize) -> Result<String, String> {
293 if *pos + 4 > data.len() {
294 return Err("truncated string length".into());
295 }
296 let len_bytes: [u8; 4] = data[*pos..*pos + 4]
297 .try_into()
298 .map_err(|_| "invalid string length bytes".to_string())?;
299 let len = u32::from_le_bytes(len_bytes) as usize;
300 *pos += 4;
301 if *pos + len > data.len() {
302 return Err("truncated string data".into());
303 }
304 let s = String::from_utf8_lossy(&data[*pos..*pos + len]).into_owned();
305 *pos += len;
306 Ok(s)
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn test_encode_decode_query() {
315 let msg = Message::Query {
316 query: "User filter .age > 30".into(),
317 };
318 let bytes = msg.encode();
319 let decoded = Message::decode(&bytes).unwrap();
320 match decoded {
321 Message::Query { query } => assert_eq!(query, "User filter .age > 30"),
322 _ => panic!("expected Query"),
323 }
324 }
325
326 #[test]
327 fn test_encode_decode_result_rows() {
328 let msg = Message::ResultRows {
329 columns: vec!["name".into(), "age".into()],
330 rows: vec![
331 vec!["Alice".into(), "30".into()],
332 vec!["Bob".into(), "25".into()],
333 ],
334 };
335 let bytes = msg.encode();
336 let decoded = Message::decode(&bytes).unwrap();
337 match decoded {
338 Message::ResultRows { columns, rows } => {
339 assert_eq!(columns, vec!["name", "age"]);
340 assert_eq!(rows.len(), 2);
341 }
342 _ => panic!("expected ResultRows"),
343 }
344 }
345
346 #[test]
347 fn test_encode_decode_result_message() {
348 let msg = Message::ResultMessage {
349 message: "type User created".into(),
350 };
351 let bytes = msg.encode();
352 let decoded = Message::decode(&bytes).unwrap();
353 match decoded {
354 Message::ResultMessage { message } => assert_eq!(message, "type User created"),
355 _ => panic!("expected ResultMessage"),
356 }
357 }
358
359 #[test]
360 fn test_encode_decode_error() {
361 let msg = Message::Error {
362 message: "table not found".into(),
363 };
364 let bytes = msg.encode();
365 let decoded = Message::decode(&bytes).unwrap();
366 match decoded {
367 Message::Error { message } => assert_eq!(message, "table not found"),
368 _ => panic!("expected Error"),
369 }
370 }
371
372 #[test]
373 fn test_decode_garbage_never_panics() {
374 let cases: Vec<Vec<u8>> = vec![
378 vec![], vec![0x03], vec![0x03, 0x00, 0x00, 0x00, 0x00], vec![0xFF, 0x00, 0x00, 0x00, 0x00, 0x00], vec![0x03, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
384 vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
386 vec![0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF],
388 vec![0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03],
390 ];
391 for bytes in cases {
392 let result = Message::decode(&bytes);
393 assert!(
394 result.is_err(),
395 "expected Err for malformed input {bytes:?}, got {result:?}"
396 );
397 }
398 }
399
400 #[test]
401 fn test_decode_arbitrary_prefixes_never_panic() {
402 let valid = Message::ResultRows {
405 columns: vec!["a".into(), "b".into()],
406 rows: vec![vec!["1".into(), "2".into()]],
407 }
408 .encode();
409 for end in 0..valid.len() {
410 let _ = Message::decode(&valid[..end]);
412 }
413 for i in 0..valid.len() {
415 let mut m = valid.clone();
416 m[i] = 0xFF;
417 let _ = Message::decode(&m);
418 }
419 }
420
421 #[test]
422 fn test_frame_length() {
423 let msg = Message::Query {
424 query: "User".into(),
425 };
426 let bytes = msg.encode();
427 assert!(bytes.len() >= 6);
428 let payload_len = u32::from_le_bytes(bytes[2..6].try_into().unwrap()) as usize;
429 assert_eq!(bytes.len(), 6 + payload_len);
430 }
431}