From cf2584b07dbf09c9b616a286aa061e87e51c698a Mon Sep 17 00:00:00 2001 From: Stuart Miller Date: Fri, 1 Dec 2023 09:38:42 -0600 Subject: [PATCH 01/29] Conditionally remove support for std::filesystem on systems that don't support it. --- include/mav/MessageSet.h | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/include/mav/MessageSet.h b/include/mav/MessageSet.h index 54c90da..b87da1c 100644 --- a/include/mav/MessageSet.h +++ b/include/mav/MessageSet.h @@ -35,7 +35,6 @@ #define MAV_MESSAGESET_H #include -#include #include #include @@ -45,6 +44,15 @@ #include "rapidxml/rapidxml.hpp" #include "rapidxml/rapidxml_utils.hpp" +#ifdef _LIBCPP_VERSION +#if _LIBCPP_VERSION < 11000 +#define _NO_STD_FILESYSTEM +#endif +#endif +#ifndef _NO_STD_FILESYSTEM +#include +#endif + namespace mav { class XMLParser { @@ -104,7 +112,7 @@ namespace mav { } public: - +#ifndef _NO_STD_FILESYSTEM static XMLParser forFile(const std::string &file_name) { auto file = std::make_shared>(file_name.c_str()); auto doc = std::make_shared>(); @@ -112,6 +120,7 @@ namespace mav { return {file, doc, std::filesystem::path{file_name}.parent_path().string()}; } +#endif // _NO_STD_FILESYSTEM static XMLParser forXMLString(const std::string &xml_string) { // pass by value on purpose, rapidxml mutates the string on parse @@ -131,7 +140,7 @@ namespace mav { if (!root_node) { throw std::runtime_error("Root node \"mavlink\" not found"); } - +#ifndef _NO_STD_FILESYSTEM for (auto include_element = root_node->first_node("include"); include_element != nullptr; include_element = include_element->next_sibling("include")) { @@ -141,6 +150,7 @@ namespace mav { (std::filesystem::path{_root_xml_folder_path} / include_name).string()); sub_parser.parse(out_enum, out_messages, out_message_ids); } +#endif // _NO_STD_FILESYSTEM auto enums_node = root_node->first_node("enums"); if (enums_node) { @@ -215,6 +225,7 @@ namespace mav { public: MessageSet() = default; +#ifndef _NO_STD_FILESYSTEM explicit MessageSet(const std::string &xml_path) { addFromXML(xml_path); } @@ -223,6 +234,7 @@ namespace mav { XMLParser parser = XMLParser::forFile(file_path); parser.parse(_enums, _messages, _message_ids); } +#endif // _NO_STD_FILESYSTEM void addFromXMLString(const std::string &xml_string) { XMLParser parser = XMLParser::forXMLString(xml_string); From 1a705fd496b0cdbca7a8c4b0d8d86332695fb6e5 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Tue, 5 Dec 2023 17:07:07 +0100 Subject: [PATCH 02/29] connection: allow arbitrary functional selector for expectation --- include/mav/Connection.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/include/mav/Connection.h b/include/mav/Connection.h index e03c1f1..fc2aa72 100644 --- a/include/mav/Connection.h +++ b/include/mav/Connection.h @@ -63,9 +63,7 @@ namespace mav { struct PromiseCallback { Expectation promise; - int message_id; - int system_id; - int component_id; + std::function selector; }; using Callback = std::variant; @@ -123,9 +121,7 @@ namespace mav { } it++; } else if constexpr (std::is_same_v) { - if (message.id() == arg.message_id && - (arg.system_id == mav::ANY_ID || message.header().systemId() == arg.system_id) && - (arg.component_id == mav::ANY_ID || message.header().componentId() == arg.component_id)) { + if (arg.selector(message)) { arg.promise->set_value(message); it = _message_callbacks.erase(it); } else { @@ -199,20 +195,24 @@ namespace mav { _message_callbacks.erase(handle); } - - [[nodiscard]] Expectation expect(int message_id, int source_id=mav::ANY_ID, - int component_id=mav::ANY_ID) { - + [[nodiscard]] Expectation expect(std::function selector) { auto promise = std::make_shared>(); std::scoped_lock lock(_message_callback_mtx); CallbackHandle handle = _next_handle; - _message_callbacks[handle] = PromiseCallback{promise, message_id, source_id, component_id}; + _message_callbacks[handle] = PromiseCallback{promise, std::move(selector)}; _next_handle++; - - auto prom = std::make_shared>(); return promise; } + [[nodiscard]] Expectation expect(int message_id, int source_id=mav::ANY_ID, + int component_id=mav::ANY_ID) { + return expect([message_id, source_id, component_id](const Message &message) { + return message.id() == message_id && + (source_id == mav::ANY_ID || message.header().systemId() == source_id) && + (component_id == mav::ANY_ID || message.header().componentId() == component_id); + }); + } + [[nodiscard]] inline Expectation expect(const std::string &message_name, int source_id=mav::ANY_ID, int component_id=mav::ANY_ID) { return expect(_message_set.idForMessage(message_name), source_id, component_id); From b11c0637fe91f3b258bbfa37a03470710667a8a8 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Tue, 5 Dec 2023 17:13:30 +0100 Subject: [PATCH 03/29] connection: allow arbitrary functional selector for receive --- include/mav/Connection.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/mav/Connection.h b/include/mav/Connection.h index fc2aa72..bc3af7e 100644 --- a/include/mav/Connection.h +++ b/include/mav/Connection.h @@ -250,6 +250,10 @@ namespace mav { Message inline receive(int message_id, int timeout_ms=-1) { return receive(message_id, mav::ANY_ID, mav::ANY_ID, timeout_ms); } + + Message inline receive(std::function selector, int timeout_ms=-1) { + return receive(expect(std::move(selector)), timeout_ms); + } }; }; From 226af4fbdd91b7ae3b5e5b26b977530cc4acd27d Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Mon, 11 Dec 2023 15:48:14 +0100 Subject: [PATCH 04/29] ci: add sonarcloud integration --- .github/workflows/test.yml | 38 ++++++++++++++++++++++++++++++++------ sonar-project.properties | 13 +++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 sonar-project.properties diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8098c43..9840137 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,12 +8,38 @@ jobs: strategy: fail-fast: false matrix: - platform: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.platform }} + platform: + - runner: ubuntu-latest + sonarcloud-build-wrapper : build-wrapper-linux-x86-64 + - runner: macos-latest + sonarcloud-build-wrapper : build-wrapper-macosx-x86 + runs-on: ${{ matrix.platform.runner }} steps: - uses: actions/checkout@v3 + + - name: Install gcovr + run: pip3 install gcovr + + - name: Setup sonar cloud + uses: SonarSource/sonarcloud-github-c-cpp@v2.0.2 + + - name: Build tests + run: cmake -B build -S . + - name: Build tests - run: mkdir build && cd build && cmake .. && make tests - - name: Run tests - run: ./tests/tests - working-directory: build + run: ${{ matrix.platform.sonarcloud-build-wrapper }} --out-dir bw-output cmake --build build + + - name: Run tests and coverage + run: ./build/tests/tests + + - name: Generate coverage report + run: gcovr --sonarqube -o coverage.xml build + + - name: Run sonar-scanner + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.AUTERION_CI_SONAR_TOKEN }} + run: | + sonar-scanner \ + --define sonar.cfamily.build-wrapper-output="bw-output" \ + --define sonar.coverageReportPaths=coverage.xml diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..0cc9bb2 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,13 @@ +sonar.projectKey = Auterion_libmav +sonar.organization = auterion + +sonar.projectName = libmav + +sonar.sources = include/,tests/ + +sonar.exclusions = include/mav/rapidxml/,tests/doctest.h + +sonar.coverage.exclusions = tests/**/* +sonar.cpd.exclusions = tests/**/* + +sonar.sourceEncoding = UTF-8 From 04fe4e760d51dc9bdc1d16257a65edd52caeaa39 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Mon, 11 Dec 2023 18:13:31 +0100 Subject: [PATCH 05/29] utils: use std::copy for pack / unpack --- include/mav/utils.h | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/include/mav/utils.h b/include/mav/utils.h index 65736c1..f18a39e 100644 --- a/include/mav/utils.h +++ b/include/mav/utils.h @@ -203,25 +203,25 @@ namespace mav { } - template - A _packUnpack(B b) { - union U { - A a; - B b; - }; - U u; - u.b = b; - return u.a; + template + To _packUnpack(From o) { + static_assert(sizeof(To) == sizeof(From), "Cannot pack/unpack different sizes"); + static_assert(std::is_trivially_copyable::value, "Cannot pack/unpack non-trivially copyable types"); + static_assert(std::is_trivially_copyable::value, "Cannot pack/unpack non-trivially copyable types"); + To result; + std::memcpy(&result, &o, sizeof(To)); + return result; } + - template - T floatUnpack(float f) { - return _packUnpack(f); + template + To floatUnpack(float f) { + return _packUnpack(f); } - template - float floatPack(T o) { - return _packUnpack(o); + template + float floatPack(From o) { + return _packUnpack(o); } From c1361495ec4e00332d7462736bc9752f999f8e95 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Mon, 11 Dec 2023 18:20:26 +0100 Subject: [PATCH 06/29] Message: initialize all fields in constructor --- include/mav/Message.h | 5 ++++- include/mav/utils.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/mav/Message.h b/include/mav/Message.h index d458468..e2e1944 100644 --- a/include/mav/Message.h +++ b/include/mav/Message.h @@ -82,7 +82,10 @@ namespace mav { int _crc_offset = -1; explicit Message(const MessageDefinition &message_definition) : - _message_definition(&message_definition) { + _source_partner({}), + _backing_memory({}), + _message_definition(&message_definition), + _crc_offset(-1) { } Message(const MessageDefinition &message_definition, ConnectionPartner source_partner, int crc_offset, diff --git a/include/mav/utils.h b/include/mav/utils.h index f18a39e..ffd3d57 100644 --- a/include/mav/utils.h +++ b/include/mav/utils.h @@ -32,6 +32,7 @@ * ****************************************************************************/ +#include #include #include #include From 36aa9b3cff3fc902e2f902179c2ced786c958795 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Mon, 11 Dec 2023 18:26:00 +0100 Subject: [PATCH 07/29] Message: Explicitly cast to uint8_t for message length and ids --- include/mav/Message.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mav/Message.h b/include/mav/Message.h index e2e1944..ee2f096 100644 --- a/include/mav/Message.h +++ b/include/mav/Message.h @@ -459,15 +459,15 @@ namespace mav { - MessageDefinition::HEADER_SIZE, 1); header().magic() = 0xFD; - header().len() = payload_size; + header().len() = static_cast(payload_size); header().incompatFlags() = 0; header().compatFlags() = 0; header().seq() = seq; if (header().systemId() == 0) { - header().systemId() = sender.system_id; + header().systemId() = static_cast(sender.system_id); } if (header().componentId() == 0) { - header().componentId() = sender.component_id; + header().componentId() = static_cast(sender.component_id); } header().msgId() = _message_definition->id(); From 6c1acbce6fdd7fcb8c2f069a111a6c96d7cd83f7 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Mon, 11 Dec 2023 18:27:56 +0100 Subject: [PATCH 08/29] NetworkInterface: Add virtual destructor --- include/mav/Network.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/mav/Network.h b/include/mav/Network.h index 92c7bb6..0b128ad 100644 --- a/include/mav/Network.h +++ b/include/mav/Network.h @@ -83,6 +83,7 @@ namespace mav { [[nodiscard]] virtual bool isConnectionOriented() const { return false; }; + virtual ~NetworkInterface() = default; }; From d7f50960c3091ff4d881e7e573c37db0260719be Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Mon, 11 Dec 2023 18:29:41 +0100 Subject: [PATCH 09/29] Network: removed unused exception types. --- include/mav/Network.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/mav/Network.h b/include/mav/Network.h index 0b128ad..2751e41 100644 --- a/include/mav/Network.h +++ b/include/mav/Network.h @@ -256,7 +256,7 @@ namespace mav { _expireConnection(connection.second, std::make_exception_ptr(e)); } } - } catch (NetworkInterfaceInterrupt &e) { + } catch (NetworkInterfaceInterrupt) { _should_terminate.store(true); } } @@ -290,7 +290,7 @@ namespace mav { _expireConnection(connection.second, std::make_exception_ptr(e)); } } - } catch (NetworkInterfaceInterrupt &e) { + } catch (NetworkInterfaceInterrupt) { _should_terminate.store(true); } From e9dd682b9775889fc222ab65b6ee7a1314cf4c25 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Mon, 11 Dec 2023 18:46:13 +0100 Subject: [PATCH 10/29] Message: make the initializer list nicer --- include/mav/Message.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/include/mav/Message.h b/include/mav/Message.h index ee2f096..1ae0321 100644 --- a/include/mav/Message.h +++ b/include/mav/Message.h @@ -76,16 +76,13 @@ namespace mav { class Message { friend MessageSet; private: - ConnectionPartner _source_partner; + ConnectionPartner _source_partner{}; std::array _backing_memory{}; - const MessageDefinition* _message_definition; - int _crc_offset = -1; + const MessageDefinition* _message_definition{nullptr}; + int _crc_offset{-1}; explicit Message(const MessageDefinition &message_definition) : - _source_partner({}), - _backing_memory({}), - _message_definition(&message_definition), - _crc_offset(-1) { + _message_definition(&message_definition) { } Message(const MessageDefinition &message_definition, ConnectionPartner source_partner, int crc_offset, From 9115b88f449e8f0778deffd6e17bffd3f6e6b80b Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Mon, 11 Dec 2023 18:50:38 +0100 Subject: [PATCH 11/29] Network: catch interruptexception by reference --- include/mav/Network.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/mav/Network.h b/include/mav/Network.h index 2751e41..90b5fa6 100644 --- a/include/mav/Network.h +++ b/include/mav/Network.h @@ -256,7 +256,7 @@ namespace mav { _expireConnection(connection.second, std::make_exception_ptr(e)); } } - } catch (NetworkInterfaceInterrupt) { + } catch (NetworkInterfaceInterrupt&) { _should_terminate.store(true); } } @@ -290,7 +290,7 @@ namespace mav { _expireConnection(connection.second, std::make_exception_ptr(e)); } } - } catch (NetworkInterfaceInterrupt) { + } catch (NetworkInterfaceInterrupt&) { _should_terminate.store(true); } From 0ce1c3bd95945af460564614439409b4f656817a Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Mon, 11 Dec 2023 18:56:20 +0100 Subject: [PATCH 12/29] sonarcloud: remove rapidxml from coverage reporting --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 0cc9bb2..18cc5f1 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,7 +7,7 @@ sonar.sources = include/,tests/ sonar.exclusions = include/mav/rapidxml/,tests/doctest.h -sonar.coverage.exclusions = tests/**/* +sonar.coverage.exclusions = tests/**/*,include/mav/rapidxml/ sonar.cpd.exclusions = tests/**/* sonar.sourceEncoding = UTF-8 From f674f3f1655fa9b4875f3adda64fb7a523a4c8fa Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Tue, 12 Dec 2023 10:31:18 +0100 Subject: [PATCH 13/29] Message: improve test coverage --- include/mav/utils.h | 4 +- tests/Message.cpp | 149 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 136 insertions(+), 17 deletions(-) diff --git a/include/mav/utils.h b/include/mav/utils.h index ffd3d57..412e6e9 100644 --- a/include/mav/utils.h +++ b/include/mav/utils.h @@ -78,14 +78,14 @@ namespace mav { }; template - inline void serialize(const T &v, uint8_t* destination) { + inline void serialize(const T &v, uint8_t* destination) noexcept { auto src_ptr = static_cast(static_cast(&v)); std::copy(src_ptr, src_ptr + sizeof(T), destination); } template - inline T deserialize(const uint8_t* source, int deserialize_size) { + inline T deserialize(const uint8_t* source, int deserialize_size) noexcept { // in case we do not have any bytes to read, we return 0 if (deserialize_size <= 0) { return T{0}; diff --git a/tests/Message.cpp b/tests/Message.cpp index 051473c..3edf1be 100644 --- a/tests/Message.cpp +++ b/tests/Message.cpp @@ -34,12 +34,25 @@ TEST_CASE("Message set creation") { description description + + description + description + description + description + description + description + description + description + description + description + description + )""""); REQUIRE(message_set.contains("BIG_MESSAGE")); - REQUIRE_EQ(message_set.size(), 2); + REQUIRE_EQ(message_set.size(), 3); auto message = message_set.create("BIG_MESSAGE"); CHECK_EQ(message.id(), message_set.idForMessage("BIG_MESSAGE")); @@ -78,7 +91,7 @@ TEST_CASE("Message set creation") { SUBCASE("Have fields truncated by zero-elision") { message.set("int64_field", 34); // since largest field, will be at the end of the message CHECK_EQ(message.get("int64_field"), 34); - auto ret = message.finalize(1, {2,3}); + auto ret = message.finalize(1, {2, 3}); CHECK_EQ(message.get("int64_field"), 34); } @@ -130,19 +143,19 @@ TEST_CASE("Message set creation") { SUBCASE("Can set values with initializer list API") { message.set({ - {"uint8_field", 0x12}, - {"int8_field", 0x12}, - {"uint16_field", 0x1234}, - {"int16_field", 0x1234}, - {"uint32_field", 0x12345678}, - {"int32_field", 0x12345678}, - {"uint64_field", 0x1234567890ABCDEF}, - {"int64_field", 0x1234567890ABCDEF}, - {"double_field", 0.123456789}, - {"float_field", 0.123456789}, - {"char_arr_field", "Hello World!"}, - {"float_arr_field", std::vector{1.0, 2.0, 3.0}}, - {"int32_arr_field", std::vector{1, 2, 3}}}); + {"uint8_field", 0x12}, + {"int8_field", 0x12}, + {"uint16_field", 0x1234}, + {"int16_field", 0x1234}, + {"uint32_field", 0x12345678}, + {"int32_field", 0x12345678}, + {"uint64_field", 0x1234567890ABCDEF}, + {"int64_field", 0x1234567890ABCDEF}, + {"double_field", 0.123456789}, + {"float_field", 0.123456789}, + {"char_arr_field", "Hello World!"}, + {"float_arr_field", std::vector{1.0, 2.0, 3.0}}, + {"int32_arr_field", std::vector{1, 2, 3}}}); CHECK_EQ(static_cast(message["uint8_field"]), 0x12); CHECK_EQ(static_cast(message["int8_field"]), 0x12); @@ -201,6 +214,9 @@ TEST_CASE("Message set creation") { message["float_field"].floatPack(0x23456789); CHECK_EQ(message["float_field"].floatUnpack(), 0x23456789); + + CHECK_THROWS_AS(message["float_field"].floatUnpack(), std::runtime_error); + CHECK_THROWS_AS(message["float_field"].floatUnpack>(), std::runtime_error); } SUBCASE("Set and get a single field in array outside of range") { @@ -258,6 +274,109 @@ TEST_CASE("Message set creation") { CHECK_EQ(this_test_message.get("field4"), 0); } + SUBCASE("Test all array conversions") { + auto this_test_message = message_set.create("ARRAY_ONLY_MESSAGE"); + this_test_message["field1"] = "Hello"; + this_test_message["field2"] = std::vector{1, 2, 3}; + this_test_message["field3"] = std::vector{4, 5, 6}; + this_test_message["field4"] = std::vector{7, 8, 9}; + this_test_message["field5"] = std::vector{10, 11, 12}; + this_test_message["field6"] = std::vector{13, 14, 15}; + this_test_message["field7"] = std::vector{16, 17, 18}; + this_test_message["field8"] = std::vector{19, 20, 21}; + this_test_message["field9"] = std::vector{22, 23, 24}; + this_test_message["field10"] = std::vector{25, 26, 27}; + this_test_message["field11"] = std::vector{28, 29, 30}; + + CHECK_EQ(this_test_message["field1"].as(), "Hello"); + CHECK_EQ(this_test_message["field2"].as>(), std::vector{1, 2, 3}); + CHECK_EQ(this_test_message["field3"].as>(), std::vector{4, 5, 6}); + CHECK_EQ(this_test_message["field4"].as>(), std::vector{7, 8, 9}); + CHECK_EQ(this_test_message["field5"].as>(), std::vector{10, 11, 12}); + CHECK_EQ(this_test_message["field6"].as>(), std::vector{13, 14, 15}); + CHECK_EQ(this_test_message["field7"].as>(), std::vector{16, 17, 18}); + CHECK_EQ(this_test_message["field8"].as>(), std::vector{19, 20, 21}); + CHECK_EQ(this_test_message["field9"].as>(), std::vector{22, 23, 24}); + CHECK_EQ(this_test_message["field10"].as>(), std::vector{25, 26, 27}); + CHECK_EQ(this_test_message["field11"].as>(), std::vector{28, 29, 30}); + } + + SUBCASE("Can get as native type variant") { + message.setFromNativeTypeVariant("uint8_field", {static_cast(1)}); + message.setFromNativeTypeVariant("int8_field", {static_cast(2)}); + message.setFromNativeTypeVariant("uint16_field", {static_cast(3)}); + message.setFromNativeTypeVariant("int16_field", {static_cast(4)}); + message.setFromNativeTypeVariant("uint32_field", {static_cast(5)}); + message.setFromNativeTypeVariant("int32_field", {static_cast(6)}); + message.setFromNativeTypeVariant("uint64_field", {static_cast(7)}); + message.setFromNativeTypeVariant("int64_field", {static_cast(8)}); + message.setFromNativeTypeVariant("double_field", {9.0}); + message.setFromNativeTypeVariant("float_field", {10.0f}); + message.setFromNativeTypeVariant("char_arr_field", {"Hello World!"}); + message.setFromNativeTypeVariant("float_arr_field", {std::vector{1.0, 2.0, 3.0}}); + message.setFromNativeTypeVariant("int32_arr_field", {std::vector{4, 5, 6}}); + CHECK_EQ(message.get("uint8_field"), 1); + CHECK_EQ(message.get("int8_field"), 2); + CHECK_EQ(message.get("uint16_field"), 3); + CHECK_EQ(message.get("int16_field"), 4); + CHECK_EQ(message.get("uint32_field"), 5); + CHECK_EQ(message.get("int32_field"), 6); + CHECK_EQ(message.get("uint64_field"), 7); + CHECK_EQ(message.get("int64_field"), 8); + CHECK_EQ(message.get("double_field"), doctest::Approx(9.0)); + CHECK_EQ(message.get("float_field"), doctest::Approx(10.0)); + CHECK_EQ(message.get("char_arr_field"), "Hello World!"); + CHECK_EQ(message.get>("float_arr_field"), std::vector{1.0, 2.0, 3.0}); + CHECK_EQ(message.get>("int32_arr_field"), std::vector{4, 5, 6}); + + + + CHECK(std::holds_alternative(message.getAsNativeTypeInVariant("uint8_field"))); + CHECK(std::holds_alternative(message.getAsNativeTypeInVariant("int8_field"))); + CHECK(std::holds_alternative(message.getAsNativeTypeInVariant("uint16_field"))); + CHECK(std::holds_alternative(message.getAsNativeTypeInVariant("int16_field"))); + CHECK(std::holds_alternative(message.getAsNativeTypeInVariant("uint32_field"))); + CHECK(std::holds_alternative(message.getAsNativeTypeInVariant("int32_field"))); + CHECK(std::holds_alternative(message.getAsNativeTypeInVariant("uint64_field"))); + CHECK(std::holds_alternative(message.getAsNativeTypeInVariant("int64_field"))); + CHECK(std::holds_alternative(message.getAsNativeTypeInVariant("double_field"))); + CHECK(std::holds_alternative(message.getAsNativeTypeInVariant("float_field"))); + CHECK(std::holds_alternative(message.getAsNativeTypeInVariant("char_arr_field"))); + CHECK(std::holds_alternative>(message.getAsNativeTypeInVariant("float_arr_field"))); + CHECK(std::holds_alternative>(message.getAsNativeTypeInVariant("int32_arr_field"))); + + + auto this_test_message = message_set.create("ARRAY_ONLY_MESSAGE"); + CHECK(std::holds_alternative(this_test_message.getAsNativeTypeInVariant("field1"))); + CHECK(std::holds_alternative>(this_test_message.getAsNativeTypeInVariant("field2"))); + CHECK(std::holds_alternative>(this_test_message.getAsNativeTypeInVariant("field3"))); + CHECK(std::holds_alternative>(this_test_message.getAsNativeTypeInVariant("field4"))); + CHECK(std::holds_alternative>(this_test_message.getAsNativeTypeInVariant("field5"))); + CHECK(std::holds_alternative>(this_test_message.getAsNativeTypeInVariant("field6"))); + CHECK(std::holds_alternative>(this_test_message.getAsNativeTypeInVariant("field7"))); + CHECK(std::holds_alternative>(this_test_message.getAsNativeTypeInVariant("field8"))); + CHECK(std::holds_alternative>(this_test_message.getAsNativeTypeInVariant("field9"))); + CHECK(std::holds_alternative>(this_test_message.getAsNativeTypeInVariant("field10"))); + CHECK(std::holds_alternative>(this_test_message.getAsNativeTypeInVariant("field11"))); + } + + SUBCASE("Test toString") { + message["uint8_field"] = 1; + message["int8_field"] = -2; + message["uint16_field"] = 3; + message["int16_field"] = -4; + message["uint32_field"] = 5; + message["int32_field"] = -6; + message["uint64_field"] = 7; + message["int64_field"] = 8; + message["double_field"] = 9.0; + message["float_field"] = 10.0; + message["char_arr_field"] = "Hello World!"; + message["float_arr_field"] = std::vector{1.0, 2.0, 3.0}; + message["int32_arr_field"] = std::vector{4, 5, 6}; + CHECK_EQ(message.toString(), + "Message ID 9915 (BIG_MESSAGE) \n char_arr_field: \"Hello World!\"\n double_field: 9\n float_arr_field: 1, 2, 3\n float_field: 10\n int16_field: -4\n int32_arr_field: 4, 5, 6\n int32_field: -6\n int64_field: 8\n int8_field: -2\n uint16_field: 3\n uint32_field: 5\n uint64_field: 7\n uint8_field: 1\n"); + } } From e34db1ccdddc9673623c570540a12dc52fb9db67 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Fri, 15 Dec 2023 09:26:59 +0100 Subject: [PATCH 14/29] Network: add test-case for parser re-synchronization --- tests/Network.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Network.cpp b/tests/Network.cpp index 744b309..c0ad348 100644 --- a/tests/Network.cpp +++ b/tests/Network.cpp @@ -159,6 +159,19 @@ TEST_CASE("Create network runtime") { CHECK_EQ(message.name(), "TEST_MESSAGE"); } + SUBCASE("Receiver re-synchronizes when garbage data between messages") { + interface.reset(); + auto expectation_1 = connection->expect("TEST_MESSAGE"); + auto expectation_2 = connection->expect("TEST_MESSAGE"); + interface.addToReceiveQueue("\xfd\x10\x00\x00\x01\x61\x61\xbc\x26\x00\x2a\x00\x00\x00\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x53\xd9"s, interface_partner); + interface.addToReceiveQueue("this is garbage data"s, interface_partner); + interface.addToReceiveQueue("\xfd\x10\x00\x00\x01\x61\x61\xbc\x26\x00\x2a\x00\x00\x00\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x53\xd9"s, interface_partner); + auto message_1 = connection->receive(expectation_1); + CHECK_EQ(message_1.name(), "TEST_MESSAGE"); + auto message_2 = connection->receive(expectation_2); + CHECK_EQ(message_2.name(), "TEST_MESSAGE"); + } + SUBCASE("Can not receive message from wrong partner") { interface.reset(); auto expectation = connection->expect("TEST_MESSAGE"); From eeb517f1480cfa35011266313ecd87231ec009cd Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Wed, 17 Jan 2024 13:48:21 +0100 Subject: [PATCH 15/29] sc: mark millis noexcept --- include/mav/utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mav/utils.h b/include/mav/utils.h index 412e6e9..3433883 100644 --- a/include/mav/utils.h +++ b/include/mav/utils.h @@ -100,7 +100,7 @@ namespace mav { } - inline uint64_t millis() { + inline uint64_t millis() noexcept { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count(); } From 00620a98efd4aa0bbe8b262ea5e4b43e3ce0b8a5 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Wed, 17 Jan 2024 14:37:56 +0100 Subject: [PATCH 16/29] utils: mark StringFormat as noexcept to improve test coverage --- .gitignore | 1 + .vscode/c_cpp_properties.json | 17 +++++++++++++++++ CMakeLists.txt | 7 +++++++ include/mav/utils.h | 10 ++++++---- 4 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 .vscode/c_cpp_properties.json diff --git a/.gitignore b/.gitignore index ea3a169..731fece 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ cmake-build-debug build Testing +html diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..6006e92 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "gcc-x64", + "compileCommands": "${workspaceFolder}/build/compile_commands.json" + } + ], + "version": 4 +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b2c3c1..9dcefef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,13 @@ include(GNUInstallDirs) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) +find_program(CCACHE_PROGRAM ccache) +if(CCACHE_PROGRAM) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") +endif() + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + set(CMAKE_CXX_STANDARD 17) add_library(mav INTERFACE) diff --git a/include/mav/utils.h b/include/mav/utils.h index 3433883..2f420cf 100644 --- a/include/mav/utils.h +++ b/include/mav/utils.h @@ -116,21 +116,23 @@ namespace mav { static constexpr formatEndType end{}; static constexpr formatEndType toString{}; + StringFormat() noexcept = default; + template - StringFormat& operator<< (const T &value) { + StringFormat& operator<< (const T &value) noexcept { _stream << value; return *this; } - std::string operator<< (const formatEndType&) { + std::string operator<< (const formatEndType&) noexcept { return _stream.str(); } - std::string str() const { + std::string str() const noexcept { return _stream.str(); } - explicit operator std::string() const { + explicit operator std::string() const noexcept { return _stream.str(); } }; From f6e7aafbacd4d18580eec47a1429cd092d60e389 Mon Sep 17 00:00:00 2001 From: Stuart Miller Date: Wed, 6 Dec 2023 14:28:02 -0600 Subject: [PATCH 17/29] Add means to directly send a message on a NetworkRuntime. --- include/mav/Network.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/mav/Network.h b/include/mav/Network.h index 90b5fa6..32457d1 100644 --- a/include/mav/Network.h +++ b/include/mav/Network.h @@ -405,6 +405,10 @@ namespace mav { _heartbeat_message = std::nullopt; } + void sendMessage(Message &message) { + _sendMessage(message, {}); + } + void stop() { _interface.close(); _should_terminate.store(true); From c92860e1ceac076365042a8ec74c6dd65efdf8e3 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Mon, 22 Jan 2024 15:23:25 +0100 Subject: [PATCH 18/29] Netowork: change low-level sendMessage to injectMessage --- include/mav/Network.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mav/Network.h b/include/mav/Network.h index 32457d1..5988b42 100644 --- a/include/mav/Network.h +++ b/include/mav/Network.h @@ -405,7 +405,7 @@ namespace mav { _heartbeat_message = std::nullopt; } - void sendMessage(Message &message) { + void injectMessage(Message &message) { _sendMessage(message, {}); } From e7035fc436658ec5bc5a2a7197f2cc005787728e Mon Sep 17 00:00:00 2001 From: Stuart Miller Date: Fri, 1 Dec 2023 09:40:33 -0600 Subject: [PATCH 19/29] Add support for signed packets per MAVLink RAS-A. --- include/mav/Message.h | 52 ++++- include/mav/MessageDefinition.h | 59 +++++ include/mav/Network.h | 19 +- include/mav/picosha2/license.txt | 21 ++ include/mav/picosha2/picosha2.h | 377 +++++++++++++++++++++++++++++++ 5 files changed, 524 insertions(+), 4 deletions(-) create mode 100644 include/mav/picosha2/license.txt create mode 100644 include/mav/picosha2/picosha2.h diff --git a/include/mav/Message.h b/include/mav/Message.h index 1ae0321..39847c5 100644 --- a/include/mav/Message.h +++ b/include/mav/Message.h @@ -41,6 +41,7 @@ #include #include "MessageDefinition.h" #include "utils.h" +#include "picosha2/picosha2.h" namespace mav { @@ -153,6 +154,35 @@ namespace mav { throw std::runtime_error("Unknown base type"); // should never happen } + uint64_t _computeSignatureHash48(const std::array& key) const { + // signature = sha256_48(secret_key + header + payload + CRC + link-ID + timestamp) + constexpr size_t maxSize = 32 + MessageDefinition::HEADER_SIZE + + MessageDefinition::MAX_PAYLOAD_SIZE + + MessageDefinition::CHECKSUM_SIZE + 1 + 6; + std::array data; + size_t actualSize = 0; + // secret_key + std::copy_n(key.begin(), 32, data.begin() + actualSize); + actualSize += 32; + // header + payload + CRC + const size_t dataSize = + MessageDefinition::HEADER_SIZE + header().len() + MessageDefinition::CHECKSUM_SIZE; + std::copy_n(_backing_memory.begin(), dataSize, data.begin() + actualSize); + actualSize += dataSize; + // link-ID + const uint8_t linkId = signature().linkId(); + serialize(linkId, data.begin() + actualSize); + actualSize += 1; + // timestamp + const uint64_t timestamp = signature().timestamp(); + serialize(timestamp, data.begin() + actualSize); + actualSize += 6; + + std::vector hash(picosha2::k_digest_size); + picosha2::hash256(data.begin(), data.begin() + actualSize, hash.begin(), hash.end()); + return deserialize(hash.data(), 6); + } + public: static inline Message _instantiateFromMemory(const MessageDefinition &definition, ConnectionPartner source_partner, @@ -222,6 +252,14 @@ namespace mav { return Header(_backing_memory.data()); } + [[nodiscard]] const Signature signature() const { + return Signature(&_backing_memory[MessageDefinition::HEADER_SIZE + header().len() + MessageDefinition::CHECKSUM_SIZE]); + } + + [[nodiscard]] Signature signature() { + return Signature(&_backing_memory[MessageDefinition::HEADER_SIZE + header().len() + MessageDefinition::CHECKSUM_SIZE]); + } + [[nodiscard]] const ConnectionPartner& source() const { return _source_partner; } @@ -440,7 +478,17 @@ namespace mav { return ss.str(); } - [[nodiscard]] uint32_t finalize(uint8_t seq, const Identifier &sender) { + void sign(const std::array& key, const uint64_t& timestamp) { + signature().linkId() = 0; + signature().timestamp() = timestamp; + signature().signature() = _computeSignatureHash48(key); + } + + [[nodiscard]] bool validate(const std::array& key) const { + return signature().signature() == _computeSignatureHash48(key); + } + + [[nodiscard]] uint32_t finalize(uint8_t seq, const Identifier &sender, const bool sign = false) { if (isFinalized()) { _unFinalize(); } @@ -457,7 +505,7 @@ namespace mav { header().magic() = 0xFD; header().len() = static_cast(payload_size); - header().incompatFlags() = 0; + header().incompatFlags() = sign ? 0x01 : 0x00; header().compatFlags() = 0; header().seq() = seq; if (header().systemId() == 0) { diff --git a/include/mav/MessageDefinition.h b/include/mav/MessageDefinition.h index 07e7eb8..f4595de 100644 --- a/include/mav/MessageDefinition.h +++ b/include/mav/MessageDefinition.h @@ -179,6 +179,10 @@ namespace mav { return _backing_memory[2]; } + [[nodiscard]] inline bool isSigned() const { + return incompatFlags() & 0x01; + } + inline uint8_t& compatFlags() { return _backing_memory[3]; } @@ -224,6 +228,61 @@ namespace mav { } }; + template + class Signature { + private: + BackingMemoryPointerType _backing_memory; + + // Both timestamp and signature use 6-byte fields + class _SignatureField { + private: + BackingMemoryPointerType _ptr; + public: + explicit _SignatureField(BackingMemoryPointerType ptr) : _ptr(ptr) {} + + operator uint64_t() const { + return static_cast((*static_cast(static_cast(_ptr))) & 0xFFFFFFFFFFFF); + } + + _SignatureField& operator=(uint64_t v) { + _ptr[0] = static_cast(v & 0xFF); + _ptr[1] = static_cast((v >> 8) & 0xFF); + _ptr[2] = static_cast((v >> 16) & 0xFF); + _ptr[3] = static_cast((v >> 24) & 0xFF); + _ptr[4] = static_cast((v >> 32) & 0xFF); + _ptr[5] = static_cast((v >> 40) & 0xFF); + return *this; + } + }; + + public: + explicit Signature(BackingMemoryPointerType backing_memory) : _backing_memory(backing_memory) {} + + inline uint8_t& linkId(){ + return _backing_memory[0]; + } + + [[nodiscard]] inline uint8_t linkId() const { + return _backing_memory[0]; + } + + inline _SignatureField timestamp() { + return _SignatureField(_backing_memory + 1); + } + + [[nodiscard]] inline const _SignatureField timestamp() const { + return _SignatureField(_backing_memory + 1); + } + + inline _SignatureField signature() { + return _SignatureField(_backing_memory + 7); + } + + [[nodiscard]] inline const _SignatureField signature() const { + return _SignatureField(_backing_memory + 7); + } + }; + class FieldType { public: enum class BaseType { diff --git a/include/mav/Network.h b/include/mav/Network.h index 5988b42..ddf4b36 100644 --- a/include/mav/Network.h +++ b/include/mav/Network.h @@ -117,7 +117,7 @@ namespace mav { backing_memory[0] = 0xFD; _interface.receive(backing_memory.data() + 1, MessageDefinition::HEADER_SIZE -1); Header header{backing_memory.data()}; - const bool message_is_signed = header.incompatFlags() & 0x01; + const bool message_is_signed = header.isSigned(); const int wire_length = MessageDefinition::HEADER_SIZE + header.len() + MessageDefinition::CHECKSUM_SIZE + (message_is_signed ? MessageDefinition::SIGNATURE_SIZE : 0); auto partner = _interface.receive(backing_memory.data() + MessageDefinition::HEADER_SIZE, @@ -158,6 +158,8 @@ namespace mav { std::mutex _heartbeat_message_mutex; StreamParser _parser; Identifier _own_id; + std::array _key; + std::function _get_timestamp_function; std::mutex _connections_mutex; std::mutex _send_mutex; std::unordered_map&)> _on_connection_lost; void _sendMessage(Message &message, const ConnectionPartner &partner) { - int wire_length = static_cast(message.finalize(_seq++, _own_id)); + const bool sign = bool(_get_timestamp_function); + int wire_length = static_cast(message.finalize(_seq++, _own_id, sign)); + if (sign) { + message.sign(_key, _get_timestamp_function()); + wire_length += MessageDefinition::SIGNATURE_SIZE; + } std::unique_lock lock(_send_mutex); _interface.send(message.data(), wire_length, partner); } @@ -409,6 +416,14 @@ namespace mav { _sendMessage(message, {}); } + void setGetTimestampFunction(std::function function) { + _get_timestamp_function = function; + } + + void setKey(std::array key) { + _key = key; + } + void stop() { _interface.close(); _should_terminate.store(true); diff --git a/include/mav/picosha2/license.txt b/include/mav/picosha2/license.txt new file mode 100644 index 0000000..b6658bb --- /dev/null +++ b/include/mav/picosha2/license.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 okdshin + +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. diff --git a/include/mav/picosha2/picosha2.h b/include/mav/picosha2/picosha2.h new file mode 100644 index 0000000..a921736 --- /dev/null +++ b/include/mav/picosha2/picosha2.h @@ -0,0 +1,377 @@ +/* +The MIT License (MIT) + +Copyright (C) 2017 okdshin + +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. +*/ +#ifndef PICOSHA2_H +#define PICOSHA2_H +// picosha2:20140213 + +#ifndef PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR +#define PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR \ + 1048576 //=1024*1024: default is 1MB memory +#endif + +#include +#include +#include +#include +#include +#include +namespace picosha2 { +typedef unsigned long word_t; +typedef unsigned char byte_t; + +static const size_t k_digest_size = 32; + +namespace detail { +inline byte_t mask_8bit(byte_t x) { return x & 0xff; } + +inline word_t mask_32bit(word_t x) { return x & 0xffffffff; } + +const word_t add_constant[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + +const word_t initial_message_digest[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, + 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19}; + +inline word_t ch(word_t x, word_t y, word_t z) { return (x & y) ^ ((~x) & z); } + +inline word_t maj(word_t x, word_t y, word_t z) { + return (x & y) ^ (x & z) ^ (y & z); +} + +inline word_t rotr(word_t x, std::size_t n) { + assert(n < 32); + return mask_32bit((x >> n) | (x << (32 - n))); +} + +inline word_t bsig0(word_t x) { return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); } + +inline word_t bsig1(word_t x) { return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); } + +inline word_t shr(word_t x, std::size_t n) { + assert(n < 32); + return x >> n; +} + +inline word_t ssig0(word_t x) { return rotr(x, 7) ^ rotr(x, 18) ^ shr(x, 3); } + +inline word_t ssig1(word_t x) { return rotr(x, 17) ^ rotr(x, 19) ^ shr(x, 10); } + +template +void hash256_block(RaIter1 message_digest, RaIter2 first, RaIter2 last) { + assert(first + 64 == last); + static_cast(last); // for avoiding unused-variable warning + word_t w[64]; + std::fill(w, w + 64, word_t(0)); + for (std::size_t i = 0; i < 16; ++i) { + w[i] = (static_cast(mask_8bit(*(first + i * 4))) << 24) | + (static_cast(mask_8bit(*(first + i * 4 + 1))) << 16) | + (static_cast(mask_8bit(*(first + i * 4 + 2))) << 8) | + (static_cast(mask_8bit(*(first + i * 4 + 3)))); + } + for (std::size_t i = 16; i < 64; ++i) { + w[i] = mask_32bit(ssig1(w[i - 2]) + w[i - 7] + ssig0(w[i - 15]) + + w[i - 16]); + } + + word_t a = *message_digest; + word_t b = *(message_digest + 1); + word_t c = *(message_digest + 2); + word_t d = *(message_digest + 3); + word_t e = *(message_digest + 4); + word_t f = *(message_digest + 5); + word_t g = *(message_digest + 6); + word_t h = *(message_digest + 7); + + for (std::size_t i = 0; i < 64; ++i) { + word_t temp1 = h + bsig1(e) + ch(e, f, g) + add_constant[i] + w[i]; + word_t temp2 = bsig0(a) + maj(a, b, c); + h = g; + g = f; + f = e; + e = mask_32bit(d + temp1); + d = c; + c = b; + b = a; + a = mask_32bit(temp1 + temp2); + } + *message_digest += a; + *(message_digest + 1) += b; + *(message_digest + 2) += c; + *(message_digest + 3) += d; + *(message_digest + 4) += e; + *(message_digest + 5) += f; + *(message_digest + 6) += g; + *(message_digest + 7) += h; + for (std::size_t i = 0; i < 8; ++i) { + *(message_digest + i) = mask_32bit(*(message_digest + i)); + } +} + +} // namespace detail + +template +void output_hex(InIter first, InIter last, std::ostream& os) { + os.setf(std::ios::hex, std::ios::basefield); + while (first != last) { + os.width(2); + os.fill('0'); + os << static_cast(*first); + ++first; + } + os.setf(std::ios::dec, std::ios::basefield); +} + +template +void bytes_to_hex_string(InIter first, InIter last, std::string& hex_str) { + std::ostringstream oss; + output_hex(first, last, oss); + hex_str.assign(oss.str()); +} + +template +void bytes_to_hex_string(const InContainer& bytes, std::string& hex_str) { + bytes_to_hex_string(bytes.begin(), bytes.end(), hex_str); +} + +template +std::string bytes_to_hex_string(InIter first, InIter last) { + std::string hex_str; + bytes_to_hex_string(first, last, hex_str); + return hex_str; +} + +template +std::string bytes_to_hex_string(const InContainer& bytes) { + std::string hex_str; + bytes_to_hex_string(bytes, hex_str); + return hex_str; +} + +class hash256_one_by_one { + public: + hash256_one_by_one() { init(); } + + void init() { + buffer_.clear(); + std::fill(data_length_digits_, data_length_digits_ + 4, word_t(0)); + std::copy(detail::initial_message_digest, + detail::initial_message_digest + 8, h_); + } + + template + void process(RaIter first, RaIter last) { + add_to_data_length(static_cast(std::distance(first, last))); + std::copy(first, last, std::back_inserter(buffer_)); + std::size_t i = 0; + for (; i + 64 <= buffer_.size(); i += 64) { + detail::hash256_block(h_, buffer_.begin() + i, + buffer_.begin() + i + 64); + } + buffer_.erase(buffer_.begin(), buffer_.begin() + i); + } + + void finish() { + byte_t temp[64]; + std::fill(temp, temp + 64, byte_t(0)); + std::size_t remains = buffer_.size(); + std::copy(buffer_.begin(), buffer_.end(), temp); + temp[remains] = 0x80; + + if (remains > 55) { + std::fill(temp + remains + 1, temp + 64, byte_t(0)); + detail::hash256_block(h_, temp, temp + 64); + std::fill(temp, temp + 64 - 4, byte_t(0)); + } else { + std::fill(temp + remains + 1, temp + 64 - 4, byte_t(0)); + } + + write_data_bit_length(&(temp[56])); + detail::hash256_block(h_, temp, temp + 64); + } + + template + void get_hash_bytes(OutIter first, OutIter last) const { + for (const word_t* iter = h_; iter != h_ + 8; ++iter) { + for (std::size_t i = 0; i < 4 && first != last; ++i) { + *(first++) = detail::mask_8bit( + static_cast((*iter >> (24 - 8 * i)))); + } + } + } + + private: + void add_to_data_length(word_t n) { + word_t carry = 0; + data_length_digits_[0] += n; + for (std::size_t i = 0; i < 4; ++i) { + data_length_digits_[i] += carry; + if (data_length_digits_[i] >= 65536u) { + carry = data_length_digits_[i] >> 16; + data_length_digits_[i] &= 65535u; + } else { + break; + } + } + } + void write_data_bit_length(byte_t* begin) { + word_t data_bit_length_digits[4]; + std::copy(data_length_digits_, data_length_digits_ + 4, + data_bit_length_digits); + + // convert byte length to bit length (multiply 8 or shift 3 times left) + word_t carry = 0; + for (std::size_t i = 0; i < 4; ++i) { + word_t before_val = data_bit_length_digits[i]; + data_bit_length_digits[i] <<= 3; + data_bit_length_digits[i] |= carry; + data_bit_length_digits[i] &= 65535u; + carry = (before_val >> (16 - 3)) & 65535u; + } + + // write data_bit_length + for (int i = 3; i >= 0; --i) { + (*begin++) = static_cast(data_bit_length_digits[i] >> 8); + (*begin++) = static_cast(data_bit_length_digits[i]); + } + } + std::vector buffer_; + word_t data_length_digits_[4]; // as 64bit integer (16bit x 4 integer) + word_t h_[8]; +}; + +inline void get_hash_hex_string(const hash256_one_by_one& hasher, + std::string& hex_str) { + byte_t hash[k_digest_size]; + hasher.get_hash_bytes(hash, hash + k_digest_size); + return bytes_to_hex_string(hash, hash + k_digest_size, hex_str); +} + +inline std::string get_hash_hex_string(const hash256_one_by_one& hasher) { + std::string hex_str; + get_hash_hex_string(hasher, hex_str); + return hex_str; +} + +namespace impl { +template +void hash256_impl(RaIter first, RaIter last, OutIter first2, OutIter last2, int, + std::random_access_iterator_tag) { + hash256_one_by_one hasher; + // hasher.init(); + hasher.process(first, last); + hasher.finish(); + hasher.get_hash_bytes(first2, last2); +} + +template +void hash256_impl(InputIter first, InputIter last, OutIter first2, + OutIter last2, int buffer_size, std::input_iterator_tag) { + std::vector buffer(buffer_size); + hash256_one_by_one hasher; + // hasher.init(); + while (first != last) { + int size = buffer_size; + for (int i = 0; i != buffer_size; ++i, ++first) { + if (first == last) { + size = i; + break; + } + buffer[i] = *first; + } + hasher.process(buffer.begin(), buffer.begin() + size); + } + hasher.finish(); + hasher.get_hash_bytes(first2, last2); +} +} + +template +void hash256(InIter first, InIter last, OutIter first2, OutIter last2, + int buffer_size = PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR) { + picosha2::impl::hash256_impl( + first, last, first2, last2, buffer_size, + typename std::iterator_traits::iterator_category()); +} + +template +void hash256(InIter first, InIter last, OutContainer& dst) { + hash256(first, last, dst.begin(), dst.end()); +} + +template +void hash256(const InContainer& src, OutIter first, OutIter last) { + hash256(src.begin(), src.end(), first, last); +} + +template +void hash256(const InContainer& src, OutContainer& dst) { + hash256(src.begin(), src.end(), dst.begin(), dst.end()); +} + +template +void hash256_hex_string(InIter first, InIter last, std::string& hex_str) { + byte_t hashed[k_digest_size]; + hash256(first, last, hashed, hashed + k_digest_size); + std::ostringstream oss; + output_hex(hashed, hashed + k_digest_size, oss); + hex_str.assign(oss.str()); +} + +template +std::string hash256_hex_string(InIter first, InIter last) { + std::string hex_str; + hash256_hex_string(first, last, hex_str); + return hex_str; +} + +inline void hash256_hex_string(const std::string& src, std::string& hex_str) { + hash256_hex_string(src.begin(), src.end(), hex_str); +} + +template +void hash256_hex_string(const InContainer& src, std::string& hex_str) { + hash256_hex_string(src.begin(), src.end(), hex_str); +} + +template +std::string hash256_hex_string(const InContainer& src) { + return hash256_hex_string(src.begin(), src.end()); +} +templatevoid hash256(std::ifstream& f, OutIter first, OutIter last){ + hash256(std::istreambuf_iterator(f), std::istreambuf_iterator(), first,last); + +} +}// namespace picosha2 +#endif // PICOSHA2_H From 715012faefbff11416ad6504ce28b3f668c3b71f Mon Sep 17 00:00:00 2001 From: Stuart Miller Date: Mon, 4 Dec 2023 12:29:02 -0600 Subject: [PATCH 20/29] Add unit test for signed packets. --- README.md | 11 +++++++++++ tests/Message.cpp | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/README.md b/README.md index 35f1025..b38477a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,17 @@ Since the library is header only, you only need the library on the build system. You can also include the library as a submodule in your project. +### Running the tests + +Libmav uses [doctest](https://github.com/doctest/doctest/). + +To run the tests, build the library, then run the test executable. Test results will be output to console. + +```bash +mkdir build && cd build && cmake .. && make tests +./tests/tests +``` + ## Getting started ### Loading a message set diff --git a/tests/Message.cpp b/tests/Message.cpp index 3edf1be..ebed704 100644 --- a/tests/Message.cpp +++ b/tests/Message.cpp @@ -379,4 +379,20 @@ TEST_CASE("Message set creation") { } + SUBCASE("Sign a packet") { + + std::array key; + for (int i = 0 ; i < 32; i++) key[i] = i; + + uint64_t timestamp = 770479200; + + uint32_t wire_size = message.finalize(5, {6, 7}, true); + message.sign(key, timestamp); + + CHECK(message.header().isSigned()); + CHECK_NE(message.signature().signature(), 0); + CHECK_EQ(message.signature().timestamp(), timestamp); + CHECK(message.validate(key)); + } + } From ed79d2b71f6d36778c3ff3d6027ca873f84efb49 Mon Sep 17 00:00:00 2001 From: Stuart Miller Date: Wed, 6 Dec 2023 13:46:03 -0600 Subject: [PATCH 21/29] Address review comments + Add (and use) constants for packet signature field sizes + Rework Network API into two simpler functions for enable/disable signing + Remove individual Message sign() function and integrate into finalize() + Add errors when accessing the signature of an unfinalized message + Add a default timestamp function + Update unit tests accordingly --- include/mav/Message.h | 48 ++++++++++++++++++++++----------- include/mav/MessageDefinition.h | 6 ++++- include/mav/Network.h | 30 ++++++++++++++------- tests/Message.cpp | 4 +-- 4 files changed, 59 insertions(+), 29 deletions(-) diff --git a/include/mav/Message.h b/include/mav/Message.h index 39847c5..9af82e8 100644 --- a/include/mav/Message.h +++ b/include/mav/Message.h @@ -154,16 +154,16 @@ namespace mav { throw std::runtime_error("Unknown base type"); // should never happen } - uint64_t _computeSignatureHash48(const std::array& key) const { + uint64_t _computeSignatureHash48(const std::array& key) const { // signature = sha256_48(secret_key + header + payload + CRC + link-ID + timestamp) - constexpr size_t maxSize = 32 + MessageDefinition::HEADER_SIZE + - MessageDefinition::MAX_PAYLOAD_SIZE + - MessageDefinition::CHECKSUM_SIZE + 1 + 6; + constexpr size_t maxSize = MessageDefinition::KEY_SIZE + MessageDefinition::HEADER_SIZE + + MessageDefinition::MAX_PAYLOAD_SIZE + MessageDefinition::CHECKSUM_SIZE + + MessageDefinition::SIGNATURE_LINK_ID_SIZE + MessageDefinition::SIGNATURE_TIMESTAMP_SIZE; std::array data; size_t actualSize = 0; // secret_key - std::copy_n(key.begin(), 32, data.begin() + actualSize); - actualSize += 32; + std::copy_n(key.begin(), MessageDefinition::KEY_SIZE, data.begin() + actualSize); + actualSize += MessageDefinition::KEY_SIZE; // header + payload + CRC const size_t dataSize = MessageDefinition::HEADER_SIZE + header().len() + MessageDefinition::CHECKSUM_SIZE; @@ -176,11 +176,11 @@ namespace mav { // timestamp const uint64_t timestamp = signature().timestamp(); serialize(timestamp, data.begin() + actualSize); - actualSize += 6; + actualSize += MessageDefinition::SIGNATURE_TIMESTAMP_SIZE; std::vector hash(picosha2::k_digest_size); picosha2::hash256(data.begin(), data.begin() + actualSize, hash.begin(), hash.end()); - return deserialize(hash.data(), 6); + return deserialize(hash.data(), MessageDefinition::SIGNATURE_SIGNATURE_SIZE); } public: @@ -253,10 +253,16 @@ namespace mav { } [[nodiscard]] const Signature signature() const { + if (!isFinalized()) { + throw std::runtime_error("Unable to parse unfinalized message."); + } return Signature(&_backing_memory[MessageDefinition::HEADER_SIZE + header().len() + MessageDefinition::CHECKSUM_SIZE]); } [[nodiscard]] Signature signature() { + if (!isFinalized()) { + throw std::runtime_error("Unable to parse unfinalized message."); + } return Signature(&_backing_memory[MessageDefinition::HEADER_SIZE + header().len() + MessageDefinition::CHECKSUM_SIZE]); } @@ -478,21 +484,23 @@ namespace mav { return ss.str(); } - void sign(const std::array& key, const uint64_t& timestamp) { - signature().linkId() = 0; - signature().timestamp() = timestamp; - signature().signature() = _computeSignatureHash48(key); + [[nodiscard]] bool validate(const std::array& key) const { + return signature().signature() == _computeSignatureHash48(key); } - [[nodiscard]] bool validate(const std::array& key) const { - return signature().signature() == _computeSignatureHash48(key); + [[nodiscard]] uint32_t finalize(uint8_t seq, const Identifier &sender) { + static const std::array null_key = {}; + return finalize(seq, sender, null_key, 0, 0); } - [[nodiscard]] uint32_t finalize(uint8_t seq, const Identifier &sender, const bool sign = false) { + [[nodiscard]] uint32_t finalize(uint8_t seq, const Identifier &sender, + const std::array& key, + const uint64_t& timestamp, const uint8_t linkId = 0) { if (isFinalized()) { _unFinalize(); } + bool sign = (timestamp > 0); auto last_nonzero = std::find_if(_backing_memory.rend() - MessageDefinition::HEADER_SIZE - _message_definition->maxPayloadSize(), _backing_memory.rend(), [](const auto &v) { @@ -523,7 +531,15 @@ namespace mav { _crc_offset = MessageDefinition::HEADER_SIZE + payload_size; serialize(crc.crc16(), _backing_memory.data() + _crc_offset); - return MessageDefinition::HEADER_SIZE + payload_size + MessageDefinition::CHECKSUM_SIZE; + int signature_size = 0; + if (sign) { + signature().linkId() = linkId; + signature().timestamp() = timestamp; + signature().signature() = _computeSignatureHash48(key); + signature_size = MessageDefinition::SIGNATURE_SIZE; + } + + return MessageDefinition::HEADER_SIZE + payload_size + MessageDefinition::CHECKSUM_SIZE + signature_size; } [[nodiscard]] const uint8_t* data() const { diff --git a/include/mav/MessageDefinition.h b/include/mav/MessageDefinition.h index f4595de..81396a8 100644 --- a/include/mav/MessageDefinition.h +++ b/include/mav/MessageDefinition.h @@ -367,8 +367,12 @@ namespace mav { static constexpr int MAX_PAYLOAD_SIZE = 255; static constexpr int HEADER_SIZE = 10; static constexpr int CHECKSUM_SIZE = 2; - static constexpr int SIGNATURE_SIZE = 13; + static constexpr int SIGNATURE_LINK_ID_SIZE = 1; + static constexpr int SIGNATURE_TIMESTAMP_SIZE = 6; + static constexpr int SIGNATURE_SIGNATURE_SIZE = 6; + static constexpr int SIGNATURE_SIZE = SIGNATURE_LINK_ID_SIZE + SIGNATURE_TIMESTAMP_SIZE + SIGNATURE_SIGNATURE_SIZE; static constexpr int MAX_MESSAGE_SIZE = MAX_PAYLOAD_SIZE + HEADER_SIZE + CHECKSUM_SIZE + SIGNATURE_SIZE; + static constexpr int KEY_SIZE = 32; [[nodiscard]] inline const std::string& name() const { return _name; diff --git a/include/mav/Network.h b/include/mav/Network.h index ddf4b36..112724d 100644 --- a/include/mav/Network.h +++ b/include/mav/Network.h @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include "Connection.h" @@ -145,6 +146,10 @@ namespace mav { } }; + static uint64_t _get_timestamp_function_default() { + const auto now = std::chrono::system_clock::now(); + return std::chrono::duration_cast(now.time_since_epoch()).count(); + } class NetworkRuntime { private: @@ -158,7 +163,8 @@ namespace mav { std::mutex _heartbeat_message_mutex; StreamParser _parser; Identifier _own_id; - std::array _key; + bool _sign; + std::array _key; std::function _get_timestamp_function; std::mutex _connections_mutex; std::mutex _send_mutex; @@ -173,11 +179,11 @@ namespace mav { std::function&)> _on_connection_lost; void _sendMessage(Message &message, const ConnectionPartner &partner) { - const bool sign = bool(_get_timestamp_function); - int wire_length = static_cast(message.finalize(_seq++, _own_id, sign)); - if (sign) { - message.sign(_key, _get_timestamp_function()); - wire_length += MessageDefinition::SIGNATURE_SIZE; + int wire_length; + if (_sign) { + wire_length = static_cast(message.finalize(_seq++, _own_id, _key, _get_timestamp_function())); + } else { + wire_length = static_cast(message.finalize(_seq++, _own_id)); } std::unique_lock lock(_send_mutex); _interface.send(message.data(), wire_length, partner); @@ -334,6 +340,7 @@ namespace mav { std::function&)> on_connection_lost = {}) : _interface(interface), _message_set(message_set), _parser(_message_set, _interface), _own_id(own_id), + _sign(false), _get_timestamp_function(_get_timestamp_function_default), _on_connection(std::move(on_connection)), _on_connection_lost(std::move(on_connection_lost)) { _receive_thread = std::thread{ @@ -416,12 +423,15 @@ namespace mav { _sendMessage(message, {}); } - void setGetTimestampFunction(std::function function) { - _get_timestamp_function = function; + void enableMessageSigning(std::array key, + std::function timestampFunction = _get_timestamp_function_default) { + _sign = true; + _key = key; + _get_timestamp_function = timestampFunction; } - void setKey(std::array key) { - _key = key; + void disableMessageSigning() { + _sign = false; } void stop() { diff --git a/tests/Message.cpp b/tests/Message.cpp index ebed704..e45a58f 100644 --- a/tests/Message.cpp +++ b/tests/Message.cpp @@ -386,9 +386,9 @@ TEST_CASE("Message set creation") { uint64_t timestamp = 770479200; - uint32_t wire_size = message.finalize(5, {6, 7}, true); - message.sign(key, timestamp); + uint32_t wire_size = message.finalize(5, {6, 7}, key, timestamp); + CHECK_EQ(wire_size, 26); CHECK(message.header().isSigned()); CHECK_NE(message.signature().signature(), 0); CHECK_EQ(message.signature().timestamp(), timestamp); From 7b0794e64bb8c669b2210e5cad4286753ca74929 Mon Sep 17 00:00:00 2001 From: Stuart Miller Date: Thu, 14 Dec 2023 16:40:46 -0600 Subject: [PATCH 22/29] Update message signature computation to use picosha2::hash256_one_by_one hasher. --- include/mav/Message.h | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/include/mav/Message.h b/include/mav/Message.h index 9af82e8..bbeaba0 100644 --- a/include/mav/Message.h +++ b/include/mav/Message.h @@ -156,30 +156,24 @@ namespace mav { uint64_t _computeSignatureHash48(const std::array& key) const { // signature = sha256_48(secret_key + header + payload + CRC + link-ID + timestamp) - constexpr size_t maxSize = MessageDefinition::KEY_SIZE + MessageDefinition::HEADER_SIZE + - MessageDefinition::MAX_PAYLOAD_SIZE + MessageDefinition::CHECKSUM_SIZE + - MessageDefinition::SIGNATURE_LINK_ID_SIZE + MessageDefinition::SIGNATURE_TIMESTAMP_SIZE; - std::array data; - size_t actualSize = 0; + picosha2::hash256_one_by_one hasher; // secret_key - std::copy_n(key.begin(), MessageDefinition::KEY_SIZE, data.begin() + actualSize); - actualSize += MessageDefinition::KEY_SIZE; + hasher.process(key.begin(), key.begin() + MessageDefinition::KEY_SIZE); // header + payload + CRC - const size_t dataSize = - MessageDefinition::HEADER_SIZE + header().len() + MessageDefinition::CHECKSUM_SIZE; - std::copy_n(_backing_memory.begin(), dataSize, data.begin() + actualSize); - actualSize += dataSize; + hasher.process(_backing_memory.begin(), _backing_memory.begin() + + MessageDefinition::HEADER_SIZE + header().len() + MessageDefinition::CHECKSUM_SIZE); // link-ID const uint8_t linkId = signature().linkId(); - serialize(linkId, data.begin() + actualSize); - actualSize += 1; + hasher.process(&linkId, &linkId + MessageDefinition::SIGNATURE_LINK_ID_SIZE); // timestamp const uint64_t timestamp = signature().timestamp(); - serialize(timestamp, data.begin() + actualSize); - actualSize += MessageDefinition::SIGNATURE_TIMESTAMP_SIZE; + std::array timestampSerialized; + serialize(timestamp, timestampSerialized.begin()); + hasher.process(timestampSerialized.begin(), timestampSerialized.begin() + MessageDefinition::SIGNATURE_TIMESTAMP_SIZE); + hasher.finish(); std::vector hash(picosha2::k_digest_size); - picosha2::hash256(data.begin(), data.begin() + actualSize, hash.begin(), hash.end()); + hasher.get_hash_bytes(hash.begin(), hash.end()); return deserialize(hash.data(), MessageDefinition::SIGNATURE_SIGNATURE_SIZE); } From 189a50e11ddf9ea279f68377d82201df00f2021a Mon Sep 17 00:00:00 2001 From: Stuart Miller Date: Thu, 11 Jan 2024 10:02:12 -0600 Subject: [PATCH 23/29] Add more message signing tests to increase code coverage. --- README.md | 7 ++++++- gcovr.cfg | 3 ++- tests/Network.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b38477a..916495b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ You can also include the library as a submodule in your project. ### Running the tests -Libmav uses [doctest](https://github.com/doctest/doctest/). +Libmav uses [doctest](https://github.com/doctest/doctest/) and [gcovr](https://github.com/gcovr/gcovr/). To run the tests, build the library, then run the test executable. Test results will be output to console. @@ -40,6 +40,11 @@ mkdir build && cd build && cmake .. && make tests ./tests/tests ``` +To test coverage, simple invoke the coverage tool from the root directory. +```bash +gcovr +``` + ## Getting started ### Loading a message set diff --git a/gcovr.cfg b/gcovr.cfg index 1440025..4e242a4 100644 --- a/gcovr.cfg +++ b/gcovr.cfg @@ -1,3 +1,4 @@ exclude-throw-branches = yes filter = include/mav -exclude = include/mav/rapidxml/* \ No newline at end of file +exclude = include/mav/rapidxml/* +exclude = include/mav/picosha2/* \ No newline at end of file diff --git a/tests/Network.cpp b/tests/Network.cpp index c0ad348..18b4e03 100644 --- a/tests/Network.cpp +++ b/tests/Network.cpp @@ -90,6 +90,9 @@ class DummyInterface : public NetworkInterface { } }; +uint64_t getTimestamp() { + return 770479200; +} TEST_CASE("Create network runtime") { @@ -122,6 +125,9 @@ TEST_CASE("Create network runtime") { DummyInterface interface; NetworkRuntime network({253, 1}, message_set, interface); + std::array key; + for (int i = 0 ; i < 32; i++) key[i] = i; + // send a heartbeat message, to establish a connection interface.addToReceiveQueue("\xfd\x09\x00\x00\x00\xfd\x01\x00\x00\x00\x04\x00\x00\x00\x01\x02\x03\x05\x06\x77\x53"s, interface_partner); auto connection = network.awaitConnection(); @@ -213,4 +219,50 @@ TEST_CASE("Create network runtime") { CHECK_EQ(message.name(), "HEARTBEAT"); CHECK(connection->alive()); } + + SUBCASE("Enable message signing") { + auto message = message_set.create("TEST_MESSAGE")({ + {"value", 42}, + {"text", "Hello World!"} + }); + interface.reset(); + network.enableMessageSigning(key); + connection->send(message); + CHECK(message.header().isSigned()); + // don't check anything after link_id in signature as the timestamp is dependent on current time + bool found = (interface.sendSpongeContains( + "\xfd\x10\x01\x00\x00\xfd\x01\xbc\x26\x00\x2a\x00\x00\x00\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\xfd\x33\x00"s, + interface_partner)); + CHECK(found); + } + + SUBCASE("Enable message signing with custom timestamp function") { + auto message = message_set.create("TEST_MESSAGE")({ + {"value", 42}, + {"text", "Hello World!"} + }); + interface.reset(); + network.enableMessageSigning(key, getTimestamp); + connection->send(message); + CHECK(message.header().isSigned()); + bool found = (interface.sendSpongeContains( + "\xfd\x10\x01\x00\x00\xfd\x01\xbc\x26\x00\x2a\x00\x00\x00\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\xfd\x33\x00\x60\x94\xec\x2d\x00\x00\x7b\xab\xfa\x1a\xed\xf9"s, + interface_partner)); + CHECK(found); + } + + SUBCASE("Disable message signing") { + auto message = message_set.create("TEST_MESSAGE")({ + {"value", 42}, + {"text", "Hello World!"} + }); + interface.reset(); + network.disableMessageSigning(); + connection->send(message); + CHECK(!message.header().isSigned()); + bool found = (interface.sendSpongeContains( + "\xfd\x10\x00\x00\x00\xfd\x01\xbc\x26\x00\x2a\x00\x00\x00\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x86\x37"s, + interface_partner)); + CHECK(found); + } } From 935ae37c6b6c292fe1d5f15181ecff6a5f97e51d Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Mon, 22 Jan 2024 15:20:45 +0100 Subject: [PATCH 24/29] sonarcloud: exclude picosha --- sonar-project.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index 18cc5f1..32b8a73 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,9 +5,9 @@ sonar.projectName = libmav sonar.sources = include/,tests/ -sonar.exclusions = include/mav/rapidxml/,tests/doctest.h +sonar.exclusions = include/mav/rapidxml/,tests/doctest.h,include/mav/picosha2/ -sonar.coverage.exclusions = tests/**/*,include/mav/rapidxml/ +sonar.coverage.exclusions = tests/**/*,include/mav/rapidxml/,include/mav/picosha2/ sonar.cpd.exclusions = tests/**/* sonar.sourceEncoding = UTF-8 From b36b1b60650d0ed4e145415e11407e4761af0939 Mon Sep 17 00:00:00 2001 From: Stuart Miller Date: Mon, 22 Jan 2024 08:40:15 -0600 Subject: [PATCH 25/29] Revert StringFormat noexcept changes from 00620a9 for GCC <= 9. --- include/mav/utils.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/mav/utils.h b/include/mav/utils.h index 2f420cf..fc7cf8e 100644 --- a/include/mav/utils.h +++ b/include/mav/utils.h @@ -116,7 +116,9 @@ namespace mav { static constexpr formatEndType end{}; static constexpr formatEndType toString{}; +#if __GNUC__ > 9 StringFormat() noexcept = default; +#endif template StringFormat& operator<< (const T &value) noexcept { From 967d91412a2adbdb273d74ae8074ac0d1c024673 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Tue, 23 Jan 2024 14:23:16 +0100 Subject: [PATCH 26/29] Update sonar-project.properties Co-authored-by: Jakob Widauer --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 32b8a73..b9c3e1d 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,7 +7,7 @@ sonar.sources = include/,tests/ sonar.exclusions = include/mav/rapidxml/,tests/doctest.h,include/mav/picosha2/ -sonar.coverage.exclusions = tests/**/*,include/mav/rapidxml/,include/mav/picosha2/ +sonar.coverage.exclusions = tests/**/*,include/mav/rapidxml/*,include/mav/picosha2/* sonar.cpd.exclusions = tests/**/* sonar.sourceEncoding = UTF-8 From 115d4addc5137a5368a4fcb0d42d126a7d69102a Mon Sep 17 00:00:00 2001 From: Stuart Miller Date: Tue, 23 Jan 2024 08:10:25 -0600 Subject: [PATCH 27/29] Add Ubuntu 20 build to CI pipelines. --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9840137..15323eb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,8 @@ jobs: platform: - runner: ubuntu-latest sonarcloud-build-wrapper : build-wrapper-linux-x86-64 + - runner: ubuntu-20.04 + sonarcloud-build-wrapper : build-wrapper-linux-x86-64 - runner: macos-latest sonarcloud-build-wrapper : build-wrapper-macosx-x86 runs-on: ${{ matrix.platform.runner }} From 1f6f08d4611de5dd4e5d13532943b12b7e7e7316 Mon Sep 17 00:00:00 2001 From: Thomas Debrunner Date: Tue, 23 Jan 2024 16:13:09 +0100 Subject: [PATCH 28/29] fixed sonar-project.properties --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index b9c3e1d..4d9c71f 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,7 +5,7 @@ sonar.projectName = libmav sonar.sources = include/,tests/ -sonar.exclusions = include/mav/rapidxml/,tests/doctest.h,include/mav/picosha2/ +sonar.exclusions = include/mav/rapidxml/*,tests/doctest.h,include/mav/picosha2/* sonar.coverage.exclusions = tests/**/*,include/mav/rapidxml/*,include/mav/picosha2/* sonar.cpd.exclusions = tests/**/* From 8d290337f67bdf95bae84e56a14c57066fc3cc7d Mon Sep 17 00:00:00 2001 From: Stuart Miller Date: Tue, 23 Jan 2024 16:34:47 -0600 Subject: [PATCH 29/29] Add more test coverage for signed packet handling. --- tests/Message.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/Message.cpp b/tests/Message.cpp index e45a58f..166a9f0 100644 --- a/tests/Message.cpp +++ b/tests/Message.cpp @@ -378,7 +378,6 @@ TEST_CASE("Message set creation") { "Message ID 9915 (BIG_MESSAGE) \n char_arr_field: \"Hello World!\"\n double_field: 9\n float_arr_field: 1, 2, 3\n float_field: 10\n int16_field: -4\n int32_arr_field: 4, 5, 6\n int32_field: -6\n int64_field: 8\n int8_field: -2\n uint16_field: 3\n uint32_field: 5\n uint64_field: 7\n uint8_field: 1\n"); } - SUBCASE("Sign a packet") { std::array key; @@ -386,6 +385,11 @@ TEST_CASE("Message set creation") { uint64_t timestamp = 770479200; + // Attempt to access signature before signed (const & non-const versions) + const auto const_message = message_set.create("UINT8_ONLY_MESSAGE"); + CHECK_THROWS_AS(message.signature(), std::runtime_error); + CHECK_THROWS_AS(const_message.signature(), std::runtime_error); + uint32_t wire_size = message.finalize(5, {6, 7}, key, timestamp); CHECK_EQ(wire_size, 26);