diff --git a/.clang-format b/.clang-format index d6b6f78..7de0803 100644 --- a/.clang-format +++ b/.clang-format @@ -1,15 +1,15 @@ --- -Language: Cpp +Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: true AlignEscapedNewlines: Left -AlignOperands: true +AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: false +AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: Never @@ -42,26 +42,26 @@ BreakBeforeTernaryOperators: true BreakConstructorInitializers: AfterColon # BreakInheritanceList: AfterColon BreakStringLiterals: true -ColumnLimit: 120 -CommentPragmas: '^ (coverity|pragma:)' +ColumnLimit: 120 +CommentPragmas: '^ (coverity|pragma:)' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false -DisableFormat: false +DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true -ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeBlocks: Preserve IndentCaseLabels: false IndentPPDirectives: AfterHash -IndentWidth: 4 +IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' -MacroBlockEnd: '' +MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None PenaltyBreakAssignment: 2 @@ -72,8 +72,8 @@ PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 10000 # Raised intentionally because it hurts readability PointerAlignment: Left -ReflowComments: true -SortIncludes: false +ReflowComments: true +SortIncludes: Never SortUsingDeclarations: false SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: true @@ -85,12 +85,12 @@ SpaceBeforeCtorInitializerColon: true SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 -SpacesInAngles: false +SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp11 -TabWidth: 8 -UseTab: Never +Standard: c++14 +TabWidth: 8 +UseTab: Never ... diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 384a609..aece7ab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: c-compiler: clang cxx-compiler: clang++ steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh @@ -59,7 +59,7 @@ jobs: c-compiler: clang cxx-compiler: clang++ steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: | sudo apt update -y && sudo apt upgrade -y sudo apt install gcc-multilib g++-multilib @@ -89,7 +89,7 @@ jobs: mcu: at90can64 flags: -Wall -Wextra -Werror -pedantic -Wconversion -Wtype-limits steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: | sudo apt update -y && sudo apt upgrade -y sudo apt install gcc-avr avr-libc @@ -105,7 +105,7 @@ jobs: env: flags: -Wall -Wextra -Werror -pedantic -Wconversion -Wtype-limits -Wcast-align -Wfatal-errors steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: | sudo apt update -y && sudo apt upgrade -y sudo apt-get install -y gcc-arm-none-eabi @@ -116,8 +116,8 @@ jobs: if: github.event_name == 'push' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: DoozyX/clang-format-lint-action@v0.15 + - uses: actions/checkout@v4 + - uses: DoozyX/clang-format-lint-action@v0.17 with: source: './libcanard ./tests' exclude: './tests/catch' @@ -132,13 +132,13 @@ jobs: contains(github.event.head_commit.message, '#sonar') runs-on: ubuntu-latest env: - SONAR_SCANNER_VERSION: 4.8.0.2856 + SONAR_SCANNER_VERSION: 5.0.1.3006 SONAR_SERVER_URL: "https://sonarcloud.io" BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -148,13 +148,13 @@ jobs: sudo apt install -y gcc-multilib g++-multilib - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: zulu - name: Cache SonarCloud packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar diff --git a/.gitignore b/.gitignore index f0e9f65..de4e106 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,7 @@ build-avr/ # Pycache __pycache__/ + +# OS stuff +.DS_Store +*.bak diff --git a/README.md b/README.md index f651750..3e61a36 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,10 @@ If you find the examples to be unclear or incorrect, please, open a ticket. ## Revisions +### v3.2 + +- Added new `canardRxGetSubscription`. + ### v3.1 - Remove the Dockerfile; use [toolshed](https://github.com/OpenCyphal/docker_toolchains/pkgs/container/toolshed) diff --git a/libcanard/.clang-tidy b/libcanard/.clang-tidy index bd04775..c711162 100644 --- a/libcanard/.clang-tidy +++ b/libcanard/.clang-tidy @@ -27,6 +27,5 @@ CheckOptions: value: '99' WarningsAsErrors: '*' HeaderFilterRegex: '.*' -AnalyzeTemporaryDtors: false FormatStyle: file ... diff --git a/libcanard/canard.c b/libcanard/canard.c index 863de50..ec6e6d3 100644 --- a/libcanard/canard.c +++ b/libcanard/canard.c @@ -1238,6 +1238,35 @@ int8_t canardRxUnsubscribe(CanardInstance* const ins, return out; } +int8_t canardRxGetSubscription(CanardInstance* const ins, + const CanardTransferKind transfer_kind, + const CanardPortID port_id, + CanardRxSubscription** const out_subscription) +{ + int8_t out = -CANARD_ERROR_INVALID_ARGUMENT; + const size_t tk = (size_t) transfer_kind; + if ((ins != NULL) && (tk < CANARD_NUM_TRANSFER_KINDS)) + { + CanardPortID port_id_mutable = port_id; + CanardRxSubscription* const sub = (CanardRxSubscription*) (void*) + cavlSearch(&ins->rx_subscriptions[tk], &port_id_mutable, &rxSubscriptionPredicateOnPortID, NULL); + if (sub != NULL) + { + CANARD_ASSERT(sub->port_id == port_id); + if (out_subscription != NULL) + { + *out_subscription = sub; + } + out = 1; + } + else + { + out = 0; + } + } + return out; +} + CanardFilter canardMakeFilterForSubject(const CanardPortID subject_id) { CanardFilter out = {0}; diff --git a/libcanard/canard.h b/libcanard/canard.h index 0f1b5a7..4cd172e 100644 --- a/libcanard/canard.h +++ b/libcanard/canard.h @@ -94,7 +94,7 @@ extern "C" { /// Semantic version of this library (not the Cyphal specification). /// API will be backward compatible within the same major version. #define CANARD_VERSION_MAJOR 3 -#define CANARD_VERSION_MINOR 1 +#define CANARD_VERSION_MINOR 2 /// The version number of the Cyphal specification implemented by this library. #define CANARD_CYPHAL_SPECIFICATION_VERSION_MAJOR 1 @@ -649,6 +649,21 @@ int8_t canardRxUnsubscribe(CanardInstance* const ins, const CanardTransferKind transfer_kind, const CanardPortID port_id); +/// This function allows to check the effect of canardRxSubscribe() and canardRxUnsubscribe(). +/// +/// The return value is 1 if the specified subscription exists, 0 otherwise. +/// The return value is a negated invalid argument error if any of the input arguments are invalid. +/// Output out_subscription could be NULL, but if it is not, it will be populated with the pointer to the existing +/// subscription. In case the subscription does not exist (or error), out_subscription won't be touched. +/// Result pointer to the subscription is valid until the subscription is terminated. +/// +/// The time complexity is logarithmic from the number of current subscriptions under the specified transfer kind. +/// This function does not allocate new memory. +int8_t canardRxGetSubscription(CanardInstance* const ins, + const CanardTransferKind transfer_kind, + const CanardPortID port_id, + CanardRxSubscription** const out_subscription); + /// Utilities for generating CAN controller hardware acceptance filter configurations /// to accept specific subjects, services, or nodes. /// diff --git a/tests/.clang-tidy b/tests/.clang-tidy index 85da250..c404eac 100644 --- a/tests/.clang-tidy +++ b/tests/.clang-tidy @@ -39,6 +39,5 @@ Checks: >- -modernize-macro-to-enum, WarningsAsErrors: '*' HeaderFilterRegex: '.*\.hpp' -AnalyzeTemporaryDtors: false FormatStyle: file ... diff --git a/tests/helpers.hpp b/tests/helpers.hpp index 618f3c8..3ca4a06 100644 --- a/tests/helpers.hpp +++ b/tests/helpers.hpp @@ -200,6 +200,18 @@ class Instance return canardRxUnsubscribe(&canard_, transfer_kind, port_id); } + [[nodiscard]] auto rxHasSubscription(const CanardTransferKind transfer_kind, const CanardPortID port_id) + { + return canardRxGetSubscription(&canard_, transfer_kind, port_id, nullptr); + } + + [[nodiscard]] auto rxGetSubscription(const CanardTransferKind transfer_kind, const CanardPortID port_id) + { + CanardRxSubscription* out_subscription = nullptr; + canardRxGetSubscription(&canard_, transfer_kind, port_id, &out_subscription); + return out_subscription; + } + /// The items are sorted by port-ID. [[nodiscard]] auto getSubs(const CanardTransferKind tk) const -> std::vector { diff --git a/tests/test_public_rx.cpp b/tests/test_public_rx.cpp index 932d654..b08b1e1 100644 --- a/tests/test_public_rx.cpp +++ b/tests/test_public_rx.cpp @@ -49,8 +49,13 @@ TEST_CASE("RxBasic0") // Create a message subscription. CanardRxSubscription sub_msg{}; + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(1 == ins.rxSubscribe(CanardTransferKindMessage, 0b0110011001100, 32, 2'000'000, sub_msg)); // New. + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); + REQUIRE(&sub_msg == ins.rxGetSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(0 == ins.rxSubscribe(CanardTransferKindMessage, 0b0110011001100, 16, 1'000'000, sub_msg)); // Replaced. + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); + REQUIRE(&sub_msg == ins.rxGetSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(ins.getMessageSubs().at(0) == &sub_msg); REQUIRE(ins.getMessageSubs().at(0)->port_id == 0b0110011001100); REQUIRE(ins.getMessageSubs().at(0)->extent == 16); @@ -61,7 +66,10 @@ TEST_CASE("RxBasic0") // Create a request subscription. CanardRxSubscription sub_req{}; + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindRequest, 0b0000110011)); REQUIRE(1 == ins.rxSubscribe(CanardTransferKindRequest, 0b0000110011, 20, 3'000'000, sub_req)); + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindRequest, 0b0000110011)); + REQUIRE(&sub_req == ins.rxGetSubscription(CanardTransferKindRequest, 0b0000110011)); REQUIRE(ins.getMessageSubs().at(0) == &sub_msg); REQUIRE(ins.getResponseSubs().empty()); REQUIRE(ins.getRequestSubs().at(0) == &sub_req); @@ -72,7 +80,10 @@ TEST_CASE("RxBasic0") // Create a response subscription. CanardRxSubscription sub_res{}; + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindResponse, 0b0000111100)); REQUIRE(1 == ins.rxSubscribe(CanardTransferKindResponse, 0b0000111100, 10, 100'000, sub_res)); + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindResponse, 0b0000111100)); + REQUIRE(&sub_res == ins.rxGetSubscription(CanardTransferKindResponse, 0b0000111100)); REQUIRE(ins.getMessageSubs().at(0) == &sub_msg); REQUIRE(ins.getResponseSubs().at(0) == &sub_res); REQUIRE(ins.getResponseSubs().at(0)->port_id == 0b0000111100); @@ -83,7 +94,10 @@ TEST_CASE("RxBasic0") // Create a second response subscription. It will come before the one we added above due to lower port-ID. CanardRxSubscription sub_res2{}; + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindResponse, 0b0000000000)); REQUIRE(1 == ins.rxSubscribe(CanardTransferKindResponse, 0b0000000000, 10, 1'000, sub_res2)); + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindResponse, 0b0000000000)); + REQUIRE(&sub_res2 == ins.rxGetSubscription(CanardTransferKindResponse, 0b0000000000)); REQUIRE(ins.getMessageSubs().at(0) == &sub_msg); REQUIRE(ins.getResponseSubs().at(0) == &sub_res2); REQUIRE(ins.getResponseSubs().at(0)->port_id == 0b0000000000); @@ -165,8 +179,13 @@ TEST_CASE("RxBasic0") REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == (3 * sizeof(RxSession) + 16 + 20)); // Destroy the message subscription and the buffer to free up memory. + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(1 == ins.rxUnsubscribe(CanardTransferKindMessage, 0b0110011001100)); + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); + REQUIRE(nullptr == ins.rxGetSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(0 == ins.rxUnsubscribe(CanardTransferKindMessage, 0b0110011001100)); // Repeat, nothing to do. + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); + REQUIRE(nullptr == ins.rxGetSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 4); REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == (2 * sizeof(RxSession) + 16 + 20)); ins.getAllocator().deallocate(msg_payload); @@ -198,12 +217,31 @@ TEST_CASE("RxBasic0") REQUIRE(subscription == nullptr); // Unsubscribe. + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindRequest, 0b0000110011)); REQUIRE(1 == ins.rxUnsubscribe(CanardTransferKindRequest, 0b0000110011)); + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindRequest, 0b0000110011)); + REQUIRE(nullptr == ins.rxGetSubscription(CanardTransferKindRequest, 0b0000110011)); REQUIRE(0 == ins.rxUnsubscribe(CanardTransferKindRequest, 0b0000110011)); + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindRequest, 0b0000110011)); + REQUIRE(nullptr == ins.rxGetSubscription(CanardTransferKindRequest, 0b0000110011)); + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindResponse, 0b0000111100)); REQUIRE(1 == ins.rxUnsubscribe(CanardTransferKindResponse, 0b0000111100)); + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindResponse, 0b0000111100)); + REQUIRE(nullptr == ins.rxGetSubscription(CanardTransferKindResponse, 0b0000111100)); REQUIRE(0 == ins.rxUnsubscribe(CanardTransferKindResponse, 0b0000111100)); + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindResponse, 0b0000111100)); + REQUIRE(nullptr == ins.rxGetSubscription(CanardTransferKindResponse, 0b0000111100)); + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindResponse, 0b0000000000)); REQUIRE(1 == ins.rxUnsubscribe(CanardTransferKindResponse, 0b0000000000)); + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindResponse, 0b0000000000)); + REQUIRE(nullptr == ins.rxGetSubscription(CanardTransferKindResponse, 0b0000000000)); REQUIRE(0 == ins.rxUnsubscribe(CanardTransferKindResponse, 0b0000000000)); + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindResponse, 0b0000000000)); + // Should not modify output if the subscription is not found. + CanardRxSubscription* out_sub = &sub_msg; + REQUIRE(nullptr == ins.rxGetSubscription(CanardTransferKindResponse, 0b0000000000)); + REQUIRE(0 == canardRxGetSubscription(&ins.getInstance(), CanardTransferKindResponse, 0b0000000000, &out_sub)); + REQUIRE(out_sub == &sub_msg); } TEST_CASE("RxAnonymous") @@ -239,7 +277,10 @@ TEST_CASE("RxAnonymous") void* const my_user_reference = &ins; CanardRxSubscription sub_msg{}; sub_msg.user_reference = my_user_reference; + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(1 == ins.rxSubscribe(CanardTransferKindMessage, 0b0110011001100, 16, 2'000'000, sub_msg)); // New. + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); + REQUIRE(&sub_msg == ins.rxGetSubscription(CanardTransferKindMessage, 0b0110011001100)); // Accepted anonymous message. subscription = nullptr; @@ -322,6 +363,19 @@ TEST_CASE("RxSubscriptionErrors") REQUIRE(-CANARD_ERROR_INVALID_ARGUMENT == canardRxUnsubscribe(nullptr, CanardTransferKindMessage, 0)); REQUIRE(-CANARD_ERROR_INVALID_ARGUMENT == canardRxUnsubscribe(&ins.getInstance(), kind.value, 0)); + REQUIRE(-CANARD_ERROR_INVALID_ARGUMENT == canardRxGetSubscription(nullptr, CanardTransferKindMessage, 0, nullptr)); + REQUIRE(-CANARD_ERROR_INVALID_ARGUMENT == canardRxGetSubscription(&ins.getInstance(), kind.value, 0, nullptr)); + + // These calls should not touch `fake_ptr`! + // + CanardRxSubscription fake_sub{}; + CanardRxSubscription* fake_ptr = &fake_sub; + REQUIRE(-CANARD_ERROR_INVALID_ARGUMENT == + canardRxGetSubscription(nullptr, CanardTransferKindMessage, 0, &fake_ptr)); + REQUIRE(fake_ptr == &fake_sub); + REQUIRE(-CANARD_ERROR_INVALID_ARGUMENT == canardRxGetSubscription(&ins.getInstance(), kind.value, 0, &fake_ptr)); + REQUIRE(fake_ptr == &fake_sub); + CanardFrame frame{}; frame.payload_size = 1U; CanardRxTransfer transfer{}; @@ -358,7 +412,10 @@ TEST_CASE("Issue189") // https://github.com/OpenCyphal/libcanard/issues/189 // Create a message subscription. CanardRxSubscription sub_msg{}; + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(1 == ins.rxSubscribe(CanardTransferKindMessage, 0b0110011001100, 50, 1'000'000, sub_msg)); + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); + REQUIRE(&sub_msg == ins.rxGetSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(ins.getMessageSubs().at(0) == &sub_msg); REQUIRE(ins.getMessageSubs().at(0)->port_id == 0b0110011001100); REQUIRE(ins.getMessageSubs().at(0)->extent == 50); @@ -465,7 +522,10 @@ TEST_CASE("Issue212") // Create a message subscription with the transfer-ID timeout of just one microsecond. CanardRxSubscription sub_msg{}; + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(1 == ins.rxSubscribe(CanardTransferKindMessage, 0b0110011001100, 50, 1, sub_msg)); + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); + REQUIRE(&sub_msg == ins.rxGetSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(ins.getMessageSubs().at(0) == &sub_msg); REQUIRE(ins.getMessageSubs().at(0)->port_id == 0b0110011001100); REQUIRE(ins.getMessageSubs().at(0)->extent == 50); @@ -569,7 +629,10 @@ TEST_CASE("RxFixedTIDWithSmallTimeout") // Create a message subscription with the transfer-ID timeout of just five microseconds. CanardRxSubscription sub_msg{}; + REQUIRE(0 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(1 == ins.rxSubscribe(CanardTransferKindMessage, 0b0110011001100, 50, 5, sub_msg)); + REQUIRE(1 == ins.rxHasSubscription(CanardTransferKindMessage, 0b0110011001100)); + REQUIRE(&sub_msg == ins.rxGetSubscription(CanardTransferKindMessage, 0b0110011001100)); REQUIRE(ins.getMessageSubs().at(0) == &sub_msg); REQUIRE(ins.getMessageSubs().at(0)->port_id == 0b0110011001100); REQUIRE(ins.getMessageSubs().at(0)->extent == 50);