cc_tools_qt
Common Environment for Protocol Analysis.
Loading...
Searching...
No Matches
Developing Custom Protocol Plugin

Developing the protocol plugins is significantly more complex than developing the socket or filter ones. Please follow this tutorial, while observing the implementation of the Demo Protocol (the sources are here) provided as part of the CommsChampion Tools Project.

Protocol Definition

The protocol messages as well as wrapping transport information need to be implemented using COMMS Library in a certain generic way, which will allow it to be compiled and used in any application, whether it is bare-metal platform with limited resouces or protocol plugin for CommsChampion Tools.

First of all, the common message interface class needs to inherit from or be alias to comms::Message class, while providing only endian and type of ID information as options. It must also provide variadic template parameter to allow extension of the interface with more options.

namespace demo
{
template <typename... TOptions>
using DemoMessage =
comms::Message<
comms::option::BigEndian,
comms::option::MsgIdType<MsgId>, // MsgId is enum with numeric message IDs
TOptions...
>;
} // namespace demo

Such definition leaves room for the application to define the required polymorphic interface as well as choose iterators for read/write operations.

All the actual message definition classes are expected to receive their interface class as a template parameter.

namespace demo
{
template <typename TMsgBase>
class IntValues : public
comms::MessageBase<
TMsgBase,
comms::option::StaticNumIdImpl<MsgId_IntValues>,
comms::option::FieldsImpl<IntValuesFields::All>,
comms::option::MsgType<IntValues<TMsgBase> >
>
{
...
};
} // namespace demo

NOTE, that the message definition class inherits from comms::MessageBase, which will inherit from provided TMsgBase template parameter. The provided template parameter must be alias to, or inherit (directly or indirectly) from comms::Message.

When defining transport layers, allow selection of the common interface class as well as input messages as template parameters (see Stack.h as an example).

namespace demo
{
template <typename TMsgBase, typename TMessages, ...>
using Stack = ...;
} // namespace demo

Messages in Plugin Environment

When developing plugin for CommsChampion Tools, there is a need to define separate message interface class, which inherits from cc_tools_qt::MessageBase and provides the defined earlier interface class as its template parameter. See cc_plugin/DemoMessage.h as an example.

namespace demo
{
namespace cc_plugin
{
class DemoMessage : public cc_tools_qt::MessageBase<demo::DemoMessage>
{
...
};
} // namespace cc_plugin
} // namespace demo
Helper class used to define protocol message interface class in CommsChampion Tools plugin environmen...
Definition MessageBase.h:62

Please note, that the first template parameter of cc_tools_qt::MessageBase is a "template template" one, and the common interface class name defined earlier (demo::DemoMessage) is passed to it "as-is" without angle brackets.

The cc_tools_qt::MessageBase uses multiple inheritance to extend both cc_tools_qt::Message and provided custom protocol message interface class (demo::DemoMessage). When extending provided interface class, additional options are passed to it to allow full available polymorphic interface (read, write, length retrieval, validity check, etc...). The cc_tools_qt::Message is the main interface class used by the CommsChampion Tools to manipulate messages.

The class hierarchy will look like this:

The cc_tools_qt::Message interface class has multiple pure virtual functions, many of them are implemented inside cc_tools_qt::MessageBase. One of them is cc_tools_qt::Message::idAsStringImpl(). It is used to convert the message numeric ID into string representation to display it to user. The default implementation inside cc_tools_qt::MessageBase will convert it as is. However, it is recommended to override this function and provide better formatting.

namespace demo
{
namespace cc_plugin
{
class DemoMessage : public cc_tools_qt::MessageBase<demo::DemoMessage>
{
protected:
virtual QString idAsStringImpl() const override
{
// uses getId() inherited from demo::DemoMessage
return QString("0x%1").arg(getId(), 2, 16, QChar('0'));
}
};
} // namespace cc_plugin
} // namespace demo

