This commit is contained in:
Lol3rrr
2026-10-10 14:36:21 +02:00
commit bf59bd4c80
13 changed files with 458 additions and 0 deletions

1
messages/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

6
messages/Cargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[package]
name = "messages"
version = "0.1.0"
edition = "2024"
[dependencies]

50
messages/src/datatypes.rs Normal file
View File

@@ -0,0 +1,50 @@
//! [Postgres Docs](https://www.postgresql.org/docs/current/protocol-message-types.html#PROTOCOL-MESSAGE-TYPES)
#[derive(Debug)]
pub enum DataTypeError {
Other(&'static str),
}
pub trait DataType: Sized {
fn parse<'b1, 'b2>(buffer: &'b1 [u8]) -> Result<(Self, &'b2 [u8]), DataTypeError> where 'b1: 'b2;
fn encoding_length(&self) -> usize;
fn encode<W>(&self, writer: &mut W) where W: std::io::Write;
}
impl DataType for i32 {
fn parse<'b1, 'b2>(buffer: &'b1 [u8]) -> Result<(Self, &'b2 [u8]), DataTypeError> where 'b1: 'b2 {
let relevant_data = &buffer[0..4];
let value = i32::from_be_bytes(relevant_data.try_into().unwrap());
Ok((value, &buffer[4..]))
}
fn encoding_length(&self) -> usize {
4
}
fn encode<W>(&self, writer: &mut W) where W: std::io::Write {
let content = self.to_be_bytes();
writer.write_all(&content).unwrap();
}
}
impl DataType for String {
fn parse<'b1, 'b2>(buffer: &'b1 [u8]) -> Result<(Self, &'b2 [u8]), DataTypeError> where 'b1: 'b2 {
let str_length = buffer.iter().enumerate().find(|(_, v)| **v == 0).map(|(i, _)| i).unwrap();
let raw_content = &buffer[0..str_length];
let content = str::from_utf8(raw_content).unwrap();
Ok((content.to_owned(), &buffer[str_length+1..]))
}
fn encoding_length(&self) -> usize {
self.len() + 1
}
fn encode<W>(&self, writer: &mut W) where W: std::io::Write {
writer.write_all(self.as_bytes()).unwrap();
writer.write_all(&[0]).unwrap();
}
}

31
messages/src/lib.rs Normal file
View File

@@ -0,0 +1,31 @@
pub mod startup;
pub mod datatypes;
#[derive(Debug)]
pub enum MessageParseError {
MismatchedId {
expected: u8,
found: u8,
},
Other(&'static str),
}
#[derive(Debug)]
pub enum EncodeError {
IO(std::io::Error),
Other(&'static str),
}
pub trait Message: Sized {
const ID: Option<u8>;
fn parse<'b1, 'b2>(buffer: &'b1 [u8]) -> Result<(Self, &'b2 [u8]), MessageParseError> where 'b1: 'b2;
fn encode<W>(&self, writer: &mut W) -> Result<(), EncodeError> where W: std::io::Write;
}
impl From<std::io::Error> for EncodeError {
fn from(value: std::io::Error) -> Self {
Self::IO(value)
}
}

229
messages/src/startup.rs Normal file
View File

@@ -0,0 +1,229 @@
use crate::{EncodeError, Message, MessageParseError, datatypes::DataType};
/// Sent by the Frontend
///
/// [Postgres Docs](https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-SSLREQUEST)
#[derive(Debug, PartialEq)]
pub struct SSLRequest {}
impl Message for SSLRequest {
const ID: Option<u8> = None;
fn parse<'b1, 'b2>(buffer: &'b1 [u8]) -> Result<(Self, &'b2 [u8]), MessageParseError> where 'b1: 'b2 {
let (length, _) = i32::parse(buffer).unwrap();
let (code, _) = i32::parse(&buffer[4..8]).unwrap();
if length != 8 || code != 80877103 {
return Err(MessageParseError::Other("Not an SSLRequest"));
}
Ok((Self {}, &buffer[8..]))
}
fn encode<W>(&self, writer: &mut W) -> Result<(), EncodeError> where W: std::io::Write {
8i32.encode(writer);
80877103i32.encode(writer);
Ok(())
}
}
/// Sent by the Backend
#[derive(Debug, PartialEq)]
pub struct SSLResponse {
pub enabled: bool,
}
impl Message for SSLResponse {
const ID: Option<u8> = None;
fn parse<'b1, 'b2>(buffer: &'b1 [u8]) -> Result<(Self, &'b2 [u8]), MessageParseError> where 'b1: 'b2 {
let flag = buffer[0];
if flag != b'S' && flag != b'N' {
return Err(MessageParseError::Other("Invalid value for SSL Response"));
}
Ok((Self {
enabled: flag == b'S',
}, &buffer[1..]))
}
fn encode<W>(&self, writer: &mut W) -> Result<(), EncodeError> where W: std::io::Write {
let flag = if self.enabled { b'S' } else { b'N' };
writer.write_all(&[flag])?;
Ok(())
}
}
/// Sent by the frontend
///
/// [Postgres Docs](https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-STARTUPMESSAGE)
#[derive(Debug, PartialEq)]
pub struct Startup {
pub version: i32,
pub parameters: Vec<(String, String)>,
}
impl Message for Startup {
const ID: Option<u8> = None;
fn parse<'b1, 'b2>(in_buffer: &'b1 [u8]) -> Result<(Self, &'b2 [u8]), crate::MessageParseError> where 'b1: 'b2 {
let (length, _) = i32::parse(in_buffer).unwrap();
let buffer = &in_buffer[4..length as usize];
let (version, buffer) = i32::parse(buffer).unwrap();
let mut parameters = Vec::new();
let mut buffer = buffer;
while buffer.first().is_some_and(|v| *v != 0) {
let (name, tmp_buffer) = String::parse(buffer).unwrap();
let (value, tmp_buffer) = String::parse(tmp_buffer).unwrap();
parameters.push((name, value));
buffer = tmp_buffer;
}
if !buffer.first().is_some_and(|v| *v == 0) {
return Err(MessageParseError::Other("TODO"));
}
Ok((Self {
version,
parameters,
}, &in_buffer[length as usize..]))
}
fn encode<W>(&self, writer: &mut W) -> Result<(), EncodeError> where W: std::io::Write {
let version_length = self.version.encoding_length();
let parameters_length = self.parameters.iter().map(|(k, v)| k.encoding_length() + v.encoding_length()).sum::<usize>()+1;
((4+version_length+parameters_length) as i32).encode(writer);
self.version.encode(writer);
for (k, v) in self.parameters.iter() {
k.encode(writer);
v.encode(writer);
}
writer.write_all(&[0])?;
Ok(())
}
}
/// Sent by the backend
///
/// [Postgres Docs](https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-AUTHENTICATIONOK)
#[derive(Debug, PartialEq)]
pub struct AuthenticationOk {}
impl Message for AuthenticationOk {
const ID: Option<u8> = Some(b'R');
fn parse<'b1, 'b2>(buffer: &'b1 [u8]) -> Result<(Self, &'b2 [u8]), MessageParseError> where 'b1: 'b2 {
let id = buffer[0];
if id != Self::ID.unwrap() {
return Err(MessageParseError::MismatchedId { expected: Self::ID.unwrap(), found: id });
}
let (len, buffer) = i32::parse(&buffer[1..]).unwrap();
if len != 8 {
return Err(MessageParseError::Other("Expected a length of 8"));
}
let (code, buffer) = i32::parse(buffer).unwrap();
if code != 0 {
return Err(MessageParseError::Other("Expectede the code to be 0"));
}
Ok((Self {}, buffer))
}
fn encode<W>(&self, writer: &mut W) -> Result<(), EncodeError> where W: std::io::Write {
writer.write_all(&[Self::ID.unwrap()])?;
8i32.encode(writer);
0i32.encode(writer);
Ok(())
}
}
/// Sent by the backend
///
/// [Postgres Docs](https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-PARAMETERSTATUS)
#[derive(Debug, PartialEq)]
pub struct ParameterStatus {
pub name: String,
pub value: String,
}
impl Message for ParameterStatus {
const ID: Option<u8> = Some(b'S');
fn parse<'b1, 'b2>(buffer: &'b1 [u8]) -> Result<(Self, &'b2 [u8]), MessageParseError> where 'b1: 'b2 {
let id = buffer[0];
if id != Self::ID.unwrap() {
return Err(MessageParseError::MismatchedId { expected: Self::ID.unwrap(), found: id });
}
let (len, buffer) = i32::parse(&buffer[1..]).unwrap();
let (name, buffer) = String::parse(buffer).unwrap();
let (value, buffer) = String::parse(buffer).unwrap();
Ok((Self { name, value }, buffer))
}
fn encode<W>(&self, writer: &mut W) -> Result<(), EncodeError> where W: std::io::Write {
let name_length = self.name.encoding_length();
let value_length = self.value.encoding_length();
writer.write_all(&[Self::ID.unwrap()])?;
((4+name_length+value_length) as i32).encode(writer);
self.name.encode(writer);
self.value.encode(writer);
Ok(())
}
}
/// Sent by the backend
///
/// [Postgres Docs](https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-BACKENDKEYDATA)
#[derive(Debug, PartialEq)]
pub struct BackendKeyData {
pub process_id: i32,
pub secret_key: Vec<u8>,
}
impl Message for BackendKeyData {
const ID: Option<u8> = Some(b'K');
fn parse<'b1, 'b2>(buffer: &'b1 [u8]) -> Result<(Self, &'b2 [u8]), MessageParseError> where 'b1: 'b2 {
let id = buffer[0];
if id != Self::ID.unwrap() {
return Err(MessageParseError::MismatchedId { expected: Self::ID.unwrap(), found: id });
}
let (len, buffer) = i32::parse(&buffer[1..]).unwrap();
let inner_buffer = &buffer[..len as usize - 4];
let (process_id, inner_buffer) = i32::parse(inner_buffer).unwrap();
Ok((Self {
process_id,
secret_key: inner_buffer.to_vec(),
}, &buffer[len as usize - 4..]))
}
fn encode<W>(&self, writer: &mut W) -> Result<(), EncodeError> where W: std::io::Write {
let process_len = self.process_id.encoding_length();
let secret_key_length = self.secret_key.len();
writer.write_all(&[Self::ID.unwrap()])?;
((4+process_len+secret_key_length) as i32).encode(writer);
self.process_id.encode(writer);
writer.write_all(self.secret_key.as_slice())?;
Ok(())
}
}

100
messages/tests/startup.rs Normal file
View File

@@ -0,0 +1,100 @@
use messages::{Message, startup::{AuthenticationOk, BackendKeyData, ParameterStatus, SSLRequest, SSLResponse, Startup}};
#[test]
fn collected_startup() {
let raw_bytes: &[u8] = &[0x0, 0x0, 0x0, 0x3f, 0x0, 0x3, 0x0, 0x0, 0x75, 0x73, 0x65, 0x72, 0x0, 0x70, 0x6f, 0x73, 0x74, 0x67, 0x72, 0x65, 0x73, 0x0, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x0, 0x70, 0x6f, 0x73, 0x74, 0x67, 0x72, 0x65, 0x73, 0x0, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x0, 0x70, 0x73, 0x71, 0x6c, 0x0, 0x0];
let (parsed, rem) = Startup::parse(raw_bytes).unwrap();
assert_eq!(&[] as &[u8], rem);
assert_eq!(Startup {
version: 196608,
parameters: vec![
("user".into(), "postgres".into()),
("database".into(), "postgres".into()),
("application_name".into(), "psql".into()),
],
}, parsed);
let mut output = Vec::<u8>::new();
parsed.encode(&mut output);
assert_eq!(raw_bytes, output.as_slice());
}
#[test]
fn collected_sslrequest() {
let raw_bytes: &[u8] = &[0x0, 0x0, 0x0, 0x8, 0x4, 0xd2, 0x16, 0x2f];
let (parsed, rem) = SSLRequest::parse(raw_bytes).unwrap();
assert_eq!(&[] as &[u8], rem);
assert_eq!(SSLRequest {}, parsed);
let mut output = Vec::<u8>::new();
parsed.encode(&mut output);
assert_eq!(raw_bytes, output.as_slice());
}
#[test]
fn collected_sslresponse() {
let raw_bytes: &[u8] = &[0x4e];
let (parsed, rem) = SSLResponse::parse(raw_bytes).unwrap();
assert_eq!(&[] as &[u8], rem);
assert_eq!(SSLResponse {
enabled: false,
}, parsed);
let mut output = Vec::<u8>::new();
parsed.encode(&mut output);
assert_eq!(raw_bytes, output.as_slice());
}
#[test]
fn collected_authenticationok() {
let raw_bytes: &[u8] = &[0x52, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0];
let (parsed, rem) = AuthenticationOk::parse(raw_bytes).unwrap();
assert_eq!(&[] as &[u8], rem);
assert_eq!(AuthenticationOk {}, parsed);
let mut output = Vec::<u8>::new();
parsed.encode(&mut output);
assert_eq!(raw_bytes, output.as_slice());
}
#[test]
fn collected_parameterstatus() {
let raw_bytes: &[u8] = &[0x53, 0x0, 0x0, 0x0, 0x17, 0x69, 0x6e, 0x5f, 0x68, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x62, 0x79, 0x0, 0x6f, 0x66, 0x66, 0x0];
let (parsed, rem) = ParameterStatus::parse(raw_bytes).unwrap();
assert_eq!(&[] as &[u8], rem);
assert_eq!(ParameterStatus {
name: "in_hot_standby".into(),
value: "off".into(),
}, parsed);
let mut output = Vec::<u8>::new();
parsed.encode(&mut output);
assert_eq!(raw_bytes, output.as_slice());
}
#[test]
fn collected_backendkeydata() {
let raw_bytes: &[u8] = &[0x4b, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x4b, 0x72, 0xb, 0xd3, 0xd6];
let (parsed, rem) = BackendKeyData::parse(raw_bytes).unwrap();
assert_eq!(&[] as &[u8], rem);
assert_eq!(BackendKeyData {
process_id: 75,
secret_key: vec![0x72, 0x0b, 0xd3, 0xd6],
}, parsed);
let mut output = Vec::<u8>::new();
parsed.encode(&mut output);
assert_eq!(raw_bytes, output.as_slice());
}