diff --git a/docs/networking.rst b/docs/networking.rst index b16c2db1c..f80a561ae 100644 --- a/docs/networking.rst +++ b/docs/networking.rst @@ -43,9 +43,8 @@ checks explicitly for an explicit type. Be careful about multiple declarations. For this partial specialisation three static methods need to be defined. .. codeblock:: c++ - static inline std::vector serialise(const T& in) + static inline std::vector serialise(const T& in) - static inline T deserialise(const std::vector& in) + static inline T deserialise(const std::vector& in) static inline uint64_t hash() - diff --git a/src/PowerPlant.cpp b/src/PowerPlant.cpp index 61eb180b6..4e20eb7e7 100644 --- a/src/PowerPlant.cpp +++ b/src/PowerPlant.cpp @@ -24,7 +24,8 @@ namespace NUClear { -PowerPlant* PowerPlant::powerplant = nullptr; // NOLINT +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +PowerPlant* PowerPlant::powerplant = nullptr; PowerPlant::~PowerPlant() { // Make sure reactors are destroyed before anything else diff --git a/src/dsl/word/Network.hpp b/src/dsl/word/Network.hpp index d796b0699..e1377b015 100644 --- a/src/dsl/word/Network.hpp +++ b/src/dsl/word/Network.hpp @@ -96,7 +96,7 @@ namespace dsl { static inline std::tuple, NetworkData> get( const threading::Reaction& /*reaction*/) { - auto* data = store::ThreadStore>::value; + auto* data = store::ThreadStore>::value; auto* source = store::ThreadStore::value; if (data && source) { diff --git a/src/dsl/word/UDP.hpp b/src/dsl/word/UDP.hpp index 5c5d72ec9..2eb36a70f 100644 --- a/src/dsl/word/UDP.hpp +++ b/src/dsl/word/UDP.hpp @@ -91,7 +91,7 @@ namespace dsl { /// @brief If the packet is valid bool valid{false}; /// @brief The data that was received - std::vector payload{}; + std::vector payload{}; /// @brief The local address that the packet was received on util::network::sock_t local{}; /// @brief The remote address that the packet was received from @@ -121,7 +121,7 @@ namespace dsl { Target remote; /// @brief The data to be sent in the packet - std::vector payload{}; + std::vector payload{}; /** * @brief Casts this packet to a boolean to check if it is valid @@ -350,13 +350,13 @@ namespace dsl { } // Allocate max size for a UDP packet - std::vector buffer(65535, 0); + std::vector buffer(65535, 0); // Make some variables to hold our message header information std::array cmbuff = {0}; util::network::sock_t remote{}; iovec payload{}; - payload.iov_base = buffer.data(); + payload.iov_base = reinterpret_cast(buffer.data()); payload.iov_len = static_cast(buffer.size()); // Make our message header to receive with diff --git a/src/dsl/word/emit/Network.hpp b/src/dsl/word/emit/Network.hpp index f30a7d0ff..458c71bf2 100644 --- a/src/dsl/word/emit/Network.hpp +++ b/src/dsl/word/emit/Network.hpp @@ -39,7 +39,7 @@ namespace dsl { /// The hash identifying the type of object uint64_t hash{0}; /// The serialised data - std::vector payload{}; + std::vector payload{}; /// If the message should be sent reliably bool reliable{false}; }; diff --git a/src/dsl/word/emit/UDP.hpp b/src/dsl/word/emit/UDP.hpp index d6b116369..286bbf878 100644 --- a/src/dsl/word/emit/UDP.hpp +++ b/src/dsl/word/emit/UDP.hpp @@ -157,11 +157,11 @@ namespace dsl { } // Serialise to our payload - std::vector payload = util::serialise::Serialise::serialise(*data); + std::vector payload = util::serialise::Serialise::serialise(*data); // Try to send our payload if (::sendto(fd, - payload.data(), + reinterpret_cast(payload.data()), static_cast(payload.size()), 0, &remote.sock, diff --git a/src/extension/NetworkController.hpp b/src/extension/NetworkController.hpp index 32b3213b4..30fff7baa 100644 --- a/src/extension/NetworkController.hpp +++ b/src/extension/NetworkController.hpp @@ -51,15 +51,15 @@ namespace extension { network.set_packet_callback([this](const network::NUClearNetwork::NetworkTarget& remote, const uint64_t& hash, const bool& reliable, - std::vector&& payload) { + std::vector&& payload) { // Construct our NetworkSource information dsl::word::NetworkSource src{remote.name, remote.target, reliable}; // Move the payload in as we are stealing it - std::vector p(std::move(payload)); + std::vector p(std::move(payload)); // Store in our thread local cache - dsl::store::ThreadStore>::value = &p; + dsl::store::ThreadStore>::value = &p; dsl::store::ThreadStore::value = &src; /* Mutex Scope */ { @@ -76,7 +76,7 @@ namespace extension { } // Clear our cache - dsl::store::ThreadStore>::value = nullptr; + dsl::store::ThreadStore>::value = nullptr; dsl::store::ThreadStore::value = nullptr; }); diff --git a/src/extension/network/NUClearNetwork.cpp b/src/extension/network/NUClearNetwork.cpp index 5aa6796a7..4a49886a1 100644 --- a/src/extension/network/NUClearNetwork.cpp +++ b/src/extension/network/NUClearNetwork.cpp @@ -46,12 +46,12 @@ namespace extension { * * @return the data and who it was sent from */ - std::pair> read_socket(fd_t fd) { + std::pair> read_socket(fd_t fd) { // Allocate a vector that can hold a datagram - std::vector payload(1500); + std::vector payload(1500); iovec iov{}; - iov.iov_base = payload.data(); + iov.iov_base = reinterpret_cast(payload.data()); iov.iov_len = static_cast(payload.size()); // Who we are receiving from @@ -82,7 +82,7 @@ namespace extension { } void NUClearNetwork::set_packet_callback( - std::function&&)> f) { + std::function&&)> f) { packet_callback = std::move(f); } @@ -534,7 +534,7 @@ namespace extension { // Send the packet if (::sendto(data_fd, - announce_packet.data(), + reinterpret_cast(announce_packet.data()), static_cast(announce_packet.size()), 0, &it->second->target.sock, @@ -547,11 +547,11 @@ namespace extension { } } - void NUClearNetwork::process_packet(const sock_t& address, std::vector&& payload) { + void NUClearNetwork::process_packet(const sock_t& address, std::vector&& payload) { // First validate this is a NUClear network packet we can read (a version 2 NUClear packet) - if (payload.size() >= sizeof(PacketHeader) && payload[0] == '\xE2' && payload[1] == '\x98' - && payload[2] == '\xA2' && payload[3] == 0x02) { + if (payload.size() >= sizeof(PacketHeader) && payload[0] == 0xE2 && payload[1] == 0x98 && payload[2] == 0xA2 + && payload[3] == 0x02) { // This is a real packet! get our header information const PacketHeader& header = *reinterpret_cast(payload.data()); @@ -595,7 +595,7 @@ namespace extension { // Say hi back! ::sendto(data_fd, - announce_packet.data(), + reinterpret_cast(announce_packet.data()), static_cast(announce_packet.size()), 0, &ptr->target.sock, @@ -671,7 +671,7 @@ namespace extension { if (it != remote->recent_packets.end() && packet.reliable) { // Allocate room for the whole ack packet - std::vector r(sizeof(ACKPacket) + (packet.packet_count / 8), 0); + std::vector r(sizeof(ACKPacket) + (packet.packet_count / 8), 0); ACKPacket& response = *reinterpret_cast(r.data()); response = ACKPacket(); response.packet_id = packet.packet_id; @@ -688,7 +688,7 @@ namespace extension { // Send the packet ::sendto(data_fd, - r.data(), + reinterpret_cast(r.data()), static_cast(r.size()), 0, &to.sock, @@ -703,8 +703,8 @@ namespace extension { if (packet.packet_count == 1) { // Copy our data into a vector - std::vector out(&packet.data, - &packet.data + payload.size() - sizeof(DataPacket) + 1); + std::vector out(&packet.data, + &packet.data + payload.size() - sizeof(DataPacket) + 1); // If this is a reliable packet, send an ack back if (packet.reliable) { @@ -751,7 +751,7 @@ namespace extension { // A basic ack has room for 8 packets and we need 1 extra byte for each 8 // additional packets - std::vector r(sizeof(NACKPacket) + (packet.packet_count / 8), 0); + std::vector r(sizeof(NACKPacket) + (packet.packet_count / 8), 0); NACKPacket& response = *reinterpret_cast(r.data()); response = NACKPacket(); response.packet_id = packet.packet_id; @@ -771,7 +771,7 @@ namespace extension { // Send the packet ::sendto(data_fd, - r.data(), + reinterpret_cast(r.data()), static_cast(r.size()), 0, &to.sock, @@ -790,7 +790,7 @@ namespace extension { if (packet.reliable) { // A basic ack has room for 8 packets and we need 1 extra byte for each 8 // additional packets - std::vector r(sizeof(ACKPacket) + (packet.packet_count / 8), 0); + std::vector r(sizeof(ACKPacket) + (packet.packet_count / 8), 0); ACKPacket& response = *reinterpret_cast(r.data()); response = ACKPacket(); response.packet_id = packet.packet_id; @@ -807,7 +807,7 @@ namespace extension { // Send the packet ::sendto(data_fd, - r.data(), + reinterpret_cast(r.data()), static_cast(r.size()), 0, &to.sock, @@ -825,7 +825,7 @@ namespace extension { } // Read in our data - std::vector out; + std::vector out; out.reserve(payload_size); for (auto& p : assembler.second) { const DataPacket& part = *reinterpret_cast(p.second.data()); @@ -997,7 +997,7 @@ namespace extension { void NUClearNetwork::send_packet(const sock_t& target, NUClear::extension::network::DataPacket header, uint16_t packet_no, - const std::vector& payload, + const std::vector& payload, const bool& /*reliable*/) { // Our packet we are sending @@ -1012,13 +1012,14 @@ namespace extension { data[0].iov_base = reinterpret_cast(&header); data[0].iov_len = sizeof(DataPacket) - 1; - // Work out what chunk of data we are sending const cast is fine as posix guarantees it won't be - // modified - data[1].iov_base = const_cast(payload.data() + (packet_data_mtu * packet_no)); // NOLINT - data[1].iov_len = packet_no + 1 < header.packet_count ? packet_data_mtu : payload.size() % packet_data_mtu; + // Work out what chunk of data we are sending + // const cast is fine as posix guarantees it won't be modified on a sendmsg + const char* start = reinterpret_cast(payload.data()) + (packet_no * packet_data_mtu); + data[1].iov_base = const_cast(start); // NOLINT(cppcoreguidelines-pro-type-const-cast) + data[1].iov_len = packet_no + 1 < header.packet_count ? packet_data_mtu : payload.size() % packet_data_mtu; // Set our target and send (once again const cast is fine) - message.msg_name = const_cast(&target.sock); // NOLINT + message.msg_name = const_cast(&target.sock); // NOLINT(cppcoreguidelines-pro-type-const-cast) message.msg_namelen = target.size(); // TODO(trent): if reliable, run select first to see if this socket is writeable @@ -1028,7 +1029,7 @@ namespace extension { void NUClearNetwork::send(const uint64_t& hash, - const std::vector& payload, + const std::vector& payload, const std::string& target, bool reliable) { diff --git a/src/extension/network/NUClearNetwork.hpp b/src/extension/network/NUClearNetwork.hpp index 0f46bfe21..fc3d93656 100644 --- a/src/extension/network/NUClearNetwork.hpp +++ b/src/extension/network/NUClearNetwork.hpp @@ -75,7 +75,7 @@ namespace extension { std::mutex assemblers_mutex; /// Storage for fragmented packets while we build them std::map>>> + std::pair>>> assemblers{}; /// Struct storing the kalman filter for round trip time @@ -130,7 +130,7 @@ namespace extension { * @param target who we are sending to (blank means everyone) * @param reliable if the delivery of the data should be ensured */ - void send(const uint64_t& hash, const std::vector& payload, const std::string& target, bool reliable); + void send(const uint64_t& hash, const std::vector& payload, const std::string& target, bool reliable); /** * @brief Set the callback to use when a data packet is completed @@ -138,7 +138,7 @@ namespace extension { * @param f the callback function */ void set_packet_callback( - std::function&&)> f); + std::function&&)> f); /** * @brief Set the callback to use when a node joins the network @@ -231,7 +231,7 @@ namespace extension { DataPacket header{}; /// The data to send - std::vector payload{}; + std::vector payload{}; }; /** @@ -255,7 +255,7 @@ namespace extension { * @param address who the packet came from * @param data the data that was sent in this packet */ - void process_packet(const sock_t& address, std::vector&& payload); + void process_packet(const sock_t& address, std::vector&& payload); /** * @brief Send an announce packet to our announce address @@ -279,7 +279,7 @@ namespace extension { void send_packet(const sock_t& target, DataPacket header, uint16_t packet_no, - const std::vector& payload, + const std::vector& payload, const bool& reliable); /** @@ -307,13 +307,13 @@ namespace extension { uint16_t packet_data_mtu{1000}; // Our announce packet - std::vector announce_packet{}; + std::vector announce_packet{}; /// An atomic source for packet IDs to make sure they are semi unique std::atomic packet_id_source{0}; /// The callback to execute when a data packet is completed - std::function&&)> + std::function&&)> packet_callback; /// The callback to execute when a node joins the network std::function join_callback; diff --git a/src/id.hpp b/src/id.hpp index 64a33759e..318f4d896 100644 --- a/src/id.hpp +++ b/src/id.hpp @@ -27,9 +27,7 @@ namespace NUClear { -/** - * @brief A unique identifier for a thread pool - */ +/// @brief This type is used when NUClear requires a unique identifier using id_t = std::size_t; } // namespace NUClear diff --git a/src/threading/ReactionTask.hpp b/src/threading/ReactionTask.hpp index 25260e6ad..765c58cb7 100644 --- a/src/threading/ReactionTask.hpp +++ b/src/threading/ReactionTask.hpp @@ -28,6 +28,7 @@ #include #include #include + #include "../id.hpp" #include "../message/ReactionStatistics.hpp" #include "../util/GroupDescriptor.hpp" @@ -151,11 +152,13 @@ namespace threading { // Initialize our id source template - std::atomic Task::task_id_source(0); // NOLINT + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + std::atomic Task::task_id_source(0); // Initialize our current task template - ATTRIBUTE_TLS Task* Task::current_task = nullptr; // NOLINT + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + ATTRIBUTE_TLS Task* Task::current_task = nullptr; // Alias the templated Task so that public API remains intact class Reaction; diff --git a/src/util/serialise/Serialise.hpp b/src/util/serialise/Serialise.hpp index 16c74e0a0..8611b8334 100644 --- a/src/util/serialise/Serialise.hpp +++ b/src/util/serialise/Serialise.hpp @@ -23,8 +23,13 @@ #ifndef NUCLEAR_UTIL_SERIALISE_SERIALISE_HPP #define NUCLEAR_UTIL_SERIALISE_SERIALISE_HPP +#include +#include +#include #include #include +#include +#include #include "../demangle.hpp" #include "xxhash.hpp" @@ -46,22 +51,21 @@ namespace util { template struct Serialise; - // Plain old data + // Trivially copyable data template - struct Serialise::value, T>> { + struct Serialise::value, T>> { - static inline std::vector serialise(const T& in) { - - // Copy the bytes into an array - const auto* dataptr = reinterpret_cast(&in); - return {dataptr, dataptr + sizeof(T)}; + static inline std::vector serialise(const T& in) { + std::vector out(sizeof(T)); + std::memcpy(out.data(), &in, sizeof(T)); + return out; } - static inline T deserialise(const std::vector& in) { - - // Copy the data into an object of the correct type - T ret = *reinterpret_cast(in.data()); - return ret; + static inline T deserialise(const std::vector& in) { + if (in.size() != sizeof(T)) { + throw std::length_error("Serialised data is not the correct size"); + } + return *reinterpret_cast(in.data()); } static inline uint64_t hash() { @@ -72,22 +76,20 @@ namespace util { } }; - // Iterable of pod + // Iterable of trivially copyable data template - struct Serialise< - T, - std::enable_if_t< - std::is_same().begin())>::iterator_category, - std::random_access_iterator_tag>::value, - T>> { + using iterator_value_type_t = + typename std::iterator_traits()))>::value_type; + template + struct Serialise>::value, T>> { - using StoredType = std::remove_reference_t().begin())>; + using V = std::remove_reference_t>; - static inline std::vector serialise(const T& in) { - std::vector out; - out.reserve(std::size_t(std::distance(in.begin(), in.end()))); + static inline std::vector serialise(const T& in) { + std::vector out; + out.reserve(sizeof(V) * size_t(std::distance(std::begin(in), std::end(in)))); - for (const StoredType& item : in) { + for (const V& item : in) { const char* i = reinterpret_cast(&item); out.insert(out.end(), i, i + sizeof(decltype(item))); } @@ -95,13 +97,17 @@ namespace util { return out; } - static inline T deserialise(const std::vector& in) { + static inline T deserialise(const std::vector& in) { + + if (in.size() % sizeof(V) != 0) { + throw std::length_error("Serialised data is not the correct size"); + } T out; - const auto* data = reinterpret_cast(in.data()); + const auto* data = reinterpret_cast(in.data()); - out.insert(out.end(), data, data + (in.size() / sizeof(StoredType))); + out.insert(out.end(), data, data + (in.size() / sizeof(V))); return out; } @@ -121,14 +127,14 @@ namespace util { || std::is_base_of<::google::protobuf::MessageLite, T>::value, T>> { - static inline std::vector serialise(const T& in) { - std::vector output(in.ByteSize()); + static inline std::vector serialise(const T& in) { + std::vector output(in.ByteSize()); in.SerializeToArray(output.data(), output.size()); return output; } - static inline T deserialise(const std::vector& in) { + static inline T deserialise(const std::vector& in) { // Make a buffer T out; diff --git a/tests/dsl/UDP.cpp b/tests/dsl/UDP.cpp index 8d2f6c8ac..863fd5eb0 100644 --- a/tests/dsl/UDP.cpp +++ b/tests/dsl/UDP.cpp @@ -165,7 +165,7 @@ struct Finished { class TestReactor : public test_util::TestBase { private: void handle_data(const std::string& name, const UDP::Packet& packet) { - const std::string data(packet.payload.data(), packet.payload.size()); + const std::string data(packet.payload.begin(), packet.payload.end()); // Convert IP address to string in dotted decimal format const std::string local = packet.local.address + ":" + std::to_string(packet.local.port); diff --git a/tests/util/serialise/serialise.cpp b/tests/util/serialise/serialise.cpp new file mode 100644 index 000000000..57afa8360 --- /dev/null +++ b/tests/util/serialise/serialise.cpp @@ -0,0 +1,338 @@ +/* + * MIT License + * + * Copyright (c) 2015 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "util/serialise/Serialise.hpp" + +#include +#include +#include +#include +#include + +SCENARIO("Serialisation works correctly on single primitives", "[util][serialise][single][primitive]") { + + GIVEN("a primitive value") { + const uint32_t in = 0xCAFEFECA; // Mirrored so that endianess doesn't matter for the test + + WHEN("it is serialised") { + const auto serialised = NUClear::util::serialise::Serialise::serialise(in); + + THEN("The serialised data is as expected") { + REQUIRE(serialised.size() == sizeof(uint32_t)); + REQUIRE(serialised == std::vector{0xCA, 0xFE, 0xFE, 0xCA}); + } + } + + WHEN("it is round tripped through the serialise and deserialise functions") { + const auto serialised = NUClear::util::serialise::Serialise::serialise(in); + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(serialised); + + THEN("The deserialised data is the same as the input") { + REQUIRE(deserialised == in); + } + } + } + + GIVEN("serialised data for a primitive value") { + const std::vector in = {0xCA, 0xFE, 0xFE, 0xCA}; + + WHEN("it is deserialised") { + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(in); + + THEN("The deserialised data is as expected") { + REQUIRE(deserialised == 0xCAFEFECA); + } + } + + WHEN("it is round tripped through the deserialise and serialise functions") { + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(in); + const auto serialised = NUClear::util::serialise::Serialise::serialise(deserialised); + + THEN("The serialised data is the same as the input") { + REQUIRE(serialised == in); + } + } + } + + GIVEN("serialised data that is too small") { + const std::vector in = {0xBA, 0xAD, 0xBA}; + + WHEN("it is deserialised") { + THEN("The deserialise function throws an exception") { + REQUIRE_THROWS_AS(NUClear::util::serialise::Serialise::deserialise(in), std::length_error); + } + } + } + + GIVEN("serialised data that is too large") { + const std::vector in = {0xBA, 0xDB, 0xAD, 0xBA, 0xDB}; + + WHEN("it is deserialised") { + THEN("The deserialise function throws an exception") { + REQUIRE_THROWS_AS(NUClear::util::serialise::Serialise::deserialise(in), std::length_error); + } + } + } +} + +TEMPLATE_TEST_CASE("Scenario: Serialisation works correctly on iterables of primitives", + "[util][serialise][multiple][primitive]", + std::vector, + std::list) { + + GIVEN("a vector of primitive values") { + const TestType in = {0xABBABAAB, 0xDEADADDE, 0xCAFEFECA, 0xBEEFEFBE}; + + WHEN("it is serialised") { + const auto serialised = NUClear::util::serialise::Serialise::serialise(in); + + THEN("The serialised data is as expected") { + const std::vector expected = + {0xAB, 0xBA, 0xBA, 0xAB, 0xDE, 0xAD, 0xAD, 0xDE, 0xCA, 0xFE, 0xFE, 0xCA, 0xBE, 0xEF, 0xEF, 0xBE}; + REQUIRE(serialised == expected); + } + } + + WHEN("it is round tripped through the serialise and deserialise functions") { + const auto serialised = NUClear::util::serialise::Serialise::serialise(in); + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(serialised); + + THEN("The deserialised data is the same as the input") { + REQUIRE(deserialised == in); + } + } + } + + GIVEN("serialised data for multiple primitives") { + const std::vector in = + {0xBE, 0xEF, 0xEF, 0xBE, 0xAB, 0xBA, 0xBA, 0xAB, 0xDE, 0xAD, 0xAD, 0xDE, 0xCA, 0xFE, 0xFE, 0xCA}; + + WHEN("it is deserialised") { + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(in); + + THEN("The deserialised data is as expected") { + REQUIRE(deserialised.size() == 4); + REQUIRE(*std::next(deserialised.begin(), 0) == 0xBEEFEFBE); + REQUIRE(*std::next(deserialised.begin(), 1) == 0xABBABAAB); + REQUIRE(*std::next(deserialised.begin(), 2) == 0xDEADADDE); + REQUIRE(*std::next(deserialised.begin(), 3) == 0xCAFEFECA); + } + } + + WHEN("it is round tripped through the deserialise and serialise functions") { + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(in); + const auto serialised = NUClear::util::serialise::Serialise::serialise(deserialised); + + THEN("The serialised data is the same as the input") { + REQUIRE(serialised == in); + } + } + } + + GIVEN("serialised data that does not divide evenly into the size") { + const std::vector in = {0xBA, 0xAD, 0xBA, 0xBA, 0xAD, 0xBA}; + + WHEN("it is deserialised") { + THEN("The deserialise function throws an exception") { + REQUIRE_THROWS_AS(NUClear::util::serialise::Serialise::deserialise(in), std::length_error); + } + } + } + + GIVEN("empty serialised data") { + const std::vector in{}; + + WHEN("it is deserialised") { + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(in); + + THEN("The deserialised data is empty") { + REQUIRE(deserialised.empty()); + } + } + } +} + +namespace { + +struct TriviallyCopyable { + uint8_t a; + int8_t b; + std::array c; + + bool operator==(const TriviallyCopyable& rhs) const { + return a == rhs.a && b == rhs.b && c[0] == rhs.c[0] && c[1] == rhs.c[1]; + } +}; +static_assert(std::is_trivially_copyable::value, "This type should be trivially copyable"); + +} // namespace + +SCENARIO("Serialisation works correctly on single trivially copyable types", "[util][serialise][single][trivial]") { + + GIVEN("a trivially copyable value") { + const TriviallyCopyable in = {0xFF, -1, {0xDE, 0xAD}}; + + WHEN("it is serialised") { + const auto serialised = NUClear::util::serialise::Serialise::serialise(in); + + THEN("The serialised data is as expected") { + REQUIRE(serialised.size() == sizeof(TriviallyCopyable)); + REQUIRE(serialised == std::vector({0xFF, 0xFF, 0xDE, 0xAD})); + } + } + + WHEN("it is round tripped through the serialise and deserialise functions") { + const auto serialised = NUClear::util::serialise::Serialise::serialise(in); + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(serialised); + + THEN("The deserialised data is the same as the input") { + REQUIRE(deserialised.a == in.a); + REQUIRE(deserialised.b == in.b); + REQUIRE(deserialised.c[0] == in.c[0]); + REQUIRE(deserialised.c[1] == in.c[1]); + } + } + } + + GIVEN("serialised data for a primitive value") { + const std::vector in = {0xCA, 0xFE, 0xFE, 0xCA}; + + WHEN("it is deserialised") { + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(in); + + THEN("The deserialised data is as expected") { + REQUIRE(deserialised.a == 0xCA); + REQUIRE(deserialised.b == -0x02); + REQUIRE(deserialised.c[0] == 0xFE); + REQUIRE(deserialised.c[1] == 0xCA); + } + } + + WHEN("it is round tripped through the deserialise and serialise functions") { + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(in); + const auto serialised = NUClear::util::serialise::Serialise::serialise(deserialised); + + THEN("The serialised data is the same as the input") { + REQUIRE(serialised == in); + } + } + } + + GIVEN("serialised data that is too small") { + const std::vector in = {0xCA, 0xFE, 0xFE}; + + WHEN("it is deserialised") { + THEN("The deserialise function throws an exception") { + REQUIRE_THROWS_AS(NUClear::util::serialise::Serialise::deserialise(in), + std::length_error); + } + } + } + + GIVEN("serialised data that is too large") { + const std::vector in = {0xCA, 0xFE, 0xFE, 0xCA, 0xFE, 0xFE}; + + WHEN("it is deserialised") { + THEN("The deserialise function throws an exception") { + REQUIRE_THROWS_AS(NUClear::util::serialise::Serialise::deserialise(in), + std::length_error); + } + } + } +} + + +TEMPLATE_TEST_CASE("Scenario: Serialisation works correctly on iterables of trivially copyable types", + "[util][serialise][multiple][trivial]", + std::vector, + std::list) { + + GIVEN("a vector of trivial values") { + const TestType in = {{'h', 'e', {'l', 'o'}}, {'w', 'o', {'r', 'd'}}}; + + WHEN("it is serialised") { + const auto serialised = NUClear::util::serialise::Serialise::serialise(in); + + THEN("The serialised data is as expected") { + auto s = std::string(serialised.begin(), serialised.end()); + REQUIRE(serialised.size() == sizeof(uint32_t) * in.size()); + REQUIRE(s == "heloword"); + } + } + + WHEN("it is round tripped through the serialise and deserialise functions") { + const auto serialised = NUClear::util::serialise::Serialise::serialise(in); + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(serialised); + + THEN("The deserialised data is the same as the input") { + REQUIRE(deserialised == in); + } + } + } + + GIVEN("serialised data for multiple trivials") { + const std::string in_s = "Hello World!"; + const std::vector in(in_s.begin(), in_s.end()); + + WHEN("it is deserialised") { + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(in); + + THEN("The deserialised data is as expected") { + REQUIRE(deserialised.size() == 3); + REQUIRE(*std::next(deserialised.begin(), 0) == TriviallyCopyable{'H', 'e', {'l', 'l'}}); + REQUIRE(*std::next(deserialised.begin(), 1) == TriviallyCopyable{'o', ' ', {'W', 'o'}}); + REQUIRE(*std::next(deserialised.begin(), 2) == TriviallyCopyable{'r', 'l', {'d', '!'}}); + } + } + + WHEN("it is round tripped through the deserialise and serialise functions") { + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(in); + const auto serialised = NUClear::util::serialise::Serialise::serialise(deserialised); + + THEN("The serialised data is the same as the input") { + REQUIRE(serialised == in); + } + } + } + + GIVEN("serialised data that does not divide evenly into the size") { + const std::vector in = {0xBA, 0xAD, 0xBA, 0xBA, 0xAD, 0xBA, 0xBA, 0xAD, 0xBA}; + + WHEN("it is deserialised") { + THEN("The deserialise function throws an exception") { + REQUIRE_THROWS_AS(NUClear::util::serialise::Serialise::deserialise(in), std::length_error); + } + } + } + + GIVEN("empty serialised data") { + const std::vector in; + + WHEN("it is deserialised") { + const auto deserialised = NUClear::util::serialise::Serialise::deserialise(in); + + THEN("The deserialised data is empty") { + REQUIRE(deserialised.empty()); + } + } + } +}