Protocols

A protocol describes how a bit-level message is encoded and decoded. Protocols are used for things like infrared (IR) tag signals and peer-to-peer comms, where the device must read and write tightly-packed binary payloads. A protocol defines the fields in a payload, the checks that decide whether an incoming signal belongs to the protocol, and any parity/validation logic.

Protocol files can be compiled to optimised C++ encode/decode functions with the toCpp CLI command — see Getting Started.

Declaring a protocol

A protocol declares a name and the transport it is for, then a body of statements. Fields are declared with define, where the type is a sized integer (uint<N> / int<N>, with N bits) or a string.

protocol "swaptx" for "ir" {
	define uint<4> "bulletTypeId";
	define uint<4> "playerId";
	define uint<2> "teamId";
	define uint<8> "hitValue";
	define uint<1> "isCritical";
}

Steps and validation

A step block groups processing for a phase. It can be qualified with decode or encode. Inside a step you can assert conditions with require: adding the terminate flag rejects the signal immediately if the assertion fails (so it is not treated as belonging to this protocol).

step decode {
	// The system pre-fills "bitLength" with the length of the incoming data.
	// This protocol uses 22 bits; if that does not match, reject the signal.
	require terminate "bitLength" == 22;
}

The comparator in a require can be ==, !=, or a named comparator.

Parity and the bit cursor

A parity block performs parity/validation calculations. Within a step or parity block you can move the read position (the bit cursor) and combine bits:

  • offset n; — move the bit cursor to absolute position n.
  • forward n; / rewind n; — move the cursor relative to its current position.
  • xor / xnor over a bits<N> or bytes<N> read, storing the result under a name.
  • rule — a conditional rule. The conditional ?: operator is valid here (it is not available in ordinary expressions).
parity {
	// Move the bit cursor to the start of the stream
	offset 0;

	// XOR the first 19 bits together and store under "parityBit"
	xor bits<19> "parityBit";

	// Derive a value from the parity result
	rule uint<3> "parityValue" = "parityBit" == 0 ? 5 : 2;

	offset 0;
}

A complete example

Putting it together — a pulse-width IR protocol that validates the payload length, computes a parity value, then declares its fields:

protocol "swaptx" for "ir" {
	step decode {
		require terminate "bitLength" == 22;
	}

	parity {
		offset 0;
		xor bits<19> "parityBit";
		rule uint<3> "parityValue" = "parityBit" == 0 ? 5 : 2;
		offset 0;
	}

	define uint<4> "bulletTypeId";
	define uint<4> "playerId";
	define uint<2> "teamId";
	define uint<8> "hitValue";
	define uint<1> "isCritical";
}

Statement reference

StatementPurpose
protocol "name" for "transport" { }Declares a protocol and the transport it targets.
define <type> "field";Declares a payload field; type is uint<N>, int<N> or string.
step [decode|encode] { }A processing phase, optionally scoped to decoding or encoding.
parity [decode|encode] { }A parity / validation block.
require [terminate] "field" <cmp> <expr>;Asserts a condition; terminate rejects the signal on failure.
xor / xnor <read> "name";Boolean op over a bits<N> / bytes<N> read.
offset / forward / rewind n;Move the bit cursor (absolute / relative forward / relative back).
rule <type> "name" = ...;A conditional rule (supports the ?: operator).
end <bool>;Marks the end of a phase.

Related Topics