163 lines
4.4 KiB
Go
163 lines
4.4 KiB
Go
// Package wire handles the few protobuf fields needed for bootstrap messages.
|
|
// Business messages remain opaque and belong to their own domain handlers.
|
|
package wire
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
var ErrMalformed = errors.New("malformed protobuf wire data")
|
|
|
|
type Field struct {
|
|
Number int
|
|
Type int
|
|
Value []byte
|
|
Start int
|
|
End int
|
|
}
|
|
|
|
func AppendVarint(dst []byte, field int, value uint64) []byte {
|
|
dst = binary.AppendUvarint(dst, uint64(field<<3))
|
|
return binary.AppendUvarint(dst, value)
|
|
}
|
|
|
|
func AppendBytes(dst []byte, field int, value []byte) []byte {
|
|
dst = binary.AppendUvarint(dst, uint64(field<<3|2))
|
|
dst = binary.AppendUvarint(dst, uint64(len(value)))
|
|
return append(dst, value...)
|
|
}
|
|
|
|
func AppendString(dst []byte, field int, value string) []byte {
|
|
return AppendBytes(dst, field, []byte(value))
|
|
}
|
|
|
|
// AppendFixed64 appends a protobuf fixed64 field. It is kept here instead of
|
|
// hand-building tags in domain packages so doubles use one canonical encoder.
|
|
func AppendFixed64(dst []byte, field int, value uint64) []byte {
|
|
dst = binary.AppendUvarint(dst, uint64(field<<3|1))
|
|
return binary.LittleEndian.AppendUint64(dst, value)
|
|
}
|
|
|
|
func AppendDouble(dst []byte, field int, value float64) []byte {
|
|
return AppendFixed64(dst, field, math.Float64bits(value))
|
|
}
|
|
|
|
// Walk visits wire fields without interpreting the payload or changing its bytes.
|
|
func Walk(data []byte, visit func(Field) error) error {
|
|
for pos := 0; pos < len(data); {
|
|
start := pos
|
|
tag, n := binary.Uvarint(data[pos:])
|
|
if n <= 0 || tag>>3 == 0 || tag>>3 > uint64(^uint(0)>>1) {
|
|
return fmt.Errorf("%w: field tag at %d", ErrMalformed, pos)
|
|
}
|
|
pos += n
|
|
field := Field{Number: int(tag >> 3), Type: int(tag & 7), Start: start}
|
|
switch field.Type {
|
|
case 0:
|
|
_, n = binary.Uvarint(data[pos:])
|
|
if n <= 0 {
|
|
return fmt.Errorf("%w: varint at %d", ErrMalformed, pos)
|
|
}
|
|
field.Value = data[pos : pos+n]
|
|
pos += n
|
|
case 1:
|
|
if len(data)-pos < 8 {
|
|
return ErrMalformed
|
|
}
|
|
field.Value = data[pos : pos+8]
|
|
pos += 8
|
|
case 2:
|
|
length, count := binary.Uvarint(data[pos:])
|
|
if count <= 0 || length > uint64(len(data)-pos-count) {
|
|
return fmt.Errorf("%w: length at %d", ErrMalformed, pos)
|
|
}
|
|
pos += count
|
|
field.Value = data[pos : pos+int(length)]
|
|
pos += int(length)
|
|
case 5:
|
|
if len(data)-pos < 4 {
|
|
return ErrMalformed
|
|
}
|
|
field.Value = data[pos : pos+4]
|
|
pos += 4
|
|
default:
|
|
return fmt.Errorf("%w: wire type %d", ErrMalformed, field.Type)
|
|
}
|
|
field.End = pos
|
|
if err := visit(field); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Varint(data []byte, number int) (uint64, bool, error) {
|
|
var result uint64
|
|
var found bool
|
|
err := Walk(data, func(field Field) error {
|
|
if field.Number == number && field.Type == 0 {
|
|
result, _ = binary.Uvarint(field.Value)
|
|
found = true
|
|
}
|
|
return nil
|
|
})
|
|
return result, found, err
|
|
}
|
|
|
|
func Bytes(data []byte, number int) ([]byte, bool, error) {
|
|
var result []byte
|
|
var found bool
|
|
err := Walk(data, func(field Field) error {
|
|
if field.Number == number && field.Type == 2 {
|
|
result = field.Value
|
|
found = true
|
|
}
|
|
return nil
|
|
})
|
|
return result, found, err
|
|
}
|
|
|
|
// ReplaceBytes keeps every unrelated field intact, including unknown fields.
|
|
func ReplaceBytes(data []byte, number int, value []byte) ([]byte, bool, error) {
|
|
var result []byte
|
|
var replaced bool
|
|
err := Walk(data, func(field Field) error {
|
|
if field.Number == number && field.Type == 2 && !replaced {
|
|
result = AppendBytes(result, number, value)
|
|
replaced = true
|
|
} else {
|
|
result = append(result, data[field.Start:field.End]...)
|
|
}
|
|
return nil
|
|
})
|
|
return result, replaced, err
|
|
}
|
|
|
|
// ReplaceVarint replaces the first varint field with number while preserving
|
|
// every unrelated (including unknown) protobuf field byte-for-byte. If the
|
|
// field is absent it is appended, which is required for proto3 zero values
|
|
// omitted from a versioned template.
|
|
func ReplaceVarint(data []byte, number int, value uint64) ([]byte, bool, error) {
|
|
var result []byte
|
|
var replaced bool
|
|
err := Walk(data, func(field Field) error {
|
|
if field.Number == number && field.Type == 0 && !replaced {
|
|
result = AppendVarint(result, number, value)
|
|
replaced = true
|
|
} else {
|
|
result = append(result, data[field.Start:field.End]...)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if !replaced {
|
|
result = AppendVarint(result, number, value)
|
|
}
|
|
return result, replaced, nil
|
|
}
|