// Copyright 2019-2020 CERN and copyright holders of ALICE O2. // See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. // All rights not expressly granted are reserved. // // This software is distributed under the terms of the GNU General Public // License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. #ifndef ALGORITHM_PARSER_H #define ALGORITHM_PARSER_H /// @file Parser.h /// @author Matthias Richter /// @since 2017-09-20 /// @brief Utilities for parsing of data sequences #include #include namespace o2 { namespace algorithm { /// helper function returning size of type with a specialization for /// void returning 0 template struct typesize { static const size_t size = sizeof(T); }; // specialization for void template <> struct typesize { static const size_t size = 0; }; /** * @class ForwardParser * Parser for a sequence of frames with header, trailer and variable payload. * The size is expected to be part of the header. * * Trailer type can be void, which is also the default template parameter. That * allows to define a frame consisting of only header and data. * * Usage: *
 *   using SomeParser = ForwardParser;
 *   SomeParser parser;
 *   std::vector frames;
 *   parser.parse(ptr, size,
 *                [] (const typename SomeParser::HeaderType& h) {
 *                  // check the header
 *                  return true;
 *                },
 *                [] (const typename SomeParser::TrailerType& t) {
 *                  // check the trailer
 *                  return true;
 *                },
 *                [] (const typename SomeParser::HeaderType& h) {
 *                  // get the size of the frame including payload
 *                  // and header and trailer size, e.g. payload size
 *                  // from a header member
 *                  return h.payloadSize + SomeParser::totalOffset;
 *                },
 *                [&frames] (typename SomeParser::FrameInfo& info) {
 *                  frames.emplace_back(info);
 *                  return true;
 *                }
 *                )
 *
 *   // a reduced version without trailer check callback
 *   using SomeParser = ForwardParser;
 *   SomeParser parser;
 *   std::vector frames;
 *   parser.parse(ptr, size,
 *                [] (const typename SomeParser::HeaderType& h) {
 *                  // check the header
 *                  return true;
 *                },
 *                [] (const typename SomeParser::HeaderType& h) {
 *                  // get the size of the frame including payload
 *                  // and header and trailer size, e.g. payload size
 *                  // from a header member
 *                  return h.payloadSize + SomeParser::totalOffset;
 *                },
 *                [&frames] (typename SomeParser::FrameInfo& info) {
 *                  frames.emplace_back(info);
 *                  return true;
 *                }
 *                )
 * 
