Tutorial 18
How to access transport framing fields.
In all the tutorials so far the transport framing fields have been silently stripped
away by the frame management and only the message object dispatched for handling.
There are ways to re-assign values from the frame using <value> layer in
conjunction with custom <interface>. However, there may be cases when it's not
enough and there is a need to actually know the values of the other fields
(from <id>, <size> and other layers).
This tutorial demonstrates a way of caching and accessing the fields of the framing layers.
The ServerSession demonstrates retrieval of only message numeric ID and the relative index of the message (there are different forms of the same message which are implemented as separate classes).
std::size_t ServerSession::processInputImpl(const std::uint8_t* buf, std::size_t bufLen) { ... std::size_t consumed = 0U; while (consumed < bufLen) { ... tutorial18::MsgId msgId = tutorial18::MsgId_ValuesLimit; std::size_t msgIdx = std::numeric_limits<std::size_t>::max(); auto es = m_frame.read( msg, iter, remLen, comms::frame::msgId(msgId), comms::frame::msgIndex(msgIdx)); ... if (es == comms::ErrorStatus::Success) { assert(msg); // Message object must be allocated std::cout << "Detected message: ID=" << (unsigned)msgId << "; idx=" << msgIdx << std::endl; // Dispatch using id + index to map to real type comms::dispatchMsg<ServerInputMessages>(msgId, msgIdx, *msg, *this); } ... } return consumed; }
The read()
member function of every protocol layer receives a variadic extraValues last parameter, which can be
used to add several output parameters to the function. In this example the
comms::frame::msgId() is used
to add msgId local variable as an output parameter for the message numeric ID. Also the
comms::frame::msgIndex()
is used to add msgIdx local variable as an output parameter for the index of the message (offset in
the tuple of the input messages starting from the first message sharing the same ID).
Later down the code both msgId and msgIdx values are used to dispatch message object into the
appropriate handling function using
comms::dispatchMsg function.
The ClientSession on the other hand demonstrates caching of all the framing fields.
std::size_t ClientSession::processInputImpl(const std::uint8_t* buf, std::size_t bufLen) { ... std::size_t consumed = 0U; while (consumed < bufLen) { ... Frame::MsgPtr msg; Frame::AllFields transportFields; auto es = m_frame.readFieldsCached( transportFields, msg, iter, remLen); ... if (es == comms::ErrorStatus::Success) { assert(msg); // Message object must be allocated std::cout << "Message transport fields:\n"; printIntFieldHex(m_frame.layer_sync().accessCachedField(transportFields)); printIntFieldHex(m_frame.layer_size().accessCachedField(transportFields)); printEnumField(m_frame.layer_id().accessCachedField(transportFields)); printDataFieldNoName(m_frame.layer_data().accessCachedField(transportFields)); printIntFieldHex(m_frame.layer_checksum().accessCachedField(transportFields)); std::cout << std::endl; msg->dispatch(*this); } ... } return consumed; }
Every layer defines
AllFields inner type which is std::tuple of all the fields starting from the layer
being defined and down to the bottom (<payload>) one. As the result the AllFields type of the outermost layer
(which is also used as a full frame) is a tuple of all the framing fields.
Also every layer defines readFieldsCached()
member function, which is similar to read() used so far, but receives the tuple of all the fields it needs to update as
its first parameter. After the read operation is complete the provided transportFields tuple will have updated values which
can be accessed later.
Please pay attention that the frame definition uses COMMS_FRAME_LAYERS_NAMES() macro, which defines names of the
used layers and allows convenient access to them using layer_*() member function.
template < typename TMessage, typename TAllMessages = tutorial18::input::AllMessages<TMessage>, typename TOpt = tutorial18::options::DefaultOptions > class Frame : public FrameLayers<TOpt>::template Stack<TMessage, TAllMessages> { using Base = typename FrameLayers<TOpt>::template Stack<TMessage, TAllMessages>; public: ... COMMS_FRAME_LAYERS_NAMES( data, id, size, checksum, sync ); };
Once the layer object is accesses its accessCachedField() member function can be used to access appropriate field in the cached fields tuple.
Note that the inner field of the <payload> layer is
comms::field::ArrayList of
raw std::uint8_t bytes (equivalent to being <data>). The options passed to
comms::frame::MsgDataLayer
are passed to the field definition. If no special options are passed then the whole payload will be
copied to the cached field. It is highly recommended to pass
comms::option::app::OridDataView to it to
avoid unnecessary copy of the data. In this particular tutorial the ClientSession
defines its options to be:
using ClientProtocolOptions =
tutorial18::options::DataViewDefaultOptionsT<
tutorial18::options::ClientDefaultOptions
>;The used tutorial::options::DataViewDefaultOptions ensures that appropriate option is passed the payload definition layer:
template <typename TBase = tutorial18::options::DefaultOptions> struct DataViewDefaultOptionsT : public TBase { struct frame : public TBase::frame { struct FrameLayers : public TBase::frame::FrameLayers { using Data = std::tuple< comms::option::app::OrigDataView, typename TBase::frame::FrameLayers::Data >; }; // struct FrameLayers }; // struct frame };
Summary
- The COMMS Library provides multiple ways to access some or all of the framing fields.
- The
read()member function can receive and update selected output parameters via comms::frame::msgId(), comms::frame::msgIndex(), etc... - In order to get all the frame fields the readFieldsCached() member function needs to be used.
- The frame definition allows access of various inner layers using
layer_*()member function. - Every layer provides accessCachedField() member function to access appropriate field from the cached tuple.
Read Previous Tutorial <-----------------------> Read Next Tutorial