musique/lib/link/include/ableton/discovery/Payload.hpp

297 lines
7.9 KiB
C++
Raw Normal View History

2023-01-06 16:53:58 +01:00
/* 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