COMMS
Template library intended to help with implementation of communication protocols.
Loading...
Searching...
No Matches
Defining Custom Message ID Protocol Stack Layer

The COMMS library provides default comms::protocol::MsgIdLayer protocol stack layer to manage message ID information in the protocol framing. However, it may be insufficient (or incorrect) for some particular use cases, such as using bitfield field to store both numeric message ID and some extra flags (like MQTT protocol does). The Implementing New Layers section of the Protocol Stack Definition Tutorial page explains how to define new (custom) protocol layer.

NOTE, that comms::protocol::MsgIdLayer class contains significant amount of compile time logic of choosing the best code based on provided options as well as available polymorphic interface messages being read and/or written. It is very impractical to try to implement something similar from scratch or copy-paste the existing definition and introduce required changes.

Since v1.2 COMMS library provides an ability to extend the existing definition of comms::protocol::MsgIdLayer and customize some bits and pieces. Let's implement the mentioned example of sharing the same byte for numeric ID and some flags.

First of all let's define the Common Interface Class, which holds the 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 flags
class MessageFlags : public
FieldBase,
comms::option::def::BitmaskReservedBits<0xf0>
>
{
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<MessageFlags> >
>
{
// (Re)define base class as inner Base type.
using Base = comms::Message<...>;
public:
// Allow access to extra transport fields.
COMMS_MSG_TRANSPORT_FIELDS_NAMES(flags);
};
} // namespace my_prot
Main interface class for all the messages.
Definition Message.h:80
Bitmask value field.
Definition BitmaskValue.h:103

Just to refresh the reader's memory: the usage of COMMS_MSG_TRANSPORT_FIELDS_NAMES() macro for the interface definition will generate transportField_flags() convenience member function to access the stored flags field, 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 one byte in half to store numeric message ID (in lower 4 bits) as well as extra flags (in upper 4 bits)

namespace my_prot
{
class IdAndFlagsField : public
FieldBase,
std::tuple<
comms::field::EnumValue<FieldBase, MsgId, comms::option::def::FixedBitLength<4> >,
comms::field::IntValue<FieldBase, std::uint8_t, comms::option::def::FixedBitLength<4> >
>
>
{
// (Re)definition of the Base class as inner 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::MsgIdLayer and support usage of the defined earlier IdAndFlagsField field.

namespace my_prot
{
template <typename TMessage, typename TAllMessages, typename TNextLayer, typename... TOptions>
class MsgIdAndFlagsLayer : public
IdAndFlagsField, // Used field that contains message ID
TMessage, // Common interface class
TAllMessages, // std::tuple of all the supported input messages
TNextLayer, // Next layer in the protocol stack
TOptions..., // Application specific extension options
comms::option::def::ExtendingClass<MsgIdAndFlagsLayer<TMessage, TAllMessages, TNextLayer, TOptions...> >
// Make the comms::protocol::MsgIdLayer aware of it being extended
>
{
// Repeat definition of the base class
using Base = comms::protocol::MsgIdLayer<...>;
public:
// Repeat types defined in the base class (not visible by default)
using MsgIdType = typename Base::MsgIdType;
using MsgIdParamType = typename Base::MsgIdParamType;
using Field = typename Base::Field; // same as IdAndFlagsField
// Retrieve message ID value from the given IdAndFlagsField field
static MsgIdType getMsgIdFromField(const Field& field)
{
return field.field_id().value();
}
// Set flags value for the message object before proceeding to the next layer read
// The message object is passed by reference
template <typename TMsg>
static void beforeRead(const Field& field, TMsg& msg)
{
msg.transportField_flags().value() = field.field_flags().value();
}
// Assemble the field's value before its write given message ID as well
// as message object itself.
template <typename TMsg>
static void prepareFieldForWrite(MsgIdParamType id, const TMsg& msg, Field& field)
{
field.field_id().value() = id;
field.field_flags().value() = msg.transportField_flags().value();
}
};
} // namespace my_prot
Protocol layer that uses uses message ID field as a prefix to all the subsequent data written by othe...
Definition MsgIdLayer.h:80

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

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.
  • getMsgIdFromField() - Member function that is invoked to retrieve the numeric message ID out of the provided field object.
  • beforeRead() - Member function that is invoked after appropriate message object has been created but the read operation has NOT yet been forwarded to the next protocol layer. It gives the developer a chance to update some extra transport fields accessible via message interface class.
  • prepareFieldForWrite() - Member function that is invoked to prepare the field value before its write (serialization). After the function returns, the comms::protocol::MsgIdLayer 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::MsgIdLayer when defining protocol stack (framing) of the protocol.