diff --git a/proto/src/handshake/client.rs b/proto/src/handshake/client.rs index 2deb5bc..ff3aaed 100644 --- a/proto/src/handshake/client.rs +++ b/proto/src/handshake/client.rs @@ -7,15 +7,20 @@ use crate::reader::backend::BackendProtoReader; use crate::writer::frontend::FrontendProtoWriter; use crate::writer::protowriter::ProtoFlush; +/// Performs client-side handshake with the server until the `ReadyForQuery` message is received. +/// For more info visit the [`55.2.1. Start-up`](https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-START-UP) pub async fn do_client_handshake( writer: &mut (impl FrontendProtoWriter + ProtoFlush), reader: &mut impl BackendProtoReader, request: HandshakeRequest, ) -> Result { + + // Send StartupMessage without SSLRequest let startup_message: StartupMessageData = request.into(); writer.write_startup_message(startup_message).await?; writer.flush().await?; + // Wait for AuthenticationOk let auth = reader.read_proto().await?; if !matches!( auth, @@ -24,6 +29,7 @@ pub async fn do_client_handshake( return Err(ClientHandshakeError::UnexpectedAuthResponse(auth)); } + // Read server parameter messages until ReadyForQuery is received let mut messages = Vec::new(); loop { let msg = reader.read_proto().await?; diff --git a/proto/src/handshake/request.rs b/proto/src/handshake/request.rs index 408238f..9f6b9cb 100644 --- a/proto/src/handshake/request.rs +++ b/proto/src/handshake/request.rs @@ -3,11 +3,14 @@ use crate::message::special::StartupMessageData; #[derive(Debug)] pub struct HandshakeRequest { - version: i32, - parameters: Vec<(PgString, PgString)>, + pub version: i32, + pub parameters: Vec<(PgString, PgString)>, } impl HandshakeRequest { + + /// Creates a new `HandshakeRequest` with the specified version. + /// Expected `version` is 196608 (3.0). pub fn new(version: i32) -> Self { Self { version, @@ -15,6 +18,9 @@ impl HandshakeRequest { } } + /// Adds a parameter to the startup message. + /// Generally recognized names are `user`, `database`, `option` and `replication` but others can be used. + /// For more info visit [`StartupMessage`](https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-STARTUPMESSAGE) pub fn parameter(mut self, key: &str, value: &str) -> Self { self.parameters.push((key.into(), value.into())); self diff --git a/proto/src/handshake/response.rs b/proto/src/handshake/response.rs index 38579b6..60d0b6f 100644 --- a/proto/src/handshake/response.rs +++ b/proto/src/handshake/response.rs @@ -11,7 +11,7 @@ pub struct HandshakeResponse { impl HandshakeResponse { pub fn new(name: &str, pid: i32, key: i32) -> Self { Self { - version: format!("16.0 ({name})", name = name), + version: format!("16.0 ({name})"), process_id: pid, secret_key: key, } @@ -37,6 +37,7 @@ impl TryFrom<&[BackendMessage]> for HandshakeResponse { process_id = Some(data.process); secret_key = Some(data.secret); } + // Different messages are ignored during the handshake _ => {} } } diff --git a/proto/src/handshake/server.rs b/proto/src/handshake/server.rs index 4459f65..6c8deb2 100644 --- a/proto/src/handshake/server.rs +++ b/proto/src/handshake/server.rs @@ -7,11 +7,15 @@ use crate::reader::frontend::FrontendProtoReader; use crate::writer::backend::BackendProtoWriter; use crate::writer::protowriter::ProtoFlush; +/// Performs server-side handshake with the client until ending it with `ReadyForQuery` message. +/// For more info visit the [`55.2.1. Start-up`](https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-START-UP) pub async fn do_server_handshake( writer: &mut (impl BackendProtoWriter + ProtoFlush), reader: &mut impl FrontendProtoReader, response: HandshakeResponse, ) -> Result { + + // Check if client requested SSL match &reader.peek_special_message().await? { Some(msg @ SpecialMessage::SSLRequest) => { reader.consume_special_message(msg).await?; @@ -23,6 +27,7 @@ pub async fn do_server_handshake( } } + // Wait for mandatory StartupMessage let startup_message = match &reader.peek_special_message().await? { Some(msg @ SpecialMessage::StartupMessage(data)) => { reader.consume_special_message(msg).await?; @@ -33,15 +38,18 @@ pub async fn do_server_handshake( } }; + // Authenticate client writer .write_proto(BackendMessage::from(AuthenticationOkData { status: 0 })) .await?; + // Send server parameters let messages: Vec = response.into(); for message in messages { writer.write_proto(message).await?; } + // Finish the handshake writer .write_proto(BackendMessage::from(ReadyForQueryData { status: b'I' })) .await?; diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 69afed6..4395510 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -1,3 +1,7 @@ +//! # PostgreSQL 16 Protocol +//! Low-level PostgreSQL protocol implementation for the version 16, protocol version 3.0. +//! Includes server and client side handshake with no password authentication. + pub mod handshake; pub mod message; pub mod reader;