proto

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package proto implements the Bolt v5 wire protocol message types, handshake negotiation, and chunked framing.

Concurrency: all types in this package are NOT safe for concurrent use unless documented otherwise. Callers must ensure that a single connection's read and write paths are each accessed by at most one goroutine at a time.

Index

Examples

Constants

View Source
const (
	// Client → Server.
	TagHello    byte = 0x01
	TagLogon    byte = 0x6A
	TagLogoff   byte = 0x6B
	TagGoodbye  byte = 0x02
	TagReset    byte = 0x0F
	TagRun      byte = 0x10
	TagPull     byte = 0x3F
	TagDiscard  byte = 0x2F
	TagBegin    byte = 0x11
	TagCommit   byte = 0x12
	TagRollback byte = 0x13
	TagRoute    byte = 0x66

	// Server → Client.
	TagSuccess byte = 0x70
	TagFailure byte = 0x7F
	TagIgnored byte = 0x7E
	TagRecord  byte = 0x71
)

Structure tag bytes for Bolt v5 messages (PackStream Struct tags).

View Source
const DefaultMaxMessageBytes = 16 << 20 // 16 MiB

DefaultMaxMessageBytes is the default upper bound on the cumulative payload size of a single reassembled Bolt message. Chosen so that a Bolt message comfortably accommodates the largest realistic record projection (PackStream lists of strings, large maps from APOC-style procedures, multi-megabyte result rows) while keeping a single malicious client from coercing the server into multi-gigabyte allocations by streaming non-zero chunks indefinitely.

View Source
const Magic = uint32(0x6060B017)

Magic is the 4-byte Bolt protocol preamble sent by the client at the start of every connection.

Variables

View Source
var ErrBadMagic = errors.New("bolt: bad magic preamble")

ErrBadMagic is returned by Negotiate when the client sends an incorrect magic preamble.

View Source
var ErrMessageTooLarge = errors.New("bolt chunk: cumulative message size exceeds MaxMessageBytes")

ErrMessageTooLarge is returned by ChunkedReader.ReadMessage when the cumulative payload size of a single Bolt message would exceed the reader's MaxMessageBytes cap. Inspect with errors.Is. No partially read message is returned; the underlying reader is left positioned at the next byte after the offending chunk's payload so the caller may close the connection cleanly.

View Source
var ErrNoCommonVersion = errors.New("bolt: no common protocol version")

ErrNoCommonVersion is returned by Negotiate when the client's offered versions do not include any version that this server supports.

View Source
var SupportedVersions = []Version{
	{5, 6}, {5, 5}, {5, 4}, {5, 3}, {5, 2}, {5, 1}, {5, 0},
	{4, 4},
}

SupportedVersions is the list of Bolt versions advertised by this server, ordered from highest to lowest preference.

Functions

func DecodeRequest

func DecodeRequest(dec *packstream.Decoder) (any, error)

DecodeRequest reads one complete Bolt request message from dec and returns a typed pointer (*Hello, *Logon, etc.).

func DecodeResponse

func DecodeResponse(dec *packstream.Decoder) (any, error)

DecodeResponse reads one complete Bolt response message from dec and returns a typed pointer (*Success, *Failure, *Ignored, *Record).

func EncodeRequest

func EncodeRequest(enc *packstream.Encoder, msg any) error

EncodeRequest encodes a client→server message msg into enc. msg must be one of: *Hello, *Logon, *Logoff, *Goodbye, *Reset, *Run, *Pull, *Discard, *Begin, *Commit, *Rollback, *Route.

Example

ExampleEncodeRequest round-trips a RUN request. EncodeRequest serialises the message to PackStream; DecodeRequest reconstructs the concrete *proto.Run, preserving the query text and parameters.

package main

import (
	"bytes"
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/bolt/packstream"
	"github.com/FlavioCFOliveira/GoGraph/bolt/proto"
)

