encoding.protobuf #
Description
encoding.protobuf is the Protocol Buffers binary wire format, in pure V. It has no C dependency and needs no protoc at build time.
It is two things:
- a runtime — a
Packeryou append fields to, anUnpackeryou pull them off, and the varint and fixed-width primitives they are built from - a schema vocabulary —
ProtoScalarandscalar_by_name, which is what lets one description of a message say which of the several protobuf types a Vi32should be treated as
Generating a codec
The usual way in is v pbgen, which reads a .proto file and writes the struct, its encode and decode, and the gRPC service declarations:
v pbgen -m kv -o kv/codec.v kv.proto
v pbgen -m kv -o kv/codec.v -grpc kv/service.v kv.proto
Only proto3 is supported. See v help pbgen for the options and cmd/tools/vpbgen/README.md for the tool.
What the generator emits
For a message, one struct, an encode, an encode_with, a decode_*, and a decode_*_with:
pub struct GetRequest {
pub mut:
// key is `string key = 1`.
key string
}
// encode serializes `msg` to the proto3 wire format. ...
pub fn (msg GetRequest) encode() ![]u8 {
return msg.encode_with(protobuf.EncodeOpts{})
}
// encode_with serializes `msg` with `opts`. ...
pub fn (msg GetRequest) encode_with(opts protobuf.EncodeOpts) ![]u8 {
mut packer := protobuf.new_packer(opts)
if opts.emit_defaults || msg.key.len > 0 {
packer.write_string(1, msg.key)!
}
return packer.bytes()
}
// decode_get_request parses the proto3 message `kv.GetRequest`. ...
pub fn decode_get_request(data []u8) !GetRequest {
return decode_get_request_with(data, protobuf.DecodeOpts{})
}
// decode_get_request_with parses the proto3 message `kv.GetRequest` with `opts`, ...
pub fn decode_get_request_with(data []u8, opts protobuf.DecodeOpts) !GetRequest {
mut out := GetRequest{}
mut unpacker := protobuf.new_unpacker(data, opts)
read_get_request(mut unpacker, mut out)!
return out
}
// read_get_request reads the fields of `kv.GetRequest` from `unpacker` into `out` ...
fn read_get_request(mut unpacker protobuf.Unpacker, mut out GetRequest) ! {
for !unpacker.eof() {
number, wire_type := unpacker.read_tag()!
match number {
1 {
protobuf.check_wire_type(1, wire_type, protobuf.WireType.length_delimited)!
out.key = unpacker.read_string()!
}
else {
// an unknown field, or one this build does not know
unpacker.skip_field(number, wire_type)!
}
}
}
}
A nested message is read through its parent's Unpacker, so DecodeOpts applies at every depth, and max_depth stops a payload that nests deeper than it allows with an error. A field this build does not know is skipped and dropped: it is not kept for re-encoding, so decoding and encoding again loses it. Set allow_unknown_fields: false to have it rejected instead.
Each field's number appears literally in every call its codec makes — the 1 in packer.write_string(1, msg.key) — and in the doc comment above the field. It is not inferred from the order of the fields, because a schema is free to number them out of order and to leave gaps.
The struct carries no attribute for the number; nothing would read it.
A few naming rules are worth knowing, because they are what makes the generated names predictable:
- A message keeps its proto name.
GetRequestin packagekvbecomesGetRequest, notKvGetRequest. A nested message is named the same way, since V has no nested types:Outer.InnerbecomesInner. Only a name two declarations both want is qualified with its package and enclosing messages, asOneItemandTwoItem, orOuterInner. - A field and an enum value become snake_case, since V refuses an uppercase letter in either:
userNamebecomesuser_nameandCOLOR_REDbecomescolor_red. A name that is a V keyword gets a trailing_, astype_. - A one-letter capital name is refused. V reserves those for generic template types.
- Names that are distinct in the schema but meet in V, such as
userNameanduser_namein one message, are refused rather than emitted.
Presence
Three shapes of presence show up in the generated struct:
| Schema | V field | Written |
|---|---|---|
string key = 1; |
key string |
when it is not the default |
optional bool flag = 1; |
flag ?bool |
whenever it is set |
oneof { int32 a = 1; } |
a ?i32 |
whenever it is set |
Inner inner = 1; |
inner ?Inner |
whenever it is set |
The first two rows are why an absent field and a field set to its default are different things in the second case and not the first. On the wire they are the same, so proto3 cannot tell them apart unless the schema asks for presence. A message field always has presence: one set to an empty message is written as an empty payload, and one never set is not written.
A message field that makes its message recursive, as Node child = 2; inside message Node does, is declared ?&Node, because V accepts a recursive struct only through an optional pointer.
Reading a member of a oneof clears the others, because the spec says the last member on the wire wins.
The wire format
A field is a tag followed by a value, and the tag is the field number shifted up three bits with a wire type in the low three:
| Wire | Meaning | Carries |
|---|---|---|
| 0 | varint | bool, int32, int64, uint32, uint64, sint*, enum |
| 1 | 64-bit | fixed64, sfixed64, double |
| 2 | length-delimited | string, bytes, embedded messages, packed repeats |
| 5 | 32-bit | fixed32, sfixed32, float |
Two rules account for most of what looks odd about the encoding:
- A negative
int32orint64costs ten bytes. The spec requires sign extension to 64 bits, and there is no compact negative varint form.sint32andsint64exist for this: they zigzag the value first, so -1 is one byte again. - A field holding its default is not written, because on the wire an absent field and a field set to its default are the same thing. A message that never carried a field decodes to the default with nothing in the input saying so.
Type mapping
| protobuf | V | Wire |
|---|---|---|
| bool | bool | varint |
| int32 | i32 | varint, negative sign-extended to 10 bytes |
| int64 | i64 / int | varint, negative sign-extended to 10 bytes |
| uint32 | u32 | varint |
| uint64 | u64 | varint |
| sint32 | i32 | varint, zigzagged |
| sint64 | i64 / int | varint, zigzagged |
| fixed32 | u32 | 4 little-endian bytes |
| sfixed32 | i32 | 4 little-endian bytes |
| float | f32 | 4 little-endian bytes |
| fixed64 | u64 | 8 little-endian bytes |
| sfixed64 | i64 / int | 8 little-endian bytes |
| double | f64 | 8 little-endian bytes |
| enum | enum | varint of the enum's integer value |
| string | string | length-delimited, UTF-8 |
| bytes | []u8 | length-delimited |
| message | struct | length-delimited |
| repeated | []T | packed for numeric T, one tag per item otherwise |
| map<K, V> | map[K]V | repeated Entry message with key = 1, value = 2 |
A V type does not always determine the encoding: i32 is int32, sint32, or sfixed32 depending on the schema, and only the schema knows which. That is what ProtoScalar is for.
i8, i16, u8, and u16 have no protobuf counterpart. Widen them to a 32- or 64-bit type, which costs nothing on the wire.
Options
@[params]
pub struct EncodeOpts {
pub:
// initial_cap is the capacity the buffer starts with.
initial_cap int = 256
// emit_defaults writes a field that holds its default value instead of
// leaving it off the wire.
emit_defaults bool
// validate_utf8 rejects a `string` field whose bytes are not valid UTF-8.
validate_utf8 bool
}
emit_defaults is the one to reach for when the peer expects explicit presence where proto3 would not keep it. The options also reach nested messages and map entries, so a field is held to the same rules however deep it sits.
validate_utf8 is off by default because it costs a pass over the bytes, and a bytes field is exempt either way: a string is defined to be UTF-8, so invalid bytes are a schema error worth rejecting, while a bytes field is opaque.
Writing a codec by hand
The manual API is Packer and Unpacker directly. The rule about defaults is the caller's, deliberately: a writer that silently dropped values would make the manual API impossible to use for a schema that keeps presence.
fn encode_get_request(msg GetRequest) ![]u8 {
mut packer := protobuf.new_packer(protobuf.EncodeOpts{})
if msg.key.len > 0 {
packer.write_string(1, msg.key)!
}
return packer.bytes()
}
Not implemented
- Groups, the deprecated proto2 construct the spec kept for compatibility. A tag can name one, so the wire types are listed, but a group's extent cannot be found without reading fields the reader does not understand, so this module refuses one rather than guessing. proto3 forbids them.
- proto2, and with it
requiredand proto2 default values. - Unknown field preservation. A field the generated code does not know is skipped, not kept, so a decode followed by an encode drops it.
Tests
v test vlib/encoding/protobuf/
Constants #
const max_field_number = (1 << 29) - 1
max_field_number is the largest field number a protobuf message can carry. A tag is one varint holding the field number shifted left three bits plus three bits of wire type, and the spec caps the number at 29 bits so the shift can never overflow.
const min_field_number = 1
min_field_number is the smallest legal field number. Zero is reserved: a tag of zero would carry no field and no wire type, so it cannot appear.
const max_varint_bytes = 10
max_varint_bytes bounds a base-128 varint. Sixty-four bits need at most ten 7-bit groups, so an eleventh byte means the input is malformed rather than merely large.
fn check_wire_type #
fn check_wire_type(field_number int, got WireType, want WireType) !
check_wire_type returns a WireTypeMismatchError unless got is the wire type a field declared to use want would arrive as.
A generated codec calls this once per field, so a producer that sends the wrong wire type is reported against the field number instead of having its bytes reinterpreted into a plausible-looking wrong value. A repeated numeric field is the one case where a mismatch is not an error, since it may arrive packed; the generated decoder checks that case itself.
fn field_key #
fn field_key(field_number int, wire_type WireType) u64
field_key builds the tag written ahead of a field's value: the field number shifted left three bits, or-ed with the wire type in the low three.
fn field_key_valid #
fn field_key_valid(field_number int) bool
field_key_valid reports whether field_number is inside the range the spec allows.
fn get_double #
fn get_double(data []u8, pos int) !(f64, int)
get_double reads a double from the eight little-endian bytes at pos, returning the value and the position just past them.
fn get_fixed32 #
fn get_fixed32(data []u8, pos int) !(u32, int)
get_fixed32 reads the four little-endian bytes at pos, returning the value and the position just past them.
fn get_fixed64 #
fn get_fixed64(data []u8, pos int) !(u64, int)
get_fixed64 reads the eight little-endian bytes at pos, returning the value and the position just past them.
fn get_float #
fn get_float(data []u8, pos int) !(f32, int)
get_float reads a float from the four little-endian bytes at pos, returning the value and the position just past them.
fn int32_varint #
fn int32_varint(value i32) u64
int32_varint returns the varint encoding of value as an int32 reaches the wire. A negative value is sign-extended to sixty-four bits, so it costs ten bytes: that is what the spec mandates, and truncating to 32 bits would turn -1 into a value a reader cannot recover.
fn int64_varint #
fn int64_varint(value i64) u64
int64_varint returns the varint encoding of value as an int64 reaches the wire, sign-extending a negative value to the full 64 bits.
fn is_packable #
fn is_packable(wt WireType) bool
is_packable reports whether a repeated field of wt's element wire type may use the packed encoding, which is a single length-delimited run of values instead of one tag per value. The spec packs every repeated numeric type and leaves string, bytes, and message unpacked.
fn new_packer #
fn new_packer(opts EncodeOpts) &Packer
new_packer returns a Packer with an empty buffer of opts.initial_cap capacity.
fn new_packer_from #
fn new_packer_from(data []u8, opts EncodeOpts) &Packer
new_packer_from returns a Packer whose buffer starts with a copy of data, so a message can be appended to an existing one. data itself is not modified.
fn new_unpacker #
fn new_unpacker(data []u8, opts DecodeOpts) &Unpacker
new_unpacker returns an Unpacker positioned at the start of data.
fn put_double #
fn put_double(mut buf []u8, value f64)
put_double appends value as the eight little-endian bytes of its IEEE-754 binary64 bit pattern.
fn put_fixed32 #
fn put_fixed32(mut buf []u8, value u32)
put_fixed32 appends value as four little-endian bytes, the encoding shared by fixed32, sfixed32, and float.
fn put_fixed64 #
fn put_fixed64(mut buf []u8, value u64)
put_fixed64 appends value as eight little-endian bytes, the encoding shared by fixed64, sfixed64, and double.
fn put_float #
fn put_float(mut buf []u8, value f32)
put_float appends value as the four little-endian bytes of its IEEE-754 binary32 bit pattern.
fn put_varint #
fn put_varint(mut buf []u8, value u64)
put_varint appends value to buf in base 128: the low seven bits per byte, with the high bit set on every byte but the last.
fn read_varint #
fn read_varint(data []u8, pos int) !(u64, int)
read_varint reads one base-128 varint at pos and returns it with the position just past it.
A varint that runs off the end of the buffer, or that needs more than max_varint_bytes bytes, is an error rather than a silent misread. A varint written with more bytes than strictly needed is accepted, because the spec requires parsers to tolerate it: the extra bytes are zero and contribute nothing. For the same reason the tenth byte's upper six bits are discarded instead of rejected, which matches the reference implementations and keeps a producer's overflow from breaking a consumer.
fn scalar_by_name #
fn scalar_by_name(name string) ?ProtoScalar
scalar_by_name resolves a name as it is spelled in a .proto schema, so a field can be annotated with the schema's own vocabulary.
fn split_field_key #
fn split_field_key(key u64) (int, WireType)
split_field_key splits a tag read off the wire into its field number and wire type. The wire type is not validated here, because a tag carrying an unassigned value is a malformed tag rather than an unknown field, and the two need different handling.
The field number is converted to int without a range check, so a caller reading untrusted input has to check key >> 3 against max_field_number first: where int is 32 bits the conversion wraps. Unpacker.read_tag does.
fn varint_size #
fn varint_size(value u64) int
varint_size returns the number of bytes value occupies as a varint.
fn wire_type_valid #
fn wire_type_valid(n int) bool
wire_type_valid reports whether n names a wire type this encoding spec defines. Six and seven are unassigned, and a tag carrying one is malformed rather than merely unknown, so callers reject it instead of skipping it.
fn zigzag_decode_i32 #
fn zigzag_decode_i32(value u32) i32
zigzag_decode_i32 inverts zigzag_encode_i32.
fn zigzag_decode_i64 #
fn zigzag_decode_i64(value u64) i64
zigzag_decode_i64 inverts zigzag_encode_i64.
fn zigzag_encode_i32 #
fn zigzag_encode_i32(value i32) u32
zigzag_encode_i32 maps a signed 32-bit value onto an unsigned one so that small magnitudes stay small: 0, -1, 1, -2, 2 become 0, 1, 2, 3, 4. Without it every negative sint32 would cost the full ten bytes an int32 does.
fn zigzag_encode_i64 #
fn zigzag_encode_i64(value i64) u64
zigzag_encode_i64 is zigzag_encode_i32 for 64-bit values.
fn ProtoScalar.from #
fn ProtoScalar.from[W](input W) !ProtoScalar
from returns the ProtoScalar value of an integer or of a field name, or an error if there is none.
fn WireType.from #
fn WireType.from[W](input W) !WireType
from returns the WireType value of an integer or of a field name, or an error if there is none.
enum ProtoScalar #
enum ProtoScalar {
boolean
int32
int64
uint32
uint64
sint32
sint64
fixed32
sfixed32
fixed64
sfixed64
float32
float64
}
ProtoScalar names a protobuf scalar type. It exists because a V type does not determine one on its own: i32 is int32, sint32, or sfixed32 depending on the schema, and only the schema knows which.
fn (ProtoScalar) wire_type #
fn (s ProtoScalar) wire_type() WireType
wire_type returns the wire type a ProtoScalar's values travel as.
enum WireType #
enum WireType {
// varint carries bool, the integer types, and enums as base 128.
varint = 0
// fixed64 carries fixed64, sfixed64, and double as eight little-endian
// bytes.
fixed64 = 1
// length_delimited carries string, bytes, embedded messages, and packed
// repeated fields, each behind a varint byte count.
length_delimited = 2
// start_group opens a group, a proto2 construct the spec deprecated and
// proto3 forbids. Listed for tag decoding only; this module cannot encode
// or skip one.
start_group = 3
// end_group closes a group. See WireType.start_group.
end_group = 4
// fixed32 carries fixed32, sfixed32, and float as four little-endian
// bytes.
fixed32 = 5
}
WireType says how a field's value is laid out on the wire. The low three bits of every field tag carry it, so the numbering is fixed by the protobuf encoding spec and must not be reordered.
struct DecodeOpts #
struct DecodeOpts {
pub:
// allow_unknown_fields skips a field this build has no mapping for. It
// defaults to true because forward compatibility is a requirement of the
// format: a newer producer must not break an older consumer. Set it false
// to reject such a field instead.
allow_unknown_fields bool = true
// reject_nonminimal_varint fails on a varint padded with redundant zero
// groups. The spec asks parsers to accept those, so this is off by default
// and is there for callers that would rather be strict about a producer.
reject_nonminimal_varint bool
// max_depth bounds how deeply messages may nest. Nesting without a bound
// is a denial of service rather than a long message, since each level costs
// only a few bytes on the wire.
max_depth int = 100
// max_length bounds a single length-delimited payload, and therefore the
// whole message. A length on the wire is a 64-bit value, so this is the
// check that stops a handful of bytes from claiming a huge slice.
max_length int = 64 * 1024 * 1024
// validate_utf8 rejects a `string` field whose bytes are not valid UTF-8.
// A `bytes` field is exempt either way, since it carries arbitrary octets.
validate_utf8 bool
}
DecodeOpts configures an Unpacker. The defaults are the ones that keep a decoder safe on untrusted input while still interoperating.
struct EncodeOpts #
struct EncodeOpts {
pub:
// initial_cap is the capacity the buffer starts with, so a message that
// fits never reallocates. Zero means the packer's own default.
initial_cap int = 256
// emit_defaults writes a field that holds its default value instead of
// leaving it off the wire. proto3 omits defaults; proto2 keeps explicit
// presence, which is what this flag is for.
emit_defaults bool
// validate_utf8 rejects a `string` field whose bytes are not valid UTF-8.
// It is off by default because it costs a pass over the bytes, and a
// `bytes` field is exempt either way.
validate_utf8 bool
}
EncodeOpts configures a Packer. The zero value is a reasonable default, so Packer{} with no options is valid.
struct GroupUnsupportedError #
struct GroupUnsupportedError {
Error
pub:
pos int
number int
}
GroupUnsupportedError reports a group, the deprecated proto2 construct that the encoding spec kept only for compatibility. Its extent cannot be found without reading fields the reader does not understand, so this module refuses it rather than guessing.
fn (GroupUnsupportedError) msg #
fn (e &GroupUnsupportedError) msg() string
msg formats a GroupUnsupportedError for IError.msg().
struct InvalidUtf8Error #
struct InvalidUtf8Error {
Error
pub:
pos int
}
InvalidUtf8Error reports a string field whose bytes are not valid UTF-8. bytes fields are exempt, since they carry arbitrary octets by design.
fn (InvalidUtf8Error) msg #
fn (e &InvalidUtf8Error) msg() string
msg formats an InvalidUtf8Error for IError.msg().
struct MalformedError #
struct MalformedError {
Error
pub:
pos int
reason string
}
MalformedError reports input that is well-sized but not well-formed.
fn (MalformedError) msg #
fn (e &MalformedError) msg() string
msg formats a MalformedError for IError.msg().
struct MaxDepthError #
struct MaxDepthError {
Error
pub:
pos int
max_depth int
}
MaxDepthError reports nested messages deeper than the reader will follow. A message that nests without bound is a denial-of-service, not a long message.
fn (MaxDepthError) msg #
fn (e &MaxDepthError) msg() string
msg formats a MaxDepthError for IError.msg().
struct MaxLengthError #
struct MaxLengthError {
Error
pub:
pos int
declared i64
max_length int
}
MaxLengthError reports a length-delimited payload, or a whole message, past the configured ceiling. The length on the wire is a 64-bit value, so this is the check that stops a small input from claiming a large allocation.
fn (MaxLengthError) msg #
fn (e &MaxLengthError) msg() string
msg formats a MaxLengthError for IError.msg().
struct MissingFieldError #
struct MissingFieldError {
Error
pub:
name string
}
MissingFieldError reports a field the schema requires that the input never carried.
fn (MissingFieldError) msg #
fn (e &MissingFieldError) msg() string
msg formats a MissingFieldError for IError.msg().
struct Packer #
struct Packer {
mut:
buf []u8
opts EncodeOpts
}
Packer appends protobuf-encoded bytes to a growable buffer.
A Packer is a plain writer: every write_* method emits the field you ask for, including a value equal to its default. Applying proto3's rule that defaults are not written is the caller's job, which the code v pbgen generates does for you based on the schema. Keeping the two apart is deliberate — a writer that silently dropped values would make the manual API impossible to use for proto2, where presence has to survive.
fn (Packer) reserve #
fn (mut p Packer) reserve(n int)
reserve grows the buffer's capacity so at least n more bytes fit without reallocating. Call it before a known-size payload write to skip the per-byte growth check that << carries.
fn (Packer) reset #
fn (mut p Packer) reset()
reset empties the buffer while keeping its capacity, so a Packer can be reused across messages without reallocating.
fn (Packer) bytes #
fn (p &Packer) bytes() []u8
bytes returns the encoded output. The result aliases the packer's buffer, so copy it before reusing the packer.
fn (Packer) len #
fn (p &Packer) len() int
len returns how many bytes have been written so far.
fn (Packer) write_tag #
fn (mut p Packer) write_tag(field_number int, wire_type WireType)
write_tag writes the field tag that introduces a value, without the value itself.
fn (Packer) write_uint32 #
fn (mut p Packer) write_uint32(field_number int, value u32)
write_uint32 writes a uint32 field.
fn (Packer) write_uint64 #
fn (mut p Packer) write_uint64(field_number int, value u64)
write_uint64 writes a uint64 field.
fn (Packer) write_int32 #
fn (mut p Packer) write_int32(field_number int, value i32)
write_int32 writes an int32 field. A negative value is sign-extended to 64 bits, so it costs ten bytes, which is what the spec requires.
fn (Packer) write_int64 #
fn (mut p Packer) write_int64(field_number int, value i64)
write_int64 writes an int64 field, sign-extending a negative value.
fn (Packer) write_sint32 #
fn (mut p Packer) write_sint32(field_number int, value i32)
write_sint32 writes a sint32 field, zigzagged so a small negative stays small.
fn (Packer) write_sint64 #
fn (mut p Packer) write_sint64(field_number int, value i64)
write_sint64 writes a sint64 field, zigzagged.
fn (Packer) write_bool #
fn (mut p Packer) write_bool(field_number int, value bool)
write_bool writes a bool field.
fn (Packer) write_enum #
fn (mut p Packer) write_enum(field_number int, value int)
write_enum writes an enum field. Enums travel as their integer value.
fn (Packer) write_fixed32 #
fn (mut p Packer) write_fixed32(field_number int, value u32)
write_fixed32 writes a fixed32 field as four little-endian bytes.
fn (Packer) write_sfixed32 #
fn (mut p Packer) write_sfixed32(field_number int, value i32)
write_sfixed32 writes an sfixed32 field as four little-endian bytes.
fn (Packer) write_float #
fn (mut p Packer) write_float(field_number int, value f32)
write_float writes a float field as the four little-endian bytes of its IEEE-754 binary32 bit pattern.
fn (Packer) write_fixed64 #
fn (mut p Packer) write_fixed64(field_number int, value u64)
write_fixed64 writes a fixed64 field as eight little-endian bytes.
fn (Packer) write_sfixed64 #
fn (mut p Packer) write_sfixed64(field_number int, value i64)
write_sfixed64 writes an sfixed64 field as eight little-endian bytes.
fn (Packer) write_double #
fn (mut p Packer) write_double(field_number int, value f64)
write_double writes a double field as the eight little-endian bytes of its IEEE-754 binary64 bit pattern.
fn (Packer) write_bytes #
fn (mut p Packer) write_bytes(field_number int, value []u8)
write_bytes writes a bytes field: the length, then the bytes unchanged.
fn (Packer) write_string #
fn (mut p Packer) write_string(field_number int, value string) !
write_string writes a string field. With validate_utf8 set, bytes that are not valid UTF-8 are rejected rather than written, since a decoder on the other end would reject them anyway.
fn (Packer) write_message #
fn (mut p Packer) write_message(field_number int, encoded []u8)
write_message writes an already-encoded message as a length-delimited field. Generated code encodes a nested message into a buffer of its own and splices it in here.
fn (Packer) write_len_delimited #
fn (mut p Packer) write_len_delimited(field_number int, payload []u8)
write_len_delimited writes a length-delimited field: the tag, the byte count, then the payload verbatim.
fn (Packer) write_packed_payload #
fn (mut p Packer) write_packed_payload(field_number int, wire_type WireType, payload []u8)
write_packed_payload writes a repeated numeric field in the packed form: one tag, one byte count, then every element already encoded back to back with no per-element tag.
Packing is the default the spec sets for repeated numeric types, and an empty list still writes the tag and a zero length, which is what keeps a present-but-empty list distinguishable from an absent one.
The elements are supplied pre-encoded because a V type does not determine the wire encoding on its own: i32 is int32, sint32, or sfixed32 depending on the schema, and only the schema knows which. write_packed_varints, write_packed_fixed32s and write_packed_fixed64s cover the common cases; generated code builds the payload itself because it knows the schema type.
fn (Packer) write_packed_varints #
fn (mut p Packer) write_packed_varints(field_number int, values []u64)
write_packed_varints writes a packed field from int32, int64, uint32, uint64, or enum elements, whose encodings are all plain varints once negative values are sign-extended.
fn (Packer) write_packed_sint32s #
fn (mut p Packer) write_packed_sint32s(field_number int, values []i32)
write_packed_sint32s writes a packed field from sint32 elements, zigzagged.
fn (Packer) write_packed_sint64s #
fn (mut p Packer) write_packed_sint64s(field_number int, values []i64)
write_packed_sint64s writes a packed field from sint64 elements, zigzagged.
fn (Packer) write_packed_bools #
fn (mut p Packer) write_packed_bools(field_number int, values []bool)
write_packed_bools writes a packed field from bool elements.
fn (Packer) write_packed_fixed32s #
fn (mut p Packer) write_packed_fixed32s(field_number int, values []u32)
write_packed_fixed32s writes a packed field from fixed32 or sfixed32 elements.
fn (Packer) write_packed_floats #
fn (mut p Packer) write_packed_floats(field_number int, values []f32)
write_packed_floats writes a packed field from float elements.
fn (Packer) write_packed_fixed64s #
fn (mut p Packer) write_packed_fixed64s(field_number int, values []u64)
write_packed_fixed64s writes a packed field from fixed64 or sfixed64 elements.
fn (Packer) write_packed_doubles #
fn (mut p Packer) write_packed_doubles(field_number int, values []f64)
write_packed_doubles writes a packed field from double elements.
struct UnexpectedEofError #
struct UnexpectedEofError {
Error
pub:
pos int // offset at which the read began
need i64 // bytes the reader was trying to read
remaining int // bytes actually available
}
UnexpectedEofError reports a read that ran past the end of the available bytes. A declared length that overflows the host's int surfaces here too, with need clamped to what was actually left.
fn (UnexpectedEofError) msg #
fn (e &UnexpectedEofError) msg() string
msg formats an UnexpectedEofError for IError.msg().
struct UnknownFieldError #
struct UnknownFieldError {
Error
pub:
pos int
number int
}
UnknownFieldError reports a field number this build has no mapping for, and only surfaces when the caller asked to reject them. By default such a field is skipped, because a newer producer must not break an older consumer.
fn (UnknownFieldError) msg #
fn (e &UnknownFieldError) msg() string
msg formats an UnknownFieldError for IError.msg().
struct UnknownWireTypeError #
struct UnknownWireTypeError {
Error
pub:
pos int
got int
}
UnknownWireTypeError reports a tag whose low three bits name a wire type the encoding spec never assigned, which makes the tag malformed rather than merely belonging to a field this build does not know.
fn (UnknownWireTypeError) msg #
fn (e &UnknownWireTypeError) msg() string
msg formats an UnknownWireTypeError for IError.msg().
struct Unpacker #
struct Unpacker {
mut:
data []u8
pos int
depth int
opts DecodeOpts
}
Unpacker reads protobuf-encoded fields out of a byte slice.
It tracks a position rather than reslicing, so a field's offset is always known and can be reported in an error — the only way to locate a problem inside a nested payload, since a protobuf message has no line or column.
fn (Unpacker) eof #
fn (u &Unpacker) eof() bool
eof reports whether the whole input has been consumed.
fn (Unpacker) remaining #
fn (u &Unpacker) remaining() int
remaining returns how many bytes are left unread.
fn (Unpacker) offset #
fn (u &Unpacker) offset() int
offset returns the current read offset.
fn (Unpacker) seek #
fn (mut u Unpacker) seek(pos int)
seek moves the read position to pos, an offset previously returned by offset.
It is for a hand-written reader that peeks at a tag to find out which field comes next: when the tag turns out to belong to someone else, seeking back to the offset taken before the peek hands it back to be read again.
fn (Unpacker) read_tag #
fn (mut u Unpacker) read_tag() !(int, WireType)
read_tag reads a field tag, returning the field number and wire type. A tag naming an unassigned wire type is malformed rather than unknown, so it is rejected here instead of being left for the caller to skip.
fn (Unpacker) read_varint #
fn (mut u Unpacker) read_varint() !u64
read_varint reads one base-128 varint.
fn (Unpacker) read_bool #
fn (mut u Unpacker) read_bool() !bool
read_bool reads a bool. Any non-zero value is true, which is what proto3 specifies, so a producer that wrote 2 rather than 1 is still understood.
fn (Unpacker) read_uint32 #
fn (mut u Unpacker) read_uint32() !u32
read_uint32 reads a uint32 varint.
fn (Unpacker) read_uint64 #
fn (mut u Unpacker) read_uint64() !u64
read_uint64 reads a uint64 varint.
fn (Unpacker) read_int32 #
fn (mut u Unpacker) read_int32() !i32
read_int32 reads an int32 varint, truncating to 32 bits as the spec requires for a value a producer wrote wider than the field allows.
fn (Unpacker) read_int64 #
fn (mut u Unpacker) read_int64() !i64
read_int64 reads an int64 varint.
fn (Unpacker) read_sint32 #
fn (mut u Unpacker) read_sint32() !i32
read_sint32 reads a sint32 varint and unzigzags it.
fn (Unpacker) read_sint64 #
fn (mut u Unpacker) read_sint64() !i64
read_sint64 reads a sint64 varint and unzigzags it.
fn (Unpacker) read_enum #
fn (mut u Unpacker) read_enum() !int
read_enum reads an enum varint.
fn (Unpacker) read_fixed32 #
fn (mut u Unpacker) read_fixed32() !u32
read_fixed32 reads four little-endian bytes.
fn (Unpacker) read_sfixed32 #
fn (mut u Unpacker) read_sfixed32() !i32
read_sfixed32 reads four little-endian bytes as a signed value.
fn (Unpacker) read_float #
fn (mut u Unpacker) read_float() !f32
read_float reads the four little-endian bytes of an IEEE-754 binary32.
fn (Unpacker) read_fixed64 #
fn (mut u Unpacker) read_fixed64() !u64
read_fixed64 reads eight little-endian bytes.
fn (Unpacker) read_sfixed64 #
fn (mut u Unpacker) read_sfixed64() !i64
read_sfixed64 reads eight little-endian bytes as a signed value.
fn (Unpacker) read_double #
fn (mut u Unpacker) read_double() !f64
read_double reads the eight little-endian bytes of an IEEE-754 binary64.
fn (Unpacker) read_len_delimited #
fn (mut u Unpacker) read_len_delimited() ![]u8
read_len_delimited reads a byte count and returns the payload it describes. The returned slice aliases the input rather than copying it.
The count arrives as a 64-bit value while the host's int may be 32 bits, so it is compared against the configured ceiling before being cast. Casting first would let a small input name a length that wraps negative, and the resulting slice would be nonsense rather than a clean error.
fn (Unpacker) read_bytes #
fn (mut u Unpacker) read_bytes() ![]u8
read_bytes reads a bytes field. The result aliases the input, so copy it if the input is going away.
fn (Unpacker) read_string #
fn (mut u Unpacker) read_string() !string
read_string reads a string field. With validate_utf8 set, bytes that are not valid UTF-8 are rejected; a string that fails that check could not be turned into a V string meaningfully.
fn (Unpacker) skip_field #
fn (mut u Unpacker) skip_field(field_number int, wire_type WireType) !
skip_field advances past a value of wire_type, which is how a field this build does not understand gets stepped over. Without it a newer producer would break an older consumer the moment it added a field.
With allow_unknown_fields off, the field is reported as an UnknownFieldError instead of being skipped.
fn (Unpacker) enter #
fn (mut u Unpacker) enter() !
enter descends into a nested message, failing once the nesting is deeper than the configured bound. Each level of nesting costs only a couple of bytes on the wire, so an unbounded one is a denial of service rather than a big message.
fn (Unpacker) leave #
fn (mut u Unpacker) leave()
leave returns from a nested message.
fn (Unpacker) sub #
fn (mut u Unpacker) sub() !&Unpacker
sub returns an Unpacker over a nested message's payload, with the depth carried over and the parent's position left just past the payload. A failure to read the payload is reported here rather than in the sub-reader, so the offset in the error points at the enclosing message.
struct WireTypeMismatchError #
struct WireTypeMismatchError {
Error
pub:
field int
expected WireType
got WireType
}
WireTypeMismatchError reports a known field arriving with a wire type its declared type cannot use. A repeated numeric field is not a mismatch when it arrives packed, so this covers only combinations the spec forbids.
fn (WireTypeMismatchError) msg #
fn (e &WireTypeMismatchError) msg() string
msg formats a WireTypeMismatchError for IError.msg().
- README
- Constants
- fn check_wire_type
- fn field_key
- fn field_key_valid
- fn get_double
- fn get_fixed32
- fn get_fixed64
- fn get_float
- fn int32_varint
- fn int64_varint
- fn is_packable
- fn new_packer
- fn new_packer_from
- fn new_unpacker
- fn put_double
- fn put_fixed32
- fn put_fixed64
- fn put_float
- fn put_varint
- fn read_varint
- fn scalar_by_name
- fn split_field_key
- fn varint_size
- fn wire_type_valid
- fn zigzag_decode_i32
- fn zigzag_decode_i64
- fn zigzag_encode_i32
- fn zigzag_encode_i64
- fn ProtoScalar.from
- fn WireType.from
- enum ProtoScalar
- enum WireType
- struct DecodeOpts
- struct EncodeOpts
- struct GroupUnsupportedError
- struct InvalidUtf8Error
- struct MalformedError
- struct MaxDepthError
- struct MaxLengthError
- struct MissingFieldError
- struct Packer
- fn reserve
- fn reset
- fn bytes
- fn len
- fn write_tag
- fn write_uint32
- fn write_uint64
- fn write_int32
- fn write_int64
- fn write_sint32
- fn write_sint64
- fn write_bool
- fn write_enum
- fn write_fixed32
- fn write_sfixed32
- fn write_float
- fn write_fixed64
- fn write_sfixed64
- fn write_double
- fn write_bytes
- fn write_string
- fn write_message
- fn write_len_delimited
- fn write_packed_payload
- fn write_packed_varints
- fn write_packed_sint32s
- fn write_packed_sint64s
- fn write_packed_bools
- fn write_packed_fixed32s
- fn write_packed_floats
- fn write_packed_fixed64s
- fn write_packed_doubles
- struct UnexpectedEofError
- struct UnknownFieldError
- struct UnknownWireTypeError
- struct Unpacker
- fn eof
- fn remaining
- fn offset
- fn seek
- fn read_tag
- fn read_varint
- fn read_bool
- fn read_uint32
- fn read_uint64
- fn read_int32
- fn read_int64
- fn read_sint32
- fn read_sint64
- fn read_enum
- fn read_fixed32
- fn read_sfixed32
- fn read_float
- fn read_fixed64
- fn read_sfixed64
- fn read_double
- fn read_len_delimited
- fn read_bytes
- fn read_string
- fn skip_field
- fn enter
- fn leave
- fn sub
- struct WireTypeMismatchError