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
moon add Lfan-ke/moonrpc@0.8.1
Download zip
Author
Version
0.8.1
License
Apache-2.0
Last updated
16 days ago
Downloads
411

Dependencies

README

#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.

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
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?
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
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
}

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::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::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::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
goaway_received : Bool
last_client_stream : 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::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::feed

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

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). Raises on an illegal stream transition or a malformed header block.

#
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.

#
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::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. 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::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.

#
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
end_stream_recv : Bool
path : String
ctx : RpcContext
recv_window : Int
send_window : Int
out : Bytes
out_off : Int
started : Bool
started_response : Bool
response_ended : Bool
finalized : Bool
trailers_only : Bool
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::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_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

#
error_compression_error

let error_compression_error : Int

#
error_connect_error

let error_connect_error : Int

#
error_enhance_your_calm

let error_enhance_your_calm : Int

#
error_flow_control_error

let error_flow_control_error : Int

#
error_frame_size_error

let error_frame_size_error : Int

#
error_http_1_1_required

let error_http_1_1_required : Int

#
error_inadequate_security

let error_inadequate_security : Int

#
error_internal_error

let error_internal_error : Int

#
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

#
error_refused_stream

let error_refused_stream : Int

#
error_settings_timeout

let error_settings_timeout : Int

#
error_stream_closed

let error_stream_closed : Int

#
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

#
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

#
flag_priority

let flag_priority : Int

#
frame_continuation

let frame_continuation : Int

#
frame_data

let frame_data : Int

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

#
frame_goaway

let frame_goaway : Int

#
frame_headers

let frame_headers : Int

#
frame_ping

let frame_ping : Int

#
frame_priority

let frame_priority : Int

#
frame_push_promise

let frame_push_promise : Int

#
frame_rst_stream

let frame_rst_stream : Int

#
frame_settings

let frame_settings : Int

#
frame_window_update

let frame_window_update : Int

#
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.

#
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_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_max_concurrent_streams

let settings_max_concurrent_streams : Int

#
settings_max_frame_size

let settings_max_frame_size : Int

#
settings_max_header_list_size

let settings_max_header_list_size : Int

#
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.