*/ template class ForwardParser { public: using HeaderType = HeaderT; using TrailerType = TrailerT; using PayloadType = unsigned char; /// @struct FrameInfo /// a compound of header, data, and trailer struct FrameInfo { using PtrT = const PayloadType*; const HeaderType* header = nullptr; const TrailerType* trailer = nullptr; PtrT payload = nullptr; size_t length = 0; }; /// the length offset due to header static const size_t headOffset = typesize::size; /// the length offset due to trailer static const size_t tailOffset = typesize::size; /// total length offset due to header and trailer static const size_t totalOffset = headOffset + tailOffset; /// alias for callback checking the header, return true if the object /// is a valid header using CheckHeaderFct = std::function; /// alias for the argument type to be used in the CheckTrailer function /// have to forward to a valid type in case of void TrailerType in order /// to allow passing by reference using CheckTrailerFctArgumentT = typename std::conditional< !std::is_void::value, TrailerType, int>::type; /// alias for callback checking the trailer, takes reference to trailer /// object if TrailerType is a valid type, no argument otherwise template using CheckTrailerFct = typename std::conditional< !std::is_void::value, std::function, std::function>::type; /// alias for callback to get the complete frame size including header, /// trailer and the data using GetFrameSizeFct = std::function; /// function callback to insert/handle one frame into, sequentially called /// for all frames if the whole block has a valid format using InsertFct = std::function; /// Parse buffer of size bufferSize, requires callbacks to check header /// trailer, the frame size, and insert callback to handle a FrameInfo /// object. template int parse(const InputType* buffer, size_t bufferSize, CheckHeaderFct checkHeader, CheckTrailerFct checkTrailer, GetFrameSizeFct getFrameSize, InsertFct insert) { static_assert(sizeof(InputType) == 1, "ForwardParser currently only supports byte type buffer"); if (buffer == nullptr || bufferSize == 0) { return 0; } size_t position = 0; std::vector frames; do { FrameInfo entry; // check the header if (sizeof(HeaderType) + position > bufferSize) { break; } entry.header = reinterpret_cast(buffer + position); if (!checkHeader(*entry.header)) { break; } // extract frame size from header, this is expected to be the // total frome size including header, payload and optional trailer auto frameSize = getFrameSize(*entry.header); if (frameSize + position > bufferSize) { break; } // payload starts right after the header entry.payload = reinterpret_cast(entry.header + 1); entry.length = frameSize - totalOffset; // optionally extract and check trailer if (tailOffset > 0) { entry.trailer = nullptr; } else { auto trailerStart = buffer + position + frameSize - tailOffset; entry.trailer = reinterpret_cast(trailerStart); if (!CheckTrailer(entry, checkTrailer)) { break; } } // store the extracted frame info and continue with remaining buffer frames.emplace_back(entry); position += frameSize; } while (position < bufferSize); if (position == bufferSize) { // frames found and format consistent, insert entries to target // Note: the complete block must be consistent for (auto entry : frames) { if (!insert(entry)) { break; } } return frames.size(); } else if (frames.size() == 0) { // no frames found at all, the buffer does not contain any return 0; } // format error detected // TODO: decide about error policy return -1; } /// Parse buffer of size bufferSize, specialization skipping the trailer /// check, e.g. when its type is void, or when the integrity of the trailer /// is not relevant. Requires callbacks to check header, frame size, and /// insert callback to handle a FrameInfo object. template typename std::enable_if::value, int>::type parse(const InputType* buffer, size_t bufferSize, CheckHeaderFct checkHeader, GetFrameSizeFct getFrameSize, InsertFct insert) { auto checkTrailer = []() { return true; }; return parse(buffer, bufferSize, checkHeader, checkTrailer, getFrameSize, insert); } private: /// internal function to check the trailer, distinguishes void and non-void /// trailer type. template typename std::enable_if::value, bool>::type CheckTrailer(const FrameInfo& entry, CheckTrailerFct& checkTrailer) const { return checkTrailer(*entry.trailer); } template typename std::enable_if::value, bool>::type CheckTrailer(const FrameInfo&, CheckTrailerFct&) const { return true; } }; /** * @class ReverseParser * Parser for a sequence of frames with header, trailer and variable payload. * The size is expected to be part of the trailer, the parsing is thus in * reverse direction. Also the insert callback is called with the entries * starting form the end of the buffer. * TODO: an easy extension can be to reverse the order of the inserts, meaning * that the entries are read from the beginning. * * Usage: *
 *   using SomeParser = ReverseParser;
 *   SomeParser parser;
 *   std::vector frames;
 *   parser.parse(ptr, size,
 *                [] (const typename SomeParser::HeaderType& h) {
 *                  // check the header
 *                  return true;
 *                },
 *                [] (const typename SomeParser::TrailerType& t) {
 *                  // check the trailer
 *                  return true;
 *                },
 *                [] (const typename SomeParser::TrailerType& t) {
 *                  // get the size of the frame including payload
 *                  // and header and trailer size, e.g. payload size
 *                  // from a trailer member
 *                  return t.payloadSize + SomeParser::totalOffset;
 *                },
 *                [&frames] (typename SomeParser::FrameInfo& info) {
 *                  frames.emplace_back(info);
 *                  return true;
 *                }
 *                )
 * 
*/ template class ReverseParser { public: using HeaderType = HeaderT; using TrailerType = TrailerT; using PayloadType = unsigned char; /// @struct FrameInfo a compound of header, data, and trailer struct FrameInfo { using PtrT = const PayloadType*; const HeaderType* header = nullptr; const TrailerType* trailer = nullptr; PtrT payload = nullptr; size_t length = 0; }; /// the length offset due to header static const size_t headOffset = typesize::size; /// the length offset due to trailer static const size_t tailOffset = typesize::size; /// total length offset due to header and trailer static const size_t totalOffset = headOffset + tailOffset; /// alias for callback checking the header, return true if the object /// is a valid header using CheckHeaderFct = std::function; /// alias for callback checking the trailer using CheckTrailerFct = std::function; /// alias for callback to get the complete frame size including header, /// trailer and the data using GetFrameSizeFct = std::function; /// function callback to insert/handle one frame into, sequentially called /// for all frames if the whole block has a valid format using InsertFct = std::function; /// Parse buffer of size bufferSize, requires callbacks to check header /// trailer, the frame size, and insert callback to handle a FrameInfo /// object. template int parse(const InputType* buffer, size_t bufferSize, CheckHeaderFct checkHeader, CheckTrailerFct checkTrailer, GetFrameSizeFct getFrameSize, InsertFct insert) { static_assert(sizeof(InputType) == 1, "ReverseParser currently only supports byte type buffer"); if (buffer == nullptr || bufferSize == 0) { return 0; } auto position = bufferSize; std::vector frames; do { FrameInfo entry; // start from end, extract and check trailer if (sizeof(TrailerType) > position) { break; } entry.trailer = reinterpret_cast(buffer + position - sizeof(TrailerType)); if (!checkTrailer(*entry.trailer)) { break; } // get the total frame size auto frameSize = getFrameSize(*entry.trailer); if (frameSize > position) { break; } // extract and check header auto headerStart = buffer + position - frameSize; entry.header = reinterpret_cast(headerStart); if (!checkHeader(*entry.header)) { break; } // payload immediately after header entry.payload = reinterpret_cast(entry.header + 1); entry.length = frameSize - sizeof(HeaderType) - sizeof(TrailerType); frames.emplace_back(entry); position -= frameSize; } while (position > 0); if (position == 0) { // frames found and format consistent, the complete block must be consistent for (auto entry : frames) { if (!insert(entry)) { break; } } return frames.size(); } else if (frames.size() == 0) { // no frames found at all, the buffer does not contain any return 0; } // format error detected // TODO: decide about error policy return -1; } }; } // namespace algorithm } // namespace o2 #endif // ALGORITHM_PARSER_H