use std::io;
use tokio::io::{AsyncRead, AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _};
pub(crate) const PROTOCOL_VERSION_3: i32 = 196_608;
const SSL_REQUEST_CODE: i32 = 80_877_103;
const GSS_ENC_REQUEST_CODE: i32 = 80_877_104;
const MAX_MESSAGE_LEN: usize = 64 * 1024 * 1024;
pub(crate) struct Startup {
pub(crate) parameters: Vec<(String, String)>,
}
impl Startup {
pub(crate) fn get(&self, key: &str) -> Option<&str> {
self.parameters
.iter()
.find(|(name, _)| name == key)
.map(|(_, value)| value.as_str())
}
}
#[derive(Debug)]
pub(crate) enum Frontend {
Query(String),
Password(String),
Parse {
name: String,
query: String,
parameter_count: usize,
},
Bind {
portal: String,
statement: String,
parameter_count: usize,
result_formats: Vec<i16>,
},
Describe {
kind: u8,
name: String,
},
Execute {
portal: String,
},
Close {
kind: u8,
name: String,
},
Sync,
Flush,
Terminate,
}
pub(crate) struct FrontendReader<R> {
inner: R,
}
impl<R: AsyncRead + Unpin> FrontendReader<R> {
pub(crate) const fn new(inner: R) -> Self {
Self { inner }
}
pub(crate) async fn read_startup<W: AsyncWrite + Unpin>(
&mut self,
writer: &mut BackendWriter<W>,
) -> io::Result<Startup> {
loop {
let length = self.inner.read_i32().await?;
let body_len = message_body_len(length, 4)?;
let mut body = vec![0u8; body_len];
self.inner.read_exact(&mut body).await?;
let version = i32::from_be_bytes([body[0], body[1], body[2], body[3]]);
match version {
SSL_REQUEST_CODE | GSS_ENC_REQUEST_CODE => {
writer.write_negotiation_refusal().await?;
}
PROTOCOL_VERSION_3 => {
return Ok(Startup {
parameters: parse_startup_parameters(&body[4..])?,
});
}
other => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("unsupported protocol version {other}"),
));
}
}
}
}
pub(crate) async fn read_message(&mut self) -> io::Result<Option<Frontend>> {
let tag = match self.inner.read_u8().await {
Ok(tag) => tag,
Err(error) if error.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
Err(error) => return Err(error),
};
let length = self.inner.read_i32().await?;
let body_len = message_body_len(length, 4)?;
let mut body = vec![0u8; body_len];
self.inner.read_exact(&mut body).await?;
let mut cursor = Cursor::new(&body);
let message = match tag {
b'Q' => Frontend::Query(cursor.read_cstr()?),
b'p' => Frontend::Password(cursor.read_cstr()?),
b'P' => {
let name = cursor.read_cstr()?;
let query = cursor.read_cstr()?;
let parameter_count = usize::from(cursor.read_i16()?.max(0).unsigned_abs());
Frontend::Parse {
name,
query,
parameter_count,
}
}
b'B' => {
let portal = cursor.read_cstr()?;
let statement = cursor.read_cstr()?;
let format_count = cursor.read_i16()?;
for _ in 0..format_count {
let _ = cursor.read_i16()?;
}
let parameter_count = usize::from(cursor.read_i16()?.max(0).unsigned_abs());
for _ in 0..parameter_count {
let len = cursor.read_i32()?;
if len > 0 {
cursor.skip(usize::try_from(len).unwrap_or(0))?;
}
}
let result_format_count = cursor.read_i16()?.max(0);
let mut result_formats =
Vec::with_capacity(usize::from(result_format_count.unsigned_abs()));
for _ in 0..result_format_count {
result_formats.push(cursor.read_i16()?);
}
Frontend::Bind {
portal,
statement,
parameter_count,
result_formats,
}
}
b'D' => {
let kind = cursor.read_u8()?;
let name = cursor.read_cstr()?;
Frontend::Describe { kind, name }
}
b'E' => {
let portal = cursor.read_cstr()?;
let _max_rows = cursor.read_i32()?;
Frontend::Execute { portal }
}
b'C' => {
let kind = cursor.read_u8()?;
let name = cursor.read_cstr()?;
Frontend::Close { kind, name }
}
b'S' => Frontend::Sync,
b'H' => Frontend::Flush,
b'X' => Frontend::Terminate,
other => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("unsupported frontend message '{}'", char::from(other)),
));
}
};
Ok(Some(message))
}
}
pub(crate) struct FieldDescription {
pub(crate) name: String,
pub(crate) type_oid: i32,
pub(crate) type_len: i16,
}
pub(crate) struct ErrorFields<'a> {
pub(crate) code: &'a str,
pub(crate) message: &'a str,
}
pub(crate) struct BackendWriter<W> {
inner: W,
buffer: Vec<u8>,
}
impl<W: AsyncWrite + Unpin> BackendWriter<W> {
pub(crate) const fn new(inner: W) -> Self {
Self {
inner,
buffer: Vec::new(),
}
}
async fn write_negotiation_refusal(&mut self) -> io::Result<()> {
self.inner.write_all(b"N").await?;
self.inner.flush().await
}
pub(crate) fn authentication_ok(&mut self) {
self.frame(b'R', |body| body.extend_from_slice(&0i32.to_be_bytes()));
}
pub(crate) fn authentication_cleartext_password(&mut self) {
self.frame(b'R', |body| body.extend_from_slice(&3i32.to_be_bytes()));
}
pub(crate) fn parameter_status(&mut self, name: &str, value: &str) {
self.frame(b'S', |body| {
put_cstr(body, name);
put_cstr(body, value);
});
}
pub(crate) fn backend_key_data(&mut self, process_id: i32, secret: i32) {
self.frame(b'K', |body| {
body.extend_from_slice(&process_id.to_be_bytes());
body.extend_from_slice(&secret.to_be_bytes());
});
}
pub(crate) fn ready_for_query(&mut self, status: u8) {
self.frame(b'Z', |body| body.push(status));
}
pub(crate) fn row_description(&mut self, fields: &[FieldDescription]) {
self.frame(b'T', |body| {
put_i16(body, i16::try_from(fields.len()).unwrap_or(i16::MAX));
for field in fields {
put_cstr(body, &field.name);
body.extend_from_slice(&0i32.to_be_bytes()); put_i16(body, 0); body.extend_from_slice(&field.type_oid.to_be_bytes());
put_i16(body, field.type_len);
body.extend_from_slice(&(-1i32).to_be_bytes()); put_i16(body, 0); }
});
}
pub(crate) fn no_data(&mut self) {
self.frame(b'n', |_| {});
}
pub(crate) fn parameter_description_empty(&mut self) {
self.frame(b't', |body| put_i16(body, 0));
}
pub(crate) fn data_row(&mut self, values: &[Option<Vec<u8>>]) {
self.frame(b'D', |body| {
put_i16(body, i16::try_from(values.len()).unwrap_or(i16::MAX));
for value in values {
match value {
Some(bytes) => {
body.extend_from_slice(
&i32::try_from(bytes.len()).unwrap_or(i32::MAX).to_be_bytes(),
);
body.extend_from_slice(bytes);
}
None => body.extend_from_slice(&(-1i32).to_be_bytes()),
}
}
});
}
pub(crate) fn command_complete(&mut self, tag: &str) {
self.frame(b'C', |body| put_cstr(body, tag));
}
pub(crate) fn empty_query_response(&mut self) {
self.frame(b'I', |_| {});
}
pub(crate) fn parse_complete(&mut self) {
self.frame(b'1', |_| {});
}
pub(crate) fn bind_complete(&mut self) {
self.frame(b'2', |_| {});
}
pub(crate) fn close_complete(&mut self) {
self.frame(b'3', |_| {});
}
pub(crate) fn error_response(&mut self, fields: &ErrorFields<'_>) {
self.frame(b'E', |body| {
body.push(b'S');
put_cstr(body, "ERROR");
body.push(b'V');
put_cstr(body, "ERROR");
body.push(b'C');
put_cstr(body, fields.code);
body.push(b'M');
put_cstr(body, fields.message);
body.push(0);
});
}
fn frame(&mut self, tag: u8, fill: impl FnOnce(&mut Vec<u8>)) {
self.buffer.push(tag);
let length_at = self.buffer.len();
self.buffer.extend_from_slice(&0i32.to_be_bytes());
fill(&mut self.buffer);
let length = i32::try_from(self.buffer.len() - length_at).unwrap_or(i32::MAX);
self.buffer[length_at..length_at + 4].copy_from_slice(&length.to_be_bytes());
}
pub(crate) async fn flush(&mut self) -> io::Result<()> {
if !self.buffer.is_empty() {
self.inner.write_all(&self.buffer).await?;
self.buffer.clear();
}
self.inner.flush().await
}
}
fn message_body_len(length: i32, header: usize) -> io::Result<usize> {
let length = usize::try_from(length)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "negative message length"))?;
if length < header || length > MAX_MESSAGE_LEN {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid message length {length}"),
));
}
Ok(length - header)
}
fn parse_startup_parameters(body: &[u8]) -> io::Result<Vec<(String, String)>> {
let mut cursor = Cursor::new(body);
let mut parameters = Vec::new();
loop {
let key = cursor.read_cstr()?;
if key.is_empty() {
break;
}
let value = cursor.read_cstr()?;
parameters.push((key, value));
}
Ok(parameters)
}
struct Cursor<'a> {
bytes: &'a [u8],
position: usize,
}
impl<'a> Cursor<'a> {
const fn new(bytes: &'a [u8]) -> Self {
Self { bytes, position: 0 }
}
fn read_u8(&mut self) -> io::Result<u8> {
let byte = *self.bytes.get(self.position).ok_or_else(truncated)?;
self.position += 1;
Ok(byte)
}
fn read_i16(&mut self) -> io::Result<i16> {
let end = self.position + 2;
let slice = self.bytes.get(self.position..end).ok_or_else(truncated)?;
self.position = end;
Ok(i16::from_be_bytes([slice[0], slice[1]]))
}
fn read_i32(&mut self) -> io::Result<i32> {
let end = self.position + 4;
let slice = self.bytes.get(self.position..end).ok_or_else(truncated)?;
self.position = end;
Ok(i32::from_be_bytes([slice[0], slice[1], slice[2], slice[3]]))
}
fn skip(&mut self, count: usize) -> io::Result<()> {
let end = self.position.checked_add(count).ok_or_else(truncated)?;
if end > self.bytes.len() {
return Err(truncated());
}
self.position = end;
Ok(())
}
fn read_cstr(&mut self) -> io::Result<String> {
let start = self.position;
while self.position < self.bytes.len() {
if self.bytes[self.position] == 0 {
let text = std::str::from_utf8(&self.bytes[start..self.position])
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid UTF-8"))?
.to_owned();
self.position += 1;
return Ok(text);
}
self.position += 1;
}
Err(truncated())
}
}
fn truncated() -> io::Error {
io::Error::new(io::ErrorKind::UnexpectedEof, "truncated message")
}
fn put_i16(buffer: &mut Vec<u8>, value: i16) {
buffer.extend_from_slice(&value.to_be_bytes());
}
fn put_cstr(buffer: &mut Vec<u8>, value: &str) {
buffer.extend_from_slice(value.as_bytes());
buffer.push(0);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cursor_reads_strings_and_integers() {
let bytes = [b'h', b'i', 0, 0x00, 0x2a, 0xff, 0xff, 0xff, 0xff];
let mut cursor = Cursor::new(&bytes);
assert_eq!(cursor.read_cstr().unwrap(), "hi");
assert_eq!(cursor.read_i16().unwrap(), 42);
assert_eq!(cursor.read_i32().unwrap(), -1);
assert!(cursor.read_u8().is_err());
}
#[test]
fn frame_writes_length_prefix() {
let mut writer = BackendWriter::new(Vec::new());
writer.command_complete("SELECT 1");
assert_eq!(writer.buffer[0], b'C');
let length = i32::from_be_bytes([
writer.buffer[1],
writer.buffer[2],
writer.buffer[3],
writer.buffer[4],
]);
assert_eq!(usize::try_from(length).unwrap(), writer.buffer.len() - 1);
}
}