1 #pragma once
2 
3 #include <cstdint>
4 #include <cstring>
5 #include <optional>
6 #include <string>
7 #include <type_traits>
8 #include <vector>
9 
10 namespace {
11 
12 // Trivial type
13 template <typename T, std::enable_if_t<std::is_trivially_copyable_v<T>, bool> = true>
encode_helper(const T & val)14 std::vector<uint8_t> encode_helper(const T& val) {
15     auto begin = reinterpret_cast<const uint8_t*>(&val);
16     auto end = begin + sizeof(val);
17     return {begin, end};
18 }
19 
20 // Container type
21 template <typename Container,
22           std::enable_if_t<!std::is_trivially_copyable_v<Container>, bool> = true>
encode_helper(const Container & val)23 std::vector<uint8_t> encode_helper(const Container& val) {
24     // Check comment in decode_helper below
25     static_assert(std::is_trivially_copyable_v<typename Container::value_type>,
26                   "Can encode only a containers of trivial types currently");
27 
28     constexpr auto member_size = sizeof(typename Container::value_type);
29     auto n_bytes = member_size * val.size();
30 
31     std::vector<uint8_t> out(n_bytes);
32     std::memcpy(out.data(), val.data(), n_bytes);
33 
34     return out;
35 }
36 
37 // Trivial type
38 template <typename T, std::enable_if_t<std::is_trivially_copyable_v<T>, bool> = true>
decode_helper(const std::vector<uint8_t> & bytes)39 std::optional<T> decode_helper(const std::vector<uint8_t>& bytes) {
40     T t;
41 
42     if (sizeof(t) != bytes.size()) {
43         return {};
44     }
45 
46     std::memcpy(&t, bytes.data(), bytes.size());
47     return t;
48 }
49 
50 // Container type
51 template <typename Container,
52           std::enable_if_t<!std::is_trivially_copyable_v<Container>, bool> = true>
decode_helper(const std::vector<uint8_t> & bytes)53 std::optional<Container> decode_helper(const std::vector<uint8_t>& bytes) {
54     Container t;
55     size_t member_size = sizeof(typename Container::value_type);
56 
57     // NOTE: This can only reconstruct container of trivial types, not a
58     // container of non-trivial types. We can either use a standard serializer
59     // (like protobuf) or roll one of our own simple ones (like prepending size
60     // of the object), but have to be careful about securing such a serializer.
61     // But, do we even need that? I do not see any metadata which is either not
62     // trivial or a container of trivial type.
63     size_t to_copy = bytes.size();
64     if (to_copy % member_size != 0) {
65         return {};
66     }
67 
68     size_t members = to_copy / member_size;
69     t.resize(members);
70     std::memcpy(t.data(), bytes.data(), to_copy);
71     return t;
72 }
73 
74 } // namespace
75 
76 namespace pixel::graphics::utils {
77 
78 // TODO: Setup a fuzzer for encode/decode
79 template <typename T>
encode(const T & val)80 std::vector<uint8_t> encode(const T& val) {
81     return encode_helper(val);
82 }
83 
84 template <typename T>
decode(const std::vector<uint8_t> & bytes)85 std::optional<T> decode(const std::vector<uint8_t>& bytes) {
86     return decode_helper<T>(bytes);
87 }
88 
89 } // namespace pixel::graphics::utils
90