pg-proto
pg-proto is an asynchronous Rust implementation of the PostgreSQL
frontend/backend wire protocol designed for proxies, poolers, gateways, drivers,
and protocol-aware test infrastructure.
Its distinguishing feature is a builder-only facade over PostgreSQL's typed
connection state machine. Client::builder(), Server::builder(), and
Intermediary::builder() require explicit transport-security, authentication,
middleware, and routing policy before they establish operational connections.
The internal protocol typestates prevent illegal sequencing without exposing
the implementation graph as application API.
Most PostgreSQL protocol libraries decode messages but retain the session phase
in a runtime enum. pg-proto is useful when protocol correctness is part of the
architecture rather than merely an implementation detail: the legal next
operations are visible in function signatures, illegal compositions are rejected
at compile time, and proxy policy can still inspect, replace, or reject complete
typed messages.
What it provides
- Direction-parameterised frontend and backend codecs. Ambiguous tags such as
SandEcannot be decoded in the wrong direction. - Typed pre-startup handling for
SSLRequest,GSSENCRequest,CancelRequest, andStartupMessage, including transport-changing rustls upgrades. - A plain/client-TLS/server-TLS network stream, configurable TCP socket options, and outbound connection retry with capped exponential backoff.
- Independent client-facing and upstream authentication sessions, including cleartext, MD5, SCRAM-SHA-256, and SCRAM-SHA-256-PLUS with channel binding.
- Simple and extended query sessions, pipelining, error draining, function calls, COPY IN/OUT/BOTH, and physical replication framing.
- Lossless, reconstructable
Parse,Bind,Describe,Execute,RowDescription, andDataRowvalues for SQL and result rewriting. - A demultiplexer for asynchronous notices, notifications, and parameter status updates without polluting the causal session type.
- Positionally tagged notices and transaction/parameter evidence for pooling decisions.
- Connection-branded prepared statements and portals with name rewriting.
- Exact typestate erasure and checked re-entry at storage and pool boundaries.
- A protocol grammar macro which emits typestates, their duals, a runtime FSM for differential testing, and railroad diagrams embedded in rustdoc.
The crate owns protocol representation and ordering. Applications retain control of listeners, credentials, authorisation, SQL transformation, routing, pooling, cancellation storage, telemetry, and failure policy.
Why use it?
PostgreSQL infrastructure tends to fail at phase boundaries rather than while
decoding an individual frame. A pooler may return a connection while it is still
in a transaction, a proxy may forward Query while a COPY exchange is active,
or an extended-query error path may forget to discard messages until Sync.
Typestate makes these transitions explicit and turns many such bugs into type
errors.
The phase index is orthogonal to connection cleanliness. A connection can be
protocol-ready but unsuitable for unconditional pool release because of an open
transaction, changed GUC, prepared statement, portal, LISTEN, or advisory lock.
Operational connections return explicit state evidence and preserve caller-owned
state until teardown.
What can be built with it?
The bounded intermediary pipeline example shows ordered forwarding, local interception, and backpressure without proxy-owned message queues.
- A TLS-terminating PostgreSQL proxy which authenticates each side independently and inspects plaintext SQL and result rows.
- A transaction or session pooler whose release policy consumes explicit protocol and cleanliness evidence.
- A SQL firewall, audit gateway, query rewriter, or column-encryption proxy.
- A sharding/router layer which rewrites prepared-statement and portal names.
- A logical or physical replication relay with typed COPY-BOTH half-closes.
- A PostgreSQL-compatible server, mock backend, recorder, replay tool, or protocol conformance harness.
- A driver or administrative client which benefits from compile-time sequencing.
Security choices come first
Every role builder requires explicit TLS and authentication policy. The short
examples below deliberately use plaintext (ClientTlsPolicy::Disabled and
ServerTlsPolicy::Disabled) and unverified trust authentication
(TrustClientAuthentication and TrustServerAuthentication) so that their
insecure posture is visible in code. They are suitable for a protected local
development network, not an Internet-facing production deployment.
For production, use ClientTlsPolicy::libpq with SslMode::VerifyFull and an
application-owned reloadable ClientTlsProvider; use ServerTlsPolicy::Required
with an application-owned ServerIdentityProvider. Supply application-defined
ClientAuthentication and ServerAuthenticationProvider implementations that
return typed identity evidence. pg-proto orchestrates the protocol but remains
policy-neutral: it does not store credentials, authorise identities, choose
authentication mechanisms, or provision certificates.
The default one-mebibyte frame limits are conservative. Raising a limit or
calling ProtocolLimits::without_frame_limit is an explicit resource-exhaustion
downgrade; production services should instead choose the smallest limit their
workload needs. Likewise, SslMode::Allow, Prefer, Require, and VerifyCa
provide less assurance than VerifyFull, and must be selected deliberately.
Client: connect to PostgreSQL
Build one reusable upstream-facing component, then establish operational connections with caller-owned state and per-call startup parameters.
use ;
async
Server: accept PostgreSQL clients
The server builder owns reusable client-facing policy. The application owns the listener, peer metadata, per-connection state, credentials, and authorisation.
use ;
async
Intermediary: compose both roles
An intermediary takes complete server and client components. Startup routing, authenticated routing, cancellation storage, middleware, and failure disclosure remain explicit application policies.
use ;
use ;
;
async
Stateful message middleware
A proxy can inspect or replace owned frontend and backend messages through
builder middleware. Each accepted connection gets a fresh handler with mutable
access to the application-supplied per-connection state. Repeated .middleware
calls compose stages in declaration order.
use ;
;
let _client = builder
.connector
.tls
.authentication
.middleware
.build?;
# Ok::
For a complete networked example, see the TLS-terminating
SQL logging proxy. The companion
protocol logging proxy prints every
decoded message in both directions. More focused examples live in the
examples/ directory, including message rewriting and the neutral
proxy composition boundary.
Rustdoc entry point
The crate overview documents the complete root facade: role builders, nested security configuration, middleware, operational connection types, and root message vocabulary.
Build the same documentation locally with:
Supported PostgreSQL versions
PostgreSQL 14, 15, 16, 17, and 18 are supported. Each version runs the same live suite against its official Alpine image. PostgreSQL 14–17 negotiate a requested protocol 3.2 startup down to 3.0; PostgreSQL 18 reports protocol 3.2. Both behaviours are covered explicitly.
Run a selected version locally with a Docker-compatible runtime:
PG_PROTO_POSTGRES_VERSION=18 \
See SUPPORTED_VERSIONS.md for the tested protocol
matrix.
Known limitations
- The API is pre-1.0 and may change as it is integrated into a production proxy.
- Kerberos V5, GSSAPI, SSPI, and GSS token exchanges are represented by the protocol API, but the crate does not ship platform credential-provider engines. GSS encryption negotiation is modelled; a production GSSENC transport adapter remains application work.
- Pool scheduling, routing, SQL parsing, policy, credential storage, certificate provisioning, and cancellation-key persistence are intentionally not included.
- Rust is affine rather than linear: callers can deliberately abandon an operational connection by dropping it.
- Unknown future PostgreSQL message tags are rejected by the typed codec until their direction and semantics are added.
- Formal multiparty verification is not provided. Client and server roles are dual generated APIs with differential runtime-FSM testing, not a machine-checked proof of a complete three-party proxy.
Security assumptions and downstream responsibilities are documented in
SECURITY.md. The audited proxy capability boundary is in
PROXY_COMPATIBILITY.md, and migration from a
runtime-enum implementation is covered by MIGRATION.md.
Contribution instructions and community expectations are in
CONTRIBUTING.md and
CODE_OF_CONDUCT.md.
Verification
The ordinary suite includes unit, fixture, property-style differential, compile-fail, and documentation tests:
Live PostgreSQL tests require a Docker-compatible runtime:
Licence
Licensed under the MIT License.