The commsdsl2comms utility uses libcommsdsl shared library to parse the XML schema files. On Windows system the shared library is installed in the same directory with commsdsl2comms.exe binary. As the result when executing## Overview The commsdsl2comms utility generates a full CMake project. Since v1.4 it also generates test application which can be used to test the generated protocol code. Fuzz testing with AFL is also supported.
After reading this documentation page it is also recommended to read GeneratedProjectWalkthrough.md to get familiar with the structure of the generated output project as well as TestingGeneratedProtocolCode.md to understand how to fuzz test the generated protocol definition code.
The commsds2comms utility has multiple command line arguments, please
use -h
option for the full list as well as default option values.
Note, that all the examples below will
be for Linux systems (using commsdsl2comms.sh script). On Windows system
just use commsdsl2comms.exe binary directly.
$> /path/to/commsdsl2comms.sh -h
Below is a summary of most important ones.
In case there are only few schema files, it is possible to pass them at the end of the arguments list.
$> /path/to/commsdsl2comms.sh schema1.xml schema2.xml schema3.xml ...
The schema files will be processed in order of their listing.
In case there are lots of schema files (for example every message
is defined in separate schema file), it is recommended to create a separate
text file with list of all schema files and use -i
option.
$> /path/to/commsdsl2comms.sh -i schemas_list.txt
The schemas in the list file may use use absolute or relative path. In case
of the latter please also provide absolute path prefix using -p
option. The
prefix will be prepended to every relative path inside the list file to locate
the schema file.
$> /path/to/commsdsl2comms.sh -i schemas_list.txt -p /path/to/schemas/dir
By default the output CMake project is written to the current directory. It
is possible to change that using -o
option.
$> /path/to/commsdsl2comms.sh -o /some/output/dir schema.xml
The commsdsl2comms utility allows injection of custom C++ code into the
generated one in case the default code is incorrect and/or incomplete. For this
purpose -c
option with path to directory containing custom code snippets is used.
$> /path/to/commsdsl2comms.sh -c /path/to/custom/code/snippets schema.xml
Please read Custom Code section below for more details on how to format and where to place the custom code.
By default the protocol name defined in the schema file(s) is used as the
main namespace for the generated code. It is possible to change it using
-n
option.
$> /path/to/commsdsl2comms.sh -n other_ns_name schema.xml
The schema file(s) is expected to specify protocol numeric version. By default
the generated code will be for the latest version including all the fields that
were introduced at some stage and omitting all the deprecated and removed
ones. However, the commsdsl2comms
utility allows generation of the code for any version of the protocol by using
--force-schema-version
option.
$> /path/to/commsdsl2comms.sh --force-schema-version 2 schema.xml
For version dependent protocols (where the used protocol version is reported
to the other side in transport framing or payload of one of the messages), the
assumed minimal remote version is 0. In means all the fields that were
introduced at later stage will be optional ones that can exist or be missing.
If it is known for sure that the other side of communication won't use some early
versions, it is possible to generate more efficient code by passing -m
option.
$> /path/to/commsdsl2comms.sh -m 5 schema.xml
In the example above, all the fields that were introduced before or in version 5, will be regular ones (instead of optional).
The code generated by the commsdsl2comms utility can allow extra compile time customizations (such as choosing custom storage type and/or having extra functionality). There are 3 levels of available customizations:
- full - All fields and messages are going to be customizable.
- limited - Only variable length fields (such as
<string>
,<data>
, and<list>
) and uni-directional messages will be customizable. - none - No fields or messages will allow extra compile time customization.
The recommended (and default) customization level is limited. However, when protocol schema being developed (i.e. new fields / and messages are being constantly added) it is recommended to temporarily use none as customization level in order to reduce (re)compilation times of the target project.
The customization level can be selected using --customization
option.
$> /path/to/commsdsl2comms.sh --customization=none schema.xml
The commsdsl2comms utility produces CMake project, which depends on and uses
comms_champioin one. If the latter
is not provided as external build, it is checked out and built automatically.
The --cc-tag
option can be used to specify tag / branch of
comms_champion project that
is going to be used in such build.
$> /path/to/commsdsl2comms.sh --cc-tag=develop schema.xml
The commsdsl2comms utility creates multiple bundles of messages based
on their direction (server vs client) as well as relevant code for dispatching
messages from such bundles to appropriate handlers. It is possible to provide
extra independent list of message types for extra bundles and extra relevant
dispatching code generation. For such purpose \n
separated list of message
names needs to be created in a sepeare file and --extra-messages-bundle
option
to be used (can be used multiple times). The format of the option value is
Name:File
, where Name
is the name of the bundle, while File
is a path
to file containing message names (as defined in CommsDSL schema).
$> /path/to/commsdsl2comms.sh \
--extra-messages-bundle=Set1:extra-set1.txt \
--extra-messages-bundle=Set2:extra-set2.txt \
schema.xml
As was already mentioned earlier, commsds2comms utility allows injection
of custom C++11 code snippets in the generated code. The
Injecting Custom Code section above described -c
option that can be used to specify directory with custom code snippets.
Every file inside that directory must have the same relative path to the file its
going to update, as the generated file inside the output directory.
Every global field and/or message class will be defined in a separate file and will have the following basic operations:
- read - Read operation
- write - Write operation
- length - Serialization length calculation
- valid - Contained value validity check
- refresh - Bring the contents into consistent state
- name - Get a descriptive name of the field / message
In order to replace the default implementation of any of these operations,
the file with the same name (and relative path) must be created, but also have
additional suffix with the operation name. For example let's assume we have
protocol named demo, with message Msg1. The class for the message
will reside in include/demo/message/Msg1.h
. In order to replace the default read
operation, there is a need to create include/demo/message/Msg1.h.read
file
which will define custom read() function. Following the same logic to
replace all the operations there is a need to have the following files:
include/demo/message/Msg1.h.read
include/demo/message/Msg1.h.write
include/demo/message/Msg1.h.length
include/demo/message/Msg1.h.valid
include/demo/message/Msg1.h.refresh
include/demo/message/Msg1.h.name
Note that for fields the provided functions are expected to have the following signatures.
template <typename TIter>
comms::ErrorStatus read(TIter& iter, std::size_t len);
template <typename TIter>
comms::ErrorStatus write(TIter& iter, std::size_t len) const;
std::size_t length() const;
bool valid() const;
bool refresh();
static const char* name();
For messages it is similar, but with do*
prefix.
template <typename TIter>
comms::ErrorStatus doRead(TIter& iter, std::size_t len);
template <typename TIter>
comms::ErrorStatus dowrite(TIter& iter, std::size_t len) const;
std::size_t doLength() const;
bool doValid() const;
bool doRefresh();
static const char* doName();
It is also possible to add unrelated custom code to public, protected, and/or private areas by using relevant suffix.
include/demo/message/Msg1.h.public
include/demo/message/Msg1.h.protected
include/demo/message/Msg1.h.private
In case there is a need for a custom constructor / destructor / assignment operator, use public section to define it.
The custom operation may required additional includes, which may be provided by using appropriate definition file with .inc suffix. The contents of this file will be added after all the default includes.
include/demo/message/Msg1.h.inc
It may happen that provided above customization options are not sufficient, and need to be completely rewritten. In this case it is possible to create appropriate file with .replace suffix, contents of which will completely replace the file generated by commsdsl2comms.
include/demo/message/Msg1.h.replace
The commsdsl2comms also allows to reuse the class it generates by default
and extend the existing functionality with the new one. It can be achieved by
defining a file with .extend suffix. Similar to .replace, it must define
the full class, but it is expected to extend and reuse default definition which
will reside in another file with Orig suffix added to its name
(include/demo/message/Msg1Orig.h
).
There are cases when commsdsl2comms cannot generate some pieces of code and they must be provided externally. For example, custom checksum algorithm and/or custom framing layer. The custom definition files are expected to be found in the following relative paths:
include/<main_namespace>/frame/checksum/<checksum_name>.h
include/<main_namespace>/frame/layer/<layer_name>.h
The example of checksum definition can be found at UbloxChecksum.h file from cc.ublox.commsdsl protocol definition.
The example of custom layer can be found at IdAndFlags.h file from cc.mqtt311.commsdsl protocol definition (defines custom ID layer that also contains extra flags) or at Length.h file from cc.mqttsn.commsdsl protocol definition.
Please note that custom layer that replaces <id>
one is expected to use
the following template parameters.
/// @tparam TField Used field type
/// @tparam TMessage Interface class
/// @tparam TAllMessages Input messages
/// @tparam TNext Layer Next frame layer
/// @tparam TExtraOpt Extra options passed to @b comms::MsgFactory
template <
typename TField,
typename TMessage,
typename TAllMessages,
typename TNextLayer,
typename... TExtraOpt>
class MyIdLayer : public ...
{
...
};
Any other custom layer is expected to use the following template parameters
/// @tparam TField Used field type
/// @tparam TNext Layer Next frame layer
/// @tparam TExtraOpt Extra options passed to @b comms::MsgFactory
template <
typename TField,
typename TNextLayer,
typename... TExtraOpts>
class MyCustomLayer : public ...
{
...
};
NOTE, that customization is available for global fields (including ones that defined in any separate namespace) as well as messages. At this stage injecting custom code to the fields defined inside message body is not supported.
For more code customization examples it is recommended to take a look at the following real-life protocols.
The commsdsl2comms also allows appending any text to any generated file
(not necessarily C++ code). It is done by creating appropriate file
with .append suffix. For example, adding extra logic to the generated CMake
file(s) are possible by creating CMakeLists.txt.append
file with custom
content.
The commsdsl2comms utility will also copy all the files, residing in the source directory and not having any of the special suffixes (.read, .write, .length, etc...), as-is without modification into the output directory preserving their relative path. It is recommended to read through the GeneratedProjectWalkthrough.md documentation page to get to know what directories / namespaces are used to contain particular classes.