COMMS
Template library intended to help with implementation of communication protocols.
Defining Custom Sync Prefix Protocol Stack Layer

The COMMS library provides default comms::protocol::SyncPrefixLayer protocol stack layer to handle predefined protocol synchronization prefix. However, it may be insufficient (or incorrect) for some particular use cases, such as using alternating bytes. For example, every first message has 0xabcd prefix while every second is expected to be 0xdead. The Implementing New Layers section of the Protocol Stack Definition Tutorial page explains how to define new (custom) protocol layer.

However, since v3.2 COMMS library provides an ability to extend the existing definition of comms::protocol::SyncPrefixLayer and customize some bits and pieces. Let's implement the mentioned example of alternating synchronization prefix.

For this example the protocol framing is defined to be

SYNC | SIZE | ID | PAYLOAD

Such synchronization prefix handling layer may be defined in the following way.

namespace my_prot
{
// Base class for all the fields defining serialization endian
using FieldBase = comms::field::Field<comms::option::def::BigEndian>;
// Definition of the synchronization prefix field
using SyncField = common::field::IntValue<FieldBase, std::uint16_t>;
template <typename TNextLayer>
class AlternatingPrefixLayer : public
SyncField,
TNextLayer, // Next layer in the protocol stack
comms::option::def::ExtendingClass<AlternatingPrefixLayer<TNextLayer> >
// Make the comms::protocol::SyncPrefixLayer aware of it being extended
>
{
// Repeat definition of the base class
public:
// Repeat types defined in the base class (not visible by default)
using Field = typename Base::Field; // same as SyncField
// Verify the field's value.
bool verifyFieldValue(const Field& field)
{
bool valid = (field.value() == getPrefix(m_inputCount));
if (valid) {
++m_inputCount;
}
return valid;
}
// Prepare field to be written
void prepareFieldForWrite(Field& field) const
{
field.value() = getPrefix(m_outputCount);
++m_outputCount;
}
private:
static typename TField::ValueType getPrefix(unsigned count)
{
static const typename TField::ValueType Map[] = {
0xabcd,
0xdead
};
static const std::size_t MapSize = std::extent<decltype(Map)>::value;
auto idx = (count % MapSize);
return Map[idx];
}
unsigned m_inputCount = 0;
mutable unsigned m_outputCount = 0; // Updated in the const function
};
} // namespace my_prot
Protocol layer that uses "sync" field as a prefix to all the subsequent data written by other (next) ...
Definition: SyncPrefixLayer.h:52

The comms::protocol::SyncPrefixLayer doesn't have any virtual functions and as the result not able to provide any polymorphic behavior. In order to be able to extend its default functionality there is a need to use Curiously Recurring Template Pattern. It is done by passing comms::option::def::ExtendingClass extension option with the type of the layer class being defined to the comms::protocol::SyncPrefixLayer.

The extending class can customize the default behavior by overriding the listed below functions. They do not necessarily need to be static, accessing inner private state of the layer object is also acceptable.

  • doReadField() - Member function that is invoked to read field's value.
  • doWriteField() - Member function that is invoked to write field's value.
  • doFieldLength() - Member function that is invoked to calculate serialization length of the field.
  • verifyFieldValue() - Member function that is invoked to check the validity of the read prefix.
  • prepareFieldForWrite() - Member function that is invoked to prepare the field value before its write (serialization). The function is invoked from doWrite(), which is const. That's the reason why prepareFieldForWrite() also needs to be const. Any variables it updates must be declared as mutable.

The newly defined custom protocol stack layer can be used instead of comms::protocol::SyncPrefixLayer when defining protocol stack (framing) of the protocol. For example:

namespace my_prot
{
template <typename TMessage, typename TAllMessages>
struct Frame1 : public
AlternatingPrefixLayer< // SYNC
comms::protocol::MsgSizeLayer< // SIZE
common::field::IntValue<FieldBase, std::uint16_t>,
comms::protocol::MsgIdLayer< // ID
comms::feild::EnumValue<FieldBase, MsgId>,
TMessage,
TAllMessages,
comms::protocol::MsgDataLayer<> // PAYLOAD
>
>
>
{
// Generate convenience access functions for various layers
COMMS_PROTOCOL_LAYERS_ACCESS_OUTER(sync, size, size, payload);
};
} // namespace my_prot