Skip to content

Commit c5f0a24

Browse files
authored
Add support for reading from a byte-container for cbor and ubjson (#469)
1 parent f676222 commit c5f0a24

File tree

7 files changed

+276
-3
lines changed

7 files changed

+276
-3
lines changed

include/rfl.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#include "rfl/apply.hpp"
5151
#include "rfl/as.hpp"
5252
#include "rfl/comparisons.hpp"
53+
#include "rfl/concepts.hpp"
5354
#include "rfl/default.hpp"
5455
#include "rfl/define_literal.hpp"
5556
#include "rfl/define_named_tuple.hpp"

include/rfl/cbor/read.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <jsoncons_ext/cbor/decode_cbor.hpp>
88
#include <string>
99

10+
#include "../concepts.hpp"
1011
#include "../Processors.hpp"
1112
#include "../internal/wrap_in_rfl_array_t.hpp"
1213
#include "Parser.hpp"
@@ -19,8 +20,7 @@ using InputVarType = typename Reader::InputVarType;
1920

2021
/// Parses an object from CBOR using reflection.
2122
template <class T, class... Ps>
22-
Result<internal::wrap_in_rfl_array_t<T>> read(const std::vector<char>& _bytes) {
23-
// TODO: Use a non-throwing decode_cbor(), pending https://github.com/danielaparker/jsoncons/issues/615
23+
Result<internal::wrap_in_rfl_array_t<T>> read(const ContiguousByteContainer auto& _bytes) {
2424
try {
2525
auto val = jsoncons::cbor::decode_cbor<jsoncons::json>(_bytes);
2626
auto r = Reader();

include/rfl/concepts.hpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#ifndef RFL_CONCEPTS_HPP_
2+
#define RFL_CONCEPTS_HPP_
3+
4+
#include <concepts>
5+
#include <type_traits>
6+
#include <cstdint>
7+
#include <cstddef>
8+
#include <iterator>
9+
10+
namespace rfl {
11+
12+
/// @brief Concept for byte-like types that can be used in contiguous containers
13+
/// Includes char, signed char, unsigned char, std::byte, and uint8_t
14+
template<typename T>
15+
concept ByteLike = std::same_as<T, char> ||
16+
std::same_as<T, signed char> ||
17+
std::same_as<T, unsigned char> ||
18+
std::same_as<T, std::uint8_t> ||
19+
std::same_as<T, std::byte>;
20+
21+
/// @brief Concept for containers with a contiguous sequence of byte-like types
22+
/// Requires:
23+
/// - Container has a value_type that is byte-like
24+
/// - Container provides data() method returning a pointer to contiguous memory
25+
/// - Container provides size() method returning the number of elements
26+
/// - Container supports range-based for loops (begin/end)
27+
template<typename Container>
28+
concept ContiguousByteContainer = requires(const Container& c) {
29+
typename Container::value_type;
30+
{ c.data() } -> std::convertible_to<const typename Container::value_type*>;
31+
{ c.size() } -> std::convertible_to<std::size_t>;
32+
{ c.begin() } -> std::input_iterator;
33+
{ c.end() } -> std::input_iterator;
34+
requires ByteLike<typename Container::value_type>;
35+
requires std::contiguous_iterator<decltype(c.begin())>;
36+
};
37+
38+
/// @brief Concept for mutable containers with a contiguous sequence of byte-like types
39+
/// Extends ContiguousByteContainer with mutable access requirements
40+
template<typename Container>
41+
concept MutableContiguousByteContainer = ContiguousByteContainer<Container> && requires(Container& c) {
42+
{ c.data() } -> std::convertible_to<typename Container::value_type*>;
43+
{ c.begin() } -> std::output_iterator<typename Container::value_type>;
44+
{ c.end() } -> std::output_iterator<typename Container::value_type>;
45+
};
46+
47+
/// @brief Concept for back-insertable byte containers (like std::vector<uint8_t>)
48+
/// Useful for containers that can grow dynamically during serialization
49+
template<typename Container>
50+
concept BackInsertableByteContainer = ContiguousByteContainer<Container> && requires(Container& c, typename Container::value_type v) {
51+
c.push_back(v);
52+
c.reserve(std::size_t{});
53+
{ c.capacity() } -> std::convertible_to<std::size_t>;
54+
};
55+
56+
/// @brief Concept for byte spans or views (read-only, non-owning containers)
57+
/// Includes std::span<const uint8_t>, std::string_view when used with char data, etc.
58+
template<typename Container>
59+
concept ByteSpanLike = ContiguousByteContainer<Container> &&
60+
std::is_trivially_copyable_v<Container> &&
61+
std::is_trivially_destructible_v<Container>;
62+
63+
} // namespace rfl
64+
65+
#endif // RFL_CONCEPTS_HPP_

include/rfl/ubjson/read.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <jsoncons_ext/ubjson/decode_ubjson.hpp>
88
#include <string>
99

10+
#include "../concepts.hpp"
1011
#include "../Processors.hpp"
1112
#include "../internal/wrap_in_rfl_array_t.hpp"
1213
#include "Parser.hpp"
@@ -19,7 +20,7 @@ using InputVarType = typename Reader::InputVarType;
1920

2021
/// Parses an object from UBJSON using reflection.
2122
template <class T, class... Ps>
22-
Result<internal::wrap_in_rfl_array_t<T>> read(const std::vector<char>& _bytes) {
23+
Result<internal::wrap_in_rfl_array_t<T>> read(const ContiguousByteContainer auto& _bytes) {
2324
// TODO: Use a non-throwing decode_ubjson(), pending https://github.com/danielaparker/jsoncons/issues/615
2425
try {
2526
auto val = jsoncons::ubjson::decode_ubjson<jsoncons::json>(_bytes);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#include <algorithm>
2+
#include <array>
3+
#include <rfl/cbor.hpp>
4+
5+
#include <gtest/gtest.h>
6+
7+
// Make sure things still compile when
8+
// rfl.hpp is included after rfl/cbor.hpp.
9+
#include <rfl.hpp>
10+
11+
namespace test_read_byte_containers
12+
{
13+
14+
struct TestBox
15+
{
16+
int length;
17+
int width;
18+
int height;
19+
};
20+
21+
// TODO: Apparently the jsoncons trait is_byte_sequence<T> is not working for std::span.
22+
// TEST(cbor, test_read_from_char_span)
23+
// {
24+
25+
// TestBox b = {
26+
// .length = 1,
27+
// .width = 2,
28+
// .height = 3,
29+
// };
30+
31+
32+
// std::vector<char> rfl_buffer = rfl::cbor::write(s);
33+
34+
// // I don't want to be forced to copy to a std::vector<char> if that's not what data strucure I use.
35+
// // So, allow reading from a std::span
36+
// std::span<char> span(rfl_buffer.data(), rfl_buffer.size());
37+
38+
// auto result = rfl::cbor::read<TestBox>(span);
39+
// EXPECT_TRUE(result);
40+
// EXPECT_EQ(result->one, 1);
41+
// EXPECT_EQ(result->two, 2);
42+
// }
43+
44+
TEST(cbor, test_read_from_byte_view)
45+
{
46+
TestBox b = {
47+
.length = 1,
48+
.width = 2,
49+
.height = 3,
50+
};
51+
52+
// TODO: Write directly into desired container, once rfl::cbor::write supports it.
53+
std::vector<char> rfl_buffer = rfl::cbor::write(b);
54+
55+
std::array<std::byte, 64> my_buffer;
56+
std::transform(rfl_buffer.begin(), rfl_buffer.end(), my_buffer.begin(),
57+
[](char c) { return static_cast<std::byte>(c); });
58+
59+
std::basic_string_view<std::byte> byte_view(my_buffer.data(), rfl_buffer.size());
60+
61+
auto result = rfl::cbor::read<TestBox>(byte_view);
62+
EXPECT_TRUE(result);
63+
EXPECT_EQ(result->length, 1);
64+
EXPECT_EQ(result->width, 2);
65+
EXPECT_EQ(result->height, 3);
66+
}
67+
68+
TEST(cbor, test_read_from_uint8_array)
69+
{
70+
TestBox b = {
71+
.length = 4,
72+
.width = 5,
73+
.height = 6,
74+
};
75+
76+
// TODO: Write directly into desired container, once rfl::cbor::write supports it.
77+
std::vector<char> rfl_buffer = rfl::cbor::write(b);
78+
79+
std::array<std::uint8_t, 64> my_buffer;
80+
std::transform(rfl_buffer.begin(), rfl_buffer.end(), my_buffer.begin(),
81+
[](char c) { return static_cast<std::uint8_t>(c); });
82+
83+
auto result = rfl::cbor::read<TestBox>(my_buffer);
84+
EXPECT_TRUE(result);
85+
EXPECT_EQ(result->length, 4);
86+
EXPECT_EQ(result->width, 5);
87+
EXPECT_EQ(result->height, 6);
88+
}
89+
90+
} // namespace test_read_byte_containers
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include <vector>
2+
#include <array>
3+
#include <string>
4+
#include <cassert>
5+
#include <rfl/concepts.hpp>
6+
7+
#include "write_and_read.hpp"
8+
9+
namespace test_byte_container_concepts {
10+
11+
TEST(generic, test_byte_container_concepts) {
12+
// Test various container types
13+
std::vector<std::uint8_t> vec_uint8{1, 2, 3, 4};
14+
std::vector<char> vec_char{'a', 'b', 'c'};
15+
std::array<std::uint8_t, 4> arr_uint8{1, 2, 3, 4};
16+
std::string str_data{"hello"};
17+
std::string_view str_view{"world"};
18+
std::array<std::byte, 5> arr_byte{std::byte{5}, std::byte{4}, std::byte{3}, std::byte{2}, std::byte{1}};
19+
std::span<std::byte> byte_span = arr_byte;
20+
21+
// Test with concepts
22+
static_assert(rfl::ByteLike<std::uint8_t>);
23+
static_assert(rfl::ByteLike<char>);
24+
static_assert(rfl::ByteLike<unsigned char>);
25+
static_assert(rfl::ByteLike<std::byte>);
26+
static_assert(!rfl::ByteLike<int>);
27+
static_assert(!rfl::ByteLike<float>);
28+
29+
static_assert(rfl::ContiguousByteContainer<decltype(vec_uint8)>);
30+
static_assert(rfl::ContiguousByteContainer<decltype(vec_char)>);
31+
static_assert(rfl::ContiguousByteContainer<decltype(arr_uint8)>);
32+
static_assert(rfl::ContiguousByteContainer<decltype(str_data)>);
33+
static_assert(rfl::ContiguousByteContainer<decltype(str_view)>);
34+
static_assert(rfl::ContiguousByteContainer<decltype(arr_byte)>);
35+
static_assert(rfl::ContiguousByteContainer<decltype(byte_span)>);
36+
37+
static_assert(rfl::MutableContiguousByteContainer<decltype(vec_uint8)>);
38+
static_assert(rfl::MutableContiguousByteContainer<decltype(vec_char)>);
39+
static_assert(rfl::MutableContiguousByteContainer<decltype(arr_uint8)>);
40+
static_assert(rfl::MutableContiguousByteContainer<decltype(str_data)>);
41+
static_assert(!rfl::MutableContiguousByteContainer<decltype(str_view)>);
42+
static_assert(rfl::MutableContiguousByteContainer<decltype(arr_byte)>);
43+
static_assert(rfl::MutableContiguousByteContainer<decltype(byte_span)>);
44+
45+
static_assert(rfl::BackInsertableByteContainer<decltype(vec_uint8)>);
46+
static_assert(rfl::BackInsertableByteContainer<decltype(vec_char)>);
47+
static_assert(!rfl::BackInsertableByteContainer<decltype(arr_uint8)>);
48+
static_assert(rfl::BackInsertableByteContainer<decltype(str_data)>);
49+
static_assert(!rfl::BackInsertableByteContainer<decltype(str_view)>);
50+
static_assert(!rfl::BackInsertableByteContainer<decltype(arr_byte)>);
51+
static_assert(!rfl::BackInsertableByteContainer<decltype(byte_span)>);
52+
}
53+
54+
} // namespace test_byte_container_concepts
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include <algorithm>
2+
#include <array>
3+
#include <rfl/ubjson.hpp>
4+
5+
#include <gtest/gtest.h>
6+
7+
// Make sure things still compile when
8+
// rfl.hpp is included after rfl/ubjson.hpp.
9+
#include <rfl.hpp>
10+
11+
namespace test_read_byte_containers
12+
{
13+
14+
struct TestBall
15+
{
16+
float radius;
17+
float mass;
18+
};
19+
20+
TEST(ubjson, test_read_from_byte_view)
21+
{
22+
TestBall b = {
23+
.radius = 1.5f,
24+
.mass = 2.5f,
25+
};
26+
27+
// TODO: Write directly into desired container, once rfl::ubjson::write supports it.
28+
std::vector<char> rfl_buffer = rfl::ubjson::write(b);
29+
30+
std::array<std::byte, 64> my_buffer;
31+
std::transform(rfl_buffer.begin(), rfl_buffer.end(), my_buffer.begin(),
32+
[](char c) { return static_cast<std::byte>(c); });
33+
34+
std::basic_string_view<std::byte> byte_view(my_buffer.data(), rfl_buffer.size());
35+
36+
auto result = rfl::ubjson::read<TestBall>(byte_view);
37+
EXPECT_TRUE(result);
38+
EXPECT_EQ(result->radius, 1.5f);
39+
EXPECT_EQ(result->mass, 2.5f);
40+
}
41+
42+
TEST(ubjson, test_read_from_uint8_array)
43+
{
44+
TestBall b = {
45+
.radius = 4.5f,
46+
.mass = 5.5f,
47+
};
48+
49+
// TODO: Write directly into desired container, once rfl::ubjson::write supports it.
50+
std::vector<char> rfl_buffer = rfl::ubjson::write(b);
51+
52+
std::array<std::uint8_t, 64> my_buffer;
53+
std::transform(rfl_buffer.begin(), rfl_buffer.end(), my_buffer.begin(),
54+
[](char c) { return static_cast<std::uint8_t>(c); });
55+
56+
auto result = rfl::ubjson::read<TestBall>(my_buffer);
57+
EXPECT_TRUE(result);
58+
EXPECT_EQ(result->radius, 4.5f);
59+
EXPECT_EQ(result->mass, 5.5f);
60+
}
61+
62+
} // namespace test_read_byte_containers

0 commit comments

Comments
 (0)