Skip to main content

rustlavel_db/postgres/
protocol.rs

1//! PostgreSQL frontend/backend protocol, version 3.
2//!
3//! Messages are length-prefixed and big-endian. Frontend messages are built
4//! into a [`Buffer`]; backend messages are parsed by [`Backend::parse`].
5
6use rustlavel_core::{Error, Result};
7
8/// The protocol version in the startup packet: major 3, minor 0.
9pub const PROTOCOL_VERSION: i32 = 196_608;
10
11/// The version field of an `SSLRequest`: 1234 in the high half, 5679 in the low.
12///
13/// PostgreSQL has no message type for "please encrypt". It reuses the startup
14/// packet's shape and puts a number there that no real protocol version can
15/// be, which is why this reads as a magic constant — because it is one.
16pub const SSL_REQUEST_CODE: i32 = 80_877_103;
17
18/// A frontend message under construction.
19#[derive(Default)]
20pub struct Buffer {
21    bytes: Vec<u8>,
22}
23
24impl Buffer {
25    pub fn new() -> Self {
26        Buffer::default()
27    }
28
29    pub fn into_bytes(self) -> Vec<u8> {
30        self.bytes
31    }
32
33    fn i16(&mut self, value: i16) -> &mut Self {
34        self.bytes.extend_from_slice(&value.to_be_bytes());
35        self
36    }
37
38    fn i32(&mut self, value: i32) -> &mut Self {
39        self.bytes.extend_from_slice(&value.to_be_bytes());
40        self
41    }
42
43    fn cstr(&mut self, value: &str) -> &mut Self {
44        self.bytes.extend_from_slice(value.as_bytes());
45        self.bytes.push(0);
46        self
47    }
48
49    /// Write a message: a type byte, then a length that counts itself.
50    fn message(&mut self, tag: u8, body: impl FnOnce(&mut Buffer)) -> &mut Self {
51        self.bytes.push(tag);
52        let length_at = self.bytes.len();
53        self.bytes.extend_from_slice(&[0; 4]);
54
55        body(self);
56
57        let length = (self.bytes.len() - length_at) as i32;
58        self.bytes[length_at..length_at + 4].copy_from_slice(&length.to_be_bytes());
59        self
60    }
61
62    /// The startup packet, which has a length and a version but no type byte.
63    pub fn startup(&mut self, parameters: &[(&str, &str)]) -> &mut Self {
64        let length_at = self.bytes.len();
65        self.bytes.extend_from_slice(&[0; 4]);
66        self.i32(PROTOCOL_VERSION);
67
68        for (key, value) in parameters {
69            self.cstr(key);
70            self.cstr(value);
71        }
72        self.bytes.push(0);
73
74        let length = (self.bytes.len() - length_at) as i32;
75        self.bytes[length_at..length_at + 4].copy_from_slice(&length.to_be_bytes());
76        self
77    }
78
79    pub fn password(&mut self, password: &str) -> &mut Self {
80        self.message(b'p', |buffer| {
81            buffer.cstr(password);
82        })
83    }
84
85    pub fn sasl_initial(&mut self, mechanism: &str, response: &str) -> &mut Self {
86        self.message(b'p', |buffer| {
87            buffer.cstr(mechanism);
88            buffer.i32(response.len() as i32);
89            buffer.bytes.extend_from_slice(response.as_bytes());
90        })
91    }
92
93    pub fn sasl_response(&mut self, response: &str) -> &mut Self {
94        self.message(b'p', |buffer| {
95            buffer.bytes.extend_from_slice(response.as_bytes());
96        })
97    }
98
99    /// A simple query. Multiple statements are allowed but no parameters, so
100    /// this is reserved for DDL and internal bookkeeping.
101    pub fn query(&mut self, sql: &str) -> &mut Self {
102        self.message(b'Q', |buffer| {
103            buffer.cstr(sql);
104        })
105    }
106
107    /// Prepare an unnamed statement. Parameter types are left unspecified so
108    /// the server infers them.
109    pub fn parse(&mut self, name: &str, sql: &str) -> &mut Self {
110        self.message(b'P', |buffer| {
111            buffer.cstr(name);
112            buffer.cstr(sql);
113            buffer.i16(0);
114        })
115    }
116
117    /// Bind parameters, sent in text format.
118    ///
119    /// Text keeps the driver free of per-type binary encoders while remaining
120    /// exact: the server parses each parameter with the type it inferred.
121    pub fn bind(&mut self, portal: &str, statement: &str, params: &[Option<String>]) -> &mut Self {
122        self.message(b'B', |buffer| {
123            buffer.cstr(portal);
124            buffer.cstr(statement);
125            // No format codes: everything is text.
126            buffer.i16(0);
127            buffer.i16(params.len() as i16);
128            for param in params {
129                match param {
130                    None => {
131                        buffer.i32(-1);
132                    }
133                    Some(text) => {
134                        buffer.i32(text.len() as i32);
135                        buffer.bytes.extend_from_slice(text.as_bytes());
136                    }
137                }
138            }
139            // Results in text format too.
140            buffer.i16(0);
141        })
142    }
143
144    pub fn describe_portal(&mut self, portal: &str) -> &mut Self {
145        self.message(b'D', |buffer| {
146            buffer.bytes.push(b'P');
147            buffer.cstr(portal);
148        })
149    }
150
151    pub fn execute(&mut self, portal: &str, max_rows: i32) -> &mut Self {
152        self.message(b'E', |buffer| {
153            buffer.cstr(portal);
154            buffer.i32(max_rows);
155        })
156    }
157
158    pub fn sync(&mut self) -> &mut Self {
159        self.message(b'S', |_| {})
160    }
161
162    pub fn terminate(&mut self) -> &mut Self {
163        self.message(b'X', |_| {})
164    }
165}
166
167/// One column's metadata from a `RowDescription`.
168#[derive(Debug, Clone)]
169pub struct Field {
170    pub name: String,
171    pub type_oid: i32,
172}
173
174/// An error or notice sent by the server.
175#[derive(Debug, Clone, Default)]
176pub struct ServerError {
177    pub severity: String,
178    pub code: String,
179    pub message: String,
180    pub detail: Option<String>,
181    pub hint: Option<String>,
182    /// Byte offset into the statement, when the server could point at one.
183    pub position: Option<usize>,
184}
185
186impl ServerError {
187    pub fn into_error(self, sql: Option<&str>) -> Error {
188        let mut text = format!("{}: {}", self.code, self.message);
189        if let Some(detail) = &self.detail {
190            text.push_str(&format!(" — {detail}"));
191        }
192        if let Some(hint) = &self.hint {
193            text.push_str(&format!(" (hint: {hint})"));
194        }
195        // Pointing at the offending statement is the difference between a
196        // usable error and a puzzle.
197        if let Some(sql) = sql {
198            text.push_str(&format!("\n  SQL: {sql}"));
199        }
200        Error::msg(text)
201    }
202}
203
204/// A message received from the server.
205#[derive(Debug)]
206pub enum Backend {
207    Authentication(Authentication),
208    ParameterStatus { name: String, value: String },
209    BackendKeyData { process_id: i32, secret: i32 },
210    RowDescription(Vec<Field>),
211    DataRow(Vec<Option<Vec<u8>>>),
212    CommandComplete(String),
213    EmptyQueryResponse,
214    ReadyForQuery(TransactionStatus),
215    Error(ServerError),
216    Notice(ServerError),
217    ParseComplete,
218    BindComplete,
219    CloseComplete,
220    NoData,
221    PortalSuspended,
222    NotificationResponse { channel: String, payload: String },
223    /// Anything the driver does not need to act on.
224    Other(u8),
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub enum TransactionStatus {
229    Idle,
230    InTransaction,
231    Failed,
232}
233
234#[derive(Debug)]
235pub enum Authentication {
236    Ok,
237    CleartextPassword,
238    Md5Password { salt: [u8; 4] },
239    Sasl { mechanisms: Vec<String> },
240    SaslContinue { data: String },
241    SaslFinal { data: String },
242    /// A mechanism this driver does not implement (GSS, SSPI).
243    Unsupported(i32),
244}
245
246impl Backend {
247    /// Parse one message body, given its type byte.
248    pub fn parse(tag: u8, body: &[u8]) -> Result<Backend> {
249        let mut reader = Reader::new(body);
250
251        Ok(match tag {
252            b'R' => Backend::Authentication(parse_authentication(&mut reader)?),
253            b'S' => Backend::ParameterStatus {
254                name: reader.cstr()?,
255                value: reader.cstr()?,
256            },
257            b'K' => Backend::BackendKeyData {
258                process_id: reader.i32()?,
259                secret: reader.i32()?,
260            },
261            b'T' => {
262                let count = reader.i16()?;
263                let mut fields = Vec::with_capacity(count.max(0) as usize);
264                for _ in 0..count {
265                    let name = reader.cstr()?;
266                    reader.skip(6)?; // table oid, column index
267                    let type_oid = reader.i32()?;
268                    reader.skip(8)?; // type size, type modifier, format code
269                    fields.push(Field { name, type_oid });
270                }
271                Backend::RowDescription(fields)
272            }
273            b'D' => {
274                let count = reader.i16()?;
275                let mut values = Vec::with_capacity(count.max(0) as usize);
276                for _ in 0..count {
277                    let length = reader.i32()?;
278                    values.push(if length < 0 {
279                        None
280                    } else {
281                        Some(reader.take(length as usize)?.to_vec())
282                    });
283                }
284                Backend::DataRow(values)
285            }
286            b'C' => Backend::CommandComplete(reader.cstr()?),
287            b'I' => Backend::EmptyQueryResponse,
288            b'Z' => Backend::ReadyForQuery(match reader.u8()? {
289                b'T' => TransactionStatus::InTransaction,
290                b'E' => TransactionStatus::Failed,
291                _ => TransactionStatus::Idle,
292            }),
293            b'E' => Backend::Error(parse_server_error(&mut reader)?),
294            b'N' => Backend::Notice(parse_server_error(&mut reader)?),
295            b'A' => {
296                reader.i32()?;
297                Backend::NotificationResponse {
298                    channel: reader.cstr()?,
299                    payload: reader.cstr()?,
300                }
301            }
302            b'1' => Backend::ParseComplete,
303            b'2' => Backend::BindComplete,
304            b'3' => Backend::CloseComplete,
305            b'n' => Backend::NoData,
306            b's' => Backend::PortalSuspended,
307            other => Backend::Other(other),
308        })
309    }
310}
311
312fn parse_authentication(reader: &mut Reader<'_>) -> Result<Authentication> {
313    Ok(match reader.i32()? {
314        0 => Authentication::Ok,
315        3 => Authentication::CleartextPassword,
316        5 => {
317            let mut salt = [0u8; 4];
318            salt.copy_from_slice(reader.take(4)?);
319            Authentication::Md5Password { salt }
320        }
321        10 => {
322            let mut mechanisms = Vec::new();
323            loop {
324                let mechanism = reader.cstr()?;
325                if mechanism.is_empty() {
326                    break;
327                }
328                mechanisms.push(mechanism);
329            }
330            Authentication::Sasl { mechanisms }
331        }
332        11 => Authentication::SaslContinue { data: reader.rest_string() },
333        12 => Authentication::SaslFinal { data: reader.rest_string() },
334        other => Authentication::Unsupported(other),
335    })
336}
337
338fn parse_server_error(reader: &mut Reader<'_>) -> Result<ServerError> {
339    let mut error = ServerError::default();
340
341    loop {
342        let field = reader.u8()?;
343        if field == 0 {
344            break;
345        }
346        let value = reader.cstr()?;
347        match field {
348            b'S' => error.severity = value,
349            b'C' => error.code = value,
350            b'M' => error.message = value,
351            b'D' => error.detail = Some(value),
352            b'H' => error.hint = Some(value),
353            b'P' => error.position = value.parse().ok(),
354            _ => {}
355        }
356    }
357
358    Ok(error)
359}
360
361/// A cursor over a message body.
362struct Reader<'a> {
363    bytes: &'a [u8],
364    position: usize,
365}
366
367impl<'a> Reader<'a> {
368    fn new(bytes: &'a [u8]) -> Self {
369        Reader { bytes, position: 0 }
370    }
371
372    fn take(&mut self, count: usize) -> Result<&'a [u8]> {
373        let end = self.position + count;
374        if end > self.bytes.len() {
375            return Err(Error::Protocol("truncated message from the server".into()));
376        }
377        let slice = &self.bytes[self.position..end];
378        self.position = end;
379        Ok(slice)
380    }
381
382    fn skip(&mut self, count: usize) -> Result<()> {
383        self.take(count).map(|_| ())
384    }
385
386    fn u8(&mut self) -> Result<u8> {
387        Ok(self.take(1)?[0])
388    }
389
390    fn i16(&mut self) -> Result<i16> {
391        Ok(i16::from_be_bytes(self.take(2)?.try_into().expect("2 bytes")))
392    }
393
394    fn i32(&mut self) -> Result<i32> {
395        Ok(i32::from_be_bytes(self.take(4)?.try_into().expect("4 bytes")))
396    }
397
398    fn cstr(&mut self) -> Result<String> {
399        let start = self.position;
400        while self.position < self.bytes.len() && self.bytes[self.position] != 0 {
401            self.position += 1;
402        }
403        if self.position >= self.bytes.len() {
404            return Err(Error::Protocol("unterminated string from the server".into()));
405        }
406        let text = String::from_utf8_lossy(&self.bytes[start..self.position]).into_owned();
407        self.position += 1;
408        Ok(text)
409    }
410
411    fn rest_string(&mut self) -> String {
412        let text = String::from_utf8_lossy(&self.bytes[self.position..]).into_owned();
413        self.position = self.bytes.len();
414        text
415    }
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421
422    #[test]
423    fn the_ssl_request_code_is_the_one_postgresql_looks_for() {
424        // 1234 in the high half, 5679 in the low. Written out because a typo
425        // here produces a server that simply closes the connection.
426        assert_eq!(SSL_REQUEST_CODE, (1234 << 16) | 5679);
427        assert_eq!(SSL_REQUEST_CODE.to_be_bytes(), [0x04, 0xd2, 0x16, 0x2f]);
428    }
429
430    #[test]
431    fn an_ssl_request_cannot_be_mistaken_for_a_startup_packet() {
432        // The two share a shape, and the version field is the only thing that
433        // tells them apart.
434        assert_ne!(SSL_REQUEST_CODE, PROTOCOL_VERSION);
435    }
436
437    #[test]
438    fn builds_a_startup_packet() {
439        let bytes = Buffer::new()
440            .startup(&[("user", "ada"), ("database", "blog")])
441            .clone_bytes();
442
443        let length = i32::from_be_bytes(bytes[0..4].try_into().unwrap()) as usize;
444        assert_eq!(length, bytes.len());
445        assert_eq!(i32::from_be_bytes(bytes[4..8].try_into().unwrap()), PROTOCOL_VERSION);
446        assert_eq!(*bytes.last().unwrap(), 0);
447    }
448
449    #[test]
450    fn a_frontend_message_length_counts_itself() {
451        let bytes = Buffer::new().query("select 1").clone_bytes();
452
453        assert_eq!(bytes[0], b'Q');
454        let length = i32::from_be_bytes(bytes[1..5].try_into().unwrap()) as usize;
455        assert_eq!(length, bytes.len() - 1);
456        assert_eq!(&bytes[5..], b"select 1\0");
457    }
458
459    #[test]
460    fn binds_null_parameters_as_minus_one() {
461        let bytes = Buffer::new()
462            .bind("", "", &[Some("7".to_string()), None])
463            .clone_bytes();
464
465        // Two parameters, the second encoded with length -1.
466        assert!(bytes.windows(4).any(|w| w == (-1i32).to_be_bytes()));
467    }
468
469    #[test]
470    fn parses_a_row_description() {
471        let mut body = Vec::new();
472        body.extend_from_slice(&1i16.to_be_bytes());
473        body.extend_from_slice(b"id\0");
474        body.extend_from_slice(&0i32.to_be_bytes()); // table oid
475        body.extend_from_slice(&0i16.to_be_bytes()); // column index
476        body.extend_from_slice(&23i32.to_be_bytes()); // int4
477        body.extend_from_slice(&4i16.to_be_bytes());
478        body.extend_from_slice(&(-1i32).to_be_bytes());
479        body.extend_from_slice(&0i16.to_be_bytes());
480
481        match Backend::parse(b'T', &body).unwrap() {
482            Backend::RowDescription(fields) => {
483                assert_eq!(fields.len(), 1);
484                assert_eq!(fields[0].name, "id");
485                assert_eq!(fields[0].type_oid, 23);
486            }
487            other => panic!("expected a row description, got {other:?}"),
488        }
489    }
490
491    #[test]
492    fn parses_a_data_row_with_a_null() {
493        let mut body = Vec::new();
494        body.extend_from_slice(&2i16.to_be_bytes());
495        body.extend_from_slice(&3i32.to_be_bytes());
496        body.extend_from_slice(b"ada");
497        body.extend_from_slice(&(-1i32).to_be_bytes());
498
499        match Backend::parse(b'D', &body).unwrap() {
500            Backend::DataRow(values) => {
501                assert_eq!(values[0].as_deref(), Some(&b"ada"[..]));
502                assert_eq!(values[1], None);
503            }
504            other => panic!("expected a data row, got {other:?}"),
505        }
506    }
507
508    #[test]
509    fn parses_an_error_response() {
510        let mut body = Vec::new();
511        body.push(b'S');
512        body.extend_from_slice(b"ERROR\0");
513        body.push(b'C');
514        body.extend_from_slice(b"42P01\0");
515        body.push(b'M');
516        body.extend_from_slice(b"relation \"nope\" does not exist\0");
517        body.push(0);
518
519        match Backend::parse(b'E', &body).unwrap() {
520            Backend::Error(error) => {
521                assert_eq!(error.code, "42P01");
522                assert!(error.message.contains("does not exist"));
523                let rendered = error.into_error(Some("select * from nope")).to_string();
524                assert!(rendered.contains("SQL: select * from nope"));
525            }
526            other => panic!("expected an error, got {other:?}"),
527        }
528    }
529
530    #[test]
531    fn a_truncated_message_is_a_protocol_error() {
532        let error = Backend::parse(b'K', &[0, 0]).unwrap_err();
533        assert!(error.to_string().contains("truncated"));
534    }
535
536    impl Buffer {
537        fn clone_bytes(&self) -> Vec<u8> {
538            self.bytes.clone()
539        }
540    }
541}