protocol

package
v0.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 5, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package protocol defines the wire format spoken between the vrok CLI and the vrok relay.

The design rule is that the relay is a router, not a store: it forwards HTTP semantics verbatim and never holds a file. Requests and responses keep their methods, headers, status codes and Range handling, so a browser talking to the relay behaves exactly as it would talking to the CLI directly — which is what preserves video seeking, resumable downloads and caching.

Index

Constants

View Source
const AgentPath = "/_vrok/agent"

AgentPath is the relay endpoint where agents open their tunnel.

View Source
const MaxFrameData = 32 << 10

MaxFrameData is the largest payload carried in one frame. 32 KiB matches the buffer io.Copy uses, so bodies are forwarded without extra copying or re-chunking.

View Source
const MaxMessageSize = 4 << 20

MaxMessageSize caps one inbound WebSocket message. Body frames are at most MaxFrameData plus a header; the largest control message is a request envelope carrying a visitor's headers, which net/http already bounds at 1 MiB before JSON escaping. Without a cap, gorilla/websocket buffers a message of any size, so one peer could exhaust the other's memory with a single frame.

View Source
const Version = 1

Version is the protocol revision. The relay rejects agents it cannot speak to rather than guessing.

Variables

View Source
var ErrClosed = errors.New("protocol: connection closed")

ErrClosed is returned once a connection has been closed.

View Source
var ErrShortFrame = errors.New("protocol: binary frame shorter than its header")

ErrShortFrame reports a binary frame that is too small to be valid.

View Source
var ErrStreamClosed = errors.New("protocol: stream closed")

ErrStreamClosed reports a write to a stream nobody will read.

Functions

func Encode

func Encode(t Type, payload any) ([]byte, error)

Encode marshals a control message into an envelope.

func EncodeFrame

func EncodeFrame(f Frame) []byte

EncodeFrame serialises a frame.

func IsExpectedClose

func IsExpectedClose(err error) bool

IsExpectedClose reports whether err is a normal end of connection rather than a fault worth logging.

func Payload

func Payload[T any](e Envelope) (T, error)

Payload decodes an envelope's payload into T.

Types

type BodyStream

type BodyStream struct {
	// contains filtered or unexported fields
}

BodyStream is the receiving half of one body stream: a bounded queue of frames exposed as an io.Reader.

Both ends of the tunnel need it — the relay to assemble response bodies, the agent to assemble request bodies — so it lives with the protocol rather than being written twice.

An io.Pipe would also work but has no buffer at all, so the shared connection read loop would have to hand over every chunk synchronously and would stall on each one.

func NewBodyStream

func NewBodyStream() *BodyStream

NewBodyStream returns an empty body stream.

func (*BodyStream) Close

func (b *BodyStream) Close(err error)

Close ends the stream. A nil error means a normal end of body; any other error surfaces from Read, so the consumer can tell a truncated transfer from a complete one.

func (*BodyStream) Push

func (b *BodyStream) Push(data []byte) error

Push appends a frame's payload. The data is copied because the caller's buffer is reused by the next read from the connection.

func (*BodyStream) Read

func (b *BodyStream) Read(p []byte) (int, error)

Read implements io.Reader.

type Cancel

type Cancel struct {
	Stream uint64 `json:"stream"`
	Reason string `json:"reason,omitempty"`
}

Cancel abandons a stream, typically because a visitor disconnected.

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn is a multiplexed, concurrency-safe view of one WebSocket connection.

A WebSocket allows only one writer at a time, which is easy to violate once several HTTP streams share the connection. Serialising every write behind one mutex here means no caller has to remember that rule.

func NewConn

func NewConn(ws *websocket.Conn) *Conn

NewConn wraps a WebSocket connection.

func (*Conn) Close

func (c *Conn) Close() error

Close shuts the connection down, attempting a clean WebSocket close first so the peer learns this was deliberate rather than a network failure.

func (*Conn) Copy

func (c *Conn) Copy(stream uint64, src io.Reader) error

Copy streams src to the peer as body frames, terminated by an end frame. The end frame is sent even when the read fails, so the receiving side is never left waiting for a stream that will not continue.

func (*Conn) Keepalive

