586 lines
16 KiB
C++
586 lines
16 KiB
C++
#pragma once
|
|
|
|
#include "buffer.h"
|
|
#include "message.h"
|
|
#include "stream_endian.h"
|
|
|
|
namespace saw {
|
|
/// @todo replace types with these
|
|
/*
|
|
* I'm not really sure if anyone will use a union which is
|
|
* bigger than uint32_t max. At least I hope noone would do this
|
|
*/
|
|
using msg_union_id_t = uint32_t;
|
|
using msg_array_length_t = uint64_t;
|
|
using msg_packet_length_t = uint64_t;
|
|
|
|
class ProtoKelCodec {
|
|
private:
|
|
struct ReadContext {
|
|
Buffer &buffer;
|
|
size_t offset = 0;
|
|
};
|
|
|
|
template <typename T> friend struct ProtoKelEncodeImpl;
|
|
|
|
template <typename T> friend struct ProtoKelDecodeImpl;
|
|
|
|
public:
|
|
struct Limits {
|
|
msg_packet_length_t packet_size;
|
|
|
|
Limits() : packet_size{4096} {}
|
|
Limits(msg_packet_length_t ps) : packet_size{ps} {}
|
|
};
|
|
|
|
struct Version {
|
|
size_t major;
|
|
size_t minor;
|
|
size_t security;
|
|
};
|
|
|
|
const Version version() const { return Version{0, 0, 0}; }
|
|
|
|
template <class Schema, class Container = MessageContainer<Schema>>
|
|
Error encode(typename Message<Schema, Container>::Reader reader,
|
|
Buffer &buffer);
|
|
|
|
template <class Schema, class Container = MessageContainer<Schema>>
|
|
Error decode(typename Message<Schema, Container>::Builder builder,
|
|
Buffer &buffer, const Limits &limits = Limits{});
|
|
};
|
|
|
|
template <class T> struct ProtoKelEncodeImpl;
|
|
|
|
template <class T, size_t N, class Container>
|
|
struct ProtoKelEncodeImpl<Message<schema::Primitive<T, N>, Container>> {
|
|
static Error
|
|
encode(typename Message<schema::Primitive<T, N>, Container>::Reader data,
|
|
Buffer &buffer) {
|
|
Error error = StreamValue<typename PrimitiveTypeHelper<
|
|
schema::Primitive<T, N>>::Type>::encode(data.get(), buffer);
|
|
return error;
|
|
}
|
|
|
|
static size_t
|
|
size(typename Message<schema::Primitive<T, N>, Container>::Reader) {
|
|
return StreamValue<typename PrimitiveTypeHelper<
|
|
schema::Primitive<T, N>>::Type>::size();
|
|
}
|
|
};
|
|
|
|
template <class Container>
|
|
struct ProtoKelEncodeImpl<Message<schema::String, Container>> {
|
|
static Error
|
|
encode(typename Message<schema::String, Container>::Reader data,
|
|
Buffer &buffer) {
|
|
std::string_view view = data.get();
|
|
size_t size = view.size();
|
|
|
|
Error error = buffer.writeRequireLength(sizeof(size) + size);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
|
|
error = StreamValue<size_t>::encode(size, buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
for (size_t i = 0; i < view.size(); ++i) {
|
|
buffer.write(i) = view[i];
|
|
}
|
|
buffer.writeAdvance(view.size());
|
|
return noError();
|
|
}
|
|
|
|
static size_t
|
|
size(typename Message<schema::String, Container>::Reader reader) {
|
|
return sizeof(size_t) + reader.get().size();
|
|
}
|
|
};
|
|
|
|
template <class... T, class Container>
|
|
struct ProtoKelEncodeImpl<Message<schema::Tuple<T...>, Container>> {
|
|
template <size_t i = 0>
|
|
static typename std::enable_if<i == sizeof...(T), Error>::type
|
|
encodeMembers(typename Message<schema::Tuple<T...>, Container>::Reader,
|
|
Buffer &) {
|
|
return noError();
|
|
}
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if<(i < sizeof...(T)), Error>::type
|
|
encodeMembers(typename Message<schema::Tuple<T...>, Container>::Reader data,
|
|
Buffer &buffer) {
|
|
Error error =
|
|
ProtoKelEncodeImpl<typename Container::template ElementType<i>>::
|
|
encode(data.template get<i>(), buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
|
|
return encodeMembers<i + 1>(data, buffer);
|
|
}
|
|
|
|
static Error
|
|
encode(typename Message<schema::Tuple<T...>, Container>::Reader data,
|
|
Buffer &buffer) {
|
|
return encodeMembers<0>(data, buffer);
|
|
}
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if<i == sizeof...(T), size_t>::type
|
|
sizeMembers(typename Message<schema::Tuple<T...>, Container>::Reader) {
|
|
return 0;
|
|
}
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if <
|
|
i<sizeof...(T), size_t>::type sizeMembers(
|
|
typename Message<schema::Tuple<T...>, Container>::Reader reader) {
|
|
return ProtoKelEncodeImpl<typename Container::template ElementType<i>>::
|
|
size(reader.template get<i>()) +
|
|
sizeMembers<i + 1>(reader);
|
|
}
|
|
|
|
static size_t
|
|
size(typename Message<schema::Tuple<T...>, Container>::Reader reader) {
|
|
return sizeMembers<0>(reader);
|
|
}
|
|
};
|
|
|
|
template <typename... V, StringLiteral... K, class Container>
|
|
struct ProtoKelEncodeImpl<
|
|
Message<schema::Struct<schema::NamedMember<V, K>...>, Container>> {
|
|
template <size_t i = 0>
|
|
static typename std::enable_if<i == sizeof...(V), Error>::type
|
|
encodeMembers(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
|
|
Container>::Reader,
|
|
Buffer &) {
|
|
return noError();
|
|
}
|
|
template <size_t i = 0>
|
|
static typename std::enable_if <
|
|
i<sizeof...(V), Error>::type encodeMembers(
|
|
typename Message<schema::Struct<schema::NamedMember<V, K>...>,
|
|
Container>::Reader data,
|
|
Buffer &buffer) {
|
|
|
|
Error error =
|
|
ProtoKelEncodeImpl<typename Container::template ElementType<i>>::
|
|
encode(data.template get<i>(), buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
return encodeMembers<i + 1>(data, buffer);
|
|
}
|
|
|
|
static Error
|
|
encode(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
|
|
Container>::Reader data,
|
|
Buffer &buffer) {
|
|
return encodeMembers<0>(data, buffer);
|
|
}
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if<i == sizeof...(V), size_t>::type
|
|
sizeMembers(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
|
|
Container>::Reader) {
|
|
return 0;
|
|
}
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if <
|
|
i<sizeof...(V), size_t>::type sizeMembers(
|
|
typename Message<schema::Struct<schema::NamedMember<V, K>...>,
|
|
Container>::Reader reader) {
|
|
return ProtoKelEncodeImpl<typename Container::template ElementType<i>>::
|
|
size(reader.template get<i>()) +
|
|
sizeMembers<i + 1>(reader);
|
|
}
|
|
|
|
static size_t
|
|
size(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
|
|
Container>::Reader reader) {
|
|
return sizeMembers<0>(reader);
|
|
}
|
|
};
|
|
|
|
template <typename... V, StringLiteral... K, class Container>
|
|
struct ProtoKelEncodeImpl<
|
|
Message<schema::Union<schema::NamedMember<V, K>...>, Container>> {
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if<i == sizeof...(V), Error>::type
|
|
encodeMembers(typename Message<schema::Union<schema::NamedMember<V, K>...>,
|
|
Container>::Reader,
|
|
Buffer &) {
|
|
return noError();
|
|
}
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if <
|
|
i<sizeof...(V), Error>::type encodeMembers(
|
|
typename Message<schema::Union<schema::NamedMember<V, K>...>,
|
|
Container>::Reader reader,
|
|
Buffer &buffer) {
|
|
if (reader.index() == i) {
|
|
Error error = StreamValue<msg_union_id_t>::encode(i, buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
return ProtoKelEncodeImpl<typename Container::template ElementType<
|
|
i>>::encode(reader.template get<i>(), buffer);
|
|
}
|
|
return encodeMembers<i + 1>(reader, buffer);
|
|
}
|
|
|
|
static Error
|
|
encode(typename Message<schema::Union<schema::NamedMember<V, K>...>,
|
|
Container>::Reader reader,
|
|
Buffer &buffer) {
|
|
return encodeMembers<0>(reader, buffer);
|
|
}
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if<i == sizeof...(V), size_t>::type
|
|
sizeMembers(typename Message<schema::Union<schema::NamedMember<V, K>...>,
|
|
Container>::Reader) {
|
|
return 0;
|
|
}
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if <
|
|
i<sizeof...(V), size_t>::type sizeMembers(
|
|
typename Message<schema::Union<schema::NamedMember<V, K>...>,
|
|
Container>::Reader reader) {
|
|
if (reader.index() == i) {
|
|
return ProtoKelEncodeImpl<typename Container::template ElementType<
|
|
i>>::size(reader.template get<i>());
|
|
}
|
|
return sizeMembers<i + 1>(reader);
|
|
}
|
|
|
|
/*
|
|
* Size of union id + member size
|
|
*/
|
|
static size_t
|
|
size(typename Message<schema::Union<schema::NamedMember<V, K>...>,
|
|
Container>::Reader reader) {
|
|
return sizeof(msg_union_id_t) + sizeMembers<0>(reader);
|
|
}
|
|
};
|
|
|
|
template <class T, class Container>
|
|
struct ProtoKelEncodeImpl<Message<schema::Array<T>, Container>> {
|
|
static Error
|
|
encode(typename Message<schema::Array<T>, Container>::Reader data,
|
|
Buffer &buffer) {
|
|
msg_array_length_t array_length = data.size();
|
|
{
|
|
Error error =
|
|
StreamValue<msg_array_length_t>::encode(array_length, buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < array_length; ++i) {
|
|
Error error =
|
|
ProtoKelEncodeImpl<typename Container::ElementType>::encode(
|
|
data.get(i), buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
}
|
|
return noError();
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static size_t
|
|
size(typename Message<schema::Array<T>, Container>::Reader data) {
|
|
size_t members = sizeof(msg_array_length_t);
|
|
for (size_t i = 0; i < data.size(); ++i) {
|
|
members +=
|
|
ProtoKelEncodeImpl<typename Container::ElementType>::size(
|
|
data.get(i));
|
|
}
|
|
|
|
return members;
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Decode Implementations
|
|
*/
|
|
template <typename T> struct ProtoKelDecodeImpl;
|
|
|
|
template <class T, size_t N, class Container>
|
|
struct ProtoKelDecodeImpl<Message<schema::Primitive<T, N>, Container>> {
|
|
static Error
|
|
decode(typename Message<schema::Primitive<T, N>, Container>::Builder data,
|
|
Buffer &buffer) {
|
|
typename PrimitiveTypeHelper<schema::Primitive<T, N>>::Type val = 0;
|
|
Error error = StreamValue<typename PrimitiveTypeHelper<
|
|
schema::Primitive<T, N>>::Type>::decode(val, buffer);
|
|
data.set(val);
|
|
return error;
|
|
}
|
|
};
|
|
|
|
template <class Container>
|
|
struct ProtoKelDecodeImpl<Message<schema::String, Container>> {
|
|
static Error
|
|
decode(typename Message<schema::String, Container>::Builder data,
|
|
Buffer &buffer) {
|
|
size_t size = 0;
|
|
if (sizeof(size) > buffer.readCompositeLength()) {
|
|
return recoverableError("Buffer too small");
|
|
}
|
|
|
|
Error error = StreamValue<size_t>::decode(size, buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
|
|
if (size > buffer.readCompositeLength()) {
|
|
return recoverableError("Buffer too small");
|
|
}
|
|
|
|
std::string value;
|
|
value.resize(size);
|
|
|
|
if (size > buffer.readCompositeLength()) {
|
|
return recoverableError("Buffer too small");
|
|
}
|
|
for (size_t i = 0; i < value.size(); ++i) {
|
|
value[i] = buffer.read(i);
|
|
}
|
|
buffer.readAdvance(value.size());
|
|
data.set(std::move(value));
|
|
return noError();
|
|
}
|
|
};
|
|
|
|
template <class... T, class Container>
|
|
struct ProtoKelDecodeImpl<Message<schema::Tuple<T...>, Container>> {
|
|
template <size_t i = 0>
|
|
static typename std::enable_if<i == sizeof...(T), Error>::type
|
|
decodeMembers(typename Message<schema::Tuple<T...>, Container>::Builder,
|
|
Buffer &) {
|
|
return noError();
|
|
}
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if <
|
|
i<sizeof...(T), Error>::type decodeMembers(
|
|
typename Message<schema::Tuple<T...>, Container>::Builder builder,
|
|
Buffer &buffer) {
|
|
|
|
Error error =
|
|
ProtoKelDecodeImpl<typename Container::template ElementType<i>>::
|
|
decode(builder.template init<i>(), buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
return decodeMembers<i + 1>(builder, buffer);
|
|
}
|
|
|
|
static Error
|
|
decode(typename Message<schema::Tuple<T...>, Container>::Builder builder,
|
|
Buffer &buffer) {
|
|
return decodeMembers<0>(builder, buffer);
|
|
}
|
|
};
|
|
|
|
template <class... V, StringLiteral... K, class Container>
|
|
struct ProtoKelDecodeImpl<
|
|
Message<schema::Struct<schema::NamedMember<V, K>...>, Container>> {
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if<i == sizeof...(V), Error>::type
|
|
decodeMembers(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
|
|
Container>::Builder,
|
|
Buffer &) {
|
|
return noError();
|
|
}
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if <
|
|
i<sizeof...(V), Error>::type decodeMembers(
|
|
typename Message<schema::Struct<schema::NamedMember<V, K>...>,
|
|
Container>::Builder builder,
|
|
Buffer &buffer) {
|
|
|
|
Error error =
|
|
ProtoKelDecodeImpl<typename Container::template ElementType<i>>::
|
|
decode(builder.template init<i>(), buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
return decodeMembers<i + 1>(builder, buffer);
|
|
}
|
|
|
|
static Error
|
|
decode(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
|
|
Container>::Builder builder,
|
|
Buffer &buffer) {
|
|
return decodeMembers<0>(builder, buffer);
|
|
}
|
|
};
|
|
|
|
template <class... V, StringLiteral... K, class Container>
|
|
struct ProtoKelDecodeImpl<
|
|
Message<schema::Union<schema::NamedMember<V, K>...>, Container>> {
|
|
template <size_t i = 0>
|
|
static typename std::enable_if<i == sizeof...(V), Error>::type
|
|
decodeMembers(typename Message<schema::Union<schema::NamedMember<V, K>...>,
|
|
Container>::Builder,
|
|
Buffer &, msg_union_id_t) {
|
|
return noError();
|
|
}
|
|
|
|
template <size_t i = 0>
|
|
static typename std::enable_if <
|
|
i<sizeof...(V), Error>::type decodeMembers(
|
|
typename Message<schema::Union<schema::NamedMember<V, K>...>,
|
|
Container>::Builder builder,
|
|
Buffer &buffer, msg_union_id_t id) {
|
|
|
|
if (id == i) {
|
|
Error error =
|
|
ProtoKelDecodeImpl<typename Container::template ElementType<
|
|
i>>::decode(builder.template init<i>(), buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
}
|
|
return decodeMembers<i + 1>(builder, buffer, id);
|
|
}
|
|
|
|
static Error
|
|
decode(typename Message<schema::Union<schema::NamedMember<V, K>...>,
|
|
Container>::Builder builder,
|
|
Buffer &buffer) {
|
|
msg_union_id_t id = 0;
|
|
Error error = StreamValue<msg_union_id_t>::decode(id, buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
if (id >= sizeof...(V)) {
|
|
return criticalError("Union doesn't have this many id's");
|
|
}
|
|
|
|
return decodeMembers<0>(builder, buffer, id);
|
|
}
|
|
};
|
|
|
|
template <class T, class Container>
|
|
struct ProtoKelDecodeImpl<Message<schema::Array<T>, Container>> {
|
|
static Error
|
|
decode(typename Message<schema::Array<T>, Container>::Builder data,
|
|
Buffer &buffer) {
|
|
msg_array_length_t array_length = 0;
|
|
{
|
|
Error error =
|
|
StreamValue<msg_array_length_t>::decode(array_length, buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
}
|
|
|
|
data.resize(array_length);
|
|
|
|
for (size_t i = 0; i < array_length; ++i) {
|
|
Error error =
|
|
ProtoKelDecodeImpl<typename Container::ElementType>::decode(
|
|
data.init(i), buffer);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
}
|
|
return noError();
|
|
}
|
|
};
|
|
|
|
template <class Schema, class Container>
|
|
Error ProtoKelCodec::encode(typename Message<Schema, Container>::Reader reader,
|
|
Buffer &buffer) {
|
|
BufferView view{buffer};
|
|
|
|
msg_packet_length_t packet_length =
|
|
ProtoKelEncodeImpl<Message<Schema, Container>>::size(reader);
|
|
// Check the size of the packet for the first
|
|
// message length description
|
|
|
|
Error error =
|
|
view.writeRequireLength(packet_length + sizeof(msg_packet_length_t));
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
|
|
{
|
|
Error error =
|
|
StreamValue<msg_packet_length_t>::encode(packet_length, view);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
}
|
|
{
|
|
Error error = ProtoKelEncodeImpl<Message<Schema, Container>>::encode(
|
|
reader, view);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
}
|
|
|
|
buffer.writeAdvance(view.writeOffset());
|
|
return noError();
|
|
}
|
|
|
|
template <class Schema, class Container>
|
|
Error ProtoKelCodec::decode(
|
|
typename Message<Schema, Container>::Builder builder, Buffer &buffer,
|
|
const Limits &limits) {
|
|
BufferView view{buffer};
|
|
|
|
msg_packet_length_t packet_length = 0;
|
|
{
|
|
Error error =
|
|
StreamValue<msg_packet_length_t>::decode(packet_length, view);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
}
|
|
|
|
if (packet_length > limits.packet_size) {
|
|
return criticalError(
|
|
[packet_length]() {
|
|
return std::string{"Packet size too big: "} +
|
|
std::to_string(packet_length);
|
|
},
|
|
"Packet size too big");
|
|
}
|
|
|
|
{
|
|
Error error = ProtoKelDecodeImpl<Message<Schema, Container>>::decode(
|
|
builder, view);
|
|
if (error.failed()) {
|
|
return error;
|
|
}
|
|
}
|
|
{
|
|
if (ProtoKelEncodeImpl<Message<Schema, Container>>::size(
|
|
builder.asReader()) != packet_length) {
|
|
return criticalError("Bad packet format");
|
|
}
|
|
}
|
|
|
|
buffer.readAdvance(view.readOffset());
|
|
return noError();
|
|
}
|
|
|
|
} // namespace saw
|