moonrpc

moonrpc — a real gRPC implementation for MoonBit (← gRPC). v0.8.1 adds Channel::stream_take, which drives a streaming call inline — no background reader — and returns once it has collected a bounded number of reply messages, so a caller can consume the first N of an unbounded subscription (etcd's Watch, a long server-streaming feed) and close, keeping the whole exchange in one task. v0.8.0 completes the client channel stack. Load balancing picks a READY sub-connection per call (pick_first or round_robin) from a connection pool that tracks each address through the gRPC connectivity states (IDLE/CONNECTING/READY/TRANSIENT_FAILURE/SHUTDOWN); a ManagedChannel dials several backends over real sockets and routes calls through the picker. Name resolution self-builds a DNS message codec (RFC 1035, A/AAAA with name compression) and a UDP resolver, so dns_connect turns a hostname into the backends it dials. Retry and hedging policies drive re-issue and parallel attempts — hedging fires a fresh attempt every hedging delay and commits on the first fatal status, cancelling the rest. Client keepalive pings an idle connection and closes it on a missed ACK. A multiplexed channel (MuxChannel) carries many concurrent calls over one connection through a shared read pump with per-stream inboxes and serialized writes, drives keepalive, and supports interactive bidirectional streaming — send while receiving, replies read message by message. v0.7.0 adds per-message gzip: a self-built DEFLATE (RFC 1951, stored/fixed/dynamic-Huffman) and gzip (RFC 1952, CRC-32 + ISIZE) reader that decompresses gzip requests on the server and gzip responses on the client, and client-side retry with exponential backoff bounded by the call deadline. It also hardens every decoder against malformed input: the gRPC length prefix, HPACK integer/string, protobuf, and health decoders now reject an out-of-range or overflowing length instead of aborting; flow-control windows saturate rather than wrap; peer SETTINGS are validated; the client reassembles a response header block across CONTINUATION; and header blocks and message sizes are capped. v0.6.1 self-builds a pure protobuf wire runtime (PbWriter/PbReader for varint, zigzag, fixed32/64, and length-delimited fields) and Server Reflection: a descriptor model and the grpc.reflection.v1.ServerReflection service (with its v1alpha alias) answering ListServices, FileContainingSymbol, and FileByFilename. v0.6 added the client half — a real Channel, a long-lived multiplexed h2c connection over @socket.Tcp driven by a pure H2Client engine symmetric to the server — plus the grpc.health.v1.Health service, server interceptors, -bin metadata, google.rpc.Status rich errors, and client-side grpc-timeout enforcement. The server engine (H2Server) serves all four call kinds over the self-built HTTP/2 (h2c) transport, driving the RFC 7540 frame layer, the stream state machine, complete HPACK (RFC 7541, with Huffman + dynamic table), and connection- and stream-level flow control. Includes the shared length-prefixed framing and the 17-code gRPC status model.

grpc
rpc
protobuf
http2
moonbit
Download zip
Author
Version
0.10.0
License
Apache-2.0
Last updated
14 hours ago
Downloads
505

Dependencies

#moonrpc

A real gRPC implementation for MoonBit — ← gRPC.

Check and Test License mooncakes

moonrpc targets real gRPC — not gRPC-Web. Where the MoonBit ecosystem lacks the primitives, we build them: the north star is a self-built HTTP/2 (RFC 7540) framing layer with stream multiplexing and HPACK (RFC 7541), carrying application/grpc+proto over h2c.

v0.5 serves all four gRPC call kinds — unary, server-, client-, and bidirectional-streaming — over the self-built HTTP/2 (h2c) transport. A pure, all-backend protocol engine (H2Server) drives the frame layer, the stream state machine, complete HPACK, and connection- and stream-level flow control honoured across the whole multi-message exchange; a native moonbitlang/async socket driver (GrpcServer) pumps the bytes:

let server = @net.GrpcServer::new()

// unary: one request, one reply.
server.register("/greet.Greeter/SayHello", req => handle(req))

// server-streaming: one request, an ordered run of replies.
server.register_server_streaming("/count.C/Up", (ctx, req) => [
first(req), second(req), third(req),
])

// client-streaming: many requests collected, one reply at half-close.
server.register_client_streaming("/sum.S/Add", (ctx, msgs) => fold(msgs))

// bidi: each request echoed as it arrives, a farewell at half-close.
server.register_bidi("/chat.C/Echo", ctx => @moonrpc.BidiHandler::{
on_message: m => [reply_to(m)],
on_end: () => [b"bye"],
})

server.serve(port=50051) // a real gRPC / in-process client gets the replies

The handler sees the call's RpcContext: the request metadata, the grpc-timeout deadline in milliseconds, and slots for response initial and trailing metadata.

#The Channel client

A Channel is one long-lived h2c connection; every call multiplexes over it on its own client-allocated stream id, sharing the connection's HPACK and flow-control state.

let chan = @net.Channel::connect("127.0.0.1", 50051)

// unary.
let reply = chan.unary("/greet.Greeter/SayHello", request)
reply.messages[0] // the reply message; reply.grpc_status is 0 on success

// server-streaming: one request, every framed reply reassembled in order.
let out = chan.server_streaming("/count.C/Up", request) // out.messages

// client-streaming: many requests, one reply.
let sum = chan.client_streaming("/sum.S/Add", [a, b, c])

// a deadline: sent as grpc-timeout and enforced locally — if it elapses the
// stream is reset and grpc_status comes back DEADLINE_EXCEEDED (4).
let bounded = chan.unary("/slow.S/Wait", request, timeout_millis=Some(200))

Under the Channel, H2Client is the same kind of pure, transport-independent engine as the server: it produces the request frames and consumes the response frames, so the whole client path is exercised in-memory on every backend against H2Server, and only the socket driver is native.

#Health and interceptors

The standard grpc.health.v1.Health service registers in one call, and server interceptors wrap unary and server-streaming handlers:

let health = @moonrpc.HealthService::new()
health.set_status("greet.Greeter", @moonrpc.Serving)
server.register_health(health) // Check + Watch on /grpc.health.v1.Health/*

engine.add_unary_interceptor((ctx, req, next) => {
// inspect, then proceed — or return without calling next to short-circuit.
next(ctx, req)
})

#Protobuf and Server Reflection

A pure protobuf wire runtime carries the proto3 binary format — varint, zigzag, fixed32/fixed64, and length-delimited fields — as a PbWriter/PbReader pair that every message serialises through on every backend:

let w = @moonrpc.PbWriter::new()
w.string_(1, "testing") // field 1, a length-delimited string
w.int32(2, -1) // field 2, a varint (negative -> ten octets)
let r = @moonrpc.PbReader::new(w.to_bytes())
let (field, wire) = r.read_tag() // (1, LengthDelim)
r.read_string() // "testing"

On top of it, a descriptor model builds and parses the FileDescriptorProto / FileDescriptorSet fields the reflection path uses — package, message names and fields, services and methods — and the standard grpc.reflection.v1.ServerReflection service answers a reflection client over the same h2c transport — ListServices to enumerate, FileContainingSymbol / FileByFilename to describe:

let greeter : @moonrpc.ServiceDescriptor = {
name: "Greeter",
methods: [
{ name: "SayHello", input_type: ".greet.HelloRequest",
output_type: ".greet.HelloReply",
client_streaming: false, server_streaming: false },
],
}
let refl = @moonrpc.ReflectionService::new()
refl.add_file(@moonrpc.FileDescriptor::new("greet.proto", "greet", services=[greeter]))
server.register_reflection(refl) // a reflection client now lists + describes greet.Greeter

Under the hood, everything below serve is a pure, transport-independent enginefeed turns a stream of decoded frames into the frames to write back, so the whole server path (HPACK, flow control, dispatch, HEADERS + DATA + grpc-status trailers) is exercised in-memory on every backend; only the socket driver is native.

let engine = @moonrpc.H2Server::new()
engine.register("/echo.Echo/Say", req => req)
let out = engine.feed(frame) // -> [HEADERS(:status 200), DATA(reply), HEADERS(grpc-status:0)]

It builds on the load-bearing transport primitives, all verified across every backend:

#The gRPC message framing

let frame = @moonrpc.encode_message(b"hello grpc")
// [0x00][0x00 0x00 0x00 0x0A]["hello grpc"] — 1-byte flag + 4-byte big-endian length + payload

let (compressed, payload) = @moonrpc.decode_message(frame).unwrap()
// (false, b"hello grpc")

This is the Length-Prefixed-Message framing shared by gRPC-Web (over HTTP/1.1) and real gRPC (over HTTP/2), so the transport can be swapped underneath it without touching the codec.

#The HTTP/2 frame layer (RFC 7540)

All ten frame types encode to and decode from their exact wire bytes — a byte-level, exhaustively round-trippable codec that the multiplexer will layer on top without touching:

let f = @moonrpc.Frame::Headers(
stream_id=1, fragment=block, end_stream=true, end_headers=true,
priority=None, padding=0,
)
let bytes = f.encode() // 9-octet header + payload
let (frame, consumed) = @moonrpc.decode_frame(bytes) // raises Incomplete until whole

@moonrpc.has_connection_preface(buf) // PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n (§3.5)

The stream state machine drives the §5.1 lifecycle (idle / open / half-closed / closed) off END_STREAM / push / reset, and rejects illegal transitions:

let s = @moonrpc.Stream::new(1)
s.send(@moonrpc.StreamEvent::Headers(end_stream=false)) // -> Open
s.recv(@moonrpc.StreamEvent::Data(end_stream=true)) // -> HalfClosedRemote

#Complete HPACK (RFC 7541)

The full header-compression stack: the 61-entry static table, prefix integers, Huffman coding (Appendix B), and the size-bounded dynamic table with eviction — driven by a stateful encoder/decoder pair over the six field representations. Verified byte-for-byte against the Appendix C worked examples.

let enc = @moonrpc.HpackEncoder::new(huffman=true)
let block = enc.encode([{ name: b":method", value: b"GET" },
{ name: b":authority", value: b"www.example.com" }])

let dec = @moonrpc.HpackDecoder::new()
let headers = dec.decode(block) // back to the same header list

@moonrpc.huffman_encode(b"www.example.com") // f1e3 c2e5 f23a 6ba0 ab90 f4ff — §C.4.1

#The status model

@moonrpc.Status::code(NotFound) // 5
@moonrpc.Status::name(Unauthenticated) // "UNAUTHENTICATED" (all 17 canonical grpc-status codes)

let m : @moonrpc.Method = { service: "greet.Greeter", name: "SayHello" }
m.path() // "/greet.Greeter/SayHello"

#Roadmap (the self-built stack, sequenced to completeness)

v0 = framing + status + method descriptors; v0.2 = the first HPACK primitives (static table + integer representation + non-Huffman string literals). v0.3 self-builds the load-bearing transport primitives: the HTTP/2 frame layer (all ten types — DATA / HEADERS / PRIORITY / RST_STREAM / SETTINGS / PUSH_PROMISE / PING / GOAWAY / WINDOW_UPDATE / CONTINUATION — with flags and payloads), the connection preface, the stream state machine (§5.1 + §5.1.1 id rules), and complete HPACK (Huffman coding + the dynamic table with eviction + the stateful encoder/decoder).

v0.4 makes the server actually run: the H2Server engine reads frames off a connection, demultiplexes by stream id, drives the per-stream state machine, exchanges SETTINGS, and honours connection- and stream-level flow-control windows with WINDOW_UPDATE; it receives a request stream, dispatches to a registered (Bytes) -> Bytes handler, and responds with HEADERS (:status 200, grpc-encoding) + DATA + trailer HEADERS (grpc-status). The native GrpcServer binds this engine to real moonbitlang/async TCP sockets, proven by an in-process h2c client that makes a unary call end-to-end. Note: the async TLS layer exposes no ALPN, so h2 runs via h2c (prior-knowledge) until an ALPN hook lands upstream.

v0.5 adds the streaming modes on the same engine. A method is registered as one of four cardinalities — unary, server-streaming ((ctx, req) -> [reply]), client-streaming ((ctx, [req]) -> reply), or bidi (a BidiHandler whose on_message fires per request message and whose on_end fires at half-close). The engine reassembles length-prefixed messages out of the DATA stream, routes each to the handler, and frames every produced message as its own length-prefixed DATA — flow control is honoured across the whole multi-message exchange, so a reply larger than the window splits and resumes on WINDOW_UPDATE. Request metadata and the grpc-timeout deadline are parsed and surfaced to the handler, which can set response initial and trailing metadata. Two in-process h2c clients — a server-streaming call receiving multiple framed replies in order and a client-streaming call sending many messages for one reply — prove it end-to-end, mutation-verified against the stream terminator and the flow-control windows.

v0.6 adds the client half and the gRPC cross-cutting services. A real Channel — a long-lived, multiplexed h2c connection over a real @socket.Tcp, driven by a pure H2Client engine symmetric to the server — opens streams (client-allocated odd ids), HPACK-encodes request HEADERS, frames request DATA under the send windows, and reassembles the reply. It performs unary, server- and client-streaming calls against a real GrpcServer over an actual socket. The grpc.health.v1.Health service ships with a hand-coded protobuf codec for its two messages; Check is fully served, and Watch emits the current status once at subscribe time (a stream that also pushes on every later change needs the async engine variant). Server-side interceptors wrap unary and server-streaming handlers in an outermost-first chain that can rewrite the request, post-process the reply, or short-circuit. The grpc-timeout deadline is now enforced client-side: the Channel races the read loop against the timer and, when it elapses, resets the stream and surfaces DEADLINE_EXCEEDED.

v0.6.1 self-builds the protobuf wire runtime and Server Reflection. A pure PbWriter/PbReader pair encodes and decodes the proto3 binary format (varint, zigzag, fixed32/fixed64, length-delimited, tag packing, unknown-field skipping, and truncation/overflow/group-type rejection). A descriptor model builds and parses the FileDescriptorProto / FileDescriptorSet subset the reflection path needs — package, message names and fields, services and methods — enough for a self-contained proto (enums, nested types, imports, and options are not yet modelled, so a decoded protoc set re-encodes only that subset), and the grpc.reflection.v1.ServerReflection service (with its v1alpha alias) answers ListServices, FileContainingSymbol, and FileByFilename as a bidi stream, returning the real descriptor bytes. An in-process reflection client, over an actual socket, lists and describes a registered service end-to-end, and the varint/descriptor path is mutation-verified.

Delivered since: grpcurl interop in CI, -bin metadata round-trip, rich errors (google.rpc.Status), per-message gzip decompression on both sides, and client-side retry with exponential backoff.

Also delivered on the server: what the engine now refuses. A request whose request line is not gRPC's — :method other than POST, a content-type that does not begin application/grpc, a missing te: trailers — is answered with an HTTP status (415 / 405 / 400) instead of the 200 a plain HTTP client would read as success. SETTINGS_MAX_CONCURRENT_STREAMS is advertised in the server preface and enforced on arrival, with REFUSED_STREAM for the stream over the limit. Received DATA is charged against the connection window before anything can refuse the frame, and overrunning either window is a FLOW_CONTROL_ERROR. A field block must arrive contiguously (§6.10). And the grpc-timeout deadline is enforced server-side against a clock the driver installs: a call already out of time never reaches its handler, one whose handler outran it never sends the reply, and H2Server::tick closes out a call that expired while it waited. Enforcement is not preemptive — a synchronous handler already running is not interrupted — which is what the concurrent read-demux engine below is for.

Next: gzip response compression (the encode direction), channelz, connection keepalive and pooling, DNS resolution with connectivity-aware load-balancing, and full descriptor fidelity — enums, nested types, imports — with transitive-dependency reflection. The concurrent read-demux engine variant, which underpins interactive bidi, push-based health Watch, and preemptive server-side deadlines, is tracked as one piece.

#License

Apache-2.0.

StreamInterceptor

type StreamInterceptor = (RpcContext, Bytes, (RpcContext, Bytes) -> Array[Bytes]) -> Array[Bytes]

A server-streaming interceptor: (ctx, request, next) -> replies. It can pre-process the request, post-process the reply sequence, or short-circuit.

UnaryInterceptor

type UnaryInterceptor = (RpcContext, Bytes, (RpcContext, Bytes) -> Bytes) -> Bytes

A unary server interceptor: (ctx, request, next) -> reply, where next is the remainder of the chain. Call next(ctx, request) to proceed, or return without calling it to short-circuit.

FrameError

pub suberror FrameError {
Incomplete
FrameSizeError(String)
ProtocolError(String)
} derive(Eq)

A raised decode failure. Incomplete is the normal "need more bytes" signal a streaming reader catches to wait for the rest; the others are protocol errors.
impl Show for FrameError

GzError

pub suberror GzError {
GzError(String)
}

A gzip / DEFLATE decode failure: a malformed member, an unsupported feature, or a checksum mismatch.

HpackError

pub suberror HpackError {
HuffmanError(String)
HpackDecodeError(String)
} derive(Eq)

A raised HPACK failure (Huffman decoding or header-block decoding).
impl Show for HpackError

PbError

pub suberror PbError {
Truncated
BadWireType(Int)
Overflow
BadUtf8
} derive(Eq)

A raised protobuf decode failure: Truncated when the buffer ends inside a field, BadWireType for a group or unknown wire type, Overflow for a varint longer than ten octets, and BadUtf8 for an invalid string field.
impl Show for PbError

StreamError

pub suberror StreamError {
InvalidTransition(String)
} derive(Eq)

An illegal stream transition (RFC 7540 §5.1): a frame not permitted in the current state (typically a STREAM_CLOSED or PROTOCOL_ERROR condition).
impl Show for StreamError

BidiHandler

pub(all) struct BidiHandler {
on_message : (Bytes) -> Array[Bytes]
on_end : () -> Array[Bytes]
}

A live bidirectional call. on_message is invoked once per fully-received request message and returns the reply messages to send right then; on_end runs after the client half-closes and returns the final replies. Both feed the same flow-controlled response stream, so responses interleave with requests.

CallReply

pub(all) struct CallReply {
status : Bytes
grpc_status : Int
grpc_message : String
headers : Array[Header]
messages : Array[Bytes]
trailers : Array[Header]
}

The completed result of a call: the HTTP :status, the numeric grpc-status (-1 if the peer never sent one), the response initial metadata, the reply messages in order, and the trailing metadata.

ClientCall

pub(all) struct ClientCall {
id : Int
path : Bytes
authority : Bytes
metadata : Array[Header]
timeout : Bytes?
out : Bytes
out_off : Int
req_ended : Bool
req_headers_sent : Bool
req_fin_sent : Bool
send_window : Int
data :
Buffer

data_off : Int
messages : Array[Bytes]
recv_window : Int
status : Bytes
grpc_status : Int?
grpc_message : String
resp_encoding : Bytes
resp_headers : Array[Header]
resp_trailers : Array[Header]
headers_seen : Bool
header_block :
Buffer

in_headers : Bool
pending_end_stream : Bool
decode_failed : Bool
retryable : Bool
done : Bool
}

One client-side call: its stream id, the request side (the length-prefixed request body still to send, the send window, and whether the request has been half-closed), and the response side (the accumulating DATA with a cursor over the messages already pulled out, the recv window, the captured :status and grpc-status, and the response initial and trailing metadata).

ConnectionPool

pub struct ConnectionPool {
order : Array[String]
subconns : Map[String, SubConn]
policy : LbPolicy
cursor : Int
}

A pool of sub-connections, one per resolved address (in resolver order), that picks a READY one per an LbPolicy.

ConnectionPool::new

fn ConnectionPool::new(addresses : Array[String], policy? : LbPolicy) -> ConnectionPool

A pool over addresses (each a host:port), each sub-connection starting IDLE, choosing among the READY ones with policy (default PickFirst).

ConnectionPool::pick

fn ConnectionPool::pick(self : ConnectionPool) -> String?

Pick a READY sub-connection's address for the next call, applying the LB policy over just the ready ones (← gRPC picker). None when no sub-connection is ready.

ConnectionPool::ready_addresses

fn ConnectionPool::ready_addresses(self : ConnectionPool) -> Array[String]

The addresses of every READY sub-connection, in resolver order.

ConnectionPool::subconn

fn ConnectionPool::subconn(self : ConnectionPool, address : String) -> SubConn?

The sub-connection for address, if the pool has one — the handle the Channel drives.

ConnectivityState

pub(all) enum ConnectivityState {
Idle
Connecting
Ready
TransientFailure
Shutdown
} derive(Eq,
Debug
)

A sub-connection's connectivity state (← gRPC connectivity.State).

DnsRecord

pub(all) struct DnsRecord {
name : String
rtype : Int
ttl : Int
address : String
} derive(Eq,
Debug
)

One answer resource record, carrying the textual address for A/AAAA records (empty for other types).

DnsResponse

pub(all) struct DnsResponse {
id : Int
rcode : Int
answers : Array[DnsRecord]
} derive(Eq,
Debug
)

A decoded DNS response: the transaction id echoed back, the response code (0 = NOERROR), and the answer records.

DnsResponse::addresses

fn DnsResponse::addresses(self : DnsResponse) -> Array[String]

Every resolved A/AAAA address in the response, in record order.

DynamicTable

pub(all) struct DynamicTable {
entries : Array[(Bytes, Bytes)]
size : Int
max_size : Int
}

The HPACK dynamic table: a FIFO of recently seen (name, value) entries, newest first (entries[0]), bounded by max_size octets where each entry costs name.len + value.len + 32 (RFC 7541 §4.1). Adding evicts the oldest entries until the newcomer fits; an entry larger than max_size empties the table and is not stored (§4.4).

DynamicTable::add

fn DynamicTable::add(self : DynamicTable, name : Bytes, value : Bytes) -> Unit

Insert (name, value) at the front, evicting oldest entries to make room. If the entry alone exceeds max_size the table ends up empty (RFC 7541 §4.4).

DynamicTable::count

fn DynamicTable::count(self : DynamicTable) -> Int

The number of entries currently in the dynamic table.

DynamicTable::current_size

fn DynamicTable::current_size(self : DynamicTable) -> Int

The current total size of the dynamic table in octets (§4.1 accounting).

DynamicTable::new

fn DynamicTable::new(max_size? : Int) -> DynamicTable

A new empty dynamic table bounded by max_size octets (default 4096, the HTTP/2 initial SETTINGS_HEADER_TABLE_SIZE).

DynamicTable::set_max_size

fn DynamicTable::set_max_size(self : DynamicTable, new_max : Int) -> Unit

Resize the table (a dynamic table size update, RFC 7541 §4.2), evicting to fit.

FieldDescriptor

pub(all) struct FieldDescriptor {
name : String
number : Int
label : FieldLabel
type_ : FieldType
type_name : String
} derive(Eq)

One field of a message (FieldDescriptorProto). type_name is the fully-qualified name of the referenced message or enum for TypeMessage / TypeEnum, and empty for scalars.

FieldDescriptor::decode

fn FieldDescriptor::decode(body : Bytes) -> FieldDescriptor raise PbError

Decode a FieldDescriptorProto.

FieldDescriptor::encode

fn FieldDescriptor::encode(self : FieldDescriptor) -> Bytes

Encode a FieldDescriptorProto.

FieldDescriptor::scalar

fn FieldDescriptor::scalar(name : String, number : Int, type_ : FieldType, label? : FieldLabel) -> FieldDescriptor

A field with a scalar type and no composite type_name.

FieldLabel

pub(all) enum FieldLabel {
LabelOptional
LabelRequired
LabelRepeated
} derive(Eq)

The FieldDescriptorProto.Label: proto3 fields are LabelOptional unless repeated.

FieldLabel::code

fn FieldLabel::code(self : FieldLabel) -> Int

The descriptor.proto enum number of a label.

FieldLabel::from_code

fn FieldLabel::from_code(n : Int) -> FieldLabel

The label for a descriptor.proto enum number; anything else reads as LabelOptional.

FieldType

pub(all) enum FieldType {
TypeDouble
TypeFloat
TypeInt64
TypeUint64
TypeInt32
TypeFixed64
TypeFixed32
TypeBool
TypeString
TypeMessage
TypeBytes
TypeUint32
TypeEnum
TypeSfixed32
TypeSfixed64
TypeSint32
TypeSint64
} derive(Eq)

The FieldDescriptorProto.Type enum (proto3's scalar and composite field types). TypeGroup is intentionally absent — groups are removed from proto3.

FieldType::code

fn FieldType::code(self : FieldType) -> Int

The descriptor.proto enum number of a field type.

FieldType::from_code

fn FieldType::from_code(n : Int) -> FieldType

The field type for a descriptor.proto enum number; an unrecognised number (including the removed group type 10) reads as TypeMessage.

FileDescriptor

pub(all) struct FileDescriptor {
name : String
package_ : String
messages : Array[MessageDescriptor]
services : Array[ServiceDescriptor]
syntax : String
} derive(Eq)

A single .proto file (FileDescriptorProto): its filename, package, the message and service types it defines, and the syntax level. This is the unit Server Reflection returns.

FileDescriptor::decode

fn FileDescriptor::decode(body : Bytes) -> FileDescriptor raise PbError

Decode a FileDescriptorProto.

FileDescriptor::encode

fn FileDescriptor::encode(self : FileDescriptor) -> Bytes

Encode a FileDescriptorProto.

FileDescriptor::new

fn FileDescriptor::new(name : String, package_ : String, messages? : Array[MessageDescriptor], services? : Array[ServiceDescriptor]) -> FileDescriptor

A proto3 file with the given filename and package.

FileDescriptor::service_names

fn FileDescriptor::service_names(self : FileDescriptor) -> Array[String]

The fully-qualified names of the services this file defines (package.Service).

FileDescriptor::symbols

fn FileDescriptor::symbols(self : FileDescriptor) -> Array[String]

The fully-qualified names this file defines: package.Service for each service and package.Message for each message. These are the symbols a FileContainingSymbol reflection request can resolve to this file.

Frame

pub(all) enum Frame {
Data(stream_id~ : Int, data~ : Bytes, end_stream~ : Bool, padding~ : Int)
Headers(stream_id~ : Int, fragment~ : Bytes, end_stream~ : Bool, end_headers~ : Bool, priority~ : Priority?, padding~ : Int)
Priority(stream_id~ : Int, priority~ : Priority)
RstStream(stream_id~ : Int, error_code~ : Int)
Settings(params~ : Array[(Int, Int)], ack~ : Bool)
PushPromise(stream_id~ : Int, promised_id~ : Int, fragment~ : Bytes, end_headers~ : Bool, padding~ : Int)
Ping(payload~ : Bytes, ack~ : Bool)
GoAway(last_stream_id~ : Int, error_code~ : Int, debug~ : Bytes)
WindowUpdate(stream_id~ : Int, increment~ : Int)
Continuation(stream_id~ : Int, fragment~ : Bytes, end_headers~ : Bool)
Unknown(ftype~ : Int, flags~ : Int, stream_id~ : Int, payload~ : Bytes)
} derive(Eq)

A decoded HTTP/2 frame (RFC 7540 §6). Each variant carries the semantic payload with padding already stripped; padding is the number of padding bytes to (re)emit. Unknown preserves extension/unrecognised frames verbatim so a reader can forward or ignore them (RFC 7540 §4.1).

Frame::encode

fn Frame::encode(self : Frame) -> Bytes

Encode this frame to its complete wire representation (9-octet header + payload), the exact inverse of decode_frame.

Frame::frame_type

fn Frame::frame_type(self : Frame) -> Int

The numeric frame-type code of this frame (RFC 7540 §6).

FrameHeader

pub(all) struct FrameHeader {
length : Int
ftype : Int
flags : Int
stream_id : Int
} derive(Eq)

The fixed 9-octet frame header (RFC 7540 §4.1): a 24-bit payload length, an 8-bit type, 8-bit flags, a reserved bit, and a 31-bit stream identifier.

FrameHeader::encode

fn FrameHeader::encode(self : FrameHeader) -> Bytes

Encode a FrameHeader to its 9 wire octets.

H2Client

pub struct H2Client {
encoder : HpackEncoder
decoder : HpackDecoder
calls : Map[Int, ClientCall]
next_id : Int
conn_send_window : Int
conn_recv_window : Int
remote_initial_window : Int
remote_max_frame : Int
goaway_received : Bool
goaway_last_stream_id : Int
orphan_blocks : Map[Int,
Buffer
]
}

The client side of one HTTP/2 connection: the HPACK codec pair (stateful across every call on the connection), the live calls keyed by stream id, the next odd stream id to allocate (§5.1.1), and the connection-level flow-control state bounded by the peer's SETTINGS.

H2Client::close_send

fn H2Client::close_send(self : H2Client, id : Int) -> Array[Frame]

Half-close the request side of a call (no more request messages) and return any frames that completes — the trailing END_STREAM.

H2Client::feed

fn H2Client::feed(self : H2Client, frame : Frame) -> Array[Frame] raise

Feed one decoded response frame to the engine, advancing all state and returning the frames to write back (SETTINGS ack, PING pong, WINDOW_UPDATE replenishing a receive window, and — once a WINDOW_UPDATE lifts back-pressure — any remaining request DATA). Captures :status, grpc-status, response metadata, and the reassembled reply messages.

H2Client::goaway_last_stream_id

fn H2Client::goaway_last_stream_id(self : H2Client) -> Int

The peer's last processed stream id from its GOAWAY (0 if none was seen); a call whose stream id is above it was never handled by the server.

H2Client::goaway_received

fn H2Client::goaway_received(self : H2Client) -> Bool

Whether the peer has sent GOAWAY; a draining connection opens no new stream, so the driver picks a fresh one for further calls. Mirrors H2Server::goaway_received.

H2Client::has_messages

fn H2Client::has_messages(self : H2Client, id : Int) -> Bool

Whether a call has reply messages buffered but not yet taken — for reading a streaming response incrementally as it arrives, rather than all at once via reply.

H2Client::is_done

fn H2Client::is_done(self : H2Client, id : Int) -> Bool

Whether a call has fully completed (its response ended). An unknown id counts as done so a driver loop terminates.

H2Client::is_retryable

fn H2Client::is_retryable(self : H2Client, id : Int) -> Bool

Whether call id was left retryable by a GOAWAY: its stream id is above the peer's last processed id, so the server never saw it and re-issuing the RPC on a fresh connection is safe (gRPC transport GOAWAY / RFC 7540 §6.8). False for a call at or below that id — it may have been processed — and when no GOAWAY arrived.

H2Client::new

fn H2Client::new() -> H2Client

A fresh client engine with no live calls. The first call takes stream id 1.

H2Client::open

fn H2Client::open(self : H2Client, path : String, metadata? : Array[Header], authority? : String, timeout_millis? : Int?) -> Int

Open a new call on this connection for path (/pkg.Service/Method), returning its freshly allocated stream id. metadata is sent as custom request HEADERS; timeout_millis, when set, becomes the grpc-timeout header. The request body is added with send and half-closed with close_send.

H2Client::preface

fn H2Client::preface(self : H2Client) -> Array[Frame]

The client's opening frames (RFC 7540 §3.5): a SETTINGS frame disabling server push. Written right after the 24-octet connection preface bytes, before any request.

H2Client::release

fn H2Client::release(self : H2Client, id : Int) -> Unit

Drop everything the engine still holds for a finished call: its buffered body, messages, header block and trailers. The map is only ever inserted into otherwise, so a long-lived connection would keep one full request and response per RPC it has ever made, and every WINDOW_UPDATE and SETTINGS frame would walk that whole history. An unknown or already-released id is a no-op.

H2Client::reply

fn H2Client::reply(self : H2Client, id : Int) -> CallReply

The completed result of a call. Meaningful once is_done is true.

H2Client::send

fn H2Client::send(self : H2Client, id : Int, message : Bytes, end? : Bool) -> Array[Frame]

Append one request message to a call and return the frames to write now (the request HEADERS the first time, then as much length-prefixed DATA as the send windows allow). Set end on the last message to half-close the request.

H2Client::take_messages

fn H2Client::take_messages(self : H2Client, id : Int) -> Array[Bytes]

Take every reply message received so far, clearing the call's buffer — the incremental counterpart to reply, for a server- or bidi-streaming response read message by message.

H2Client::unary

fn H2Client::unary(self : H2Client, path : String, request : Bytes, metadata? : Array[Header], authority? : String, timeout_millis? : Int?) -> (Int, Array[Frame])

Open a unary call and return (stream_id, frames_to_write) in one step: the request HEADERS and the single length-prefixed request message with END_STREAM.

H2Server

pub struct H2Server {
handlers : Map[String, Handler]
encoder : HpackEncoder
decoder : HpackDecoder
streams : Map[Int, SrvStream]
unary_interceptors : Array[(RpcContext, Bytes, (RpcContext, Bytes) -> Bytes) -> Bytes]
stream_interceptors : Array[(RpcContext, Bytes, (RpcContext, Bytes) -> Array[Bytes]) -> Array[Bytes]]
conn_recv_window : Int
conn_send_window : Int
remote_initial_window : Int
remote_max_frame : Int
max_streams : Int
clock : () -> Int64
continuing : Int?
goaway_received : Bool
last_client_stream : Int
closing : Bool
drained : Int?
}

The server side of one HTTP/2 connection: the HPACK codec pair, the live streams, the connection-level flow-control windows, and the peer's settings that bound what we may send. Persistent across the whole connection because HPACK and flow control are stateful.

H2Server::active_streams

fn H2Server::active_streams(self : H2Server) -> Int

How many streams are still running: the ones RFC 9113 §5.1.2 counts against SETTINGS_MAX_CONCURRENT_STREAMS, which is open and both half-closed states. A graceful shutdown waits for this to reach zero before closing the connection. Finished streams stay in the map with their state but are not counted, and neither is one the engine only knows of because a WINDOW_UPDATE named it — that stream was never opened, so it holds nothing and occupies no slot.

H2Server::add_stream_interceptor

fn H2Server::add_stream_interceptor(self : H2Server, interceptor : (RpcContext, Bytes, (RpcContext, Bytes) -> Array[Bytes]) -> Array[Bytes]) -> Unit

Add a server-streaming interceptor to the server's chain.

H2Server::add_unary_interceptor

fn H2Server::add_unary_interceptor(self : H2Server, interceptor : (RpcContext, Bytes, (RpcContext, Bytes) -> Bytes) -> Bytes) -> Unit

Add a unary interceptor to the server's chain. Applies to every unary method; interceptors run in registration order, outermost first.

H2Server::closing

fn H2Server::closing(self : H2Server) -> Bool

Whether a connection error has been reported. The GOAWAY carrying it came back from the feed that found it, so the driver writes that and then closes; the engine processes nothing more (RFC 9113 §5.4.1).

H2Server::feed

fn H2Server::feed(self : H2Server, frame : Frame) -> Array[Frame]

Feed one decoded incoming frame to the engine, advancing all state and returning the frames to write back (SETTINGS ack, PING pong, WINDOW_UPDATE, and — as the request stream progresses — the framed gRPC response messages and trailers). A protocol fault comes back as the frames that report it rather than as a raise the driver can only discard: RST_STREAM for a stream error, and for a connection error a GOAWAY, after which closing is set and every later frame is ignored (RFC 9113 §5.4).

H2Server::goaway

fn H2Server::goaway(self : H2Server, last_stream_id : Int) -> Array[Frame]

Frames for a graceful shutdown: a GOAWAY announcing last_stream_id — the highest client stream the server will still process (RFC 7540 §6.8) — with NO_ERROR, so the peer opens no new streams while in-flight ones finish. The driver writes these before closing the connection. A stream opened above last_stream_id afterwards is declined with RST_STREAM(REFUSED_STREAM), which tells the client it was never processed and can be re-issued elsewhere.

H2Server::goaway_received

fn H2Server::goaway_received(self : H2Server) -> Bool

Whether the peer has sent GOAWAY; the driver stops accepting new streams once this is set.

H2Server::highest_stream

fn H2Server::highest_stream(self : H2Server) -> Int

The highest client stream id this connection has opened, which is what a GOAWAY has to name so the peer knows which of its streams were accepted.

H2Server::new

fn H2Server::new() -> H2Server

A fresh server engine with no registered handlers. Flow-control windows start at the HTTP/2 defaults until the peer's SETTINGS adjust them.

H2Server::preface

fn H2Server::preface(self : H2Server) -> Array[Frame]

The server's opening frames (RFC 7540 §3.5): a SETTINGS frame disabling server push and naming how many streams this connection will run at once. Sent immediately after the client connection preface is validated, before any request frame is read.

H2Server::register

fn H2Server::register(self : H2Server, path : String, handler : (Bytes) -> Bytes) -> Unit

Register a unary handler for a fully-qualified gRPC path (/pkg.Service/Method): one request message in, one reply message out. An unmatched path gets a trailers-only grpc-status: 12 (UNIMPLEMENTED) response.

H2Server::register_bidi

fn H2Server::register_bidi(self : H2Server, path : String, factory : (RpcContext) -> BidiHandler) -> Unit

Register a bidirectional-streaming handler. The factory runs once per call and returns a BidiHandler whose on_message fires per request message (its replies stream out immediately) and whose on_end fires at half-close.

H2Server::register_client_streaming

fn H2Server::register_client_streaming(self : H2Server, path : String, handler : (RpcContext, Array[Bytes]) -> Bytes) -> Unit

Register a client-streaming handler: every request message the client sends is collected, and after the client half-closes the handler returns one reply.

H2Server::register_handler

fn H2Server::register_handler(self : H2Server, path : String, handler : Handler) -> Unit

Register a handler of any of the four gRPC call kinds.

H2Server::register_server_streaming

fn H2Server::register_server_streaming(self : H2Server, path : String, handler : (RpcContext, Bytes) -> Array[Bytes]) -> Unit

Register a server-streaming handler: one request message, an ordered sequence of reply messages, each framed as its own gRPC message.

H2Server::register_unary

fn H2Server::register_unary(self : H2Server, path : String, handler : (RpcContext, Bytes) -> Bytes) -> Unit

Register a unary handler that also sees the call context (metadata, deadline, and the response metadata slots).

H2Server::release

fn H2Server::release(self : H2Server, id : Int) -> Unit

Drop everything the engine still holds for a finished stream. The map is only ever inserted into otherwise, so a long-lived connection would keep one full request and response per RPC it has ever served. An unknown id is a no-op.

H2Server::set_clock

fn H2Server::set_clock(self : H2Server, clock : () -> Int64) -> Unit

Install the clock the engine measures grpc-timeout deadlines against: a function returning the current time in milliseconds on any monotonic scale. The engine is a pure state machine with no clock of its own, so until one is installed every deadline is inert; the native driver installs @async.now.

H2Server::set_max_streams

fn H2Server::set_max_streams(self : H2Server, n : Int) -> Unit

Set how many streams this connection may run at once. The value is both advertised as SETTINGS_MAX_CONCURRENT_STREAMS and enforced on arrival, so set it before preface and the peer is told exactly what it will be held to.

H2Server::stream_state

fn H2Server::stream_state(self : H2Server, id : Int) -> StreamState

The lifecycle state of stream id, or Idle if the engine has never seen it.

H2Server::tick

fn H2Server::tick(self : H2Server) -> Array[Frame]

Expire every call whose deadline has passed, returning the frames that end them. The driver calls this on whatever schedule it keeps time on; the engine also checks the deadline whenever a stream advances, so a call that is making progress never needs a tick to be cut off.

Handler

pub(all) enum Handler {
Unary((RpcContext, Bytes) -> Bytes)
ServerStreaming((RpcContext, Bytes) -> Array[Bytes])
ClientStreaming((RpcContext, Array[Bytes]) -> Bytes)
Bidi((RpcContext) -> BidiHandler)
}

A registered method, in one of gRPC's four cardinalities. The reply shape mirrors the request shape: streaming handlers produce an ordered Array[Bytes] of messages, each framed as its own length-prefixed gRPC message on the wire.
pub(all) struct Header {
name : Bytes
value : Bytes
} derive(Eq,
Debug
)

A decoded header field: name and value as raw octet strings (HTTP/2 header names and values are byte sequences, and gRPC -bin metadata is binary).

HealthService

pub struct HealthService {
statuses : Map[String, ServingStatus]
}

A grpc.health.v1.Health service backed by a per-service status table. The empty key "" is the overall-server status; a fresh service reports the whole server SERVING.

HealthService::check

fn HealthService::check(self : HealthService, service : String) -> ServingStatus

The serving status of a named service: its set status, or ServiceUnknown when the service was never registered.

HealthService::handlers

fn HealthService::handlers(self : HealthService) -> Array[(String, Handler)]

The (path, handler) pairs implementing the service: Check as a unary method and Watch as a server-streaming method that emits the current status. A live Watch that also pushes on every later change needs a streaming source the pure engine's eager ServerStreaming shape does not model, so this emits the status at subscribe time — the first message a real Watch always sends.

HealthService::install

fn HealthService::install(self : HealthService, server : H2Server) -> Unit

Register Check and Watch on a pure server engine.

HealthService::new

A health service reporting the overall server as SERVING.

HealthService::set_status

fn HealthService::set_status(self : HealthService, service : String, status : ServingStatus) -> Unit

Set the serving status of a named service (or the overall server with "").

HedgingPolicy

pub(all) struct HedgingPolicy {
max_attempts : Int
hedging_delay_millis : Int
non_fatal : Array[Int]
}

A method's hedging policy. max_attempts counts every parallel attempt (gRPC caps it at 5); a new attempt originates every hedging_delay_millis while attempts remain. A finished attempt commits the whole call unless its status is one of non_fatal, in which case hedging continues.

HedgingPolicy::attempt_cap

fn HedgingPolicy::attempt_cap(self : HedgingPolicy) -> Int

The effective attempt cap: the configured max_attempts, but never above gRPC's ceiling of 5.

HedgingPolicy::can_start_another

fn HedgingPolicy::can_start_another(self : HedgingPolicy, attempts_started : Int) -> Bool

Whether another hedged attempt may originate given attempts_started (the number already launched): only while attempts remain under the cap.

HedgingPolicy::default

fn HedgingPolicy::default() -> HedgingPolicy

A conventional default: up to 3 parallel attempts, a fresh one every 500 ms, treating only UNAVAILABLE as non-fatal — so a transient transport failure keeps hedging while a real application error commits at once.

HedgingPolicy::delay_millis

fn HedgingPolicy::delay_millis(self : HedgingPolicy) -> Int

The delay between originating hedged attempts. The async driver waits this long before launching the next parallel attempt; 0 fires them all at once.

HedgingPolicy::should_commit

fn HedgingPolicy::should_commit(self : HedgingPolicy, status_code : Int) -> Bool

Whether a finished attempt with status_code commits the whole call — stopping hedging and returning this result. OK commits (success), and so does any error not listed as non-fatal (a real failure retrying won't fix). A non-fatal error does not commit; hedging continues.

HpackDecoder

pub(all) struct HpackDecoder {
table : DynamicTable
limit : Int
}

A stateful HPACK decoder: it owns a dynamic table that persists across the header blocks of a connection. limit is the peer-agreed hard cap (SETTINGS_HEADER_TABLE_SIZE) a size update may not exceed.

HpackDecoder::decode

fn HpackDecoder::decode(self : HpackDecoder, block : Bytes) -> Array[Header] raise HpackError

Decode one complete header block into its header list (RFC 7541 §6), mutating the dynamic table for incrementally indexed fields and size updates. Raises HpackDecodeError/HuffmanError on any malformed representation.

HpackDecoder::new

fn HpackDecoder::new(max_size? : Int) -> HpackDecoder

A new decoder whose dynamic table is bounded by max_size octets (also the hard cap enforced on dynamic table size updates).

HpackEncoder

pub(all) struct HpackEncoder {
table : DynamicTable
huffman : Bool
}

A stateful HPACK encoder: it owns a dynamic table mirroring the decoder's, and prefers indexed representations. huffman selects Huffman string literals when they are shorter.

HpackEncoder::encode

fn HpackEncoder::encode(self : HpackEncoder, headers : Array[Header]) -> Bytes

Encode a header list into a header block (RFC 7541 §6), using indexed fields where possible and literal-with-incremental-indexing otherwise (mutating the dynamic table to mirror what the peer decoder will build). The output decodes back to the same header list via HpackDecoder.

HpackEncoder::new

fn HpackEncoder::new(max_size? : Int, huffman? : Bool) -> HpackEncoder

A new encoder bounded by max_size octets; huffman (default true) enables the shorter-of-two string-literal heuristic.

Keepalive

pub struct Keepalive {
keepalive_time : Int64
keepalive_timeout : Int64
last_activity : Int64
ping_outstanding : Bool
ping_sent_at : Int64
}

Keepalive state for one connection: the idle threshold that triggers a PING, the ACK deadline that declares the connection dead, and the timers tracking the last activity and any in-flight PING.

Keepalive::is_timed_out

fn Keepalive::is_timed_out(self : Keepalive, now : Int64) -> Bool

Whether the connection is dead at now: a PING is outstanding and its ACK has not arrived within keepalive_timeout of sending it.

Keepalive::new

fn Keepalive::new(keepalive_time~ : Int64, keepalive_timeout~ : Int64, now? : Int64) -> Keepalive

A keepalive that pings after keepalive_time of idleness and gives a PING keepalive_timeout to be acknowledged. now seeds the activity clock.

Keepalive::on_activity

fn Keepalive::on_activity(self : Keepalive, now : Int64) -> Unit

Record connection activity (a frame sent or received) at now, resetting the idle timer.

Keepalive::on_ping_ack

fn Keepalive::on_ping_ack(self : Keepalive, now : Int64) -> Unit

Record that the PING's ACK arrived at now — the connection is alive, and this counts as activity.

Keepalive::on_ping_sent

fn Keepalive::on_ping_sent(self : Keepalive, now : Int64) -> Unit

Record that a keepalive PING was sent at now.

Keepalive::should_ping

fn Keepalive::should_ping(self : Keepalive, now : Int64) -> Bool

Whether a keepalive PING should be sent at now: the connection has been idle at least keepalive_time and no PING is already awaiting its ACK.

LbPolicy

pub(all) enum LbPolicy {
PickFirst
RoundRobin
} derive(Eq,
Debug
)

A client-side load-balancing policy (← gRPC loadBalancingConfig).

LoadBalancer

pub struct LoadBalancer {
addresses : Array[String]
policy : LbPolicy
cursor : Int
}

A load balancer over a resolver's addresses, applying an LbPolicy to choose the next one.

LoadBalancer::new

fn LoadBalancer::new(addresses : Array[String], policy? : LbPolicy) -> LoadBalancer

A balancer over addresses (each a host:port) using policy (default PickFirst).

LoadBalancer::pick

fn LoadBalancer::pick(self : LoadBalancer) -> String?

The address the next call should use, or None when the resolver produced none. PickFirst always returns the first address; RoundRobin returns each in turn, wrapping around.

LoadBalancer::size

fn LoadBalancer::size(self : LoadBalancer) -> Int

The number of addresses the balancer is choosing among.

MessageDescriptor

pub(all) struct MessageDescriptor {
name : String
fields : Array[FieldDescriptor]
} derive(Eq)

A message type (DescriptorProto): its (simple) name and its fields.

MessageDescriptor::decode

fn MessageDescriptor::decode(body : Bytes) -> MessageDescriptor raise PbError

Decode a DescriptorProto.

MessageDescriptor::encode

fn MessageDescriptor::encode(self : MessageDescriptor) -> Bytes

Encode a DescriptorProto.

Method

pub(all) struct Method {
service : String
name : String
}

A fully-qualified RPC method: package.Service and the method name.

Method::path

fn Method::path(self : Method) -> String

The gRPC HTTP/2 :path, i.e. /package.Service/Method.

MethodDescriptor

pub(all) struct MethodDescriptor {
name : String
input_type : String
output_type : String
client_streaming : Bool
server_streaming : Bool
} derive(Eq)

One RPC method (MethodDescriptorProto): its name, the fully-qualified request and response message names, and the two streaming flags that together pick the call cardinality.

MethodDescriptor::decode

fn MethodDescriptor::decode(body : Bytes) -> MethodDescriptor raise PbError

Decode a MethodDescriptorProto.

MethodDescriptor::encode

fn MethodDescriptor::encode(self : MethodDescriptor) -> Bytes

Encode a MethodDescriptorProto.

PbReader

pub struct PbReader {
data : Bytes
pos : Int
}

A forward cursor over an encoded protobuf message. read_tag pulls the next field's number and wire type; the typed readers then consume its body. skip discards an unknown field's body so a decoder tolerates fields it does not know (protobuf forward compatibility).

PbReader::eof

fn PbReader::eof(self : PbReader) -> Bool

Whether the whole message has been consumed.

PbReader::new

fn PbReader::new(data : Bytes) -> PbReader

A reader positioned at the start of data.

PbReader::read_bool

fn PbReader::read_bool(self : PbReader) -> Bool raise PbError

Read a bool field body.

PbReader::read_bytes

fn PbReader::read_bytes(self : PbReader) -> Bytes raise PbError

Read a bytes field body.

PbReader::read_fixed32

fn PbReader::read_fixed32(self : PbReader) -> UInt raise PbError

Read a little-endian 32-bit fixed value.

PbReader::read_fixed64

fn PbReader::read_fixed64(self : PbReader) -> UInt64 raise PbError

Read a little-endian 64-bit fixed value.

PbReader::read_int32

fn PbReader::read_int32(self : PbReader) -> Int raise PbError

Read an int32 field body (the low 32 bits of the varint).

PbReader::read_int64

fn PbReader::read_int64(self : PbReader) -> Int64 raise PbError

Read an int64 field body.

PbReader::read_len_delim

fn PbReader::read_len_delim(self : PbReader) -> Bytes raise PbError

Read a length-delimited body's raw bytes.

PbReader::read_map_string_string

fn PbReader::read_map_string_string(self : PbReader) -> (String, String) raise PbError

Read one map<string, string> entry (a length-delimited { key = 1, value = 2 } submessage) as (key, value). Call once the tag for the map field has been read.

PbReader::read_packed_int32

fn PbReader::read_packed_int32(self : PbReader) -> Array[Int] raise PbError

Read a packed repeated int32 field: the length-delimited body decoded as back-to-back int32 varints.

PbReader::read_sint32

fn PbReader::read_sint32(self : PbReader) -> Int raise PbError

Read a sint32 field body (zigzag-decoded).

PbReader::read_sint64

fn PbReader::read_sint64(self : PbReader) -> Int64 raise PbError

Read a sint64 field body (zigzag-decoded).

PbReader::read_string

fn PbReader::read_string(self : PbReader) -> String raise PbError

Read a string field body, decoding UTF-8. Raises BadUtf8 on invalid bytes.

PbReader::read_tag

fn PbReader::read_tag(self : PbReader) -> (Int, WireType) raise PbError

Read a field tag, returning (field_number, wire_type). Raises BadWireType for a group or unknown wire type.

PbReader::read_uint32

fn PbReader::read_uint32(self : PbReader) -> UInt raise PbError

Read a uint32 field body.

PbReader::read_uint64

fn PbReader::read_uint64(self : PbReader) -> UInt64 raise PbError

Read a uint64 field body.

PbReader::read_varint

fn PbReader::read_varint(self : PbReader) -> UInt64 raise PbError

Read a base-128 varint. Raises Overflow past ten octets and Truncated if the buffer ends mid-varint.

PbReader::skip

fn PbReader::skip(self : PbReader, wire : WireType) -> Unit raise PbError

Discard the body of a field whose number the decoder does not recognise, given its wire type — the mechanism behind protobuf's forward compatibility.

PbWriter

pub struct PbWriter {
buf :
Buffer

}

An append-only protobuf message encoder. Field writers append a tag and the field body; to_bytes yields the finished message. Fields are written in the caller's order — protobuf places no ordering requirement on distinct fields, and a message struct's encoder writes them by ascending number by convention.

PbWriter::bool_

fn PbWriter::bool_(self : PbWriter, field : Int, v : Bool) -> Unit

Write a bool field.

PbWriter::bytes_

fn PbWriter::bytes_(self : PbWriter, field : Int, v : Bytes) -> Unit

Write a bytes field.

PbWriter::enum_

fn PbWriter::enum_(self : PbWriter, field : Int, v : Int) -> Unit

Write an enum field (its integer value, as a varint).

PbWriter::fixed32

fn PbWriter::fixed32(self : PbWriter, field : Int, v : UInt) -> Unit

Write a fixed32/sfixed32/float field.

PbWriter::fixed64

fn PbWriter::fixed64(self : PbWriter, field : Int, v : UInt64) -> Unit

Write a fixed64/sfixed64/double field.

PbWriter::int32

fn PbWriter::int32(self : PbWriter, field : Int, v : Int) -> Unit

Write an int32 field. Negative values sign-extend to a full ten-octet varint, exactly as the reference implementation encodes them.

PbWriter::int64

fn PbWriter::int64(self : PbWriter, field : Int, v : Int64) -> Unit

Write an int64 field.

PbWriter::map_string_string

fn PbWriter::map_string_string(self : PbWriter, field : Int, key : String, value : String) -> Unit

Write one map<string, string> entry for field: a length-delimited submessage { key = 1, value = 2 }, emitted once per pair (a proto map is repeated entries).

PbWriter::message_

fn PbWriter::message_(self : PbWriter, field : Int, v : Bytes) -> Unit

Write an embedded-message field: the pre-encoded sub-message as a length-delimited body.

PbWriter::new

fn PbWriter::new() -> PbWriter

A fresh, empty message encoder.

PbWriter::packed_int32

fn PbWriter::packed_int32(self : PbWriter, field : Int, values : Array[Int]) -> Unit

Write a packed repeated int32 field: all values as back-to-back varints inside one length-delimited field (proto3's default for scalar repeated).

PbWriter::sint32

fn PbWriter::sint32(self : PbWriter, field : Int, v : Int) -> Unit

Write a sint32 field (zigzag-encoded so small-magnitude negatives stay short).

PbWriter::sint64

fn PbWriter::sint64(self : PbWriter, field : Int, v : Int64) -> Unit

Write a sint64 field (zigzag-encoded).

PbWriter::string_

fn PbWriter::string_(self : PbWriter, field : Int, v : String) -> Unit

Write a string field (UTF-8 encoded).

PbWriter::to_bytes

fn PbWriter::to_bytes(self : PbWriter) -> Bytes

The bytes written so far.

PbWriter::uint32

fn PbWriter::uint32(self : PbWriter, field : Int, v : UInt) -> Unit

Write a uint32 field.

PbWriter::uint64

fn PbWriter::uint64(self : PbWriter, field : Int, v : UInt64) -> Unit

Write a uint64 field.

PbWriter::write_fixed32

fn PbWriter::write_fixed32(self : PbWriter, v : UInt) -> Unit

Append a little-endian 32-bit fixed value (wire type 5, no tag).

PbWriter::write_fixed64

fn PbWriter::write_fixed64(self : PbWriter, v : UInt64) -> Unit

Append a little-endian 64-bit fixed value (wire type 1, no tag).

PbWriter::write_len_delim

fn PbWriter::write_len_delim(self : PbWriter, body : Bytes) -> Unit

Append a length-delimited body: a varint length then the raw bytes (wire type 2, no tag).

PbWriter::write_tag

fn PbWriter::write_tag(self : PbWriter, field : Int, wire : WireType) -> Unit

Append a field tag: (field_number << 3) | wire_type, itself a varint.

PbWriter::write_varint

fn PbWriter::write_varint(self : PbWriter, value : UInt64) -> Unit

Append a base-128 varint (protobuf "Base 128 Varints"): seven bits per octet, little-endian groups, the high bit marking continuation.

Priority

pub(all) struct Priority {
exclusive : Bool
stream_dependency : Int
weight : Int
} derive(Eq)

The stream-priority block shared by PRIORITY frames and the optional priority section of HEADERS (RFC 7540 §5.3.2). weight is the raw wire byte (0..255); the effective priority weight is weight + 1 (§6.3).

ReflectionRequest

pub(all) enum ReflectionRequest {
ListServices(String)
FileByFilename(String)
FileContainingSymbol(String)
Unsupported
} derive(Eq)

A decoded ServerReflectionRequest, reduced to the one message_request oneof arm that was set. Extension-number queries are surfaced as Unsupported, which the service answers with an UNIMPLEMENTED error response.

ReflectionResponse

pub(all) enum ReflectionResponse {
ServiceList(Array[String])
FileDescriptors(Array[Bytes])
ReflectionError(Int, String)
} derive(Eq)

A decoded ServerReflectionResponse, reduced to the one message_response arm.

ReflectionService

pub struct ReflectionService {
files : Array[FileDescriptor]
by_symbol : Map[String, Int]
by_filename : Map[String, Int]
services : Array[String]
}

A grpc.reflection.v1.ServerReflection service backed by an in-memory descriptor database. Each added FileDescriptor is indexed by filename and by every symbol (package.Service / package.Message) it defines, so a FileContainingSymbol or FileByFilename query resolves to the right file, and ListServices enumerates every registered service.

ReflectionService::add_file

fn ReflectionService::add_file(self : ReflectionService, file : FileDescriptor) -> Unit

Register a file descriptor: index it by filename and by each symbol it defines, and add its services to the ListServices set.

ReflectionService::handle

fn ReflectionService::handle(self : ReflectionService, request : Bytes) -> Bytes

Answer one ServerReflectionRequest (raw bytes) with the encoded ServerReflectionResponse. A decode failure or an unknown symbol/filename yields an ErrorResponse rather than raising, since it rides a non-raising stream handler.

ReflectionService::handler

The bidi handler backing ServerReflectionInfo: one response per request, none at half-close.

ReflectionService::install

fn ReflectionService::install(self : ReflectionService, server : H2Server) -> Unit

Register ServerReflectionInfo at both the v1 and v1alpha paths on a pure server engine.

ReflectionService::new

An empty reflection service. Add the descriptors of the services you serve with add_file.

RetryPolicy

pub(all) struct RetryPolicy {
max_attempts : Int
initial_backoff_millis : Int
max_backoff_millis : Int
backoff_multiplier : Double
retryable : Array[Int]
}

A method's retry policy. max_attempts counts the first try plus every retry (gRPC caps it at 5); a call is retried only while attempts remain and its status is in retryable. Backoff starts at initial_backoff_millis and grows by backoff_multiplier each retry, capped at max_backoff_millis.

RetryPolicy::backoff_millis

fn RetryPolicy::backoff_millis(self : RetryPolicy, retry_index : Int) -> Int

The backoff cap before the retry_index-th retry (the first retry is 1): min(initial * multiplier^(retry_index-1), max). gRPC sleeps a value drawn uniformly from [0, cap]; the async driver applies that jitter, so this returns the deterministic upper bound (which is also what a jitter-free driver sleeps).

RetryPolicy::default

fn RetryPolicy::default() -> RetryPolicy

A conventional default: up to 3 attempts, 100 ms initial backoff doubling to a 1 s cap, retrying only UNAVAILABLE — the code a transient, safe-to-retry transport failure carries.

RetryPolicy::should_retry

fn RetryPolicy::should_retry(self : RetryPolicy, status_code : Int, attempts_made : Int) -> Bool

Whether a call that has made attempts_made attempts (the first is 1) and got status_code should be tried again: attempts must remain, and the status must be non-OK and listed as retryable.

RpcContext

pub(all) struct RpcContext {
path : String
metadata : Array[Header]
deadline_millis : Int?
resp_headers : Array[Header]
resp_trailers : Array[Header]
status_fail : (Int, String)?
error_details : Array[Bytes]
}

The context surfaced to a handler for one RPC call: the invoked :path, the request metadata (custom HEADERS, minus the pseudo- and reserved gRPC headers), the deadline parsed from grpc-timeout (in milliseconds, None when absent), and mutable slots for response initial metadata and trailing metadata the handler can set before it returns.

RpcContext::add_error_detail

fn RpcContext::add_error_detail(self : RpcContext, detail : Bytes) -> Unit

Attach a rich-error detail (a serialized google.protobuf.Any) to a failing call. The server packs the status code, message, and every detail into a google.rpc.Status and sends it base64-encoded in the grpc-status-details-bin trailer — gRPC's rich-error channel. Combine with fail, which sets the base grpc-status / grpc-message.

RpcContext::add_header

fn RpcContext::add_header(self : RpcContext, name : Bytes, value : Bytes) -> Unit

Add an initial-metadata header to the response. Only takes effect if called before the response HEADERS are flushed (any point inside a unary / server- / client-streaming handler, or inside a bidi factory before the first message).

RpcContext::add_trailer

fn RpcContext::add_trailer(self : RpcContext, name : Bytes, value : Bytes) -> Unit

Add a trailing-metadata header, sent in the trailer HEADERS alongside grpc-status.

RpcContext::empty

fn RpcContext::empty() -> RpcContext

A context with no path, metadata, or deadline — the placeholder a stream holds until its request HEADERS are decoded.

RpcContext::fail

fn RpcContext::fail(self : RpcContext, code : Int, message : String) -> Unit

End the call with a non-OK gRPC status. A handler calls this (e.g. ctx.fail(Status::code(NotFound), "user 42 does not exist")) instead of returning a normal reply; the server sends the grpc-status / grpc-message trailer and drops the reply message. The five common codes have named helpers on [Status]; any code is accepted.

RpcContext::metadata_get

fn RpcContext::metadata_get(self : RpcContext, name : Bytes) -> Bytes?

The value of request metadata name, or None. Names are matched byte-for-byte (gRPC lowercases header names on the wire), so binary -bin metadata works too.

ServiceDescriptor

pub(all) struct ServiceDescriptor {
name : String
methods : Array[MethodDescriptor]
} derive(Eq)

A service (ServiceDescriptorProto): its (simple) name and its methods.

ServiceDescriptor::decode

fn ServiceDescriptor::decode(body : Bytes) -> ServiceDescriptor raise PbError

Decode a ServiceDescriptorProto.

ServiceDescriptor::encode

fn ServiceDescriptor::encode(self : ServiceDescriptor) -> Bytes

Encode a ServiceDescriptorProto.

ServingStatus

pub(all) enum ServingStatus {
StatusUnknown
Serving
NotServing
ServiceUnknown
} derive(Eq)

The grpc.health.v1.HealthCheckResponse.ServingStatus enum.

ServingStatus::code

fn ServingStatus::code(self : ServingStatus) -> Int

The wire value of a serving status (the protobuf enum number).

ServingStatus::from_code

fn ServingStatus::from_code(n : Int) -> ServingStatus

The serving status for a protobuf enum number; out-of-range numbers read as StatusUnknown.

SrvStream

pub(all) struct SrvStream {
id : Int
state : StreamState
header_block :
Buffer

data :
Buffer

req_off : Int
req_msgs : Array[Bytes]
bidi_processed : Int
headers_complete : Bool
in_headers : Bool
end_stream_recv : Bool
path : String
ctx : RpcContext
deadline_at : Int64?
recv_window : Int
send_window : Int
out : Bytes
out_off : Int
started : Bool
started_response : Bool
response_ended : Bool
finalized : Bool
trailers_only : Bool
http_status : Int
status_code : Int
status_message : String
req_encoding : Bytes
compressed_unsupported : Bool
oversize : Bool
headers_sent : Bool
trailers_sent : Bool
bidi : BidiHandler?
}

One server-side stream: its lifecycle state, the accumulating request header block and DATA (with a cursor over the length-prefixed messages already pulled out of it), the per-stream flow-control windows, and the response side — the bytes still to send, whether the initial HEADERS and the trailers have gone out, and any live bidi call state.

Status

pub(all) enum Status {
Ok
Cancelled
Unknown
InvalidArgument
DeadlineExceeded
NotFound
AlreadyExists
PermissionDenied
ResourceExhausted
FailedPrecondition
Aborted
OutOfRange
Unimplemented
Internal
Unavailable
DataLoss
Unauthenticated
} derive(Eq)

The 17 canonical gRPC status codes (grpc-status).

Status::code

fn Status::code(self : Status) -> Int

The numeric grpc-status code.

Status::from_h2_error

fn Status::from_h2_error(code : Int) -> Status

The status a call ends with when the peer resets its stream instead of sending trailers, from the RST_STREAM error code (gRPC PROTOCOL-HTTP2, "HTTP2 Error Code → Status"). A refused stream was never processed, so it is UNAVAILABLE and safe to retry; anything the table does not name is INTERNAL, since the peer broke the transport rather than the call.

Status::from_http

fn Status::from_http(code : Int) -> Status

The status a non-200 HTTP response maps to when it carries no grpc-status — a proxy or a plain HTTP server answering on the gRPC port (gRPC http-grpc-status-mapping). Anything outside the table is UNKNOWN.

Status::name

fn Status::name(self : Status) -> String

The canonical uppercase status name.

Stream

pub(all) struct Stream {
id : Int
state : StreamState
}

A mutable stream: its identifier and current lifecycle state. send/recv advance the state in place, raising on an illegal transition.

Stream::new

fn Stream::new(id : Int) -> Stream

A fresh idle stream with the given identifier.

Stream::recv

fn Stream::recv(self : Stream, ev : StreamEvent) -> StreamState raise StreamError

Advance this stream by receiving ev, returning the new state.

Stream::send

fn Stream::send(self : Stream, ev : StreamEvent) -> StreamState raise StreamError

Advance this stream by sending ev, returning the new state.

StreamEvent

pub(all) enum StreamEvent {
Headers(end_stream~ : Bool)
Data(end_stream~ : Bool)
Reserve
RstStream
} derive(Eq)

A state-changing stream event: the arrival or departure of the frames that drive §5.1 transitions. Headers/Data carry the END_STREAM flag; Reserve is a PUSH_PROMISE reserving this (promised) stream. Frames that never change stream state (PRIORITY, WINDOW_UPDATE, SETTINGS, PING) are intentionally absent.

StreamState

pub(all) enum StreamState {
Idle
ReservedLocal
ReservedRemote
Open
HalfClosedLocal
HalfClosedRemote
Closed
} derive(Eq,
Debug
)

The lifecycle state of a single HTTP/2 stream (RFC 7540 §5.1).
impl Show for StreamState

StreamState::on_recv

fn StreamState::on_recv(self : StreamState, ev : StreamEvent) -> StreamState raise StreamError

The next state after receiving ev in this state (RFC 7540 §5.1, remote side — the mirror of on_send). Raises InvalidTransition on an illegal frame.

StreamState::on_send

fn StreamState::on_send(self : StreamState, ev : StreamEvent) -> StreamState raise StreamError

The next state after sending ev from this state (RFC 7540 §5.1, local side). Raises InvalidTransition for a frame illegal in the current state.

SubConn

pub struct SubConn {
address : String
state : ConnectivityState
}

One address the Channel dials, tracking its connectivity state.

SubConn::connect

fn SubConn::connect(self : SubConn) -> Unit

Begin connecting (IDLE or TRANSIENT_FAILURE → CONNECTING). A no-op once SHUTDOWN or already connecting/ready.

SubConn::is_ready

fn SubConn::is_ready(self : SubConn) -> Bool

Whether this sub-connection can carry RPCs right now.

SubConn::new

fn SubConn::new(address : String) -> SubConn

A fresh sub-connection to address, starting IDLE.

SubConn::on_connected

fn SubConn::on_connected(self : SubConn) -> Unit

The connection attempt succeeded (CONNECTING → READY).

SubConn::on_failure

fn SubConn::on_failure(self : SubConn) -> Unit

A connection attempt or an established connection failed (→ TRANSIENT_FAILURE), unless shut down.

SubConn::on_idle

fn SubConn::on_idle(self : SubConn) -> Unit

A READY connection went idle (READY → IDLE).

SubConn::shutdown

fn SubConn::shutdown(self : SubConn) -> Unit

Shut the sub-connection down permanently (→ SHUTDOWN, a terminal state).

WireType

pub(all) enum WireType {
Varint
Fixed64
LengthDelim
Fixed32
} derive(Eq)

The four protobuf wire types carried by a field tag's low three bits. The two group types (3 start-group, 4 end-group) are deprecated and unsupported, so from_code rejects them.
impl Show for WireType

WireType::code

fn WireType::code(self : WireType) -> Int

The wire-type number (the tag's low three bits).

WireType::from_code

fn WireType::from_code(n : Int) -> WireType?

The wire type for a tag's low three bits, or None for the deprecated group types (3/4) and any out-of-range value.

base64_decode

fn base64_decode(data : Bytes) -> Bytes

Standard base64 decode. Non-alphabet bytes (padding, whitespace) are skipped, so both padded and unpadded input decode.

base64_encode

fn base64_encode(data : Bytes) -> Bytes

Standard base64 encode with = padding.

connection_preface

let connection_preface : Bytes

The HTTP/2 client connection preface (RFC 7540 §3.5): the fixed, case-sensitive 24-octet sequence PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n a client sends before its first frame, which must be a SETTINGS frame. Its bytes deliberately form a malformed HTTP/1.1 request so an HTTP/1.1-only server rejects it cleanly.

decode_file_descriptor_set

fn decode_file_descriptor_set(body : Bytes) -> Array[FileDescriptor] raise PbError

Decode a FileDescriptorSet into its files.

decode_frame

fn decode_frame(data : Bytes, offset? : Int) -> (Frame, Int) raise FrameError

Decode exactly one frame at offset, returning (frame, bytes_consumed) where bytes_consumed is 9 + payload_length. Raises Incomplete when the buffer does not yet hold the whole frame, or a protocol error when the payload is malformed for its type. The inverse of Frame::encode.

decode_frame_header

fn decode_frame_header(data : Bytes, offset? : Int) -> FrameHeader raise FrameError

Decode the 9-octet frame header at offset. Raises Incomplete when fewer than 9 octets are available. The reserved bit is masked off the stream id.

decode_health_request

fn decode_health_request(msg : Bytes) -> Bytes

Decode the service field of a HealthCheckRequest, or empty bytes when the field is absent (the overall-server check). Unknown fields are skipped.

decode_health_response

fn decode_health_response(msg : Bytes) -> ServingStatus

Decode the status field of a HealthCheckResponse; an absent field reads as the 0 default (StatusUnknown).

decode_message

fn decode_message(data : Bytes) -> (Bool, Bytes)?

Decode one gRPC length-prefixed message from the front of data, returning (compressed, payload), or None if fewer than a full frame is present.

decode_reflection_request

fn decode_reflection_request(body : Bytes) -> ReflectionRequest raise PbError

Decode a ServerReflectionRequest down to its message_request oneof arm. host (field 1) is accepted and ignored; an unset oneof reads as Unsupported.

decode_reflection_response

fn decode_reflection_response(body : Bytes) -> ReflectionResponse raise PbError

Decode a ServerReflectionResponse down to its message_response arm — the half an in-process reflection client (and the tests) needs to read an answer.

default_max_frame_size

let default_max_frame_size : Int

The HTTP/2 default (and minimum) SETTINGS_MAX_FRAME_SIZE (RFC 7540 §6.5.2).

default_max_streams

let default_max_streams : Int

The SETTINGS_MAX_CONCURRENT_STREAMS the server advertises and enforces unless a caller says otherwise: 100, the value gRPC and nghttp2 both settle on. RFC 9113 §5.1.2 leaves the setting unset by default, which means unbounded — and every live stream holds a request buffer, so a peer that opens streams and never finishes them pins memory for as long as the connection lasts.

default_window_size

let default_window_size : Int

The HTTP/2 default flow-control window and initial SETTINGS_INITIAL_WINDOW_SIZE (RFC 7540 §6.9.2): 65 535 octets.

dns_decode_response

fn dns_decode_response(msg : Bytes) -> DnsResponse

Decode a DNS response: echoed id, response code, and every answer record (A/AAAA records carry their textual address). Questions are skipped; authority and additional sections are ignored.

dns_encode_query

fn dns_encode_query(id : Int, name : String, qtype : Int) -> Bytes

Encode a standard recursion-desired query for name of type qtype (dns_type_a or dns_type_aaaa) in class IN, with transaction id id.

dns_type_a

let dns_type_a : Int

The DNS record type for an IPv4 address.

dns_type_aaaa

let dns_type_aaaa : Int

The DNS record type for an IPv6 address.

encode_file_descriptor_set

fn encode_file_descriptor_set(files : Array[FileDescriptor]) -> Bytes

Encode a FileDescriptorSet (protoc --descriptor_set_out): the concatenation of FileDescriptorProtos under repeated field 1.

encode_grpc_timeout

fn encode_grpc_timeout(millis : Int) -> Bytes

Encode whole millis as a grpc-timeout header value (RFC gRPC HTTP/2 mapping): the m (millisecond) unit when the count fits the 8-digit field, else seconds with the S unit.

encode_health_request

fn encode_health_request(service : Bytes) -> Bytes

Encode a HealthCheckRequest: field 1 (service, a length-delimited string). An empty service name encodes to the empty message, the wire form of the overall-server check.

encode_health_response

fn encode_health_response(status : ServingStatus) -> Bytes

Encode a HealthCheckResponse: field 1 (status, a varint enum). The SERVING default 0 still encodes to the empty message per protobuf default-omission.

encode_message

fn encode_message(payload : Bytes, compressed? : Bool) -> Bytes

Encode a payload as a gRPC Length-Prefixed-Message: a 1-byte compression flag, a 4-byte big-endian length, then the payload. This is the framing every gRPC transport shares (gRPC-Web over HTTP/1.1 and real gRPC over HTTP/2 alike).

encode_reflection_request

fn encode_reflection_request(req : ReflectionRequest) -> Bytes

Encode a ServerReflectionRequest carrying a single oneof arm. Used by an in-process reflection client (and by the round-trip tests).

error_cancel

let error_cancel : Int

CANCEL: the stream is no longer wanted.

error_compression_error

let error_compression_error : Int

COMPRESSION_ERROR: the HPACK context is corrupt, which kills the connection.

error_connect_error

let error_connect_error : Int

CONNECT_ERROR: the TCP connection behind a CONNECT stream failed.

error_enhance_your_calm

let error_enhance_your_calm : Int

ENHANCE_YOUR_CALM: the peer is generating too much load.

error_flow_control_error

let error_flow_control_error : Int

FLOW_CONTROL_ERROR: the peer sent more than its credit allowed.

error_frame_size_error

let error_frame_size_error : Int

FRAME_SIZE_ERROR: the frame's length is invalid for its type.

error_http_1_1_required

let error_http_1_1_required : Int

HTTP_1_1_REQUIRED: the request cannot be served over HTTP/2.

error_inadequate_security

let error_inadequate_security : Int

INADEQUATE_SECURITY: the transport does not meet the minimum this endpoint requires.

error_internal_error

let error_internal_error : Int

INTERNAL_ERROR: the endpoint failed for reasons of its own.

error_no_error

let error_no_error : Int

HTTP/2 error codes (RFC 7540 §7), carried by RST_STREAM and GOAWAY.

error_protocol_error

let error_protocol_error : Int

PROTOCOL_ERROR: the peer broke the protocol in a way not covered below.

error_refused_stream

let error_refused_stream : Int

REFUSED_STREAM: the stream was declined before any processing, so it is safe to retry.

error_settings_timeout

let error_settings_timeout : Int

SETTINGS_TIMEOUT: a SETTINGS frame went unacknowledged too long.

error_stream_closed

let error_stream_closed : Int

STREAM_CLOSED: a frame arrived for a stream that was already finished.

flag_ack

let flag_ack : Int

ACK on SETTINGS and PING shares bit 0x1 with END_STREAM.

flag_end_headers

let flag_end_headers : Int

END_HEADERS: no CONTINUATION follows this header block.

flag_end_stream

let flag_end_stream : Int

HTTP/2 frame flags (RFC 7540 §6). Flags are type-specific; the same bit carries different meaning per frame type, hence the shared numeric values.

flag_padded

let flag_padded : Int

PADDED: a length byte and that many padding bytes wrap the payload.

flag_priority

let flag_priority : Int

PRIORITY: this HEADERS frame carries a stream-dependency block.

frame_continuation

let frame_continuation : Int

CONTINUATION: the rest of a header block too large for one frame.

frame_data

let frame_data : Int

HTTP/2 frame type codes (RFC 7540 §6).

frame_goaway

let frame_goaway : Int

GOAWAY: stop opening streams, and here is the last id I accepted.

frame_headers

let frame_headers : Int

HEADERS: opens a stream and carries its HPACK header block.

frame_ping

let frame_ping : Int

PING: the round-trip probe keepalive is built on.

frame_priority

let frame_priority : Int

PRIORITY: the deprecated stream-dependency hint; accepted and ignored.

frame_push_promise

let frame_push_promise : Int

PUSH_PROMISE: server push, which gRPC never uses.

frame_rst_stream

let frame_rst_stream : Int

RST_STREAM: abort one stream without touching the connection.

frame_settings

let frame_settings : Int

SETTINGS: the connection parameters each peer announces, and their ack.

frame_window_update

let frame_window_update : Int

WINDOW_UPDATE: hand the peer more flow-control credit.

gunzip

fn gunzip(data : Bytes) -> Bytes raise GzError

Decode a single gzip member (RFC 1952): validate the 10-byte header, skip the optional EXTRA / NAME / COMMENT / HCRC fields, inflate the DEFLATE body, and verify the trailing CRC-32 and ISIZE. Raises [GzError] on any malformation.

has_connection_preface

fn has_connection_preface(data : Bytes) -> Bool

Whether data begins with the exact 24-octet HTTP/2 connection preface.

health_check_path

let health_check_path : String

The gRPC HTTP/2 path of the Check method.

health_watch_path

let health_watch_path : String

The gRPC HTTP/2 path of the Watch method.

hpack_decode_int

fn hpack_decode_int(data : Bytes, offset : Int, prefix_bits : Int) -> (Int, Int) raise HpackError

Decode an HPACK integer with an prefix_bits-bit prefix from data starting at offset (RFC 7541 §5.1), returning (value, bytes_consumed). Any flag bits above the prefix in the first octet are masked off and ignored.

hpack_decode_string

fn hpack_decode_string(data : Bytes, offset : Int) -> (Bytes, Int) raise HpackError

Decode an HPACK string literal from data at offset, returning (octets,bytes_consumed). The length is read as a 7-bit-prefix integer (the H bit is masked off); this is the inverse of hpack_encode_string for H = 0. Use hpack_string_is_huffman first if the literal may be Huffman-coded, as Huffman decoding is not applied here.

hpack_encode_int

fn hpack_encode_int(value : Int, prefix_bits : Int) -> Bytes

Encode value as an HPACK integer with an prefix_bits-bit prefix (RFC 7541 §5.1). The high 8 - prefix_bits bits of the first octet are left zero for the caller to OR in any flag bits. Examples: 10 on a 5-bit prefix is [0x0A]; 1337 on a 5-bit prefix is [0x1F, 0x9A, 0x0A].

hpack_encode_size_update

fn hpack_encode_size_update(new_max : Int) -> Bytes

Encode a dynamic table size update (RFC 7541 §6.3): 001 prefix with the new maximum size as a 5-bit-prefix integer.

hpack_encode_string

fn hpack_encode_string(octets : Bytes) -> Bytes

Encode octets as a non-Huffman HPACK string literal (RFC 7541 §5.2): the length as a 7-bit-prefix integer with the H bit clear, followed by the raw octets.

hpack_encode_string_auto

fn hpack_encode_string_auto(octets : Bytes) -> Bytes

Encode octets as an HPACK string literal, choosing the shorter of the raw (H = 0) and Huffman (H = 1) forms — the standard encoder heuristic.

hpack_encode_string_huffman

fn hpack_encode_string_huffman(octets : Bytes) -> Bytes

Encode octets as a Huffman-coded HPACK string literal (RFC 7541 §5.2): the H bit set, the Huffman length as a 7-bit-prefix integer, then the code.

hpack_read_string

fn hpack_read_string(data : Bytes, offset : Int) -> (Bytes, Int) raise HpackError

Read an HPACK string literal at offset, resolving Huffman coding when the H bit is set, returning (octets, bytes_consumed). Unlike hpack_decode_string, this applies Huffman decoding. Raises on bad Huffman.

hpack_static_entry

fn hpack_static_entry(index : Int) -> (String, String)?

Look up an HPACK static-table entry by its 1-based RFC index (1..=61), returning (name, value) or None when the index is out of range.

hpack_static_table

fn hpack_static_table() -> Array[(String, String)]

The 61-entry HPACK static header table (RFC 7541, Appendix A) as (name,value) pairs in RFC index order, i.e. result[0] is index 1 (:authority) and result[60] is index 61 (www-authenticate).

hpack_string_is_huffman

fn hpack_string_is_huffman(data : Bytes, offset : Int) -> Bool

Whether the string literal at offset is Huffman-coded, i.e. the H bit (the top bit of the length octet) is set (RFC 7541 §5.2).

huffman_decode

fn huffman_decode(input : Bytes) -> Bytes raise HpackError

Huffman-decode input (RFC 7541 §5.2). Raises HuffmanError if the input contains the EOS symbol, if the trailing padding is not a run of fewer than 8 one-bits, or if the bit stream leaves the code space. The inverse of huffman_encode.

huffman_encode

fn huffman_encode(input : Bytes) -> Bytes

Huffman-encode input (RFC 7541 §5.2): each octet becomes its code, and the final partial octet is padded with the most-significant bits of the EOS code (all ones). The inverse of huffman_decode for valid inputs.

huffman_encoded_length

fn huffman_encoded_length(input : Bytes) -> Int

The number of octets input occupies when Huffman-encoded, without building the output — used to choose the shorter of raw vs. Huffman string literals.

is_binary_metadata

fn is_binary_metadata(name : Bytes) -> Bool

Whether a metadata key names binary content: it ends in -bin (gRPC's convention for base64-on-the-wire values).

max_header_list_size

let max_header_list_size : Int

The cap on one accumulated header block (HEADERS plus its CONTINUATION frames). Without it a peer could stream endless non-final CONTINUATION frames and grow the buffer without bound (a CONTINUATION flood); 128 KiB is far above any real gRPC request's headers.

max_message_size

let max_message_size : Int

The default cap on a single received gRPC message (4 MiB, matching gRPC's default MaxRecvMsgSize). A length prefix above this — including one whose 4 bytes decode to a negative Int because the high bit is set — is rejected rather than trusted, so a hostile prefix can neither slice out of bounds nor pin unbounded buffer.

metadata_value_from_wire

fn metadata_value_from_wire(name : Bytes, value : Bytes) -> Bytes

Decode a metadata value coming off the wire: base64-decoded for a -bin key, verbatim otherwise.

metadata_value_to_wire

fn metadata_value_to_wire(name : Bytes, value : Bytes) -> Bytes

Encode a metadata value for the wire: base64-encoded for a -bin key, verbatim otherwise.

parse_grpc_timeout

fn parse_grpc_timeout(v : Bytes) -> Int?

Parse a grpc-timeout value (RFC: up to 8 ASCII digits then a unit — H/M/S/m/u/n) to whole milliseconds, flooring sub-millisecond units. None for a malformed value.

percent_decode

fn percent_decode(v : Bytes) -> String

Percent-decode a received grpc-message value — the inverse of the server's percent_encode (gRPC spec §"Responses"): each %XX escape becomes the byte its two hex digits name, every other byte passes through, and a malformed escape is left literal; the result is read as UTF-8.

reflection_v1_path

let reflection_v1_path : String

The gRPC HTTP/2 path of the v1 reflection stream.

reflection_v1alpha_path

let reflection_v1alpha_path : String

The gRPC HTTP/2 path of the legacy v1alpha reflection stream. grpcurl tries v1 first and falls back to this, so a server registers both.

settings_enable_push

let settings_enable_push : Int

SETTINGS_ENABLE_PUSH: whether the peer may send PUSH_PROMISE.

settings_header_table_size

let settings_header_table_size : Int

SETTINGS parameter identifiers (RFC 7540 §6.5.2).

settings_initial_window_size

let settings_initial_window_size : Int

SETTINGS_INITIAL_WINDOW_SIZE: the flow-control credit a new stream starts with.

settings_max_concurrent_streams

let settings_max_concurrent_streams : Int

SETTINGS_MAX_CONCURRENT_STREAMS: how many streams may be open at once.

settings_max_frame_size

let settings_max_frame_size : Int

SETTINGS_MAX_FRAME_SIZE: the largest payload the peer will accept in one frame.

settings_max_header_list_size

let settings_max_header_list_size : Int

SETTINGS_MAX_HEADER_LIST_SIZE: the advisory ceiling on a decoded header list.

stream_id_valid_for_initiator

fn stream_id_valid_for_initiator(id : Int, by_client~ : Bool) -> Bool

Whether a peer that is a client (by_client = true) or server may legally open stream id: clients use odd ids, servers use even ids, and 0 is the connection control stream, openable by neither (RFC 7540 §5.1.1).

stream_is_client_initiated

fn stream_is_client_initiated(id : Int) -> Bool

Whether id is a client-initiated stream: a non-zero odd identifier.

stream_is_server_initiated

fn stream_is_server_initiated(id : Int) -> Bool

Whether id is a server-initiated (pushed) stream: a non-zero even identifier.