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
{
using FieldBase = comms::field::Field<comms::option::def::BigEndian>;
using SyncField = common::field::IntValue<FieldBase, std::uint16_t>;
template <typename TNextLayer>
class AlternatingPrefixLayer : public
SyncField,
TNextLayer,
comms::option::def::ExtendingClass<AlternatingPrefixLayer<TNextLayer> >
>
{
public:
using Field = typename Base::Field;
bool verifyFieldValue(const Field& field)
{
bool valid = (field.value() == getPrefix(m_inputCount));
if (valid) {
++m_inputCount;
}
return valid;
}
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;
};
}
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<
comms::protocol::MsgSizeLayer<
common::field::IntValue<FieldBase, std::uint16_t>,
comms::protocol::MsgIdLayer<
comms::feild::EnumValue<FieldBase, MsgId>,
TMessage,
TAllMessages,
comms::protocol::MsgDataLayer<>
>
>
>
{
COMMS_PROTOCOL_LAYERS_ACCESS_OUTER(sync, size, size, payload);
};
}