func main() {
	msg := &proto.Run{
		Query:      "MATCH (n) RETURN n",
		Parameters: map[string]packstream.Value{"limit": int64(10)},
	}

	var buf bytes.Buffer
	enc := packstream.NewEncoder(&buf)
	if err := proto.EncodeRequest(enc, msg); err != nil {
		fmt.Println("encode:", err)
		return
	}
	if err := enc.Flush(); err != nil {
		fmt.Println("flush:", err)
		return
	}

	dec := packstream.NewDecoder(&buf)
	decoded, err := proto.DecodeRequest(dec)
	if err != nil {
		fmt.Println("decode:", err)
		return
	}

	run := decoded.(*proto.Run)
	fmt.Println("query:", run.Query)
	fmt.Println("limit:", run.Parameters["limit"])
}
Output:
query: MATCH (n) RETURN n
limit: 10

func EncodeResponse

func EncodeResponse(enc *packstream.Encoder, msg any) error

EncodeResponse encodes a server→client message msg into enc. msg must be one of: *Success, *Failure, *Ignored, *Record.

Example

ExampleEncodeResponse round-trips a SUCCESS response carrying server metadata. The decoded *proto.Success exposes the same metadata map.

package main

import (
	"bytes"
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/bolt/packstream"
	"github.com/FlavioCFOliveira/GoGraph/bolt/proto"
)

func main() {
	msg := &proto.Success{
		Metadata: map[string]packstream.Value{"server": "GoGraph/1.0"},
	}

	var buf bytes.Buffer
	enc := packstream.NewEncoder(&buf)
	if err := proto.EncodeResponse(enc, msg); err != nil {
		fmt.Println("encode:", err)
		return
	}
	if err := enc.Flush(); err != nil {
		fmt.Println("flush:", err)
		return
	}

	dec := packstream.NewDecoder(&buf)
	decoded, err := proto.DecodeResponse(dec)
	if err != nil {
		fmt.Println("decode:", err)
		return
	}

	success := decoded.(*proto.Success)
	fmt.Printf("server: %v\n", success.Metadata["server"])
}
Output:
server: GoGraph/1.0

Types

type Begin

type Begin struct {
	Extra map[string]packstream.Value
}

Begin starts an explicit transaction. Fields: [extra map].

type ChunkedReader

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

ChunkedReader reassembles a complete Bolt message from a sequence of length-prefixed chunks read from an underlying buffered reader.

Wire format (per chunk):

uint16 big-endian length  (0 = end-of-message sentinel)
<length bytes of payload>

Bounded growth: every ChunkedReader carries a maxMessageBytes cap (configured via NewChunkedReaderWithLimit; defaults to DefaultMaxMessageBytes for NewChunkedReader). When the cumulative payload of a single message would exceed the cap, ReadMessage returns ErrMessageTooLarge before performing the would-be-oversized allocation. This closes the Slowloris-style DoS vector where a single client streams non-zero chunks until the server OOMs.

Aggregate bound: maxMessageBytes caps ONE connection's reassembly buffer, but the sum across all connections is only implicitly bounded by MaxConnections × maxMessageBytes. A ChunkedReader with an packstream.InboundBudget attached via ChunkedReader.SetInboundBudget charges the transient reassembly buffer's bytes against that shared, engine-wide ceiling as the buffer grows and releases them symmetrically once the message is assembled — so the total reassembly memory in flight across every connection is centrally bounded, sharing one pool with the decoder's inbound-decode accounting. A nil/disabled budget leaves the reader bounded only by maxMessageBytes.

ChunkedReader is NOT safe for concurrent use. The attached packstream.InboundBudget, however, IS safe for concurrent use: one budget is shared by every connection's reader (and decoder) at once.

func NewChunkedReader

func NewChunkedReader(r io.Reader) *ChunkedReader

NewChunkedReader returns a ChunkedReader that reads from r with the DefaultMaxMessageBytes cap on cumulative message size. Use NewChunkedReaderWithLimit to set a different cap.

func NewChunkedReaderWithLimit

func NewChunkedReaderWithLimit(r io.Reader, maxMessageBytes int) *ChunkedReader

NewChunkedReaderWithLimit returns a ChunkedReader whose ReadMessage rejects any single Bolt message whose cumulative payload size would exceed maxMessageBytes with ErrMessageTooLarge.

