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

The COMMS library provides default comms::protocol::TransportValueLayer protocol stack layer to handle extra value in transport framing and re-assigning it to the message object. However, it may be insufficient (or incorrect) for some particular use cases, such as using bitfield field to store both protocol version and some extra flags. 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::TransportValueLayer and customize some bits and pieces. Let's implement the mentioned example of sharing the same byte for message version and some flags.

For this example the protocol framing is defined to be

SIZE | ID | VERSION (12 bits) + FLAGS (4 bits) | PAYLOAD

First of all let's define the Common Interface Class, which holds the version and flags information as data member of every message object.

namespace my_prot
{
// Enum used for numeric message IDs
enum MsgId
{
MsgId_Message1,
MsgId_Message2,
...
};
// Base class for all the fields defining serialization endian
using FieldBase = comms::field::Field<comms::option::def::BigEndian>;
// Definition of the message version
template <typename... TExtraOpts>
class MessageVersion : public
FieldBase,
std::uint16_t,
TExtraOpts...
>
{
};
// Definition of the message flags
template <typename... TExtraOpts>
class MessageFlags : public
FieldBase,
comms::option::def::FixedLength<1>,
comms::option::def::BitmaskReservedBits<0xf0>,
TExtraOpts...
>
{
public:
// Provides names and generates access functions for internal bits.
COMMS_BITMASK_BITS_SEQ(bit0, bit1, bit2, bit3);
};
// Definition of the extensible common message interface
template <typename... TOptions>
class Message : public
TOptions...,
comms::option::def::BigEndian,
comms::option::def::MsgIdType<MsgId>,
comms::option::def::ExtraTransportFields<
std::tuple<
MessageVersion<>,
MessageFlags<>
>
>,
comms::option::VersionInExtraTransportFields<0> // Make messages version dependent
>
{
// (Re)definition of the base class as inner Base type.
using Base = comms::Message<...>;
public:
// Allow access to extra transport fields.
COMMS_MSG_TRANSPORT_FIELDS_NAMES(version, flags);
};
} // namespace my_prot
Main interface class for all the messages.
Definition: Message.h:80
Bitmask value field.
Definition: BitmaskValue.h:103
Field that represent integral value.
Definition: IntValue.h:72
constexpr unsigned version()
Version of the COMMS library as single numeric value.
Definition: version.h:64

Just to refresh the reader's memory: the usage of COMMS_MSG_TRANSPORT_FIELDS_NAMES() macro for the interface definition will generate transportField_version() and transportField_flags() convenience member functions to access the stored version and flags fields, while usage of COMMS_BITMASK_BITS_SEQ() in the flags field definition will genereate getBitValue_X() and setBitValue_X() convenience member functions to get / set values of the bits (where X is one of the defined names: bit0, bit1, bit2, and bit3).

Now, let's define the bitfield field, that splits two bytes to store version (in lower 12 bits) as well as extra flags (in upper 4 bits)

namespace my_prot
{
class VersionAndFlagsField : public
FieldBase,
std::tuple<
MessageVersion<comms::option::def::FixedBitLength<12> >,
MessageFlags<comms::option::def::FixedBitLength<4> >
>
>
{
// (Re)definition of the base class as inner Base type.
using Base = comms::field::Bitfield<...>;
public:
// Allow access to internal member fields.
};
} // my_prot
#define COMMS_FIELD_MEMBERS_NAMES(...)
Provide names for member fields of composite fields, such as comms::field::Bundle or comms::field::Bi...
Definition: Field.h:380
Bitfield field.
Definition: Bitfield.h:98

Again, just to refresh the reader's memory: the usage of COMMS_FIELD_MEMBERS_NAMES() macro for the bitfield definition will generate field_X() convenience access member functions for the listed names.

Now it's time to actually extend the provided definition of the comms::protocol::TransportValueLayer and support usage of the defined earlier VersionAndFlagsField field.

namespace my_prot
{
template <typename TNextLayer>
class VersionAndFlagsLayer : public
VersionAndFlagsField, // Used field that contains version and flags
std::numeric_limit<std::size_t>::max(),
// Index inside the field inside message's transportFields(),
// irrelevant for this situation, because the interface splits
// the information into two separate fields.
TNextLayer, // Next layer in the protocol stack
comms::option::def::ExtendingClass<VersionAndFlagsLayer<TNextLayer> >
// Make the comms::protocol::TransportValueLayer 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 VersionAndFlagsField
// Re-assign the field's value to available message object.
template <typename TMsg>
bool reassignFieldValueToMsg(const Field& field, TMsg* msgPtr)
{
if (msgPtr == nullptr) {
return false;
}
msgPtr->transportField_version().value() = field.field_version().value();
msgPtr->transportField_flags().value() = field.field_flags().value();
return true;
}
// Update the transport field values to be written when message is serialized.
template <typename TMsg>
static void prepareFieldForWrite(const TMsg& msg, Field& field)
{
field.field_version().value() = msg.transportField_version().value();
field.field_flags().value() = msg.transportField_flags().value();
}
};
} // namespace my_prot
Protocol layer that reads a value from transport wrapping and reassigns it to appropriate "extra tran...
Definition: TransportValueLayer.h:79

The comms::protocol::TransportValueLayer 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::TransportValueLayer.

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.
  • reassignFieldValueToMsg() - Member function that is invoked to re-assign the transport field value to the appropriate transport field(s) in the message object.
  • prepareFieldForWrite() - Member function that is invoked to prepare the field value before its write (serialization). After the function returns, the comms::protocol::TransportValueLayer will invoke write member function of the passed field in order to serialize it.

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

namespace my_prot
{
template <typename TMessage, typename TAllMessages>
struct Frame1 : public
common::field::IntValue<FieldBase, std::uint16_t>,
comms::protocol::MsgIdLayer< // ID
comms::feild::EnumValue<FieldBase, MsgId>,
TMessage,
TAllMessages,
VersionAndFlagsLayer< // VERSION + FLAGS
comms::protocol::MsgDataLayer<> // PAYLOAD
>
>
>
{
// Generate convenience access functions for various layers
COMMS_PROTOCOL_LAYERS_ACCESS_OUTER(size, size, versionFlags, payload);
};
} // namespace my_prot
Protocol layer that uses size field as a prefix to all the subsequent data written by other (next) la...
Definition: MsgSizeLayer.h:75

NOTE, that the common Common Interface Class had to split the version + flags bitfield into two separate fields, because in order to properly support protocol versioning the COMMS library requires a version to reside in the separate extra transport field index to which is passed to the interface definition using comms::option::VersionInExtraTransportFields option. Otherwise automatic presence of the version dependent optional fields could not be detected.