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

The COMMS library provides default comms::protocol::ChecksumLayer and comms::protocol::ChecksumPrefixLayer protocol stack layer to handle transport framing checksum. However, they may be insufficient (or incorrect) for some particular use cases. For example the protocol may support multiple checksum algorithms usage of which is determined at runtime. 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::ChecksumLayer as well as comms::protocol::ChecksumPrefixLayer and customize some bits and pieces. Let's implement the mentioned above example of supporting multiple checksum algorithms.

This tutorial page focuses on the customization of comms::protocol::ChecksumLayer, but all the listed customization points are also applicable to comms::protocol::ChecksumPrefixLayer.

For this example the protocol framing is defined to be

SIZE | ID | CHECKSUM_TYPE | PAYLOAD | CHECKSUM

First of all let's define the Common Interface Class, which holds the checksum type 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 checksum type
enum ChecksumTypeVal : std::uint8_t
{
Sum8,
Crc16,
Crc32
};
// Definition of the checksum type field
class ChecksumType : public
FieldBase,
ChecksumTypeVal
>
{
};
// 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<
ChecksumType
>
>
>
{
// (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(checksumType);
};
} // namespace my_prot
Main interface class for all the messages.
Definition: Message.h:80
Enumerator value field.
Definition: EnumValue.h:73

Just to refresh the reader's memory: the usage of COMMS_MSG_TRANSPORT_FIELDS_NAMES() macro for the interface definition will generate transportField_checksumType() convenience member function to access the stored checksum type field.

When implementing the transport framing (protocol stack) the handling of CHECKSUM_TYPE can be done using regular comms::protocol::TransportValueLayer (see Extra Transport Values).

Now it's time to actually extend the provided definition of the comms::protocol::ChecksumLayer and support usage of multiple checksum algorithms.

