From b557c55fa2888d8b976441a61431e43fd17f8978 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 29 Apr 2025 06:45:48 +0200 Subject: [PATCH 1/8] first implementation of usubscription --- .../up-cpp/client/usubscription/v3/Consumer.h | 101 ++------------ .../client/usubscription/v3/RequestBuilder.h | 119 ++++++++++++++++ .../usubscription/v3/RpcClientUSubscription.h | 110 +++++++++++++++ .../client/usubscription/v3/USubscription.h | 49 +++++++ .../v3/USubscriptionUUriBuilder.h | 51 +++++++ include/up-cpp/communication/RpcClient.h | 43 ++++++ include/up-cpp/utils/ProtoConverter.h | 108 +++++++++++++++ src/client/usubscription/v3/Consumer.cpp | 17 ++- .../usubscription/v3/RequestBuilder.cpp | 68 ++++++++++ .../v3/RpcClientUSubscription.cpp | 116 ++++++++++++++++ .../v3/USubscriptionUUriBuilder.cpp | 52 +++++++ src/utils/ProtoConverter.cpp | 8 ++ test/CMakeLists.txt | 3 + .../client/usubscription/v3/ConsumerTest.cpp | 7 +- .../v3/RpcClientUSubscriptionTest.cpp | 128 ++++++++++++++++++ 15 files changed, 881 insertions(+), 99 deletions(-) create mode 100644 include/up-cpp/client/usubscription/v3/RequestBuilder.h create mode 100644 include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h create mode 100644 include/up-cpp/client/usubscription/v3/USubscription.h create mode 100644 include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h create mode 100644 src/client/usubscription/v3/RequestBuilder.cpp create mode 100644 src/client/usubscription/v3/RpcClientUSubscription.cpp create mode 100644 src/client/usubscription/v3/USubscriptionUUriBuilder.cpp create mode 100644 test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp diff --git a/include/up-cpp/client/usubscription/v3/Consumer.h b/include/up-cpp/client/usubscription/v3/Consumer.h index 2fed6ab4b..b9678d315 100644 --- a/include/up-cpp/client/usubscription/v3/Consumer.h +++ b/include/up-cpp/client/usubscription/v3/Consumer.h @@ -20,93 +20,13 @@ #include #include -#include +#include "RequestBuilder.h" +#include "USubscriptionUUriBuilder.h" namespace uprotocol::client::usubscription::v3 { using uprotocol::core::usubscription::v3::SubscriptionRequest; using uprotocol::core::usubscription::v3::UnsubscribeRequest; using uprotocol::core::usubscription::v3::Update; -using uprotocol::core::usubscription::v3::uSubscription; - -/** - * @struct ConsumerOptions - * @brief Additional details for uSubscription service. - * - * Each member represents an optional parameter for the uSubscription service. - */ -struct ConsumerOptions { - /// Permission level of the subscription request - std::optional permission_level; - /// TAP token for access. - std::optional token; - /// Expiration time of the subscription. - std::optional when_expire; - /// Sample period for the subscription messages in milliseconds. - std::optional sample_period_ms; - /// Details of the subscriber. - std::optional subscriber_details; - /// Details of the subscription. - std::optional subscription_details; -}; - -/// @struct uSubscriptionUUriBuilder -/// @brief Structure to build uSubscription request URIs. -/// -/// This structure is used to build URIs for uSubscription service. It uses the -/// service options from uSubscription proto to set the authority name, ue_id, -/// ue_version_major, and the notification topic resource ID in the URI. -struct USubscriptionUUriBuilder { -private: - /// URI for the uSubscription service - v1::UUri uri_; - /// Resource ID of the notification topic - uint32_t sink_resource_id_; - -public: - /// @brief Constructor for uSubscriptionUUriBuilder. - USubscriptionUUriBuilder() { - // Get the service descriptor - const google::protobuf::ServiceDescriptor* service = - uSubscription::descriptor(); - const auto& service_options = service->options(); - - // Get the service options - const auto& service_name = - service_options.GetExtension(uprotocol::service_name); - const auto& service_version_major = - service_options.GetExtension(uprotocol::service_version_major); - const auto& service_id = - service_options.GetExtension(uprotocol::service_id); - const auto& notification_topic = - service_options.GetExtension(uprotocol::notification_topic, 0); - - // Set the values in the URI - uri_.set_authority_name(service_name); - uri_.set_ue_id(service_id); - uri_.set_ue_version_major(service_version_major); - sink_resource_id_ = notification_topic.id(); - } - - /// @brief Get the URI with a specific resource ID. - /// - /// @param resource_id The resource ID to set in the URI. - /// - /// @return The URI with the specified resource ID. - v1::UUri getServiceUriWithResourceId(uint32_t resource_id) const { - v1::UUri uri = uri_; // Copy the base URI - uri.set_resource_id(resource_id); - return uri; - } - - /// @brief Get the notification URI. - /// - /// @return The notification URI. - v1::UUri getNotificationUri() const { - v1::UUri uri = uri_; // Copy the base URI - uri.set_resource_id(sink_resource_id_); - return uri; - } -}; /// @brief Interface for uEntities to create subscriptions. /// @@ -133,7 +53,7 @@ struct Consumer { const v1::UUri& subscription_topic, ListenCallback&& callback, v1::UPriority priority, std::chrono::milliseconds subscription_request_ttl, - ConsumerOptions consumer_options); + core::usubscription::v3::USubscriptionOptions consumer_options); /// @brief Unsubscribe from the topic and call uSubscription service to /// close the subscription. @@ -158,9 +78,10 @@ struct Consumer { /// /// @param transport Transport to register with. /// @param subscriber_details Additional details about the subscriber. - Consumer(std::shared_ptr transport, - v1::UUri subscription_topic, - ConsumerOptions consumer_options = {}); + Consumer( + std::shared_ptr transport, + v1::UUri subscription_topic, + core::usubscription::v3::USubscriptionOptions consumer_options = {}); private: // Transport @@ -169,10 +90,10 @@ struct Consumer { // Topic to subscribe to const v1::UUri subscription_topic_; // Additional details about uSubscription service - ConsumerOptions consumer_options_; + core::usubscription::v3::USubscriptionOptions consumer_options_; // URI info about the uSubscription service - USubscriptionUUriBuilder uSubscriptionUUriBuilder_; + core::usubscription::v3::USubscriptionUUriBuilder uSubscriptionUUriBuilder_; // Subscription updates std::unique_ptr noficationSinkHandle_; @@ -191,10 +112,10 @@ struct Consumer { friend std::unique_ptr std::make_unique, const uprotocol::v1::UUri, - uprotocol::client::usubscription::v3::ConsumerOptions>( + uprotocol::core::usubscription::v3::USubscriptionOptions>( std::shared_ptr&&, const uprotocol::v1::UUri&&, - uprotocol::client::usubscription::v3::ConsumerOptions&&); + uprotocol::core::usubscription::v3::USubscriptionOptions&&); /// @brief Build SubscriptionRequest for subscription request SubscriptionRequest buildSubscriptionRequest(); diff --git a/include/up-cpp/client/usubscription/v3/RequestBuilder.h b/include/up-cpp/client/usubscription/v3/RequestBuilder.h new file mode 100644 index 000000000..b87579280 --- /dev/null +++ b/include/up-cpp/client/usubscription/v3/RequestBuilder.h @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef UP_CPP_CLIENT_USUBSCRIPTION_V3_REQUESTBUILDER_H +#define UP_CPP_CLIENT_USUBSCRIPTION_V3_REQUESTBUILDER_H +#include +#include + +#include + +namespace uprotocol::core::usubscription::v3 { + +/** + * @struct USubscriptionOptions + * @brief Additional details for uSubscription service. + * + * Each member represents an optional parameter for the uSubscription service. + */ +struct USubscriptionOptions { + /// Permission level of the subscription request + std::optional permission_level; + /// TAP token for access. + std::optional token; + /// Expiration time of the subscription. + std::optional when_expire; + /// Sample period for the subscription messages in milliseconds. + std::optional sample_period_ms; + /// Details of the subscriber. + std::optional subscriber_details; + /// Details of the subscription. + std::optional subscription_details; +}; + +/** + * @brief Builds different requests using specified options. + * + * This struct facilitates the construction of requests based on + * `USubscriptionOptions`, providing methods to build different requests. + */ +struct RequestBuilder { + /** + * @brief Constructs a RequestBuilder with the given subscription options. + * + * @param options Subscription options to configure the requests. Defaults + * to empty options. + */ + explicit RequestBuilder(USubscriptionOptions options = {}) + : options_(std::move(options)) {} + + /** + * @brief Builds a subscription request for a given topic. + * + * @param topic The `v1::UUri` representing the topic for the subscription. + * + * @return A `SubscriptionRequest` configured for the specified topic. + */ + SubscriptionRequest buildSubscriptionRequest(const v1::UUri& topic) const; + + /** + * @brief Builds an unsubscription request for a given topic. + * + * @param topic The `v1::UUri` representing the topic to unsubscribe from. + * + * @return An `UnsubscribeRequest` configured for the specified topic. + */ + static UnsubscribeRequest buildUnsubscribeRequest(const v1::UUri& topic); + + /** + * @brief Build fetch subscritions request for a given topic. + * + * @param topic The `v1::UUri` representing the topic to fetch. + * + * @return An `FetchSubscriptionsRequest` configured for the specified topic. + */ + static FetchSubscriptionsRequest buildFetchSubscriptionsRequest(const v1::UUri& topic); + + /** + * @brief Build fetch subscritions request for a given subscriber. + * + * @param subscriber The `SubscriberInfo` representing the subscriber to fetch. + * + * @return An `FetchSubscriptionsRequest` configured for the specified subscriber. + */ + static FetchSubscriptionsRequest buildFetchSubscriptionsRequest(const SubscriberInfo& subscriber); + + /** + * @brief Build fetch subscribers request for a given topic. + * + * @param topic The `v1::UUri` representing the topic to fetch. + * + * @return An `FetchSubscribersRequest` configured for the specified topic. + */ + static FetchSubscribersRequest buildFetchSubscribersRequest(const v1::UUri& topic); + + /** + * @brief Build notification request for a given topic. Subscription change notifications + * MUST use topic SubscriptionChange with resource id 0x8000, as per the protobuf definition. + * + * @param topic The `v1::UUri` representing the topic to (un)register for/from. + * + * @return An `NotificationsRequest` configured for the specified topic. + */ + static NotificationsRequest buildNotificationRequest(const v1::UUri& topic); + + +private: + USubscriptionOptions options_; ///< Options used to configure the requests. +}; + +} // namespace uprotocol::core::usubscription::v3 +#endif // UP_CPP_CLIENT_USUBSCRIPTION_V3_REQUESTBUILDER_H diff --git a/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h b/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h new file mode 100644 index 000000000..56f4ce4e4 --- /dev/null +++ b/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef UP_CPP_CLIENT_USUBSCRIPTION_V3_RPCCLIENTUSUBSCRIPTION_H +#define UP_CPP_CLIENT_USUBSCRIPTION_V3_RPCCLIENTUSUBSCRIPTION_H + +#include +#include +#include +#include + +#include "up-cpp/client/usubscription/v3/USubscription.h" +#include "up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h" + +/// The uEntity (type) identifier of the uSubscription service. +constexpr uint32_t USUBSCRIPTION_TYPE_ID = 0x00000000; +/// The (latest) major version of the uSubscription service. +constexpr uint8_t USUBSCRIPTION_VERSION_MAJOR = 0x03; +/// The resource identifier of uSubscription's _subscribe_ operation. +constexpr uint16_t RESOURCE_ID_SUBSCRIBE = 0x0001; +/// The resource identifier of uSubscription's _unsubscribe_ operation. +constexpr uint16_t RESOURCE_ID_UNSUBSCRIBE = 0x0002; +/// The resource identifier of uSubscription's _fetch subscriptions_ operation. +constexpr uint16_t RESOURCE_ID_FETCH_SUBSCRIPTIONS = 0x0003; +/// The resource identifier of uSubscription's _register for notifications_ operation. +constexpr uint16_t RESOURCE_ID_REGISTER_FOR_NOTIFICATIONS = 0x0006; +/// The resource identifier of uSubscription's _unregister for notifications_ operation. +constexpr uint16_t RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS = 0x0007; +/// The resource identifier of uSubscription's _fetch subscribers_ operation. +constexpr uint16_t RESOURCE_ID_FETCH_SUBSCRIBERS = 0x0008; +// TODO(lennart) see default_call_options() for the request in Rust +constexpr auto USUBSCRIPTION_REQUEST_TTL = + std::chrono::milliseconds(0x0800); // TODO(lennart) change time + +namespace uprotocol::core::usubscription::v3 { +using v3::SubscriptionRequest; +using v3::UnsubscribeRequest; + +/// @brief Interface for uEntities to create subscriptions. +/// +/// Like all L3 client APIs, the RpcClientUSubscription is a wrapper on top of +/// the L2 Communication APIs and USubscription service. +struct RpcClientUSubscription : USubscription { + using RpcClientUSubscriptionOrStatus = + utils::Expected, v1::UStatus>; + using ListenCallback = transport::UTransport::ListenCallback; + using ListenHandle = transport::UTransport::ListenHandle; + + template + Response invokeResponse(communication::RpcClient rpc_client); + + /// @brief Subscribe to the topic + /// + utils::Expected subscribe( + const SubscriptionRequest& subscription_request) override; + + /// @brief Unsubscribe from the topic + /// + utils::Expected unsubscribe( + const UnsubscribeRequest& unsubscribe_request) override; + + /// @brief Fetch all subscriptions either by topic or subscriber + /// + utils::Expected fetch_subscriptions( + const FetchSubscriptionsRequest& fetch_subscribers) override; + + /// @brief Fetch all subscribers + /// + utils::Expected fetch_subscribers( + const FetchSubscribersRequest& fetch_subscribers) override; + + /// @brief Register for notifications + /// + utils::Expected register_for_notifications(const + NotificationsRequest& register_notifications_request) override ; + + /// @brief Unregister for notifications + /// + utils::Expected unregister_for_notifications(const + NotificationsRequest& unregister_notifications_request) override ; + + /// @brief Constructor + /// + /// @param transport Transport to register with. + explicit RpcClientUSubscription( + std::shared_ptr transport) + : transport_(std::move(transport)) {} + + /// @brief Destructor + ~RpcClientUSubscription() override = default; + +private: + // Transport + std::shared_ptr transport_; + + // URI info about the uSubscription service + USubscriptionUUriBuilder uuri_builder_; +}; + +} // namespace uprotocol::core::usubscription::v3 + +#endif // UP_CPP_CLIENT_USUBSCRIPTION_V3_RPCCLIENTUSUBSCRIPTION_H \ No newline at end of file diff --git a/include/up-cpp/client/usubscription/v3/USubscription.h b/include/up-cpp/client/usubscription/v3/USubscription.h new file mode 100644 index 000000000..e6673588c --- /dev/null +++ b/include/up-cpp/client/usubscription/v3/USubscription.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef UP_CPP_CLIENT_USUBSCRIPTION_V3_USUBSCRIPTION_H +#define UP_CPP_CLIENT_USUBSCRIPTION_V3_USUBSCRIPTION_H +#include +#include +#include + +#include "up-cpp/utils/Expected.h" + +namespace uprotocol::core::usubscription::v3 { + +struct USubscription { + template + using ResponseOrStatus = utils::Expected; + + virtual ~USubscription() = default; + + virtual ResponseOrStatus subscribe( + const SubscriptionRequest& subscription_request) = 0; + + virtual ResponseOrStatus unsubscribe( + const UnsubscribeRequest& unsubscribe_request) = 0; + + virtual ResponseOrStatus fetch_subscriptions(const + FetchSubscriptionsRequest& fetch_subscribers_request) = 0; + + virtual ResponseOrStatus register_for_notifications(const + NotificationsRequest& register_notifications_request) =0 ; + + virtual ResponseOrStatus unregister_for_notifications(const + NotificationsRequest& unregister_notifications_request) = 0; + + virtual ResponseOrStatus fetch_subscribers(const + FetchSubscribersRequest& fetch_subscribers_request) = 0; +}; + +} // namespace uprotocol::core::usubscription::v3 + +#endif // UP_CPP_CLIENT_USUBSCRIPTION_V3_USUBSCRIPTION_H diff --git a/include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h b/include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h new file mode 100644 index 000000000..63bcdec79 --- /dev/null +++ b/include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef UP_CPP_CLIENT_USUBSCRIPTION_V3_USUBSCRIPTIONUURIBUILDER_H +#define UP_CPP_CLIENT_USUBSCRIPTION_V3_USUBSCRIPTIONUURIBUILDER_H + +#include +#include + +namespace uprotocol::core::usubscription::v3 { +/// @struct uSubscriptionUUriBuilder +/// @brief Structure to build uSubscription request URIs. +/// +/// This structure is used to build URIs for uSubscription service. It uses the +/// service options from uSubscription proto to set the authority name, ue_id, +/// ue_version_major, and the notification topic resource ID in the URI. +struct USubscriptionUUriBuilder { +private: + /// URI for the uSubscription service + v1::UUri base_uri_; + /// Resource ID of the notification topic + uint32_t sink_resource_id_; + +public: + /// @brief Constructor for uSubscriptionUUriBuilder. + USubscriptionUUriBuilder(); + + /// @brief Get the URI with a specific resource ID. + /// + /// @param resource_id The resource ID to set in the URI. + /// + /// @return The URI with the specified resource ID. + v1::UUri getServiceUriWithResourceId(uint32_t resource_id) const; + + /// @brief Get the notification URI. + /// + /// @return The notification URI. + v1::UUri getNotificationUri(); +}; + +} // namespace uprotocol::core::usubscription::v3 + +#endif // UP_CPP_CLIENT_USUBSCRIPTION_V3_USUBSCRIPTIONUURIBUILDER_H diff --git a/include/up-cpp/communication/RpcClient.h b/include/up-cpp/communication/RpcClient.h index ce5115ab1..83d0e602d 100644 --- a/include/up-cpp/communication/RpcClient.h +++ b/include/up-cpp/communication/RpcClient.h @@ -12,11 +12,14 @@ #ifndef UP_CPP_COMMUNICATION_RPCCLIENT_H #define UP_CPP_COMMUNICATION_RPCCLIENT_H +#include #include #include #include #include +#include #include +#include #include #include @@ -25,6 +28,9 @@ #include namespace uprotocol::communication { +template +using ResponseOrStatus = utils::Expected; +using UnexpectedStatus = utils::Unexpected; /// @brief Interface for uEntities to invoke RPC methods. /// @@ -167,6 +173,43 @@ struct RpcClient { /// * A UMessage containing the response from the RPC target. [[nodiscard]] InvokeFuture invokeMethod(const v1::UUri&); + template + ResponseOrStatus invokeProtoMethod(const v1::UUri& method, const R& request_message){ + auto payload_or_status = + uprotocol::utils::ProtoConverter::protoToPayload(request_message); + + if (!payload_or_status.has_value()) { + return ResponseOrStatus( + UnexpectedStatus(payload_or_status.error())); + } + datamodel::builder::Payload payload(payload_or_status.value()); + + auto message_or_status = this->invokeMethod(method, std::move(payload)).get(); + + if (!message_or_status.has_value()) { + return ResponseOrStatus( + UnexpectedStatus(message_or_status.error())); + } + + T response_message; + + auto response_or_status = + utils::ProtoConverter::extractFromProtobuf( + message_or_status.value()); + + if (!response_or_status.has_value()) { + spdlog::error( + "invokeProtoMethod: Error when extracting response from protobuf."); + return ResponseOrStatus( + UnexpectedStatus(response_or_status.error())); + } + + response_message = response_or_status.value(); + + return ResponseOrStatus( + std::move(response_message)); + } + /// @brief Default move constructor (defined in RpcClient.cpp) RpcClient(RpcClient&&) noexcept; diff --git a/include/up-cpp/utils/ProtoConverter.h b/include/up-cpp/utils/ProtoConverter.h index ee4123936..e9fae0afb 100644 --- a/include/up-cpp/utils/ProtoConverter.h +++ b/include/up-cpp/utils/ProtoConverter.h @@ -3,11 +3,21 @@ #include #include +#include +#include #include #include +#include "up-cpp/datamodel/builder/Payload.h" +#include "up-cpp/utils/Expected.h" + namespace uprotocol::utils { +template +using TOrStatus = utils::Expected; +using UnexpectedStatus = utils::Unexpected; +using PayloadOrStatus = + utils::Expected; using uprotocol::core::usubscription::v3::SubscribeAttributes; using uprotocol::core::usubscription::v3::SubscriberInfo; using uprotocol::core::usubscription::v3::SubscriptionRequest; @@ -53,6 +63,104 @@ struct ProtoConverter { /// @return the built UnsubscribeRequest static UnsubscribeRequest BuildUnSubscribeRequest( const v1::UUri& subscription_topic); + static UnsubscribeRequest BuildUnSubscribeRequest( + const v1::UUri& uri, const SubscribeAttributes& attributes); + + /** + * @brief Deserializes a protobuf message from a given payload. + * + * Parses the payload in `v1::UMessage` using `google::protobuf::Any`, + * returning a deserialized object of type `T` or an error if parsing fails. + * + * @tparam T The type to deserialize the message into. + * + * @param message The `v1::UMessage` containing the payload. + * + * @return `TOrStatus` with the deserialized object or an error status. + */ + template + static TOrStatus extractFromProtobuf(const v1::UMessage& message) { + + switch (message.attributes().payload_format()) { + + case v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF: { + T response; + if (!response.ParseFromString(message.payload())) { + v1::UStatus status; + status.set_code(v1::UCode::INTERNAL); + status.set_message("extractFromProtobuf: Error when parsing payload from protobuf."); + return TOrStatus(UnexpectedStatus(status)); + } + return TOrStatus(response); + } + case v1::UPayloadFormat::UPAYLOAD_FORMAT_UNSPECIFIED: + case v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY: { + google::protobuf::Any any; + if (!any.ParseFromString(message.payload())) { + v1::UStatus status; + status.set_code(v1::UCode::INTERNAL); + status.set_message( + "extractFromProtobuf: Error when parsing payload from protobuf any."); + return TOrStatus(UnexpectedStatus(status)); + } + T response; + if (!any.UnpackTo(&response)) { + v1::UStatus status; + status.set_code(v1::UCode::INTERNAL); + status.set_message( + "extractFromProtobuf: Error when unpacking any."); + return TOrStatus(UnexpectedStatus(status)); + } + return TOrStatus(response); + } + case v1::UPayloadFormat::UPAYLOAD_FORMAT_JSON: + case v1::UPayloadFormat::UPAYLOAD_FORMAT_SOMEIP: + case v1::UPayloadFormat::UPAYLOAD_FORMAT_SOMEIP_TLV: + case v1::UPayloadFormat::UPAYLOAD_FORMAT_RAW: + case v1::UPayloadFormat::UPAYLOAD_FORMAT_TEXT: + case v1::UPayloadFormat::UPAYLOAD_FORMAT_SHM: + case v1::UPayloadFormat::UPayloadFormat_INT_MIN_SENTINEL_DO_NOT_USE_: + case v1::UPayloadFormat::UPayloadFormat_INT_MAX_SENTINEL_DO_NOT_USE_: + default: { + v1::UStatus status; + status.set_code(v1::UCode::INVALID_ARGUMENT); + status.set_message("Unknown/invalid/unsupported payload format."); + return TOrStatus(UnexpectedStatus(status)); + } + } + + } + + /** + * @brief Serializes a protobuf object into a payload. + * + * Converts the given `proto` object to a payload using + * `google::protobuf::Any`. Returns the payload or an error status if + * serialization fails. + * + * @tparam T The type of the protobuf object to serialize. + * + * @param proto The protobuf object to be converted into a payload. + * + * @return `PayloadOrStatus` containing the payload or an error status. + */ + template + static PayloadOrStatus protoToPayload(const T& proto) { + google::protobuf::Any any; + + if (!any.PackFrom(proto)) { + v1::UStatus status; + status.set_code(v1::UCode::INTERNAL); + status.set_message( + "protoToPayload: There was an error when serializing the " + "subscription request."); + return PayloadOrStatus(UnexpectedStatus(status)); + } + + const datamodel::builder::Payload payload(any); + + return PayloadOrStatus(payload); + } }; }; // namespace uprotocol::utils #endif // UP_CPP_UTILS_PROTOCONVERTER_H diff --git a/src/client/usubscription/v3/Consumer.cpp b/src/client/usubscription/v3/Consumer.cpp index 9bc5f8769..7738b18cc 100644 --- a/src/client/usubscription/v3/Consumer.cpp +++ b/src/client/usubscription/v3/Consumer.cpp @@ -13,28 +13,33 @@ #include +#include "up-cpp/client/usubscription/v3/RequestBuilder.h" + namespace uprotocol::client::usubscription::v3 { -Consumer::Consumer(std::shared_ptr transport, - v1::UUri subscription_topic, - ConsumerOptions consumer_options) +Consumer::Consumer( + std::shared_ptr transport, + v1::UUri subscription_topic, + core::usubscription::v3::USubscriptionOptions consumer_options) : transport_(std::move(transport)), subscription_topic_(std::move(subscription_topic)), consumer_options_(std::move(consumer_options)), rpc_client_(nullptr) { // Initialize uSubscriptionUUriBuilder_ - uSubscriptionUUriBuilder_ = USubscriptionUUriBuilder(); + uSubscriptionUUriBuilder_ = + core::usubscription::v3::USubscriptionUUriBuilder(); } [[nodiscard]] Consumer::ConsumerOrStatus Consumer::create( std::shared_ptr transport, const v1::UUri& subscription_topic, ListenCallback&& callback, v1::UPriority priority, std::chrono::milliseconds subscription_request_ttl, - ConsumerOptions consumer_options) { + core::usubscription::v3::USubscriptionOptions consumer_options) { auto consumer = std::make_unique( std::forward>(transport), std::forward(subscription_topic), - std::forward(consumer_options)); + std::forward( + consumer_options)); // Attempt to connect create notification sink for updates. auto status = consumer->createNotificationSink(); diff --git a/src/client/usubscription/v3/RequestBuilder.cpp b/src/client/usubscription/v3/RequestBuilder.cpp new file mode 100644 index 000000000..39a51f669 --- /dev/null +++ b/src/client/usubscription/v3/RequestBuilder.cpp @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 + +#include "up-cpp/client/usubscription/v3/RequestBuilder.h" +#include + +namespace uprotocol::core::usubscription::v3 { + +SubscriptionRequest RequestBuilder::buildSubscriptionRequest( + const v1::UUri& topic) const { + auto attributes = utils::ProtoConverter::BuildSubscribeAttributes( + options_.when_expire, options_.subscription_details, + options_.sample_period_ms); + + return utils::ProtoConverter::BuildSubscriptionRequest(topic, attributes); +} + +UnsubscribeRequest RequestBuilder::buildUnsubscribeRequest( + const v1::UUri& topic) { + // auto attributes = utils::ProtoConverter::BuildSubscribeAttributes( + // options_.when_expire, + // options_.subscription_details, + // options_.sample_period_ms); + + return utils::ProtoConverter::BuildUnSubscribeRequest(topic); +} + +FetchSubscriptionsRequest RequestBuilder::buildFetchSubscriptionsRequest( + const v1::UUri& topic) { + FetchSubscriptionsRequest fetch_subscriptions_request; + *fetch_subscriptions_request.mutable_topic() = topic; + + return fetch_subscriptions_request; +} + +FetchSubscriptionsRequest RequestBuilder::buildFetchSubscriptionsRequest( + const SubscriberInfo& subscriber) { + FetchSubscriptionsRequest fetch_subscriptions_request; + *fetch_subscriptions_request.mutable_subscriber() = subscriber; + + return fetch_subscriptions_request; +} + +FetchSubscribersRequest RequestBuilder::buildFetchSubscribersRequest( + const v1::UUri& topic) { + FetchSubscribersRequest fetch_subscribers_request; + *fetch_subscribers_request.mutable_topic() = topic; + + return fetch_subscribers_request; +} + +NotificationsRequest RequestBuilder::buildNotificationRequest( + const v1::UUri& topic) { + NotificationsRequest notification_request; + *notification_request.mutable_topic() = topic; + + return notification_request; +} + +} // namespace uprotocol::core::usubscription::v3 diff --git a/src/client/usubscription/v3/RpcClientUSubscription.cpp b/src/client/usubscription/v3/RpcClientUSubscription.cpp new file mode 100644 index 000000000..c8351faad --- /dev/null +++ b/src/client/usubscription/v3/RpcClientUSubscription.cpp @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include + +#include + +#include "up-cpp/communication/RpcClient.h" +#include "up-cpp/utils/Expected.h" + +auto priority = uprotocol::v1::UPriority::UPRIORITY_CS4; // MUST be >= 4 + +namespace uprotocol::core::usubscription::v3 { + +template +Response RpcClientUSubscription::invokeResponse( + communication::RpcClient rpc_client) {} + +RpcClientUSubscription::ResponseOrStatus +RpcClientUSubscription::subscribe( + const SubscriptionRequest& subscription_request) { + communication::RpcClient rpc_client( + transport_, + uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_SUBSCRIBE), + priority, USUBSCRIPTION_REQUEST_TTL); + + auto response_or_status = + rpc_client.invokeProtoMethod(subscription_request); + + if (!response_or_status.has_value()) { + return utils::Expected( + utils::Unexpected(response_or_status.error())); + } + auto subscription_response = response_or_status.value(); + + if (subscription_response.topic().SerializeAsString() != + subscription_request.topic().SerializeAsString()) { + v1::UStatus status; + status.set_code(v1::UCode::INTERNAL); + status.set_message("subscribe: topics do not match."); + return ResponseOrStatus( + utils::Unexpected(status)); + } + + return ResponseOrStatus( + std::move(subscription_response)); +} + +RpcClientUSubscription::ResponseOrStatus +RpcClientUSubscription::unsubscribe( + const UnsubscribeRequest& unsubscribe_request) { + communication::RpcClient rpc_client( + transport_, + uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_UNSUBSCRIBE), + priority, USUBSCRIPTION_REQUEST_TTL); + + return rpc_client.invokeProtoMethod(unsubscribe_request); +} + +RpcClientUSubscription::ResponseOrStatus +RpcClientUSubscription::fetch_subscriptions( + const FetchSubscriptionsRequest& fetch_subscribers_request) { + communication::RpcClient rpc_client( + transport_, + uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_FETCH_SUBSCRIPTIONS), + priority, USUBSCRIPTION_REQUEST_TTL); + + return rpc_client.invokeProtoMethod(fetch_subscribers_request); +} + +RpcClientUSubscription::ResponseOrStatus +RpcClientUSubscription::fetch_subscribers( + const FetchSubscribersRequest& fetch_subscribers_request) { + communication::RpcClient rpc_client( + transport_, + uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_FETCH_SUBSCRIBERS), + priority, USUBSCRIPTION_REQUEST_TTL); + + return rpc_client.invokeProtoMethod(fetch_subscribers_request); +} + +RpcClientUSubscription::ResponseOrStatus +RpcClientUSubscription::register_for_notifications( + const NotificationsRequest& register_notifications_request) { + communication::RpcClient rpc_client( + transport_, + uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_REGISTER_FOR_NOTIFICATIONS), + priority, USUBSCRIPTION_REQUEST_TTL); + + return rpc_client.invokeProtoMethod(register_notifications_request); +} + +RpcClientUSubscription::ResponseOrStatus +RpcClientUSubscription::unregister_for_notifications( + const NotificationsRequest& unregister_notifications_request) { + communication::RpcClient rpc_client( + transport_, + uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS), + priority, USUBSCRIPTION_REQUEST_TTL); + + return rpc_client.invokeProtoMethod(unregister_notifications_request); +} + +} // namespace uprotocol::core::usubscription::v3 diff --git a/src/client/usubscription/v3/USubscriptionUUriBuilder.cpp b/src/client/usubscription/v3/USubscriptionUUriBuilder.cpp new file mode 100644 index 000000000..f2f2ca483 --- /dev/null +++ b/src/client/usubscription/v3/USubscriptionUUriBuilder.cpp @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 + +#include "up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h" + +namespace uprotocol::core::usubscription::v3 { + +USubscriptionUUriBuilder::USubscriptionUUriBuilder() { + // Get the service descriptor + const google::protobuf::ServiceDescriptor* service = + uSubscription::descriptor(); + const auto& service_options = service->options(); + + // Get the service options + const auto& service_name = + service_options.GetExtension(uprotocol::service_name); + const auto& service_version_major = + service_options.GetExtension(uprotocol::service_version_major); + const auto& service_id = + service_options.GetExtension(uprotocol::service_id); + const auto& notification_topic = + service_options.GetExtension(uprotocol::notification_topic, 0); + + // Set the values in the URI + base_uri_.set_authority_name(service_name); + base_uri_.set_ue_id(service_id); + base_uri_.set_ue_version_major(service_version_major); + sink_resource_id_ = notification_topic.id(); +} + +v1::UUri USubscriptionUUriBuilder::getServiceUriWithResourceId( + uint32_t resource_id) const { + v1::UUri uri = base_uri_; // Copy the base URI + uri.set_resource_id(resource_id); + return uri; +} + +v1::UUri USubscriptionUUriBuilder::getNotificationUri() { + v1::UUri uri = base_uri_; // Copy the base URI + uri.set_resource_id(sink_resource_id_); + return uri; +} + +} // namespace uprotocol::core::usubscription::v3 \ No newline at end of file diff --git a/src/utils/ProtoConverter.cpp b/src/utils/ProtoConverter.cpp index c4159ee9f..a50a74a41 100644 --- a/src/utils/ProtoConverter.cpp +++ b/src/utils/ProtoConverter.cpp @@ -1,5 +1,13 @@ #include "up-cpp/utils/ProtoConverter.h" +#include +#include + +#include + +#include "up-cpp/datamodel/builder/Payload.h" +#include "up-cpp/utils/Expected.h" + namespace uprotocol::utils { google::protobuf::Timestamp ProtoConverter::ConvertToProtoTimestamp( const std::chrono::system_clock::time_point& tp) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0dd7940e7..ddc3ad24e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -93,6 +93,9 @@ add_coverage_test("NotificationSourceTest" coverage/communication/NotificationSo # client add_coverage_test("ConsumerTest" coverage/client/usubscription/v3/ConsumerTest.cpp) +# core +add_coverage_test("RpcClientUSubscriptionTest" coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp) + ########################## EXTRAS ############################################# add_extra_test("PublisherSubscriberTest" extra/PublisherSubscriberTest.cpp) add_extra_test("NotificationTest" extra/NotificationTest.cpp) diff --git a/test/coverage/client/usubscription/v3/ConsumerTest.cpp b/test/coverage/client/usubscription/v3/ConsumerTest.cpp index 6c6c133e4..647110d15 100644 --- a/test/coverage/client/usubscription/v3/ConsumerTest.cpp +++ b/test/coverage/client/usubscription/v3/ConsumerTest.cpp @@ -17,6 +17,7 @@ #include #include "UTransportMock.h" +#include "up-cpp/client/usubscription/v3/RequestBuilder.h" namespace { using MsgDiff = google::protobuf::util::MessageDifferencer; @@ -106,7 +107,7 @@ TEST_F(ConsumerTest, ConstructorTestSuccess) { // NOLINT auto subscribe_request_ttl = std::chrono::milliseconds(REQUEST_TTL_TIME); auto priority = uprotocol::v1::UPriority::UPRIORITY_CS4; - auto options = uprotocol::client::usubscription::v3::ConsumerOptions(); + auto options = uprotocol::core::usubscription::v3::USubscriptionOptions(); auto consumer_or_status = uprotocol::client::usubscription::v3::Consumer::create( @@ -131,7 +132,7 @@ TEST_F(ConsumerTest, SubscribeTestSuccess) { // NOLINT auto subscribe_request_ttl = std::chrono::milliseconds(REQUEST_TTL_TIME); auto priority = uprotocol::v1::UPriority::UPRIORITY_CS4; - auto options = uprotocol::client::usubscription::v3::ConsumerOptions(); + auto options = uprotocol::core::usubscription::v3::USubscriptionOptions(); auto consumer_or_status = uprotocol::client::usubscription::v3::Consumer::create( @@ -177,7 +178,7 @@ TEST_F(ConsumerTest, UnsubscribeTestSuccess) { // NOLINT auto subscribe_request_ttl = std::chrono::milliseconds(REQUEST_TTL_TIME); auto priority = uprotocol::v1::UPriority::UPRIORITY_CS4; - auto options = uprotocol::client::usubscription::v3::ConsumerOptions(); + auto options = uprotocol::core::usubscription::v3::USubscriptionOptions(); auto consumer_or_status = uprotocol::client::usubscription::v3::Consumer::create( diff --git a/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp b/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp new file mode 100644 index 000000000..d87ee1e5d --- /dev/null +++ b/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +#include "UTransportMock.h" + +namespace { +using MsgDiff = google::protobuf::util::MessageDifferencer; + +class RpcClientUSubscriptionTest : public testing::Test { +private: + std::shared_ptr mockTransportClient_; + std::shared_ptr mockTransportServer_; + uprotocol::v1::UUri client_uuri; + uprotocol::v1::UUri server_uuri; + uprotocol::v1::UUri subscription_uuri; + +protected: + // Run once per TEST_F. + // Used to set up clean environments per test. + + std::shared_ptr getMockTransportClient() + const { + return mockTransportClient_; + } + std::shared_ptr getMockTransportServer() + const { + return mockTransportServer_; + } + uprotocol::v1::UUri& getClientUUri() { return client_uuri; } + const uprotocol::v1::UUri& getServerUUri() const { return server_uuri; } + const uprotocol::v1::UUri& getSubscriptionUUri() const { + return subscription_uuri; + } + + void SetUp() override { + constexpr uint32_t TEST_UE_ID = 0x18000; + constexpr uint32_t DEFAULT_RESOURCE_ID = 0x8000; + // Create a generic transport uri + client_uuri.set_authority_name("random_string"); + client_uuri.set_ue_id(TEST_UE_ID); + client_uuri.set_ue_version_major(USUBSCRIPTION_VERSION_MAJOR); + client_uuri.set_resource_id(0); + + // Set up a transport + mockTransportClient_ = + std::make_shared(client_uuri); + + // Craete server default uri and set up a transport + server_uuri.set_authority_name("core.usubscription"); + server_uuri.set_ue_id(0); + server_uuri.set_ue_version_major(USUBSCRIPTION_VERSION_MAJOR); + server_uuri.set_resource_id(0); + + mockTransportServer_ = + std::make_shared(server_uuri); + + // Create a generic subscription uri + subscription_uuri.set_authority_name("10.0.0.2"); + subscription_uuri.set_ue_id(TEST_UE_ID); + subscription_uuri.set_ue_version_major(USUBSCRIPTION_VERSION_MAJOR); + subscription_uuri.set_resource_id(DEFAULT_RESOURCE_ID); + }; + void TearDown() override {} + + // Run once per execution of the test application. + // Used for setup of all tests. Has access to this instance. + RpcClientUSubscriptionTest() = default; + + void buildDefaultSourceURI(); + void buildValidNotificationURI(); + void buildInValidNotificationURI(); + + // Run once per execution of the test application. + // Used only for global setup outside of tests. + static void SetUpTestSuite() {} + static void TearDownTestSuite() {} + +public: + ~RpcClientUSubscriptionTest() override = default; +}; + +// Negative test case with no source filter +TEST_F(RpcClientUSubscriptionTest, ConstructorTestSuccess) { // NOLINT + + auto rpc_client_usubscription = std::make_unique< + uprotocol::core::usubscription::v3::RpcClientUSubscription>( + getMockTransportClient()); + + // Verify that the RpcClientUSubscription pointer is not null, indicating + // successful + ASSERT_NE(rpc_client_usubscription, nullptr); +} + +// TEST_F(RpcClientUSubscriptionTest, SubscribeTestSuccess) { // NOLINT +// +// uprotocol::core::usubscription::v3::SubscriptionRequest +// subscription_request = +// uprotocol::utils::ProtoConverter::BuildSubscriptionRequest( +// getSubscriptionUUri(), +// uprotocol::core::usubscription::v3::SubscribeAttributes()); +// +// auto rpc_client_usubscription = std::make_unique< +// uprotocol::core::usubscription::v3::RpcClientUSubscription>( +// getMockTransportClient()); +// +// // Verify that the RpcClientUSubscription pointer is not null, indicating +// // successful +// ASSERT_NE(rpc_client_usubscription, nullptr); +// +// auto result = rpc_client_usubscription->subscribe(subscription_request); +// +// ASSERT_NE(&result, nullptr); +// } + +} // namespace From 73541364b3e2178ccf2a947452cd31040b3c51fd Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 23 May 2025 16:07:52 +0200 Subject: [PATCH 2/8] Added test and refactored some parts of usubscription # Conflicts: # include/up-cpp/communication/RpcClient.h --- .../client/usubscription/v3/RequestBuilder.h | 130 +- .../usubscription/v3/RpcClientUSubscription.h | 80 +- .../client/usubscription/v3/USubscription.h | 50 +- .../v3/USubscriptionUUriBuilder.h | 2 +- include/up-cpp/communication/RpcClient.h | 34 +- include/up-cpp/utils/ProtoConverter.h | 100 +- .../usubscription/v3/RequestBuilder.cpp | 36 +- .../v3/RpcClientUSubscription.cpp | 41 +- .../v3/USubscriptionUUriBuilder.cpp | 4 +- src/utils/ProtoConverter.cpp | 32 + test/CMakeLists.txt | 4 +- .../usubscription/v3/RequestBuilderTest.cpp | 154 +++ .../v3/RpcClientUSubscriptionTest.cpp | 1057 +++++++++++++++-- .../v3/USubscriptionUUriBuilderTest.cpp | 60 + test/extra/PublisherSubscriberTest.cpp | 2 +- 15 files changed, 1486 insertions(+), 300 deletions(-) create mode 100644 test/coverage/client/usubscription/v3/RequestBuilderTest.cpp create mode 100644 test/coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp diff --git a/include/up-cpp/client/usubscription/v3/RequestBuilder.h b/include/up-cpp/client/usubscription/v3/RequestBuilder.h index b87579280..367d8cbe0 100644 --- a/include/up-cpp/client/usubscription/v3/RequestBuilder.h +++ b/include/up-cpp/client/usubscription/v3/RequestBuilder.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. @@ -18,12 +18,10 @@ namespace uprotocol::core::usubscription::v3 { -/** - * @struct USubscriptionOptions - * @brief Additional details for uSubscription service. - * - * Each member represents an optional parameter for the uSubscription service. - */ +/// @struct USubscriptionOptions +/// @brief Additional details for uSubscription service. +/// +/// Each member represents an optional parameter for the uSubscription service. struct USubscriptionOptions { /// Permission level of the subscription request std::optional permission_level; @@ -39,80 +37,64 @@ struct USubscriptionOptions { std::optional subscription_details; }; -/** - * @brief Builds different requests using specified options. - * - * This struct facilitates the construction of requests based on - * `USubscriptionOptions`, providing methods to build different requests. - */ +/// @brief Builds different requests using specified options. +/// +/// This struct facilitates the construction of requests based on +/// `USubscriptionOptions`, providing methods to build different requests. struct RequestBuilder { - /** - * @brief Constructs a RequestBuilder with the given subscription options. - * - * @param options Subscription options to configure the requests. Defaults - * to empty options. - */ - explicit RequestBuilder(USubscriptionOptions options = {}) - : options_(std::move(options)) {} + /// @brief Builds a subscription request for a given topic. + /// + /// @param topic The `v1::UUri` representing the topic for the subscription. + /// + /// @return A `SubscriptionRequest` configured for the specified topic. + static SubscriptionRequest buildSubscriptionRequest( + const v1::UUri& topic, const USubscriptionOptions& options = {}); - /** - * @brief Builds a subscription request for a given topic. - * - * @param topic The `v1::UUri` representing the topic for the subscription. - * - * @return A `SubscriptionRequest` configured for the specified topic. - */ - SubscriptionRequest buildSubscriptionRequest(const v1::UUri& topic) const; - - /** - * @brief Builds an unsubscription request for a given topic. - * - * @param topic The `v1::UUri` representing the topic to unsubscribe from. - * - * @return An `UnsubscribeRequest` configured for the specified topic. - */ + /// @brief Builds an unsubscription request for a given topic. + /// + /// @param topic The `v1::UUri` representing the topic to unsubscribe from. + /// + /// @return An `UnsubscribeRequest` configured for the specified topic. static UnsubscribeRequest buildUnsubscribeRequest(const v1::UUri& topic); - /** - * @brief Build fetch subscritions request for a given topic. - * - * @param topic The `v1::UUri` representing the topic to fetch. - * - * @return An `FetchSubscriptionsRequest` configured for the specified topic. - */ - static FetchSubscriptionsRequest buildFetchSubscriptionsRequest(const v1::UUri& topic); - - /** - * @brief Build fetch subscritions request for a given subscriber. - * - * @param subscriber The `SubscriberInfo` representing the subscriber to fetch. - * - * @return An `FetchSubscriptionsRequest` configured for the specified subscriber. - */ - static FetchSubscriptionsRequest buildFetchSubscriptionsRequest(const SubscriberInfo& subscriber); + /// @brief Build fetch subscritions request for a given topic. + /// + /// @param topic The `v1::UUri` representing the topic to fetch. + /// + /// @return A `FetchSubscriptionsRequest` configured for the specified + /// topic. + static FetchSubscriptionsRequest buildFetchSubscriptionsRequest( + const v1::UUri& topic); - /** - * @brief Build fetch subscribers request for a given topic. - * - * @param topic The `v1::UUri` representing the topic to fetch. - * - * @return An `FetchSubscribersRequest` configured for the specified topic. - */ - static FetchSubscribersRequest buildFetchSubscribersRequest(const v1::UUri& topic); + /// @brief Build fetch subscritions request for a given subscriber. + /// + /// @param subscriber The `SubscriberInfo` representing the subscriber to + /// fetch. + /// + /// @return A `FetchSubscriptionsRequest` configured for the specified + /// subscriber. + static FetchSubscriptionsRequest buildFetchSubscriptionsRequest( + const SubscriberInfo& subscriber); - /** - * @brief Build notification request for a given topic. Subscription change notifications - * MUST use topic SubscriptionChange with resource id 0x8000, as per the protobuf definition. - * - * @param topic The `v1::UUri` representing the topic to (un)register for/from. - * - * @return An `NotificationsRequest` configured for the specified topic. - */ - static NotificationsRequest buildNotificationRequest(const v1::UUri& topic); - + /// @brief Build fetch subscribers request for a given topic. + /// + /// @param topic The `v1::UUri` representing the topic to fetch. + /// + /// @return A `FetchSubscribersRequest` configured for the specified topic. + static FetchSubscribersRequest buildFetchSubscribersRequest( + const v1::UUri& topic); -private: - USubscriptionOptions options_; ///< Options used to configure the requests. + /// @brief Build a notifications request for a given topic. Subscription + /// change + /// notifications MUST use topic SubscriptionsChange with resource id + /// 0x8000, as per the protobuf definition. + /// + /// @param topic The `v1::UUri` representing the topic to (un)register + /// for/from. + /// + /// @return A `NotificationsRequest` configured for the specified topic. + static NotificationsRequest buildNotificationsRequest( + const v1::UUri& topic); }; } // namespace uprotocol::core::usubscription::v3 diff --git a/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h b/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h index 56f4ce4e4..7d2c80254 100644 --- a/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h +++ b/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. @@ -23,31 +23,29 @@ /// The uEntity (type) identifier of the uSubscription service. constexpr uint32_t USUBSCRIPTION_TYPE_ID = 0x00000000; /// The (latest) major version of the uSubscription service. -constexpr uint8_t USUBSCRIPTION_VERSION_MAJOR = 0x03; +constexpr uint8_t UE_VERSION_MAJOR = 0x03; /// The resource identifier of uSubscription's _subscribe_ operation. constexpr uint16_t RESOURCE_ID_SUBSCRIBE = 0x0001; /// The resource identifier of uSubscription's _unsubscribe_ operation. constexpr uint16_t RESOURCE_ID_UNSUBSCRIBE = 0x0002; /// The resource identifier of uSubscription's _fetch subscriptions_ operation. constexpr uint16_t RESOURCE_ID_FETCH_SUBSCRIPTIONS = 0x0003; -/// The resource identifier of uSubscription's _register for notifications_ operation. +/// The resource identifier of uSubscription's _register for notifications_ +/// operation. constexpr uint16_t RESOURCE_ID_REGISTER_FOR_NOTIFICATIONS = 0x0006; -/// The resource identifier of uSubscription's _unregister for notifications_ operation. +/// The resource identifier of uSubscription's _unregister for notifications_ +/// operation. constexpr uint16_t RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS = 0x0007; /// The resource identifier of uSubscription's _fetch subscribers_ operation. constexpr uint16_t RESOURCE_ID_FETCH_SUBSCRIBERS = 0x0008; -// TODO(lennart) see default_call_options() for the request in Rust -constexpr auto USUBSCRIPTION_REQUEST_TTL = - std::chrono::milliseconds(0x0800); // TODO(lennart) change time + +constexpr auto USUBSCRIPTION_REQUEST_TTL = std::chrono::milliseconds(5000); namespace uprotocol::core::usubscription::v3 { using v3::SubscriptionRequest; using v3::UnsubscribeRequest; -/// @brief Interface for uEntities to create subscriptions. -/// -/// Like all L3 client APIs, the RpcClientUSubscription is a wrapper on top of -/// the L2 Communication APIs and USubscription service. +/// @brief Client which implements the USubscription interface struct RpcClientUSubscription : USubscription { using RpcClientUSubscriptionOrStatus = utils::Expected, v1::UStatus>; @@ -57,54 +55,72 @@ struct RpcClientUSubscription : USubscription { template Response invokeResponse(communication::RpcClient rpc_client); - /// @brief Subscribe to the topic + /// @brief Subscribes from a given topic /// + /// @param subscription_request The request object containing the topic to + /// subscribe to + /// @return Returns a SubscriptionResponse on success and a UStatus else utils::Expected subscribe( const SubscriptionRequest& subscription_request) override; - /// @brief Unsubscribe from the topic + /// @brief Unsubscribes from a given topic /// + /// @param unsubscribe_request The request object containing the topic to + /// unsubscribe from + /// @return Returns an UnsubscribeResponse on success and a UStatus else utils::Expected unsubscribe( const UnsubscribeRequest& unsubscribe_request) override; - - /// @brief Fetch all subscriptions either by topic or subscriber - /// - utils::Expected fetch_subscriptions( - const FetchSubscriptionsRequest& fetch_subscribers) override; - /// @brief Fetch all subscribers + /// @brief Fetches the list of topics the client is subscribed to /// + /// @param fetch_subscriptions_request The request object + /// @return Returns a FetchSubscriptionsResponse on success and a UStatus + /// else + utils::Expected + fetch_subscriptions( + const FetchSubscriptionsRequest& fetch_subscriptions_request) override; + + /// @brief Fetches the list of subscribers for a given topic + /// + /// @param fetch_subscribers_request The request object containing the topic + /// for which the subscribers are to be fetched + /// @return Returns a FetchSubscribersResponse on success and a UStatus else utils::Expected fetch_subscribers( - const FetchSubscribersRequest& fetch_subscribers) override; + const FetchSubscribersRequest& fetch_subscribers_request) override; - /// @brief Register for notifications + /// @brief Registers to receive notifications /// - utils::Expected register_for_notifications(const - NotificationsRequest& register_notifications_request) override ; - - /// @brief Unregister for notifications + /// @param register_notifications_request The request object containing + /// the details to register for notifications + /// @return Returns a NotificationResponse on success and a UStatus else + utils::Expected + register_for_notifications( + const NotificationsRequest& register_notifications_request) override; + + /// @brief Unregisters from receiving notifications. /// - utils::Expected unregister_for_notifications(const - NotificationsRequest& unregister_notifications_request) override ; + /// @param unregister_notifications_request The request object containing + /// the details needed to stop receiving notifications. + /// @return Returns a NotificationResponse on success and a UStatus else + utils::Expected + unregister_for_notifications( + const NotificationsRequest& unregister_notifications_request) override; /// @brief Constructor /// - /// @param transport Transport to register with. + /// @param transport Transport used to send messages explicit RpcClientUSubscription( std::shared_ptr transport) : transport_(std::move(transport)) {} - /// @brief Destructor ~RpcClientUSubscription() override = default; private: - // Transport std::shared_ptr transport_; - // URI info about the uSubscription service USubscriptionUUriBuilder uuri_builder_; }; } // namespace uprotocol::core::usubscription::v3 -#endif // UP_CPP_CLIENT_USUBSCRIPTION_V3_RPCCLIENTUSUBSCRIPTION_H \ No newline at end of file +#endif // UP_CPP_CLIENT_USUBSCRIPTION_V3_RPCCLIENTUSUBSCRIPTION_H diff --git a/include/up-cpp/client/usubscription/v3/USubscription.h b/include/up-cpp/client/usubscription/v3/USubscription.h index e6673588c..1eea2a67e 100644 --- a/include/up-cpp/client/usubscription/v3/USubscription.h +++ b/include/up-cpp/client/usubscription/v3/USubscription.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. @@ -19,29 +19,61 @@ namespace uprotocol::core::usubscription::v3 { +/// @brief Interface for uEntities to create subscriptions. +/// +/// Like all L3 client APIs, the RpcClientUSubscription is a wrapper on top of +/// the L2 Communication APIs and USubscription service. struct USubscription { template using ResponseOrStatus = utils::Expected; virtual ~USubscription() = default; + /// @brief sends a subscription request to a USubscription backend and a + /// response on success or else a status code + /// + /// @param subscription_request containing a topic to subscribe to + /// @return SubscriptionReponse on success and UStatus else virtual ResponseOrStatus subscribe( const SubscriptionRequest& subscription_request) = 0; + /// @brief sends an unsubscribe request to a USubscription backend and a + /// response on success or else a status code + /// + /// @param unsubscribe_request containing a topic to unsubscribe + /// @return UnsubscribeResponse on success and UStatus else virtual ResponseOrStatus unsubscribe( const UnsubscribeRequest& unsubscribe_request) = 0; - virtual ResponseOrStatus fetch_subscriptions(const - FetchSubscriptionsRequest& fetch_subscribers_request) = 0; + /// @brief fetches all topics the client is subscribed to from the backend + /// + /// @param fetch_subscriptions_request + /// @return FetchSubscriptionsResponse on success and UStatus else + virtual ResponseOrStatus fetch_subscriptions( + const FetchSubscriptionsRequest& fetch_subscriptions_request) = 0; - virtual ResponseOrStatus register_for_notifications(const - NotificationsRequest& register_notifications_request) =0 ; + /// @brief registers for notifications to a USubscription backend + /// + /// @param register_notifications_request + /// @return NotificationResponse on success and UStatus else + virtual ResponseOrStatus register_for_notifications( + const NotificationsRequest& register_notifications_request) = 0; - virtual ResponseOrStatus unregister_for_notifications(const - NotificationsRequest& unregister_notifications_request) = 0; + /// @brief unregisters for notifications to a USubscription backend + /// + /// @param unregister_notifications_request + /// @return NotificationResponse on success and UStatus else + virtual ResponseOrStatus + unregister_for_notifications( + const NotificationsRequest& unregister_notifications_request) = 0; - virtual ResponseOrStatus fetch_subscribers(const - FetchSubscribersRequest& fetch_subscribers_request) = 0; + /// @brief fetches all subscribers for a given topic from the backend + /// + /// @param fetch_subscriptions_request containing the topic for which the + /// subscribers are fetched + /// @return FetchSubscriptionsResponse on success and UStatus else + virtual ResponseOrStatus fetch_subscribers( + const FetchSubscribersRequest& fetch_subscribers_request) = 0; }; } // namespace uprotocol::core::usubscription::v3 diff --git a/include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h b/include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h index 63bcdec79..3797f1c1c 100644 --- a/include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h +++ b/include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. diff --git a/include/up-cpp/communication/RpcClient.h b/include/up-cpp/communication/RpcClient.h index 83d0e602d..0e3690e02 100644 --- a/include/up-cpp/communication/RpcClient.h +++ b/include/up-cpp/communication/RpcClient.h @@ -173,41 +173,37 @@ struct RpcClient { /// * A UMessage containing the response from the RPC target. [[nodiscard]] InvokeFuture invokeMethod(const v1::UUri&); - template - ResponseOrStatus invokeProtoMethod(const v1::UUri& method, const R& request_message){ + template + ResponseOrStatus invokeProtoMethod(const v1::UUri& method, const R& request_message) { auto payload_or_status = - uprotocol::utils::ProtoConverter::protoToPayload(request_message); + uprotocol::utils::ProtoConverter::protoToPayload(request_message); if (!payload_or_status.has_value()) { return ResponseOrStatus( - UnexpectedStatus(payload_or_status.error())); + UnexpectedStatus(payload_or_status.error())); } - datamodel::builder::Payload payload(payload_or_status.value()); - auto message_or_status = this->invokeMethod(method, std::move(payload)).get(); + datamodel::builder::Payload tmp_payload(payload_or_status.value()); + + auto message_or_status = + this->invokeMethod(method, std::move(tmp_payload)).get(); if (!message_or_status.has_value()) { return ResponseOrStatus( - UnexpectedStatus(message_or_status.error())); + UnexpectedStatus(message_or_status.error())); } - T response_message; - - auto response_or_status = - utils::ProtoConverter::extractFromProtobuf( - message_or_status.value()); + auto response_or_status = utils::ProtoConverter::extractFromProtobuf( + message_or_status.value()); if (!response_or_status.has_value()) { spdlog::error( - "invokeProtoMethod: Error when extracting response from protobuf."); - return ResponseOrStatus( - UnexpectedStatus(response_or_status.error())); + "invokeProtoMethod: Error when extracting response from " + "protobuf."); + return response_or_status; } - response_message = response_or_status.value(); - - return ResponseOrStatus( - std::move(response_message)); + return ResponseOrStatus(response_or_status.value()); } /// @brief Default move constructor (defined in RpcClient.cpp) diff --git a/include/up-cpp/utils/ProtoConverter.h b/include/up-cpp/utils/ProtoConverter.h index e9fae0afb..8f5c3deaa 100644 --- a/include/up-cpp/utils/ProtoConverter.h +++ b/include/up-cpp/utils/ProtoConverter.h @@ -18,6 +18,9 @@ using TOrStatus = utils::Expected; using UnexpectedStatus = utils::Unexpected; using PayloadOrStatus = utils::Expected; +using core::usubscription::v3::FetchSubscribersRequest; +using core::usubscription::v3::FetchSubscriptionsRequest; +using core::usubscription::v3::NotificationsRequest; using uprotocol::core::usubscription::v3::SubscribeAttributes; using uprotocol::core::usubscription::v3::SubscriberInfo; using uprotocol::core::usubscription::v3::SubscriptionRequest; @@ -63,32 +66,53 @@ struct ProtoConverter { /// @return the built UnsubscribeRequest static UnsubscribeRequest BuildUnSubscribeRequest( const v1::UUri& subscription_topic); - static UnsubscribeRequest BuildUnSubscribeRequest( - const v1::UUri& uri, const SubscribeAttributes& attributes); - - /** - * @brief Deserializes a protobuf message from a given payload. - * - * Parses the payload in `v1::UMessage` using `google::protobuf::Any`, - * returning a deserialized object of type `T` or an error if parsing fails. - * - * @tparam T The type to deserialize the message into. - * - * @param message The `v1::UMessage` containing the payload. - * - * @return `TOrStatus` with the deserialized object or an error status. - */ + + /// @brief Builds a FetchSubscriptionsRequest from the given topic + /// + /// @param topic the UUri of the topic to fetch subscriptions for + /// @return the built FetchSubscriptionsRequest + static FetchSubscriptionsRequest BuildFetchSubscriptionsRequest( + const v1::UUri& topic); + + /// @brief Builds a FetchSubscriptionsRequest from the given subscriber + /// information + /// + /// @param subscriber the SubscriberInfo containing details of the + /// subscriber + /// @return the built FetchSubscriptionsRequest + static FetchSubscriptionsRequest BuildFetchSubscriptionsRequest( + const SubscriberInfo& subscriber); + + /// @brief Builds a FetchSubscribersRequest from the given topic + /// + /// @param topic the UUri of the topic to fetch subscribers for + /// @return the built FetchSubscribersRequest + static FetchSubscribersRequest BuildFetchSubscribersRequest( + const v1::UUri& topic); + + /// @brief Builds a NotificationsRequest from the given topic + /// + /// @param topic the UUri of the topic to build a notification request for + /// @return the built NotificationsRequest + static NotificationsRequest BuildNotificationsRequest( + const v1::UUri& topic); + + /// @brief Deserializes a protobuf message from a given payload. + /// + /// @tparam T The type to deserialize the message into. + /// @param message The `v1::UMessage` containing the payload. + /// @return `TOrStatus` with the deserialized object or an error status. template static TOrStatus extractFromProtobuf(const v1::UMessage& message) { - switch (message.attributes().payload_format()) { - case v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF: { T response; if (!response.ParseFromString(message.payload())) { v1::UStatus status; status.set_code(v1::UCode::INTERNAL); - status.set_message("extractFromProtobuf: Error when parsing payload from protobuf."); + status.set_message( + "extractFromProtobuf: Error when parsing payload from " + "protobuf."); return TOrStatus(UnexpectedStatus(status)); } return TOrStatus(response); @@ -100,7 +124,8 @@ struct ProtoConverter { v1::UStatus status; status.set_code(v1::UCode::INTERNAL); status.set_message( - "extractFromProtobuf: Error when parsing payload from protobuf any."); + "extractFromProtobuf: Error when parsing payload from " + "protobuf any."); return TOrStatus(UnexpectedStatus(status)); } T response; @@ -108,7 +133,7 @@ struct ProtoConverter { v1::UStatus status; status.set_code(v1::UCode::INTERNAL); status.set_message( - "extractFromProtobuf: Error when unpacking any."); + "extractFromProtobuf: Error when unpacking any."); return TOrStatus(UnexpectedStatus(status)); } return TOrStatus(response); @@ -119,31 +144,30 @@ struct ProtoConverter { case v1::UPayloadFormat::UPAYLOAD_FORMAT_RAW: case v1::UPayloadFormat::UPAYLOAD_FORMAT_TEXT: case v1::UPayloadFormat::UPAYLOAD_FORMAT_SHM: - case v1::UPayloadFormat::UPayloadFormat_INT_MIN_SENTINEL_DO_NOT_USE_: - case v1::UPayloadFormat::UPayloadFormat_INT_MAX_SENTINEL_DO_NOT_USE_: + case v1::UPayloadFormat:: + UPayloadFormat_INT_MIN_SENTINEL_DO_NOT_USE_: + case v1::UPayloadFormat:: + UPayloadFormat_INT_MAX_SENTINEL_DO_NOT_USE_: { + v1::UStatus status; + status.set_code(v1::UCode::UNIMPLEMENTED); + status.set_message("Unimplemented payload format."); + return TOrStatus(UnexpectedStatus(status)); + } default: { v1::UStatus status; status.set_code(v1::UCode::INVALID_ARGUMENT); - status.set_message("Unknown/invalid/unsupported payload format."); + status.set_message( + "Unknown/invalid/unsupported payload format."); return TOrStatus(UnexpectedStatus(status)); } - } - + } } - /** - * @brief Serializes a protobuf object into a payload. - * - * Converts the given `proto` object to a payload using - * `google::protobuf::Any`. Returns the payload or an error status if - * serialization fails. - * - * @tparam T The type of the protobuf object to serialize. - * - * @param proto The protobuf object to be converted into a payload. - * - * @return `PayloadOrStatus` containing the payload or an error status. - */ + /// @brief Serializes a protobuf object into a payload. + /// + /// @tparam T The type of the protobuf object to serialize. + /// @param proto The protobuf object to be converted into a payload. + /// @return `PayloadOrStatus` containing the payload or an error status. template static PayloadOrStatus protoToPayload(const T& proto) { google::protobuf::Any any; diff --git a/src/client/usubscription/v3/RequestBuilder.cpp b/src/client/usubscription/v3/RequestBuilder.cpp index 39a51f669..052a568e8 100644 --- a/src/client/usubscription/v3/RequestBuilder.cpp +++ b/src/client/usubscription/v3/RequestBuilder.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. @@ -10,59 +10,43 @@ // SPDX-License-Identifier: Apache-2.0 #include "up-cpp/client/usubscription/v3/RequestBuilder.h" + #include namespace uprotocol::core::usubscription::v3 { SubscriptionRequest RequestBuilder::buildSubscriptionRequest( - const v1::UUri& topic) const { + const v1::UUri& topic, const USubscriptionOptions& options) { auto attributes = utils::ProtoConverter::BuildSubscribeAttributes( - options_.when_expire, options_.subscription_details, - options_.sample_period_ms); + options.when_expire, options.subscription_details, + options.sample_period_ms); return utils::ProtoConverter::BuildSubscriptionRequest(topic, attributes); } UnsubscribeRequest RequestBuilder::buildUnsubscribeRequest( const v1::UUri& topic) { - // auto attributes = utils::ProtoConverter::BuildSubscribeAttributes( - // options_.when_expire, - // options_.subscription_details, - // options_.sample_period_ms); - return utils::ProtoConverter::BuildUnSubscribeRequest(topic); } FetchSubscriptionsRequest RequestBuilder::buildFetchSubscriptionsRequest( const v1::UUri& topic) { - FetchSubscriptionsRequest fetch_subscriptions_request; - *fetch_subscriptions_request.mutable_topic() = topic; - - return fetch_subscriptions_request; + return utils::ProtoConverter::BuildFetchSubscriptionsRequest(topic); } FetchSubscriptionsRequest RequestBuilder::buildFetchSubscriptionsRequest( const SubscriberInfo& subscriber) { - FetchSubscriptionsRequest fetch_subscriptions_request; - *fetch_subscriptions_request.mutable_subscriber() = subscriber; - - return fetch_subscriptions_request; + return utils::ProtoConverter::BuildFetchSubscriptionsRequest(subscriber); } FetchSubscribersRequest RequestBuilder::buildFetchSubscribersRequest( const v1::UUri& topic) { - FetchSubscribersRequest fetch_subscribers_request; - *fetch_subscribers_request.mutable_topic() = topic; - - return fetch_subscribers_request; + return utils::ProtoConverter::BuildFetchSubscribersRequest(topic); } -NotificationsRequest RequestBuilder::buildNotificationRequest( +NotificationsRequest RequestBuilder::buildNotificationsRequest( const v1::UUri& topic) { - NotificationsRequest notification_request; - *notification_request.mutable_topic() = topic; - - return notification_request; + return utils::ProtoConverter::BuildNotificationsRequest(topic); } } // namespace uprotocol::core::usubscription::v3 diff --git a/src/client/usubscription/v3/RpcClientUSubscription.cpp b/src/client/usubscription/v3/RpcClientUSubscription.cpp index c8351faad..60565041b 100644 --- a/src/client/usubscription/v3/RpcClientUSubscription.cpp +++ b/src/client/usubscription/v3/RpcClientUSubscription.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. @@ -24,10 +24,6 @@ auto priority = uprotocol::v1::UPriority::UPRIORITY_CS4; // MUST be >= 4 namespace uprotocol::core::usubscription::v3 { -template -Response RpcClientUSubscription::invokeResponse( - communication::RpcClient rpc_client) {} - RpcClientUSubscription::ResponseOrStatus RpcClientUSubscription::subscribe( const SubscriptionRequest& subscription_request) { @@ -37,11 +33,11 @@ RpcClientUSubscription::subscribe( priority, USUBSCRIPTION_REQUEST_TTL); auto response_or_status = - rpc_client.invokeProtoMethod(subscription_request); + rpc_client.invokeProtoMethod( + subscription_request); if (!response_or_status.has_value()) { - return utils::Expected( - utils::Unexpected(response_or_status.error())); + return response_or_status; } auto subscription_response = response_or_status.value(); @@ -66,18 +62,21 @@ RpcClientUSubscription::unsubscribe( uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_UNSUBSCRIBE), priority, USUBSCRIPTION_REQUEST_TTL); - return rpc_client.invokeProtoMethod(unsubscribe_request); + return rpc_client.invokeProtoMethod( + unsubscribe_request); } RpcClientUSubscription::ResponseOrStatus RpcClientUSubscription::fetch_subscriptions( - const FetchSubscriptionsRequest& fetch_subscribers_request) { + const FetchSubscriptionsRequest& fetch_subscriptions_request) { communication::RpcClient rpc_client( transport_, - uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_FETCH_SUBSCRIPTIONS), + uuri_builder_.getServiceUriWithResourceId( + RESOURCE_ID_FETCH_SUBSCRIPTIONS), priority, USUBSCRIPTION_REQUEST_TTL); - return rpc_client.invokeProtoMethod(fetch_subscribers_request); + return rpc_client.invokeProtoMethod( + fetch_subscriptions_request); } RpcClientUSubscription::ResponseOrStatus @@ -85,10 +84,12 @@ RpcClientUSubscription::fetch_subscribers( const FetchSubscribersRequest& fetch_subscribers_request) { communication::RpcClient rpc_client( transport_, - uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_FETCH_SUBSCRIBERS), + uuri_builder_.getServiceUriWithResourceId( + RESOURCE_ID_FETCH_SUBSCRIBERS), priority, USUBSCRIPTION_REQUEST_TTL); - return rpc_client.invokeProtoMethod(fetch_subscribers_request); + return rpc_client.invokeProtoMethod( + fetch_subscribers_request); } RpcClientUSubscription::ResponseOrStatus @@ -96,10 +97,12 @@ RpcClientUSubscription::register_for_notifications( const NotificationsRequest& register_notifications_request) { communication::RpcClient rpc_client( transport_, - uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_REGISTER_FOR_NOTIFICATIONS), + uuri_builder_.getServiceUriWithResourceId( + RESOURCE_ID_REGISTER_FOR_NOTIFICATIONS), priority, USUBSCRIPTION_REQUEST_TTL); - return rpc_client.invokeProtoMethod(register_notifications_request); + return rpc_client.invokeProtoMethod( + register_notifications_request); } RpcClientUSubscription::ResponseOrStatus @@ -107,10 +110,12 @@ RpcClientUSubscription::unregister_for_notifications( const NotificationsRequest& unregister_notifications_request) { communication::RpcClient rpc_client( transport_, - uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS), + uuri_builder_.getServiceUriWithResourceId( + RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS), priority, USUBSCRIPTION_REQUEST_TTL); - return rpc_client.invokeProtoMethod(unregister_notifications_request); + return rpc_client.invokeProtoMethod( + unregister_notifications_request); } } // namespace uprotocol::core::usubscription::v3 diff --git a/src/client/usubscription/v3/USubscriptionUUriBuilder.cpp b/src/client/usubscription/v3/USubscriptionUUriBuilder.cpp index f2f2ca483..4c8bfdb4d 100644 --- a/src/client/usubscription/v3/USubscriptionUUriBuilder.cpp +++ b/src/client/usubscription/v3/USubscriptionUUriBuilder.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. @@ -49,4 +49,4 @@ v1::UUri USubscriptionUUriBuilder::getNotificationUri() { return uri; } -} // namespace uprotocol::core::usubscription::v3 \ No newline at end of file +} // namespace uprotocol::core::usubscription::v3 diff --git a/src/utils/ProtoConverter.cpp b/src/utils/ProtoConverter.cpp index a50a74a41..b6ee5403a 100644 --- a/src/utils/ProtoConverter.cpp +++ b/src/utils/ProtoConverter.cpp @@ -84,4 +84,36 @@ UnsubscribeRequest ProtoConverter::BuildUnSubscribeRequest( return unsubscribe_request; } +FetchSubscriptionsRequest ProtoConverter::BuildFetchSubscriptionsRequest( + const v1::UUri& topic) { + FetchSubscriptionsRequest fetch_subscriptions_request; + *fetch_subscriptions_request.mutable_topic() = topic; + + return fetch_subscriptions_request; +} + +FetchSubscriptionsRequest ProtoConverter::BuildFetchSubscriptionsRequest( + const SubscriberInfo& subscriber) { + FetchSubscriptionsRequest fetch_subscriptions_request; + *fetch_subscriptions_request.mutable_subscriber() = subscriber; + + return fetch_subscriptions_request; +} + +FetchSubscribersRequest ProtoConverter::BuildFetchSubscribersRequest( + const v1::UUri& topic) { + FetchSubscribersRequest fetch_subscribers_request; + *fetch_subscribers_request.mutable_topic() = topic; + + return fetch_subscribers_request; +} + +NotificationsRequest ProtoConverter::BuildNotificationsRequest( + const v1::UUri& topic) { + NotificationsRequest notifications_request; + *notifications_request.mutable_topic() = topic; + + return notifications_request; +} + } // namespace uprotocol::utils diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ddc3ad24e..1adee2504 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -92,9 +92,11 @@ add_coverage_test("NotificationSourceTest" coverage/communication/NotificationSo # client add_coverage_test("ConsumerTest" coverage/client/usubscription/v3/ConsumerTest.cpp) +add_coverage_test("RequestBuilderTest" coverage/client/usubscription/v3/RequestBuilderTest.cpp) +add_coverage_test("RpcClientUSubscriptionTest" coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp) +add_coverage_test("USubscriptionUUriBuilderTest" coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp) # core -add_coverage_test("RpcClientUSubscriptionTest" coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp) ########################## EXTRAS ############################################# add_extra_test("PublisherSubscriberTest" extra/PublisherSubscriberTest.cpp) diff --git a/test/coverage/client/usubscription/v3/RequestBuilderTest.cpp b/test/coverage/client/usubscription/v3/RequestBuilderTest.cpp new file mode 100644 index 000000000..ad2c8c602 --- /dev/null +++ b/test/coverage/client/usubscription/v3/RequestBuilderTest.cpp @@ -0,0 +1,154 @@ +// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include + +#include +#include + +#include "up-cpp/client/usubscription/v3/RequestBuilder.h" + +constexpr uint32_t SOURCE_UE_ID = 0x00011101; +constexpr uint32_t SOURCE_UE_VERSION_MAJOR = 0xF8; +constexpr uint32_t SOURCE_RESOURCE_ID = 0x8101; + +namespace uprotocol::core::usubscription::v3 { + +class RequestBuilderTest : public ::testing::Test { +private: + v1::UUri source_; + USubscriptionOptions options_; + +protected: + const v1::UUri& getSource() const { return source_; } + const USubscriptionOptions& getOptions() const { return options_; } + + void SetUp() override { + // Create a UUri object for testing + source_.set_authority_name("10.0.0.1"); + source_.set_ue_id(SOURCE_UE_ID); + source_.set_ue_version_major(SOURCE_UE_VERSION_MAJOR); + source_.set_resource_id(SOURCE_RESOURCE_ID); + + options_.permission_level = 2; + options_.token = "sample_token"; + options_.when_expire = + std::chrono::system_clock::now() + std::chrono::milliseconds(1); + options_.sample_period_ms = std::chrono::seconds(1); + options_.subscriber_details = google::protobuf::Any(); + options_.subscription_details = google::protobuf::Any(); + } + void TearDown() override {} + + // Run once per execution of the test application. + // Used for setup of all tests. Has access to this instance. + RequestBuilderTest() = default; + + // Run once per execution of the test application. + // Used only for global setup outside of tests. + static void SetUpTestSuite() {} + static void TearDownTestSuite() {} + +public: + ~RequestBuilderTest() override = default; +}; + +TEST_F(RequestBuilderTest, BuildSubscriptionRequestWithOptions) { // NOLINT + const v1::UUri topic = getSource(); + + SubscriptionRequest request; + ASSERT_NO_THROW( // NOLINT + request = + RequestBuilder::buildSubscriptionRequest(topic, getOptions())); + + // Verify the attributes in the request + // TODO(max) there should probably be some test that explicitely checks data + // from the options + EXPECT_TRUE(request.has_topic()); + EXPECT_TRUE(request.has_attributes()); + EXPECT_EQ(request.topic().SerializeAsString(), topic.SerializeAsString()); + EXPECT_EQ(request.GetTypeName(), + "uprotocol.core.usubscription.v3.SubscriptionRequest"); +} + +TEST_F(RequestBuilderTest, BuildUnsubscribeRequest) { // NOLINT + const v1::UUri topic = getSource(); + + UnsubscribeRequest request; + ASSERT_NO_THROW( // NOLINT + request = RequestBuilder::buildUnsubscribeRequest(topic)); + + // Verify the attributes in the request + EXPECT_TRUE(request.has_topic()); + EXPECT_EQ(request.topic().SerializeAsString(), topic.SerializeAsString()); + EXPECT_EQ(request.GetTypeName(), + "uprotocol.core.usubscription.v3.UnsubscribeRequest"); +} + +TEST_F(RequestBuilderTest, BuildFetchSubscriptionsRequestWithTopic) { // NOLINT + const v1::UUri topic = getSource(); + + FetchSubscriptionsRequest request; + ASSERT_NO_THROW(request = // NOLINT + RequestBuilder::buildFetchSubscriptionsRequest(topic)); + + // Verify the attributes in the request + EXPECT_TRUE(request.has_topic()); + EXPECT_EQ(request.topic().SerializeAsString(), topic.SerializeAsString()); + EXPECT_EQ(request.GetTypeName(), + "uprotocol.core.usubscription.v3.FetchSubscriptionsRequest"); +} + +TEST_F(RequestBuilderTest, // NOLINT + BuildFetchSubscriptionsRequestWithSubscriberInfo) { + const SubscriberInfo subscriber; + + FetchSubscriptionsRequest request; + ASSERT_NO_THROW( // NOLINT + request = RequestBuilder::buildFetchSubscriptionsRequest(subscriber)); + + // Verify the attributes in the request + EXPECT_FALSE(request.has_topic()); + EXPECT_EQ(request.GetTypeName(), + "uprotocol.core.usubscription.v3.FetchSubscriptionsRequest"); +} + +TEST_F(RequestBuilderTest, BuildFetchSubscribersRequest) { // NOLINT + const v1::UUri topic = getSource(); + + FetchSubscribersRequest request; + ASSERT_NO_THROW(request = // NOLINT + RequestBuilder::buildFetchSubscribersRequest(topic)); + + // Verify the attributes in the request + EXPECT_TRUE(request.has_topic()); + EXPECT_EQ(request.topic().SerializeAsString(), topic.SerializeAsString()); + EXPECT_EQ(request.GetTypeName(), + "uprotocol.core.usubscription.v3.FetchSubscribersRequest"); +} + +TEST_F(RequestBuilderTest, BuildNotificationsRequest) { // NOLINT + const v1::UUri topic = getSource(); + + NotificationsRequest request; + ASSERT_NO_THROW( // NOLINT + request = RequestBuilder::buildNotificationsRequest(topic)); + + // Verify the attributes in the request + EXPECT_TRUE(request.has_topic()); + EXPECT_EQ(request.topic().SerializeAsString(), topic.SerializeAsString()); + EXPECT_EQ(request.GetTypeName(), + "uprotocol.core.usubscription.v3.NotificationsRequest"); +} + +} // namespace uprotocol::core::usubscription::v3 diff --git a/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp b/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp index d87ee1e5d..0fa50f8c6 100644 --- a/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp +++ b/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. @@ -9,120 +9,1019 @@ // // SPDX-License-Identifier: Apache-2.0 -#include #include -#include -#include +#include #include "UTransportMock.h" +#include "up-cpp/client/usubscription/v3/RequestBuilder.h" +#include "up-cpp/client/usubscription/v3/RpcClientUSubscription.h" +#include "up-cpp/communication/RpcServer.h" +#include "up-cpp/utils/ProtoConverter.h" +#include "uprotocol/v1/uri.pb.h" + +using UMessage = uprotocol::v1::UMessage; +using Payload = uprotocol::datamodel::builder::Payload; +using ProtoConverter = uprotocol::utils::ProtoConverter; +using SubscriptionRequest = + uprotocol::core::usubscription::v3::SubscriptionRequest; +using SubscriptionResponse = + uprotocol::core::usubscription::v3::SubscriptionResponse; +using RequestBuilder = uprotocol::core::usubscription::v3::RequestBuilder; namespace { -using MsgDiff = google::protobuf::util::MessageDifferencer; -class RpcClientUSubscriptionTest : public testing::Test { -private: - std::shared_ptr mockTransportClient_; - std::shared_ptr mockTransportServer_; - uprotocol::v1::UUri client_uuri; - uprotocol::v1::UUri server_uuri; - uprotocol::v1::UUri subscription_uuri; +constexpr uint32_t UE_VERSION_MAJOR = 3; +constexpr uint32_t CLIENT_UE_ID = 23492; +constexpr int ITERATIONS_TILL_TIMEOUT = 10; +constexpr std::chrono::milliseconds MILLISECONDS_PER_ITERATION = + std::chrono::milliseconds(50); + +class RpcClientUSubscriptionTest : public testing::Test { protected: - // Run once per TEST_F. + // Run once per TEST_F.s // Used to set up clean environments per test. - - std::shared_ptr getMockTransportClient() - const { - return mockTransportClient_; - } - std::shared_ptr getMockTransportServer() - const { - return mockTransportServer_; - } - uprotocol::v1::UUri& getClientUUri() { return client_uuri; } - const uprotocol::v1::UUri& getServerUUri() const { return server_uuri; } - const uprotocol::v1::UUri& getSubscriptionUUri() const { - return subscription_uuri; - } - void SetUp() override { - constexpr uint32_t TEST_UE_ID = 0x18000; - constexpr uint32_t DEFAULT_RESOURCE_ID = 0x8000; - // Create a generic transport uri - client_uuri.set_authority_name("random_string"); - client_uuri.set_ue_id(TEST_UE_ID); - client_uuri.set_ue_version_major(USUBSCRIPTION_VERSION_MAJOR); + uprotocol::v1::UUri client_uuri; + client_uuri.set_authority_name("client.usubscription"); + client_uuri.set_ue_id(CLIENT_UE_ID); + client_uuri.set_ue_version_major(UE_VERSION_MAJOR); client_uuri.set_resource_id(0); - // Set up a transport - mockTransportClient_ = + client_transport_ = std::make_shared(client_uuri); - // Craete server default uri and set up a transport + uprotocol::v1::UUri server_uuri; server_uuri.set_authority_name("core.usubscription"); - server_uuri.set_ue_id(0); - server_uuri.set_ue_version_major(USUBSCRIPTION_VERSION_MAJOR); + server_uuri.set_ue_id(1); + server_uuri.set_ue_version_major(UE_VERSION_MAJOR); server_uuri.set_resource_id(0); - mockTransportServer_ = + server_transport_ = std::make_shared(server_uuri); - // Create a generic subscription uri - subscription_uuri.set_authority_name("10.0.0.2"); - subscription_uuri.set_ue_id(TEST_UE_ID); - subscription_uuri.set_ue_version_major(USUBSCRIPTION_VERSION_MAJOR); - subscription_uuri.set_resource_id(DEFAULT_RESOURCE_ID); - }; + constexpr uint32_t SERVER_RESOURCE_ID = 32600; + server_method_uuri_.set_authority_name("core.usubscription"); + server_method_uuri_.set_ue_id(1); + server_method_uuri_.set_ue_version_major(UE_VERSION_MAJOR); + server_method_uuri_.set_resource_id(SERVER_RESOURCE_ID); + + constexpr uint32_t TOPIC_UE = 2342; + constexpr uint32_t TOPIC_RESOURCE_ID = 12340; + subscription_topic_.set_authority_name("topic.usubscription"); + subscription_topic_.set_ue_id(TOPIC_UE); + subscription_topic_.set_ue_version_major(UE_VERSION_MAJOR); + subscription_topic_.set_resource_id(TOPIC_RESOURCE_ID); + } + void TearDown() override {} // Run once per execution of the test application. // Used for setup of all tests. Has access to this instance. RpcClientUSubscriptionTest() = default; - void buildDefaultSourceURI(); - void buildValidNotificationURI(); - void buildInValidNotificationURI(); - // Run once per execution of the test application. // Used only for global setup outside of tests. static void SetUpTestSuite() {} static void TearDownTestSuite() {} + std::shared_ptr getClientTransport() { + return client_transport_; + } + + std::shared_ptr getServerTransport() { + return server_transport_; + } + + uprotocol::v1::UUri getServerMethodUuri() { return server_method_uuri_; } + + uprotocol::v1::UUri getSubscriptionTopic() { return subscription_topic_; } + +private: + std::shared_ptr client_transport_; + std::shared_ptr server_transport_; + uprotocol::v1::UUri server_method_uuri_; + uprotocol::v1::UUri subscription_topic_; + public: ~RpcClientUSubscriptionTest() override = default; }; -// Negative test case with no source filter -TEST_F(RpcClientUSubscriptionTest, ConstructorTestSuccess) { // NOLINT +// +// Tests for subscribe method +// + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + SubscribeRoundtripWithValidProtoPayload) { + bool server_callback_executed = false; + SubscriptionRequest server_capture; + SubscriptionResponse server_response; + *server_response.mutable_topic() = getSubscriptionTopic(); + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf( + message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + Payload response_payload(server_response); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); - auto rpc_client_usubscription = std::make_unique< - uprotocol::core::usubscription::v3::RpcClientUSubscription>( - getMockTransportClient()); + const auto subscription_request = + RequestBuilder::buildSubscriptionRequest(getSubscriptionTopic()); - // Verify that the RpcClientUSubscription pointer is not null, indicating - // successful - ASSERT_NE(rpc_client_usubscription, nullptr); + auto response_or_status_future = + std::async(std::launch::async, + [&client, &subscription_request]() + -> uprotocol::utils::Expected { + return client.subscribe(subscription_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + subscription_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); } -// TEST_F(RpcClientUSubscriptionTest, SubscribeTestSuccess) { // NOLINT -// -// uprotocol::core::usubscription::v3::SubscriptionRequest -// subscription_request = -// uprotocol::utils::ProtoConverter::BuildSubscriptionRequest( -// getSubscriptionUUri(), -// uprotocol::core::usubscription::v3::SubscribeAttributes()); -// -// auto rpc_client_usubscription = std::make_unique< -// uprotocol::core::usubscription::v3::RpcClientUSubscription>( -// getMockTransportClient()); -// -// // Verify that the RpcClientUSubscription pointer is not null, indicating -// // successful -// ASSERT_NE(rpc_client_usubscription, nullptr); -// -// auto result = rpc_client_usubscription->subscribe(subscription_request); -// -// ASSERT_NE(&result, nullptr); -// } +TEST_F(RpcClientUSubscriptionTest, // NOLINT + SubscribeRoundtripWithValidProtoAnyPayload) { + bool server_callback_executed = false; + SubscriptionRequest server_capture; + SubscriptionResponse server_response; + *server_response.mutable_topic() = getSubscriptionTopic(); + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf( + message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + google::protobuf::Any any; + if (!any.PackFrom(server_response)) { + return std::nullopt; + } + Payload response_payload(any); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const auto subscription_request = + RequestBuilder::buildSubscriptionRequest(getSubscriptionTopic()); + + auto response_or_status_future = + std::async(std::launch::async, + [&client, &subscription_request]() + -> uprotocol::utils::Expected { + return client.subscribe(subscription_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + subscription_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); +} + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + SubscribeRoundtripWithValidProtoPayloadDifferentTopic) { + bool server_callback_executed = false; + SubscriptionRequest server_capture; + SubscriptionResponse server_response; + + constexpr uint32_t TOPIC_UE = 4321; + constexpr uint32_t TOPIC_RESOURCE_ID = 54321; + uprotocol::v1::UUri wrong_subscription_topic; + wrong_subscription_topic.set_authority_name("topic.usubscription.wrong"); + wrong_subscription_topic.set_ue_id(TOPIC_UE); + wrong_subscription_topic.set_ue_version_major(UE_VERSION_MAJOR); + wrong_subscription_topic.set_resource_id(TOPIC_RESOURCE_ID); + *server_response.mutable_topic() = wrong_subscription_topic; + + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf( + message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + Payload response_payload(server_response); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const auto subscription_request = + RequestBuilder::buildSubscriptionRequest(getSubscriptionTopic()); + + auto response_or_status_future = + std::async(std::launch::async, + [&client, &subscription_request]() + -> uprotocol::utils::Expected { + return client.subscribe(subscription_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + subscription_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_FALSE( + response_or_status + .has_value()); // Should fail because the topics do not match +} + +//////////////////////////////// +// Tests for unsubscribe method// +//////////////////////////////// + +using UnsubscibeRequest = + uprotocol::core::usubscription::v3::UnsubscribeRequest; +using UnsubscribeResponse = + uprotocol::core::usubscription::v3::UnsubscribeResponse; + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + UnsubscribeRoundtripWithValidProtoPayload) { + bool server_callback_executed = false; + UnsubscibeRequest server_capture; + UnsubscribeResponse server_response; + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf(message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + Payload response_payload(server_response); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const auto unsubscribe_request = + RequestBuilder::buildUnsubscribeRequest(getSubscriptionTopic()); + + auto response_or_status_future = + std::async(std::launch::async, + [&client, &unsubscribe_request]() + -> uprotocol::utils::Expected { + return client.unsubscribe(unsubscribe_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + unsubscribe_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); +} + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + UnsubscribeRoundtripWithValidProtoAnyPayload) { + bool server_callback_executed = false; + UnsubscibeRequest server_capture; + UnsubscribeResponse server_response; + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf(message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + google::protobuf::Any any; + if (!any.PackFrom(server_response)) { + return std::nullopt; + } + Payload response_payload(any); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const auto unsubscribe_request = + RequestBuilder::buildUnsubscribeRequest(getSubscriptionTopic()); + + auto response_or_status_future = + std::async(std::launch::async, + [&client, &unsubscribe_request]() + -> uprotocol::utils::Expected { + return client.unsubscribe(unsubscribe_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + unsubscribe_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); +} + +//////////////////////////////// +// Tests for fetch_subscribers method// +//////////////////////////////// + +using FetchSubscribersRequest = + uprotocol::core::usubscription::v3::FetchSubscribersRequest; +using FetchSubscribersResponse = + uprotocol::core::usubscription::v3::FetchSubscribersResponse; + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + fetchSubscriberRoundtripWithValidProtoPayload) { + bool server_callback_executed = false; + FetchSubscribersRequest server_capture; + FetchSubscribersResponse server_response; + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf( + message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + Payload response_payload(server_response); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const auto fetch_subscribers_request = + RequestBuilder::buildFetchSubscribersRequest(getSubscriptionTopic()); + + auto response_or_status_future = std::async( + std::launch::async, + [&client, &fetch_subscribers_request]() + -> uprotocol::utils::Expected { + return client.fetch_subscribers(fetch_subscribers_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + fetch_subscribers_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); +} + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + FetchSubscriberRoundtripWithValidProtoAnyPayload) { + bool server_callback_executed = false; + FetchSubscribersRequest server_capture; + FetchSubscribersResponse server_response; + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf( + message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + google::protobuf::Any any; + if (!any.PackFrom(server_response)) { + return std::nullopt; + } + Payload response_payload(any); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const auto fetch_subscribers_request = + RequestBuilder::buildFetchSubscribersRequest(getSubscriptionTopic()); + + auto response_or_status_future = std::async( + std::launch::async, + [&client, &fetch_subscribers_request]() + -> uprotocol::utils::Expected { + return client.fetch_subscribers(fetch_subscribers_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + fetch_subscribers_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); +} + +//////////////////////////////// +// Tests for fetch_subscriptions method// +//////////////////////////////// + +using FetchSubscriptionsRequest = + uprotocol::core::usubscription::v3::FetchSubscriptionsRequest; +using FetchSubscriptionsResponse = + uprotocol::core::usubscription::v3::FetchSubscriptionsResponse; + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + fetchSubscriptionsRoundtripWithValidProtoPayload) { + bool server_callback_executed = false; + FetchSubscriptionsRequest server_capture; + FetchSubscriptionsResponse server_response; + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf( + message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + Payload response_payload(server_response); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const uprotocol::core::usubscription::v3::SubscriberInfo subscriber_info; + const auto fetch_subscriptions_request = + RequestBuilder::buildFetchSubscriptionsRequest(subscriber_info); + + auto response_or_status_future = std::async( + std::launch::async, + [&client, &fetch_subscriptions_request]() + -> uprotocol::utils::Expected { + return client.fetch_subscriptions(fetch_subscriptions_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + fetch_subscriptions_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); +} + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + fetchSubscriptionRoundtripWithValidProtoAnyPayload) { + bool server_callback_executed = false; + FetchSubscriptionsRequest server_capture; + FetchSubscriptionsResponse server_response; + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf( + message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + google::protobuf::Any any; + if (!any.PackFrom(server_response)) { + return std::nullopt; + } + Payload response_payload(any); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const uprotocol::core::usubscription::v3::SubscriberInfo subscriber_info; + const auto fetch_subscribers_request = + RequestBuilder::buildFetchSubscriptionsRequest(subscriber_info); + + auto response_or_status_future = std::async( + std::launch::async, + [&client, &fetch_subscribers_request]() + -> uprotocol::utils::Expected { + return client.fetch_subscriptions(fetch_subscribers_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + fetch_subscribers_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); +} + +//////////////////////////////// +// Tests for register_for_notification method// +//////////////////////////////// + +using NotificationsRequest = + uprotocol::core::usubscription::v3::NotificationsRequest; +using NotificationsResponse = + uprotocol::core::usubscription::v3::NotificationsResponse; + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + registerNotificationRoundtripWithValidProtoPayload) { + bool server_callback_executed = false; + NotificationsRequest server_capture; + NotificationsResponse server_response; + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf( + message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + Payload response_payload(server_response); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const auto notifications_request = + RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); + + auto response_or_status_future = std::async( + std::launch::async, + [&client, ¬ifications_request]() + -> uprotocol::utils::Expected { + return client.register_for_notifications(notifications_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + notifications_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); +} + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + registerNotificationRoundtripWithValidProtoAnyPayload) { + bool server_callback_executed = false; + NotificationsRequest server_capture; + NotificationsResponse server_response; + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf( + message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + google::protobuf::Any any; + if (!any.PackFrom(server_response)) { + return std::nullopt; + } + Payload response_payload(any); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const auto notifications_request = + RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); + + auto response_or_status_future = std::async( + std::launch::async, + [&client, ¬ifications_request]() + -> uprotocol::utils::Expected { + return client.register_for_notifications(notifications_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + notifications_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); +} + +//////////////////////////////// +// Tests for unregister_for_notification method// +//////////////////////////////// + +using NotificationsRequest = + uprotocol::core::usubscription::v3::NotificationsRequest; +using NotificationsResponse = + uprotocol::core::usubscription::v3::NotificationsResponse; + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + unregisterNotificationRoundtripWithValidProtoPayload) { + bool server_callback_executed = false; + NotificationsRequest server_capture; + NotificationsResponse server_response; + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf( + message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + Payload response_payload(server_response); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const auto notifications_request = + RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); + + auto response_or_status_future = std::async( + std::launch::async, + [&client, ¬ifications_request]() + -> uprotocol::utils::Expected { + return client.unregister_for_notifications(notifications_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + notifications_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); +} + +TEST_F(RpcClientUSubscriptionTest, // NOLINT + unregisterNotificationRoundtripWithValidProtoAnyPayload) { + bool server_callback_executed = false; + NotificationsRequest server_capture; + NotificationsResponse server_response; + auto server_or_status = uprotocol::communication::RpcServer::create( + getServerTransport(), getServerMethodUuri(), + [&server_callback_executed, &server_capture, + &server_response](const UMessage& message) -> std::optional { + server_callback_executed = true; + auto request_or_status = + ProtoConverter::extractFromProtobuf( + message); + if (!request_or_status.has_value()) { + return std::nullopt; + } + server_capture = request_or_status.value(); + google::protobuf::Any any; + if (!any.PackFrom(server_response)) { + return std::nullopt; + } + Payload response_payload(any); + return response_payload; + }, + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY); + + ASSERT_TRUE(server_or_status.has_value()); + ASSERT_NE(server_or_status.value(), nullptr); + EXPECT_TRUE(getServerTransport()->getListener()); + + auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( + getClientTransport()); + + const auto notifications_request = + RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); + + auto response_or_status_future = std::async( + std::launch::async, + [&client, ¬ifications_request]() + -> uprotocol::utils::Expected { + return client.unregister_for_notifications(notifications_request); + }); + + // wait to give the client time to send the request. Otherwise this would + // cause a race condition + int counter = ITERATIONS_TILL_TIMEOUT; + while (counter > 0 && getClientTransport()->getSendCount() == 0) { + counter--; + std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); + } + ASSERT_EQ(getClientTransport()->getSendCount(), 1); + EXPECT_TRUE(getClientTransport()->getListener()); + + (*getServerTransport()->getListener())(getClientTransport()->getMessage()); + EXPECT_TRUE(server_callback_executed); + EXPECT_EQ(server_capture.SerializeAsString(), + notifications_request.SerializeAsString()); + + getClientTransport()->mockMessage(getServerTransport()->getMessage()); + EXPECT_TRUE(getClientTransport()->getListener()); + EXPECT_EQ(getClientTransport()->getSendCount(), 1); + auto response_or_status = response_or_status_future.get(); + ASSERT_TRUE(response_or_status.has_value()); + EXPECT_EQ(response_or_status.value().SerializeAsString(), + server_response.SerializeAsString()); +} -} // namespace +}; // namespace diff --git a/test/coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp b/test/coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp new file mode 100644 index 000000000..a2b0ad7ff --- /dev/null +++ b/test/coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h" + +constexpr uint16_t RESOURCE_ID_TEST = 0x0001; +constexpr uint16_t RESOURCE_ID_NOTIFICATION_ID = 0x8000; + +namespace uprotocol::core::usubscription::v3 { +class USubscriptionUUriBuilderTest : public ::testing::Test { +private: + v1::UUri expected_uri_; + +protected: + v1::UUri getExpectedUri() const { return expected_uri_; } + + void SetUp() override { + expected_uri_.set_authority_name("core.usubscription"); + expected_uri_.set_ue_id(0); + expected_uri_.set_ue_version_major(3); + } + + void TearDown() override {} +}; + +TEST_F(USubscriptionUUriBuilderTest, GetServiceUriWithResourceId) { // NOLINT + // Example test case for building a subscription UUri + auto expected_uri = getExpectedUri(); + expected_uri.set_resource_id(RESOURCE_ID_TEST); + const USubscriptionUUriBuilder builder; + const v1::UUri actual_uri = + builder.getServiceUriWithResourceId(RESOURCE_ID_TEST); + + EXPECT_TRUE(actual_uri.IsInitialized()); + EXPECT_EQ(actual_uri.GetTypeName(), "uprotocol.v1.UUri"); + EXPECT_EQ(actual_uri.SerializeAsString(), expected_uri.SerializeAsString()); +} + +TEST_F(USubscriptionUUriBuilderTest, GetNotificationUri) { // NOLINT + auto expected_uri = getExpectedUri(); + expected_uri.set_resource_id(RESOURCE_ID_NOTIFICATION_ID); + USubscriptionUUriBuilder builder; + v1::UUri actual_uri = builder.getNotificationUri(); + EXPECT_TRUE(actual_uri.IsInitialized()); + EXPECT_EQ(actual_uri.GetTypeName(), "uprotocol.v1.UUri"); + EXPECT_EQ(actual_uri.SerializeAsString(), expected_uri.SerializeAsString()); +} + +} // namespace uprotocol::core::usubscription::v3 diff --git a/test/extra/PublisherSubscriberTest.cpp b/test/extra/PublisherSubscriberTest.cpp index a14f346f8..2c1e1c076 100644 --- a/test/extra/PublisherSubscriberTest.cpp +++ b/test/extra/PublisherSubscriberTest.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. From 2bd40b16ca917e8dd4faf38ba1a21bf5d962be19 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 7 Jun 2025 19:31:28 +0200 Subject: [PATCH 3/8] made USubscription async with InvokeProtoFuture return type --- .../up-cpp/client/usubscription/v3/Consumer.h | 2 - .../usubscription/v3/RpcClientUSubscription.h | 46 +++-- .../client/usubscription/v3/USubscription.h | 41 ++-- include/up-cpp/communication/RpcClient.h | 78 +++++--- .../v3/RpcClientUSubscription.cpp | 130 ++++++------- src/communication/RpcClient.cpp | 9 - .../v3/RpcClientUSubscriptionTest.cpp | 181 +++--------------- test/extra/PublisherSubscriberTest.cpp | 2 +- 8 files changed, 186 insertions(+), 303 deletions(-) diff --git a/include/up-cpp/client/usubscription/v3/Consumer.h b/include/up-cpp/client/usubscription/v3/Consumer.h index b9678d315..3a2df2f54 100644 --- a/include/up-cpp/client/usubscription/v3/Consumer.h +++ b/include/up-cpp/client/usubscription/v3/Consumer.h @@ -16,9 +16,7 @@ #include #include #include -#include #include -#include #include "RequestBuilder.h" #include "USubscriptionUUriBuilder.h" diff --git a/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h b/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h index 7d2c80254..a719a2523 100644 --- a/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h +++ b/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h @@ -52,15 +52,13 @@ struct RpcClientUSubscription : USubscription { using ListenCallback = transport::UTransport::ListenCallback; using ListenHandle = transport::UTransport::ListenHandle; - template - Response invokeResponse(communication::RpcClient rpc_client); - /// @brief Subscribes from a given topic /// /// @param subscription_request The request object containing the topic to /// subscribe to - /// @return Returns a SubscriptionResponse on success and a UStatus else - utils::Expected subscribe( + /// @return Returns a future that reslves to a SubscriptionResponse on + /// success and a UStatus else + communication::RpcClient::InvokeProtoFuture subscribe( const SubscriptionRequest& subscription_request) override; /// @brief Unsubscribes from a given topic @@ -68,15 +66,15 @@ struct RpcClientUSubscription : USubscription { /// @param unsubscribe_request The request object containing the topic to /// unsubscribe from /// @return Returns an UnsubscribeResponse on success and a UStatus else - utils::Expected unsubscribe( - const UnsubscribeRequest& unsubscribe_request) override; + communication::RpcClient::InvokeProtoFuture + unsubscribe(const UnsubscribeRequest& unsubscribe_request) override; /// @brief Fetches the list of topics the client is subscribed to /// /// @param fetch_subscriptions_request The request object - /// @return Returns a FetchSubscriptionsResponse on success and a UStatus - /// else - utils::Expected + /// @return Returns a future that reslves to a FetchSubscriptionsResponse on + /// success and a UStatus else + communication::RpcClient::InvokeProtoFuture fetch_subscriptions( const FetchSubscriptionsRequest& fetch_subscriptions_request) override; @@ -85,15 +83,17 @@ struct RpcClientUSubscription : USubscription { /// @param fetch_subscribers_request The request object containing the topic /// for which the subscribers are to be fetched /// @return Returns a FetchSubscribersResponse on success and a UStatus else - utils::Expected fetch_subscribers( + communication::RpcClient::InvokeProtoFuture + fetch_subscribers( const FetchSubscribersRequest& fetch_subscribers_request) override; /// @brief Registers to receive notifications /// /// @param register_notifications_request The request object containing /// the details to register for notifications - /// @return Returns a NotificationResponse on success and a UStatus else - utils::Expected + /// @return Returns a future that resolves to a NotificationResponse on + /// success and a UStatus else + communication::RpcClient::InvokeProtoFuture register_for_notifications( const NotificationsRequest& register_notifications_request) override; @@ -101,8 +101,9 @@ struct RpcClientUSubscription : USubscription { /// /// @param unregister_notifications_request The request object containing /// the details needed to stop receiving notifications. - /// @return Returns a NotificationResponse on success and a UStatus else - utils::Expected + /// @return Returns future that resolves to a NotificationResponse on + /// success and a UStatus else + communication::RpcClient::InvokeProtoFuture unregister_for_notifications( const NotificationsRequest& unregister_notifications_request) override; @@ -110,13 +111,24 @@ struct RpcClientUSubscription : USubscription { /// /// @param transport Transport used to send messages explicit RpcClientUSubscription( - std::shared_ptr transport) - : transport_(std::move(transport)) {} + std::shared_ptr transport); ~RpcClientUSubscription() override = default; private: std::shared_ptr transport_; + // Currently a single RpcClient can only send messages to a fixed UUri. + // This forces us to use different RpcClients for different resource ids. + // The alternative would be to create a new RpcClient for each request and + // return a pointer to the client together with the InvokeProtoFuture to + // keep it alive + std::shared_ptr subscribe_client_; + std::shared_ptr unsubscribe_client_; + std::shared_ptr fetch_subscriptions_client_; + std::shared_ptr fetch_subscribers_client_; + std::shared_ptr register_for_notification_client_; + std::shared_ptr + unregister_for_notification_client_; USubscriptionUUriBuilder uuri_builder_; }; diff --git a/include/up-cpp/client/usubscription/v3/USubscription.h b/include/up-cpp/client/usubscription/v3/USubscription.h index 1eea2a67e..c0b5d48a3 100644 --- a/include/up-cpp/client/usubscription/v3/USubscription.h +++ b/include/up-cpp/client/usubscription/v3/USubscription.h @@ -12,9 +12,9 @@ #ifndef UP_CPP_CLIENT_USUBSCRIPTION_V3_USUBSCRIPTION_H #define UP_CPP_CLIENT_USUBSCRIPTION_V3_USUBSCRIPTION_H #include -#include #include +#include "up-cpp/communication/RpcClient.h" #include "up-cpp/utils/Expected.h" namespace uprotocol::core::usubscription::v3 { @@ -33,37 +33,45 @@ struct USubscription { /// response on success or else a status code /// /// @param subscription_request containing a topic to subscribe to - /// @return SubscriptionReponse on success and UStatus else - virtual ResponseOrStatus subscribe( - const SubscriptionRequest& subscription_request) = 0; + /// @return future that resolves to a SubscriptionReponse on success and + /// UStatus else + virtual communication::RpcClient::InvokeProtoFuture + subscribe(const SubscriptionRequest& subscription_request) = 0; /// @brief sends an unsubscribe request to a USubscription backend and a /// response on success or else a status code /// /// @param unsubscribe_request containing a topic to unsubscribe - /// @return UnsubscribeResponse on success and UStatus else - virtual ResponseOrStatus unsubscribe( - const UnsubscribeRequest& unsubscribe_request) = 0; + /// @return future that resolves to UnsubscribeResponse on success and + /// UStatus else + virtual communication::RpcClient::InvokeProtoFuture + unsubscribe(const UnsubscribeRequest& unsubscribe_request) = 0; /// @brief fetches all topics the client is subscribed to from the backend /// /// @param fetch_subscriptions_request - /// @return FetchSubscriptionsResponse on success and UStatus else - virtual ResponseOrStatus fetch_subscriptions( + /// @return future that resolves to FetchSubscriptionsResponse on success + /// and UStatus else + virtual communication::RpcClient::InvokeProtoFuture< + FetchSubscriptionsResponse> + fetch_subscriptions( const FetchSubscriptionsRequest& fetch_subscriptions_request) = 0; /// @brief registers for notifications to a USubscription backend /// /// @param register_notifications_request - /// @return NotificationResponse on success and UStatus else - virtual ResponseOrStatus register_for_notifications( + /// @return future that resolves to NotificationResponse on success and + /// UStatus else + virtual communication::RpcClient::InvokeProtoFuture + register_for_notifications( const NotificationsRequest& register_notifications_request) = 0; /// @brief unregisters for notifications to a USubscription backend /// /// @param unregister_notifications_request - /// @return NotificationResponse on success and UStatus else - virtual ResponseOrStatus + /// @return future that resolves to NotificationResponse on success and + /// UStatus else + virtual communication::RpcClient::InvokeProtoFuture unregister_for_notifications( const NotificationsRequest& unregister_notifications_request) = 0; @@ -71,8 +79,11 @@ struct USubscription { /// /// @param fetch_subscriptions_request containing the topic for which the /// subscribers are fetched - /// @return FetchSubscriptionsResponse on success and UStatus else - virtual ResponseOrStatus fetch_subscribers( + /// @return future that resolves to FetchSubscriptionsResponse on success + /// and UStatus else + virtual communication::RpcClient::InvokeProtoFuture< + FetchSubscribersResponse> + fetch_subscribers( const FetchSubscribersRequest& fetch_subscribers_request) = 0; }; diff --git a/include/up-cpp/communication/RpcClient.h b/include/up-cpp/communication/RpcClient.h index 0e3690e02..b0afe3ee1 100644 --- a/include/up-cpp/communication/RpcClient.h +++ b/include/up-cpp/communication/RpcClient.h @@ -75,17 +75,22 @@ struct RpcClient { /// for the duration of an RPC call. using InvokeHandle = Connection::Handle; - /// @brief Extension to std::future that also holds a callback handle - class InvokeFuture { + /// @brief Extension to std::future with template type that also holds a + /// callback handle + template + class InvokeProtoFuture { InvokeHandle callback_handle_; - std::future future_; + std::future> future_; public: - InvokeFuture(); - InvokeFuture(InvokeFuture&&) noexcept; - InvokeFuture(std::future&&, InvokeHandle&&) noexcept; + InvokeProtoFuture() = default; + InvokeProtoFuture(InvokeProtoFuture&& other) noexcept = default; + InvokeProtoFuture& operator=(InvokeProtoFuture&& other) noexcept = + default; - InvokeFuture& operator=(InvokeFuture&&) noexcept; + InvokeProtoFuture(std::future>&& future, + InvokeHandle&& handle) noexcept + : callback_handle_(std::move(handle)), future_(std::move(future)) {} /// @name Passthroughs for std::future /// @{ @@ -103,6 +108,8 @@ struct RpcClient { /// @} }; + using InvokeFuture = InvokeProtoFuture; + /// @brief Invokes an RPC method by sending a request message. /// /// @param The method that will be invoked @@ -174,36 +181,47 @@ struct RpcClient { [[nodiscard]] InvokeFuture invokeMethod(const v1::UUri&); template - ResponseOrStatus invokeProtoMethod(const v1::UUri& method, const R& request_message) { + InvokeProtoFuture invokeProtoMethod(const v1::UUri& method, const R& request_message) { + auto result_promise = + std::make_shared>>(); + auto future = result_promise->get_future(); auto payload_or_status = uprotocol::utils::ProtoConverter::protoToPayload(request_message); if (!payload_or_status.has_value()) { - return ResponseOrStatus( - UnexpectedStatus(payload_or_status.error())); + result_promise->set_value(ResponseOrStatus( + UnexpectedStatus(payload_or_status.error()))); + return {std::move(future), InvokeHandle()}; } datamodel::builder::Payload tmp_payload(payload_or_status.value()); - auto message_or_status = - this->invokeMethod(method, std::move(tmp_payload)).get(); - - if (!message_or_status.has_value()) { - return ResponseOrStatus( - UnexpectedStatus(message_or_status.error())); - } - - auto response_or_status = utils::ProtoConverter::extractFromProtobuf( - message_or_status.value()); - - if (!response_or_status.has_value()) { - spdlog::error( - "invokeProtoMethod: Error when extracting response from " - "protobuf."); - return response_or_status; - } - - return ResponseOrStatus(response_or_status.value()); + auto handle = invokeMethod( + builder_.build(std::move(tmp_payload)), + [result_promise](const MessageOrStatus& message_or_status) { + if (!message_or_status.has_value()) { + result_promise->set_value(ResponseOrStatus( + UnexpectedStatus(message_or_status.error()))); + return; + } + auto response_or_status = + utils::ProtoConverter::extractFromProtobuf( + message_or_status.value()); + + if (!response_or_status.has_value()) { + spdlog::error( + "invokeProtoMethod: Error when extracting response " + "from " + "protobuf."); + result_promise->set_value(response_or_status); + return; + } + + result_promise->set_value( + ResponseOrStatus(response_or_status.value())); + }); + + return {std::move(future), std::move(handle)}; } /// @brief Default move constructor (defined in RpcClient.cpp) @@ -229,4 +247,4 @@ struct RpcClient { } // namespace uprotocol::communication -#endif // UP_CPP_COMMUNICATION_RPCCLIENT_H +#endif // UP_CPP_COMMUNICATION_RPCCLIENT_H \ No newline at end of file diff --git a/src/client/usubscription/v3/RpcClientUSubscription.cpp b/src/client/usubscription/v3/RpcClientUSubscription.cpp index 60565041b..a72930223 100644 --- a/src/client/usubscription/v3/RpcClientUSubscription.cpp +++ b/src/client/usubscription/v3/RpcClientUSubscription.cpp @@ -9,113 +9,101 @@ // // SPDX-License-Identifier: Apache-2.0 -#include #include -#include #include -#include -#include +#include #include "up-cpp/communication/RpcClient.h" -#include "up-cpp/utils/Expected.h" auto priority = uprotocol::v1::UPriority::UPRIORITY_CS4; // MUST be >= 4 namespace uprotocol::core::usubscription::v3 { -RpcClientUSubscription::ResponseOrStatus -RpcClientUSubscription::subscribe( - const SubscriptionRequest& subscription_request) { - communication::RpcClient rpc_client( +RpcClientUSubscription::RpcClientUSubscription( + std::shared_ptr transport) + : transport_(std::move(transport)) { + subscribe_client_ = std::make_shared( transport_, uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_SUBSCRIBE), priority, USUBSCRIPTION_REQUEST_TTL); - auto response_or_status = - rpc_client.invokeProtoMethod( - subscription_request); - - if (!response_or_status.has_value()) { - return response_or_status; - } - auto subscription_response = response_or_status.value(); - - if (subscription_response.topic().SerializeAsString() != - subscription_request.topic().SerializeAsString()) { - v1::UStatus status; - status.set_code(v1::UCode::INTERNAL); - status.set_message("subscribe: topics do not match."); - return ResponseOrStatus( - utils::Unexpected(status)); - } - - return ResponseOrStatus( - std::move(subscription_response)); -} - -RpcClientUSubscription::ResponseOrStatus -RpcClientUSubscription::unsubscribe( - const UnsubscribeRequest& unsubscribe_request) { - communication::RpcClient rpc_client( + unsubscribe_client_ = std::make_shared( transport_, uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_UNSUBSCRIBE), priority, USUBSCRIPTION_REQUEST_TTL); - return rpc_client.invokeProtoMethod( - unsubscribe_request); -} - -RpcClientUSubscription::ResponseOrStatus -RpcClientUSubscription::fetch_subscriptions( - const FetchSubscriptionsRequest& fetch_subscriptions_request) { - communication::RpcClient rpc_client( + fetch_subscriptions_client_ = std::make_shared( transport_, uuri_builder_.getServiceUriWithResourceId( RESOURCE_ID_FETCH_SUBSCRIPTIONS), priority, USUBSCRIPTION_REQUEST_TTL); - return rpc_client.invokeProtoMethod( - fetch_subscriptions_request); -} - -RpcClientUSubscription::ResponseOrStatus -RpcClientUSubscription::fetch_subscribers( - const FetchSubscribersRequest& fetch_subscribers_request) { - communication::RpcClient rpc_client( + fetch_subscribers_client_ = std::make_shared( transport_, uuri_builder_.getServiceUriWithResourceId( RESOURCE_ID_FETCH_SUBSCRIBERS), priority, USUBSCRIPTION_REQUEST_TTL); - return rpc_client.invokeProtoMethod( - fetch_subscribers_request); + register_for_notification_client_ = + std::make_shared( + transport_, + uuri_builder_.getServiceUriWithResourceId( + RESOURCE_ID_REGISTER_FOR_NOTIFICATIONS), + priority, USUBSCRIPTION_REQUEST_TTL); + + unregister_for_notification_client_ = + std::make_shared( + transport_, + uuri_builder_.getServiceUriWithResourceId( + RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS), + priority, USUBSCRIPTION_REQUEST_TTL); +} + +communication::RpcClient::InvokeProtoFuture +RpcClientUSubscription::subscribe( + const SubscriptionRequest& subscription_request) { + return subscribe_client_->invokeProtoMethod( + subscription_request); +} + +communication::RpcClient::InvokeProtoFuture +RpcClientUSubscription::unsubscribe( + const UnsubscribeRequest& unsubscribe_request) { + return unsubscribe_client_->invokeProtoMethod( + unsubscribe_request); +} + +communication::RpcClient::InvokeProtoFuture +RpcClientUSubscription::fetch_subscriptions( + const FetchSubscriptionsRequest& fetch_subscriptions_request) { + return fetch_subscriptions_client_ + ->invokeProtoMethod( + fetch_subscriptions_request); +} + +communication::RpcClient::InvokeProtoFuture +RpcClientUSubscription::fetch_subscribers( + const FetchSubscribersRequest& fetch_subscribers_request) { + return fetch_subscribers_client_ + ->invokeProtoMethod( + fetch_subscribers_request); } -RpcClientUSubscription::ResponseOrStatus +communication::RpcClient::InvokeProtoFuture RpcClientUSubscription::register_for_notifications( const NotificationsRequest& register_notifications_request) { - communication::RpcClient rpc_client( - transport_, - uuri_builder_.getServiceUriWithResourceId( - RESOURCE_ID_REGISTER_FOR_NOTIFICATIONS), - priority, USUBSCRIPTION_REQUEST_TTL); - - return rpc_client.invokeProtoMethod( - register_notifications_request); + return register_for_notification_client_ + ->invokeProtoMethod( + register_notifications_request); } -RpcClientUSubscription::ResponseOrStatus +communication::RpcClient::InvokeProtoFuture RpcClientUSubscription::unregister_for_notifications( const NotificationsRequest& unregister_notifications_request) { - communication::RpcClient rpc_client( - transport_, - uuri_builder_.getServiceUriWithResourceId( - RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS), - priority, USUBSCRIPTION_REQUEST_TTL); - - return rpc_client.invokeProtoMethod( - unregister_notifications_request); + return unregister_for_notification_client_ + ->invokeProtoMethod( + unregister_notifications_request); } } // namespace uprotocol::core::usubscription::v3 diff --git a/src/communication/RpcClient.cpp b/src/communication/RpcClient.cpp index 8fd100a48..63411d05d 100644 --- a/src/communication/RpcClient.cpp +++ b/src/communication/RpcClient.cpp @@ -253,15 +253,6 @@ RpcClient::InvokeFuture RpcClient::invokeMethod(const v1::UUri& method) { RpcClient::RpcClient(RpcClient&&) noexcept = default; RpcClient::~RpcClient() = default; -RpcClient::InvokeFuture::InvokeFuture() = default; -RpcClient::InvokeFuture::InvokeFuture(InvokeFuture&& other) noexcept = default; -RpcClient::InvokeFuture& RpcClient::InvokeFuture::operator=( - InvokeFuture&& other) noexcept = default; - -RpcClient::InvokeFuture::InvokeFuture(std::future&& future, - InvokeHandle&& handle) noexcept - : callback_handle_(std::move(handle)), future_(std::move(future)) {} - } // namespace uprotocol::communication /////////////////////////////////////////////////////////////////////////////// diff --git a/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp b/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp index 0fa50f8c6..3bf1baf77 100644 --- a/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp +++ b/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp @@ -144,13 +144,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto subscription_request = RequestBuilder::buildSubscriptionRequest(getSubscriptionTopic()); - auto response_or_status_future = - std::async(std::launch::async, - [&client, &subscription_request]() - -> uprotocol::utils::Expected { - return client.subscribe(subscription_request); - }); + auto response_or_status_future = client.subscribe(subscription_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition @@ -213,13 +207,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto subscription_request = RequestBuilder::buildSubscriptionRequest(getSubscriptionTopic()); - auto response_or_status_future = - std::async(std::launch::async, - [&client, &subscription_request]() - -> uprotocol::utils::Expected { - return client.subscribe(subscription_request); - }); + auto response_or_status_future = client.subscribe(subscription_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition @@ -245,80 +233,6 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT server_response.SerializeAsString()); } -TEST_F(RpcClientUSubscriptionTest, // NOLINT - SubscribeRoundtripWithValidProtoPayloadDifferentTopic) { - bool server_callback_executed = false; - SubscriptionRequest server_capture; - SubscriptionResponse server_response; - - constexpr uint32_t TOPIC_UE = 4321; - constexpr uint32_t TOPIC_RESOURCE_ID = 54321; - uprotocol::v1::UUri wrong_subscription_topic; - wrong_subscription_topic.set_authority_name("topic.usubscription.wrong"); - wrong_subscription_topic.set_ue_id(TOPIC_UE); - wrong_subscription_topic.set_ue_version_major(UE_VERSION_MAJOR); - wrong_subscription_topic.set_resource_id(TOPIC_RESOURCE_ID); - *server_response.mutable_topic() = wrong_subscription_topic; - - auto server_or_status = uprotocol::communication::RpcServer::create( - getServerTransport(), getServerMethodUuri(), - [&server_callback_executed, &server_capture, - &server_response](const UMessage& message) -> std::optional { - server_callback_executed = true; - auto request_or_status = - ProtoConverter::extractFromProtobuf( - message); - if (!request_or_status.has_value()) { - return std::nullopt; - } - server_capture = request_or_status.value(); - Payload response_payload(server_response); - return response_payload; - }, - uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF); - - ASSERT_TRUE(server_or_status.has_value()); - ASSERT_NE(server_or_status.value(), nullptr); - EXPECT_TRUE(getServerTransport()->getListener()); - - auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); - - const auto subscription_request = - RequestBuilder::buildSubscriptionRequest(getSubscriptionTopic()); - - auto response_or_status_future = - std::async(std::launch::async, - [&client, &subscription_request]() - -> uprotocol::utils::Expected { - return client.subscribe(subscription_request); - }); - - // wait to give the client time to send the request. Otherwise this would - // cause a race condition - int counter = ITERATIONS_TILL_TIMEOUT; - while (counter > 0 && getClientTransport()->getSendCount() == 0) { - counter--; - std::this_thread::sleep_for(MILLISECONDS_PER_ITERATION); - } - ASSERT_EQ(getClientTransport()->getSendCount(), 1); - EXPECT_TRUE(getClientTransport()->getListener()); - - (*getServerTransport()->getListener())(getClientTransport()->getMessage()); - EXPECT_TRUE(server_callback_executed); - EXPECT_EQ(server_capture.SerializeAsString(), - subscription_request.SerializeAsString()); - - getClientTransport()->mockMessage(getServerTransport()->getMessage()); - EXPECT_TRUE(getClientTransport()->getListener()); - EXPECT_EQ(getClientTransport()->getSendCount(), 1); - auto response_or_status = response_or_status_future.get(); - ASSERT_FALSE( - response_or_status - .has_value()); // Should fail because the topics do not match -} - //////////////////////////////// // Tests for unsubscribe method// //////////////////////////////// @@ -359,13 +273,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto unsubscribe_request = RequestBuilder::buildUnsubscribeRequest(getSubscriptionTopic()); - auto response_or_status_future = - std::async(std::launch::async, - [&client, &unsubscribe_request]() - -> uprotocol::utils::Expected { - return client.unsubscribe(unsubscribe_request); - }); + auto response_or_status_future = client.unsubscribe(unsubscribe_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition @@ -426,13 +334,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto unsubscribe_request = RequestBuilder::buildUnsubscribeRequest(getSubscriptionTopic()); - auto response_or_status_future = - std::async(std::launch::async, - [&client, &unsubscribe_request]() - -> uprotocol::utils::Expected { - return client.unsubscribe(unsubscribe_request); - }); + auto response_or_status_future = client.unsubscribe(unsubscribe_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition @@ -499,13 +401,8 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto fetch_subscribers_request = RequestBuilder::buildFetchSubscribersRequest(getSubscriptionTopic()); - auto response_or_status_future = std::async( - std::launch::async, - [&client, &fetch_subscribers_request]() - -> uprotocol::utils::Expected { - return client.fetch_subscribers(fetch_subscribers_request); - }); + auto response_or_status_future = + client.fetch_subscribers(fetch_subscribers_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition @@ -567,13 +464,8 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto fetch_subscribers_request = RequestBuilder::buildFetchSubscribersRequest(getSubscriptionTopic()); - auto response_or_status_future = std::async( - std::launch::async, - [&client, &fetch_subscribers_request]() - -> uprotocol::utils::Expected { - return client.fetch_subscribers(fetch_subscribers_request); - }); + auto response_or_status_future = + client.fetch_subscribers(fetch_subscribers_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition @@ -641,13 +533,8 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto fetch_subscriptions_request = RequestBuilder::buildFetchSubscriptionsRequest(subscriber_info); - auto response_or_status_future = std::async( - std::launch::async, - [&client, &fetch_subscriptions_request]() - -> uprotocol::utils::Expected { - return client.fetch_subscriptions(fetch_subscriptions_request); - }); + auto response_or_status_future = + client.fetch_subscriptions(fetch_subscriptions_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition @@ -710,13 +597,8 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto fetch_subscribers_request = RequestBuilder::buildFetchSubscriptionsRequest(subscriber_info); - auto response_or_status_future = std::async( - std::launch::async, - [&client, &fetch_subscribers_request]() - -> uprotocol::utils::Expected { - return client.fetch_subscriptions(fetch_subscribers_request); - }); + auto response_or_status_future = + client.fetch_subscriptions(fetch_subscribers_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition @@ -783,13 +665,8 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto notifications_request = RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); - auto response_or_status_future = std::async( - std::launch::async, - [&client, ¬ifications_request]() - -> uprotocol::utils::Expected { - return client.register_for_notifications(notifications_request); - }); + auto response_or_status_future = + client.register_for_notifications(notifications_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition @@ -851,13 +728,8 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto notifications_request = RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); - auto response_or_status_future = std::async( - std::launch::async, - [&client, ¬ifications_request]() - -> uprotocol::utils::Expected { - return client.register_for_notifications(notifications_request); - }); + auto response_or_status_future = + client.register_for_notifications(notifications_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition @@ -924,13 +796,8 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto notifications_request = RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); - auto response_or_status_future = std::async( - std::launch::async, - [&client, ¬ifications_request]() - -> uprotocol::utils::Expected { - return client.unregister_for_notifications(notifications_request); - }); + auto response_or_status_future = + client.unregister_for_notifications(notifications_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition @@ -951,6 +818,9 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getClientTransport()->getListener()); EXPECT_EQ(getClientTransport()->getSendCount(), 1); auto response_or_status = response_or_status_future.get(); + if (!response_or_status.has_value()) { + std::cout << response_or_status.error().DebugString() << std::endl; + } ASSERT_TRUE(response_or_status.has_value()); EXPECT_EQ(response_or_status.value().SerializeAsString(), server_response.SerializeAsString()); @@ -992,13 +862,8 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT const auto notifications_request = RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); - auto response_or_status_future = std::async( - std::launch::async, - [&client, ¬ifications_request]() - -> uprotocol::utils::Expected { - return client.unregister_for_notifications(notifications_request); - }); + auto response_or_status_future = + client.unregister_for_notifications(notifications_request); // wait to give the client time to send the request. Otherwise this would // cause a race condition diff --git a/test/extra/PublisherSubscriberTest.cpp b/test/extra/PublisherSubscriberTest.cpp index 2c1e1c076..a14f346f8 100644 --- a/test/extra/PublisherSubscriberTest.cpp +++ b/test/extra/PublisherSubscriberTest.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation +// SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. From 19fcfd162b74de8b9fd01439241b388eef8929d3 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 16 Jun 2025 22:25:07 +0200 Subject: [PATCH 4/8] implemented cr comments --- .../up-cpp/client/usubscription/v3/Consumer.h | 15 +++--- .../client/usubscription/v3/RequestBuilder.h | 6 +-- .../usubscription/v3/RpcClientUSubscription.h | 10 +++- .../v3/USubscriptionUUriBuilder.h | 10 ++++ include/up-cpp/communication/RpcClient.h | 48 +++++++++++++++---- include/up-cpp/utils/ProtoConverter.h | 2 +- src/client/usubscription/v3/Consumer.cpp | 12 ++--- .../usubscription/v3/RequestBuilder.cpp | 2 +- .../v3/RpcClientUSubscription.cpp | 6 ++- .../v3/USubscriptionUUriBuilder.cpp | 40 ++++++++++++++-- .../client/usubscription/v3/ConsumerTest.cpp | 6 +-- .../usubscription/v3/RequestBuilderTest.cpp | 4 +- .../v3/RpcClientUSubscriptionTest.cpp | 33 ++++++++----- .../v3/USubscriptionUUriBuilderTest.cpp | 12 ++++- 14 files changed, 152 insertions(+), 54 deletions(-) diff --git a/include/up-cpp/client/usubscription/v3/Consumer.h b/include/up-cpp/client/usubscription/v3/Consumer.h index 3a2df2f54..b88f2aafc 100644 --- a/include/up-cpp/client/usubscription/v3/Consumer.h +++ b/include/up-cpp/client/usubscription/v3/Consumer.h @@ -51,7 +51,7 @@ struct Consumer { const v1::UUri& subscription_topic, ListenCallback&& callback, v1::UPriority priority, std::chrono::milliseconds subscription_request_ttl, - core::usubscription::v3::USubscriptionOptions consumer_options); + core::usubscription::v3::CallOptions consumer_options); /// @brief Unsubscribe from the topic and call uSubscription service to /// close the subscription. @@ -76,10 +76,9 @@ struct Consumer { /// /// @param transport Transport to register with. /// @param subscriber_details Additional details about the subscriber. - Consumer( - std::shared_ptr transport, - v1::UUri subscription_topic, - core::usubscription::v3::USubscriptionOptions consumer_options = {}); + Consumer(std::shared_ptr transport, + v1::UUri subscription_topic, + core::usubscription::v3::CallOptions consumer_options = {}); private: // Transport @@ -88,7 +87,7 @@ struct Consumer { // Topic to subscribe to const v1::UUri subscription_topic_; // Additional details about uSubscription service - core::usubscription::v3::USubscriptionOptions consumer_options_; + core::usubscription::v3::CallOptions consumer_options_; // URI info about the uSubscription service core::usubscription::v3::USubscriptionUUriBuilder uSubscriptionUUriBuilder_; @@ -110,10 +109,10 @@ struct Consumer { friend std::unique_ptr std::make_unique, const uprotocol::v1::UUri, - uprotocol::core::usubscription::v3::USubscriptionOptions>( + uprotocol::core::usubscription::v3::CallOptions>( std::shared_ptr&&, const uprotocol::v1::UUri&&, - uprotocol::core::usubscription::v3::USubscriptionOptions&&); + uprotocol::core::usubscription::v3::CallOptions&&); /// @brief Build SubscriptionRequest for subscription request SubscriptionRequest buildSubscriptionRequest(); diff --git a/include/up-cpp/client/usubscription/v3/RequestBuilder.h b/include/up-cpp/client/usubscription/v3/RequestBuilder.h index 367d8cbe0..479be152b 100644 --- a/include/up-cpp/client/usubscription/v3/RequestBuilder.h +++ b/include/up-cpp/client/usubscription/v3/RequestBuilder.h @@ -18,11 +18,11 @@ namespace uprotocol::core::usubscription::v3 { -/// @struct USubscriptionOptions +/// @struct CallOptions /// @brief Additional details for uSubscription service. /// /// Each member represents an optional parameter for the uSubscription service. -struct USubscriptionOptions { +struct CallOptions { /// Permission level of the subscription request std::optional permission_level; /// TAP token for access. @@ -48,7 +48,7 @@ struct RequestBuilder { /// /// @return A `SubscriptionRequest` configured for the specified topic. static SubscriptionRequest buildSubscriptionRequest( - const v1::UUri& topic, const USubscriptionOptions& options = {}); + const v1::UUri& topic, const CallOptions& options = {}); /// @brief Builds an unsubscription request for a given topic. /// diff --git a/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h b/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h index a719a2523..59da5c70a 100644 --- a/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h +++ b/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h @@ -45,6 +45,11 @@ namespace uprotocol::core::usubscription::v3 { using v3::SubscriptionRequest; using v3::UnsubscribeRequest; +struct USubscriptionOptions { + std::string authority_name; + uint16_t instance_id = 0x0000; +}; + /// @brief Client which implements the USubscription interface struct RpcClientUSubscription : USubscription { using RpcClientUSubscriptionOrStatus = @@ -110,8 +115,11 @@ struct RpcClientUSubscription : USubscription { /// @brief Constructor /// /// @param transport Transport used to send messages + /// @param options Struct containing all options for the USubscription + /// client explicit RpcClientUSubscription( - std::shared_ptr transport); + std::shared_ptr transport, + const USubscriptionOptions& options); ~RpcClientUSubscription() override = default; diff --git a/include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h b/include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h index 3797f1c1c..28f718e38 100644 --- a/include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h +++ b/include/up-cpp/client/usubscription/v3/USubscriptionUUriBuilder.h @@ -33,6 +33,16 @@ struct USubscriptionUUriBuilder { /// @brief Constructor for uSubscriptionUUriBuilder. USubscriptionUUriBuilder(); + USubscriptionUUriBuilder& setAuthorityName( + const std::string& authority_name); + + USubscriptionUUriBuilder& setUEntityId(u_int32_t ue_id); + + USubscriptionUUriBuilder& setInstanceId(u_int16_t instance_id); + + USubscriptionUUriBuilder& setServiceId(u_int16_t service_id); + + USubscriptionUUriBuilder& setResourceId(u_int32_t resource_id); /// @brief Get the URI with a specific resource ID. /// /// @param resource_id The resource ID to set in the URI. diff --git a/include/up-cpp/communication/RpcClient.h b/include/up-cpp/communication/RpcClient.h index b0afe3ee1..3151f6c68 100644 --- a/include/up-cpp/communication/RpcClient.h +++ b/include/up-cpp/communication/RpcClient.h @@ -180,24 +180,30 @@ struct RpcClient { /// * A UMessage containing the response from the RPC target. [[nodiscard]] InvokeFuture invokeMethod(const v1::UUri&); - template - InvokeProtoFuture invokeProtoMethod(const v1::UUri& method, const R& request_message) { - auto result_promise = - std::make_shared>>(); - auto future = result_promise->get_future(); + template + InvokeHandle invokeProtoMethod(const R& request_message, + Callback&& callback) { auto payload_or_status = uprotocol::utils::ProtoConverter::protoToPayload(request_message); if (!payload_or_status.has_value()) { - result_promise->set_value(ResponseOrStatus( - UnexpectedStatus(payload_or_status.error()))); - return {std::move(future), InvokeHandle()}; + return {}; } datamodel::builder::Payload tmp_payload(payload_or_status.value()); + auto handle = invokeMethod(builder_.build(std::move(tmp_payload)), + std::move(callback)); + + return handle; + } - auto handle = invokeMethod( - builder_.build(std::move(tmp_payload)), + template + InvokeProtoFuture invokeProtoMethod(const v1::UUri& method, const R& request_message) { + auto result_promise = + std::make_shared>>(); + auto future = result_promise->get_future(); + auto handle = invokeProtoMethod( + request_message, [result_promise](const MessageOrStatus& message_or_status) { if (!message_or_status.has_value()) { result_promise->set_value(ResponseOrStatus( @@ -224,6 +230,28 @@ struct RpcClient { return {std::move(future), std::move(handle)}; } + template + InvokeFuture invokeProtoMethod(const R& request_message) { + auto result_promise = + std::make_shared>>(); + auto future = result_promise->get_future(); + + auto handle = invokeProtoMethod( + request_message, + [result_promise](const MessageOrStatus& message_or_status) { + if (!message_or_status.has_value()) { + result_promise->set_value(ResponseOrStatus( + UnexpectedStatus(message_or_status.error()))); + return; + } + + result_promise->set_value( + ResponseOrStatus(message_or_status.value())); + }); + + return {std::move(future), std::move(handle)}; + } + /// @brief Default move constructor (defined in RpcClient.cpp) RpcClient(RpcClient&&) noexcept; diff --git a/include/up-cpp/utils/ProtoConverter.h b/include/up-cpp/utils/ProtoConverter.h index 8f5c3deaa..3103f3e77 100644 --- a/include/up-cpp/utils/ProtoConverter.h +++ b/include/up-cpp/utils/ProtoConverter.h @@ -117,7 +117,6 @@ struct ProtoConverter { } return TOrStatus(response); } - case v1::UPayloadFormat::UPAYLOAD_FORMAT_UNSPECIFIED: case v1::UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY: { google::protobuf::Any any; if (!any.ParseFromString(message.payload())) { @@ -138,6 +137,7 @@ struct ProtoConverter { } return TOrStatus(response); } + case v1::UPayloadFormat::UPAYLOAD_FORMAT_UNSPECIFIED: case v1::UPayloadFormat::UPAYLOAD_FORMAT_JSON: case v1::UPayloadFormat::UPAYLOAD_FORMAT_SOMEIP: case v1::UPayloadFormat::UPAYLOAD_FORMAT_SOMEIP_TLV: diff --git a/src/client/usubscription/v3/Consumer.cpp b/src/client/usubscription/v3/Consumer.cpp index 7738b18cc..83012f7d6 100644 --- a/src/client/usubscription/v3/Consumer.cpp +++ b/src/client/usubscription/v3/Consumer.cpp @@ -17,10 +17,9 @@ namespace uprotocol::client::usubscription::v3 { -Consumer::Consumer( - std::shared_ptr transport, - v1::UUri subscription_topic, - core::usubscription::v3::USubscriptionOptions consumer_options) +Consumer::Consumer(std::shared_ptr transport, + v1::UUri subscription_topic, + core::usubscription::v3::CallOptions consumer_options) : transport_(std::move(transport)), subscription_topic_(std::move(subscription_topic)), consumer_options_(std::move(consumer_options)), @@ -34,12 +33,11 @@ Consumer::Consumer( std::shared_ptr transport, const v1::UUri& subscription_topic, ListenCallback&& callback, v1::UPriority priority, std::chrono::milliseconds subscription_request_ttl, - core::usubscription::v3::USubscriptionOptions consumer_options) { + core::usubscription::v3::CallOptions consumer_options) { auto consumer = std::make_unique( std::forward>(transport), std::forward(subscription_topic), - std::forward( - consumer_options)); + std::forward(consumer_options)); // Attempt to connect create notification sink for updates. auto status = consumer->createNotificationSink(); diff --git a/src/client/usubscription/v3/RequestBuilder.cpp b/src/client/usubscription/v3/RequestBuilder.cpp index 052a568e8..555f125e3 100644 --- a/src/client/usubscription/v3/RequestBuilder.cpp +++ b/src/client/usubscription/v3/RequestBuilder.cpp @@ -16,7 +16,7 @@ namespace uprotocol::core::usubscription::v3 { SubscriptionRequest RequestBuilder::buildSubscriptionRequest( - const v1::UUri& topic, const USubscriptionOptions& options) { + const v1::UUri& topic, const CallOptions& options) { auto attributes = utils::ProtoConverter::BuildSubscribeAttributes( options.when_expire, options.subscription_details, options.sample_period_ms); diff --git a/src/client/usubscription/v3/RpcClientUSubscription.cpp b/src/client/usubscription/v3/RpcClientUSubscription.cpp index a72930223..6d9941717 100644 --- a/src/client/usubscription/v3/RpcClientUSubscription.cpp +++ b/src/client/usubscription/v3/RpcClientUSubscription.cpp @@ -21,8 +21,12 @@ auto priority = uprotocol::v1::UPriority::UPRIORITY_CS4; // MUST be >= 4 namespace uprotocol::core::usubscription::v3 { RpcClientUSubscription::RpcClientUSubscription( - std::shared_ptr transport) + std::shared_ptr transport, + const USubscriptionOptions& options) : transport_(std::move(transport)) { + uuri_builder_.setAuthorityName(options.authority_name) + .setInstanceId(options.instance_id); + subscribe_client_ = std::make_shared( transport_, uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_SUBSCRIBE), diff --git a/src/client/usubscription/v3/USubscriptionUUriBuilder.cpp b/src/client/usubscription/v3/USubscriptionUUriBuilder.cpp index 4c8bfdb4d..63cbd0725 100644 --- a/src/client/usubscription/v3/USubscriptionUUriBuilder.cpp +++ b/src/client/usubscription/v3/USubscriptionUUriBuilder.cpp @@ -13,6 +13,9 @@ namespace uprotocol::core::usubscription::v3 { +constexpr uint32_t SERVICE_ID_BITMASK = 0x0000FFFF; +constexpr uint32_t INSTANCE_ID_BITMASK = 0xFFFF0000; + USubscriptionUUriBuilder::USubscriptionUUriBuilder() { // Get the service descriptor const google::protobuf::ServiceDescriptor* service = @@ -20,8 +23,6 @@ USubscriptionUUriBuilder::USubscriptionUUriBuilder() { const auto& service_options = service->options(); // Get the service options - const auto& service_name = - service_options.GetExtension(uprotocol::service_name); const auto& service_version_major = service_options.GetExtension(uprotocol::service_version_major); const auto& service_id = @@ -30,12 +31,45 @@ USubscriptionUUriBuilder::USubscriptionUUriBuilder() { service_options.GetExtension(uprotocol::notification_topic, 0); // Set the values in the URI - base_uri_.set_authority_name(service_name); base_uri_.set_ue_id(service_id); base_uri_.set_ue_version_major(service_version_major); sink_resource_id_ = notification_topic.id(); } +USubscriptionUUriBuilder& USubscriptionUUriBuilder::setAuthorityName( + const std::string& authority_name) { + base_uri_.set_authority_name(authority_name); + return *this; +} + +USubscriptionUUriBuilder& USubscriptionUUriBuilder::setUEntityId( + u_int32_t ue_id) { + base_uri_.set_ue_id(ue_id); + return *this; +} + +constexpr uint16_t BITS_UINT_16 = 16; +USubscriptionUUriBuilder& USubscriptionUUriBuilder::setInstanceId( + u_int16_t instance_id) { + auto updated_ue_id = (SERVICE_ID_BITMASK & base_uri_.ue_id()) + + (static_cast(instance_id) << BITS_UINT_16); + base_uri_.set_ue_id(updated_ue_id); + return *this; +} + +USubscriptionUUriBuilder& USubscriptionUUriBuilder::setServiceId( + u_int16_t service_id) { + auto updated_ue_id = (INSTANCE_ID_BITMASK & base_uri_.ue_id()) + service_id; + base_uri_.set_ue_id(updated_ue_id); + return *this; +} + +USubscriptionUUriBuilder& USubscriptionUUriBuilder::setResourceId( + u_int32_t resource_id) { + base_uri_.set_resource_id(resource_id); + return *this; +} + v1::UUri USubscriptionUUriBuilder::getServiceUriWithResourceId( uint32_t resource_id) const { v1::UUri uri = base_uri_; // Copy the base URI diff --git a/test/coverage/client/usubscription/v3/ConsumerTest.cpp b/test/coverage/client/usubscription/v3/ConsumerTest.cpp index 647110d15..78e0ced79 100644 --- a/test/coverage/client/usubscription/v3/ConsumerTest.cpp +++ b/test/coverage/client/usubscription/v3/ConsumerTest.cpp @@ -107,7 +107,7 @@ TEST_F(ConsumerTest, ConstructorTestSuccess) { // NOLINT auto subscribe_request_ttl = std::chrono::milliseconds(REQUEST_TTL_TIME); auto priority = uprotocol::v1::UPriority::UPRIORITY_CS4; - auto options = uprotocol::core::usubscription::v3::USubscriptionOptions(); + auto options = uprotocol::core::usubscription::v3::CallOptions(); auto consumer_or_status = uprotocol::client::usubscription::v3::Consumer::create( @@ -132,7 +132,7 @@ TEST_F(ConsumerTest, SubscribeTestSuccess) { // NOLINT auto subscribe_request_ttl = std::chrono::milliseconds(REQUEST_TTL_TIME); auto priority = uprotocol::v1::UPriority::UPRIORITY_CS4; - auto options = uprotocol::core::usubscription::v3::USubscriptionOptions(); + auto options = uprotocol::core::usubscription::v3::CallOptions(); auto consumer_or_status = uprotocol::client::usubscription::v3::Consumer::create( @@ -178,7 +178,7 @@ TEST_F(ConsumerTest, UnsubscribeTestSuccess) { // NOLINT auto subscribe_request_ttl = std::chrono::milliseconds(REQUEST_TTL_TIME); auto priority = uprotocol::v1::UPriority::UPRIORITY_CS4; - auto options = uprotocol::core::usubscription::v3::USubscriptionOptions(); + auto options = uprotocol::core::usubscription::v3::CallOptions(); auto consumer_or_status = uprotocol::client::usubscription::v3::Consumer::create( diff --git a/test/coverage/client/usubscription/v3/RequestBuilderTest.cpp b/test/coverage/client/usubscription/v3/RequestBuilderTest.cpp index ad2c8c602..a0faaa4d4 100644 --- a/test/coverage/client/usubscription/v3/RequestBuilderTest.cpp +++ b/test/coverage/client/usubscription/v3/RequestBuilderTest.cpp @@ -27,11 +27,11 @@ namespace uprotocol::core::usubscription::v3 { class RequestBuilderTest : public ::testing::Test { private: v1::UUri source_; - USubscriptionOptions options_; + CallOptions options_; protected: const v1::UUri& getSource() const { return source_; } - const USubscriptionOptions& getOptions() const { return options_; } + const CallOptions& getOptions() const { return options_; } void SetUp() override { // Create a UUri object for testing diff --git a/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp b/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp index 3bf1baf77..e404dfdbe 100644 --- a/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp +++ b/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp @@ -97,11 +97,20 @@ class RpcClientUSubscriptionTest : public testing::Test { uprotocol::v1::UUri getSubscriptionTopic() { return subscription_topic_; } + uprotocol::core::usubscription::v3::USubscriptionOptions + getUSubscriptionOptions() { + return options_; + } + private: std::shared_ptr client_transport_; std::shared_ptr server_transport_; uprotocol::v1::UUri server_method_uuri_; uprotocol::v1::UUri subscription_topic_; + uprotocol::core::usubscription::v3::USubscriptionOptions options_ = { + "core.usubscription", + 0x0000, + }; public: ~RpcClientUSubscriptionTest() override = default; @@ -139,7 +148,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const auto subscription_request = RequestBuilder::buildSubscriptionRequest(getSubscriptionTopic()); @@ -202,7 +211,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const auto subscription_request = RequestBuilder::buildSubscriptionRequest(getSubscriptionTopic()); @@ -268,7 +277,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const auto unsubscribe_request = RequestBuilder::buildUnsubscribeRequest(getSubscriptionTopic()); @@ -329,7 +338,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const auto unsubscribe_request = RequestBuilder::buildUnsubscribeRequest(getSubscriptionTopic()); @@ -396,7 +405,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const auto fetch_subscribers_request = RequestBuilder::buildFetchSubscribersRequest(getSubscriptionTopic()); @@ -459,7 +468,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const auto fetch_subscribers_request = RequestBuilder::buildFetchSubscribersRequest(getSubscriptionTopic()); @@ -527,7 +536,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const uprotocol::core::usubscription::v3::SubscriberInfo subscriber_info; const auto fetch_subscriptions_request = @@ -591,7 +600,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const uprotocol::core::usubscription::v3::SubscriberInfo subscriber_info; const auto fetch_subscribers_request = @@ -660,7 +669,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const auto notifications_request = RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); @@ -723,7 +732,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const auto notifications_request = RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); @@ -791,7 +800,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const auto notifications_request = RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); @@ -857,7 +866,7 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT EXPECT_TRUE(getServerTransport()->getListener()); auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( - getClientTransport()); + getClientTransport(), getUSubscriptionOptions()); const auto notifications_request = RequestBuilder::buildNotificationsRequest(getSubscriptionTopic()); diff --git a/test/coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp b/test/coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp index a2b0ad7ff..2cd17be40 100644 --- a/test/coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp +++ b/test/coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp @@ -16,6 +16,8 @@ constexpr uint16_t RESOURCE_ID_TEST = 0x0001; constexpr uint16_t RESOURCE_ID_NOTIFICATION_ID = 0x8000; +// test ue id with instance id 1 and service id 2 +constexpr uint32_t TEST_UE_ID = 0x00010002; namespace uprotocol::core::usubscription::v3 { class USubscriptionUUriBuilderTest : public ::testing::Test { @@ -27,7 +29,7 @@ class USubscriptionUUriBuilderTest : public ::testing::Test { void SetUp() override { expected_uri_.set_authority_name("core.usubscription"); - expected_uri_.set_ue_id(0); + expected_uri_.set_ue_id(TEST_UE_ID); expected_uri_.set_ue_version_major(3); } @@ -38,7 +40,10 @@ TEST_F(USubscriptionUUriBuilderTest, GetServiceUriWithResourceId) { // NOLINT // Example test case for building a subscription UUri auto expected_uri = getExpectedUri(); expected_uri.set_resource_id(RESOURCE_ID_TEST); - const USubscriptionUUriBuilder builder; + USubscriptionUUriBuilder builder; + builder.setAuthorityName("core.usubscription") + .setInstanceId(1) + .setServiceId(2); const v1::UUri actual_uri = builder.getServiceUriWithResourceId(RESOURCE_ID_TEST); @@ -51,6 +56,9 @@ TEST_F(USubscriptionUUriBuilderTest, GetNotificationUri) { // NOLINT auto expected_uri = getExpectedUri(); expected_uri.set_resource_id(RESOURCE_ID_NOTIFICATION_ID); USubscriptionUUriBuilder builder; + builder.setAuthorityName("core.usubscription") + .setInstanceId(1) + .setServiceId(2); v1::UUri actual_uri = builder.getNotificationUri(); EXPECT_TRUE(actual_uri.IsInitialized()); EXPECT_EQ(actual_uri.GetTypeName(), "uprotocol.v1.UUri"); From 17ef0cf564dafbea9b3447b03414a82959e0389f Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 8 Aug 2025 17:21:35 +0200 Subject: [PATCH 5/8] rebase and use method in subscription calls --- .../usubscription/v3/RpcClientUSubscription.h | 14 +--- include/up-cpp/communication/RpcClient.h | 18 +++-- .../v3/RpcClientUSubscription.cpp | 77 +++++++------------ .../v3/RpcClientUSubscriptionTest.cpp | 2 + 4 files changed, 40 insertions(+), 71 deletions(-) diff --git a/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h b/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h index 59da5c70a..e6e9f4ef5 100644 --- a/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h +++ b/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h @@ -125,19 +125,7 @@ struct RpcClientUSubscription : USubscription { private: std::shared_ptr transport_; - // Currently a single RpcClient can only send messages to a fixed UUri. - // This forces us to use different RpcClients for different resource ids. - // The alternative would be to create a new RpcClient for each request and - // return a pointer to the client together with the InvokeProtoFuture to - // keep it alive - std::shared_ptr subscribe_client_; - std::shared_ptr unsubscribe_client_; - std::shared_ptr fetch_subscriptions_client_; - std::shared_ptr fetch_subscribers_client_; - std::shared_ptr register_for_notification_client_; - std::shared_ptr - unregister_for_notification_client_; - + std::shared_ptr rpc_client_; USubscriptionUUriBuilder uuri_builder_; }; diff --git a/include/up-cpp/communication/RpcClient.h b/include/up-cpp/communication/RpcClient.h index 3151f6c68..40d75c3c7 100644 --- a/include/up-cpp/communication/RpcClient.h +++ b/include/up-cpp/communication/RpcClient.h @@ -181,7 +181,8 @@ struct RpcClient { [[nodiscard]] InvokeFuture invokeMethod(const v1::UUri&); template - InvokeHandle invokeProtoMethod(const R& request_message, + InvokeHandle invokeProtoMethod(const v1::UUri& method, + const R& request_message, Callback&& callback) { auto payload_or_status = uprotocol::utils::ProtoConverter::protoToPayload(request_message); @@ -191,19 +192,21 @@ struct RpcClient { } datamodel::builder::Payload tmp_payload(payload_or_status.value()); - auto handle = invokeMethod(builder_.build(std::move(tmp_payload)), - std::move(callback)); + auto handle = invokeMethod( + builder_.withMethod(method).build(std::move(tmp_payload)), + std::move(callback)); return handle; } template - InvokeProtoFuture invokeProtoMethod(const v1::UUri& method, const R& request_message) { + InvokeProtoFuture invokeProtoMethod(const v1::UUri& method, + const R& request_message) { auto result_promise = std::make_shared>>(); auto future = result_promise->get_future(); auto handle = invokeProtoMethod( - request_message, + method, request_message, [result_promise](const MessageOrStatus& message_or_status) { if (!message_or_status.has_value()) { result_promise->set_value(ResponseOrStatus( @@ -231,13 +234,14 @@ struct RpcClient { } template - InvokeFuture invokeProtoMethod(const R& request_message) { + InvokeFuture invokeProtoMethod(const v1::UUri& method, + const R& request_message) { auto result_promise = std::make_shared>>(); auto future = result_promise->get_future(); auto handle = invokeProtoMethod( - request_message, + method, request_message, [result_promise](const MessageOrStatus& message_or_status) { if (!message_or_status.has_value()) { result_promise->set_value(ResponseOrStatus( diff --git a/src/client/usubscription/v3/RpcClientUSubscription.cpp b/src/client/usubscription/v3/RpcClientUSubscription.cpp index 6d9941717..04a153fe7 100644 --- a/src/client/usubscription/v3/RpcClientUSubscription.cpp +++ b/src/client/usubscription/v3/RpcClientUSubscription.cpp @@ -27,87 +27,62 @@ RpcClientUSubscription::RpcClientUSubscription( uuri_builder_.setAuthorityName(options.authority_name) .setInstanceId(options.instance_id); - subscribe_client_ = std::make_shared( - transport_, - uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_SUBSCRIBE), - priority, USUBSCRIPTION_REQUEST_TTL); - - unsubscribe_client_ = std::make_shared( - transport_, - uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_UNSUBSCRIBE), - priority, USUBSCRIPTION_REQUEST_TTL); - - fetch_subscriptions_client_ = std::make_shared( - transport_, - uuri_builder_.getServiceUriWithResourceId( - RESOURCE_ID_FETCH_SUBSCRIPTIONS), - priority, USUBSCRIPTION_REQUEST_TTL); - - fetch_subscribers_client_ = std::make_shared( - transport_, - uuri_builder_.getServiceUriWithResourceId( - RESOURCE_ID_FETCH_SUBSCRIBERS), - priority, USUBSCRIPTION_REQUEST_TTL); - - register_for_notification_client_ = - std::make_shared( - transport_, - uuri_builder_.getServiceUriWithResourceId( - RESOURCE_ID_REGISTER_FOR_NOTIFICATIONS), - priority, USUBSCRIPTION_REQUEST_TTL); - - unregister_for_notification_client_ = - std::make_shared( - transport_, - uuri_builder_.getServiceUriWithResourceId( - RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS), - priority, USUBSCRIPTION_REQUEST_TTL); + rpc_client_ = std::make_shared( + transport_, priority, USUBSCRIPTION_REQUEST_TTL); } communication::RpcClient::InvokeProtoFuture RpcClientUSubscription::subscribe( const SubscriptionRequest& subscription_request) { - return subscribe_client_->invokeProtoMethod( - subscription_request); + auto method = + uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_SUBSCRIBE); + return rpc_client_->invokeProtoMethod( + method, subscription_request); } communication::RpcClient::InvokeProtoFuture RpcClientUSubscription::unsubscribe( const UnsubscribeRequest& unsubscribe_request) { - return unsubscribe_client_->invokeProtoMethod( - unsubscribe_request); + auto method = + uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_UNSUBSCRIBE); + return rpc_client_->invokeProtoMethod( + method, unsubscribe_request); } communication::RpcClient::InvokeProtoFuture RpcClientUSubscription::fetch_subscriptions( const FetchSubscriptionsRequest& fetch_subscriptions_request) { - return fetch_subscriptions_client_ - ->invokeProtoMethod( - fetch_subscriptions_request); + auto method = uuri_builder_.getServiceUriWithResourceId( + RESOURCE_ID_FETCH_SUBSCRIPTIONS); + return rpc_client_->invokeProtoMethod( + method, fetch_subscriptions_request); } communication::RpcClient::InvokeProtoFuture RpcClientUSubscription::fetch_subscribers( const FetchSubscribersRequest& fetch_subscribers_request) { - return fetch_subscribers_client_ - ->invokeProtoMethod( - fetch_subscribers_request); + auto method = uuri_builder_.getServiceUriWithResourceId( + RESOURCE_ID_FETCH_SUBSCRIBERS); + return rpc_client_->invokeProtoMethod( + method, fetch_subscribers_request); } communication::RpcClient::InvokeProtoFuture RpcClientUSubscription::register_for_notifications( const NotificationsRequest& register_notifications_request) { - return register_for_notification_client_ - ->invokeProtoMethod( - register_notifications_request); + auto method = uuri_builder_.getServiceUriWithResourceId( + RESOURCE_ID_REGISTER_FOR_NOTIFICATIONS); + return rpc_client_->invokeProtoMethod( + method, register_notifications_request); } communication::RpcClient::InvokeProtoFuture RpcClientUSubscription::unregister_for_notifications( const NotificationsRequest& unregister_notifications_request) { - return unregister_for_notification_client_ - ->invokeProtoMethod( - unregister_notifications_request); + auto method = uuri_builder_.getServiceUriWithResourceId( + RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS); + return rpc_client_->invokeProtoMethod( + method, unregister_notifications_request); } } // namespace uprotocol::core::usubscription::v3 diff --git a/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp b/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp index e404dfdbe..226280a10 100644 --- a/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp +++ b/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp @@ -865,6 +865,8 @@ TEST_F(RpcClientUSubscriptionTest, // NOLINT ASSERT_NE(server_or_status.value(), nullptr); EXPECT_TRUE(getServerTransport()->getListener()); + auto test = getUSubscriptionOptions().authority_name; + std::cout << "the authority name is " << test << std::endl; auto client = uprotocol::core::usubscription::v3::RpcClientUSubscription( getClientTransport(), getUSubscriptionOptions()); From f665f7dbc84b2585f6229a641114255979d9a4b2 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 11 Aug 2025 20:20:46 +0200 Subject: [PATCH 6/8] renamed invokeProtoMethod --- include/up-cpp/communication/RpcClient.h | 18 +++++++++--------- .../v3/RpcClientUSubscription.cpp | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/up-cpp/communication/RpcClient.h b/include/up-cpp/communication/RpcClient.h index 40d75c3c7..b800ae1c0 100644 --- a/include/up-cpp/communication/RpcClient.h +++ b/include/up-cpp/communication/RpcClient.h @@ -181,9 +181,9 @@ struct RpcClient { [[nodiscard]] InvokeFuture invokeMethod(const v1::UUri&); template - InvokeHandle invokeProtoMethod(const v1::UUri& method, - const R& request_message, - Callback&& callback) { + InvokeHandle invokeMethodToProto(const v1::UUri& method, + const R& request_message, + Callback&& callback) { auto payload_or_status = uprotocol::utils::ProtoConverter::protoToPayload(request_message); @@ -200,12 +200,12 @@ struct RpcClient { } template - InvokeProtoFuture invokeProtoMethod(const v1::UUri& method, - const R& request_message) { + InvokeProtoFuture invokeMethodToProto(const v1::UUri& method, + const R& request_message) { auto result_promise = std::make_shared>>(); auto future = result_promise->get_future(); - auto handle = invokeProtoMethod( + auto handle = invokeMethodToProto( method, request_message, [result_promise](const MessageOrStatus& message_or_status) { if (!message_or_status.has_value()) { @@ -234,13 +234,13 @@ struct RpcClient { } template - InvokeFuture invokeProtoMethod(const v1::UUri& method, - const R& request_message) { + InvokeFuture invokeMethodToProto(const v1::UUri& method, + const R& request_message) { auto result_promise = std::make_shared>>(); auto future = result_promise->get_future(); - auto handle = invokeProtoMethod( + auto handle = invokeMethodToProto( method, request_message, [result_promise](const MessageOrStatus& message_or_status) { if (!message_or_status.has_value()) { diff --git a/src/client/usubscription/v3/RpcClientUSubscription.cpp b/src/client/usubscription/v3/RpcClientUSubscription.cpp index 04a153fe7..d882b8e8b 100644 --- a/src/client/usubscription/v3/RpcClientUSubscription.cpp +++ b/src/client/usubscription/v3/RpcClientUSubscription.cpp @@ -36,7 +36,7 @@ RpcClientUSubscription::subscribe( const SubscriptionRequest& subscription_request) { auto method = uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_SUBSCRIBE); - return rpc_client_->invokeProtoMethod( + return rpc_client_->invokeMethodToProto( method, subscription_request); } @@ -45,7 +45,7 @@ RpcClientUSubscription::unsubscribe( const UnsubscribeRequest& unsubscribe_request) { auto method = uuri_builder_.getServiceUriWithResourceId(RESOURCE_ID_UNSUBSCRIBE); - return rpc_client_->invokeProtoMethod( + return rpc_client_->invokeMethodToProto( method, unsubscribe_request); } @@ -54,7 +54,7 @@ RpcClientUSubscription::fetch_subscriptions( const FetchSubscriptionsRequest& fetch_subscriptions_request) { auto method = uuri_builder_.getServiceUriWithResourceId( RESOURCE_ID_FETCH_SUBSCRIPTIONS); - return rpc_client_->invokeProtoMethod( + return rpc_client_->invokeMethodToProto( method, fetch_subscriptions_request); } @@ -63,7 +63,7 @@ RpcClientUSubscription::fetch_subscribers( const FetchSubscribersRequest& fetch_subscribers_request) { auto method = uuri_builder_.getServiceUriWithResourceId( RESOURCE_ID_FETCH_SUBSCRIBERS); - return rpc_client_->invokeProtoMethod( + return rpc_client_->invokeMethodToProto( method, fetch_subscribers_request); } @@ -72,7 +72,7 @@ RpcClientUSubscription::register_for_notifications( const NotificationsRequest& register_notifications_request) { auto method = uuri_builder_.getServiceUriWithResourceId( RESOURCE_ID_REGISTER_FOR_NOTIFICATIONS); - return rpc_client_->invokeProtoMethod( + return rpc_client_->invokeMethodToProto( method, register_notifications_request); } @@ -81,7 +81,7 @@ RpcClientUSubscription::unregister_for_notifications( const NotificationsRequest& unregister_notifications_request) { auto method = uuri_builder_.getServiceUriWithResourceId( RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS); - return rpc_client_->invokeProtoMethod( + return rpc_client_->invokeMethodToProto( method, unregister_notifications_request); } From ce562790d8e2d7cb29145b32c0bf5198d0dd9a57 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 24 Aug 2025 18:29:41 +0200 Subject: [PATCH 7/8] added local copies of builder in invokeMethod for thread safety --- src/communication/RpcClient.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/communication/RpcClient.cpp b/src/communication/RpcClient.cpp index 63411d05d..a7e8cece5 100644 --- a/src/communication/RpcClient.cpp +++ b/src/communication/RpcClient.cpp @@ -208,13 +208,18 @@ RpcClient::InvokeHandle RpcClient::invokeMethod(v1::UMessage&& request, RpcClient::InvokeHandle RpcClient::invokeMethod( const v1::UUri& method, datamodel::builder::Payload&& payload, Callback&& callback) { - return invokeMethod(builder_.withMethod(method).build(std::move(payload)), - std::move(callback)); + // makes a local copy of the builder to make this call thread safe + auto local_builder = builder_; + return invokeMethod( + local_builder.withMethod(method).build(std::move(payload)), + std::move(callback)); } RpcClient::InvokeHandle RpcClient::invokeMethod(const v1::UUri& method, Callback&& callback) { - return invokeMethod(builder_.withMethod(method).build(), + // makes a local copy of the builder to make this call thread safe + auto local_builder = builder_; + return invokeMethod(local_builder.withMethod(method).build(), std::move(callback)); } From f899ee864513b9d0ee2795b5ab32a2d2e76f8af3 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 24 Aug 2025 19:16:16 +0200 Subject: [PATCH 8/8] new overloads for build method of UMessageBuilder to avoid copy --- include/up-cpp/datamodel/builder/UMessage.h | 25 +++++++++++++++ src/communication/RpcClient.cpp | 12 ++----- src/datamodel/builder/UMessage.cpp | 35 +++++++++++++++++++++ 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/include/up-cpp/datamodel/builder/UMessage.h b/include/up-cpp/datamodel/builder/UMessage.h index 34863b2e3..408fd1744 100644 --- a/include/up-cpp/datamodel/builder/UMessage.h +++ b/include/up-cpp/datamodel/builder/UMessage.h @@ -234,6 +234,16 @@ struct UMessageBuilder { /// @return A built message with no payload populated. [[nodiscard]] v1::UMessage build() const; + /// @brief Creates a UMessage based on the builder's current state. + /// + /// @param A UUri of the method that should be called + /// + /// @throws UnexpectedFormat if withPayloadFormat() has been previously + /// called. + /// + /// @return A built message with no payload populated. + [[nodiscard]] v1::UMessage build(const v1::UUri&) const; + /// @brief Creates a UMessage with a provided payload based on the /// builder's current state. /// @@ -247,6 +257,21 @@ struct UMessageBuilder { /// @return A built message with the provided payload data embedded. [[nodiscard]] v1::UMessage build(builder::Payload&&) const; + /// @brief Creates a UMessage with a provided payload based on the + /// builder's current state. + /// + /// @param A UUri of the method that should be called + /// + /// @param A Payload builder containing a payload to embed in the message. + /// + /// @note The contents of the payload builder will be moved. + /// + /// @throws UnexpectedFormat if withPayloadFormat() has been previously + /// called and the format in the payload builder does not match. + /// + /// @return A built message with the provided payload data embedded. + [[nodiscard]] v1::UMessage build(const v1::UUri&, builder::Payload&&) const; + /// @brief Access the attributes of the message being built. /// @return A reference to the attributes of the message being built. [[deprecated( diff --git a/src/communication/RpcClient.cpp b/src/communication/RpcClient.cpp index a7e8cece5..f4dc0c148 100644 --- a/src/communication/RpcClient.cpp +++ b/src/communication/RpcClient.cpp @@ -208,19 +208,13 @@ RpcClient::InvokeHandle RpcClient::invokeMethod(v1::UMessage&& request, RpcClient::InvokeHandle RpcClient::invokeMethod( const v1::UUri& method, datamodel::builder::Payload&& payload, Callback&& callback) { - // makes a local copy of the builder to make this call thread safe - auto local_builder = builder_; - return invokeMethod( - local_builder.withMethod(method).build(std::move(payload)), - std::move(callback)); + return invokeMethod(builder_.build(method, std::move(payload)), + std::move(callback)); } RpcClient::InvokeHandle RpcClient::invokeMethod(const v1::UUri& method, Callback&& callback) { - // makes a local copy of the builder to make this call thread safe - auto local_builder = builder_; - return invokeMethod(local_builder.withMethod(method).build(), - std::move(callback)); + return invokeMethod(builder_.build(method), std::move(callback)); } RpcClient::InvokeFuture RpcClient::invokeMethod( diff --git a/src/datamodel/builder/UMessage.cpp b/src/datamodel/builder/UMessage.cpp index c3408ecf7..a27612680 100644 --- a/src/datamodel/builder/UMessage.cpp +++ b/src/datamodel/builder/UMessage.cpp @@ -218,6 +218,21 @@ v1::UMessage UMessageBuilder::build() const { return message; } +v1::UMessage UMessageBuilder::build(const v1::UUri& method) const { + v1::UMessage message; + if (expectedPayloadFormat_.has_value()) { + throw UnexpectedFormat( + "Tried to build with no payload when a payload format has been set " + "using withPayloadFormat()"); + } + + *message.mutable_attributes() = attributes_; + *message.mutable_attributes()->mutable_sink() = method; + *(message.mutable_attributes()->mutable_id()) = uuidBuilder_.build(); + + return message; +} + v1::UMessage UMessageBuilder::build(builder::Payload&& payload) const { v1::UMessage message; @@ -236,6 +251,26 @@ v1::UMessage UMessageBuilder::build(builder::Payload&& payload) const { return message; } +v1::UMessage UMessageBuilder::build(const v1::UUri& method, + builder::Payload&& payload) const { + v1::UMessage message; + + *message.mutable_attributes() = attributes_; + *message.mutable_attributes()->mutable_sink() = method; + *(message.mutable_attributes()->mutable_id()) = uuidBuilder_.build(); + auto [payloadData, payloadFormat] = std::move(payload).buildMove(); + if (expectedPayloadFormat_.has_value()) { + if (payloadFormat != expectedPayloadFormat_) { + throw UnexpectedFormat( + "Payload format does not match the expected format"); + } + } + *message.mutable_payload() = std::move(payloadData); + message.mutable_attributes()->set_payload_format(payloadFormat); + + return message; +} + UMessageBuilder::UMessageBuilder(v1::UMessageType msg_type, v1::UUri&& source, std::optional&& sink, std::optional&& request_id)