Skip to content

Commit

Permalink
mpp: implement container adapter
Browse files Browse the repository at this point in the history
Previously encode/decode expected the first argument (cont) to
be tnt::Buffer of something with the same API.

This commit introduces contaier adapter, so it is allowed now to
encode to standard contiguous container (std::vector etc) and
encode/decode just by pointer to data.

Fix decode headers while we are here.
  • Loading branch information
alyapunov committed Jul 16, 2024
1 parent ab60f23 commit a4574ef
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 32 deletions.
273 changes: 273 additions & 0 deletions src/mpp/ContAdapter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
#pragma once
/*
* Copyright 2010-2024 Tarantool AUTHORS: please see AUTHORS file.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY AUTHORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#include <cstdint>
#include <cstring>
#include <utility>

#include "../Utils/CStr.hpp"
#include "../Utils/Traits.hpp"

namespace mpp {

namespace encode_details {

/** Common data+size pair that is used for writing of variable-length data. */
struct WData {
const char *data;
size_t size;
};

/** Random struct; used to check whether container has template write method. */
struct TestWriteStruct {
uint32_t a;
uint16_t b;
};

/** Test that container if Buffer-like: has several needed write methods. */
template <class CONT, class _ = void>
struct is_write_callable_h : std::false_type {};

template <class CONT>
struct is_write_callable_h<CONT,
std::void_t<decltype(std::declval<CONT>().write(uint8_t{})),
decltype(std::declval<CONT>().write(uint64_t{})),
decltype(std::declval<CONT>().write(TestWriteStruct{})),
decltype(std::declval<CONT>().write({(const char *)0, 1})),
decltype(std::declval<CONT>().write(tnt::CStr<'a', 'b'>{}))
>> : std::true_type {};

template <class CONT>
constexpr bool is_write_callable_v = is_write_callable_h<CONT>::value;

template <class CONT>
class BufferWriter {
public:
explicit BufferWriter(CONT& cont_) : cont{cont_} {}
void write(WData data) { cont.write({data.data, data.size}); }
template <char... C>
void write(tnt::CStr<C...> str)
{
cont.write(std::move(str));
}
template <class T>
void write(T&& t)
{
cont.write(std::forward<T>(t));
}

private:
CONT& cont;
};

template <class CONT>
class StdContWriter {
public:
static_assert(sizeof(*std::declval<CONT>().data()) == 1);
explicit StdContWriter(CONT& cont_) : cont{cont_} {}
void write(WData data)
{
size_t old_size = std::size(cont);
cont.resize(old_size + data.size);
std::memcpy(std::data(cont) + old_size, data.data, data.size);
}
template <char... C>
void write(tnt::CStr<C...> data)
{
size_t old_size = std::size(cont);
cont.resize(old_size + data.size);
std::memcpy(std::data(cont) + old_size, data.data, data.size);
}

template <class T>
void write(T&& t)
{
static_assert(std::is_standard_layout_v<std::remove_reference_t<T>>);
size_t old_size = std::size(cont);
cont.resize(old_size + sizeof(T));
std::memcpy(std::data(cont) + old_size, &t, sizeof(T));
}

private:
CONT& cont;
};

template <class C>
class PtrWriter {
public:
static_assert(sizeof(C) == 1);
static_assert(!std::is_const_v<C>);
explicit PtrWriter(C *& ptr_) : ptr{ptr_} {}
void write(WData data)
{
std::memcpy(ptr, data.data, data.size);
ptr += data.size;
}
template <char... D>
void write(tnt::CStr<D...> data)
{
std::memcpy(ptr, data.data, data.size);
ptr += data.size;
}

template <class T>
void write(T&& t)
{
static_assert(std::is_standard_layout_v<std::remove_reference_t<T>>);
std::memcpy(ptr, &t, sizeof(t));
ptr += sizeof(t);
}

private:
C *& ptr;
};

