Help us shape the future of Go! Take the 2026 Go Developer Survey

trace

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 16, 2026 License: GPL-3.0 Imports: 6 Imported by: 0

Documentation

Overview

Package trace is the binary codec for lap traces: the format described by decision D-1 of docs/SERVER.md, and the reason this module exists.

A 300-sample lap is twelve small integers per sample, about 30 kB as JSON. Stored as

structure of arrays → zig-zag delta → varint → zstd

the same lap is one to two kilobytes, because speed, throttle and track position all move smoothly and a smooth channel deltas down to a handful of bits per sample. The twelve channels are laid down as twelve independent streams rather than interleaved, so each stream contains only values of one magnitude and one shape, which is what makes the deltas small and the entropy coder effective.

Format

A blob is

byte 0      codec version, currently 1
bytes 1…k   the point count, as an unsigned varint
bytes k…    one zstd frame

and the plaintext of that frame is the twelve streams, back to back, in the field order of wire.TracePoint — t, v, thr, brk, g, r, st, p, lg, og, la, lo. Each stream is exactly n values; each value is the difference from its predecessor in the same stream, the first taken against zero, zig-zag mapped to an unsigned integer and written as a varint. Streams carry no length of their own: the count in the header says how many values each holds, which is one number to bounds-check instead of twelve.

The version byte is outside the compressed frame on purpose, so Version can answer without decompressing anything — the server stores it in a neighbouring smallint column and can migrate blobs without reading them. The point count is outside it too, so a blob claiming more than MaxPoints samples is refused before a single byte is decompressed.

Hostile input

Decode is the only part of this module that reads bytes it did not write. Every length and every offset is bounds-checked, the decompressor is capped at MaxPoints worth of plaintext, an over-long input is rejected before zstd is invoked at all, and the decoded trace is put through the same Validate that Encode applies. A truncated, corrupt or hostile blob returns an error. It never panics, and it never returns a trace that would not have been legal to encode.

Allocation

Encode performs one allocation: the returned slice, sized exactly. Decode performs none at all when the caller's destination slice is long enough, because it fills that slice in place and takes its scratch space from a sync.Pool. That is what lets the server's lap-ingest path decode forty laps without touching the heap.

Compatibility

The golden vectors in testdata/golden are the contract, and testdata/README.md says what may and may not change about them.

Index

Constants

View Source
const (
	MaxOffsetMs = 3_600_000 // t, ms: one hour
	MaxDistPct  = 1_000     // p, ‰ of the lap
	MaxSpeedKmh = 700       // v, km/h
	MaxPercent  = 100       // thr and brk, %
	MinGear     = -1        // g, reverse
	MaxGear     = 10        // g
	MaxRPM      = 30_000    // r, rpm
	MaxSteerDeg = 1_000     // st, degrees, symmetric
	MaxAccelG   = 1_000     // lg and og, g × 100, symmetric
	MaxLat      = 9_000_000 // la, degrees × 10⁵, symmetric
	MaxLon      = 18_000_000
)

The bounds of every channel, from the "Trace format" table of API-V1.md. A value outside them is refused by Validate, on the way in and on the way out.

Four of these the specification leaves open, and closing them is this package's decision, recorded here because it is a decision and not a reading:

  • t is documented only as "≥ 0". MaxOffsetMs is one hour, far longer than any lap of any circuit, and short enough that the difference between two offsets cannot overflow.
  • r is documented only as "≥ 0". MaxRPM is 30000, above any racing engine and any plausible sensor glitch.
  • la and lo are given no range at all. They are degrees × 10⁵, so the range is the range of latitude and longitude on Earth.

All four are wide enough that no honest sample reaches them, which is the test a bound like this has to pass.

View Source
const CodecVersion = 1

CodecVersion is the version byte at the head of every blob this package writes. It is the number the server keeps in its trace_codec column, and the only value Decode accepts.

Bumping it is the one way the format is allowed to change, and it is a change that must be made deliberately: see testdata/README.md.

View Source
const MaxEncodedBytes = 1 << 20

MaxEncodedBytes is the largest blob Decode will look at. A legal trace at MaxPoints cannot exceed roughly half of it even when every channel is incompressible noise, so this only ever rejects something that was never going to decode — and it rejects it before the decompressor is handed anything.

View Source
const MaxPoints = 4096

MaxPoints is the hard ceiling on the number of samples in one encoded trace. It is not the same thing as the server's advertised Limits.TracePoints, which is a policy a deployment chooses and which is typically 300: this is the structural bound of the codec itself, the number past which a blob is refused before it is decompressed. It exists so that an attacker cannot hand the server a two-byte header that turns into gigabytes of samples.

Variables

View Source
var ErrInvalidTrace = errors.New("trace: invalid trace")

ErrInvalidTrace reports a trace that breaks the rules of the wire format: a channel outside its documented range, or an offset that does not increase. Encode refuses to write one and Decode refuses to return one, so the rule holds in both directions and cannot be smuggled past either side.

View Source
var ErrMalformed = errors.New("trace: malformed blob")

ErrMalformed reports a blob that is truncated, corrupt, or carries bytes the format does not account for. It is the answer to every hostile input that is not simply too long.

View Source
var ErrTooManyPoints = errors.New("trace: too many points")

ErrTooManyPoints reports a trace, or a blob claiming to hold a trace, longer than MaxPoints.

View Source
var ErrVersion = errors.New("trace: unsupported codec version")

ErrVersion reports a blob whose codec version this build does not know. A server holding blobs of an older version keeps the older decoder, or migrates them; it never guesses.

Functions

func Decode

func Decode(b []byte, dst []wire.TracePoint) ([]wire.TracePoint, error)

Decode reads a blob back into dst and returns the result.

dst is reused: when it has room for the trace, Decode fills it in place and allocates nothing at all, which is what the server's ingest path depends on. Pass the slice from the previous call — its length is irrelevant, only its capacity is used — and keep the one Decode returns for the call after that.

Every length and offset in the blob is bounds-checked, the decompressor is capped, and the result is put through Validate before it is returned, so a truncated, corrupt or hostile blob produces an error and never a panic and never a trace that could not have been encoded. On any error dst is returned truncated to zero length rather than dropped, so the caller keeps its buffer.

A trace of no samples comes back as a zero-length slice, which is the caller's own slice emptied and is therefore nil if the caller passed nil. The wire format does not distinguish a nil trace from an empty one and neither does this: compare lengths, not slices.

func Encode

func Encode(pts []wire.TracePoint) ([]byte, error)

Encode writes pts as one self-describing blob.

It validates first: a trace that would not survive the round trip — a channel out of range, an offset that does not increase, more than MaxPoints samples — is refused here rather than stored and discovered later by whoever reads it back. A nil or empty slice is legal and encodes to a header and an empty frame.

Exactly one allocation is performed, the returned slice, and it is sized to fit. The scratch space the twelve streams are built in comes from a pool, so a server encoding lap after lap reuses the same few kilobytes for all of them.

func Validate

func Validate(pts []wire.TracePoint) error

Validate reports whether pts may be encoded, and is also applied to everything Decode produces.

The rules are the ones API-V1.md states: at most MaxPoints samples, every channel inside its documented range, and t strictly increasing. A trace with no GPS fix is valid — la and lo are optional as a pair and both zero means absent — so there is no rule about them beyond their range.

It allocates only when it has something to complain about.

func Version

func Version(b []byte) (int, error)

Version reads the codec version from the head of a blob without decompressing it, so a caller can route an old blob to an old decoder, or migrate a table, having read one byte of each row.

Types

This section is empty.

Jump to

Keyboard shortcuts

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