Serialization tools¶
Serializing data on the fly is very common in embedded systems. For example, if your microcontroller needs to communicate with an external device through an UART line, chances are this device requires a specific protocol to be implemented. Unpadded provides a special kind of tuple which will help you encode and decode packets.
Formatting packets with the upd::tuple
class¶
The upd::tuple
works a bit like std::tuple. It even specializes the std::tuple_element and std::tuple_size structure template in order to be compatible with structured bindings. However, the internal layout is widly different. upd::tuple
use an internal byte buffer to store its elements in an unaligned way. Therefore, it is not possible to get references to the elements of a upd::tuple
instance, only copies.
However, upd::tuple
is iterable like a container. When you iterate through it, it actually iterate the underlying the internal byte buffer. Therefore, do not use upd::tuple
as a mean of storing data, but like a flexible packet encoder and decoder.
Example¶
Let’s assume we need to communicate with an external device that use big endian and ones’ complement and follows this protocol:
Each packet start with a 4-byte long header
Packet to the device contains 2 32-bit unsigned integer right after the header
Packet from the device contains a 16-bit signed integer and a 16-bit unsigned integer right after the header
Finally, each packet ends with a 16-bit CRC
Assuming write_byte_to_device and read_byte_from_device are defined and work as their names suggest, the following code will allow our microcontroller to send a packet and wait for a response from the device before unserializing it.
constexpr upd::byte_t header[4] = {0xff, 0xff, 0xff, 0xff};
// We fill our tuple with the header and the data and keep
// some room for the CRC.
upd::tuple output_tuple{
upd::big_endian,
upd::ones_complement,
header,
std::uint32_t{64},
std::uint32_t{32},
std::uint16_t{}
};
// We calculate the CRC from the data fields
upd::set<3>(output_tuple, calculate_crc(output_tuple.begin() + 4, output_tuple.end() - 2));
// Finally we send the packet to the device
for (auto byte: output_tuple) write_byte_to_device(byte);
// We prepare the tuple for the reception of the response
upd::tuple<
upd::endianess::BIG,
upd::signed_mode::ONES_COMPLEMENT,
upd::byte_t[4],
std::int16_t,
std::uint16_t,
std::uint16_t>
input_tuple{};
// We receive the packet from the device (assuming
// `read_byte_from_device` did not get rid of the header)
for (auto &byte: input_tuple) byte = read_byte_from_device();
// Finally we handle the values from the remote device
auto &&[rcv_header, arg1, arg2, crc] = input_tuple;
assert(std::equal(rcv_header.begin(), rcv_header.end(), header));
assert(crc == calculate_crc(input_tuple.begin() + 4, input_tuple.end() - 2));
return arg1 + arg2;
Make a view of a part of a tuple or out of an existing buffer using upd::tuple_view
¶
In some case, having a view of a subtuple or interpreting a bare buffer as if it was a upd::tuple
content is useful as it prevents any copy from occuring. A upd::tuple_view
instance is either constructed from a buffer iterator or created from the upd::tuple::view()
function.
Warning
upd::tuple_view
will assume the end of the container it is associated with by incrementing the provided iterator according to the sizes of the elements. Therefore, this iterator might go past the real end iterator if the viewed container is not large enough.
Warning
upd::tuple_view
do not extend the lifetime of the container it is associated with.
Customization points: defining serialization processes for foreign types¶
Unpadded only handles primitive types and array of those. However, you can make any type serializable with upd_extension.
// Here we define our object, and we want to make it
// serializable.
struct object_t {
std::int16_t x, y;
std::uint32_t z;
};
// Thanks to the wonders of template specialization, we can
// define how our object must be serialized and unserialized.
template<>
struct upd_extension<object_t> {
// Here we define how `object_t` should be serialized by
// packing it into a tuple view. Do not worry about the
// exact type of the tuple view, the library will deduce
// it from the signature of `unserialize`.
template<typename View_T>
static void serialize(const object_t &o, View_T &view) {
upd::set<0>(view, o.x);
upd::set<1>(view, o.y);
upd::set<2>(view, o.z);
}
// Here we define how `object_t` should be unserialized
// from a pack of integers.
static object_t unserialize(std::int16_t x, std::int16_t y, std::uint32_t z) { return {x, y, z}; }
};
API References¶
tuple
¶
-
template<endianess Endianess, signed_mode Signed_Mode, typename ...Ts>
class tuple : public upd::detail::tuple_base<tuple<Endianess, Signed_Mode, Ts...>, Endianess, Signed_Mode, Ts...>¶ Unaligned storage tuple.
tuple instances hold values like a tuple but in an unaligned manner (i.e., there is no padding between two consecutive values).
- Template Parameters:
Endianess, Signed_Mode – Serialization parameters
Ts... – Types of the serialized values
Public Types
-
using arg_t = detail::at<types_t, I>¶
Type of one of the serialized values.
- Template Parameters:
I – Index of the requested type in
Ts...
Public Functions
-
inline tuple()¶
Initialize the internal storage with default constructed values.
-
inline explicit tuple(const Ts&... args)¶
Serialize the provided values.
- Parameters:
args... – Values to be serialized
-
inline explicit tuple(endianess_h<Endianess>, signed_mode_h<Signed_Mode>, const Ts&... values)¶
(C++17) Serialize the provided values
- Template Parameters:
Endianess, Signed_Mode – Serialization parameters
- Parameters:
values... – Values to be serialized
-
inline byte_t &operator[](std::size_t i)¶
Access the object content.
Warning
There is no bound check performed.
- Parameters:
i – Index of the accessed byte
-
inline const byte_t &operator[](std::size_t i) const¶
Access the object content.
Warning
There is no bound check performed.
- Parameters:
i – Index of the accessed byte
-
template<std::size_t I, std::size_t L>
inline auto view()¶ Make a view out of a subset of the tuple.
- Template Parameters:
I – First element in the view
L – Number of elements in the view
- Returns:
a tuple_view instance including every elements in the given range
-
template<std::size_t I, std::size_t L>
inline auto view() const¶ Make a view out of a subset of the tuple.
- Template Parameters:
I – First element in the view
L – Number of elements in the view
- Returns:
a tuple_view instance including every elements in the given range
-
auto get() const¶
Unserialize one of the value held by the object.
- Template Parameters:
I – Index of the requested value
- Returns:
A copy of the serialized value or a
std::array
instance ifI
designates an array type
-
inline void set(const arg_t<I> &value)¶
Set one of the value held by the object.
- Template Parameters:
I – Index of the value which will be set
- Parameters:
value – Value to be copied from
-
auto invoke(F &&ftor) const¶
Invoke a functor with the stored values.
- Parameters:
ftor – Callback to be invoked
- Returns:
ftor(upd::get<Is>()...)
withIs
=0
,1
, …,sizeof...(Ts)
Public Static Attributes
-
static constexpr auto size¶
Storage size in byte.
-
static constexpr auto storage_endianess¶
Equals the endianess given as template parameter.
-
static constexpr auto storage_signed_mode¶
Equals the signed mode given as template parameter.
-
template<typename ...Args, endianess Endianess, signed_mode Signed_Mode>
tuple<Endianess, Signed_Mode, Args...> make_tuple(endianess_h<Endianess>, signed_mode_h<Signed_Mode>)¶ Construct a tuple instance holding default values.
- Template Parameters:
Args... – Types of the values held by the tuple
Endianess, Signed_Mode – Serialization parameters
-
template<endianess Endianess, signed_mode Signed_Mode, typename ...Args>
tuple<Endianess, Signed_Mode, Args...> make_tuple(endianess_h<Endianess>, signed_mode_h<Signed_Mode>, const Args&... args)¶ Construct a tuple instance from provided values.
- Template Parameters:
Endianess, Signed_Mode – Serialization parameters
- Parameters:
args... – Values to be serialized into the return value
- Returns:
a tuple instance initialized from
args...
view_tuple
¶
-
template<typename It, endianess Endianess, signed_mode Signed_Mode, typename ...Ts>
class tuple_view : public upd::detail::tuple_base<tuple_view<It, Endianess, Signed_Mode, Ts...>, Endianess, Signed_Mode, Ts...>¶ Binds a byte sequence to a tuple view.
Once bound, the byte sequence content can be read and modified as it were the content of a tuple instance. The byte sequence and the tuple view are bound through an iterator. It must at least be a forward iterator, but tuple views are faster with random access iterators. Best case would be a non-volatile plain pointer, as it can be called with memcpy.
- Template Parameters:
It – Type of the iterator used for binding with the byte sequence
Endianess, Signed_Mode – Serialization parameters
Ts... – Types of the serialized values
Public Types
Public Functions
-
inline explicit tuple_view(const It &src)¶
Bind the view to a byte sequence through an iterator.
- Parameters:
src – Iterator to the start of the byte sequence
-
auto get() const¶
Unserialize one of the value held by the object.
- Template Parameters:
I – Index of the requested value
- Returns:
A copy of the serialized value or a
std::array
instance ifI
designates an array type
-
inline void set(const arg_t<I> &value)¶
Set one of the value held by the object.
- Template Parameters:
I – Index of the value which will be set
- Parameters:
value – Value to be copied from
-
auto invoke(F &&ftor) const¶
Invoke a functor with the stored values.
- Parameters:
ftor – Callback to be invoked
- Returns:
ftor(upd::get<Is>()...)
withIs
=0
,1
, …,sizeof...(Ts)
Public Static Attributes
-
static constexpr auto size¶
Storage size in byte.
-
static constexpr auto storage_endianess¶
Equals the endianess given as template parameter.
-
static constexpr auto storage_signed_mode¶
Equals the signed mode given as template parameter.
-
template<typename ...Ts, typename It, endianess Endianess, signed_mode Signed_Mode>
tuple_view<It, Endianess, Signed_Mode, Ts...> make_view(endianess_h<Endianess>, signed_mode_h<Signed_Mode>, const It &src)¶ Bind a byte sequence to a tuple view.
- Template Parameters:
Ts... – Types held by the tuple
Endianess – Target endianess for serialization
Signed_Mode – Target signed representation for serialization
- Parameters:
src – Start of the byte sequence
- Returns:
a tuple_view instance bound to the byte sequence
-
template<std::size_t I, std::size_t L, endianess Endianess, signed_mode Signed_Mode, typename ...Ts>
auto make_view(tuple<Endianess, Signed_Mode, Ts...> &tuple)¶ Bind a view to a slice of a tuple.
\upd_doc{MakeView_Tuple}
- Template Parameters:
I – First element in the view
L – Number of elements in the view
- Parameters:
tuple – Tuple to bind the view to
- Returns:
a tuple_view including every elements in the given range
get
¶
-
template<std::size_t I, typename D, endianess Endianess, signed_mode Signed_Mode, typename ...Ts>
auto upd::get(const detail::tuple_base<D, Endianess, Signed_Mode, Ts...> &t)¶ Call the member function
get()
from an Unpadded tuple-like instance.This function create a coherent interface with std::tuple for the sake of genericity.
- Parameters:
t – Tuple to get a value from
set
¶
-
template<std::size_t I, typename D, endianess Endianess, signed_mode Signed_Mode, typename ...Ts, typename U>
void upd::set(detail::tuple_base<D, Endianess, Signed_Mode, Ts...> &t, U &&value)¶ Call the member function
set()
from an Unpadded tuple-like instance.This function create a coherent interface std::tuple for the sake of genericity
- Parameters:
t – Tuple to set a value in
value – Value to set
Serialization parameters¶
-
enum class upd::endianess¶
Used to specify endianess for serialization and unserialization.
Values:
-
enumerator LITTLE¶
-
enumerator BIG¶
-
enumerator LITTLE¶
-
enum class upd::signed_mode¶
Used to specify signed integer representation for serialization and unserialization.
Values:
-
enumerator SIGNED_MAGNITUDE¶
-
enumerator ONES_COMPLEMENT¶
-
enumerator TWOS_COMPLEMENT¶
-
enumerator OFFSET_BINARY¶
-
enumerator SIGNED_MAGNITUDE¶
-
template<endianess Endianess>
struct endianess_h : public upd::unevaluated<endianess, Endianess>¶ endianess enumerator holder for named parameters
-
template<signed_mode Signed_Mode>
struct signed_mode_h : public upd::unevaluated<signed_mode, Signed_Mode>¶ signed_mode enumerator holder for named parameters
Other¶
-
using upd::byte_t = unsigned char¶
Portable type for inspecting object representation.
unsigned char being relevant to inspecting object representation regardless of the platform is enforced by the standard (Fundamental types - Character Types - cppreference).