template <class CONT>
auto
wr(CONT& cont)
{
if constexpr (is_write_callable_v<CONT>)
return BufferWriter<CONT>{cont};
else if constexpr (tnt::is_resizable_v<CONT> && tnt::is_contiguous_v<CONT>)
return StdContWriter<CONT>{cont};
else if constexpr (std::is_pointer_v<CONT>)
return PtrWriter<std::remove_pointer_t<CONT>>{cont};
else
static_assert(tnt::always_false_v<CONT>);
}

} // namespace encode_details

namespace decode_details {

/** Common data+size pair that is used for reading of variable-length data. */
struct RData {
char *data;
size_t size;
};

/** Struct that when used as read argument, means skipping of 'size' data. */
struct Skip {
size_t size;
};

/** Random struct; used to check whether container has template read method. */
struct TestReadStruct {
uint8_t a;
uint64_t b;
};

/** Test that container if Buffer-like: has several needed read methods. */
template <class CONT, class _ = void>
struct is_read_callable_h : std::false_type {};

template <class CONT>
struct is_read_callable_h<CONT,
std::void_t<decltype(std::declval<CONT>().read(*(uint8_t*)0)),
decltype(std::declval<CONT>().read(*(uint64_t*)0)),
decltype(std::declval<CONT>().read(*(TestReadStruct*)0)),
decltype(std::declval<CONT>().read({(char *)0, 1})),
decltype(std::declval<CONT>().template get<uint8_t>()),
decltype(std::declval<CONT>().read({1}))
>> : std::true_type {};

template <class CONT>
constexpr bool is_read_callable_v = is_read_callable_h<CONT>::value;

template <class CONT>
class BufferReader {
public:
explicit BufferReader(CONT& cont_) : cont{cont_} {}
void read(RData data) { cont.read({data.data, data.size}); }
void read(Skip data) { cont.read({data.size}); }
template <class T>
void read(T&& t)
{
cont.read(std::forward<T>(t));
}
template <class T>
T get()
{
return cont.template get<T>();
}

private:
CONT& cont;
};

template <class C>
class PtrReader {
public:
static_assert(sizeof(C) == 1);
explicit PtrReader(C *& ptr_) : ptr{ptr_} {}
void read(RData data)
{
std::memcpy(data.data, ptr, data.size);
ptr += data.size;
}
void read(Skip data) { ptr += data.size; }

template <class T>
void read(T& t)
{
static_assert(std::is_standard_layout_v<std::remove_reference_t<T>>);
std::memcpy(&t, ptr, sizeof(t));
ptr += sizeof(t);
}
template <class T>
T get()
{
static_assert(std::is_standard_layout_v<std::remove_reference_t<T>>);
T t;
std::memcpy(&t, ptr, sizeof(t));
return t;
}

private:
C *& ptr;
};

template <class CONT>
auto
rd(CONT& cont)
{
if constexpr (is_read_callable_v<CONT>)
return BufferReader<CONT>{cont};
else if constexpr (std::is_pointer_v<CONT>)
return PtrReader<std::remove_pointer_t<CONT>>{cont};
else
static_assert(tnt::always_false_v<CONT>);
}

} // namespace decode_details

} // namespace mpp
33 changes: 20 additions & 13 deletions src/mpp/Dec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,19 @@
#include <cassert>
#include <functional>
#include <cstdint>
#include <cstring>
#include <tuple>
#include <utility>
#include <variant>

#include "BSwap.hpp"
#include "ClassRule.hpp"
#include "ContAdapter.hpp"
#include "Constants.hpp"
#include "Rules.hpp"
#include "Spec.hpp"
#include "../Utils/CStr.hpp"
#include "../Utils/Traits.hpp"