Please note, that there are two polymorphic interface classes: cc_tools_qt::Message and comms::Message (or demo::DemoMessage). The latter defines its virtual functions, which are implemented as part of protocol message definition (using comms::MessageBase). The cc_tools_qt::Message is used only by CommsChampion Tools and defines its own (pure) virtual functions which are expected to be overridden by the message implementation class. One of them is message name retrieval.

namespace cc_tools_qt
{
class Message
{
...
protected:
virtual const char* nameImpl() const = 0;
};
} // namespace cc_tools_qt
virtual const char * nameImpl() const =0
Polymorphic name retrieval functionality.
Main namespace for all classes / functions of the shared library.

In order to be able to instantiate message object this pure virtual function needs to be implemented. In order to do this, there is a need to extend the existing implementation of the defined protocol message and implement missing function. Let's take IntValues.h and cc_plugin/message/IntValues.h as an example. The latter is implemented in the following way:

namespace demo
{
namespace cc_plugin
{
namespace message
{
class IntValues : public
demo::message::IntValues<demo::cc_plugin::DemoMessage>,
IntValues
>
{
...
protected:
virtual const char* nameImpl() const override
{
return "IntValues";
}
};
} // namespace message
} // namespace cc_plugin
} // namespace demo
Helper class used to implement several pure virtual functions defined in cc_tools_qt::Message interfa...
Definition ProtocolMessageBase.h:44

Please note that the class inherits from cc_tools_qt::ProtocolMessageBase, which overrides and implements two pure virtual functions defined by cc_tools_qt::Message:

Also note, that cc_tools_qt::ProtocolMessageBase class receives two template parameters. The first one is the protocol message class the cc_tools_qt::ProtocolMessageBase will inherit from (demo::message::IntValues<demo::cc_plugin::DemoMessage>). The second one is the type of the message class itself (IntValues).

One more thing to note, is the previously defined demo::cc_plugin::DemoMessage is passed as interface class to demo::message::IntValues (as a template parameter).

The full inheritence hierarchy will look like this:

Fields and Their Properties

The CommsChampion Tools are there to help visualise and debug custom binary protocols. The definition of the messages using COMMS library is used to define serialisation and deserialisation of the protocol messages. However, such definitions do not contain any extra information on how the message fields need to be displayed. For example, when displaying a value of comms::field::EnumValue field, there is a need to display a human readable "name" of the value in addition to its serialised raw bytes.

In order to provide the required information, the message definition class for the protocol plugin (demo::cc_plugin::IntValues) is expected to override inherited cc_tools_qt::Message::fieldsPropertiesImpl() and provide the required properties of the fields.

namespace demo
{
namespace cc_plugin
{
namespace message
{
class IntValues : public
demo::message::IntValues<demo::cc_plugin::DemoMessage>,
IntValues
>
{
...
protected:
virtual const QVariantList& fieldsPropertiesImpl() const override
{
static const QVariantList Props = createFieldsProperties(); // defined in anonymous namespace
return Props;
}
};
} // namespace message
} // namespace cc_plugin
} // namespace demo

NOTE, that the properties are defined as static const variable and created only once upon first entry. The field's properties are the description on the way how the fields need to be displayed and do not depend on the field's value.

Also note, that the properties are stored in QVariantList, size of which must be equal to number of fields, the message contains.

namespace
{
QVariantList createFieldsProperties()
{
QVariantList props;
props.append(.../* properties of field1 */);
props.append(.../* properties of field2 */);
...
assert(props.size() == IntValues::FieldIdx_numOfValues);
return props;
}
} // namespace

One more thing to note is that set of properties for a single field are stored in QVariantMap. However, it is implicitly converted to QVariant when inserted into the QVariantList.

If the message doesn't have any fields, there is no need to override cc_tools_qt::Message::fieldsPropertiesImpl(). The cc_tools_qt::Message class itself provides default implementation which returns empty list.