namespace my_prot
{
// Field to contain all possible checksums
template <typename TNextLayer>
class MyChecksumLayer : public
ChecksumField,
comms::protocol::checksum::Crc_32, // Not really important for this example,
// but template parameter type needs to be populated
TNextLayer, // Next layer in the protocol stack
comms::option::def::ExtendingClass<MyChecksumLayer<TNextLayer> >
// Make the comms::protocol::ChecksumLayer aware of it being extended
>
{
// Repeat definition of the base class
using Base = comms::protocol::ChecksumLayer<...>;
public:
// Repeat types defined in the base class (not visible by default)
using Field = typename Base::Field; // same as ChecksumField
// Override default way of calculating checksum
template <typename TMsg, typename TIter>
static typename Field::ValueType calculateChecksum(const TMsg* msgPtr, TIter& iter, std::size_t len, bool& checksumValid)
{
if (msgPtr == nullptr) {
checksumValid = false;
return static_cast<typename Field::ValueType>(0);
}
checksumValid = true;
auto checksumType = msgPtr->transportField_checksumType().value();
if (checksumType == ChecksumTypeVal::Sum8) {
return Calc()(iter, len);
}
if (checksumType == ChecksumType::Crc16) {
return Calc()(iter, len);
}
if (checksumType == ChecksumType::Crc32) {
return Calc()(iter, len);
}
checksumValid = false;
return static_cast<typename Field::ValueType>(0);
}
// Due to the fact that the used checksums have different lengths, the
// functionality of reading a field's value also needs to be customized.
template <typename TMsg, typename TIter>
static comms::ErrorStatus doReadField(const TMsg* msgPtr, Field& field, TIter& iter, std::size_t len)
{
if (msgPtr == nullptr) {
}
auto checksumType = msgPtr->transportField_checksumType().value();
if (checksumType == ChecksumType::Sum8) {
return readFieldInternal<std::uint8_t>(field, iter, len);
}
if (checksumType == ChecksumType::Crc16) {
return readFieldInternal<std::uint16_t>(field, iter, len);
}
if (checksumType == ChecksumType::Crc32) {
return readFieldInternal<std::uint32_t>(field, iter, len);
}
}
// Due to the fact that the used checksums have different lengths, the
// functionality of reading a field's value also needs to be customized.
template <typename TMsg, typename TIter>
static comms::ErrorStatus doWriteField(const TMsg* msgPtr, const Field& field, TIter& iter, std::size_t len)
{
if (msgPtr == nullptr) {
}
auto checksumType = msgPtr->transportField_checksumType().value();
if (checksumType == ChecksumType::Sum8) {
return writeFieldInternal<std::uint8_t>(field, iter, len);
}
if (checksumType == ChecksumType::Crc16) {
return writeFieldInternal<std::uint16_t>(field, iter, len);
}
if (checksumType == ChecksumType::Crc32) {
return writeFieldInternal<std::uint32_t>(field, iter, len);
}
}
// Due to the fact that the used checksums have different lengths, the
// functionality of calculating the field's length needs to be overriden
template <typename TMsg>
static std::size_t doFieldLength(const TMsg& msg)
{
auto checksumType = msg.transportField_checksumType().value();
if (checksumType == ChecksumType::Sum8) {
return sizeof(std::uint8_t);
}
if (checksumType == ChecksumType::Crc16) {
return sizeof(std::uint16_t);
}
if (checksumType == ChecksumType::Crc32) {
return sizeof(std::uint32_t);
}
COMMS_ASSERT(!"Should not happen");
return 0;
}
private:
template <typename TTmpType, typename TIter>
static comms::ErrorStatus readFieldInternal(Field& field, TIter& iter, std::size_t len)
{
FieldTmp fieldTmp;
auto es = fieldTmp.read(iter, len);
return es;
}
field = comms::field_cast<Field>(fieldTmp);
return es;
}
template <typename TTmpType, typename TIter>
static comms::ErrorStatus writeFieldInternal(const Field& field, TIter& iter, std::size_t len)
{
auto fieldTmp = comms::field_cast<FieldTmp>(field); // Re-assign the field's value
return fieldTmp.write(iter, len);
}
};
} // namespace my_prot
#define COMMS_ASSERT(expr)
Generic assert macro.
Definition: Assert.h:170
Field that represent integral value.
Definition: IntValue.h:72
ErrorStatus read(TIter &iter, std::size_t size)
Read field value from input data sequence.
Definition: IntValue.h:305
Protocol layer that is responsible to calculate checksum on the data written by all the wrapped inter...
Definition: ChecksumLayer.h:73
Summary of all bytes checksum calculator.
Definition: BasicSum.h:32
Crc< std::uint32_t, 0x04c11db7, 0xffffffff, 0xffffffff, true, true > Crc_32
Alias to Crc checksum calculator for standard CRC-32.
Definition: Crc.h:399
Crc< std::uint16_t, 0x8005, 0, 0, true, true > Crc_16
Alias to Crc checksum calculator for standard CRC-16.
Definition: Crc.h:389
ErrorStatus
Error statuses reported by the Communication module.
Definition: ErrorStatus.h:17
@ Success
Used to indicate successful outcome of the operation.

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

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.

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

namespace my_prot
{
template <typename TMessage, typename TAllMessages>
struct Frame1 : public
MyChecksumLayer< // CHECKSUM
comms::protocol::MsgSizeLayer< // SIZE
common::field::IntValue<FieldBase, std::uint16_t>,
comms::protocol::MsgIdLayer< // ID
comms::feild::EnumValue<FieldBase, MsgId>,
TMessage,
TAllMessages,
comms::field::TransportValueLayer< // CHECKSUM_TYPE
ChecksumType,
Message::TransportFieldIdx_checksumType,
comms::protocol::MsgDataLayer<> // PAYLOAD
>
>
>
>
{
// Generate convenience access functions for various layers
COMMS_PROTOCOL_LAYERS_ACCESS_OUTER(checksum, size, size, checksumType, payload);
};
} // namespace my_prot