namespace mpp {

Expand All @@ -60,7 +67,7 @@ constexpr bool is_any_putable_v =
/**
* If it is true, the object of type T will not be decoded - raw data will
* be saved to it.
*
*
* Now it supports only a pair of iterators (probably, wrapped with
* mpp::as_raw). The check implicilty implies that BUF is an iterator, not
* buffer - it would be strange to pass a pair of buffer to decoder.
Expand Down Expand Up @@ -411,7 +418,7 @@ auto read_value(BUF& buf)
using RULE = rule_by_family_t<FAMILY>;
if constexpr (SUBRULE == SIMPLEX_SUBRULE) {
typename RULE::simplex_value_t tag;
buf.read(tag);
rd(buf).read(tag);
assert(tag >= rule_simplex_tag_range_v<RULE>.first);
assert(tag <= rule_simplex_tag_range_v<RULE>.last);
[[maybe_unused]] typename RULE::simplex_value_t val =
Expand All @@ -427,12 +434,12 @@ auto read_value(BUF& buf)
return val;
} else {
uint8_t tag;
buf.read(tag);
rd(buf).read(tag);
assert(tag == RULE::complex_tag + SUBRULE);
using TYPES = typename RULE::complex_types;
using V = std::tuple_element_t<SUBRULE, TYPES>;
under_uint_t<V> u;
buf.read(u);
rd(buf).read(u);
V val = bswap<V>(u);
return val;
}
Expand All @@ -445,7 +452,7 @@ auto read_item(BUF& buf, ITEM& item)
auto val = read_value<FAMILY, SUBRULE>(buf);
if constexpr (RULE::has_ext) {
int8_t ext_type;
buf.read(ext_type);
rd(buf).read(ext_type);
item.ext_type = ext_type;
}
if constexpr (RULE::has_data) {
Expand All @@ -468,11 +475,11 @@ auto read_item(BUF& buf, ITEM& item)
if (size > std::size(item))
size = std::size(item);
}
buf.read({std::data(item), size});
rd(buf).read({std::data(item), size});
if constexpr (tnt::is_limited_v<ITEM> ||
!tnt::is_resizable_v<ITEM>) {
if (size < size_t(val))
buf.read({size_t(val) - size});
rd(buf).read({size_t(val) - size});
}
} else if constexpr (RULE::has_children) {
if constexpr (tnt::is_clearable_v<ITEM>)
Expand Down Expand Up @@ -743,7 +750,7 @@ decode_jump(BUF& buf, T... t)
{
static_assert(path_item_type(PATH::last()) != PIT_BAD);
static constexpr auto jumps = JumpsBuilder<PATH, BUF, T...>::build();
uint8_t tag = buf.template get<uint8_t>();
uint8_t tag = rd(buf).template get<uint8_t>();
return jumps.data[tag](buf, t...);
}

Expand Down Expand Up @@ -922,10 +929,10 @@ bool jump_skip(BUF& buf, T... t)

if constexpr (RULE::has_ext) {
int8_t ext_type;
buf.read(ext_type);
rd(buf).read(ext_type);
}
if constexpr (RULE::has_data) {
buf.read({size_t(val)});
rd(buf).read({size_t(val)});
}
if constexpr (RULE::has_children) {
auto& arg = std::get<sizeof...(T) - 1>(std::tie(t...));
Expand Down Expand Up @@ -1116,7 +1123,7 @@ bool jump_find_key([[maybe_unused]] K k, tnt::iseq<>, BUF& buf, T... t)
static_assert(path_item_type(PATH::last()) == PIT_DYN_KEY);
using NEXT_PATH = path_push_t<PATH, PIT_DYN_SKIP>;
if constexpr (FAMILY == MP_STR)
buf.read({k});
rd(buf).read({k});
return decode_impl<NEXT_PATH>(buf, t..., size_t(1));
}

Expand All @@ -1143,7 +1150,7 @@ bool jump_find_key(K k, tnt::iseq<I, J...>, BUF& buf, T... t)

if (compare_key<FAMILY>(k, key, buf)) {
if constexpr (FAMILY == MP_STR)
buf.read({k});
rd(buf).read({k});
return decode_impl<NEXT_PATH>(buf, t...);
}

Expand Down Expand Up @@ -1274,7 +1281,7 @@ bool broken_msgpack_jump(BUF&, T...)

template <class BUF, class... T>
bool
decode(BUF& buf, T&&... t)
decode(BUF&& buf, T&&... t)
{
// TODO: Guard
bool res = decode_details::decode(buf, std::forward<T>(t)...);
Expand Down
Loading

0 comments on commit a4574ef

Please sign in to comment.