Transport Abstraction and Method IDs¶
uRPC is transport-agnostic.
All framing works on top of a minimal asynchronous byte-stream interface.
The transport layer is responsible for:
- async reads/writes
- connection shutdown
- optional TLS/mTLS handshake
- optional certificate validation
- optional app-level AES payload encryption (only payload, never header)
The protocol itself does not depend on TCP or OpenSSL directly.
IRpcStream¶
The core abstraction:
struct IRpcStream
{
virtual usub::uvent::task::Awaitable<ssize_t> async_read(
usub::uvent::utils::DynamicBuffer& buf,
size_t max_read
) = 0;
virtual usub::uvent::task::Awaitable<ssize_t> async_write(
uint8_t* data,
size_t len
) = 0;
virtual void shutdown() = 0;
// Optional; not all transports implement it
virtual PeerCertInfo peer_certificate() const { return {}; }
virtual ~IRpcStream() = default;
};
Semantics¶
-
async_read(buf, max_read)- Appends up to
max_readbytes intobuf. - Returns:
> 0— number of bytes appended<= 0— EOF or error.
- Appends up to
-
async_write(data, len)- Writes exactly
lenbytes. - Returns:
> 0— written bytes<= 0— error.
- Writes exactly
-
shutdown()- Terminates the transport (close TCP, close TLS session, etc).
The uRPC layer (client/server) uses only these three operations.
IRpcStreamFactory¶
Both client and server obtain streams through a factory:
struct IRpcStreamFactory
{
virtual std::shared_ptr<IRpcStream>
create_client_stream(std::string host, uint16_t port) = 0;
virtual std::shared_ptr<IRpcStream>
create_server_stream(usub::uvent::net::TCPClientSocket&& sock) = 0;
virtual ~IRpcStreamFactory() = default;
};
Factories produce:
- plain TCP streams (
TcpRpcStream); - TLS/mTLS streams (
TlsRpcStream); - custom transports (QUIC, pipes, etc.).
TcpRpcStream¶
A thin wrapper over TCPClientSocket:
class TcpRpcStream : public IRpcStream
{
public:
explicit TcpRpcStream(usub::uvent::net::TCPClientSocket&& sock);
Awaitable<ssize_t> async_read(DynamicBuffer& buf, size_t max_read) override;
Awaitable<ssize_t> async_write(uint8_t* data, size_t len) override;
void shutdown() override;
private:
usub::uvent::net::TCPClientSocket socket_;
};
Properties:
- No buffering, no framing.
- Direct passthrough to uvent socket primitives.
shutdown()just closes FD.
TlsRpcStream (TLS / mTLS)¶
TLS transport is a drop-in implementation of IRpcStream, using OpenSSL in non-blocking mode with uvent coroutines.
Provided by:
Client-side flow¶
create_client_stream(host, port):
- Create TCP socket.
- Connect via
async_connect. - Initialize SSL object.
- Configure CA, certificate, key, server-name (SNI).
- Perform async TLS handshake.
- Derive AES exporter keys (if app-level encryption is enabled).
- Return TLS stream.
Server-side flow¶
create_server_stream(TCPClientSocket&& sock):
- Wrap socket with SSL.
- Load server cert/key.
- Enable mTLS if required.
- Perform async handshake.
- Verify client certificate if mTLS.
- Derive AES exporter keys (if enabled).
- Return stream.
Header visibility under TLS¶
- TLS encrypts the transport stream.
- The uRPC header (28 bytes) is still parsed as plaintext, because OpenSSL
decrypts it before delivering bytes to
async_read.
Peer certificate¶
Used for authorization, logging, auditing.
IO helpers: write_all and send_frame¶
Defined in IOOps.h.
inline task::Awaitable<bool> write_all(
IRpcStream& stream,
const uint8_t* data,
size_t n);
inline task::Awaitable<bool> send_frame(
IRpcStream& stream,
const RpcFrameHeader& hdr,
std::span<const uint8_t> payload);
AES note¶
If app-level AES is enabled:
- encryption happens before calling
send_frame. - the payload passed to
send_frameis already:
send_frame itself is unaware of encryption.
write_all¶
- Performs a single write call.
- Returns
falseon<= 0.
send_frame¶
- Serializes the 28-byte header.
- Calls
write_allfor header. - Calls
write_allfor payload (raw or AES-encrypted).
Used by:
RpcClientRpcConnection
Method IDs¶
Method IDs are 64-bit FNV-1a hashes of method names.
Why:
- deterministic
- fast
- collision probability extremely low
- trivial compile-time evaluation
Runtime hashing¶
Compile-time hashing¶
Used by:
server.register_method_ct<method_id("Example.Echo")>(fn);
client->async_call_ct<method_id("Example.Echo")>(payload);
Summary¶
Transport layer provides:
- async read/write
- clean shutdown
- optional TLS/mTLS
- optional per-connection AES key derivation
- peer certificate access
Protocol layer provides:
- 28-byte plaintext header (always)
- optional payload encryption (AES-256-GCM)
- multiplexing via stream IDs
- method resolution via 64-bit FNV-1a hashes
Client and server exchange only three transport operations:
Everything else—framing, flags, AES, errors, pings—lives fully above the transport.