1use rustlavel_core::{Error, Result};
7
8pub const PROTOCOL_VERSION: i32 = 196_608;
10
11pub const SSL_REQUEST_CODE: i32 = 80_877_103;
17
18#[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 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 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 pub fn query(&mut self, sql: &str) -> &mut Self {
102 self.message(b'Q', |buffer| {
103 buffer.cstr(sql);
104 })
105 }
106
107 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 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 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 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#[derive(Debug, Clone)]
169pub struct Field {
170 pub name: String,
171 pub type_oid: i32,
172}
173
174#[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 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 if let Some(sql) = sql {
198 text.push_str(&format!("\n SQL: {sql}"));
199 }
200 Error::msg(text)
201 }
202}
203
204#[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 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 Unsupported(i32),
244}
245
246impl Backend {
247 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)?; let type_oid = reader.i32()?;
268 reader.skip(8)?; 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
361struct 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 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 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 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()); body.extend_from_slice(&0i16.to_be_bytes()); body.extend_from_slice(&23i32.to_be_bytes()); 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}