# 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 } ``` ## 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:\ > [IDENTIFIER](#identifier) `=` [INTEGER](#integer) An *enumeration* or for short *enum*, is a declaration of a set of named [integer](#integer) constants. The [integer](#integer) following the name specifies the bit size of the values. ``` enum CoffeeAddition: 3 { Empty = 0, Cream = 1, Vanilla = 2, Chocolate = 3, Whisky = 4, Rum = 5, Kahlua = 6, Aquavit = 7 } ``` ### 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). 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. ``` 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 adds a number of **octet** of padding. ``` packet Padded { _padding_[1] // 1 octet/8bit of padding } ``` ### 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+ > > INTVALUE:\ > DIGIT+ > > 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:\ > `+` | `-` | `*` | `/` DIGIT | `+` | `-` | `*` | `/` Part of a arithmetic expression where the missing part is a size For example: - `+ 2` defines that the size is 2 octet bigger than the real size - `* 8` defines that the size is 8 times bigger than the real size ### Comment > COMMENT:\ > BLOCK_COMMENT | LINE_COMMENT > > BLOCK_COMMENT:\ > `/*` (!`*/` ANY) `*/` > > LINE_COMMENT:\ > `//` (!\n ANY) `//` ### Whitespace > WHITESPACE:\ > ` ` | `\t` | `\n`