1use super::types::*;
4
5pub(crate) const MAX_BACKEND_FRAME_LEN: usize = 64 * 1024 * 1024;
10
11fn decode_utf8(bytes: &[u8], context: &str) -> Result<String, String> {
12 std::str::from_utf8(bytes)
13 .map(str::to_string)
14 .map_err(|e| format!("{} is not valid UTF-8: {}", context, e))
15}
16
17impl BackendMessage {
18 pub fn decode(buf: &[u8]) -> Result<(Self, usize), String> {
20 if buf.len() < 5 {
21 return Err("Buffer too short".to_string());
22 }
23
24 let msg_type = buf[0];
25 let len = u32::from_be_bytes([buf[1], buf[2], buf[3], buf[4]]) as usize;
26
27 if len < 4 {
30 return Err(format!("Invalid message length: {} (minimum is 4)", len));
31 }
32 if len > MAX_BACKEND_FRAME_LEN {
33 return Err(format!(
34 "Message too large: {} bytes (max {})",
35 len, MAX_BACKEND_FRAME_LEN
36 ));
37 }
38
39 let frame_len = len
40 .checked_add(1)
41 .ok_or_else(|| "Message length overflow".to_string())?;
42
43 if buf.len() < frame_len {
44 return Err("Incomplete message".to_string());
45 }
46
47 let payload = &buf[5..frame_len];
48
49 let message = match msg_type {
50 b'R' => Self::decode_auth(payload)?,
51 b'S' => Self::decode_parameter_status(payload)?,
52 b'K' => Self::decode_backend_key(payload)?,
53 b'v' => Self::decode_negotiate_protocol_version(payload)?,
54 b'Z' => Self::decode_ready_for_query(payload)?,
55 b'T' => Self::decode_row_description(payload)?,
56 b'D' => Self::decode_data_row(payload)?,
57 b'C' => Self::decode_command_complete(payload)?,
58 b'E' => Self::decode_error_response(payload)?,
59 b'1' => {
60 if !payload.is_empty() {
61 return Err("ParseComplete must have empty payload".to_string());
62 }
63 BackendMessage::ParseComplete
64 }
65 b'2' => {
66 if !payload.is_empty() {
67 return Err("BindComplete must have empty payload".to_string());
68 }
69 BackendMessage::BindComplete
70 }
71 b'3' => {
72 if !payload.is_empty() {
73 return Err("CloseComplete must have empty payload".to_string());
74 }
75 BackendMessage::CloseComplete
76 }
77 b'n' => {
78 if !payload.is_empty() {
79 return Err("NoData must have empty payload".to_string());
80 }
81 BackendMessage::NoData
82 }
83 b's' => {
84 if !payload.is_empty() {
85 return Err("PortalSuspended must have empty payload".to_string());
86 }
87 BackendMessage::PortalSuspended
88 }
89 b't' => Self::decode_parameter_description(payload)?,
90 b'G' => Self::decode_copy_in_response(payload)?,
91 b'H' => Self::decode_copy_out_response(payload)?,
92 b'W' => Self::decode_copy_both_response(payload)?,
93 b'd' => BackendMessage::CopyData(payload.to_vec()),
94 b'c' => {
95 if !payload.is_empty() {
96 return Err("CopyDone must have empty payload".to_string());
97 }
98 BackendMessage::CopyDone
99 }
100 b'A' => Self::decode_notification_response(payload)?,
101 b'I' => {
102 if !payload.is_empty() {
103 return Err("EmptyQueryResponse must have empty payload".to_string());
104 }
105 BackendMessage::EmptyQueryResponse
106 }
107 b'N' => BackendMessage::NoticeResponse(Self::parse_error_fields(payload)?),
108 _ => return Err(format!("Unknown message type: {}", msg_type as char)),
109 };
110
111 Ok((message, frame_len))
112 }
113
114 fn decode_auth(payload: &[u8]) -> Result<Self, String> {
115 if payload.len() < 4 {
116 return Err("Auth payload too short".to_string());
117 }
118 let auth_type = i32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
119 match auth_type {
120 0 => {
121 if payload.len() != 4 {
122 return Err(format!(
123 "AuthenticationOk invalid payload length: {}",
124 payload.len()
125 ));
126 }
127 Ok(BackendMessage::AuthenticationOk)
128 }
129 2 => {
130 if payload.len() != 4 {
131 return Err(format!(
132 "AuthenticationKerberosV5 invalid payload length: {}",
133 payload.len()
134 ));
135 }
136 Ok(BackendMessage::AuthenticationKerberosV5)
137 }
138 3 => {
139 if payload.len() != 4 {
140 return Err(format!(
141 "AuthenticationCleartextPassword invalid payload length: {}",
142 payload.len()
143 ));
144 }
145 Ok(BackendMessage::AuthenticationCleartextPassword)
146 }
147 5 => {
148 if payload.len() != 8 {
149 return Err("MD5 auth payload too short (need salt)".to_string());
150 }
151 let mut salt = [0u8; 4];
152 salt.copy_from_slice(&payload[4..8]);
153 Ok(BackendMessage::AuthenticationMD5Password(salt))
154 }
155 6 => {
156 if payload.len() != 4 {
157 return Err(format!(
158 "AuthenticationSCMCredential invalid payload length: {}",
159 payload.len()
160 ));
161 }
162 Ok(BackendMessage::AuthenticationSCMCredential)
163 }
164 7 => {
165 if payload.len() != 4 {
166 return Err(format!(
167 "AuthenticationGSS invalid payload length: {}",
168 payload.len()
169 ));
170 }
171 Ok(BackendMessage::AuthenticationGSS)
172 }
173 8 => Ok(BackendMessage::AuthenticationGSSContinue(
174 payload[4..].to_vec(),
175 )),
176 9 => {
177 if payload.len() != 4 {
178 return Err(format!(
179 "AuthenticationSSPI invalid payload length: {}",
180 payload.len()
181 ));
182 }
183 Ok(BackendMessage::AuthenticationSSPI)
184 }
185 10 => {
186 let mut mechanisms = Vec::new();
188 let mut pos = 4;
189 while pos < payload.len() {
190 if payload[pos] == 0 {
191 break; }
193 let end = payload[pos..]
194 .iter()
195 .position(|&b| b == 0)
196 .map(|p| pos + p)
197 .ok_or("SASL mechanism list missing null terminator")?;
198 mechanisms.push(decode_utf8(&payload[pos..end], "SASL mechanism")?);
199 pos = end + 1;
200 }
201 if pos >= payload.len() {
202 return Err("SASL mechanism list missing final terminator".to_string());
203 }
204 if pos + 1 != payload.len() {
205 return Err("SASL mechanism list has trailing bytes".to_string());
206 }
207 if mechanisms.is_empty() {
208 return Err("SASL mechanism list is empty".to_string());
209 }
210 Ok(BackendMessage::AuthenticationSASL(mechanisms))
211 }
212 11 => {
213 Ok(BackendMessage::AuthenticationSASLContinue(
215 payload[4..].to_vec(),
216 ))
217 }
218 12 => {
219 Ok(BackendMessage::AuthenticationSASLFinal(
221 payload[4..].to_vec(),
222 ))
223 }
224 _ => Err(format!("Unknown auth type: {}", auth_type)),
225 }
226 }
227
228 fn decode_parameter_status(payload: &[u8]) -> Result<Self, String> {
229 let name_end = payload
230 .iter()
231 .position(|&b| b == 0)
232 .ok_or("ParameterStatus missing name terminator")?;
233 if name_end == 0 {
234 return Err("ParameterStatus name is empty".to_string());
235 }
236 let value_start = name_end + 1;
237 if value_start > payload.len() {
238 return Err("ParameterStatus missing value".to_string());
239 }
240 let value_end_rel = payload[value_start..]
241 .iter()
242 .position(|&b| b == 0)
243 .ok_or("ParameterStatus missing value terminator")?;
244 let value_end = value_start + value_end_rel;
245 if value_end + 1 != payload.len() {
246 return Err("ParameterStatus has trailing bytes".to_string());
247 }
248 Ok(BackendMessage::ParameterStatus {
249 name: decode_utf8(&payload[..name_end], "ParameterStatus name")?,
250 value: decode_utf8(&payload[value_start..value_end], "ParameterStatus value")?,
251 })
252 }
253
254 fn decode_backend_key(payload: &[u8]) -> Result<Self, String> {
255 if payload.len() < 8 {
256 return Err("BackendKeyData payload too short".to_string());
257 }
258 let key_len = payload.len() - 4;
259 if !(4..=256).contains(&key_len) {
260 return Err(format!(
261 "BackendKeyData invalid secret key length: {} (expected 4..=256)",
262 key_len
263 ));
264 }
265 let process_id = i32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
266 if process_id <= 0 {
267 return Err(format!("BackendKeyData invalid process id: {process_id}"));
268 }
269 Ok(BackendMessage::BackendKeyData {
270 process_id,
271 secret_key: payload[4..].to_vec(),
272 })
273 }
274
275 fn decode_negotiate_protocol_version(payload: &[u8]) -> Result<Self, String> {
276 if payload.len() < 8 {
277 return Err("NegotiateProtocolVersion payload too short".to_string());
278 }
279
280 let newest_minor_supported =
281 i32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
282 if newest_minor_supported < 0 {
283 return Err("NegotiateProtocolVersion newest_minor_supported is negative".to_string());
284 }
285
286 let unrecognized_count =
287 i32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
288 if unrecognized_count < 0 {
289 return Err(
290 "NegotiateProtocolVersion unrecognized option count is negative".to_string(),
291 );
292 }
293 let unrecognized_count = unrecognized_count as usize;
294 let remaining = payload.len() - 8;
295 if unrecognized_count > remaining {
298 return Err(format!(
299 "NegotiateProtocolVersion unrecognized option count {} exceeds payload capacity {}",
300 unrecognized_count, remaining
301 ));
302 }
303
304 let mut options = Vec::with_capacity(unrecognized_count);
305 let mut pos = 8usize;
306 for _ in 0..unrecognized_count {
307 if pos >= payload.len() {
308 return Err("NegotiateProtocolVersion missing option string terminator".to_string());
309 }
310 let rel_end = payload[pos..]
311 .iter()
312 .position(|&b| b == 0)
313 .ok_or("NegotiateProtocolVersion option missing null terminator")?;
314 let end = pos + rel_end;
315 options.push(decode_utf8(
316 &payload[pos..end],
317 "NegotiateProtocolVersion option",
318 )?);
319 pos = end + 1;
320 }
321
322 if pos != payload.len() {
323 return Err("NegotiateProtocolVersion has trailing bytes".to_string());
324 }
325
326 Ok(BackendMessage::NegotiateProtocolVersion {
327 newest_minor_supported,
328 unrecognized_protocol_options: options,
329 })
330 }
331
332 fn decode_ready_for_query(payload: &[u8]) -> Result<Self, String> {
333 if payload.len() != 1 {
334 return Err("ReadyForQuery payload empty".to_string());
335 }
336 let status = match payload[0] {
337 b'I' => TransactionStatus::Idle,
338 b'T' => TransactionStatus::InBlock,
339 b'E' => TransactionStatus::Failed,
340 _ => return Err("Unknown transaction status".to_string()),
341 };
342 Ok(BackendMessage::ReadyForQuery(status))
343 }
344
345 fn decode_row_description(payload: &[u8]) -> Result<Self, String> {
346 if payload.len() < 2 {
347 return Err("RowDescription payload too short".to_string());
348 }
349
350 let raw_count = i16::from_be_bytes([payload[0], payload[1]]);
351 if raw_count < 0 {
352 return Err(format!("RowDescription invalid field count: {}", raw_count));
353 }
354 let field_count = raw_count as usize;
355 let mut fields = Vec::with_capacity(field_count);
356 let mut pos = 2;
357
358 for _ in 0..field_count {
359 let name_end = payload[pos..]
361 .iter()
362 .position(|&b| b == 0)
363 .ok_or("Missing null terminator in field name")?;
364 let name = decode_utf8(&payload[pos..pos + name_end], "RowDescription field name")?;
365 pos += name_end + 1; if pos + 18 > payload.len() {
369 return Err("RowDescription field truncated".to_string());
370 }
371
372 let table_oid = u32::from_be_bytes([
373 payload[pos],
374 payload[pos + 1],
375 payload[pos + 2],
376 payload[pos + 3],
377 ]);
378 pos += 4;
379
380 let column_attr = i16::from_be_bytes([payload[pos], payload[pos + 1]]);
381 pos += 2;
382
383 let type_oid = u32::from_be_bytes([
384 payload[pos],
385 payload[pos + 1],
386 payload[pos + 2],
387 payload[pos + 3],
388 ]);
389 pos += 4;
390
391 let type_size = i16::from_be_bytes([payload[pos], payload[pos + 1]]);
392 pos += 2;
393
394 let type_modifier = i32::from_be_bytes([
395 payload[pos],
396 payload[pos + 1],
397 payload[pos + 2],
398 payload[pos + 3],
399 ]);
400 pos += 4;
401
402 let format = i16::from_be_bytes([payload[pos], payload[pos + 1]]);
403 if !(0..=1).contains(&format) {
404 return Err(format!("RowDescription invalid format code: {}", format));
405 }
406 pos += 2;
407
408 fields.push(FieldDescription {
409 name,
410 table_oid,
411 column_attr,
412 type_oid,
413 type_size,
414 type_modifier,
415 format,
416 });
417 }
418
419 if pos != payload.len() {
420 return Err("RowDescription has trailing bytes".to_string());
421 }
422
423 Ok(BackendMessage::RowDescription(fields))
424 }
425
426 fn decode_data_row(payload: &[u8]) -> Result<Self, String> {
427 if payload.len() < 2 {
428 return Err("DataRow payload too short".to_string());
429 }
430
431 let raw_count = i16::from_be_bytes([payload[0], payload[1]]);
432 if raw_count < 0 {
433 return Err(format!("DataRow invalid column count: {}", raw_count));
434 }
435 let column_count = raw_count as usize;
436 if column_count > (payload.len() - 2) / 4 + 1 {
438 return Err(format!(
439 "DataRow claims {} columns but payload is only {} bytes",
440 column_count,
441 payload.len()
442 ));
443 }
444 let mut columns = Vec::with_capacity(column_count);
445 let mut pos = 2;
446
447 for _ in 0..column_count {
448 if pos + 4 > payload.len() {
449 return Err("DataRow truncated".to_string());
450 }
451
452 let len = i32::from_be_bytes([
453 payload[pos],
454 payload[pos + 1],
455 payload[pos + 2],
456 payload[pos + 3],
457 ]);
458 pos += 4;
459
460 if len == -1 {
461 columns.push(None);
463 } else {
464 if len < -1 {
465 return Err(format!("DataRow invalid column length: {}", len));
466 }
467 let len = len as usize;
468 if len > payload.len().saturating_sub(pos) {
469 return Err("DataRow column data truncated".to_string());
470 }
471 let data = payload[pos..pos + len].to_vec();
472 pos += len;
473 columns.push(Some(data));
474 }
475 }
476
477 if pos != payload.len() {
478 return Err("DataRow has trailing bytes".to_string());
479 }
480
481 Ok(BackendMessage::DataRow(columns))
482 }
483
484 fn decode_command_complete(payload: &[u8]) -> Result<Self, String> {
485 if payload.last().copied() != Some(0) {
486 return Err("CommandComplete missing null terminator".to_string());
487 }
488 let tag_bytes = &payload[..payload.len() - 1];
489 if tag_bytes.is_empty() {
490 return Err("CommandComplete tag is empty".to_string());
491 }
492 if tag_bytes.contains(&0) {
493 return Err("CommandComplete contains interior null byte".to_string());
494 }
495 let tag = decode_utf8(tag_bytes, "CommandComplete tag")?;
496 Ok(BackendMessage::CommandComplete(tag))
497 }
498
499 fn decode_error_response(payload: &[u8]) -> Result<Self, String> {
500 Ok(BackendMessage::ErrorResponse(Self::parse_error_fields(
501 payload,
502 )?))
503 }
504
505 fn parse_error_fields(payload: &[u8]) -> Result<ErrorFields, String> {
506 if payload.last().copied() != Some(0) {
507 return Err("ErrorResponse missing final terminator".to_string());
508 }
509 let mut fields = ErrorFields::default();
510 let mut i = 0;
511 while i < payload.len() && payload[i] != 0 {
512 let field_type = payload[i];
513 i += 1;
514 let end = payload[i..]
515 .iter()
516 .position(|&b| b == 0)
517 .map(|p| p + i)
518 .ok_or("ErrorResponse field missing null terminator")?;
519 let value = decode_utf8(&payload[i..end], "ErrorResponse field")?;
520 i = end + 1;
521
522 match field_type {
523 b'S' => fields.severity = value,
524 b'C' => fields.code = value,
525 b'M' => fields.message = value,
526 b'D' => fields.detail = Some(value),
527 b'H' => fields.hint = Some(value),
528 _ => {}
529 }
530 }
531 if i + 1 != payload.len() {
532 return Err("ErrorResponse has trailing bytes after terminator".to_string());
533 }
534 Ok(fields)
535 }
536
537 fn decode_parameter_description(payload: &[u8]) -> Result<Self, String> {
538 if payload.len() < 2 {
539 return Err("ParameterDescription payload too short".to_string());
540 }
541 let raw_count = i16::from_be_bytes([payload[0], payload[1]]);
542 if raw_count < 0 {
543 return Err(format!("ParameterDescription invalid count: {}", raw_count));
544 }
545 let count = raw_count as usize;
546 let expected_len = 2 + count * 4;
547 if payload.len() < expected_len {
548 return Err(format!(
549 "ParameterDescription truncated: expected {} bytes, got {}",
550 expected_len,
551 payload.len()
552 ));
553 }
554 let mut oids = Vec::with_capacity(count);
555 let mut pos = 2;
556 for _ in 0..count {
557 oids.push(u32::from_be_bytes([
558 payload[pos],
559 payload[pos + 1],
560 payload[pos + 2],
561 payload[pos + 3],
562 ]));
563 pos += 4;
564 }
565 if pos != payload.len() {
566 return Err("ParameterDescription has trailing bytes".to_string());
567 }
568 Ok(BackendMessage::ParameterDescription(oids))
569 }
570
571 fn decode_copy_in_response(payload: &[u8]) -> Result<Self, String> {
572 if payload.len() < 3 {
573 return Err("CopyInResponse payload too short".to_string());
574 }
575 let format = payload[0];
576 if format > 1 {
577 return Err(format!(
578 "CopyInResponse invalid overall format code: {}",
579 format
580 ));
581 }
582 let num_columns = if payload.len() >= 3 {
583 let raw = i16::from_be_bytes([payload[1], payload[2]]);
584 if raw < 0 {
585 return Err(format!(
586 "CopyInResponse invalid negative column count: {}",
587 raw
588 ));
589 }
590 raw as usize
591 } else {
592 0
593 };
594 let mut column_formats = Vec::with_capacity(num_columns);
595 let mut pos = 3usize;
596 for _ in 0..num_columns {
597 if pos + 2 > payload.len() {
598 return Err("CopyInResponse truncated column format list".to_string());
599 }
600 let raw = i16::from_be_bytes([payload[pos], payload[pos + 1]]);
601 if !(0..=1).contains(&raw) {
602 return Err(format!("CopyInResponse invalid format code: {}", raw));
603 }
604 column_formats.push(raw as u8);
605 pos += 2;
606 }
607 if pos != payload.len() {
608 return Err("CopyInResponse has trailing bytes".to_string());
609 }
610 Ok(BackendMessage::CopyInResponse {
611 format,
612 column_formats,
613 })
614 }
615
616 fn decode_copy_out_response(payload: &[u8]) -> Result<Self, String> {
617 if payload.len() < 3 {
618 return Err("CopyOutResponse payload too short".to_string());
619 }
620 let format = payload[0];
621 if format > 1 {
622 return Err(format!(
623 "CopyOutResponse invalid overall format code: {}",
624 format
625 ));
626 }
627 let num_columns = if payload.len() >= 3 {
628 let raw = i16::from_be_bytes([payload[1], payload[2]]);
629 if raw < 0 {
630 return Err(format!(
631 "CopyOutResponse invalid negative column count: {}",
632 raw
633 ));
634 }
635 raw as usize
636 } else {
637 0
638 };
639 let mut column_formats = Vec::with_capacity(num_columns);
640 let mut pos = 3usize;
641 for _ in 0..num_columns {
642 if pos + 2 > payload.len() {
643 return Err("CopyOutResponse truncated column format list".to_string());
644 }
645 let raw = i16::from_be_bytes([payload[pos], payload[pos + 1]]);
646 if !(0..=1).contains(&raw) {
647 return Err(format!("CopyOutResponse invalid format code: {}", raw));
648 }
649 column_formats.push(raw as u8);
650 pos += 2;
651 }
652 if pos != payload.len() {
653 return Err("CopyOutResponse has trailing bytes".to_string());
654 }
655 Ok(BackendMessage::CopyOutResponse {
656 format,
657 column_formats,
658 })
659 }
660
661 fn decode_copy_both_response(payload: &[u8]) -> Result<Self, String> {
662 if payload.len() < 3 {
663 return Err("CopyBothResponse payload too short".to_string());
664 }
665 let format = payload[0];
666 if format > 1 {
667 return Err(format!(
668 "CopyBothResponse invalid overall format code: {}",
669 format
670 ));
671 }
672 let num_columns = if payload.len() >= 3 {
673 let raw = i16::from_be_bytes([payload[1], payload[2]]);
674 if raw < 0 {
675 return Err(format!(
676 "CopyBothResponse invalid negative column count: {}",
677 raw
678 ));
679 }
680 raw as usize
681 } else {
682 0
683 };
684 let mut column_formats = Vec::with_capacity(num_columns);
685 let mut pos = 3usize;
686 for _ in 0..num_columns {
687 if pos + 2 > payload.len() {
688 return Err("CopyBothResponse truncated column format list".to_string());
689 }
690 let raw = i16::from_be_bytes([payload[pos], payload[pos + 1]]);
691 if !(0..=1).contains(&raw) {
692 return Err(format!("CopyBothResponse invalid format code: {}", raw));
693 }
694 column_formats.push(raw as u8);
695 pos += 2;
696 }
697 if pos != payload.len() {
698 return Err("CopyBothResponse has trailing bytes".to_string());
699 }
700 Ok(BackendMessage::CopyBothResponse {
701 format,
702 column_formats,
703 })
704 }
705
706 fn decode_notification_response(payload: &[u8]) -> Result<Self, String> {
707 if payload.len() < 6 {
708 return Err("NotificationResponse too short".to_string());
710 }
711 let process_id = i32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
712
713 let mut i = 4;
715 let remaining = payload.get(i..).unwrap_or(&[]);
716 let channel_end = remaining
717 .iter()
718 .position(|&b| b == 0)
719 .ok_or("NotificationResponse: missing channel null terminator")?;
720 if channel_end == 0 {
721 return Err("NotificationResponse channel is empty".to_string());
722 }
723 let channel = decode_utf8(&remaining[..channel_end], "NotificationResponse channel")?;
724 i += channel_end + 1;
725
726 let remaining = payload.get(i..).unwrap_or(&[]);
728 let payload_end = remaining
729 .iter()
730 .position(|&b| b == 0)
731 .ok_or("NotificationResponse: missing payload null terminator")?;
732 let notification_payload =
733 decode_utf8(&remaining[..payload_end], "NotificationResponse payload")?;
734 if i + payload_end + 1 != payload.len() {
735 return Err("NotificationResponse has trailing bytes".to_string());
736 }
737
738 Ok(BackendMessage::NotificationResponse {
739 process_id,
740 channel,
741 payload: notification_payload,
742 })
743 }
744}