Documentation
¶
Overview ¶
Package serialize implements fast bitpacked binary serialization for Go.
It is a pure Go port of the C++ serialize library (github.com/mas-bandwidth/serialize) and produces bit-for-bit identical output, so streams written by one language can be read by the other.
It has the following features:
- Serialize a bool with only one bit
- Serialize any integer value from [1,64] bits writing only that number of bits to the buffer
- Serialize signed integer values in [min,max] writing only the required bits to the buffer
- Serialize floats, doubles, compressed floats, strings, byte arrays, and integers relative to another integer
- Alignment support so you can align your bitstream to a byte boundary whenever you want
- Unified serialization through the Stream interface, so you can write one function that handles read, write and measure
The bit stream is written to memory in little endian order, which is considered network byte order for this library.
Values read from a stream are untrusted network data: every read is bounds checked and range validated, and the first failure latches an error on the stream. Serialize methods return that error and become no-ops once it is set, so you can either check each call or serialize an entire object and check Stream.Err once at the end — with one rule: a value that controls a loop must have its error checked before the loop uses it, because after an error values are never updated again and a loop waiting for one spins forever on a truncated or malicious packet. Use Continue for sentinel-driven loops, and check the error on serialized loop counts.
Example (UnifiedSerialization) ¶
Write one serialize function per type and use it for write, read and measure.
package main
import (
"fmt"
"github.com/mas-bandwidth/serialize.go"
)
type Vector struct {
X, Y, Z float32
}
func (v *Vector) Serialize(stream serialize.Stream) error {
stream.SerializeFloat32(&v.X)
stream.SerializeFloat32(&v.Y)
stream.SerializeFloat32(&v.Z)
return stream.Err()
}
type RigidBody struct {
Position Vector
LinearVelocity Vector
AtRest bool
}
// Serialize is a unified serialize function: the same code writes, reads and measures.
func (b *RigidBody) Serialize(stream serialize.Stream) error {
stream.SerializeObject(&b.Position)
stream.SerializeBool(&b.AtRest)
if !b.AtRest {
stream.SerializeObject(&b.LinearVelocity)
} else if stream.IsReading() {
b.LinearVelocity = Vector{}
}
return stream.Err()
}
func main() {
buffer := make([]byte, 256)
body := RigidBody{
Position: Vector{X: 10, Y: 20, Z: 30},
LinearVelocity: Vector{X: 1, Y: 2, Z: 3},
}
writeStream := serialize.NewWriteStream(buffer)
if err := body.Serialize(writeStream); err != nil {
panic(err)
}
writeStream.Flush()
packet := writeStream.Data()
fmt.Printf("wrote %d bytes\n", len(packet))
var received RigidBody
readStream := serialize.NewReadStream(packet)
if err := received.Serialize(readStream); err != nil {
panic(err)
}
fmt.Println(received.Position, received.AtRest)
}
Output: wrote 25 bytes {10 20 30} false
Example (VariantSerialization) ¶
Serialize a discriminated union: a type field followed by fields that depend on it.
package main
import (
"fmt"
"github.com/mas-bandwidth/serialize.go"
)
// An Address is a discriminated union: the type field decides which fields follow on
// the wire. Ported from example.cpp in the C++ serialize library.
const (
addressNone = iota
addressIPv4
addressIPv6
numAddressTypes
)
type Address struct {
Type int32
IPv4 [4]uint8
IPv6 [8]uint16
Port uint16
}
func (a *Address) Serialize(stream serialize.Stream) error {
if err := stream.SerializeInt(&a.Type, addressNone, numAddressTypes-1); err != nil {
return err
}
switch a.Type {
case addressIPv4:
for i := range a.IPv4 {
stream.SerializeUint8(&a.IPv4[i])
}
case addressIPv6:
for i := range a.IPv6 {
stream.SerializeUint16(&a.IPv6[i])
}
}
stream.SerializeUint16(&a.Port)
return stream.Err()
}
func main() {
buffer := make([]byte, 64)
address := Address{Type: addressIPv4, IPv4: [4]uint8{127, 0, 0, 1}, Port: 40000}
writeStream := serialize.NewWriteStream(buffer)
if err := address.Serialize(writeStream); err != nil {
panic(err)
}
writeStream.Flush()
var received Address
readStream := serialize.NewReadStream(writeStream.Data())
if err := received.Serialize(readStream); err != nil {
panic(err)
}
fmt.Printf("%d.%d.%d.%d:%d\n", received.IPv4[0], received.IPv4[1], received.IPv4[2], received.IPv4[3], received.Port)
}
Output: 127.0.0.1:40000
Index ¶
- Variables
- func BitsRequired(min, max uint32) int
- func BitsRequired64(min, max uint64) int
- func Continue(stream Stream, more *bool) bool
- func SignedToUnsigned(n int32) uint32
- func UnsignedToSigned(n uint32) int32
- func Until(stream Stream, done *bool) bool
- type BitReader
- func (r *BitReader) AlignBits() int
- func (r *BitReader) BitsRead() int64
- func (r *BitReader) BitsRemaining() int64
- func (r *BitReader) ReadAlign() bool
- func (r *BitReader) ReadBits(bits int) uint32
- func (r *BitReader) ReadBytes(data []byte)
- func (r *BitReader) Reset(data []byte)
- func (r *BitReader) WouldReadPastEnd(bits int) bool
- type BitWriter
- func (w *BitWriter) AlignBits() int
- func (w *BitWriter) BitsAvailable() int64
- func (w *BitWriter) BitsWritten() int64
- func (w *BitWriter) BytesWritten() int64
- func (w *BitWriter) Data() []byte
- func (w *BitWriter) FlushBits()
- func (w *BitWriter) Reset(buffer []byte)
- func (w *BitWriter) WriteAlign()
- func (w *BitWriter) WriteBits(value uint32, bits int)
- func (w *BitWriter) WriteBytes(data []byte)
- type MeasureStream
- func (s *MeasureStream) AlignBits() int
- func (s *MeasureStream) BitsProcessed() int64
- func (s *MeasureStream) BytesProcessed() int64
- func (s *MeasureStream) Context() any
- func (s *MeasureStream) Err() error
- func (s *MeasureStream) IsReading() bool
- func (s *MeasureStream) IsWriting() bool
- func (s *MeasureStream) Reset()
- func (s *MeasureStream) SerializeAlign() error
- func (s *MeasureStream) SerializeBits(value *uint32, bits int) error
- func (s *MeasureStream) SerializeBits64(value *uint64, bits int) error
- func (s *MeasureStream) SerializeBool(value *bool) error
- func (s *MeasureStream) SerializeBytes(data []byte) error
- func (s *MeasureStream) SerializeCompressedFloat32(value *float32, min, max, resolution float32) error
- func (s *MeasureStream) SerializeFloat32(value *float32) error
- func (s *MeasureStream) SerializeFloat64(value *float64) error
- func (s *MeasureStream) SerializeInt(value *int32, min, max int32) error
- func (s *MeasureStream) SerializeInt64(value *int64, min, max int64) error
- func (s *MeasureStream) SerializeIntRelative(previous int32, current *int32) error
- func (s *MeasureStream) SerializeObject(object Serializer) error
- func (s *MeasureStream) SerializeString(value *string, bufferSize int) error
- func (s *MeasureStream) SerializeUint8(value *uint8) error
- func (s *MeasureStream) SerializeUint16(value *uint16) error
- func (s *MeasureStream) SerializeUint32(value *uint32) error
- func (s *MeasureStream) SerializeUint64(value *uint64) error
- func (s *MeasureStream) SerializeWideString(value *string, bufferSize int) error
- func (s *MeasureStream) SetContext(context any)
- type ReadStream
- func (s *ReadStream) AlignBits() int
- func (s *ReadStream) BitsProcessed() int64
- func (s *ReadStream) BytesProcessed() int64
- func (s *ReadStream) Context() any
- func (s *ReadStream) Err() error
- func (s *ReadStream) IsReading() bool
- func (s *ReadStream) IsWriting() bool
- func (s *ReadStream) Reset(data []byte)
- func (s *ReadStream) SerializeAlign() error
- func (s *ReadStream) SerializeBits(value *uint32, bits int) error
- func (s *ReadStream) SerializeBits64(value *uint64, bits int) error
- func (s *ReadStream) SerializeBool(value *bool) error
- func (s *ReadStream) SerializeBytes(data []byte) error
- func (s *ReadStream) SerializeCompressedFloat32(value *float32, min, max, resolution float32) error
- func (s *ReadStream) SerializeFloat32(value *float32) error
- func (s *ReadStream) SerializeFloat64(value *float64) error
- func (s *ReadStream) SerializeInt(value *int32, min, max int32) error
- func (s *ReadStream) SerializeInt64(value *int64, min, max int64) error
- func (s *ReadStream) SerializeIntRelative(previous int32, current *int32) error
- func (s *ReadStream) SerializeObject(object Serializer) error
- func (s *ReadStream) SerializeString(value *string, bufferSize int) error
- func (s *ReadStream) SerializeUint8(value *uint8) error
- func (s *ReadStream) SerializeUint16(value *uint16) error
- func (s *ReadStream) SerializeUint32(value *uint32) error
- func (s *ReadStream) SerializeUint64(value *uint64) error
- func (s *ReadStream) SerializeWideString(value *string, bufferSize int) error
- func (s *ReadStream) SetContext(context any)
- type Serializer
- type Stream
- type WriteStream
- func (s *WriteStream) AlignBits() int
- func (s *WriteStream) BitsProcessed() int64
- func (s *WriteStream) BytesProcessed() int64
- func (s *WriteStream) Context() any
- func (s *WriteStream) Data() []byte
- func (s *WriteStream) Err() error
- func (s *WriteStream) Flush()
- func (s *WriteStream) IsReading() bool
- func (s *WriteStream) IsWriting() bool
- func (s *WriteStream) Reset(buffer []byte)
- func (s *WriteStream) SerializeAlign() error
- func (s *WriteStream) SerializeBits(value *uint32, bits int) error
- func (s *WriteStream) SerializeBits64(value *uint64, bits int) error
- func (s *WriteStream) SerializeBool(value *bool) error
- func (s *WriteStream) SerializeBytes(data []byte) error
- func (s *WriteStream) SerializeCompressedFloat32(value *float32, min, max, resolution float32) error
- func (s *WriteStream) SerializeFloat32(value *float32) error
- func (s *WriteStream) SerializeFloat64(value *float64) error
- func (s *WriteStream) SerializeInt(value *int32, min, max int32) error
- func (s *WriteStream) SerializeInt64(value *int64, min, max int64) error
- func (s *WriteStream) SerializeIntRelative(previous int32, current *int32) error
- func (s *WriteStream) SerializeObject(object Serializer) error
- func (s *WriteStream) SerializeString(value *string, bufferSize int) error
- func (s *WriteStream) SerializeUint8(value *uint8) error
- func (s *WriteStream) SerializeUint16(value *uint16) error
- func (s *WriteStream) SerializeUint32(value *uint32) error
- func (s *WriteStream) SerializeUint64(value *uint64) error
- func (s *WriteStream) SerializeWideString(value *string, bufferSize int) error
- func (s *WriteStream) SetContext(context any)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrOverflow is returned when a read would go past the end of the buffer, or a write // would go past the end of the buffer. ErrOverflow = errors.New("serialize: stream overflow") // ErrValueOutOfRange is returned when a value is outside the range it is serialized with. // On read this typically means the packet is corrupt or maliciously crafted. ErrValueOutOfRange = errors.New("serialize: value out of range") // ErrAlign is returned when the zero pad bits read by an align are not zero. // This typically means the read and write serialize functions don't match. ErrAlign = errors.New("serialize: nonzero padding bits in align") )
Functions ¶
func BitsRequired ¶
BitsRequired returns the number of bits required to serialize an integer in range [min,max].
func BitsRequired64 ¶
BitsRequired64 returns the number of bits required to serialize a 64 bit integer in range [min,max]. The result is in [0,64].
func Continue ¶
Continue serializes *more as a single continuation bit and reports whether a sentinel-driven loop should proceed, folding the stream error state into the loop condition in the style of bufio.Scanner: it returns false as soon as the stream has an error, so loops of this form always terminate on truncated or malicious data, bounded by the size of the packet.
hasNext := ... // when writing: true if there is a first element
for serialize.Continue(stream, &hasNext) {
// serialize one element
// when writing: set hasNext = true if there is another element
}
if err := stream.Err(); err != nil {
return err
}
Never write the loop as `for hasNext { stream.SerializeBool(&hasNext); ... }`: once the stream has an error the failed read leaves hasNext unmodified and the loop never exits. For wire formats with the opposite bit polarity — a termination bit rather than a continuation bit — use Until. See the README section on reading untrusted data.
Example ¶
Serialize a variable length sequence with a continuation bit per element. Continue folds the stream error state into the loop condition, so the loop always terminates on truncated or malicious data.
package main
import (
"fmt"
"github.com/mas-bandwidth/serialize.go"
)
func main() {
buffer := make([]byte, 64)
items := []uint32{10, 20, 30}
writeStream := serialize.NewWriteStream(buffer)
i := 0
hasNext := len(items) > 0
for serialize.Continue(writeStream, &hasNext) {
writeStream.SerializeBits(&items[i], 8)
i++
hasNext = i < len(items)
}
writeStream.Flush()
readStream := serialize.NewReadStream(writeStream.Data())
var received []uint32
hasNext = true
for serialize.Continue(readStream, &hasNext) {
var item uint32
readStream.SerializeBits(&item, 8)
received = append(received, item)
}
if err := readStream.Err(); err != nil {
panic(err)
}
fmt.Println(received)
}
Output: [10 20 30]
func SignedToUnsigned ¶
SignedToUnsigned converts a signed integer to an unsigned integer with zig-zag encoding. 0,-1,+1,-2,+2... becomes 0,1,2,3,4...
func UnsignedToSigned ¶
UnsignedToSigned converts an unsigned integer to a signed integer with zig-zag encoding. 0,1,2,3,4... becomes 0,-1,+1,-2,+2...
func Until ¶
Until serializes *done as a single termination bit and reports whether a sentinel-driven loop should proceed. It is the inverse of Continue, for wire formats that mark the end of a sequence with a true bit instead of marking each element with a continuation bit — the polarity cannot be flipped without changing the wire format, so both helpers exist. Like Continue, it returns false as soon as the stream has an error, so loops of this form always terminate on truncated or malicious data, bounded by the size of the packet.
done := ... // when writing: true if the sequence is empty
for serialize.Until(stream, &done) {
// serialize one element
// when writing: set done = true after the last element
}
if err := stream.Err(); err != nil {
return err
}
Never write the loop as `for !done { stream.SerializeBool(&done); ... }`: once the stream has an error the failed read leaves done unmodified and the loop never exits.
Types ¶
type BitReader ¶
type BitReader struct {
// contains filtered or unexported fields
}
BitReader reads bitpacked integer values from a buffer.
The reader relies on the user reconstructing the exact same set of bit reads as bit writes when the buffer was written. This is an unattributed bitpacked binary stream!
Reads are effectively branchless: each read loads a 64 bit little endian window at the current byte position and shifts by the bit remainder. There is no scratch state and no refill loop, so reads carry no dependency between calls other than advancing the bit index.
Any buffer size is supported. For the fastest reads, keep at least 7 bytes of slack in the backing array beyond the data — for example, read packets into a large buffer and slice the packet out of it. The reader detects the slack via cap() and uses the fully branchless window load everywhere; without slack, reads near the end of the buffer assemble the window from the remaining bytes instead. Slack bytes are loaded but never interpreted: bits past the end of the data cannot reach the output of a read.
The zero value is an exhausted reader: create one with NewBitReader, or Reset one onto a buffer before use.
func NewBitReader ¶
NewBitReader creates a bit reader that reads the bitpacked data in the given slice. The slice does not need any particular alignment. Buffer sizes are effectively unlimited, because bit counts are stored in 64 bit signed integers.
func (*BitReader) AlignBits ¶
AlignBits returns the number of align bits that would be read, if an align was read right now. The result is in [0,7], where 0 means the stream is already byte aligned.
func (*BitReader) BitsRemaining ¶
BitsRemaining returns the number of bits still available to read. For example, if the buffer size is 8 bytes and 10 bits have been read, 54 bits remain.
func (*BitReader) ReadAlign ¶
ReadAlign reads an align, corresponding to a WriteAlign call when the buffer was written, and skips ahead to the next byte boundary. As a safety check, it verifies that the padding bits are zero and returns false if they are not; this typically aborts the packet read.
func (*BitReader) ReadBits ¶
ReadBits reads bits from the buffer and returns the integer value read, in range [0,(1<<bits)-1]. bits must be in [1,32]. Panics if the read would go past the end of the buffer: check WouldReadPastEnd first when reading untrusted data, or use ReadStream, which performs all checks and returns errors instead.
func (*BitReader) ReadBytes ¶
ReadBytes reads len(data) bytes from the bit stream into data, corresponding to a WriteBytes call when the buffer was written. The reader must be aligned to a byte boundary. Panics if the read would go past the end of the buffer: bounds check with BitsRemaining first when reading untrusted data, or use ReadStream.
func (*BitReader) Reset ¶
Reset points the bit reader at a data slice and clears all read state, allowing a single reader to be reused without allocation.
func (*BitReader) WouldReadPastEnd ¶
WouldReadPastEnd returns true if reading the given number of bits would read past the end of the buffer.
type BitWriter ¶
type BitWriter struct {
// contains filtered or unexported fields
}
BitWriter bitpacks unsigned integer values to a buffer.
Integer bit values are written to a 64 bit scratch value from right to left. Once the scratch fills to 64 bits it is stored to memory as a qword and the handful of bits that spilled past 64 carry over into the next scratch. Flushing half as often as a dword design makes writes significantly faster.
The bit stream is written to memory in little endian order, which is considered network byte order for this library. The output is bit-for-bit identical to the C++ serialize library.
IMPORTANT: The buffer size must be a multiple of 8 bytes, because words are stored to memory 8 bytes at a time. Bytes past the end of the written data are only ever written as zeros. The buffer does not need any particular alignment.
The zero value is not usable: create a BitWriter with NewBitWriter, or Reset one onto a buffer before use.
Example ¶
Use the bitpacker directly when you want full control over each bit.
package main
import (
"fmt"
"github.com/mas-bandwidth/serialize.go"
)
func main() {
buffer := make([]byte, 256)
writer := serialize.NewBitWriter(buffer)
writer.WriteBits(0, 1)
writer.WriteBits(1, 1)
writer.WriteBits(10, 8)
writer.WriteBits(255, 8)
writer.WriteBits(1000, 10)
writer.WriteBits(50000, 16)
writer.WriteBits(9999999, 32)
writer.FlushBits()
fmt.Printf("wrote %d bytes\n", writer.BytesWritten())
reader := serialize.NewBitReader(writer.Data())
fmt.Println(reader.ReadBits(1))
fmt.Println(reader.ReadBits(1))
fmt.Println(reader.ReadBits(8))
fmt.Println(reader.ReadBits(8))
fmt.Println(reader.ReadBits(10))
fmt.Println(reader.ReadBits(16))
fmt.Println(reader.ReadBits(32))
}
Output: wrote 10 bytes 0 1 10 255 1000 50000 9999999
func NewBitWriter ¶
NewBitWriter creates a bit writer that fills the given buffer with bitpacked data. The buffer size must be a multiple of 8 bytes. Buffer sizes are effectively unlimited, because bit counts are stored in 64 bit signed integers.
func (*BitWriter) AlignBits ¶
AlignBits returns the number of align bits that would be written, if an align was written right now. The result is in [0,7], where 0 means the stream is already byte aligned.
func (*BitWriter) BitsAvailable ¶
BitsAvailable returns the number of bits still available to write. For example, if the buffer size is 8 bytes and 10 bits have been written, 54 bits are available.
func (*BitWriter) BitsWritten ¶
BitsWritten returns the number of bits written so far.
func (*BitWriter) BytesWritten ¶
BytesWritten returns the number of bytes flushed to memory, which is the number of bits written rounded up to the next byte. This is effectively the size of the packet you should send after you have finished bitpacking values with this writer.
IMPORTANT: Call FlushBits first, otherwise you risk missing the last word of data.
func (*BitWriter) Data ¶
Data returns the written portion of the buffer: the first BytesWritten bytes of the buffer passed to the writer.
IMPORTANT: Call FlushBits first, otherwise you risk missing the last word of data.
func (*BitWriter) FlushBits ¶
func (w *BitWriter) FlushBits()
FlushBits flushes any remaining bits in the scratch to memory. Call this once after you have finished writing bits. The flush stores a full qword: the buffer size is a multiple of 8 so this stays in bounds, and bytes past the written data are only ever written as zeros.
FlushBits ends the write: writing more bits after a mid-stream flush corrupts the stream, because the flushed partial word cannot be resumed.
func (*BitWriter) Reset ¶
Reset points the bit writer at a buffer and clears all write state, allowing a single writer to be reused without allocation. The buffer size must be a multiple of 8 bytes.
func (*BitWriter) WriteAlign ¶
func (w *BitWriter) WriteAlign()
WriteAlign pads the bit stream with zeros so the bit index becomes a multiple of 8. This is useful if you want to write data that should be byte aligned, for example an array of bytes or a string. If the current bit index is already a multiple of 8, nothing is written.
func (*BitWriter) WriteBits ¶
WriteBits writes the low order bits of value to the buffer, without padding to the nearest byte. A boolean value writes just 1 bit, a value in [0,31] can be written with just 5 bits and so on. bits must be in [1,32]; bits of value above that count are ignored. Panics if the write would go past the end of the buffer.
IMPORTANT: When you have finished writing, call FlushBits, otherwise the last word of data will not get flushed to memory!
func (*BitWriter) WriteBytes ¶
WriteBytes writes an array of bytes to the bit stream. Use this when you have to copy a large block of data into your bitstream. Faster than writing each byte via WriteBits(value, 8), because the aligned middle of the data is block copied into the buffer without bitpacking. The writer must be aligned to a byte boundary: call WriteAlign first.
type MeasureStream ¶
type MeasureStream struct {
// contains filtered or unexported fields
}
MeasureStream counts how many bits it would take to serialize something, without writing any data. It acts like a write stream (IsWriting is true), so a unified serialize function measures the exact same fields it would write.
When the serialization includes alignment to byte boundaries, the measurement is an estimate rather than exact, because the true pad depends on where the object lands in the final bit stream. The estimate is guaranteed to be conservative: every align is counted as the worst case 7 bits.
Example ¶
Measure how many bytes an object needs before allocating a buffer and writing it.
package main
import (
"fmt"
"github.com/mas-bandwidth/serialize.go"
)
type Vector struct {
X, Y, Z float32
}
func (v *Vector) Serialize(stream serialize.Stream) error {
stream.SerializeFloat32(&v.X)
stream.SerializeFloat32(&v.Y)
stream.SerializeFloat32(&v.Z)
return stream.Err()
}
type RigidBody struct {
Position Vector
LinearVelocity Vector
AtRest bool
}
// Serialize is a unified serialize function: the same code writes, reads and measures.
func (b *RigidBody) Serialize(stream serialize.Stream) error {
stream.SerializeObject(&b.Position)
stream.SerializeBool(&b.AtRest)
if !b.AtRest {
stream.SerializeObject(&b.LinearVelocity)
} else if stream.IsReading() {
b.LinearVelocity = Vector{}
}
return stream.Err()
}
func main() {
body := RigidBody{
Position: Vector{X: 10, Y: 20, Z: 30},
LinearVelocity: Vector{X: 1, Y: 2, Z: 3},
}
measureStream := serialize.NewMeasureStream()
if err := body.Serialize(measureStream); err != nil {
panic(err)
}
// round up to a multiple of 8 bytes, as the write stream requires
bufferSize := int(measureStream.BytesProcessed()+7) / 8 * 8
writeStream := serialize.NewWriteStream(make([]byte, bufferSize))
if err := body.Serialize(writeStream); err != nil {
panic(err)
}
writeStream.Flush()
fmt.Printf("measured %d bytes, wrote %d bytes\n", measureStream.BytesProcessed(), writeStream.BytesProcessed())
}
Output: measured 25 bytes, wrote 25 bytes
func NewMeasureStream ¶
func NewMeasureStream() *MeasureStream
NewMeasureStream creates a measure stream. The zero value is also ready to use.
func (*MeasureStream) AlignBits ¶
func (s *MeasureStream) AlignBits() int
AlignBits returns the worst case align of 7 bits. The number of bits required for alignment depends on where an object lands in the final bit stream, so the measurement is conservative.
func (*MeasureStream) BitsProcessed ¶
func (s *MeasureStream) BitsProcessed() int64
BitsProcessed returns the number of bits measured so far.
func (*MeasureStream) BytesProcessed ¶
func (s *MeasureStream) BytesProcessed() int64
BytesProcessed returns the number of bits measured so far, rounded up to the next byte.
func (*MeasureStream) Context ¶
func (s *MeasureStream) Context() any
Context returns the context value set on the stream. It may be nil.
func (*MeasureStream) Err ¶
func (s *MeasureStream) Err() error
Err returns the first error latched on the stream, or nil.
func (*MeasureStream) IsWriting ¶
func (s *MeasureStream) IsWriting() bool
IsWriting returns true: a measure stream behaves like a write stream so that unified serialize functions measure exactly what they would write.
func (*MeasureStream) Reset ¶
func (s *MeasureStream) Reset()
Reset clears the measured bit count and any latched error. The context is kept.
func (*MeasureStream) SerializeAlign ¶
func (s *MeasureStream) SerializeAlign() error
SerializeAlign measures the conservative worst case align of 7 bits.
func (*MeasureStream) SerializeBits ¶
func (s *MeasureStream) SerializeBits(value *uint32, bits int) error
SerializeBits measures bits, which must be in [1,32].
func (*MeasureStream) SerializeBits64 ¶
func (s *MeasureStream) SerializeBits64(value *uint64, bits int) error
SerializeBits64 measures bits, which must be in [1,64].
func (*MeasureStream) SerializeBool ¶
func (s *MeasureStream) SerializeBool(value *bool) error
SerializeBool measures 1 bit.
func (*MeasureStream) SerializeBytes ¶
func (s *MeasureStream) SerializeBytes(data []byte) error
SerializeBytes measures a worst case align plus the data bytes.
func (*MeasureStream) SerializeCompressedFloat32 ¶
func (s *MeasureStream) SerializeCompressedFloat32(value *float32, min, max, resolution float32) error
SerializeCompressedFloat32 measures the bits required for the quantized range.
func (*MeasureStream) SerializeFloat32 ¶
func (s *MeasureStream) SerializeFloat32(value *float32) error
SerializeFloat32 measures 32 bits.
func (*MeasureStream) SerializeFloat64 ¶
func (s *MeasureStream) SerializeFloat64(value *float64) error
SerializeFloat64 measures 64 bits.
func (*MeasureStream) SerializeInt ¶
func (s *MeasureStream) SerializeInt(value *int32, min, max int32) error
SerializeInt measures the bits required for the range [min,max]. Like a write, the value must be in range or ErrValueOutOfRange is returned.
func (*MeasureStream) SerializeInt64 ¶
func (s *MeasureStream) SerializeInt64(value *int64, min, max int64) error
SerializeInt64 measures the bits required for the range [min,max]. Like a write, the value must be in range or ErrValueOutOfRange is returned.
func (*MeasureStream) SerializeIntRelative ¶
func (s *MeasureStream) SerializeIntRelative(previous int32, current *int32) error
SerializeIntRelative measures the encoding of *current relative to previous, exactly as SerializeIntRelative on a write stream would encode it. previous must be less than *current or ErrValueOutOfRange is returned.
func (*MeasureStream) SerializeObject ¶
func (s *MeasureStream) SerializeObject(object Serializer) error
SerializeObject measures an object that implements Serializer.
func (*MeasureStream) SerializeString ¶
func (s *MeasureStream) SerializeString(value *string, bufferSize int) error
SerializeString measures the length prefix, a worst case align, and the string bytes. Like a write, the string must fit in bufferSize-1 bytes or ErrValueOutOfRange is returned.
func (*MeasureStream) SerializeUint8 ¶
func (s *MeasureStream) SerializeUint8(value *uint8) error
SerializeUint8 measures 8 bits.
func (*MeasureStream) SerializeUint16 ¶
func (s *MeasureStream) SerializeUint16(value *uint16) error
SerializeUint16 measures 16 bits.
func (*MeasureStream) SerializeUint32 ¶
func (s *MeasureStream) SerializeUint32(value *uint32) error
SerializeUint32 measures 32 bits.
func (*MeasureStream) SerializeUint64 ¶
func (s *MeasureStream) SerializeUint64(value *uint64) error
SerializeUint64 measures 64 bits.
func (*MeasureStream) SerializeWideString ¶
func (s *MeasureStream) SerializeWideString(value *string, bufferSize int) error
SerializeWideString measures the length prefix plus 32 bits per code point. Like a write, the string must fit in bufferSize-1 code points or ErrValueOutOfRange is returned.
func (*MeasureStream) SetContext ¶
func (s *MeasureStream) SetContext(context any)
SetContext sets a context value that serialize functions can retrieve with Context.
type ReadStream ¶
type ReadStream struct {
// contains filtered or unexported fields
}
ReadStream reads bitpacked data from a buffer. It wraps BitReader with bounds and range checking on every read, so maliciously crafted packets fail with errors instead of panicking or smuggling out of range values, and implements the Stream interface so unified serialize functions can read with it.
The zero value is an exhausted stream: create a ReadStream with NewReadStream, or Reset one onto a buffer before use.
func NewReadStream ¶
func NewReadStream(data []byte) *ReadStream
NewReadStream creates a read stream that reads the bitpacked data in the given slice. Any slice length is supported. For the fastest reads, keep at least 7 bytes of slack in the backing array beyond the data: see BitReader for details.
func (*ReadStream) AlignBits ¶
func (s *ReadStream) AlignBits() int
AlignBits returns the number of bits required to align the stream to the next byte boundary, in [0,7].
func (*ReadStream) BitsProcessed ¶
func (s *ReadStream) BitsProcessed() int64
BitsProcessed returns the number of bits read so far.
func (*ReadStream) BytesProcessed ¶
func (s *ReadStream) BytesProcessed() int64
BytesProcessed returns the number of bits read so far, rounded up to the next byte.
func (*ReadStream) Context ¶
func (s *ReadStream) Context() any
Context returns the context value set on the stream. It may be nil.
func (*ReadStream) Err ¶
func (s *ReadStream) Err() error
Err returns the first error latched on the stream, or nil.
func (*ReadStream) Reset ¶
func (s *ReadStream) Reset(data []byte)
Reset points the stream at a data slice and clears all read state including any latched error, allowing a single stream to be reused without allocation. The context is kept.
func (*ReadStream) SerializeAlign ¶
func (s *ReadStream) SerializeAlign() error
SerializeAlign skips ahead to the next byte boundary, verifying that the padding bits are zero. Nonzero padding fails with ErrAlign, which typically means the read and write serialize functions don't match.
func (*ReadStream) SerializeBits ¶
func (s *ReadStream) SerializeBits(value *uint32, bits int) error
SerializeBits reads bits into *value. bits must be in [1,32]. On success *value is in [0,(1<<bits)-1]; on failure it is left unmodified.
func (*ReadStream) SerializeBits64 ¶
func (s *ReadStream) SerializeBits64(value *uint64, bits int) error
SerializeBits64 reads bits into *value. bits must be in [1,64]. Values wider than 32 bits are read as the low dword first, then the high remainder.
func (*ReadStream) SerializeBool ¶
func (s *ReadStream) SerializeBool(value *bool) error
SerializeBool reads a boolean value from one bit.
func (*ReadStream) SerializeBytes ¶
func (s *ReadStream) SerializeBytes(data []byte) error
SerializeBytes aligns the stream to a byte boundary and block copies len(data) bytes into data.
func (*ReadStream) SerializeCompressedFloat32 ¶
func (s *ReadStream) SerializeCompressedFloat32(value *float32, min, max, resolution float32) error
SerializeCompressedFloat32 reads a quantized floating point value. On success *value is guaranteed to be in [min,max]; quantized values smuggled into the bit headroom fail with ErrValueOutOfRange.
func (*ReadStream) SerializeFloat32 ¶
func (s *ReadStream) SerializeFloat32(value *float32) error
SerializeFloat32 reads an uncompressed 32 bit floating point value.
func (*ReadStream) SerializeFloat64 ¶
func (s *ReadStream) SerializeFloat64(value *float64) error
SerializeFloat64 reads an uncompressed 64 bit floating point value.
func (*ReadStream) SerializeInt ¶
func (s *ReadStream) SerializeInt(value *int32, min, max int32) error
SerializeInt reads a signed integer into *value. On success *value is guaranteed to be in [min,max]; values smuggled into the bit headroom of the range fail with ErrValueOutOfRange.
func (*ReadStream) SerializeInt64 ¶
func (s *ReadStream) SerializeInt64(value *int64, min, max int64) error
SerializeInt64 reads a signed 64 bit integer into *value. On success *value is guaranteed to be in [min,max]; values smuggled into the bit headroom of the range fail with ErrValueOutOfRange.
func (*ReadStream) SerializeIntRelative ¶
func (s *ReadStream) SerializeIntRelative(previous int32, current *int32) error
SerializeIntRelative reads *current relative to previous. The value is reconstructed in the unsigned domain, so it wraps rather than overflowing when previous is near the top of the int32 range. The absolute fallback encoding validates that the decoded value is greater than previous.
func (*ReadStream) SerializeObject ¶
func (s *ReadStream) SerializeObject(object Serializer) error
SerializeObject reads an object that implements Serializer.
func (*ReadStream) SerializeString ¶
func (s *ReadStream) SerializeString(value *string, bufferSize int) error
SerializeString reads a string of fewer than bufferSize bytes into *value with a single allocation. On failure *value is left unmodified.
func (*ReadStream) SerializeUint8 ¶
func (s *ReadStream) SerializeUint8(value *uint8) error
SerializeUint8 reads an unsigned 8 bit integer.
func (*ReadStream) SerializeUint16 ¶
func (s *ReadStream) SerializeUint16(value *uint16) error
SerializeUint16 reads an unsigned 16 bit integer.
func (*ReadStream) SerializeUint32 ¶
func (s *ReadStream) SerializeUint32(value *uint32) error
SerializeUint32 reads an unsigned 32 bit integer.
func (*ReadStream) SerializeUint64 ¶
func (s *ReadStream) SerializeUint64(value *uint64) error
SerializeUint64 reads an unsigned 64 bit integer as the low dword then the high dword.
func (*ReadStream) SerializeWideString ¶
func (s *ReadStream) SerializeWideString(value *string, bufferSize int) error
SerializeWideString reads a string stored as 32 bits per code point into *value. Code points that are not valid (surrogates or values above 0x10FFFF) fail with ErrValueOutOfRange. On failure *value is left unmodified.
func (*ReadStream) SetContext ¶
func (s *ReadStream) SetContext(context any)
SetContext sets a context value that serialize functions can retrieve with Context.
type Serializer ¶
Serializer is the interface implemented by objects that serialize themselves to a stream. Write one Serialize method per type and it works for write, read and measure.
Return an error to abort serialization: the standard pattern is to call serialize methods for each field and return stream.Err() at the end, adding your own validation errors where needed.
IMPORTANT: A value that controls how much more work your serialize function does — a loop count or a continuation bit — must have its error checked before you use it. Once an error latches, serialize calls are no-ops that leave values unmodified, so a loop waiting for a serialized value to change spins forever on a truncated or malicious packet. Use Continue or Until for sentinel-driven loops.
type Stream ¶
type Stream interface {
// IsWriting returns true if the stream writes or measures values (WriteStream and
// MeasureStream), and false if it reads them.
IsWriting() bool
// IsReading returns true if the stream reads values (ReadStream).
IsReading() bool
// SerializeBits serializes the low order bits of an unsigned integer. bits must be
// in [1,32]. A value in [0,31] can be serialized with just 5 bits and so on.
SerializeBits(value *uint32, bits int) error
// SerializeBits64 serializes the low order bits of a 64 bit unsigned integer.
// bits must be in [1,64].
SerializeBits64(value *uint64, bits int) error
// SerializeInt serializes a signed integer in [min,max], writing only the number of
// bits required to represent the range. On read the value is guaranteed to be in
// [min,max] if the call succeeds.
SerializeInt(value *int32, min, max int32) error
// SerializeInt64 serializes a signed 64 bit integer in [min,max], writing only the
// number of bits required to represent the range. The full 64 bit range is supported.
SerializeInt64(value *int64, min, max int64) error
// SerializeUint8 serializes an unsigned 8 bit integer.
SerializeUint8(value *uint8) error
// SerializeUint16 serializes an unsigned 16 bit integer.
SerializeUint16(value *uint16) error
// SerializeUint32 serializes an unsigned 32 bit integer.
SerializeUint32(value *uint32) error
// SerializeUint64 serializes an unsigned 64 bit integer.
SerializeUint64(value *uint64) error
// SerializeBool serializes a boolean value with one bit.
SerializeBool(value *bool) error
// SerializeFloat32 serializes an uncompressed 32 bit floating point value.
SerializeFloat32(value *float32) error
// SerializeFloat64 serializes an uncompressed 64 bit floating point value.
SerializeFloat64(value *float64) error
// SerializeCompressedFloat32 serializes a floating point value in [min,max] with the
// given resolution, writing only the bits required for the quantized range. On write
// the value is clamped into [min,max]; on read it is guaranteed to be in [min,max]
// if the call succeeds.
SerializeCompressedFloat32(value *float32, min, max, resolution float32) error
// SerializeBytes serializes an array of bytes. The stream aligns to a byte boundary
// first, then block copies the data. Both sides must know the length: it is not sent.
SerializeBytes(data []byte) error
// SerializeString serializes a string of fewer than bufferSize bytes: the length is
// serialized in [0,bufferSize-1], the stream aligns to a byte boundary, then the
// string bytes are block copied. bufferSize mirrors the C++ API, where a string with
// its terminating null character must fit into the buffer, keeping streams
// compatible between the two languages.
SerializeString(value *string, bufferSize int) error
// SerializeWideString serializes a string as 32 bits per code point, wire compatible
// with serialize_wstring in the C++ library. The length is serialized in
// [0,bufferSize-1] code points. On read, code points that are not valid (surrogates
// or values above 0x10FFFF) fail with ErrValueOutOfRange.
SerializeWideString(value *string, bufferSize int) error
// SerializeAlign pads the stream with zero bits to the next byte boundary. On read
// the padding is verified to be zero.
SerializeAlign() error
// SerializeObject serializes an object that implements Serializer.
SerializeObject(object Serializer) error
// SerializeIntRelative serializes an integer relative to a previous integer, using
// fewer bits the closer the two values are. previous must be less than current.
SerializeIntRelative(previous int32, current *int32) error
// AlignBits returns the number of bits required to align the stream to the next byte
// boundary, in [0,7]. MeasureStream always returns the conservative worst case 7.
AlignBits() int
// BitsProcessed returns the number of bits written to, read from or measured on
// the stream.
BitsProcessed() int64
// BytesProcessed returns the number of bits processed rounded up to the next byte.
// After writing, this is effectively the packet size.
BytesProcessed() int64
// Err returns the first error latched on the stream, or nil.
Err() error
// SetContext sets a context value on the stream. The context lets you pass data
// through to your serialize functions, for example lookup tables or min/max ranges
// needed to read and write values. It mirrors the context pointer in the C++
// library and is unrelated to context.Context.
SetContext(context any)
// Context returns the context value set on the stream. It may be nil.
Context() any
}
Stream is the unified serialization interface implemented by WriteStream, ReadStream and MeasureStream. It is the Go equivalent of the templated stream parameter in the C++ serialize library: write one serialize function against Stream and it handles write, read and measure.
All serialize methods take pointers so the same call reads or writes the value depending on the stream. Use IsWriting and IsReading inside serialize functions for logic that differs between the two, such as initializing fields that are not sent.
Errors are sticky: the first failure latches on the stream and every later serialize call returns it without touching the stream, so you can either check every call or serialize a whole object and check Err once at the end. The one rule: a value that controls a loop must have its error checked before the loop runs, because after an error the value is never updated again. See Continue and the Serializer documentation.
type WriteStream ¶
type WriteStream struct {
// contains filtered or unexported fields
}
WriteStream writes bitpacked data to a buffer. It wraps BitWriter with overflow and range checking that returns errors instead of panicking, and implements the Stream interface so unified serialize functions can write with it.
The zero value is not usable: create a WriteStream with NewWriteStream, or Reset one onto a buffer before use.
func NewWriteStream ¶
func NewWriteStream(buffer []byte) *WriteStream
NewWriteStream creates a write stream that writes to the given buffer. The buffer size must be a multiple of 8 bytes, because the bit writer stores qwords to memory.
func (*WriteStream) AlignBits ¶
func (s *WriteStream) AlignBits() int
AlignBits returns the number of bits required to align the stream to the next byte boundary, in [0,7].
func (*WriteStream) BitsProcessed ¶
func (s *WriteStream) BitsProcessed() int64
BitsProcessed returns the number of bits written so far.
func (*WriteStream) BytesProcessed ¶
func (s *WriteStream) BytesProcessed() int64
BytesProcessed returns the number of bits written so far, rounded up to the next byte. This is effectively the packet size.
func (*WriteStream) Context ¶
func (s *WriteStream) Context() any
Context returns the context value set on the stream. It may be nil.
func (*WriteStream) Data ¶
func (s *WriteStream) Data() []byte
Data returns the written portion of the buffer: the packet you should send.
IMPORTANT: Call Flush first.
func (*WriteStream) Err ¶
func (s *WriteStream) Err() error
Err returns the first error latched on the stream, or nil.
func (*WriteStream) Flush ¶
func (s *WriteStream) Flush()
Flush flushes the last word of bits to memory. Always call this after you finish writing and before you call Data, or you risk truncating the last word of data. The flush ends the write: do not serialize more values after it.
func (*WriteStream) Reset ¶
func (s *WriteStream) Reset(buffer []byte)
Reset points the stream at a buffer and clears all write state including any latched error, allowing a single stream to be reused without allocation. The context is kept.
func (*WriteStream) SerializeAlign ¶
func (s *WriteStream) SerializeAlign() error
SerializeAlign pads the stream with zero bits to the next byte boundary.
func (*WriteStream) SerializeBits ¶
func (s *WriteStream) SerializeBits(value *uint32, bits int) error
SerializeBits writes the low order bits of *value. bits must be in [1,32].
func (*WriteStream) SerializeBits64 ¶
func (s *WriteStream) SerializeBits64(value *uint64, bits int) error
SerializeBits64 writes the low order bits of *value. bits must be in [1,64]. Values wider than 32 bits are written as the low dword first, then the high remainder.
func (*WriteStream) SerializeBool ¶
func (s *WriteStream) SerializeBool(value *bool) error
SerializeBool writes a boolean value with one bit.
func (*WriteStream) SerializeBytes ¶
func (s *WriteStream) SerializeBytes(data []byte) error
SerializeBytes aligns the stream to a byte boundary and block copies data into it.
func (*WriteStream) SerializeCompressedFloat32 ¶
func (s *WriteStream) SerializeCompressedFloat32(value *float32, min, max, resolution float32) error
SerializeCompressedFloat32 quantizes *value into [min,max] at the given resolution and writes only the bits required for the quantized range. The value is clamped into [min,max] before quantization; the !>= / !<= clamp form forces NaN into range too.
func (*WriteStream) SerializeFloat32 ¶
func (s *WriteStream) SerializeFloat32(value *float32) error
SerializeFloat32 writes an uncompressed 32 bit floating point value.
func (*WriteStream) SerializeFloat64 ¶
func (s *WriteStream) SerializeFloat64(value *float64) error
SerializeFloat64 writes an uncompressed 64 bit floating point value.
func (*WriteStream) SerializeInt ¶
func (s *WriteStream) SerializeInt(value *int32, min, max int32) error
SerializeInt writes *value, which must be in [min,max], using only the bits required to represent the range. Returns ErrValueOutOfRange if it is not.
func (*WriteStream) SerializeInt64 ¶
func (s *WriteStream) SerializeInt64(value *int64, min, max int64) error
SerializeInt64 writes *value, which must be in [min,max], using only the bits required to represent the range. Returns ErrValueOutOfRange if it is not.
func (*WriteStream) SerializeIntRelative ¶
func (s *WriteStream) SerializeIntRelative(previous int32, current *int32) error
SerializeIntRelative writes *current relative to previous, using fewer bits the closer the two values are. previous must be less than *current or ErrValueOutOfRange is returned. The difference is computed in the unsigned domain, so gaps wider than 2^31 wrap and fall through to the absolute 32 bit encoding.
func (*WriteStream) SerializeObject ¶
func (s *WriteStream) SerializeObject(object Serializer) error
SerializeObject writes an object that implements Serializer.
func (*WriteStream) SerializeString ¶
func (s *WriteStream) SerializeString(value *string, bufferSize int) error
SerializeString writes the length of *value in [0,bufferSize-1], aligns the stream to a byte boundary, then block copies the string bytes. Returns ErrValueOutOfRange if the string does not fit in bufferSize-1 bytes.
func (*WriteStream) SerializeUint8 ¶
func (s *WriteStream) SerializeUint8(value *uint8) error
SerializeUint8 writes an unsigned 8 bit integer.
func (*WriteStream) SerializeUint16 ¶
func (s *WriteStream) SerializeUint16(value *uint16) error
SerializeUint16 writes an unsigned 16 bit integer.
func (*WriteStream) SerializeUint32 ¶
func (s *WriteStream) SerializeUint32(value *uint32) error
SerializeUint32 writes an unsigned 32 bit integer.
func (*WriteStream) SerializeUint64 ¶
func (s *WriteStream) SerializeUint64(value *uint64) error
SerializeUint64 writes an unsigned 64 bit integer as the low dword then the high dword.
func (*WriteStream) SerializeWideString ¶
func (s *WriteStream) SerializeWideString(value *string, bufferSize int) error
SerializeWideString writes the length of *value in code points in [0,bufferSize-1], then each code point as 32 bits. Wire compatible with serialize_wstring in the C++ library. Returns ErrValueOutOfRange if the string does not fit in bufferSize-1 code points.
func (*WriteStream) SetContext ¶
func (s *WriteStream) SetContext(context any)
SetContext sets a context value that serialize functions can retrieve with Context.