Skip to content

Latest commit

 

History

History
741 lines (577 loc) · 20.3 KB

reference.md

File metadata and controls

741 lines (577 loc) · 20.3 KB

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 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

file:
   endianess declaration*

endianess:
   little_endian_packets | big_endian_packets

The structure of a .pdlfile 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:

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 (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.

Declarations

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:

Enum

enum_declaration:
   enum IDENTIFIER : INTEGER {
     enum_tag_list
   }

enum_tag_list:
   enum_tag (, enum_tag)* ,?

enum_tag:
   enum_range | enum_value | enum_other

enum_range:
   IDENTIFIER = INTEGER .. INTEGER) ({
     enum_value_list
   })?

enum_value_list:
   enum_value (, enum_value)* ,?

enum_value:
   IDENTIFIER = INTEGER

enum_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 {#decl-packet}

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 {#decl-struct}

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 {#decl-group}

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

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

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

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"
}

Constraints

constraint:
   IDENTIFIER = IDENTIFIER | INTEGER

constraint_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) {}

Fields

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:

Scalar {#fields-scalar}

scalar_field:
   IDENTIFIER : INTEGER

A scalar field defines a numeric value with a bit size.

struct Coffee {
  temperature: 8
}

Typedef {#fields-typedef}

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 {#fields-array}

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 {#fields-group}

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 {#fields-size}

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 {#fields-count}

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 {#fields-payload}

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 {#fields-body}

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 {#fields-fixed}

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 {#fields-checksum}

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 {#fields-padding}

padding_field:
   _padding_ [ INTEGER ]

A _padding_ field immediately following an array field pads the array field with 0s to the specified number of octets.

packet PaddedCoffee {
  additions: CoffeeAddition[],
  _padding_[100]
}

Reserved {#fields-reserved}

reserved_field:
   _reserved_ : INTEGER

A _reserved_ field adds reserved bits.

packet DeloreanCoffee {
  _reserved_: 2014
}

Optional (#fields-optional)

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,
}

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:
   + 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