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