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:
little_endian_packets
or big_endian_packets
. Followed by// 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:
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:
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:
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:
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
A field is either:
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:
T
can be:
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:
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 }
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