597 lines
16 KiB
Markdown
597 lines
16 KiB
Markdown
|
# 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<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:\
|
||
|
> `+` | `-` | `*` | `/` 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`
|