583 lines
16 KiB
C++
583 lines
16 KiB
C++
#pragma once
|
|
|
|
#include "buffer.h"
|
|
#include "message.h"
|
|
#include "stream_endian.h"
|
|
|
|
namespace saw {
|
|
class ProtoKelCodec {
|
|
public:
|
|
using UnionIdT = uint32_t;
|
|
using ArrayLengthT = uint64_t;
|
|
using PacketLengthT = uint64_t;
|
|
|
|
private:
|
|
struct ReadContext {
|
|
buffer &buffer;
|
|
size_t offset = 0;
|
|
};
|
|
|
|
template <typename T> friend struct ProtoKelEncodeImpl;
|
|
|
|
template <typename T> friend struct ProtoKelDecodeImpl;
|
|
|
|
public:
|
|
struct Limits {
|
|
ProtoKelCodec::PacketLengthT packet_size;
|
|
|
|
Limits() : packet_size{4096} {}
|
|
Limits(ProtoKelCodec::PacketLengthT 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 = message_container<Schema>>
|
|
error encode(typename message<Schema, Container>::reader reader,
|
|
buffer &buffer);
|
|
|
|
template <class Schema, class Container = message_container<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 err = stream_value<typename PrimitiveTypeHelper<
|
|
schema::Primitive<T, N>>::Type>::encode(data.get(), buffer);
|
|
return err;
|
|
}
|
|
|
|
static size_t
|
|
size(typename message<schema::Primitive<T, N>, Container>::reader) {
|
|
return stream_value<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 err = buffer.write_require_length(sizeof(size) + size);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
|
|
err = stream_value<size_t>::encode(size, buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
for (size_t i = 0; i < view.size(); ++i) {
|
|
buffer.write(i) = view[i];
|
|
}
|
|
buffer.write_advance(view.size());
|
|
return no_error();
|
|
}
|
|
|
|
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 no_error();
|
|
}
|
|
|
|
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 err =
|
|
ProtoKelEncodeImpl<typename Container::template ElementType<i>>::
|
|
encode(data.template get<i>(), buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
|
|
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, string_literal... 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 no_error();
|
|
}
|
|
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 err =
|
|
ProtoKelEncodeImpl<typename Container::template ElementType<i>>::
|
|
encode(data.template get<i>(), buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
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, string_literal... 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 no_error();
|
|
}
|
|
|
|
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 err =
|
|
stream_value<ProtoKelCodec::UnionIdT>::encode(i, buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
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(ProtoKelCodec::UnionIdT) + 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) {
|
|
ProtoKelCodec::ArrayLengthT array_length = data.size();
|
|
{
|
|
error err = stream_value<ProtoKelCodec::ArrayLengthT>::encode(
|
|
array_length, buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < array_length; ++i) {
|
|
error err =
|
|
ProtoKelEncodeImpl<typename Container::ElementType>::encode(
|
|
data.get(i), buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
}
|
|
return no_error();
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static size_t
|
|
size(typename message<schema::Array<T>, Container>::reader data) {
|
|
size_t members = sizeof(ProtoKelCodec::ArrayLengthT);
|
|
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 err = stream_value<typename PrimitiveTypeHelper<
|
|
schema::Primitive<T, N>>::Type>::decode(val, buffer);
|
|
data.set(val);
|
|
return err;
|
|
}
|
|
};
|
|
|
|
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.read_composite_length()) {
|
|
return recoverable_error("Buffer too small");
|
|
}
|
|
|
|
error err = stream_value<size_t>::decode(size, buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
|
|
if (size > buffer.read_composite_length()) {
|
|
return recoverable_error("Buffer too small");
|
|
}
|
|
|
|
std::string value;
|
|
value.resize(size);
|
|
|
|
if (size > buffer.read_composite_length()) {
|
|
return recoverable_error("Buffer too small");
|
|
}
|
|
for (size_t i = 0; i < value.size(); ++i) {
|
|
value[i] = buffer.read(i);
|
|
}
|
|
buffer.read_advance(value.size());
|
|
data.set(std::move(value));
|
|
return no_error();
|
|
}
|
|
};
|
|
|
|
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 no_error();
|
|
}
|
|
|
|
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 err =
|
|
ProtoKelDecodeImpl<typename Container::template ElementType<i>>::
|
|
decode(builder.template init<i>(), buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
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, string_literal... 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 no_error();
|
|
}
|
|
|
|
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 err =
|
|
ProtoKelDecodeImpl<typename Container::template ElementType<i>>::
|
|
decode(builder.template init<i>(), buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
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, string_literal... 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 &, ProtoKelCodec::UnionIdT) {
|
|
return no_error();
|
|
}
|
|
|
|
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, ProtoKelCodec::UnionIdT id) {
|
|
|
|
if (id == i) {
|
|
error err =
|
|
ProtoKelDecodeImpl<typename Container::template ElementType<
|
|
i>>::decode(builder.template init<i>(), buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
}
|
|
return decodeMembers<i + 1>(builder, buffer, id);
|
|
}
|
|
|
|
static error
|
|
decode(typename message<schema::Union<schema::NamedMember<V, K>...>,
|
|
Container>::builder builder,
|
|
buffer &buffer) {
|
|
ProtoKelCodec::UnionIdT id = 0;
|
|
error err = stream_value<ProtoKelCodec::UnionIdT>::decode(id, buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
if (id >= sizeof...(V)) {
|
|
return critical_error("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) {
|
|
ProtoKelCodec::ArrayLengthT array_length = 0;
|
|
{
|
|
error err = stream_value<ProtoKelCodec::ArrayLengthT>::decode(
|
|
array_length, buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
data.resize(array_length);
|
|
|
|
for (size_t i = 0; i < array_length; ++i) {
|
|
error err =
|
|
ProtoKelDecodeImpl<typename Container::ElementType>::decode(
|
|
data.init(i), buffer);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
}
|
|
return no_error();
|
|
}
|
|
};
|
|
|
|
template <class Schema, class Container>
|
|
error ProtoKelCodec::encode(typename message<Schema, Container>::reader reader,
|
|
buffer &buffer) {
|
|
buffer_view view{buffer};
|
|
|
|
ProtoKelCodec::PacketLengthT packet_length =
|
|
ProtoKelEncodeImpl<message<Schema, Container>>::size(reader);
|
|
// Check the size of the packet for the first
|
|
// message length description
|
|
|
|
error err = view.write_require_length(packet_length +
|
|
sizeof(ProtoKelCodec::PacketLengthT));
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
|
|
{
|
|
error err = stream_value<ProtoKelCodec::PacketLengthT>::encode(
|
|
packet_length, view);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
}
|
|
{
|
|
error err = ProtoKelEncodeImpl<message<Schema, Container>>::encode(
|
|
reader, view);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
buffer.write_advance(view.write_offset());
|
|
return no_error();
|
|
}
|
|
|
|
template <class Schema, class Container>
|
|
error ProtoKelCodec::decode(
|
|
typename message<Schema, Container>::builder builder, buffer &buffer,
|
|
const Limits &limits) {
|
|
buffer_view view{buffer};
|
|
|
|
ProtoKelCodec::PacketLengthT packet_length = 0;
|
|
{
|
|
error err = stream_value<ProtoKelCodec::PacketLengthT>::decode(
|
|
packet_length, view);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
if (packet_length > limits.packet_size) {
|
|
return critical_error(
|
|
[packet_length]() {
|
|
return std::string{"Packet size too big: "} +
|
|
std::to_string(packet_length);
|
|
},
|
|
"Packet size too big");
|
|
}
|
|
|
|
{
|
|
error err = ProtoKelDecodeImpl<message<Schema, Container>>::decode(
|
|
builder, view);
|
|
if (err.failed()) {
|
|
return err;
|
|
}
|
|
}
|
|
{
|
|
if (ProtoKelEncodeImpl<message<Schema, Container>>::size(
|
|
builder.as_reader()) != packet_length) {
|
|
return critical_error("Bad packet format");
|
|
}
|
|
}
|
|
|
|
buffer.read_advance(view.read_offset());
|
|
return no_error();
|
|
}
|
|
|
|
} // namespace saw
|