There is a need to have a generic way to handle such cases.
Implementing Read
Let's start with implementing the read first.
namespace comms{// TField is type of the field used to read/write checksum value// TCalc is generic class that is responsible to implement checksum calculation logic// TNext is the next layer this one wrapstemplate <typenameTField,typenameTCalc,typenameTNext>classChecksumLayer{public: // Type of the field object used to read/write SYNC prefix.usingField=TField; // Take type of the ReadIterator from the next layerusingReadIterator=typename TNext::ReadIterator; // Take type of the WriteIterator from the next layerusingWriteIterator=typename TNext::WriteIterator; // Take type of the message interface from the next layerusingMessage=typename TNext::Message; // Take type of the message interface pointer from the next layerusingMsgPtr=typename TNext::MsgPtr; template <typenameTMsgPtr>ErrorStatusread(TMsgPtr& msgPtr,ReadIterator& iter, std::size_t len) { Field field;if (len <field.length()) {return ErrorStatus::NotEnoughData; }auto fromIter = iter; // The read is expected to use random-access iteratorauto es =m_next.read(iter, len -field.length());if (es != ErrorStatus::Success) {return es; }auto consumedLen =static_cast<std::size_t>(std::distance(fromIter, iter));auto remLen = len - consumedLen; es =field.read(iter, remLen);if (es != ErrorStatus::Success) {msgPtr.reset();return es; }auto checksum =TCalc()(fromIter, consumedLen);auto expectedValue =field.value();if (expectedValue != checksum) {msgPtr.reset(); // Delete allocated messagereturn ErrorStatus::ProtocolError; }return ErrorStatus::Success; } private: TNext m_next;};} // namespace comms
It is clear that TCalc class is expected to have operator() member function, which receives the iterator for reading and number of bytes in the buffer.
As an example let's implement generic total sum of bytes calculator:
Now, let's tackle the write problem. As it was mentioned earlier, there is a need to recognise the type of the iterator used for writing and behave accordingly. If the iterator is properly defined, the std::iterator_traits class will define iterator_category internal type.
For random access iterators the WriteIteratorCategoryTag class will be either std::random_access_iterator_tag or any other class that inherits from it. Using this information, we can use Tag Dispatch Idiom to choose the right writing functionality.
Please pay attention, that writeOutput() function above is unable to properly calculate the checksum of the written data. There is no way the iterator can be reversed back and used as input instead of output. As the result the function returns special error status: ErrorStatus::UpdateRequired. It is an indication that the write operation is not complete and the output should be updated using random access iterator.
Implementing Update
namespace comms{template <...>classChecksumLayer{public:template <typenameTIter>ErrorStatusupdate(TIter& iter, std::size_t len) const { Field field;auto fromIter = iter;auto es =m_next.update(iter, len -field.length());if (es != ErrorStatus::Success) {return es; }auto consumedLen =static_cast<std::size_t>(std::distance(fromIter, iter));auto remLen = len - consumedLen;field.value() =TCalc()(fromIter, consumedLen); es =field.write(iter, remLen);return es; }private: TNext m_next;};} // namespace comms
Please note, that every other layer must also implement the update() member function, which will just advance the provided iterator by the number of bytes required to write its field and invoke update() member function of the next (wrapped) layer.
And so on for the rest of the layers. Also note, that the code above will work, only when the field has the same serialisation length for any value. If this is not the case (Base-128 encoding is used), the previously written value needs to be read, instead of just advancing the iterator, to make sure the iterator is advanced right amount of bytes:
template <typenameTIter>ErrorStatusupdate(TIter& iter, std::size_t len) const{ TField field;auto es =field.read(iter);if (es != ErrorStatus::Success) {return es; }returnm_next.update(iter, len -field.length());}
The variable serialisation length encoding will be forced using some kind of special option. It can be identified at compile time and Tag Dispatch Idiom can be used to select appropriate update functionality.
The caller, that requests protocol stack to serialise a message, must check the error status value returned by the write() operation. If it is ErrorStatus::UpdateRequired, the caller must create random-access iterator to the already written buffer and invoke update() function with it, to make sure the written information is correct.
usingProtocolStack= ...;ProtocolStack protStack;ErrorStatussendMessage(constMessage& msg){ ProtocolStack::WriteIterator writeIter = ...;auto es =protStack.write(msg, writeIter, bufLen);if (es == ErrorStatus::UpdateRequired) {auto updateIter = ...; // Random access iterator to written data es =protStack.update(updateIter, ...); }return es;}