This module contains a data-driven encoder/decoder plus some auxiliary helper functions.
For information on how to generate code for a set of .proto
files,
see the gpb_compile
module.
The reason for keeping this (slower) data-driven encoder/decoder is mostly to be able to cross-check with the generated encoder/decoder. and also to some extent for easier inter-op testing.
The encoder/decoder in this module works on records or tuples,
but the functions msg_to_map/3
and msg_from_map/4
can convert to and from maps.
encode_msg/2
and
decode_msg/3
expect the definitions to be on the latest format
version. If you use functions in gpb_compile
to parse proto
files to definitions, for example gpb_compile:file/2
, then
make sure to include the option
{proto_defs_version, gpb_defs:latest_defs_version()}
.
This is in contrast to the advice in the
[doc/dev-guide/proto-defs-versions.md] to tool writers to use a
hard-wired version number, but this module is part of gpb and thus
developed in tandem with any changes to the definitions, whereas
the definitions returned from gpb_compile
can occasionally default
to an earlier-than-latest version for compatibility reasons.
field() = #field{name = atom() | undefined, fnum = integer() | undefined, rnum = pos_integer() | undefined, type = gpb_field_type() | gpb_internal_intermediary_ref() | gpb_internal_intermediary_map_ref() | undefined, occurrence = required | optional | repeated | defaulty | undefined, opts = [term()]} | #gpb_oneof{name = atom() | undefined, rnum = pos_integer() | undefined, fields = [#field{name = atom() | undefined, fnum = integer() | undefined, rnum = pos_integer() | undefined, type = gpb_field_type() | gpb_internal_intermediary_ref() | gpb_internal_intermediary_map_ref() | undefined, occurrence = required | optional | repeated | defaulty | undefined, opts = [term()]}] | undefined, opts = [term()]}
gpb_field_type() = int32 | int64 | uint32 | uint64 | sint32 | sint64 | fixed32 | fixed64 | sfixed32 | sfixed64 | bool | float | double | string | bytes | {enum, atom()} | {msg, atom()} | {group, atom()} | {map, gpb_map_key(), gpb_map_value()} | unknown
gpb_internal_intermediary_map_ref() = {map, gpb_map_key(), gpb_map_value() | gpb_internal_intermediary_ref()}
gpb_internal_intermediary_ref() = {ref, term()} | {msg, list()} | {group, list()} | {enum, list()}
gpb_map_key() = int32 | int64 | uint32 | uint64 | sint32 | sint64 | fixed32 | fixed64 | sfixed32 | sfixed64 | bool | string
gpb_map_value() = gpb_scalar() | {enum, atom()} | {msg, atom()}
gpb_scalar() = int32 | int64 | uint32 | uint64 | sint32 | sint64 | fixed32 | fixed64 | sfixed32 | sfixed64 | bool | float | double | string | bytes
map_opt(Other) = {maps_unset_optional, omitted | present_undefined} | {maps_oneof, flat | tuples} | Other
map_opts(OtherOpts) = [map_opt(OtherOpts)]
map_opts() = map_opts(none())
proplist_def() = {{msg, Name :: atom()}, [proplist_field()]} | term()
proplist_defs() = [proplist_def()]
proplist_field() = [proplist_field_item()] | [proplist_oneof_item()]
proplist_field_item() = {name, atom()} | {fnum, integer()} | {rnum, pos_integer()} | {type, gpb_field_type()} | {occurrence, required | optional | repeated} | {opts, [term()]}
proplist_oneof_item() = {name, atom()} | {rnum, pos_integer()} | {field, [proplist_field_item()]}
proplist_rpc() = [proplist_rpc_item()]
proplist_rpc_item() = {name, atom()} | {input, [field()]} | {output, [field()]} | {input_stream, boolean()} | {output_stream, boolean()} | {opts, [term()]}
check_scalar/2 | Verify type and range of a boolean, integral, floating point, string, bytes or value. |
decode_msg/3 | Decode a binary to a message on record format. |
decode_packet/3 | Decode a varint-length-delimited packet. |
decode_varint/1 | Equivalent to decode_varint(Bin, 64). |
decode_varint/2 | Decode an unsigned varint. |
decode_wiretype/1 | Decode an integer 0..5 to a wire type. |
defs_records_to_proplists/1 | In definitions, convert msg field records to proplists. |
encode_msg/2 | Encode a message on record format to a binary. |
encode_varint/1 | Encode an unsigned varint to a binary. |
encode_wiretype/1 | Encode a wire type for a protobuf field type. |
field_record_to_proplist/1 | Convert one field definition on field record format to a proplist. |
field_records_to_proplists/1 | Convert msg field definitions on field record format to proplists. |
is_allowed_as_key_type/1 | Test whether a field type is allowed as key type in a msg<_,_>
type field. |
is_msg_proto3/2 | Test whether a message was defined with "proto3" syntax. |
is_type_packable/1 | Test whether a field is allowed to have the option [packed] or not. |
map_item_pseudo_fields/2 | Make a list of fields for a pseudo message, given a key type and
a value type for a map<_,_> type field, for use in encoding,
verification. |
map_item_pseudo_fields_for_decoding/2 | Make a list of fields for a pseudo message, given a key type and
a value type for a map<_,_> type field, for use in decoding. |
merge_msgs/3 | Merge messages on record format. |
msg_from_map/4 | Convert a message, on map format to a tuple, so that it can be
used with eg encode_msg/2 . |
msg_to_map/3 | Convert a message, as returned by eg decode_msg/3
on tuple format to a map. |
proplist_to_field_record/1 | Convert a proplist for an msg field to an msg field record. |
proplists_to_defs_records/1 | In definitions, convert msg fields as proplists to field records. |
proplists_to_field_records/1 | Convert proplists for msg fields to msg field records. |
proplists_to_rpc_records/1 | Convert proplists to rpc records. |
proto2_type_default/2 | Return the default for a type of a message field in a "proto2" file. |
proto3_type_default/2 | Return the default for a type of a message field in a "proto3" file. |
rpc_record_to_proplist/1 | Convert an rpc record to a proplist. |
rpc_records_to_proplists/1 | Convert rpc records to proplists. |
verify_msg/2 | Verify type and range of fields in a message on record format. |
version_as_list/0 | Return the version on a list format, so that it can be compared. |
version_as_string/0 | Return the version as a string. |
version_source/0 | Retrieve the source of the version. |
check_scalar(Value :: any(), Type :: gpb_scalar()) -> ok | {error, Reason :: term()}
Verify type and range of a boolean, integral, floating point, string, bytes or value.
decode_msg(Bin :: binary(), MsgName :: atom(), MsgDefs :: gpb_defs:defs()) -> tuple()
Decode a binary to a message on record format.
If a field would occur more than once in the message, it is subject to merging, as per how protobuf is expected to work. Seemerge_msgs/3
for more details.
decode_packet(Type, Bin :: binary(), Opts) -> Result
Type = uint32 | uint64
Opts = [Opt]
Opt = {packet_size, non_neg_integer()}
Result = {ok, Packet, Rest} | {more, Length} | {error, Reason :: term()}
Packet = binary()
Rest = binary()
Length = pos_integer() | undefined
Decode a varint-length-delimited packet.
This function works like erlang:decode_packet/3
, but the length
is a varint encoded value.
The Type
is either uint32
or uint64
, indicating the maximum
number of bits considered as length of the packet.
erlang:decode_packet/3
):
{packet_size, MaxNumBytes :: integer() >= 0}
packet_size
, then {error, invalid}
will
be returned instead.{ok, Packet :: binary(), Rest :: binary()}
Packet
, trailing data is in Rest
{more, Length}
whenLength = TotalSize :: integer() >= 1 | undefined
{error, Reason :: term()}
{error,invalid}
will be returned. The idea is to protect against a denial of
service in case an adversary would send a valid (possibly small)
length encoded using a huge number of bytes.active
and
binary
mode.
receive_tcp_data(Socket, Pending) when is_binary(Pending) -> receive {tcp, Socket, IncomingBin} -> Data = <<Pending/binary, IncomingBin/binary>>, %% We may now have either a partial packet, %% or one packet and possibly some more, %% or several packets and possibly some more. %% Handle them in a loop: NewPending = handle_packets(Data), receive_tcp_data(Socket, NewPending) end. handle_packets(Data) -> %% In this example, we do not expect any decoding errors, %% for instance a malformed varint encoded length, %% so for simplicity fail fast on any error. case gpb:decode_packet(uint32, Data, []) of {ok, Packet, Rest} -> handle_one_packet(Packet), % do some work here handle_packets(Rest); {more, _Length} -> Data end.
decode_varint(Bin :: binary()) -> {non_neg_integer(), Rest :: binary()}
Equivalent to decode_varint(Bin, 64).
decode_varint(Bin :: binary(), MaxNumBits :: pos_integer()) -> {non_neg_integer(), binary()}
Decode an unsigned varint. The decoded integer will have be at most
MaxNumBits
bits long. Any higher bits will be masked away. If the binary
contains an overly long encoded integer, this function will fail.
decode_wiretype(X1 :: non_neg_integer()) -> varint | bits32 | bits64 | group_start | group_end | length_delimited
Decode an integer 0..5 to a wire type
defs_records_to_proplists(Defs :: gpb_defs:defs()) -> proplist_defs()
In definitions, convert msg field records to proplists.
encode_msg(Msg :: tuple(), MsgDefs :: gpb_defs:defs()) -> binary()
Encode a message on record format to a binary.
encode_varint(N :: integer()) -> binary()
Encode an unsigned varint to a binary.
encode_wiretype(X1 :: gpb_field_type()) -> non_neg_integer()
Encode a wire type for a protobuf field type.
field_record_to_proplist(Field :: #field{name = atom() | undefined, fnum = integer() | undefined, rnum = pos_integer() | undefined, type = gpb_field_type() | gpb_internal_intermediary_ref() | gpb_internal_intermediary_map_ref() | undefined, occurrence = required | optional | repeated | defaulty | undefined, opts = [term()]}) -> [proplist_field_item()]
Convert one field definition on field record format to a proplist.
field_records_to_proplists(Fields :: [field()]) -> [proplist_field()]
Convert msg field definitions on field record format to proplists.
is_allowed_as_key_type(X1 :: gpb_field_type()) -> boolean()
Test whether a field type is allowed as key type in a msg<_,_>
type field.
is_msg_proto3(Name :: atom(), MsgDefs :: gpb_defs:defs()) -> boolean()
Test whether a message was defined with "proto3"
syntax.
"proto3"
syntax import another file with "proto2"
syntax, or vice versa, then some messages will be of "proto2"
while
others will be "proto3"
.
is_type_packable(X1 :: gpb_field_type()) -> boolean()
Test whether a field is allowed to have the option [packed]
or not.
map_item_pseudo_fields(KeyType :: gpb_map_key(), ValueType :: gpb_map_value()) -> [field()]
Make a list of fields for a pseudo message, given a key type and
a value type for a map<_,_>
type field, for use in encoding,
verification.
"proto3"
and the value to encode is the type's default value.
map_item_pseudo_fields_for_decoding(KeyType :: gpb_map_key(), ValueType :: gpb_map_value()) -> [field()]
Make a list of fields for a pseudo message, given a key type and
a value type for a map<_,_>
type field, for use in decoding.
"proto2"
.
merge_msgs(PrevMsg :: tuple(), NewMsg :: tuple(), MsgDefs :: gpb_defs:defs()) -> tuple()
Merge messages on record format.
Merging of messages M1 and M2 are defined in protobuf to work like this:msg_from_map(Map :: map(), MsgName :: atom(), Defs :: gpb_defs:defs(), Opts :: map_opts()) -> tuple()
Convert a message, on map format to a tuple, so that it can be
used with eg encode_msg/2
. The options control how the map
representation is to be interpreted, see msg_to_map/3
for
further discussion on this.
msg_to_map(Msg :: tuple(), Defs :: gpb_defs:defs(), MapOpts :: map_opts()) -> map()
Convert a message, as returned by eg decode_msg/3
on tuple format to a map.
MapOpts
look similar to the options understood by
gpb_compile, but is only a limited subset of them and they
concern only different map representations and not the
record representation. While it can support some of the map
options understood by gpb_compile it does not support
mapfields_as_maps for the records representation, as this is not
supported by the rest of the code in this module.
proplist_to_field_record(PL :: [proplist_field_item()]) -> #field{name = atom() | undefined, fnum = integer() | undefined, rnum = pos_integer() | undefined, type = gpb_field_type() | gpb_internal_intermediary_ref() | gpb_internal_intermediary_map_ref() | undefined, occurrence = required | optional | repeated | defaulty | undefined, opts = [term()]}
Convert a proplist for an msg field to an msg field record.
proplists_to_defs_records(Defs :: proplist_defs()) -> gpb_defs:defs()
In definitions, convert msg fields as proplists to field records.
proplists_to_field_records(PLs :: [proplist_field()]) -> [field()]
Convert proplists for msg fields to msg field records.
proplists_to_rpc_records(PLs :: [proplist_rpc()]) -> [#rpc{name = atom() | undefined, input = any(), output = any(), input_stream = boolean() | undefined, output_stream = boolean() | undefined, opts = [term()] | undefined}]
Convert proplists to rpc records.
proto2_type_default(Type :: gpb_field_type(), MsgDefs :: gpb_defs:defs()) -> term()
Return the default for a type of a message field in a "proto2"
file.
proto3_type_default(Type :: gpb_field_type(), MsgDefs :: gpb_defs:defs()) -> term()
Return the default for a type of a message field in a "proto3"
file.
rpc_record_to_proplist(Rpc :: #rpc{name = atom() | undefined, input = any(), output = any(), input_stream = boolean() | undefined, output_stream = boolean() | undefined, opts = [term()] | undefined}) -> proplist_rpc()
Convert an rpc record to a proplist.
rpc_records_to_proplists(Rpcs :: [#rpc{name = atom() | undefined, input = any(), output = any(), input_stream = boolean() | undefined, output_stream = boolean() | undefined, opts = [term()] | undefined}]) -> [proplist_rpc()]
Convert rpc records to proplists.
verify_msg(Msg :: tuple() | term(), MsgDefs :: gpb_defs:defs()) -> ok
Verify type and range of fields in a message on record format.
version_as_list() -> [integer() | string()]
Return the version on a list format, so that it can be compared. The version_as_list is better if you want to be able to compare versions, for instance to see if one version is larger/later than another.
For the case of non-tagged versions, this scheme often works, but is a bit of a kluge, since in erlang, numbers are the smallest of types, and a string such as "gb996fbe" cannot (and generally should not) be converted to a number. So for non-tagged versions, one should only check whether they are or are not equal, not whether one is larger/later or smaller/earlier than another.
This will return for example:"2.1" -> [2,1] "2.1-53-gb996fbe" -> [2,1,0,0,53,"gb996fbe"] "2.1.1" -> [2,1,1] "2.2" -> [2,2](Lists are better than tuples when doing version comparisons. For tuples this holds:
{2,2} < {2,1,1}
, since tuples are first compared by
size then element by element for tuples of the same size. For
lists, it holds instead that: [2,1,1] < [2,2]
.)
version_as_string() -> string()
Return the version as a string. Valid version format is:
<n>.<m> % e.g. 2.1, 2.1.1, etc (any number of dots and ints) <n>.<m>-<o>-<text> % e.g. 2.1-53-gb996fbe means: a commit after 2.1The format is what
git describe --always --tags
produces,
given that all tags are always on the format <n>.<m>
.
version_source() -> string()
Retrieve the source of the version.
If it is extracted from git, it will be "git"
, else "file"
.
Generated by EDoc