Initial
This commit is contained in:
1
messages/.gitignore
vendored
Normal file
1
messages/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
6
messages/Cargo.toml
Normal file
6
messages/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "messages"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
50
messages/src/datatypes.rs
Normal file
50
messages/src/datatypes.rs
Normal 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
31
messages/src/lib.rs
Normal 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
229
messages/src/startup.rs
Normal 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
100
messages/tests/startup.rs
Normal 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());
|
||||
}
|
||||
Reference in New Issue
Block a user