func (c *Conn) Keepalive(ctx context.Context)

Keepalive pings the peer until ctx is cancelled or the connection fails.

func (*Conn) Receive

func (c *Conn) Receive() (*Envelope, *Frame, error)

Receive reads the next message. Exactly one of the results is non-nil: a control envelope or a body frame.

A frame's Data aliases an internal buffer that is reused by the next call, so it must be consumed or copied before reading again.

func (*Conn) Send

func (c *Conn) Send(t Type, payload any) error

Send writes a control message.

func (*Conn) SendFrame

func (c *Conn) SendFrame(f Frame) error

SendFrame writes one body frame. Data longer than MaxFrameData must be split by the caller, or sent with Copy.

type Envelope

type Envelope struct {
	Type    Type            `json:"type"`
	Payload json.RawMessage `json:"payload,omitempty"`
}

Envelope wraps a control message with its type so the receiver can decode the payload into the right struct.

func Decode

func Decode(data []byte) (Envelope, error)

Decode unmarshals an envelope.

type Error

type Error struct {
	Stream  uint64 `json:"stream,omitempty"`
	Code    string `json:"code,omitempty"`
	Message string `json:"message"`
}

Error reports a failure. A zero Stream means the whole connection failed.

type Frame

type Frame struct {
	// Stream matches the Request that opened this body stream.
	Stream uint64
	// End marks the last frame; a frame may be both final and empty.
	End bool
	// Data is the payload, at most MaxFrameData bytes.
	Data []byte
}

Frame is one chunk of a request or response body.

Bodies travel as binary frames rather than inside JSON because base64 would inflate every byte of every download by a third, and because a frame can be forwarded straight into an io.Writer without re-encoding.

func DecodeFrame

func DecodeFrame(buf []byte) (Frame, error)

DecodeFrame parses a frame. The returned Data aliases the input buffer, so callers that retain it past the read loop must copy it.

type Register

type Register struct {
	Version int `json:"version"`
	// ShareID becomes the hostname label, e.g. "a82kd9" in
	// https://a82kd9.example.com.
	ShareID string `json:"share_id"`
	// Token is the share secret. The relay never inspects it; it is echoed
	// back so the agent can confirm the relay is talking about its share.
	Token string `json:"token"`
	// Auth is an optional relay credential for private deployments.
	Auth string `json:"auth,omitempty"`
	// Agent identifies the client build, for diagnostics.
	Agent string `json:"agent,omitempty"`
}

Register claims a hostname label on the relay.

type Registered

type Registered struct {
	// URL is the public origin the share is now reachable on.
	URL string `json:"url"`
	// Hostname is the full hostname the relay assigned.
	Hostname string `json:"hostname"`
}

Registered confirms a successful registration.

type Request

type Request struct {
	// Stream correlates this request with its body and response frames.
	Stream uint64 `json:"stream"`
	Method string `json:"method"`
	// URI is the request target including the query string, exactly as the
	// visitor sent it.
	URI    string              `json:"uri"`
	Header map[string][]string `json:"header,omitempty"`
	// RemoteAddr is the visitor's address, forwarded for logging only.
	RemoteAddr string `json:"remote_addr,omitempty"`
	// HasBody tells the agent whether to expect body frames.
	HasBody bool `json:"has_body"`
}

Request describes an inbound HTTP request the relay wants served.

type Response

type Response struct {
	Stream uint64              `json:"stream"`
	Status int                 `json:"status"`
	Header map[string][]string `json:"header,omitempty"`
}

Response carries the status line and headers of a reply.

type Type

type Type string

Type identifies a control message.

const (
	// TypeRegister claims a hostname. Agent to relay, first message.
	TypeRegister Type = "register"
	// TypeRegistered confirms the claim and reports the public URL.
	TypeRegistered Type = "registered"
	// TypeRequest starts a proxied request. Relay to agent.
	TypeRequest Type = "request"
	// TypeResponse returns status and headers. Agent to relay.
	TypeResponse Type = "response"
	// TypeCancel abandons a stream. Either direction.
	TypeCancel Type = "cancel"
	// TypeError reports a failure, either fatal to the connection or scoped
	// to one stream.
	TypeError Type = "error"
)

Control message types.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL