Tutorial 20
Reporting protocol version in one of the messages.
This tutorial is a logical continuation of the previous one. It demonstrates a protocol definition when client reports its version in one of the messages.
Usually in such the client has a special Connect (or similar) message that reports
its version to the server. The latter either adjusts its communication version to match
the client's (like in this tutorial) or reports its own version in some kind of
acknowledgement response message.
The relevant parts of this tutorial schema looks like this:
<fields> <string name="ConnectMsgName" defaultValue="Connect" /> ... <enum name="MsgId" type="uint8" semanticType="messageId"> <validValue name="Connect" val="0" displayName="^ConnectMsgName" /> ... </enum> <int name="Version" type="uint16" semanticType="version" /> </fields> <interface name="Interface" description="Common Interface for all the messages."> <ref field="Version" /> </interface> <message name="Connect" id="MsgId.Connect" displayName="^ConnectMsgName" sender="client"> <ref field="Version" /> </message>
REMINDER: The version field (marked with semanticType="version") is default constructed to have
a value equal to the schema version (5 in this tutorial).
This creates a problem for the server that was compiled with version 5 (with default value of
the Version field inside the <interface> to be 5), but the connected client
has version 4. There is a need for the Version field of the <interface> to have updated
value before the read() operation of the newly created message object is invoked.
In order to properly support such case the following trick is required:
<frame name="Frame"> <size name="Size"> <int name="Field" type="uint16" displayName="Size" /> </size> <id name="Id" field="MsgId" /> <value name="Version" interfaceFieldName="Version" field="Version" pseudo="true" /> <payload name="Data" /> </frame>
There is still <value> layer (like in previous tutorial)
which is responsible to assign proper value to the Value field, but it is marked as
pseudo (pseudo="true"). The pseudo value layer does not really serialize/deserialize
any field, it just pretends to. Such layer contains inner field member in its private data,
which can be accessed via API and assigned proper value. During the read() operation
such pseudo layer pretends to read the value and just takes the data from its inner field as
if read.
Note, that pseudo layer can be put anywhere in the frame, but it is recommended (for
performance reasons) to put it right before the <payload> to make sure that
the message object is properly created.
Let's take a look how the Connect message is handled by the ServerSession:
void ServerSession::handle(ConnectMsg& msg) { std::cout << "Received message \"" << msg.doName() << "\" with ID=" << (unsigned)msg.getId() << " and version=" << (unsigned)msg.version() << std::endl; // Assign version for all future messages. m_frame.layer_version().pseudoField().value() = msg.field_version().value(); }
REMINDER: The frame definition uses
COMMS_FRAME_LAYERS_NAMES() macro which assigns names to the used framing layers.
The macro also generates layer_*() member functions for all the provided names to
access relevant layer. As the result m_frame.layer_version() provides an access to the
Version layer.
The pseudo <value> layer is defined with usage of
comms::option::def::PseudoValue option.
using Version = comms::frame::TransportValueLayer< tutorial20::field::Version<TOpt, comms::option::def::EmptySerialization>, 0U, Data, comms::option::def::PseudoValue >;
When the comms::option::def::PseudoValue option is passed to the
comms::frame::TransportValueLayer
it creates pseudoField() member functions to access the field stored in the private data members.
The code above (m_frame.layer_version().pseudoField()) accesses it and assigns the reported
version.
As the result, for all the messages that follow, the Version member field
(in the <interface>) will be assigned the right value before
the read() operation is forwarded to the <payload> layer, which in turn invokes read() operation of
the message object.
Note that in this tutorial the client is also compiled to have the default version 5 (the default state of all the
fields in all the messages is valid for version 5). In order to emulate version 4 client the
field of the pseudo Version <value> layer is modified when Connect message is sent:
void ClientSession::sendConnect() { static const unsigned UsedVersion = 4U; ConnectMsg msg; assert(msg.version() == 5U); // Schema version by default comms::cast_assign(msg.field_version().value()) = UsedVersion; m_frame.layer_version().pseudoField().value() = msg.field_version().value(); sendMessage(msg); doNextStage(); }
Also when every message is sent out its version info is updated and message contents are refreshed to make sure that the message has the right consistent state:
void ClientSession::sendMessage(Message& msg) { // Update the version and refresh msg.version() = m_frame.layer_version().pseudoField().value(); msg.refresh(); ... }
Note that the refresh functionality is polymorphic. It is available thanks to the passing the comms::option::app::RefreshInterface option to the message interface definition:
using Message = tutorial20::Interface< ..., comms::option::app::RefreshInterface // Polymorphic refresh >;
It is important to understand that manual update of the version information and forceful refresh()
is done in this example to emulate client of older version. It is NOT required in normal
operation.
Summary
- When the version is reported in one (usually the first) of the messages, the definition is very similar to the one that reports the version in the frame of every message (described in the previous tutorial).
- The main difference is that
<value>layer needs to be set as pseudo (pseudo="true"). - When handling the version reporting message, the relevant pseudo
<value>layer object in the frame needs to be accessed and its pseudo field needs to be updated to the right version. It will result in all subsequent incoming messages to be read as if having the reported version.
Read Previous Tutorial <-----------------------> Read Next Tutorial