The helper classes that allow definition of the fields' properties reside in cc_tools_qt::property::field namespace, please refer to Defining Fields' Properties separate tutorial page for details on how to use them.

All Messages

Afther all the message classes for the protocol plagin were defined, they need to be bundled into std::tuple (see cc_plugin/AllMessages.h).

namespace demo
{
namespace cc_plugin
{
using AllMessages = std::tuple<
cc_plugin::message::IntValues,
cc_plugin::message::EnumValues,
cc_plugin::message::BitmaskValues,
cc_plugin::message::Bitfields,
cc_plugin::message::Strings,
cc_plugin::message::Lists,
cc_plugin::message::Optionals,
cc_plugin::message::FloatValues,
cc_plugin::message::Variants
>;
static_assert(std::tuple_size<AllMessages>::value == MsgId_NumOfValues,
"Some messages are missing");
} // namespace cc_plugin
} // namespace demo

Protocol Stack

The Protocol Definition section above defined how protocol stack should be defined when implementing common protocol definition. Usage of the template parameters leaves us space to redefine it for CommsChampion Tools plugin environment (see cc_plugin/DemoStack.h).

namespace demo
{
namespace cc_plugin
{
using DemoStack = demo::Stack<
cc_plugin::Message,
cc_plugin::AllMessages
>;
} // namespace cc_plugin
} // namespace demo

NOTE, that the passed first template parameter is the common interface class for all the message classes in the plugin environment (defined in the Messages in Plugin Environment section), and the second template parameter is the messages themselves.

Transport Message

In order to understand what the "Transport Message" is, let's take a look how the cc_view (main GUI application of the CommsChampion Tools) displays a message. The bottom right area allows to request a display of "Transport" information instead of message itself with its fields.

When clicked it displays the transport information instead of application message.

The displayed transport information is presented like any other message, but with fields difined by the "protocol stack".

In order to support displaying of the transport information for the newly developed protocol plugin, there is a need to create a dummy message, which will contain all the fields defined by the protocol stack. Please see the cc_plugin/DemoTransportMessage.h and cc_plugin/DemoTransportMessage.cpp as an example.

namespace demo
{
namespace cc_plugin
{
class DemoTransportMessage : public
cc_plugin::Message,
std::tuple<
demo::SyncField,
demo::LengthField,
demo::MsgIdField,
demo::DataField<>,
demo::ChecksumField
>
>
{
...
};
} // namespace cc_plugin
} // namespace demo
Base class for TransportMessage definition in protocol plugin.
Definition TransportMessageBase.h:60

Note, that the demo::cc_plugin::DemoTransportMessage class inherits from cc_tools_qt::TransportMessageBase providing two template parameters. The first one is common message interface class (demo::cc_plugin::Message) defined in Messages in Plugin Environment section. The second parameter is std::tuple of all the fields used to defined "protocol stack", but appearing in order of their serialisation.

The "protocol stack" defines its internal type AllFields, which contains all the "transport" fields in order of their appearance in protocol stack definition. If order of fields' serialisation is the same as order of fields' appearance in protocol stack, the AllFields internal type could be reused. However, this is not the case for demo protocol. The latter defines its protocol stack in the following way:

The CHECKSUM layer wraps the LENGTH. As the result the fields are defined in the following order.

std::tuple<
demo::SyncField,
demo::ChecksumField, // <--- The checksum is here
demo::LengthField,
demo::MsgIdField,
demo::DataField<>
>

In case of direct reusing of demo::Stack::AllFields the transport information would be displayed incorrectly.

The DemoTransportMessage is a descendent of common message interface class defined for the plugin (demo::cc_plugin::DemoMessage) and is expected to provide properties of its fields.

namespace demo
{
namespace cc_plugin
{
class DemoTransportMessage : public
{
...
protected:
virtual const QVariantList& fieldsPropertiesImpl() const override;
};
} // namespace cc_plugin
} // namespace demo

