| # Packet Description Language |
| |
| [TOC] |
| |
| ## Notation |
| |
| | Notation | Example | Meaning | |
| |:-------------:|:----------------------------:|:----------------------------------------------------:| |
| | __ANY__ | __ANY__ | Any character | |
| | CAPITAL | IDENTIFIER, INT | A token production | |
| | snake_case | declaration, constraint | A syntactical production | |
| | `string` | `enum`, `=` | The exact character(s) | |
| | \x | \n, \r, \t, \0 | The character represented by this escape | |
| | x? | `,`? | An optional item | |
| | x* | ALPHANUM* | 0 or more of x | |
| | x+ | HEXDIGIT+ | 1 or more of x | |
| | x \| y | ALPHA \| DIGIT, `0x` \| `0X` | Either x or y | |
| | [x-y] | [`a`-`z`] | Any of the characters in the range from x to y | |
| | !x | !\n | Negative Predicate (lookahead), do not consume input | |
| | () | (`,` enum_tag) | Groups items | |
| |
| |
| [WHITESPACE](#Whitespace) and [COMMENT](#Comment) are implicitly inserted between every item |
| and repetitions in syntactical rules (snake_case). |
| |
| ``` |
| file: endianess declaration* |
| ``` |
| behaves like: |
| ``` |
| file: (WHITESPACE | COMMENT)* endianess (WHITESPACE | COMMENT)* (declaration | WHITESPACE | COMMENT)* |
| ``` |
| |
| ## File |
| |
| > file:\ |
| > endianess [declaration](#declarations)* |
| > |
| > endianess:\ |
| > `little_endian_packets` | `big_endian_packets` |
| |
| The structure of a `.pdl`file is: |
| 1. A declaration of the protocol endianess: `little_endian_packets` or `big_endian_packets`. Followed by |
| 2. Declarations describing the structure of the protocol. |
| |
| ``` |
| // The protocol is little endian |
| little_endian_packets |
| |
| // Brew a coffee |
| packet Brew { |
| pot: 8, // Output Pot: 8bit, 0-255 |
| additions: CoffeeAddition[2] // Coffee Additions: array of 2 CoffeeAddition |
| } |
| ``` |
| |
| The endianess affects how fields of fractional byte sizes (hence named |
| bit-fields) are parsed or serialized. Such fields are grouped together to the |
| next byte boundary, least significant bit first, and then byte-swapped to the |
| required endianess before being written to memory, or after being read from |
| memory. |
| |
| ``` |
| packet Coffee { |
| a: 1, |
| b: 15, |
| c: 3, |
| d: 5, |
| } |
| |
| // The first two field are laid out as a single |
| // integer of 16-bits |
| // MSB LSB |
| // 16 8 0 |
| // +---------------------------------------+ |
| // | b14 .. .. b0 |a| |
| // +---------------------------------------+ |
| // |
| // The file endianness is applied to this integer |
| // to obtain the byte layout of the packet fields. |
| // |
| // Little endian layout |
| // MSB LSB |
| // 7 6 5 4 3 2 1 0 |
| // +---------------------------------------+ |
| // 0 | b[6:0] | a | |
| // +---------------------------------------+ |
| // 1 | b[14:7] | |
| // +---------------------------------------+ |
| // 2 | d | c | |
| // +---------------------------------------+ |
| // |
| // Big endian layout |
| // MSB LSB |
| // 7 6 5 4 3 2 1 0 |
| // +---------------------------------------+ |
| // 0 | b[14:7] | |
| // +---------------------------------------+ |
| // 1 | b[6:0] | a | |
| // +---------------------------------------+ |
| // 2 | d | c | |
| // +---------------------------------------+ |
| ``` |
| |
| Fields which qualify as bit-fields are: |
| - [Scalar](#fields-scalar) fields |
| - [Size](#fields-size) fields |
| - [Count](#fields-count) fields |
| - [Fixed](#fields-fixed) fields |
| - [Reserved](#fields-reserved) fields |
| - [Typedef](#fields-typedef) fields, when the field type is an |
| [Enum](#enum) |
| |
| Fields that do not qualify as bit-fields _must_ start and end on a byte boundary. |
| |
| ## Identifiers |
| |
| - Identifiers can denote a field; an enumeration tag; or a declared type. |
| |
| - Field identifiers declared in a [packet](#packet) (resp. [struct](#struct)) belong to the _scope_ that extends |
| to the packet (resp. struct), and all derived packets (resp. structs). |
| |
| - Field identifiers declared in a [group](#group) belong to the _scope_ that |
| extends to the packets declaring a [group field](#group_field) for this group. |
| |
| - Two fields may not be declared with the same identifier in any packet scope. |
| |
| - Two types may not be declared width the same identifier. |
| |
| ## Declarations |
| |
| > declaration: {#declaration}\ |
| > [enum_declaration](#enum) |\ |
| > [packet_declaration](#packet) |\ |
| > [struct_declaration](#struct) |\ |
| > [group_declaration](#group) |\ |
| > [checksum_declaration](#checksum) |\ |
| > [custom_field_declaration](#custom-field) |\ |
| > [test_declaration](#test) |
| |
| A *declaration* defines a type inside a `.pdl` file. A declaration can reference |
| another declaration appearing later in the file. |
| |
| A declaration is either: |
| - an [Enum](#enum) declaration |
| - a [Packet](#packet) declaration |
| - a [Struct](#struct) declaration |
| - a [Group](#group) declaration |
| - a [Checksum](#checksum) declaration |
| - a [Custom Field](#custom-field) declaration |
| - a [Test](#test) declaration |
| |
| ### Enum |
| |
| > enum_declaration:\ |
| > `enum` [IDENTIFIER](#identifier) `:` [INTEGER](#integer) `{`\ |
| > enum_tag_list\ |
| > `}` |
| > |
| > enum_tag_list:\ |
| > enum_tag (`,` enum_tag)* `,`? |
| > |
| > enum_tag:\ |
| > enum_range | enum_value | enum_other |
| > |
| > enum_range:\ |
| > [IDENTIFIER](#identifier) `=` [INTEGER](#integer) `..` [INTEGER](#integer)) (`{`\ |
| > enum_value_list\ |
| > `}`)? |
| > |
| > enum_value_list:\ |
| > enum_value (`,` enum_value)* `,`? |
| > |
| > enum_value:\ |
| > [IDENTIFIER](#identifier) `=` [INTEGER](#integer) |
| > |
| > enum_other:\ |
| > [IDENTIFIER](#identifier) `=` `..` |
| |
| An *enumeration* or for short *enum*, is a declaration of a set of named [integer](#integer) constants |
| or named [integer](#integer) ranges. [integer](#integer) ranges are inclusive in both ends. |
| [integer](#integer) value within a range *must* be unique. [integer](#integer) ranges |
| *must not* overlap. |
| |
| *enumeration* are closed by default, all values that are not explicitely described in the declaration are treated as invalid and _may_ cause a parsing error. |
| |
| An *enumaration* _may_ be declared open by specifiying the default case; all unrecognized values |
| _shall_ falltrough to the default. |
| |
| The [integer](#integer) following the name specifies the bit size of the values. |
| |
| ``` |
| enum CoffeeAddition: 5 { |
| Empty = 0, |
| |
| NonAlcoholic = 1..9 { |
| Cream = 1, |
| Vanilla = 2, |
| Chocolate = 3, |
| }, |
| |
| Alcoholic = 10..19 { |
| Whisky = 10, |
| Rum = 11, |
| Kahlua = 12, |
| Aquavit = 13, |
| }, |
| |
| Custom = 20..29, |
| |
| Other = .. |
| } |
| ``` |
| |
| ### Packet |
| |
| > packet_declaration:\ |
| > `packet` [IDENTIFIER](#identifier)\ |
| > (`:` [IDENTIFIER](#identifier)\ |
| > (`(` [constraint_list](#constraints) `)`)?\ |
| > )?\ |
| > `{`\ |
| > [field_list](#fields)?\ |
| > `}` |
| |
| A *packet* is a declaration of a sequence of [fields](#fields). While packets |
| can contain bit-fields, the size of the whole packet must be a multiple of 8 |
| bits. |
| |
| A *packet* can optionally inherit from another *packet* declaration. In this case the packet |
| inherits the parent's fields and the child's fields replace the |
| [*\_payload\_*](#fields-payload) or [*\_body\_*](#fields-body) field of the parent. |
| |
| When inheriting, you can use constraints to set values on parent fields. |
| See [constraints](#constraints) for more details. |
| |
| ``` |
| packet Error { |
| code: 32, |
| _payload_ |
| } |
| |
| packet ImATeapot: Error(code = 418) { |
| brand_id: 8 |
| } |
| ``` |
| |
| ### Struct |
| |
| > struct_declaration:\ |
| > `struct` [IDENTIFIER](#identifier)\ |
| > (`:` [IDENTIFIER](#identifier)\ |
| > (`(` [constraint_list](#constraints) `)`)?\ |
| > )?\ |
| > `{`\ |
| > [field_list](#fields)?\ |
| > `}` |
| |
| A *struct* follows the same rules as a [*packet*](#packet) with the following differences: |
| - It inherits from a *struct* declaration instead of *packet* declaration. |
| - A [typedef](#fields-typedef) field can reference a *struct*. |
| |
| ### Group |
| |
| > group_declaration:\ |
| > `group` [IDENTIFIER](#identifier) `{`\ |
| > [field_list](#fields)\ |
| > `}` |
| |
| A *group* is a sequence of [fields](#fields) that expand in a |
| [packet](#packet) or [struct](#struct) when used. |
| |
| See also the [Group field](#fields-group). |
| |
| ``` |
| group Paged { |
| offset: 8, |
| limit: 8 |
| } |
| |
| packet AskBrewHistory { |
| pot: 8, // Coffee Pot |
| Paged |
| } |
| ``` |
| behaves like: |
| ``` |
| packet AskBrewHistory { |
| pot: 8, // Coffee Pot |
| offset: 8, |
| limit: 8 |
| } |
| ``` |
| |
| ### Checksum |
| |
| > checksum_declaration:\ |
| > `checksum` [IDENTIFIER](#identifier) `:` [INTEGER](#integer) [STRING](#string) |
| |
| A *checksum* is a native type (not implemented in PDL). See your generator documentation |
| for more information on how to use it. |
| |
| The [integer](#integer) following the name specify the bit size of the checksum value. |
| The [string](#string) following the size is a value defined by the generator implementation. |
| |
| ``` |
| checksum CRC16: 16 "crc16" |
| ``` |
| |
| ### Custom Field |
| |
| > custom_field_declaration:\ |
| > `custom_field` [IDENTIFIER](#identifier) (`:` [INTEGER](#integer))? [STRING](#string) |
| |
| A *custom field* is a native type (not implemented in PDL). See your generator documentation for more |
| information on how to use it. |
| |
| If present, the [integer](#integer) following the name specify the bit size of the value. |
| The [string](#string) following the size is a value defined by the generator implementation. |
| |
| ``` |
| custom_field URL "url" |
| ``` |
| |
| ### Test |
| |
| > test_declaration:\ |
| > `test` [IDENTIFIER](#identifier) `{`\ |
| > test_case_list\ |
| > `}` |
| > |
| > test_case_list:\ |
| > test_case (`,` test_case)* `,`? |
| > |
| > test_case:\ |
| > [STRING](#string) |
| |
| A *test* declares a set of valid octet representations of a packet identified by its name. |
| The generator implementation defines how to use the test data. |
| |
| A test passes if the packet parser accepts the input; if you want to test |
| the values returned for each field, you may specify a derived packet with field values enforced using |
| constraints. |
| |
| ``` |
| packet Brew { |
| pot: 8, |
| addition: CoffeeAddition |
| } |
| |
| test Brew { |
| "\x00\x00", |
| "\x00\x04" |
| } |
| |
| // Fully Constrained Packet |
| packet IrishCoffeeBrew: Brew(pot = 0, additions_list = Whisky) {} |
| |
| test IrishCoffeeBrew { |
| "\x00\x04" |
| } |
| ``` |
| |
| ## Constraints |
| |
| > constraint:\ |
| > [IDENTIFIER](#identifier) `=` [IDENTIFIER](#identifier) | [INTEGER](#integer) |
| > |
| > constraint_list:\ |
| > constraint (`,` constraint)* `,`? |
| |
| A *constraint* defines the value of a parent field. |
| The value can either be an [enum](#enum) tag or an [integer](#integer). |
| |
| ``` |
| group Additionable { |
| addition: CoffeAddition |
| } |
| |
| packet IrishCoffeeBrew { |
| pot: 8, |
| Additionable { |
| addition = Whisky |
| } |
| } |
| |
| packet Pot0IrishCoffeeBrew: IrishCoffeeBrew(pot = 0) {} |
| ``` |
| |
| ## Fields |
| |
| > field_list:\ |
| > field (`,` field)* `,`? |
| > |
| > field:\ |
| > [checksum_field](#fields-checksum) |\ |
| > [padding_field](#fields-padding) |\ |
| > [size_field](#fields-size) |\ |
| > [count_field](#fields-count) |\ |
| > [payload_field](#fields-payload) |\ |
| > [body_field](#fields-body) |\ |
| > [fixed_field](#fields-fixed) |\ |
| > [reserved_field](#fields-reserved) |\ |
| > [array_field](#fields-array) |\ |
| > [scalar_field](#fields-scalar) |\ |
| > [typedef_field](#fields-typedef) |\ |
| > [group_field](#fields-group) |
| |
| A field is either: |
| - a [Scalar](#fields-scalar) field |
| - a [Typedef](#fields-typedef) field |
| - a [Group](#fields-group) field |
| - an [Array](#fields-array) field |
| - a [Size](#fields-size) field |
| - a [Count](#fields-count) field |
| - a [Payload](#fields-payload) field |
| - a [Body](#fields-body) field |
| - a [Fixed](#fields-fixed) field |
| - a [Checksum](#fields-checksum) field |
| - a [Padding](#fields-padding) field |
| - a [Reserved](#fields-reserved) field |
| |
| ### Scalar {#fields-scalar} |
| |
| > scalar_field:\ |
| > [IDENTIFIER](#identifier) `:` [INTEGER](#integer) |
| |
| A *scalar* field defines a numeric value with a bit size. |
| |
| ``` |
| struct Coffee { |
| temperature: 8 |
| } |
| ``` |
| |
| ### Typedef {#fields-typedef} |
| |
| > typedef_field:\ |
| > [IDENTIFIER](#identifier) `:` [IDENTIFIER](#identifier) |
| |
| A *typedef* field defines a field taking as value either an [enum](#enum), [struct](#struct), |
| [checksum](#checksum) or a [custom_field](#custom-field). |
| |
| ``` |
| packet LastTimeModification { |
| coffee: Coffee, |
| addition: CoffeeAddition |
| } |
| ``` |
| |
| ### Array {#fields-array} |
| |
| > array_field:\ |
| > [IDENTIFIER](#identifier) `:` [INTEGER](#integer) | [IDENTIFIER](#identifier) `[`\ |
| > [SIZE_MODIFIER](#size-modifier) | [INTEGER](#integer)\ |
| > `]` |
| |
| An *array* field defines a sequence of `N` elements of type `T`. |
| |
| `N` can be: |
| - An [integer](#integer) value. |
| - A [size modifier](#size-modifier). |
| - Unspecified: In this case the array is dynamically sized using a |
| [*\_size\_*](#fields-size) or a [*\_count\_*](#fields-count). |
| |
| `T` can be: |
| - An [integer](#integer) denoting the bit size of one element. |
| - An [identifier](#identifier) referencing an [enum](#enum), a [struct](#struct) |
| or a [custom field](#custom-field) type. |
| |
| The size of `T` must always be a multiple of 8 bits, that is, the array elements |
| must start at byte boundaries. |
| |
| ``` |
| packet Brew { |
| pots: 8[2], |
| additions: CoffeeAddition[2], |
| extra_additions: CoffeeAddition[], |
| } |
| ``` |
| |
| ### Group {#fields-group} |
| |
| > group_field:\ |
| > [IDENTIFIER](#identifier) (`{` [constraint_list](#constraints) `}`)? |
| |
| A *group* field inlines all the fields defined in the referenced group. |
| |
| If a [constraint list](#constraints) constrains a [scalar](#fields-scalar) field |
| or [typedef](#fields-typedef) field with an [enum](#enum) type, the field will |
| become a [fixed](#fields-fixed) field. |
| The [fixed](#fields-fixed) field inherits the type or size of the original field and the |
| value from the constraint list. |
| |
| See [Group Declaration](#group) for more information. |
| |
| ### Size {#fields-size} |
| |
| > size_field:\ |
| > `_size_` `(` [IDENTIFIER](#identifier) | `_payload_` | `_body_` `)` `:` [INTEGER](#integer) |
| |
| A *\_size\_* field is a [scalar](#fields-scalar) field with as value the size in octet of the designated |
| [array](#fields-array), [*\_payload\_*](#fields-payload) or [*\_body\_*](#fields-body). |
| |
| ``` |
| packet Parent { |
| _size_(_payload_): 2, |
| _payload_ |
| } |
| |
| packet Brew { |
| pot: 8, |
| _size_(additions): 8, |
| additions: CoffeeAddition[] |
| } |
| ``` |
| |
| ### Count {#fields-count} |
| |
| > count_field:\ |
| > `_count_` `(` [IDENTIFIER](#identifier) `)` `:` [INTEGER](#integer) |
| |
| A *\_count\_* field is a [*scalar*](#fields-scalar) field with as value the number of elements of the designated |
| [array](#fields-array). |
| |
| ``` |
| packet Brew { |
| pot: 8, |
| _count_(additions): 8, |
| additions: CoffeeAddition[] |
| } |
| ``` |
| |
| ### Payload {#fields-payload} |
| |
| > payload_field:\ |
| > `_payload_` (`:` `[` [SIZE_MODIFIER](#size-modifier) `]` )? |
| |
| A *\_payload\_* field is a dynamically sized array of octets. |
| |
| It declares where to parse the definition of a child [packet](#packet) or [struct](#struct). |
| |
| A [*\_size\_*](#fields-size) or a [*\_count\_*](#fields-count) field referencing |
| the payload induce its size. |
| |
| If used, a [size modifier](#size-modifier) can alter the octet size. |
| |
| ### Body {#fields-body} |
| |
| > body_field:\ |
| > `_body_` |
| |
| A *\_body\_* field is like a [*\_payload\_*](#fields-payload) field with the following differences: |
| - The body field is private to the packet definition, it's accessible only when inheriting. |
| - The body does not accept a size modifier. |
| |
| ### Fixed {#fields-fixed} |
| |
| > fixed_field:\ |
| > `_fixed_` `=` \ |
| > ( [INTEGER](#integer) `:` [INTEGER](#integer) ) |\ |
| > ( [IDENTIFIER](#identifier) `:` [IDENTIFIER](#identifier) ) |
| |
| A *\_fixed\_* field defines a constant with a known bit size. |
| The constant can be either: |
| - An [integer](#integer) value |
| - An [enum](#enum) tag |
| |
| ``` |
| packet Teapot { |
| _fixed_ = 42: 8, |
| _fixed_ = Empty: CoffeeAddition |
| } |
| ``` |
| |
| ### Checksum {#fields-checksum} |
| |
| > checksum_field:\ |
| > `_checksum_start_` `(` [IDENTIFIER](#identifier) `)` |
| |
| A *\_checksum_start\_* field is a zero sized field that acts as a marker for the beginning of |
| the fields covered by a checksum. |
| |
| The *\_checksum_start\_* references a [typedef](#fields-typedef) field |
| with a [checksum](#checksum) type that stores the checksum value and selects the algorithm |
| for the checksum. |
| |
| ``` |
| checksum CRC16: 16 "crc16" |
| |
| packet CRCedBrew { |
| crc: CRC16, |
| _checksum_start_(crc), |
| pot: 8, |
| } |
| ``` |
| |
| ### Padding {#fields-padding} |
| |
| > padding_field:\ |
| > `_padding_` `[` [INTEGER](#integer) `]` |
| |
| A *\_padding\_* field immediately following an array field pads the array field with `0`s to the |
| specified number of **octets**. |
| |
| ``` |
| packet PaddedCoffee { |
| additions: CoffeeAddition[], |
| _padding_[100] |
| } |
| ``` |
| |
| ### Reserved {#fields-reserved} |
| |
| > reserved_field:\ |
| > `_reserved_` `:` [INTEGER](#integer) |
| |
| A *\_reserved\_* field adds reserved bits. |
| |
| ``` |
| packet DeloreanCoffee { |
| _reserved_: 2014 |
| } |
| ``` |
| |
| ## Tokens |
| |
| ### Integer |
| |
| > INTEGER:\ |
| > HEXVALUE | INTVALUE |
| > |
| > HEXVALUE:\ |
| > `0x` | `0X` HEXDIGIT<sup>+</sup> |
| > |
| > INTVALUE:\ |
| > DIGIT<sup>+</sup> |
| > |
| > HEXDIGIT:\ |
| > DIGIT | [`a`-`f`] | [`A`-`F`] |
| > |
| > DIGIT:\ |
| > [`0`-`9`] |
| |
| A integer is a number in base 10 (decimal) or in base 16 (hexadecimal) with |
| the prefix `0x` |
| |
| ### String |
| |
| > STRING:\ |
| > `"` (!`"` __ANY__)* `"` |
| |
| A string is sequence of character. It can be multi-line. |
| |
| ### Identifier |
| |
| > IDENTIFIER: \ |
| > ALPHA (ALPHANUM | `_`)* |
| > |
| > ALPHA:\ |
| > [`a`-`z`] | [`A`-`Z`] |
| > |
| > ALPHANUM:\ |
| > ALPHA | DIGIT |
| |
| An identifier is a sequence of alphanumeric or `_` characters |
| starting with a letter. |
| |
| ### Size Modifier |
| |
| > SIZE_MODIFIER:\ |
| > `+` INTVALUE |
| |
| A size modifier alters the octet size of the field it is attached to. |
| For example, `+ 2` defines that the size is 2 octet bigger than the |
| actual field size. |
| |
| ### Comment |
| |
| > COMMENT:\ |
| > BLOCK_COMMENT | LINE_COMMENT |
| > |
| > BLOCK_COMMENT:\ |
| > `/*` (!`*/` ANY) `*/` |
| > |
| > LINE_COMMENT:\ |
| > `//` (!\n ANY) `//` |
| |
| ### Whitespace |
| |
| > WHITESPACE:\ |
| > ` ` | `\t` | `\n` |