A maxMessageBytes value of 0 or negative is replaced with DefaultMaxMessageBytes; the cap can never be disabled by an accidental zero-value configuration. Callers that genuinely want a very large bound should pass it explicitly.

func (*ChunkedReader) ReadMessage

func (cr *ChunkedReader) ReadMessage() ([]byte, error)

ReadMessage reads and reassembles one complete Bolt message.

It reads chunks until it encounters a uint16(0) sentinel, appending each chunk's payload into a contiguous byte slice. The returned slice is freshly allocated and owned by the caller.

Returns io.EOF when the underlying connection is closed cleanly before any bytes of the next message have arrived. Any other I/O error is wrapped and returned.

A standalone uint16(0) chunk that does NOT terminate an in-progress message (i.e. a 00 00 arriving with no preceding payload chunk) is a Bolt 4.1+ NOOP keep-alive: per the Bolt Protocol Manual a conformant peer MUST silently discard it (zero PackStream bytes, never decoded, never answered). Such a NOOP is skipped here at the framing layer — ReadMessage loops past it and reads the next real message — so the serve loop never sees a spurious zero-length message to (mis)decode. A uint16(0) that legitimately TERMINATES a non-empty message is ordinary end-of-message chunking and is unaffected. Skipping the NOOP at this layer (rather than in the serve loop) keeps chunk-reassembly self-contained: the reader's contract becomes "every returned message has at least one payload byte", which matches real Bolt messages (always at least a struct header byte) and leaves callers free of NOOP-awareness.

Returns ErrMessageTooLarge when the cumulative payload of the message in flight would exceed the reader's MaxMessageBytes cap. The check is performed against the prospective total (current msg length + incoming chunkLen) before the would-be-oversized allocation is attempted, so a malicious client cannot coerce a single multi-gigabyte allocation by streaming non-zero chunks indefinitely.

func (*ChunkedReader) SetInboundBudget added in v0.9.0

func (cr *ChunkedReader) SetInboundBudget(b *packstream.InboundBudget)

SetInboundBudget attaches the engine-wide inbound-memory ceiling that this reader charges its transient message-reassembly buffer against, so aggregate reassembly memory across all connections shares one pool with the decoder's inbound-decode accounting (a per-Server DoS bound, CWE-770). Pass the Server's shared packstream.InboundBudget; a nil or disabled budget detaches the accounting, leaving the reader bounded only by its maxMessageBytes cap.

It is the reassembly-side counterpart of github.com/FlavioCFOliveira/GoGraph/bolt/packstream.Decoder.SetInboundBudget. Call it once, immediately after construction and before the first ChunkedReader.ReadMessage; the reader draws on the budget for the lifetime of every subsequent reassembly and releases symmetrically. A disabled budget is stored as nil so ReadMessage pays no per-chunk accounting cost when the operator has not opted into an inbound-memory ceiling.

type ChunkedWriter

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

ChunkedWriter frames a logical Bolt message into one or more chunks and writes them to the underlying buffered writer, followed by the uint16(0) end-of-message sentinel. By default it then flushes the buffer, so one WriteMessage call reaches the peer on its own; see ChunkedWriter.SetAutoFlush for the streaming case.

ChunkedWriter is NOT safe for concurrent use.

func NewChunkedWriter

func NewChunkedWriter(w io.Writer) *ChunkedWriter

NewChunkedWriter returns a ChunkedWriter that writes to w and flushes after every message.

func (*ChunkedWriter) Flush added in v0.11.0

func (cw *ChunkedWriter) Flush() error

Flush writes any buffered bytes to the underlying writer. It is a no-op when the buffer is empty, so it is safe to call unconditionally.

func (*ChunkedWriter) SetAutoFlush added in v0.11.0

func (cw *ChunkedWriter) SetAutoFlush(on bool)

SetAutoFlush controls whether ChunkedWriter.WriteMessage flushes the underlying writer before returning. It defaults to true.

