# pg-proto
[](https://github.com/freshtonic/pg-proto/actions/workflows/ci.yml)
[](https://docs.rs/pg-proto)
[](https://crates.io/crates/pg-proto)
[](https://blog.rust-lang.org/2025/06/26/Rust-1.88.0/)
[](https://github.com/freshtonic/pg-proto/blob/main/LICENSE)
`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 that PostgreSQL's connection state machine is
represented in Rust's type system. Operations consume a
`Conn<Transport, Phase, Cleanliness>` and return a connection in its next phase.
Code which tries to issue a query during `COPY IN`, execute before binding, send
a startup packet while TLS negotiation is pending, or release a dirty connection
to a pool does not compile.
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
`S` and `E` cannot be decoded in the wrong direction.
- Typed pre-startup handling for `SSLRequest`, `GSSENCRequest`, `CancelRequest`,
and `StartupMessage`, 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`, and `DataRow` values 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.
Only `Conn<_, Ready, Pristine>` exposes unconditional release.
## What can be built with it?
The [bounded intermediary pipeline example](examples/intermediary_pipeline.rs)
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.
## Usage
The generated grammar witnesses make the sequencing model easy to see. Every
method consumes the previous phase; uncommenting an operation which is illegal
in the current phase produces a compiler error.
```rust
use pg_proto::grammar::frontend::Session;
let ready = Session::new();
let building = ready.begin_extended().parse().bind().execute();
let ready = building.sync().ready();
// `building.query()` would not compile: Query is unavailable during an
// extended-query pipeline, which must leave through Sync.
let _terminated = ready.terminate();
```
## Stateful message middleware
A proxy can inspect or replace any owned frontend, backend, or pre-startup
message before reconstructing its checked wire representation. `TypedMiddleware`
indexes each policy by sender role and generated grammar phase. Its input and
output enums contain only messages legal in that phase, so a replacement from a
different role or phase is a compile error. `Conn::receive_frontend_typed`,
`Conn::receive_backend_typed`, `Conn::receive_pre_startup_typed`, and
`Conn::receive_encryption_reply_typed` infer those indices from the connection;
there is no caller-supplied runtime state to mismatch.
Middleware has mutable access to caller-defined state. Returning the message
unchanged is the identity operation, `Identity` supplies a universal pass-through,
and `MessageMiddlewareExt::then` chains typed stages deterministically. A
`WireAdapter` can apply a direction-wide policy across generated phases while
rechecking any replacement before it re-enters the typed API.
```rust
use bytes::Bytes;
use pg_proto::{
codec::FrontendMessage,
grammar::backend,
middleware::{ClientRole, Middleware},
};
.intercept_typed::<ClientRole, backend::Ready, _>(message)
.unwrap()
.into_wire();
let frame = rewritten.to_frame()?;
assert_eq!(*proxy.state(), 1);
# Ok::<(), std::io::Error>(())
```
Protocol legality is compile-time checked, while wire-shape constraints remain
runtime checked. For example, embedded NUL bytes, oversized frames, and other
encoding failures are reported as `TypedReceiveError::InvalidWire`. Backend
asynchronous messages pass through typed middleware in wire order without
advancing the connection phase; `receive_typed` then records them before
returning the next protocol-advancing `SessionItem`.
The older `intercept_checked` and `receive_*_with_middleware` APIs remain
available for runtime-selected sessions. They validate replacement legality
against a supplied generated `RuntimeState` at runtime. Prefer the typed receive
APIs whenever the connection typestate is available.
For a complete networked example, see the TLS-terminating
[`SQL logging proxy`](examples/sql_logging_proxy/README.md). The companion
[`protocol logging proxy`](examples/protocol_logging_proxy/README.md) prints every
decoded message in both directions. More focused examples live in the
[`examples/`](examples/) directory, including message rewriting and the neutral
proxy composition boundary.
## Rustdoc entry points
- [Crate overview and `Conn`](https://docs.rs/pg-proto/latest/pg_proto/)
- [Frontend/backend messages and codecs](https://docs.rs/pg-proto/latest/pg_proto/codec/)
- [Buffered network transport and interception](https://docs.rs/pg-proto/latest/pg_proto/transport/)
- [Pre-startup and TLS negotiation](https://docs.rs/pg-proto/latest/pg_proto/pre_startup/)
- [Upstream/client-role authentication](https://docs.rs/pg-proto/latest/pg_proto/auth/)
- [Downstream/server-role authentication](https://docs.rs/pg-proto/latest/pg_proto/server_auth/)
- [Client-role query sessions](https://docs.rs/pg-proto/latest/pg_proto/session/)
- [Server-role query sessions](https://docs.rs/pg-proto/latest/pg_proto/server_session/)
- [Proxy-side composition hooks](https://docs.rs/pg-proto/latest/pg_proto/intermediary/)
- [Stateful message middleware](https://docs.rs/pg-proto/latest/pg_proto/middleware/)
- [Bounded intermediary pipeline](https://docs.rs/pg-proto/latest/pg_proto/pipeline/)
- [Prepared-statement and portal resources](https://docs.rs/pg-proto/latest/pg_proto/resources/)
- [Generated protocol grammars and railroad diagrams](https://docs.rs/pg-proto/latest/pg_proto/grammar/)
Build the same documentation locally with:
```console
cargo doc --workspace --no-deps --open
```
## 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:
```console
PG_PROTO_POSTGRES_VERSION=18 \
cargo test --test postgres_container -- --ignored
```
See [`SUPPORTED_VERSIONS.md`](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 abandon a session by dropping
it. `Conn` is `#[must_use]` and has a debug-only drop bomb, but release builds
cannot make deliberate connection abandonment impossible.
- 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`](SECURITY.md). The audited proxy capability boundary is in
[`PROXY_COMPATIBILITY.md`](PROXY_COMPATIBILITY.md), and migration from a
runtime-enum implementation is covered by [`MIGRATION.md`](MIGRATION.md).
Contribution instructions and community expectations are in
[`CONTRIBUTING.md`](CONTRIBUTING.md) and
[`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md).
## Verification
The ordinary suite includes unit, fixture, property-style differential,
compile-fail, and documentation tests:
```console
cargo test --workspace
```
Live PostgreSQL tests require a Docker-compatible runtime:
```console
cargo test --test postgres_container -- --ignored
```
## Licence
Licensed under the [MIT License](https://github.com/freshtonic/pg-proto/blob/main/LICENSE).