bin_format
bin_format generates the Elixir code for handling binary formats throuh structs. The code created by bin_format is the same as you would write by hand, but the fields and their order are kept in one place.
The format description is done through the defformat macro, which uses a set of macros designed to make long specifications easy to transcribe and supports metaprogramming.
Supported Field Types
The full documentation for supported field types can be found in the ExDoc
files for the BinFormat.FieldType.*
modules.
Built In
Types supported in standard Elixir byte strings
integer
binary
(andbytes
)bitstring
(andbits
)float
utf8
utf16
utf32
Formatting
constant
- Must be present for binary patterns to matchpadding
- Ignored on binary decode, set to a default on binary encode
Convenience
ip_addr
- IP addresses in the :inet {a,b,c,d} formatlookup
- Replace a decoded value with an Elixir term from a list
Custom
Additional field types can be added by creating implentations
of the BinFormat.Field
protocol and wrapping the
BinFormat.FieldType.Util.add_field
macro. See ADDING_TYPES.md for more
details.
Usage
To define a new format, create a module and add the BinFormat module with
use BinFormat
. This will add the defformat
macro which is where you define
the fields and their order.
Each field in the packet is defined by a macro call.
Example
defmodule Foo do
defstruct a: 0, b: 10, c: <<1,2,3,4>>, d:3
def decode(<<a :: integer-size(8), b :: integer-size(8), c :: binary-size(4), d :: integer-size(8)>>) do
%Foo{a: a, b: b, c: c, d: d}
end
def encode(%Foo{a: a, b: b, c: c, d: c}) do
<<a :: integer-size(8), b :: integer-size(8), c :: binary-size(4), d::integer-size(8)>>
end
end
becomes
defmodule Foo do
use BinFormat
defformat do
integer :a, 0, 8
integer :b, 10, 8
binary :c, <<1,2,3,4>>, 3
integer :d, 3, 8
end
end
Metaprogramming
The body of defpacket supports metaprogramming like normal Elixir code. The fields in the binary are defined to be in the order that the functions declaring them are called. This can be used to automatically build packet structures from machine readable specifications.
For Example:
defformat do
integer :first, 0, 8
names = [{:a1, {:a2, <<1,2,3>>}}, {:b1, {:b2, <<2,3,4>>}}, {:c1, {:c2, <<3,4,5>>}}]
Enum.map(names, fn({v1, {v2, default}}) ->
integer v1, 0, 8
binary v2, default, 3
end)
integer :last, 0, 8
end
is equivalent to
defformat do
integer :first, 0, 8
integer :a1, 0, 8
binary :a2, <<1,2,3>>, 3
integer :b1, 0, 8
binary :b2, <<2,3,4>>, 3
integer :c1, 0, 8
binary :c2, <<3,4,5>>, 3
integer :last, 0, 8
end