[TOC]
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 and 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:
endianess declaration*endianess:
little_endian_packets
|big_endian_packets
The structure of a .pdl
file is:
- A declaration of the protocol endianess:
little_endian_packets
orbig_endian_packets
. Followed by - 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
- Size fields
- Count fields
- Fixed fields
- Reserved fields
- Typedef fields, when the field type is an Enum
Fields that do not qualify as bit-fields must start and end on a byte boundary.
-
Identifiers can denote a field; an enumeration tag; or a declared type.
-
Field identifiers declared in a packet (resp. struct) belong to the scope that extends to the packet (resp. struct), and all derived packets (resp. structs).
-
Field identifiers declared in a group belong to the scope that extends to the packets declaring a 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.
declaration: {#declaration}
enum_declaration |
packet_declaration |
struct_declaration |
group_declaration |
checksum_declaration |
custom_field_declaration |
test_declaration
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 declaration
- a Packet declaration
- a Struct declaration
- a Group declaration
- a Checksum declaration
- a Custom Field declaration
- a Test declaration
enum_declaration:
enum
IDENTIFIER:
INTEGER{
enum_tag_list
}
enum_tag_list:
enum_tag (,
enum_tag)*,
?enum_tag:
enum_range | enum_value | enum_otherenum_range:
IDENTIFIER=
INTEGER..
INTEGER) ({
enum_value_list
}
)?enum_value_list:
enum_value (,
enum_value)*,
?enum_value:
IDENTIFIER=
INTEGERenum_other:
IDENTIFIER=
..
An enumeration or for short enum, is a declaration of a set of named integer constants or named integer ranges. integer ranges are inclusive in both ends. integer value within a range must be unique. 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 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_declaration:
packet
IDENTIFIER
(:
IDENTIFIER
((
constraint_list)
)?
)?
{
field_list?
}
A packet is a declaration of a sequence of 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_ or _body_ field of the parent.
When inheriting, you can use constraints to set values on parent fields. See constraints for more details.
packet Error {
code: 32,
_payload_
}
packet ImATeapot: Error(code = 418) {
brand_id: 8
}
struct_declaration:
struct
IDENTIFIER
(:
IDENTIFIER
((
constraint_list)
)?
)?
{
field_list?
}
A struct follows the same rules as a packet with the following differences:
- It inherits from a struct declaration instead of packet declaration.
- A typedef field can reference a struct.
group_declaration:
group
IDENTIFIER{
field_list
}
A group is a sequence of fields that expand in a packet or struct when used.
See also the Group field.
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_declaration:
checksum
IDENTIFIER:
INTEGER 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 following the name specify the bit size of the checksum value. The string following the size is a value defined by the generator implementation.
checksum CRC16: 16 "crc16"
custom_field_declaration:
custom_field
IDENTIFIER (:
INTEGER)? 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 following the name specify the bit size of the value. The string following the size is a value defined by the generator implementation.
custom_field URL "url"
test_declaration:
test
IDENTIFIER{
test_case_list
}
test_case_list:
test_case (,
test_case)*,
?test_case:
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"
}
constraint:
IDENTIFIER=
IDENTIFIER | INTEGERconstraint_list:
constraint (,
constraint)*,
?
A constraint defines the value of a parent field. The value can either be an enum tag or an integer.
group Additionable {
addition: CoffeAddition
}
packet IrishCoffeeBrew {
pot: 8,
Additionable {
addition = Whisky
}
}
packet Pot0IrishCoffeeBrew: IrishCoffeeBrew(pot = 0) {}
field_list:
field (,
field)*,
?field:
checksum_field |
padding_field |
size_field |
count_field |
payload_field |
body_field |
fixed_field |
reserved_field |
array_field |
scalar_field |
typedef_field |
group_field |
optional_field
A field is either:
- a Scalar field
- a Typedef field
- a Group field
- an Array field
- a Size field
- a Count field
- a Payload field
- a Body field
- a Fixed field
- a Checksum field
- a Padding field
- a Reserved field
- an Optional field
scalar_field:
IDENTIFIER:
INTEGER
A scalar field defines a numeric value with a bit size.
struct Coffee {
temperature: 8
}
typedef_field:
IDENTIFIER:
IDENTIFIER
A typedef field defines a field taking as value either an enum, struct, checksum or a custom_field.
packet LastTimeModification {
coffee: Coffee,
addition: CoffeeAddition
}
array_field:
IDENTIFIER:
INTEGER | IDENTIFIER[
SIZE_MODIFIER | INTEGER
]
An array field defines a sequence of N
elements of type T
.
N
can be:
- An integer value.
- A size modifier.
- Unspecified: In this case the array is dynamically sized using a _size_ or a _count_.
T
can be:
- An integer denoting the bit size of one element.
- An identifier referencing an enum, a struct or a 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_field:
IDENTIFIER ({
constraint_list}
)?
A group field inlines all the fields defined in the referenced group.
If a constraint list constrains a scalar field or typedef field with an enum type, the field will become a fixed field. The fixed field inherits the type or size of the original field and the value from the constraint list.
See Group Declaration for more information.
size_field:
_size_
(
IDENTIFIER |_payload_
|_body_
)
:
INTEGER
A _size_ field is a scalar field with as value the size in octet of the designated array, _payload_ or _body_.
packet Parent {
_size_(_payload_): 2,
_payload_
}
packet Brew {
pot: 8,
_size_(additions): 8,
additions: CoffeeAddition[]
}
count_field:
_count_
(
IDENTIFIER)
:
INTEGER
A _count_ field is a scalar field with as value the number of elements of the designated array.
packet Brew {
pot: 8,
_count_(additions): 8,
additions: CoffeeAddition[]
}
payload_field:
_payload_
(:
[
SIZE_MODIFIER]
)?
A _payload_ field is a dynamically sized array of octets.
It declares where to parse the definition of a child packet or struct.
A _size_ or a _count_ field referencing the payload induce its size.
If used, a size modifier can alter the octet size.
body_field:
_body_
A _body_ field is like a _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_field:
_fixed_
=
( INTEGER:
INTEGER ) |
( IDENTIFIER:
IDENTIFIER )
A _fixed_ field defines a constant with a known bit size. The constant can be either:
packet Teapot {
_fixed_ = 42: 8,
_fixed_ = Empty: CoffeeAddition
}
checksum_field:
_checksum_start_
(
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 field with a 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_field:
_padding_
[
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_field:
_reserved_
:
INTEGER
A _reserved_ field adds reserved bits.
packet DeloreanCoffee {
_reserved_: 2014
}
optional_field:
(scalar_field | typedef_field)if
constraint
An optional field is present in the raw bytes if and only if the condition attached is satisfied.
An optional field must start on a byte boundary, and have a size that is an integral number of bytes.
The field used as condition of an optional field must be declared in the same packet, struct, or group declaration as the optional field.
The field used as condition of an optional field must be a
scalar field of width 1
.
The field used as condition of an optional field may not be a optional field.
Multiple optional fields cannot use the same scalar field as condition variable.
struct Cream {
fat_percentage: 8,
}
enum Alcohol : 8 {
WHISKY = 0,
COGNAC = 1,
}
packet CoffeeWithAdditions {
want_sugar: 1,
want_cream: 1,
want_alcohol: 1,
_reserved_: 5,
sugar: 16 if want_sugar = 1,
cream: Cream if want_cream = 1,
alcohol: Alcohol if want_alcohol = 1,
}
INTEGER:
HEXVALUE | INTVALUEHEXVALUE:
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:
"
(!"
ANY)*"
A string is sequence of character. It can be multi-line.
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:
+
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:
BLOCK_COMMENT | LINE_COMMENTBLOCK_COMMENT:
/*
(!*/
ANY)*/
LINE_COMMENT:
//
(!\n ANY)//
WHITESPACE:
|
\t
|\n