The cc_plugin/DemoTransportMessage.cpp file contains code of this function. Please refer to Defining Fields' Properties tutorial page to learn how to set field's properties.

the TransportMessage is also a descendent of common message interface class defined as part of the protocol definition (demo::DemoMessage). One of the base classes up the inheritance chain provides default implementation of comms::Message::readImpl() virtual function. However, in case of the demo protocol, the default implementation is incorrect. An attempt to deserialise such message will fail. This is because the PAYLOAD field doesn't have any length limitation and will consume all input without leaving anything to the CHECKSUM value. In order to fix it the implementation of demo::cc_plugin::DemoTransportMessage needs to override it and provide correct implementation.

namespace demo
{
namespace cc_plugin
{
class DemoTransportMessage : public
{
public:
enum FieldIdx
{
FieldIdx_Sync,
FieldIdx_Len,
FieldIdx_Id,
FieldIdx_Payload,
FieldIdx_Checksum,
FieldIdx_NumOfValues
};
...
protected:
virtual comms::ErrorStatus readImpl(ReadIterator& iter, std::size_t size) override
{
static const auto ChecksumLen =
sizeof(demo::ChecksumField::ValueType);
size -= ChecksumLen;
auto es = readFieldsUntil<FieldIdx_Checksum>(iter, size);
if (es == comms::ErrorStatus::Success) {
size += ChecksumLen;
es = readFieldsFrom<FieldIdx_Checksum>(iter, size);
}
return es;
}
};
} // namespace cc_plugin
} // namespace demo

The readFieldsUntil() and readFieldsFrom() functions are provided by comms::MessageBase class, which is also one of the ancestors to the TransportMessage class.

Protocol Class

The CommsChampion Tools use cc_tools_qt::Protocol polymorphic interface class to create and update messages. It means that our protocol definition class needs to be a descendent of cc_tools_qt::Protocol. The latter defines multiple pure virtual functions, which the derived class must implement. The cc_tools_qt library also provides cc_tools_qt::ProtocolBase template class, which inherits from cc_tools_qt::Protocol and implements most of the required virtual functions using info from provided template parameters. Hence, the custom protocol definition class needs to inherit from cc_tools_qt::ProtocolBase and provide necessary info.

The demo protocol defines its Protocol class in cc_plugin/DemoProtocol.h and cc_plugin/DemoProtocol.cpp files.

namespace demo
{
namespace cc_plugin
{
class DemoProtocol : public
cc_plugin::DemoStack,
DemoTransportMessage
>
{
public:
DemoProtocol() = default;
virtual ~DemoProtocol();
protected:
virtual const QString& nameImpl() const override
{
static const QString& Str("Demo");
return Str;
}
};
} // namespace cc_plugin
} // namespace demo
Helper class to define custom Protocol.
Definition ProtocolBase.h:57

Please note the following:

Plugin Class

The final thing to do is to define the actual plugin class. Please read the Defining a Plugin page first to understand the way the plugins are defined.

The demo protocol defines its Plugin class in cc_plugin/DemoPlugin.h and cc_plugin/DemoPlugin.cpp files.

namespace demo
{
namespace cc_plugin
{
class DemoPlugin : public cc_tools_qt::Plugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "cc.DemoProtocol" FILE "demo.json")
Q_INTERFACES(cc_tools_qt::Plugin)
public:
Plugin()
{
pluginProperties()
.setProtocolCreateFunc(
[this]()
{
// allocates previously defined demo::cc_plugin::Protocol
return cc_tools_qt::ProtocolPtr(new Protocol());
});
}
};
} // namespace cc_plugin
} // namespace demo
Interface class for plugin definition.
Definition Plugin.h:39
std::shared_ptr< Protocol > ProtocolPtr
Pointer to Protocol object.
Definition Protocol.h:293

If the protocol requires configuration, please also provide a callback which creates the widget when invoked (see Configuration Widget).