/* 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 <http://www.gnu.org/licenses/>. * * If you would like to incorporate Link into a proprietary software application, * please contact <link-devs@ableton.com>. */ #pragma once #include <ableton/discovery/NetworkByteStreamSerializable.hpp> #include <functional> #include <sstream> #include <unordered_map> 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 <typename It> friend It toNetworkByteStream(const PayloadEntryHeader& header, It out) { return toNetworkByteStream( header.size, toNetworkByteStream(header.key, std::move(out))); } template <typename It> static std::pair<PayloadEntryHeader, It> fromNetworkByteStream(It begin, const It end) { using namespace std; Key key; Size size; tie(key, begin) = Deserialize<Key>::fromNetworkByteStream(begin, end); tie(size, begin) = Deserialize<Size>::fromNetworkByteStream(begin, end); return make_pair( PayloadEntryHeader{std::move(key), std::move(size)}, std::move(begin)); } }; template <typename EntryType> 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 <typename It> friend It toNetworkByteStream(const PayloadEntry& entry, It out) { return toNetworkByteStream( entry.value, toNetworkByteStream(entry.header, std::move(out))); } }; namespace detail { template <typename It> using HandlerMap = std::unordered_map<typename PayloadEntryHeader::Key, std::function<void(It, It)>>; // 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 <typename It> void parseByteStream(HandlerMap<It>& 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<PayloadEntryHeader>::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 <typename... Entries> struct Payload; template <typename First, typename Rest> struct Payload<First, Rest> { Payload(First first, Rest rest) : mFirst(std::move(first)) , mRest(std::move(rest)) { } Payload(PayloadEntry<First> first, Rest rest) : mFirst(std::move(first)) , mRest(std::move(rest)) { } template <typename RhsFirst, typename RhsRest> using PayloadSum = Payload<First, typename Rest::template PayloadSum<RhsFirst, RhsRest>>; // Concatenate payloads together into a single payload template <typename RhsFirst, typename RhsRest> friend PayloadSum<RhsFirst, RhsRest> operator+( Payload lhs, Payload<RhsFirst, RhsRest> 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 <typename It> friend It toNetworkByteStream(const Payload& payload, It streamIt) { return toNetworkByteStream( payload.mRest, toNetworkByteStream(payload.mFirst, std::move(streamIt))); } PayloadEntry<First> mFirst; Rest mRest; }; template <> struct Payload<> { template <typename RhsFirst, typename RhsRest> using PayloadSum = Payload<RhsFirst, RhsRest>; template <typename RhsFirst, typename RhsRest> friend PayloadSum<RhsFirst, RhsRest> operator+(Payload, Payload<RhsFirst, RhsRest> rhs) { return rhs; } friend std::size_t sizeInByteStream(const Payload&) { return 0; } template <typename It> friend It toNetworkByteStream(const Payload&, It streamIt) { return streamIt; } }; template <typename... Entries> struct PayloadBuilder; // Payload factory function template <typename... Entries> auto makePayload(Entries... entries) -> decltype(PayloadBuilder<Entries...>{}(std::move(entries)...)) { return PayloadBuilder<Entries...>{}(std::move(entries)...); } template <typename First, typename... Rest> struct PayloadBuilder<First, Rest...> { auto operator()(First first, Rest... rest) -> Payload<First, decltype(makePayload(std::move(rest)...))> { return {std::move(first), makePayload(std::move(rest)...)}; } }; template <> struct PayloadBuilder<> { Payload<> operator()() { return {}; } }; // Parse payloads to values template <typename... Entries> struct ParsePayload; template <typename First, typename... Rest> struct ParsePayload<First, Rest...> { template <typename It, typename... Handlers> static void parse(It begin, It end, Handlers... handlers) { detail::HandlerMap<It> map; collectHandlers(map, std::move(handlers)...); detail::parseByteStream(map, std::move(begin), std::move(end)); } template <typename It, typename FirstHandler, typename... RestHandlers> static void collectHandlers( detail::HandlerMap<It>& 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<Rest...>::collectHandlers(map, std::move(rest)...); } }; template <> struct ParsePayload<> { template <typename It> static void collectHandlers(detail::HandlerMap<It>&) { } }; template <typename... Entries, typename It, typename... Handlers> void parsePayload(It begin, It end, Handlers... handlers) { using namespace std; ParsePayload<Entries...>::parse( std::move(begin), std::move(end), std::move(handlers)...); } } // namespace discovery } // namespace ableton