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