/* 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