README.md

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` (and `bytes`)
* `bitstring` (and `bits`)
* `float`
* `utf8`
* `utf16`
* `utf32`

### Formatting
* `constant` - Must be present for binary patterns to match
* `padding` - Ignored on binary decode, set to a default on binary encode

### Convenience
* `ip_addr` - IP addresses in the :inet {a,b,c,d} format
* `lookup` - 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
```