fraiseql_wire/protocol/decode/
mod.rs1use super::constants::{auth, tags};
4use super::message::{AuthenticationMessage, BackendMessage, ErrorFields, FieldDescription};
5use bytes::{Bytes, BytesMut};
6use std::io;
7
8struct Cursor<'a> {
14 data: &'a [u8],
15 offset: usize,
16}
17
18impl<'a> Cursor<'a> {
19 const fn new(data: &'a [u8]) -> Self {
20 Self { data, offset: 0 }
21 }
22
23 fn remaining(&self) -> &'a [u8] {
24 self.data.get(self.offset..).unwrap_or(&[])
27 }
28
29 const fn is_empty(&self) -> bool {
30 self.offset >= self.data.len()
31 }
32
33 fn read_u8(&mut self) -> io::Result<u8> {
34 let byte = *self
35 .data
36 .get(self.offset)
37 .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "byte"))?;
38 self.offset += 1;
39 Ok(byte)
40 }
41
42 fn read_i16_be(&mut self) -> io::Result<i16> {
43 let bytes: [u8; 2] = self
44 .data
45 .get(self.offset..self.offset + 2)
46 .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "i16"))?
47 .try_into()
48 .expect("slice of length 2 always converts to [u8; 2]");
52 self.offset += 2;
53 Ok(i16::from_be_bytes(bytes))
54 }
55
56 fn read_i32_be(&mut self) -> io::Result<i32> {
57 let bytes: [u8; 4] = self
58 .data
59 .get(self.offset..self.offset + 4)
60 .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "i32"))?
61 .try_into()
62 .expect("slice of length 4 always converts to [u8; 4]");
64 self.offset += 4;
65 Ok(i32::from_be_bytes(bytes))
66 }
67
68 fn read_slice(&mut self, n: usize) -> io::Result<&'a [u8]> {
69 let slice = self
70 .data
71 .get(self.offset..self.offset + n)
72 .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "slice"))?;
73 self.offset += n;
74 Ok(slice)
75 }
76
77 fn read_until_null(&mut self) -> io::Result<&'a [u8]> {
80 let tail = self.remaining();
81 let end = tail.iter().position(|&b| b == 0).ok_or_else(|| {
82 io::Error::new(
83 io::ErrorKind::InvalidData,
84 "missing null terminator in string",
85 )
86 })?;
87 let bytes = tail.get(..end).unwrap_or(&[]);
88 self.offset += end + 1;
90 Ok(bytes)
91 }
92
93 fn position_of_null(&self) -> Option<usize> {
95 self.remaining().iter().position(|&b| b == 0)
96 }
97}
98
99pub(crate) const MAX_FIELD_COUNT: usize = 2048;
105
106pub(crate) const MAX_ERROR_FIELD_BYTES: usize = 64 * 1024; pub(crate) const MAX_SASL_MECHANISMS: usize = 32;
118
119pub(crate) const MAX_PARAMETER_NAME_BYTES: usize = 256;
123
124pub(crate) const MAX_PARAMETER_VALUE_BYTES: usize = 64 * 1024; pub(crate) const MAX_MESSAGE_LEN: usize = 256 * 1024 * 1024; pub fn decode_message(data: &mut BytesMut) -> io::Result<(BackendMessage, usize)> {
160 if data.len() < 5 {
161 return Err(io::Error::new(
162 io::ErrorKind::UnexpectedEof,
163 "incomplete message header",
164 ));
165 }
166
167 let mut header = Cursor::new(data);
168 let tag = header.read_u8()?;
169 let len_i32 = header.read_i32_be()?;
170
171 if len_i32 < 4 {
174 return Err(io::Error::new(
175 io::ErrorKind::InvalidData,
176 "message length too small",
177 ));
178 }
179
180 let len = len_i32 as usize;
181
182 if len > MAX_MESSAGE_LEN {
187 return Err(io::Error::new(
188 io::ErrorKind::InvalidData,
189 format!("message length {len} exceeds maximum {MAX_MESSAGE_LEN}"),
190 ));
191 }
192
193 if data.len() < len + 1 {
194 return Err(io::Error::new(
195 io::ErrorKind::UnexpectedEof,
196 "incomplete message body",
197 ));
198 }
199
200 let msg_start = 5;
202 let msg_end = len + 1;
203 let msg_data = data
204 .get(msg_start..msg_end)
205 .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "message body slice"))?;
206
207 let msg = match tag {
208 tags::AUTHENTICATION => decode_authentication(msg_data)?,
209 tags::BACKEND_KEY_DATA => decode_backend_key_data(msg_data)?,
210 tags::COMMAND_COMPLETE => decode_command_complete(msg_data)?,
211 tags::DATA_ROW => decode_data_row(msg_data)?,
212 tags::ERROR_RESPONSE => decode_error_response(msg_data)?,
213 tags::NOTICE_RESPONSE => decode_notice_response(msg_data)?,
214 tags::PARAMETER_STATUS => decode_parameter_status(msg_data)?,
215 tags::READY_FOR_QUERY => decode_ready_for_query(msg_data)?,
216 tags::ROW_DESCRIPTION => decode_row_description(msg_data)?,
217 tags::EMPTY_QUERY_RESPONSE => BackendMessage::EmptyQueryResponse,
218 tags::NOTIFICATION_RESPONSE => decode_notification_response(msg_data)?,
219 tags::COPY_IN_RESPONSE | tags::COPY_OUT_RESPONSE | tags::COPY_BOTH_RESPONSE => {
226 return Err(io::Error::new(
227 io::ErrorKind::Unsupported,
228 "COPY protocol is not supported by fraiseql-wire",
229 ))
230 }
231 _ => {
232 return Err(io::Error::new(
233 io::ErrorKind::InvalidData,
234 format!("unknown message tag: {}", tag),
235 ))
236 }
237 };
238
239 Ok((msg, len + 1))
240}
241
242fn decode_authentication(data: &[u8]) -> io::Result<BackendMessage> {
243 let mut cur = Cursor::new(data);
244 let auth_type = cur
245 .read_i32_be()
246 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "auth type"))?;
247
248 let auth_msg = match auth_type {
249 auth::OK => AuthenticationMessage::Ok,
250 auth::CLEARTEXT_PASSWORD => AuthenticationMessage::CleartextPassword,
251 auth::MD5_PASSWORD => {
252 let salt_slice = cur
253 .read_slice(4)
254 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "salt data"))?;
255 let salt: [u8; 4] = salt_slice
256 .try_into()
257 .expect("slice of length 4 always converts to [u8; 4]");
259 AuthenticationMessage::Md5Password { salt }
260 }
261 auth::SASL => {
262 let mut mechanisms = Vec::new();
264 loop {
265 if cur.is_empty() {
266 break;
267 }
268 let Some(end) = cur.position_of_null() else {
269 break;
270 };
271 let mech_bytes = cur.read_slice(end).unwrap_or(&[]);
272 let mechanism = String::from_utf8_lossy(mech_bytes).to_string();
273 let _ = cur.read_u8();
275 if mechanism.is_empty() {
276 break;
277 }
278 if mechanisms.len() >= MAX_SASL_MECHANISMS {
279 break;
280 }
281 mechanisms.push(mechanism);
282 }
283 AuthenticationMessage::Sasl { mechanisms }
284 }
285 auth::SASL_CONTINUE => {
286 let data_vec = cur.remaining().to_vec();
288 AuthenticationMessage::SaslContinue { data: data_vec }
289 }
290 auth::SASL_FINAL => {
291 let data_vec = cur.remaining().to_vec();
293 AuthenticationMessage::SaslFinal { data: data_vec }
294 }
295 _ => {
296 return Err(io::Error::new(
297 io::ErrorKind::Unsupported,
298 format!("unsupported auth type: {}", auth_type),
299 ))
300 }
301 };
302
303 Ok(BackendMessage::Authentication(auth_msg))
304}
305
306fn decode_backend_key_data(data: &[u8]) -> io::Result<BackendMessage> {
307 let mut cur = Cursor::new(data);
308 let process_id = cur
309 .read_i32_be()
310 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "backend key data"))?;
311 let secret_key = cur
312 .read_i32_be()
313 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "backend key data"))?;
314 Ok(BackendMessage::BackendKeyData {
315 process_id,
316 secret_key,
317 })
318}
319
320fn decode_command_complete(data: &[u8]) -> io::Result<BackendMessage> {
321 let mut cur = Cursor::new(data);
322 let tag_bytes = cur.read_until_null()?;
323 let tag = String::from_utf8_lossy(tag_bytes).to_string();
324 Ok(BackendMessage::CommandComplete(tag))
325}
326
327fn decode_data_row(data: &[u8]) -> io::Result<BackendMessage> {
328 let mut cur = Cursor::new(data);
329 let field_count_i16 = cur
330 .read_i16_be()
331 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "field count"))?;
332 if field_count_i16 < 0 {
333 return Err(io::Error::new(
334 io::ErrorKind::InvalidData,
335 "negative field count",
336 ));
337 }
338 let field_count = field_count_i16 as usize;
339 if field_count > MAX_FIELD_COUNT {
340 return Err(io::Error::new(
341 io::ErrorKind::InvalidData,
342 format!("DataRow field count {field_count} exceeds maximum {MAX_FIELD_COUNT}"),
343 ));
344 }
345 let mut fields = Vec::with_capacity(field_count);
346
347 for _ in 0..field_count {
348 let field_len = cur
349 .read_i32_be()
350 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "field length"))?;
351
352 let field = if field_len == -1 {
353 None
354 } else if field_len < 0 {
355 return Err(io::Error::new(
356 io::ErrorKind::InvalidData,
357 "negative field length",
358 ));
359 } else {
360 let len = field_len as usize;
361 let field_slice = cur
362 .read_slice(len)
363 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "field data"))?;
364 Some(Bytes::copy_from_slice(field_slice))
365 };
366 fields.push(field);
367 }
368
369 Ok(BackendMessage::DataRow(fields))
370}
371
372fn decode_error_response(data: &[u8]) -> io::Result<BackendMessage> {
373 let fields = decode_error_fields(data)?;
374 Ok(BackendMessage::ErrorResponse(fields))
375}
376
377fn decode_notice_response(data: &[u8]) -> io::Result<BackendMessage> {
378 let fields = decode_error_fields(data)?;
379 Ok(BackendMessage::NoticeResponse(fields))
380}
381
382fn decode_error_fields(data: &[u8]) -> io::Result<ErrorFields> {
383 let mut fields = ErrorFields::default();
384 let mut cur = Cursor::new(data);
385
386 loop {
387 if cur.is_empty() {
388 break;
389 }
390 let field_type = cur.read_u8()?;
391 if field_type == 0 {
392 break;
393 }
394
395 let end = cur.position_of_null().ok_or_else(|| {
396 io::Error::new(
397 io::ErrorKind::InvalidData,
398 "missing null terminator in error field",
399 )
400 })?;
401 if end > MAX_ERROR_FIELD_BYTES {
402 return Err(io::Error::new(
403 io::ErrorKind::InvalidData,
404 format!("Error field too large ({end} bytes, max {MAX_ERROR_FIELD_BYTES})"),
405 ));
406 }
407 let value_bytes = cur.read_slice(end).unwrap_or(&[]);
408 let value = String::from_utf8_lossy(value_bytes).to_string();
409 let _ = cur.read_u8();
411
412 match field_type {
413 b'S' => fields.severity = Some(value),
414 b'C' => fields.code = Some(value),
415 b'M' => fields.message = Some(value),
416 b'D' => fields.detail = Some(value),
417 b'H' => fields.hint = Some(value),
418 b'P' => fields.position = Some(value),
419 _ => {} }
421 }
422
423 Ok(fields)
424}
425
426fn decode_parameter_status(data: &[u8]) -> io::Result<BackendMessage> {
427 let mut cur = Cursor::new(data);
428
429 let name_end = cur.position_of_null().ok_or_else(|| {
430 io::Error::new(
431 io::ErrorKind::InvalidData,
432 "missing null terminator in parameter name",
433 )
434 })?;
435 if name_end > MAX_PARAMETER_NAME_BYTES {
436 return Err(io::Error::new(
437 io::ErrorKind::InvalidData,
438 format!("Parameter name too long ({name_end} bytes, max {MAX_PARAMETER_NAME_BYTES})"),
439 ));
440 }
441 let name_bytes = cur.read_slice(name_end).unwrap_or(&[]);
442 let name = String::from_utf8_lossy(name_bytes).to_string();
443 let _ = cur.read_u8();
445
446 if cur.is_empty() {
447 return Err(io::Error::new(
448 io::ErrorKind::UnexpectedEof,
449 "parameter value",
450 ));
451 }
452 let value_end = cur.position_of_null().ok_or_else(|| {
453 io::Error::new(
454 io::ErrorKind::InvalidData,
455 "missing null terminator in parameter value",
456 )
457 })?;
458 if value_end > MAX_PARAMETER_VALUE_BYTES {
459 return Err(io::Error::new(
460 io::ErrorKind::InvalidData,
461 format!(
462 "Parameter value too long ({value_end} bytes, max {MAX_PARAMETER_VALUE_BYTES})"
463 ),
464 ));
465 }
466 let value_bytes = cur.read_slice(value_end).unwrap_or(&[]);
467 let value = String::from_utf8_lossy(value_bytes).to_string();
468
469 Ok(BackendMessage::ParameterStatus { name, value })
470}
471
472fn decode_notification_response(data: &[u8]) -> io::Result<BackendMessage> {
473 let mut cur = Cursor::new(data);
474 let process_id = cur
475 .read_i32_be()
476 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "notification process id"))?;
477 let channel_bytes = cur.read_until_null()?;
478 let channel = String::from_utf8_lossy(channel_bytes).to_string();
479 let payload_bytes = cur.read_until_null()?;
480 let payload = String::from_utf8_lossy(payload_bytes).to_string();
481 Ok(BackendMessage::NotificationResponse {
482 process_id,
483 channel,
484 payload,
485 })
486}
487
488fn decode_ready_for_query(data: &[u8]) -> io::Result<BackendMessage> {
489 let status = *data
490 .first()
491 .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "status byte"))?;
492 Ok(BackendMessage::ReadyForQuery { status })
493}
494
495fn decode_row_description(data: &[u8]) -> io::Result<BackendMessage> {
496 let mut cur = Cursor::new(data);
497 let field_count_i16 = cur
498 .read_i16_be()
499 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "field count"))?;
500 if field_count_i16 < 0 {
501 return Err(io::Error::new(
502 io::ErrorKind::InvalidData,
503 "negative field count",
504 ));
505 }
506 let field_count = field_count_i16 as usize;
507 if field_count > MAX_FIELD_COUNT {
508 return Err(io::Error::new(
509 io::ErrorKind::InvalidData,
510 format!("RowDescription field count {field_count} exceeds maximum {MAX_FIELD_COUNT}"),
511 ));
512 }
513 let mut fields = Vec::with_capacity(field_count);
514
515 for _ in 0..field_count {
516 let name_end = cur.position_of_null().ok_or_else(|| {
518 io::Error::new(
519 io::ErrorKind::InvalidData,
520 "missing null terminator in field name",
521 )
522 })?;
523 let name_bytes = cur.read_slice(name_end).unwrap_or(&[]);
524 let name = String::from_utf8_lossy(name_bytes).to_string();
525 let _ = cur.read_u8();
527
528 let table_oid = cur
530 .read_i32_be()
531 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "field descriptor"))?;
532 let column_attr = cur
533 .read_i16_be()
534 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "field descriptor"))?;
535 let type_oid = cur
536 .read_i32_be()
537 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "field descriptor"))?
538 as u32;
539 let type_size = cur
540 .read_i16_be()
541 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "field descriptor"))?;
542 let type_modifier = cur
543 .read_i32_be()
544 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "field descriptor"))?;
545 let format_code = cur
546 .read_i16_be()
547 .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "field descriptor"))?;
548
549 fields.push(FieldDescription {
550 name,
551 table_oid,
552 column_attr,
553 type_oid,
554 type_size,
555 type_modifier,
556 format_code,
557 });
558 }
559
560 Ok(BackendMessage::RowDescription(fields))
561}
562
563#[cfg(test)]
564mod tests;