Documentation
¶
Overview ¶
Package protocol implements the binary wire protocol for Vango V2.
The protocol is optimized for minimal bandwidth and fast encoding/decoding. It defines how events flow from client to server and patches flow from server to client over WebSocket connections.
Design Goals ¶
- Minimal size: Typical event < 10 bytes, typical patch < 20 bytes
- Fast encoding/decoding: No reflection, direct byte manipulation
- Reliable delivery: Sequence numbers, acknowledgments
- Reconnection: Resync capability after disconnect
- Extensible: Version negotiation, reserved opcodes
Wire Format ¶
All messages are framed with a 4-byte header:
┌─────────────┬──────────────┬───────────────────────────────┐ │ Frame Type │ Flags │ Payload Length │ │ (1 byte) │ (1 byte) │ (2 bytes, big-endian) │ └─────────────┴──────────────┴───────────────────────────────┘
Frame Types ¶
- FrameHandshake (0x00): Connection setup
- FrameEvent (0x01): Client → Server events
- FramePatches (0x02): Server → Client patches
- FrameControl (0x03): Control messages (ping, resync)
- FrameAck (0x04): Acknowledgment
- FrameError (0x05): Error message
Encoding ¶
The protocol uses several encoding strategies:
- Varint: Compact encoding for small integers (protobuf-style)
- ZigZag: Signed integers encoded as unsigned varints
- Length-prefixed: Strings and byte arrays prefixed with varint length
- Big-endian: Fixed-width integers (uint16, uint32, uint64)
Events ¶
Events are sent from client to server when user interactions occur. Each event includes a sequence number, event type, hydration ID (HID), and type-specific payload.
Example click event encoding:
[Seq: varint][Type: 0x01][HID: len-prefixed string] Total: ~5 bytes for "h1"
Patches ¶
Patches are sent from server to client to update the DOM. Each patch includes an operation type, target HID, and operation-specific data.
Example SetText patch encoding:
[Op: 0x01][HID: len-prefixed][Value: len-prefixed] Total: ~15 bytes for updating "h1" with "Hello"
Handshake ¶
Connection establishment uses ClientHello and ServerHello messages:
Client Server │ │ │──── ClientHello ─────────────>│ │ (version, csrf, session) │ │ │ │<──── ServerHello ─────────────│ │ (status, session, time) │ │ │
Error ServerHello messages may include an optional auth-expired reason byte.
Control Messages ¶
- Ping/Pong: Heartbeat for connection health
- ResyncRequest: Client requests missed patches after reconnect
- ResyncPatches/ResyncFull: Server response with missed data
- SessionRefresh: Server notifies client of schema refresh
- SessionRefreshAck: Client acknowledges session refresh
- Close: Graceful session termination
Usage Example ¶
// Encode an event
event := &Event{
Seq: 1,
Type: EventClick,
HID: "h42",
}
data := EncodeEvent(event)
// Decode an event
decoded, err := DecodeEvent(data)
if err != nil {
// Handle error
}
// Encode patches
pf := &PatchesFrame{
Seq: 1,
Patches: []Patch{
NewSetTextPatch("h1", "Hello, World!"),
NewSetAttrPatch("h2", "class", "active"),
},
}
data = EncodePatches(pf)
// Decode patches
decoded, err := DecodePatches(data)
Performance ¶
Target metrics:
- Event encode/decode: < 500ns
- Patch encode/decode: < 500ns
- 100 patches encode: < 50μs
- 100 patches decode: < 50μs
File Structure ¶
The package is organized as follows:
- varint.go: Varint encoding/decoding
- encoder.go: Binary encoder
- decoder.go: Binary decoder
- frame.go: Frame types and transport
- event.go: Event types and encoding
- patch.go: Patch types and encoding
- vnode.go: VNode wire format
- handshake.go: Handshake protocol
- control.go: Control messages
- ack.go: Acknowledgment
- error.go: Error messages
Index ¶
- Constants
- Variables
- func CompareProtocolVersion(a, b ProtocolVersion) int
- func DecodeFrameHeader(data []byte) (FrameType, FrameFlags, int, error)
- func DecodeSvarint(buf []byte) (int64, int)
- func DecodeUvarint(buf []byte) (uint64, int)
- func EncodeAck(ack *Ack) []byte
- func EncodeAckTo(e *Encoder, ack *Ack)
- func EncodeClientHello(ch *ClientHello) []byte
- func EncodeClientHelloTo(e *Encoder, ch *ClientHello)
- func EncodeControl(ct ControlType, payload any) []byte
- func EncodeControlTo(e *Encoder, ct ControlType, payload any)
- func EncodeErrorMessage(em *ErrorMessage) []byte
- func EncodeErrorMessageTo(e *Encoder, em *ErrorMessage)
- func EncodeEvent(e *Event) []byte
- func EncodeEventTo(enc *Encoder, e *Event)
- func EncodePatches(pf *PatchesFrame) []byte
- func EncodePatchesTo(e *Encoder, pf *PatchesFrame)
- func EncodeServerHello(sh *ServerHello) []byte
- func EncodeServerHelloTo(e *Encoder, sh *ServerHello)
- func EncodeSvarint(buf []byte, v int64) int
- func EncodeUvarint(buf []byte, v uint64) int
- func EncodeVNodeWire(e *Encoder, node *VNodeWire)
- func NewAuthCommand(cmd *AuthCommand) (ControlType, *AuthCommand)
- func NewClose(reason CloseReason, message string) (ControlType, *CloseMessage)
- func NewHookRevert(hid string) (ControlType, *HookRevert)
- func NewPing(timestamp uint64) (ControlType, *PingPong)
- func NewPong(timestamp uint64) (ControlType, *PingPong)
- func NewResyncFull(html string) (ControlType, *ResyncResponse)
- func NewResyncPatches(fromSeq uint64, patches []Patch) (ControlType, *ResyncResponse)
- func NewResyncRequest(lastSeq uint64) (ControlType, *ResyncRequest)
- func NewSessionRefresh(cmd *SessionRefreshCommand) (ControlType, *SessionRefreshCommand)
- func NewSessionRefreshAck(source string) (ControlType, *SessionRefreshAck)
- func SvarintLen(v int64) int
- func UvarintLen(v uint64) int
- func WriteFrame(w io.Writer, f *Frame) error
- type Ack
- type AnimationEventData
- type AuthAction
- type AuthCommand
- type BannerSpec
- type ClientHello
- type CloseMessage
- type CloseReason
- type ControlType
- type CustomEventData
- type Decoder
- func (d *Decoder) EOF() bool
- func (d *Decoder) Position() int
- func (d *Decoder) ReadBool() (bool, error)
- func (d *Decoder) ReadByte() (byte, error)
- func (d *Decoder) ReadBytes(n int) ([]byte, error)
- func (d *Decoder) ReadCollectionCount() (int, error)
- func (d *Decoder) ReadFloat32() (float32, error)
- func (d *Decoder) ReadFloat64() (float64, error)
- func (d *Decoder) ReadInt16() (int16, error)
- func (d *Decoder) ReadInt32() (int32, error)
- func (d *Decoder) ReadInt64() (int64, error)
- func (d *Decoder) ReadLenBytes() ([]byte, error)
- func (d *Decoder) ReadString() (string, error)
- func (d *Decoder) ReadSvarint() (int64, error)
- func (d *Decoder) ReadUint16() (uint16, error)
- func (d *Decoder) ReadUint32() (uint32, error)
- func (d *Decoder) ReadUint64() (uint64, error)
- func (d *Decoder) ReadUvarint() (uint64, error)
- func (d *Decoder) Remaining() int
- func (d *Decoder) Skip(n int) error
- type DepthLimits
- type Encoder
- func (e *Encoder) Bytes() []byte
- func (e *Encoder) Len() int
- func (e *Encoder) Reset()
- func (e *Encoder) WriteBool(b bool)
- func (e *Encoder) WriteByte(b byte) error
- func (e *Encoder) WriteByteUnsafe(b byte)
- func (e *Encoder) WriteBytes(b []byte)
- func (e *Encoder) WriteFloat32(v float32)
- func (e *Encoder) WriteFloat64(v float64)
- func (e *Encoder) WriteInt16(v int16)
- func (e *Encoder) WriteInt32(v int32)
- func (e *Encoder) WriteInt64(v int64)
- func (e *Encoder) WriteLenBytes(b []byte)
- func (e *Encoder) WriteString(s string)
- func (e *Encoder) WriteSvarint(v int64)
- func (e *Encoder) WriteUint16(v uint16)
- func (e *Encoder) WriteUint32(v uint32)
- func (e *Encoder) WriteUint64(v uint64)
- func (e *Encoder) WriteUvarint(v uint64)
- type ErrorCode
- type ErrorMessage
- type Event
- type EventType
- type Frame
- type FrameFlags
- type FrameType
- type HandshakeStatus
- type HookEventData
- type HookRevert
- type HookValueType
- type InputEventData
- type KeyboardEventData
- type Modifiers
- type MouseEventData
- type NavigateEventData
- type Patch
- func NewAddClassPatch(hid, class string) Patch
- func NewBlurPatch(hid string) Patch
- func NewDispatchPatch(hid, eventName, detail string) Patch
- func NewFocusPatch(hid string) Patch
- func NewInsertNodePatch(hid, parentID string, index int, node *VNodeWire) Patch
- func NewMoveNodePatch(hid, parentID string, index int) Patch
- func NewNavPushPatch(path string) Patch
- func NewNavReplacePatch(path string) Patch
- func NewRemoveAttrPatch(hid, key string) Patch
- func NewRemoveClassPatch(hid, class string) Patch
- func NewRemoveNodeAtPatch(parentID string, index int) Patch
- func NewRemoveNodePatch(hid string) Patch
- func NewRemoveStylePatch(hid, property string) Patch
- func NewReplaceNodeAtPatch(parentID string, index int, node *VNodeWire) Patch
- func NewReplaceNodePatch(hid string, node *VNodeWire) Patch
- func NewScrollToPatch(hid string, x, y int, behavior ScrollBehavior) Patch
- func NewSetAttrPatch(hid, key, value string) Patch
- func NewSetCheckedPatch(hid string, checked bool) Patch
- func NewSetDataPatch(hid, key, value string) Patch
- func NewSetSelectedPatch(hid string, selected bool) Patch
- func NewSetStylePatch(hid, property, value string) Patch
- func NewSetTextAtPatch(parentID string, index int, text string) Patch
- func NewSetTextPatch(hid, text string) Patch
- func NewSetValuePatch(hid, value string) Patch
- func NewToggleClassPatch(hid, class string) Patch
- func NewURLPushPatch(params map[string]string) Patch
- func NewURLReplacePatch(params map[string]string) Patch
- type PatchOp
- type PatchesFrame
- type PingPong
- type ProtocolVersion
- type ResizeEventData
- type ResyncRequest
- type ResyncResponse
- type SchemaRefreshReason
- type ScrollBehavior
- type ScrollEventData
- type ServerHello
- func DecodeServerHello(data []byte) (*ServerHello, error)
- func DecodeServerHelloFrom(d *Decoder) (*ServerHello, error)
- func NewServerHello(sessionID string, nextSeq uint32, serverTime uint64) *ServerHello
- func NewServerHelloError(status HandshakeStatus) *ServerHello
- func NewServerHelloErrorWithReason(status HandshakeStatus, reason uint8) *ServerHello
- func NewServerHelloSchemaError(reason SchemaRefreshReason, banner BannerSpec) *ServerHello
- type SessionRefreshAck
- type SessionRefreshCommand
- type SubmitEventData
- type TouchEventData
- type TouchPoint
- type TransitionEventData
- type VNodeWire
- func DecodeVNodeWire(d *Decoder) (*VNodeWire, error)
- func NewElementWire(tag string, attrs map[string]string, children ...*VNodeWire) *VNodeWire
- func NewFragmentWire(children ...*VNodeWire) *VNodeWire
- func NewRawWire(html string) *VNodeWire
- func NewTextWire(text string) *VNodeWire
- func VNodeToWire(node *vdom.VNode) *VNodeWire
- func VNodeToWireWithPolicy(node *vdom.VNode, policy vdom.URLPolicy) *VNodeWire
- type WheelEventData
Constants ¶
const ( // DefaultMaxAllocation is the default maximum allocation size (4MB). // This is sufficient for normal binary patches and events. DefaultMaxAllocation = 4 * 1024 * 1024 // HardMaxAllocation is the absolute ceiling for allocations (16MB). // Even if configured higher, allocations are capped at this limit. HardMaxAllocation = 16 * 1024 * 1024 // MaxCollectionCount is the maximum number of items in a collection (array/map). // This prevents OOM from huge counts with small per-item overhead. MaxCollectionCount = 100_000 )
Allocation limits to prevent DoS attacks via malicious length prefixes.
const ( // FrameHeaderSize is the size of the frame header in bytes. FrameHeaderSize = 4 // MaxPayloadSize is the maximum payload size (2^16 - 1 bytes). MaxPayloadSize = 65535 // MaxFrameSize is the maximum complete frame size in bytes (header + payload). MaxFrameSize = FrameHeaderSize + MaxPayloadSize )
Frame constants.
const ( ServerFlagCompression uint16 = 0x0001 // Server supports compression ServerFlagBinaryBlobs uint16 = 0x0002 // Server supports binary blob uploads ServerFlagStreaming uint16 = 0x0004 // Server supports streaming responses )
Server capability flags.
const ( // MaxVNodeDepth limits the maximum nesting depth of VNode trees. // This prevents stack overflow from maliciously deep component trees. // 256 levels is sufficient for any reasonable component hierarchy. MaxVNodeDepth = 256 // MaxPatchDepth limits the maximum nesting depth of patch structures. // Patches can contain VNodes (InsertNode, ReplaceNode), so this must // account for VNode nesting within patches. MaxPatchDepth = 128 )
Depth limits to prevent stack overflow attacks via deeply nested structures. These limits complement the allocation limits in decoder.go.
const DefaultWindow = 100
DefaultWindow is the default receive window size.
const MaxHookDepth = 64
MaxHookDepth is the maximum nesting depth for hook values. Prevents stack overflow from maliciously deeply nested payloads.
const MaxVarintLen = 10
MaxVarintLen is the maximum number of bytes a varint can occupy. A uint64 requires at most 10 bytes in varint encoding.
Variables ¶
var ( ErrBufferTooShort = errors.New("protocol: buffer too short") ErrVarintOverflow = errors.New("protocol: varint overflow") ErrInvalidBool = errors.New("protocol: invalid boolean value") ErrAllocationTooLarge = errors.New("protocol: allocation size exceeds limit") ErrCollectionTooLarge = errors.New("protocol: collection count exceeds limit") )
Common decoding errors.
var ( ErrInvalidEventType = errors.New("protocol: invalid event type") ErrInvalidPayload = errors.New("protocol: invalid event payload") ErrMaxDepthExceeded = errors.New("protocol: maximum nesting depth exceeded") )
Event encoding errors.
var ( ErrFrameTooLarge = errors.New("protocol: frame payload too large") ErrInvalidFrameType = errors.New("protocol: invalid frame type") ErrTrailingFrameData = errors.New("protocol: trailing bytes after frame payload") )
Frame errors.
var CurrentVersion = ProtocolVersion{Major: 2, Minor: 1}
CurrentVersion is the current protocol version.
Functions ¶
func CompareProtocolVersion ¶
func CompareProtocolVersion(a, b ProtocolVersion) int
CompareProtocolVersion compares two protocol versions lexicographically. It returns:
-1 when a < b 0 when a == b +1 when a > b
func DecodeFrameHeader ¶
func DecodeFrameHeader(data []byte) (FrameType, FrameFlags, int, error)
DecodeFrameHeader decodes just the frame header, returning type, flags, and payload length.
func DecodeSvarint ¶
DecodeSvarint decodes a signed varint using ZigZag decoding. Returns (value, bytesRead). Negative bytesRead indicates error (see DecodeUvarint).
func DecodeUvarint ¶
DecodeUvarint decodes an unsigned varint from buf. Returns (value, bytesRead). If bytesRead < 0, decoding failed:
- -1: buffer too short (incomplete varint)
- -2: varint overflow (more than 10 bytes)
func EncodeAckTo ¶
EncodeAckTo encodes an Ack using the provided encoder.
func EncodeClientHello ¶
func EncodeClientHello(ch *ClientHello) []byte
EncodeClientHello encodes a ClientHello to bytes.
func EncodeClientHelloTo ¶
func EncodeClientHelloTo(e *Encoder, ch *ClientHello)
EncodeClientHelloTo encodes a ClientHello using the provided encoder.
func EncodeControl ¶
func EncodeControl(ct ControlType, payload any) []byte
EncodeControl encodes a control message to bytes.
func EncodeControlTo ¶
func EncodeControlTo(e *Encoder, ct ControlType, payload any)
EncodeControlTo encodes a control message using the provided encoder.
func EncodeErrorMessage ¶
func EncodeErrorMessage(em *ErrorMessage) []byte
EncodeErrorMessage encodes an ErrorMessage to bytes.
func EncodeErrorMessageTo ¶
func EncodeErrorMessageTo(e *Encoder, em *ErrorMessage)
EncodeErrorMessageTo encodes an ErrorMessage using the provided encoder.
func EncodeEventTo ¶
EncodeEventTo encodes an event using the provided encoder.
func EncodePatches ¶
func EncodePatches(pf *PatchesFrame) []byte
EncodePatches encodes a patches frame to bytes.
func EncodePatchesTo ¶
func EncodePatchesTo(e *Encoder, pf *PatchesFrame)
EncodePatchesTo encodes a patches frame using the provided encoder.
func EncodeServerHello ¶
func EncodeServerHello(sh *ServerHello) []byte
EncodeServerHello encodes a ServerHello to bytes.
func EncodeServerHelloTo ¶
func EncodeServerHelloTo(e *Encoder, sh *ServerHello)
EncodeServerHelloTo encodes a ServerHello using the provided encoder.
func EncodeSvarint ¶
EncodeSvarint encodes a signed integer as a varint using ZigZag encoding. Returns the number of bytes written. ZigZag maps signed integers to unsigned: 0->0, -1->1, 1->2, -2->3, 2->4, etc.
func EncodeUvarint ¶
EncodeUvarint encodes an unsigned integer as a varint into buf. Returns the number of bytes written. buf must have at least MaxVarintLen bytes available. Uses protobuf-style encoding: 7 bits of data per byte, MSB indicates continuation.
func EncodeVNodeWire ¶
EncodeVNodeWire encodes a VNodeWire to bytes using the provided encoder.
func NewAuthCommand ¶
func NewAuthCommand(cmd *AuthCommand) (ControlType, *AuthCommand)
NewAuthCommand creates a new AuthCommand message.
func NewClose ¶
func NewClose(reason CloseReason, message string) (ControlType, *CloseMessage)
NewClose creates a new Close message.
func NewHookRevert ¶
func NewHookRevert(hid string) (ControlType, *HookRevert)
NewHookRevert creates a new HookRevert message.
func NewPing ¶
func NewPing(timestamp uint64) (ControlType, *PingPong)
NewPing creates a new Ping message.
func NewPong ¶
func NewPong(timestamp uint64) (ControlType, *PingPong)
NewPong creates a new Pong message.
func NewResyncFull ¶
func NewResyncFull(html string) (ControlType, *ResyncResponse)
NewResyncFull creates a new ResyncFull response.
func NewResyncPatches ¶
func NewResyncPatches(fromSeq uint64, patches []Patch) (ControlType, *ResyncResponse)
NewResyncPatches creates a new ResyncPatches response.
func NewResyncRequest ¶
func NewResyncRequest(lastSeq uint64) (ControlType, *ResyncRequest)
NewResyncRequest creates a new ResyncRequest message.
func NewSessionRefresh ¶
func NewSessionRefresh(cmd *SessionRefreshCommand) (ControlType, *SessionRefreshCommand)
NewSessionRefresh creates a new session refresh control message.
func NewSessionRefreshAck ¶
func NewSessionRefreshAck(source string) (ControlType, *SessionRefreshAck)
NewSessionRefreshAck creates a new session refresh acknowledgment.
func SvarintLen ¶
SvarintLen returns the number of bytes needed to encode v as a signed varint.
func UvarintLen ¶
UvarintLen returns the number of bytes needed to encode v as a varint.
Types ¶
type Ack ¶
type Ack struct {
LastSeq uint64 // Last received sequence number
Window uint64 // Receive window size (how many more patches client can accept)
}
Ack is sent by the client to acknowledge received patches. It serves multiple purposes:
- Garbage collection of patch history on the server
- Flow control (server knows client's processing capacity)
- Detecting client lag
func DecodeAckFrom ¶
DecodeAckFrom decodes an Ack from a decoder.
type AnimationEventData ¶
AnimationEventData contains CSS animation event data.
type AuthAction ¶
type AuthAction uint8
AuthAction defines the type of auth command to execute on the client.
const ( AuthActionForceReload AuthAction = 0x01 AuthActionBroadcast AuthAction = 0x03 )
type AuthCommand ¶
type AuthCommand struct {
Action AuthAction
Reason uint8
Path string
Channel string
Type string
}
AuthCommand is sent by the server when auth expires.
type BannerSpec ¶
BannerSpec is an optional banner payload for schema refresh messaging.
type ClientHello ¶
type ClientHello struct {
Version ProtocolVersion // Protocol version
CSRFToken string // CSRF token for validation
SessionID string // Existing session ID (empty if new)
LastSeq uint32 // Last seen sequence number
ViewportW uint16 // Viewport width
ViewportH uint16 // Viewport height
TZOffset int16 // Timezone offset in minutes from UTC
}
ClientHello is sent by the client after WebSocket connection is established.
func DecodeClientHello ¶
func DecodeClientHello(data []byte) (*ClientHello, error)
DecodeClientHello decodes a ClientHello from bytes.
func DecodeClientHelloFrom ¶
func DecodeClientHelloFrom(d *Decoder) (*ClientHello, error)
DecodeClientHelloFrom decodes a ClientHello from a decoder.
func NewClientHello ¶
func NewClientHello(csrfToken string) *ClientHello
NewClientHello creates a new ClientHello with default version.
type CloseMessage ¶
type CloseMessage struct {
Reason CloseReason
Message string
}
CloseMessage is sent when closing a session.
type CloseReason ¶
type CloseReason uint8
CloseReason indicates why a session is being closed.
const ( CloseNormal CloseReason = 0x00 // Normal closure CloseGoingAway CloseReason = 0x01 // Client/server going away CloseSessionExpired CloseReason = 0x02 // Session expired CloseServerShutdown CloseReason = 0x03 // Server shutting down CloseError CloseReason = 0x04 // Error occurred )
func (CloseReason) String ¶
func (cr CloseReason) String() string
String returns the string representation of the close reason.
type ControlType ¶
type ControlType uint8
ControlType identifies the type of control message.
const ( ControlPing ControlType = 0x01 // Client/server ping ControlPong ControlType = 0x02 // Response to ping ControlResyncRequest ControlType = 0x10 // Client requests missed patches ControlResyncPatches ControlType = 0x11 // Server sends missed patches ControlResyncFull ControlType = 0x12 // Server sends full HTML reload ControlHookRevert ControlType = 0x30 // Server requests hook revert by HID ControlAuthCommand ControlType = 0x31 // Server auth command (reload, navigate, broadcast) ControlSessionRefresh ControlType = 0x32 // Server session refresh notification ControlSessionRefreshAck ControlType = 0x33 // Client acknowledges session refresh ControlClose ControlType = 0x20 // Session close )
func DecodeControl ¶
func DecodeControl(data []byte) (ControlType, any, error)
DecodeControl decodes a control message from bytes. Returns the control type and the decoded payload.
func DecodeControlFrom ¶
func DecodeControlFrom(d *Decoder) (ControlType, any, error)
DecodeControlFrom decodes a control message from a decoder.
func (ControlType) String ¶
func (ct ControlType) String() string
String returns the string representation of the control type.
type CustomEventData ¶
CustomEventData contains custom event data.
type Decoder ¶
type Decoder struct {
// contains filtered or unexported fields
}
Decoder is a binary decoder that reads from a byte buffer.
func NewDecoder ¶
NewDecoder creates a new decoder from the given byte slice.
func (*Decoder) ReadBytes ¶
ReadBytes reads exactly n bytes and returns them. The returned slice references the decoder's buffer; do not modify.
func (*Decoder) ReadCollectionCount ¶
ReadCollectionCount reads a varint count and validates it against limits. Returns ErrCollectionTooLarge if count exceeds MaxCollectionCount. This should be used when reading the size of arrays, maps, or other collections.
func (*Decoder) ReadFloat32 ¶
ReadFloat32 reads a float32 in IEEE 754 format (big-endian).
func (*Decoder) ReadFloat64 ¶
ReadFloat64 reads a float64 in IEEE 754 format (big-endian).
func (*Decoder) ReadLenBytes ¶
ReadLenBytes reads length-prefixed bytes. Returns a copy of the bytes (safe to retain). Returns ErrAllocationTooLarge if the byte slice exceeds DefaultMaxAllocation.
func (*Decoder) ReadString ¶
ReadString reads a length-prefixed UTF-8 string. Returns ErrAllocationTooLarge if the string exceeds DefaultMaxAllocation.
func (*Decoder) ReadSvarint ¶
ReadSvarint reads a signed varint using ZigZag decoding.
func (*Decoder) ReadUint16 ¶
ReadUint16 reads a uint16 in big-endian byte order.
func (*Decoder) ReadUint32 ¶
ReadUint32 reads a uint32 in big-endian byte order.
func (*Decoder) ReadUint64 ¶
ReadUint64 reads a uint64 in big-endian byte order.
func (*Decoder) ReadUvarint ¶
ReadUvarint reads an unsigned varint.
type DepthLimits ¶
type DepthLimits struct {
// MaxVNodeDepth is the maximum VNode tree depth.
VNodeDepth int
// MaxPatchDepth is the maximum patch structure depth.
PatchDepth int
// MaxHookDepth is the maximum JSON payload depth.
HookDepth int
}
DepthLimits allows configuring custom depth limits for decoding. Use DefaultDepthLimits() for sensible defaults.
func DefaultDepthLimits ¶
func DefaultDepthLimits() *DepthLimits
DefaultDepthLimits returns the default depth limits.
type Encoder ¶
type Encoder struct {
// contains filtered or unexported fields
}
Encoder is a binary encoder that appends data to an internal buffer. It is designed for efficient encoding without allocations in the hot path.
func NewEncoder ¶
func NewEncoder() *Encoder
NewEncoder creates a new encoder with a default initial capacity.
func NewEncoderWithCap ¶
NewEncoderWithCap creates a new encoder with the specified initial capacity.
func (*Encoder) Bytes ¶
Bytes returns the encoded bytes. The returned slice is valid until the next call to Reset or any Write method.
func (*Encoder) Reset ¶
func (e *Encoder) Reset()
Reset resets the encoder to empty state, reusing the underlying buffer.
func (*Encoder) WriteByte ¶
WriteByte appends a single byte. It implements io.ByteWriter for compatibility with standard interfaces. The encoder cannot error, so this always returns nil.
func (*Encoder) WriteByteUnsafe ¶
WriteByteUnsafe appends a single byte without returning an error. Use internally to avoid repetitive nil error handling.
func (*Encoder) WriteFloat32 ¶
WriteFloat32 appends a float32 in IEEE 754 format (big-endian).
func (*Encoder) WriteFloat64 ¶
WriteFloat64 appends a float64 in IEEE 754 format (big-endian).
func (*Encoder) WriteInt16 ¶
WriteInt16 appends an int16 in big-endian byte order.
func (*Encoder) WriteInt32 ¶
WriteInt32 appends an int32 in big-endian byte order.
func (*Encoder) WriteInt64 ¶
WriteInt64 appends an int64 in big-endian byte order.
func (*Encoder) WriteLenBytes ¶
WriteLenBytes appends length-prefixed bytes. Format: varint length + bytes
func (*Encoder) WriteString ¶
WriteString appends a length-prefixed UTF-8 string. Format: varint length + string bytes
func (*Encoder) WriteSvarint ¶
WriteSvarint appends a signed varint using ZigZag encoding.
func (*Encoder) WriteUint16 ¶
WriteUint16 appends a uint16 in big-endian byte order.
func (*Encoder) WriteUint32 ¶
WriteUint32 appends a uint32 in big-endian byte order.
func (*Encoder) WriteUint64 ¶
WriteUint64 appends a uint64 in big-endian byte order.
func (*Encoder) WriteUvarint ¶
WriteUvarint appends an unsigned varint.
type ErrorCode ¶
type ErrorCode uint16
ErrorCode identifies the type of error.
const ( ErrUnknown ErrorCode = 0x0000 // Unknown error ErrInvalidFrame ErrorCode = 0x0001 // Malformed frame ErrInvalidEvent ErrorCode = 0x0002 // Malformed event ErrHandlerNotFound ErrorCode = 0x0003 // No handler for HID ErrHandlerPanic ErrorCode = 0x0004 // Handler panicked ErrSessionExpired ErrorCode = 0x0005 // Session no longer valid ErrRateLimited ErrorCode = 0x0006 // Too many requests ErrServerError ErrorCode = 0x0100 // Internal server error ErrNotAuthorized ErrorCode = 0x0101 // Not authorized ErrNotFound ErrorCode = 0x0102 // Resource not found ErrValidation ErrorCode = 0x0103 // Validation failed ErrRouteError ErrorCode = 0x0104 // Route matching/navigation error )
type ErrorMessage ¶
type ErrorMessage struct {
Code ErrorCode // Error code
Message string // Human-readable error message
Fatal bool // If true, connection should be closed
}
ErrorMessage is sent when an error occurs.
func DecodeErrorMessage ¶
func DecodeErrorMessage(data []byte) (*ErrorMessage, error)
DecodeErrorMessage decodes an ErrorMessage from bytes.
func DecodeErrorMessageFrom ¶
func DecodeErrorMessageFrom(d *Decoder) (*ErrorMessage, error)
DecodeErrorMessageFrom decodes an ErrorMessage from a decoder.
func NewError ¶
func NewError(code ErrorCode, message string) *ErrorMessage
NewError creates a new non-fatal ErrorMessage.
func NewFatalError ¶
func NewFatalError(code ErrorCode, message string) *ErrorMessage
NewFatalError creates a new fatal ErrorMessage.
func (*ErrorMessage) Error ¶
func (em *ErrorMessage) Error() string
Error implements the error interface.
func (*ErrorMessage) IsFatal ¶
func (em *ErrorMessage) IsFatal() bool
IsFatal returns true if this error should close the connection.
type Event ¶
type Event struct {
Seq uint64
Type EventType
HID string
Payload any // Type-specific payload (nil for simple events like Click)
}
Event represents a decoded event from the client.
func DecodeEvent ¶
DecodeEvent decodes an event from bytes.
func DecodeEventFrom ¶
DecodeEventFrom decodes an event from a decoder.
type EventType ¶
type EventType uint8
EventType identifies the type of client event.
const ( // Mouse events (0x01-0x08) EventClick EventType = 0x01 EventDblClick EventType = 0x02 EventMouseDown EventType = 0x03 EventMouseUp EventType = 0x04 EventMouseMove EventType = 0x05 EventMouseEnter EventType = 0x06 EventMouseLeave EventType = 0x07 EventWheel EventType = 0x08 // Mouse wheel event // Form events (0x10-0x14) EventInput EventType = 0x10 EventChange EventType = 0x11 EventSubmit EventType = 0x12 EventFocus EventType = 0x13 EventBlur EventType = 0x14 // Keyboard events (0x20-0x22) EventKeyDown EventType = 0x20 EventKeyUp EventType = 0x21 EventKeyPress EventType = 0x22 // Scroll/Resize events (0x30-0x31) EventScroll EventType = 0x30 EventResize EventType = 0x31 // Touch events (0x40-0x42) EventTouchStart EventType = 0x40 EventTouchMove EventType = 0x41 EventTouchEnd EventType = 0x42 // Drag events (0x50-0x52) EventDragStart EventType = 0x50 EventDragEnd EventType = 0x51 EventDrop EventType = 0x52 // Animation events (0x53-0x56) EventAnimationStart EventType = 0x53 EventAnimationEnd EventType = 0x54 EventAnimationIteration EventType = 0x55 EventAnimationCancel EventType = 0x56 // Transition events (0x57-0x5A) EventTransitionStart EventType = 0x57 EventTransitionEnd EventType = 0x58 EventTransitionRun EventType = 0x59 EventTransitionCancel EventType = 0x5A // Special events (0x60+) EventHook EventType = 0x60 // Client hook event EventCustom EventType = 0xFF // Custom event )
Event type constants.
type Frame ¶
type Frame struct {
Type FrameType
Flags FrameFlags
Payload []byte
}
Frame represents a protocol frame with header and payload.
Wire format (4 bytes header + variable payload):
┌─────────────┬──────────────┬───────────────────────────────┐ │ Frame Type │ Flags │ Payload Length │ │ (1 byte) │ (1 byte) │ (2 bytes, big-endian) │ └─────────────┴──────────────┴───────────────────────────────┘ │ │ │ Payload (variable length) │ │ │ └─────────────────────────────────────────────────────────────┘
func DecodeFrame ¶
DecodeFrame decodes a frame from bytes. The input must contain at least the header (4 bytes) and full payload.
func NewFrameWithFlags ¶
func NewFrameWithFlags(ft FrameType, flags FrameFlags, payload []byte) *Frame
NewFrameWithFlags creates a new frame with the given type, flags, and payload.
type FrameFlags ¶
type FrameFlags uint8
FrameFlags are optional flags for frame processing.
const ( FlagCompressed FrameFlags = 0x01 // Payload is gzip compressed FlagSequenced FrameFlags = 0x02 // Includes sequence number FlagFinal FrameFlags = 0x04 // Last frame in batch FlagPriority FrameFlags = 0x08 // High priority (skip queue) )
func (FrameFlags) Has ¶
func (ff FrameFlags) Has(flag FrameFlags) bool
Has returns true if the flags contain the specified flag.
type FrameType ¶
type FrameType uint8
FrameType identifies the type of frame.
const ( FrameHandshake FrameType = 0x00 // Connection setup FrameEvent FrameType = 0x01 // Client → Server events FramePatches FrameType = 0x02 // Server → Client patches FrameControl FrameType = 0x03 // Control messages (ping, etc.) FrameAck FrameType = 0x04 // Acknowledgment FrameError FrameType = 0x05 // Error message )
type HandshakeStatus ¶
type HandshakeStatus uint8
HandshakeStatus represents the result of a handshake.
const ( HandshakeOK HandshakeStatus = 0x00 HandshakeVersionMismatch HandshakeStatus = 0x01 HandshakeInvalidCSRF HandshakeStatus = 0x02 HandshakeSessionExpired HandshakeStatus = 0x03 HandshakeServerBusy HandshakeStatus = 0x04 HandshakeUpgradeRequired HandshakeStatus = 0x05 HandshakeInvalidFormat HandshakeStatus = 0x06 // Malformed handshake message HandshakeNotAuthorized HandshakeStatus = 0x07 // Authentication failed HandshakeInternalError HandshakeStatus = 0x08 // Server error HandshakeLimitExceeded HandshakeStatus = 0x09 // Per-IP session limit exceeded HandshakeSchemaMismatch HandshakeStatus = 0x0A // Persisted schema incompatible )
func HandshakeStatusForVersion ¶
func HandshakeStatusForVersion(client, server ProtocolVersion) HandshakeStatus
HandshakeStatusForVersion returns the handshake status implied by client/server protocol versions.
Policy:
- Exact match is required for a successful handshake.
- Lower client versions are rejected with HandshakeUpgradeRequired.
- Higher client versions are rejected with HandshakeVersionMismatch.
func (HandshakeStatus) String ¶
func (hs HandshakeStatus) String() string
String returns the string representation of the handshake status.
type HookEventData ¶
HookEventData contains client hook event data.
type HookRevert ¶
type HookRevert struct {
HID string
}
HookRevert instructs the client to run the registered revert callback for the hook instance.
type HookValueType ¶
type HookValueType uint8
HookValueType identifies the type of a hook data value.
const ( HookValueNull HookValueType = 0x00 HookValueBool HookValueType = 0x01 HookValueInt HookValueType = 0x02 HookValueFloat HookValueType = 0x03 HookValueString HookValueType = 0x04 HookValueArray HookValueType = 0x05 HookValueObject HookValueType = 0x06 )
type InputEventData ¶
type InputEventData struct {
Value string
InputType string // e.g., "insertText", "deleteContentBackward"
Data string // Inserted text (if any)
}
InputEventData contains input event data with full details.
type KeyboardEventData ¶
type KeyboardEventData struct {
Key string
Code string // Physical key code (e.g., "KeyA", "Enter")
Modifiers Modifiers
Repeat bool // True if key is held down (auto-repeat)
Location uint8 // 0=standard, 1=left, 2=right, 3=numpad
}
KeyboardEventData contains keyboard event data.
type MouseEventData ¶
type MouseEventData struct {
ClientX int
ClientY int
PageX int // Position relative to document
PageY int
OffsetX int // Position relative to target element
OffsetY int
Button uint8
Buttons uint8 // Bitmask of currently pressed buttons
Modifiers Modifiers
}
MouseEventData contains mouse event data.
type NavigateEventData ¶
type NavigateEventData struct {
}
NavigateEventData contains navigation event data.
type Patch ¶
type Patch struct {
Op PatchOp
HID string // Target element's hydration ID
Key string // Attribute/style/class key
Value string // Value for text/attr/style/class
ParentID string // Parent HID for InsertNode/MoveNode/*At operations
Index int // Insert/Move/*At position
Node *VNodeWire // For InsertNode/ReplaceNode
Bool bool // For SetChecked/SetSelected
X int // For ScrollTo
Y int // For ScrollTo
Behavior ScrollBehavior // For ScrollTo
Params map[string]string // URL query delta; empty value means remove key
Path string // For NavPush/NavReplace (includes query string)
}
Patch represents a single DOM operation.
func NewAddClassPatch ¶
NewAddClassPatch creates an AddClass patch.
func NewDispatchPatch ¶
NewDispatchPatch creates a Dispatch patch.
func NewInsertNodePatch ¶
NewInsertNodePatch creates an InsertNode patch.
func NewMoveNodePatch ¶
NewMoveNodePatch creates a MoveNode patch.
func NewNavPushPatch ¶
NewNavPushPatch creates a NavPush patch for full navigation (adds history entry). The path should be a relative path starting with "/" and may include query string. SECURITY: The path must be validated server-side to prevent open-redirect attacks.
func NewNavReplacePatch ¶
NewNavReplacePatch creates a NavReplace patch for full navigation (replaces history). The path should be a relative path starting with "/" and may include query string. SECURITY: The path must be validated server-side to prevent open-redirect attacks.
func NewRemoveAttrPatch ¶
NewRemoveAttrPatch creates a RemoveAttr patch.
func NewRemoveClassPatch ¶
NewRemoveClassPatch creates a RemoveClass patch.
func NewRemoveNodeAtPatch ¶
NewRemoveNodeAtPatch creates a RemoveNodeAt patch.
func NewRemoveNodePatch ¶
NewRemoveNodePatch creates a RemoveNode patch.
func NewRemoveStylePatch ¶
NewRemoveStylePatch creates a RemoveStyle patch.
func NewReplaceNodeAtPatch ¶
NewReplaceNodeAtPatch creates a ReplaceNodeAt patch.
func NewReplaceNodePatch ¶
NewReplaceNodePatch creates a ReplaceNode patch.
func NewScrollToPatch ¶
func NewScrollToPatch(hid string, x, y int, behavior ScrollBehavior) Patch
NewScrollToPatch creates a ScrollTo patch.
func NewSetAttrPatch ¶
NewSetAttrPatch creates a SetAttr patch.
func NewSetCheckedPatch ¶
NewSetCheckedPatch creates a SetChecked patch.
func NewSetDataPatch ¶
NewSetDataPatch creates a SetData patch.
func NewSetSelectedPatch ¶
NewSetSelectedPatch creates a SetSelected patch.
func NewSetStylePatch ¶
NewSetStylePatch creates a SetStyle patch.
func NewSetTextAtPatch ¶
NewSetTextAtPatch creates a SetTextAt patch.
func NewSetTextPatch ¶
NewSetTextPatch creates a SetText patch.
func NewSetValuePatch ¶
NewSetValuePatch creates a SetValue patch.
func NewToggleClassPatch ¶
NewToggleClassPatch creates a ToggleClass patch.
func NewURLPushPatch ¶
NewURLPushPatch creates a URLPush patch (adds history entry).
func NewURLReplacePatch ¶
NewURLReplacePatch creates a URLReplace patch (replaces current entry).
type PatchOp ¶
type PatchOp uint8
PatchOp is the type of patch operation. This is a superset of vdom.PatchOp, with additional operations for the protocol.
const ( // Core operations (matching vdom.PatchOp) PatchSetText PatchOp = 0x01 // Update text content PatchSetAttr PatchOp = 0x02 // Set attribute PatchRemoveAttr PatchOp = 0x03 // Remove attribute PatchInsertNode PatchOp = 0x04 // Insert new node PatchRemoveNode PatchOp = 0x05 // Remove node PatchMoveNode PatchOp = 0x06 // Move node PatchReplaceNode PatchOp = 0x07 // Replace node PatchSetValue PatchOp = 0x08 // Set input value PatchSetChecked PatchOp = 0x09 // Set checkbox checked PatchSetSelected PatchOp = 0x0A // Set select option selected PatchFocus PatchOp = 0x0B // Focus element // Extended operations (protocol-only) PatchBlur PatchOp = 0x0C // Blur element PatchScrollTo PatchOp = 0x0D // Scroll to position PatchAddClass PatchOp = 0x10 // Add CSS class PatchRemoveClass PatchOp = 0x11 // Remove CSS class PatchToggleClass PatchOp = 0x12 // Toggle CSS class PatchSetStyle PatchOp = 0x13 // Set style property PatchRemoveStyle PatchOp = 0x14 // Remove style property PatchSetData PatchOp = 0x15 // Set data attribute PatchSetTextAt PatchOp = 0x16 // Set text by parent/index PatchRemoveNodeAt PatchOp = 0x17 // Remove node by parent/index PatchReplaceNodeAt PatchOp = 0x18 // Replace node by parent/index PatchDispatch PatchOp = 0x20 // Dispatch client event // URL operations (Phase 12: URLParam 2.0) // Params are delta updates: keys omitted are left unchanged. // Empty-string values are delete tombstones for removing query keys. PatchURLPush PatchOp = 0x30 // Update query params delta, push to history PatchURLReplace PatchOp = 0x31 // Update query params delta, replace current entry // Navigation operations (full route navigation) )
Patch operation constants. Values 0x01-0x0B match vdom.PatchOp for compatibility.
type PatchesFrame ¶
PatchesFrame represents a batch of patches with sequence number.
func DecodePatches ¶
func DecodePatches(data []byte) (*PatchesFrame, error)
DecodePatches decodes a patches frame from bytes.
func DecodePatchesFrom ¶
func DecodePatchesFrom(d *Decoder) (*PatchesFrame, error)
DecodePatchesFrom decodes a patches frame from a decoder. SECURITY: Enforces MaxPatchDepth to prevent stack overflow attacks.
type PingPong ¶
type PingPong struct {
Timestamp uint64 // Unix timestamp in milliseconds
}
PingPong is the payload for Ping and Pong messages.
type ProtocolVersion ¶
ProtocolVersion represents a protocol version as major.minor.
type ResizeEventData ¶
ResizeEventData contains resize event data.
type ResyncRequest ¶
type ResyncRequest struct {
LastSeq uint64 // Last received sequence number
}
ResyncRequest is sent by client to request missed patches.
type ResyncResponse ¶
type ResyncResponse struct {
Type ControlType // ResyncPatches or ResyncFull
FromSeq uint64 // Starting sequence number (for ResyncPatches)
Patches []Patch // Missed patches (for ResyncPatches)
HTML string // Full HTML (for ResyncFull)
}
ResyncResponse is sent by server with missed patches or full reload.
type SchemaRefreshReason ¶
type SchemaRefreshReason uint8
SchemaRefreshReason indicates why a session refresh was triggered.
const ( SchemaRefreshUnknown SchemaRefreshReason = 0x00 SchemaRefreshIDRemoved SchemaRefreshReason = 0x01 SchemaRefreshIDTypeChanged SchemaRefreshReason = 0x02 SchemaRefreshColdDeploy SchemaRefreshReason = 0x03 SchemaRefreshHashMismatch SchemaRefreshReason = 0x04 SchemaRefreshRuntimeViolation SchemaRefreshReason = 0x05 )
func (SchemaRefreshReason) String ¶
func (sr SchemaRefreshReason) String() string
String returns the string representation of the schema refresh reason.
type ScrollBehavior ¶
type ScrollBehavior uint8
ScrollBehavior represents the scroll behavior for PatchScrollTo.
const ( ScrollInstant ScrollBehavior = 0 ScrollSmooth ScrollBehavior = 1 )
type ScrollEventData ¶
ScrollEventData contains scroll event data.
type ServerHello ¶
type ServerHello struct {
Status HandshakeStatus // Handshake result
SessionID string // Session ID (new or existing)
NextSeq uint32 // Next expected sequence number
ServerTime uint64 // Server time in Unix milliseconds
Flags uint16 // Server capability flags
// AuthReason is an optional auth expiration reason for error handshakes.
// AuthReasonSet indicates whether the reason was included in the payload.
AuthReason uint8
AuthReasonSet bool
// SchemaReason is provided when Status == HandshakeSchemaMismatch.
SchemaReason SchemaRefreshReason
SchemaReasonSet bool
// Banner payload (optional).
BannerMessage string
BannerDetail string
BannerDismissMS uint16
BannerCSSClass string
BannerSet bool
}
ServerHello is the server's response to ClientHello.
func DecodeServerHello ¶
func DecodeServerHello(data []byte) (*ServerHello, error)
DecodeServerHello decodes a ServerHello from bytes.
func DecodeServerHelloFrom ¶
func DecodeServerHelloFrom(d *Decoder) (*ServerHello, error)
DecodeServerHelloFrom decodes a ServerHello from a decoder.
func NewServerHello ¶
func NewServerHello(sessionID string, nextSeq uint32, serverTime uint64) *ServerHello
NewServerHello creates a new successful ServerHello.
func NewServerHelloError ¶
func NewServerHelloError(status HandshakeStatus) *ServerHello
NewServerHelloError creates a ServerHello with an error status.
func NewServerHelloErrorWithReason ¶
func NewServerHelloErrorWithReason(status HandshakeStatus, reason uint8) *ServerHello
NewServerHelloErrorWithReason creates a ServerHello with an error status and reason.
func NewServerHelloSchemaError ¶
func NewServerHelloSchemaError(reason SchemaRefreshReason, banner BannerSpec) *ServerHello
NewServerHelloSchemaError creates a schema mismatch ServerHello with banner payload.
type SessionRefreshAck ¶
type SessionRefreshAck struct {
Source string
}
SessionRefreshAck acknowledges a session refresh notification.
type SessionRefreshCommand ¶
type SessionRefreshCommand struct {
Reason SchemaRefreshReason
DevMode bool
Message string
Detail string
DelayMS uint16
CSSClass string
}
SessionRefreshCommand notifies the client of a session refresh.
type SubmitEventData ¶
SubmitEventData contains form submission data.
type TouchEventData ¶
type TouchEventData struct {
Touches []TouchPoint // All current touches
TargetTouches []TouchPoint // Touches on this element
ChangedTouches []TouchPoint // Touches that changed in this event
}
TouchEventData contains touch event data.
type TouchPoint ¶
type TouchPoint struct {
ID int
ClientX int
ClientY int
PageX int // Position relative to document
PageY int
}
TouchPoint represents a single touch point.
type TransitionEventData ¶
TransitionEventData contains CSS transition event data.
type VNodeWire ¶
type VNodeWire struct {
Kind vdom.VKind // Node type
Tag string // Element tag name
HID string // Hydration ID
Attrs map[string]string // String attributes only (no handlers)
Children []*VNodeWire // Child nodes
Text string // For Text and Raw nodes
}
VNodeWire is the wire format for VNodes. It contains only serializable data (no event handlers or components).
func DecodeVNodeWire ¶
DecodeVNodeWire decodes a VNodeWire from the decoder. SECURITY: Enforces MaxVNodeDepth to prevent stack overflow attacks.
func NewElementWire ¶
NewElementWire creates an element VNodeWire.
func NewFragmentWire ¶
NewFragmentWire creates a fragment VNodeWire.
func VNodeToWire ¶
VNodeToWire converts a vdom.VNode to wire format. Event handlers are stripped; only string attributes are included. Component nodes should be rendered before calling this function.
func VNodeToWireWithPolicy ¶
VNodeToWireWithPolicy converts a vdom.VNode to wire format using the provided URL policy. The policy should be normalized (use NormalizeURLPolicy or URLPolicyFromSchemes). Raw HTML nodes are sink-sanitized unless policy.AllowUnsafeRawHTML is true.