/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include namespace ableton { namespace discovery { struct PayloadEntryHeader { using Key = std::uint32_t; using Size = std::uint32_t; Key key; Size size; friend Size sizeInByteStream(const PayloadEntryHeader& header) { return sizeInByteStream(header.key) + sizeInByteStream(header.size); } template friend It toNetworkByteStream(const PayloadEntryHeader& header, It out) { return toNetworkByteStream( header.size, toNetworkByteStream(header.key, std::move(out))); } template static std::pair fromNetworkByteStream(It begin, const It end) { using namespace std; Key key; Size size; tie(key, begin) = Deserialize::fromNetworkByteStream(begin, end); tie(size, begin) = Deserialize::fromNetworkByteStream(begin, end); return make_pair( PayloadEntryHeader{std::move(key), std::move(size)}, std::move(begin)); } }; template struct PayloadEntry { PayloadEntry(EntryType entryVal) : value(std::move(entryVal)) { header = {EntryType::key, sizeInByteStream(value)}; } PayloadEntryHeader header; EntryType value; friend std::uint32_t sizeInByteStream(const PayloadEntry& entry) { return sizeInByteStream(entry.header) + sizeInByteStream(entry.value); } template friend It toNetworkByteStream(const PayloadEntry& entry, It out) { // Don't serialize Entry if its value is of size zero if (sizeInByteStream(entry.value) == 0) { return out; } return toNetworkByteStream( entry.value, toNetworkByteStream(entry.header, std::move(out))); } }; namespace detail { template using HandlerMap = std::unordered_map>; // Given an index of handlers and a byte range, parse the bytes as a // sequence of payload entries and invoke the appropriate handler for // each entry type. Entries that are encountered that do not have a // corresponding handler in the map are ignored. Throws // std::runtime_error if parsing fails for any entry. Note that if an // exception is thrown, some of the handlers may have already been called. template void parseByteStream(HandlerMap& map, It bsBegin, const It bsEnd) { using namespace std; while (bsBegin < bsEnd) { // Try to parse an entry header at this location in the byte stream PayloadEntryHeader header; It valueBegin; tie(header, valueBegin) = Deserialize::fromNetworkByteStream(bsBegin, bsEnd); // Ensure that the reported size of the entry does not exceed the // length of the byte stream It valueEnd = valueBegin + header.size; if (bsEnd < valueEnd) { throw range_error("Payload with incorrect size."); } // The next entry will start at the end of this one bsBegin = valueEnd; // Use the appropriate handler for this entry, if available auto handlerIt = map.find(header.key); if (handlerIt != end(map)) { handlerIt->second(std::move(valueBegin), std::move(valueEnd)); } } } } // namespace detail // Payload encoding template struct Payload; template struct Payload { Payload(First first, Rest rest) : mFirst(std::move(first)) , mRest(std::move(rest)) { } Payload(PayloadEntry first, Rest rest) : mFirst(std::move(first)) , mRest(std::move(rest)) { } template using PayloadSum = Payload>; // Concatenate payloads together into a single payload template friend PayloadSum operator+( Payload lhs, Payload rhs) { return {std::move(lhs.mFirst), std::move(lhs.mRest) + std::move(rhs)}; } friend std::size_t sizeInByteStream(const Payload& payload) { return sizeInByteStream(payload.mFirst) + sizeInByteStream(payload.mRest); } template friend It toNetworkByteStream(const Payload& payload, It streamIt) { return toNetworkByteStream( payload.mRest, toNetworkByteStream(payload.mFirst, std::move(streamIt))); } PayloadEntry mFirst; Rest mRest; }; template <> struct Payload<> { template using PayloadSum = Payload; template friend PayloadSum operator+(Payload, Payload rhs) { return rhs; } friend std::size_t sizeInByteStream(const Payload&) { return 0; } template friend It toNetworkByteStream(const Payload&, It streamIt) { return streamIt; } }; template struct PayloadBuilder; // Payload factory function template auto makePayload(Entries... entries) -> decltype(PayloadBuilder{}(std::move(entries)...)) { return PayloadBuilder{}(std::move(entries)...); } template struct PayloadBuilder { auto operator()(First first, Rest... rest) -> Payload { return {std::move(first), makePayload(std::move(rest)...)}; } }; template <> struct PayloadBuilder<> { Payload<> operator()() { return {}; } }; // Parse payloads to values template struct ParsePayload; template struct ParsePayload { template static void parse(It begin, It end, Handlers... handlers) { detail::HandlerMap map; collectHandlers(map, std::move(handlers)...); detail::parseByteStream(map, std::move(begin), std::move(end)); } template static void collectHandlers( detail::HandlerMap& map, FirstHandler handler, RestHandlers... rest) { using namespace std; map[First::key] = [handler](const It begin, const It end) { const auto res = First::fromNetworkByteStream(begin, end); if (res.second != end) { std::ostringstream stringStream; stringStream << "Parsing payload entry " << First::key << " did not consume the expected number of bytes. " << " Expected: " << distance(begin, end) << ", Actual: " << distance(begin, res.second); throw range_error(stringStream.str()); } handler(res.first); }; ParsePayload::collectHandlers(map, std::move(rest)...); } }; template <> struct ParsePayload<> { template static void collectHandlers(detail::HandlerMap&) { } }; template void parsePayload(It begin, It end, Handlers... handlers) { using namespace std; ParsePayload::parse( std::move(begin), std::move(end), std::move(handlers)...); } } // namespace discovery } // namespace ableton