Disabling it lets a run of messages accumulate in the buffer and reach the peer in a small number of writes instead of one write per message, which is what a result stream wants: a K-row result otherwise costs K write syscalls, and the buffer in front of the connection can never do its job. Measured on the Bolt server, a 1 000-row result cost 1 953 µs of CPU with a flush per message and 324 µs without one (docs/cpu-vs-neo4j-memgraph-2026-08-11.md §4).

A caller that disables auto-flush TAKES OVER responsibility for delivery: it MUST call ChunkedWriter.Flush before it blocks waiting for the peer, or the buffered messages will never be sent and the exchange deadlocks.

func (*ChunkedWriter) WriteMessage

func (cw *ChunkedWriter) WriteMessage(msg []byte) error

WriteMessage writes msg as one or more Bolt chunks and appends the uint16(0) sentinel. It flushes the underlying writer unless auto-flush has been disabled with ChunkedWriter.SetAutoFlush.

If msg is empty, WriteMessage writes only the sentinel (a valid, zero-length Bolt message).

type Commit

type Commit struct{}

Commit commits the current explicit transaction. Fields: none.

type Discard

type Discard struct {
	N   int64
	QID int64
}

Discard discards pending records without streaming them to the client. Fields: [extra map {n: int64, qid: int64}].

type Failure

type Failure struct {
	Code    string
	Message string
}

Failure indicates that the preceding request failed. Fields: [metadata map {code string, message string}].

type Goodbye

type Goodbye struct{}

Goodbye signals orderly connection teardown. Fields: none.

type Hello

type Hello struct {
	Extra map[string]packstream.Value
}

Hello is sent by the client to initiate a Bolt connection. Fields: [extra map] containing agent, bolt_agent, scheme, principal, credentials, routing, and other driver metadata.

type Ignored

type Ignored struct{}

Ignored indicates that the preceding request was ignored (e.g., the connection is in a failed state). Fields: none.

type Logoff

type Logoff struct{}

Logoff ends an authenticated session without closing the connection. Fields: none.

type Logon

type Logon struct {
	Auth map[string]packstream.Value
}

Logon sends authentication credentials on an established connection. Fields: [auth map].

type Pull

type Pull struct {
	N   int64
	QID int64
}

Pull requests records from the server. Fields: [extra map {n: int64, qid: int64}]. n=-1 means pull all; qid=-1 means the most recent query.

type Record

type Record struct {
	Data []packstream.Value
}

Record carries one row of result data from a query. Fields: [data list].

type Reset

type Reset struct{}

Reset returns the connection to a clean, ready state, discarding any pending results and rolling back any open transaction. Fields: none.

type Rollback

type Rollback struct{}

Rollback rolls back the current explicit transaction. Fields: none.

type Route

type Route struct {
	DB        packstream.Value // string or nil
	Routing   map[string]packstream.Value
	Bookmarks []packstream.Value
}

Route requests routing table information. Fields: [routing map, bookmarks list, db string|null].

type Run

type Run struct {
	Parameters map[string]packstream.Value
	Extra      map[string]packstream.Value
	Query      string
}

Run submits a Cypher query for execution. Fields: [query string, parameters map, extra map].

type Success

type Success struct {
	Metadata map[string]packstream.Value
}

Success indicates that the preceding request succeeded. Fields: [metadata map].

type Version

type Version struct {
	Major, Minor uint8
}

Version represents a Bolt protocol version.

func Negotiate

func Negotiate(ctx context.Context, conn net.Conn) (Version, error)

Negotiate performs the Bolt handshake on conn.

The client sends a 20-byte payload: 4-byte magic followed by four 4-byte version slots. Each slot is big-endian and laid out as:

[0x00, minor_range, minor, major]

minor_range > 0 means the client accepts versions in the range [minor-minor_range, minor], allowing a range offer in a single slot.

Negotiate selects the highest version from SupportedVersions that falls within any offered range, writes back 4 bytes ([0x00, 0x00, minor, major]), and returns the agreed Version. The context deadline, if set, governs I/O timeouts via conn.SetDeadline.

Returns ErrBadMagic if the magic preamble is wrong, ErrNoCommonVersion if no version matches, or an I/O error if the connection fails mid-handshake.

Jump to

Keyboard shortcuts

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