Etiquette.Spec (Etiquette v0.1.1)

View Source

Module containing the utilities needed to use the Etiquette library.

Add use Etiquette.Spec to your module for the best experience.

Summary

Functions

Defines the structure of a packet field. A packet field is a specific section of the packet. Must be inside the do block of a packet.

A range going from the start up to maximum num. Equivalent to ..num

A range going from minimum num up to the end. Equivalent to num..

Declares a packet specification. A packet specification is composed of a sequence of fields.

Functions

field(name, length, opts \\ [])

(macro)
@spec field(String.t(), pos_integer() | Range.t(),
  length_by: atom(),
  length_in: :bits | :bytes,
  part_of: atom(),
  id: atom(),
  doc: String.t()
) :: Macro.t()

Defines the structure of a packet field. A packet field is a specific section of the packet. Must be inside the do block of a packet.

For example:

packet "Packet spec", id: :spec do
  field "First field", 4
  field "Second field", 4
end

Mandatory arguments to be provided are:

  • name: The full name of the field.

  • length: The length of the field. Can be an int or a Range. An int will be interpreted as the field having a fixed length. A Range can be provided as a way to limit and validate the size of the field at runtime. It can take the form of a normal range (i. e. (8..256)) or a full range ((..)). Helper functions are also provided (min/1 and max/1) to create clear ranges that only have upper or lower limits.

The list of optional arguments is:

  • fixed: If a field is fixed to a specific value. Useful when specifying variants of a packet type. Using this, it's possible, for instance, to declare in the spec that a field has to have a certain value for the packet to be considered a certain type. Think of how headers usually have a field that is used to specify the packet type depending on it's value. Setting this option is most relevant to determine the result of the is_header?/1 function that would be generated following the example above.

    Example

    iex> defmodule Spec do
    ...>   use Etiquette.Spec
    ...>   packet "Header", id: :header do
    ...>     field "Type", 2
    ...>     field "Data", (..)
    ...>   end
    ...>   packet "Type 0", id: :type_0, of: :header do
    ...>     field "Type", 2, fixed: 0b00
    ...>     field "Data", (..)
    ...>   end
    ...>   packet "Type 1", id: :type_1, of: :header do
    ...>     field "Type", 2, fixed: 0b01
    ...>     field "Data", (..)
    ...>   end
    ...> end
    iex> packet_0 = <<0b00::2, "this is data">>
    iex> packet_1 = <<0b01::2, "this is data">>
    iex> Spec.is_header?(packet_0)
    true
    iex> Spec.is_header?(packet_1)
    true
    iex> Spec.is_type_0?(packet_0)
    true
    iex> Spec.is_type_1?(packet_0)
    false
    iex> Spec.is_type_0?(packet_1)
    false
    iex> Spec.is_type_1?(packet_1)
    true

    See the tests and section Validating Packet Formats for more examples.

  • length_by: Used to indicate that the length of the field is not fixed and declared through other means. The following are supported:

    • An atom: An atom can be used to use for reference. It will be interpreted that length_by: :atom means that the field's length is the value of the field that has id: :atom.
  • length_in: Used to indicate the format of the length. It can be specified with :bits or :bytes.

  • part_of: Used to declare that a specific field of a parent packet specification is subdivided into smaller fields. For example, a packet payload of a generic packet spec, can be divided into well-defined fields inside type-specific packet specs. part_of: :field_id will be interpreted as this field being part of the field containing id: :field_id. This also means that when one or several fields have part_of: :field_id, the fields will take the position of the field with id: :field_id, and so respect the order of the parent field. This also means that the size of the parent field will have to be coherent with the size of the children fields.

  • id: Must be an atom. An id will be used if another field references this one with length_by or part_of. It can also be used to specify the name that you want to be used by the generated functions and the maps they results return. IDs are also used when parent and child both have the same field declared. You may want to do this when declaring a fixed value in a child spec, for example. In this case, it would be recommended to add an ID to the field in the parent packet spec, and then add the same ID to the same field in the child spec.

  • doc: Provides full documentation on the field. This documentation will be used to document the whole module that is using Etiquette.Spec. Alternatively, use @fdoc to document a field before the declaration. For example:

      packet "Header", id: :header do
        field "Type", 2, doc: "Determines the packet type"
        # is equivalent to
        @fdoc "Determines the packet type"
        field "Type", 2
      end

is_range(range)

(macro)

max(num)

A range going from the start up to maximum num. Equivalent to ..num

min(num)

A range going from minimum num up to the end. Equivalent to num..

packet(name, args \\ [], list)

(macro)

Declares a packet specification. A packet specification is composed of a sequence of fields.

The available arguments are:

  • name: Mandatory field. A string with the name of the field.

  • id: An atom used to take care of references. When another packet uses the of argument, the id is what will be used for reference. By default, the id will be the name converted to an snake case atom, but if the name has a long or weird structure, this id can override it for its references and function names.

  • of: Declares that a packet follows a previously declared packet specification. The specs have to be declared in the same file. This argument needs to be present when using part_of.

The packet contents themselves are declared inside a do block containing fields. For more information, see field