275 lines
7.6 KiB
C++
275 lines
7.6 KiB
C++
//-----------------------------------------------------------------------------
|
|
// ___ __ _ _
|
|
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
|
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
|
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
|
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// Author: Kurt Sassenrath
|
|
// Module: msgpack
|
|
//
|
|
// Core writing implementation, upon which writers are built.
|
|
//
|
|
// Copyright (c) 2023 Kurt Sassenrath.
|
|
//
|
|
// License TBD.
|
|
//-----------------------------------------------------------------------------
|
|
#ifndef msgpack_core_writer_ce48a51aa6ed0858
|
|
#define msgpack_core_writer_ce48a51aa6ed0858
|
|
|
|
#include "format.h"
|
|
#include "../util/endianness.h"
|
|
#include <limits>
|
|
#include <type_traits>
|
|
|
|
#include <tl/expected.hpp>
|
|
|
|
namespace msgpack {
|
|
|
|
enum class writer_error {
|
|
none,
|
|
unsupported,
|
|
not_implemented,
|
|
bad_value,
|
|
out_of_space
|
|
};
|
|
|
|
// Helper template for writing a non-integral datatype to an output.
|
|
//template <typename T>
|
|
//struct write_adapter {
|
|
//template <typename Itr>
|
|
//static constexpr tl::expected<Itr, writer_error> write(T const& t);
|
|
//};
|
|
|
|
template <std::size_t N, typename Itr>
|
|
constexpr inline decltype(auto) write_bytes(std::array<std::byte, N>&& data,
|
|
Itr out) noexcept {
|
|
for (auto b : data) {
|
|
*out++ = b;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
template <typename T>
|
|
struct write_adapter {};
|
|
|
|
template <std::integral T>
|
|
struct write_adapter<T> {
|
|
static constexpr auto size(T) noexcept {
|
|
return sizeof(T);
|
|
}
|
|
|
|
template <typename Itr>
|
|
static constexpr auto write(T t, Itr out) noexcept {
|
|
return write_bytes(detail::raw_cast(host_to_be(t)), out);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct write_adapter<std::string_view> {
|
|
static constexpr auto size(std::string_view str) noexcept {
|
|
return str.size();
|
|
}
|
|
|
|
template <typename Itr>
|
|
static constexpr auto write(std::string_view str, Itr out) noexcept {
|
|
std::byte const* beg = reinterpret_cast<std::byte const*>(&*str.begin());
|
|
std::copy(beg, beg + str.size(), out);
|
|
return out += str.size();
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct write_adapter<std::span<std::byte const>> {
|
|
static constexpr auto size(std::span<std::byte const> bytes) noexcept {
|
|
return bytes.size();
|
|
}
|
|
|
|
template <typename Itr>
|
|
static constexpr auto write(std::span<std::byte const> bytes,
|
|
Itr out) noexcept {
|
|
std::copy(bytes.begin(), bytes.end(), out);
|
|
return out += bytes.size();
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct write_adapter<map_desc> {
|
|
static constexpr auto value(map_desc desc) noexcept {
|
|
return desc.count;
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct write_adapter<array_desc> {
|
|
static constexpr auto value(array_desc desc) noexcept {
|
|
return desc.count;
|
|
}
|
|
};
|
|
|
|
// TODO: These could be optimized away because invalid/nil never contain real
|
|
// data.
|
|
template <>
|
|
struct write_adapter<invalid> {
|
|
static constexpr auto value(invalid) noexcept {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct write_adapter<nil> {
|
|
static constexpr auto value(nil) noexcept {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
namespace detail {
|
|
template <typename T>
|
|
using expected = tl::expected<T, writer_error>;
|
|
|
|
template <format_type F>
|
|
constexpr inline std::size_t calculate_space(typename F::value_type val)
|
|
noexcept {
|
|
// At a minimum, one byte is needed to store the format.
|
|
std::size_t size = sizeof(typename F::first_type);
|
|
|
|
if (!is_fixtype<F>) {
|
|
++size; // For format
|
|
}
|
|
|
|
if constexpr (F::payload_type == format::payload::variable) {
|
|
size += write_adapter<typename F::value_type>::size(val);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
// The "first type" is either the size of the variable length payload or
|
|
// a fixed-length value. Additionally, this "first type" may be inlined
|
|
// into the marker byte if it's a fix type.
|
|
|
|
template <format_type F>
|
|
constexpr inline expected<typename F::first_type> pack_first(
|
|
typename F::value_type value) noexcept {
|
|
using value_type = typename F::value_type;
|
|
if constexpr (F::payload_type == format::payload::variable) {
|
|
return typename F::first_type(write_adapter<value_type>::size(value));
|
|
} else {
|
|
if constexpr (requires { write_adapter<value_type>::value; }) {
|
|
return typename F::first_type(write_adapter<value_type>::value(value));
|
|
} else {
|
|
return typename F::first_type(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <format_type F, typename Itr>
|
|
constexpr inline expected<Itr> write(
|
|
typename F::value_type&& value, Itr out, Itr const end) {
|
|
using diff_type = typename std::iterator_traits<Itr>::difference_type;
|
|
if (diff_type(calculate_space<F>(value)) > std::distance(out, end)) {
|
|
return tl::make_unexpected(writer_error::out_of_space);
|
|
}
|
|
|
|
auto marker = F::marker;
|
|
|
|
auto result = pack_first<F>(value);
|
|
if (!result) {
|
|
return tl::make_unexpected(result.error());
|
|
}
|
|
|
|
if constexpr (is_fixtype<F>) {
|
|
if (*result > 0xff) {
|
|
return tl::make_unexpected(writer_error::bad_value);
|
|
}
|
|
if constexpr (F::flags & format::flag::apply_mask) {
|
|
marker |= std::byte(*result);
|
|
} else {
|
|
marker = std::byte(*result);
|
|
}
|
|
if ((marker & ~F::mask) != F::marker) {
|
|
return tl::make_unexpected(writer_error::bad_value);
|
|
}
|
|
}
|
|
|
|
*out++ = marker;
|
|
|
|
if constexpr (!is_fixtype<F>) {
|
|
out = write_adapter<typename decltype(result)::value_type>::write(*result, out);
|
|
}
|
|
|
|
if constexpr (F::payload_type == format::payload::variable) {
|
|
out = write_adapter<typename F::value_type>::write(value, out);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
template <typename T>
|
|
struct format_hint;
|
|
|
|
template <>
|
|
struct format_hint<std::uint8_t> {
|
|
using type = format::positive_fixint;
|
|
};
|
|
|
|
template <>
|
|
struct format_hint<std::uint16_t> {
|
|
using type = format::uint16;
|
|
};
|
|
} // namespace detail
|
|
|
|
class writer {
|
|
public:
|
|
|
|
using error_code = writer_error;
|
|
|
|
template <typename T>
|
|
using expected = detail::expected<T>;
|
|
|
|
constexpr writer(std::span<std::byte> dest) :
|
|
data(dest), curr(std::begin(data)), end(std::end(data)) {}
|
|
|
|
template <format_type F>
|
|
constexpr expected<tl::monostate> write(typename F::value_type&& v)
|
|
noexcept {
|
|
using value_type = typename F::value_type;
|
|
if (curr == end) return tl::make_unexpected(error_code::out_of_space);
|
|
auto result = detail::write<F>(std::forward<value_type>(v), curr, end);
|
|
if (!result) {
|
|
return tl::make_unexpected(result.error());
|
|
}
|
|
|
|
curr = *result;
|
|
return tl::monostate{};
|
|
}
|
|
|
|
template <typename T>
|
|
requires requires { typename detail::format_hint<T>::type; }
|
|
constexpr expected<tl::monostate> write(T&& v) {
|
|
return write<typename detail::format_hint<T>::type>(std::forward<T>(v));
|
|
}
|
|
|
|
constexpr auto pos() const noexcept {
|
|
return curr;
|
|
}
|
|
|
|
constexpr auto tell() const noexcept {
|
|
return std::distance(std::begin(data), curr);
|
|
}
|
|
|
|
constexpr auto subspan() const noexcept {
|
|
return std::span<std::byte>(std::begin(data), tell());
|
|
}
|
|
|
|
private:
|
|
std::span<std::byte> data;
|
|
decltype(data)::iterator curr;
|
|
decltype(data)::iterator end;
|
|
};
|
|
|
|
} // namespace msgpack
|
|
|
|
#endif // msgpack_core_writer_ce48a51aa6ed0858
|