Bidirectional msgpack wire protocol with unpack support
This commit is contained in:
@@ -21,10 +21,7 @@ enum class error_code {
|
||||
type_error,
|
||||
};
|
||||
|
||||
// MessagePack format byte constants. Ranges are handled via helper functions
|
||||
// rather than enumerating every value.
|
||||
namespace format {
|
||||
// Fixed ranges (use is_* helpers below)
|
||||
constexpr uint8_t POSITIVE_FIXINT_MIN = 0x00;
|
||||
constexpr uint8_t POSITIVE_FIXINT_MAX = 0x7F;
|
||||
constexpr uint8_t FIXMAP_MIN = 0x80;
|
||||
@@ -36,7 +33,6 @@ namespace format {
|
||||
constexpr uint8_t NEGATIVE_FIXINT_MIN = 0xE0;
|
||||
constexpr uint8_t NEGATIVE_FIXINT_MAX = 0xFF;
|
||||
|
||||
// Specific type bytes
|
||||
constexpr uint8_t NIL = 0xC0;
|
||||
constexpr uint8_t NEVER_USED = 0xC1;
|
||||
constexpr uint8_t FALSE = 0xC2;
|
||||
@@ -80,7 +76,6 @@ namespace format {
|
||||
template <typename T>
|
||||
using result = std::expected<T, error_code>;
|
||||
|
||||
// Read a big-endian number from position m_p+1.
|
||||
template <typename T>
|
||||
result<T> body_number(const uint8_t *p, int size) {
|
||||
if (size < 1 + static_cast<int>(sizeof(T))) {
|
||||
@@ -105,8 +100,6 @@ result<T> body_number(const uint8_t *p, int size) {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns {header_bytes, body_bytes} for a given format byte.
|
||||
// For container types (array/map), body_bytes is not meaningful (returns 0).
|
||||
struct body_info {
|
||||
int header; // bytes before the body (includes format byte + length fields + ext type byte)
|
||||
uint32_t body; // body size in bytes (0 for containers, computed for variable-length)
|
||||
@@ -360,8 +353,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Generic pack: dispatches based on type.
|
||||
// Integers
|
||||
template <typename T>
|
||||
requires std::is_integral_v<T> && (!std::is_same_v<T, bool>)
|
||||
pack_result pack(T n) { return pack_integer(n); }
|
||||
@@ -373,10 +364,8 @@ public:
|
||||
pack_result pack(std::string_view v) { return pack_str(v); }
|
||||
pack_result pack(const std::string &v) { return pack_str(v); }
|
||||
|
||||
// Binary (vector<uint8_t>)
|
||||
pack_result pack(const std::vector<uint8_t> &v) { return pack_bin(v); }
|
||||
|
||||
// Tuples → msgpack array
|
||||
template <typename... Ts>
|
||||
pack_result pack(const std::tuple<Ts...> &t) {
|
||||
auto r = pack_array(sizeof...(Ts));
|
||||
@@ -384,7 +373,6 @@ public:
|
||||
return pack_tuple_elements(t, std::index_sequence_for<Ts...>{});
|
||||
}
|
||||
|
||||
// Structs with ext_id and as_tuple() → ext wrapping msgpack array
|
||||
template <typename T>
|
||||
requires requires(const T &v) { { T::ext_id } -> std::convertible_to<int8_t>; v.as_tuple(); }
|
||||
pack_result pack(const T &v) {
|
||||
@@ -394,7 +382,6 @@ public:
|
||||
return pack_ext(T::ext_id, inner.get_payload());
|
||||
}
|
||||
|
||||
// Structs with as_tuple() but no ext_id → plain msgpack array
|
||||
template <typename T>
|
||||
requires (requires(const T &v) { v.as_tuple(); } && !requires { { T::ext_id } -> std::convertible_to<int8_t>; })
|
||||
pack_result pack(const T &v) {
|
||||
@@ -440,7 +427,6 @@ public:
|
||||
return parser(m_p + n, m_size - n);
|
||||
}
|
||||
|
||||
// Navigate to the next value in a sequence.
|
||||
result<parser> next() const {
|
||||
auto hdr = header_byte();
|
||||
if (!hdr) return std::unexpected(hdr.error());
|
||||
@@ -467,11 +453,9 @@ public:
|
||||
auto cur = advance(info->header);
|
||||
if (!cur) return std::unexpected(cur.error());
|
||||
for (uint32_t i = 0; i < *cnt; ++i) {
|
||||
// key
|
||||
auto k = cur->next();
|
||||
if (!k) return std::unexpected(k.error());
|
||||
cur = *k;
|
||||
// value
|
||||
auto v = cur->next();
|
||||
if (!v) return std::unexpected(v.error());
|
||||
cur = *v;
|
||||
@@ -485,7 +469,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Type checks
|
||||
bool is_nil() const {
|
||||
auto h = header_byte();
|
||||
return h && *h == format::NIL;
|
||||
@@ -544,7 +527,6 @@ public:
|
||||
return b == format::MAP16 || b == format::MAP32;
|
||||
}
|
||||
|
||||
// Value accessors
|
||||
|
||||
result<bool> get_bool() const {
|
||||
auto h = header_byte();
|
||||
@@ -610,7 +592,6 @@ public:
|
||||
return std::string_view(reinterpret_cast<const char *>(m_p + offset), len);
|
||||
}
|
||||
|
||||
// Returns {ext_type, data_view}.
|
||||
result<std::tuple<int8_t, std::string_view>> get_ext() const {
|
||||
auto h = header_byte();
|
||||
if (!h) return std::unexpected(h.error());
|
||||
@@ -714,4 +695,79 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
requires std::is_integral_v<T> && (!std::is_same_v<T, bool>)
|
||||
result<parser> unpack(const parser &p, T &out) {
|
||||
auto v = p.get_number<T>();
|
||||
if (!v) return std::unexpected(v.error());
|
||||
out = *v;
|
||||
return p.next();
|
||||
}
|
||||
|
||||
inline result<parser> unpack(const parser &p, bool &out) {
|
||||
auto v = p.get_bool();
|
||||
if (!v) return std::unexpected(v.error());
|
||||
out = *v;
|
||||
return p.next();
|
||||
}
|
||||
|
||||
inline result<parser> unpack(const parser &p, std::string_view &out) {
|
||||
auto v = p.get_string();
|
||||
if (!v) return std::unexpected(v.error());
|
||||
out = *v;
|
||||
return p.next();
|
||||
}
|
||||
|
||||
inline result<parser> unpack(const parser &p, std::vector<uint8_t> &out) {
|
||||
auto v = p.get_binary_view();
|
||||
if (!v) return std::unexpected(v.error());
|
||||
out.assign(v->begin(), v->end());
|
||||
return p.next();
|
||||
}
|
||||
|
||||
template <typename... Ts, size_t... Is>
|
||||
result<parser> unpack_tuple_elements(const parser &p, std::tuple<Ts...> &t, std::index_sequence<Is...>) {
|
||||
result<parser> cur = p.first_item();
|
||||
if (!cur) return cur;
|
||||
((cur = cur ? unpack(*cur, std::get<Is>(t)) : cur), ...);
|
||||
return cur;
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
result<parser> unpack(const parser &p, std::tuple<Ts...> &t) {
|
||||
auto cnt = p.count();
|
||||
if (!cnt) return std::unexpected(cnt.error());
|
||||
if (*cnt != sizeof...(Ts)) return std::unexpected(error_code::type_error);
|
||||
auto r = unpack_tuple_elements(p, t, std::index_sequence_for<Ts...>{});
|
||||
if (!r) return r;
|
||||
return p.next();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires (requires(T &v) { v.as_tuple(); } && !requires { { T::ext_id } -> std::convertible_to<int8_t>; })
|
||||
result<parser> unpack(const parser &p, T &out) {
|
||||
auto tup = out.as_tuple();
|
||||
auto cnt = p.count();
|
||||
if (!cnt) return std::unexpected(cnt.error());
|
||||
if (*cnt != std::tuple_size_v<decltype(tup)>) return std::unexpected(error_code::type_error);
|
||||
auto r = unpack_tuple_elements(p, tup, std::make_index_sequence<std::tuple_size_v<decltype(tup)>>{});
|
||||
if (!r) return r;
|
||||
return p.next();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires requires(T &v) { { T::ext_id } -> std::convertible_to<int8_t>; v.as_tuple(); }
|
||||
result<parser> unpack(const parser &p, T &out) {
|
||||
auto ext = p.get_ext();
|
||||
if (!ext) return std::unexpected(ext.error());
|
||||
auto [ext_type, ext_data] = *ext;
|
||||
if (ext_type != T::ext_id) return std::unexpected(error_code::type_error);
|
||||
parser inner(reinterpret_cast<const uint8_t *>(ext_data.data()),
|
||||
static_cast<int>(ext_data.size()));
|
||||
auto tup = out.as_tuple();
|
||||
auto r = unpack_tuple_elements(inner, tup, std::make_index_sequence<std::tuple_size_v<decltype(tup)>>{});
|
||||
if (!r) return r;
|
||||
return p.next();
|
||||
}
|
||||
|
||||
} // namespace msgpackpp
|
||||
|
||||
33
include/static_vector.h
Normal file
33
include/static_vector.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
template <typename T, size_t Capacity>
|
||||
class static_vector {
|
||||
T m_data[Capacity];
|
||||
size_t m_size = 0;
|
||||
|
||||
public:
|
||||
void push_back(const T &v) {
|
||||
if (m_size < Capacity) m_data[m_size++] = v;
|
||||
}
|
||||
|
||||
void clear() { m_size = 0; }
|
||||
|
||||
size_t size() const { return m_size; }
|
||||
size_t capacity() const { return Capacity; }
|
||||
bool full() const { return m_size >= Capacity; }
|
||||
bool empty() const { return m_size == 0; }
|
||||
|
||||
T *data() { return m_data; }
|
||||
const T *data() const { return m_data; }
|
||||
|
||||
T &operator[](size_t i) { return m_data[i]; }
|
||||
const T &operator[](size_t i) const { return m_data[i]; }
|
||||
|
||||
T *begin() { return m_data; }
|
||||
T *end() { return m_data + m_size; }
|
||||
const T *begin() const { return m_data; }
|
||||
const T *end() const { return m_data + m_size; }
|
||||
};
|
||||
Reference in New Issue
Block a user