diff --git a/CMakeLists.txt b/CMakeLists.txt index 256391aae..051776efa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,11 @@ endif() file(GLOB_RECURSE SRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") -add_library(${PROJECT_NAME} ${SRC_FILES}) +add_library(${PROJECT_NAME} ${SRC_FILES} + src/client/usubscription/v3/RpcClientUSubscription.cpp + include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h + include/up-cpp/client/usubscription/v3/USubscription.h +) add_library(up-cpp::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) target_include_directories(${PROJECT_NAME} 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..e1c7d099d --- /dev/null +++ b/include/up-cpp/client/usubscription/v3/RequestBuilder.h @@ -0,0 +1,101 @@ +// 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 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 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 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 An `NotificationsRequest` configured for the specified topic. + static NotificationsRequest buildNotificationsRequest( + const v1::UUri& topic); +}; + +} // 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..6f7663d55 --- /dev/null +++ b/include/up-cpp/client/usubscription/v3/RpcClientUSubscription.h @@ -0,0 +1,126 @@ +// 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 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. +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; + +constexpr auto USUBSCRIPTION_REQUEST_TTL = std::chrono::milliseconds(5000); + +namespace uprotocol::core::usubscription::v3 { +using v3::SubscriptionRequest; +using v3::UnsubscribeRequest; + +/// @brief Client which implements the USubscription interface +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 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 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 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_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 + register_for_notifications( + const NotificationsRequest& register_notifications_request) override; + + /// @brief Unregisters from receiving notifications. + /// + /// @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 used to send messages + explicit RpcClientUSubscription( + std::shared_ptr transport) + : transport_(std::move(transport)) {} + + ~RpcClientUSubscription() override = default; + +private: + std::shared_ptr transport_; + + 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..8d0e4a092 --- /dev/null +++ b/include/up-cpp/client/usubscription/v3/USubscription.h @@ -0,0 +1,81 @@ +// 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 { + +/// @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; + + /// @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; + + /// @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; + + /// @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; + + /// @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 + +#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 c78ab5f94..ef218e04e 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. /// @@ -162,6 +168,39 @@ struct RpcClient { /// * A UMessage containing the response from the RPC target. [[nodiscard]] InvokeFuture invokeMethod(); + template + ResponseOrStatus invokeProtoMethod(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 tmp_payload(payload_or_status.value()); + + auto message_or_status = + this->invokeMethod(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()); + } + /// @brief Default move constructor (defined in RpcClient.cpp) RpcClient(RpcClient&&) noexcept; diff --git a/include/up-cpp/datamodel/validator/UUri.h b/include/up-cpp/datamodel/validator/UUri.h index 8989a5cf3..958d9a61c 100644 --- a/include/up-cpp/datamodel/validator/UUri.h +++ b/include/up-cpp/datamodel/validator/UUri.h @@ -150,10 +150,35 @@ isValidDefaultSource(const v1::UUri&); /// This is just a check for a zero-length authority name string. [[nodiscard]] bool isLocal(const v1::UUri&); -/// @brief Checks if a UUri uses wildcards +/// @brief Checks if a UUri has a wildcard authority name. /// -/// Checks for all types of wildcards, returns true if any are found. -[[nodiscard]] bool uses_wildcards(const v1::UUri&); +/// Checks if a UUri has a wildcard authority name, returns true if yes. +[[nodiscard]] bool has_wildcard_authority(const v1::UUri& uuri); + +/// @brief Checks if a UUri has a wildcard service id. +/// +/// Checks if a UUri has a wildcard service id, returns true if yes. +[[nodiscard]] bool has_wildcard_service_id(const v1::UUri& uuri); + +/// @brief Checks if a UUri has a wildcard service instance id. +/// +/// Checks if a UUri has a wildcard service instance id, returns true if yes. +[[nodiscard]] bool has_wildcard_service_instance_id(const v1::UUri& uuri); + +/// @brief Checks if a UUri has a wildcard version. +/// +/// Checks if a UUri has a wildcard version, returns true if yes. +[[nodiscard]] bool has_wildcard_version(const v1::UUri& uuri); + +/// @brief Checks if a UUri has a wildcard resoruce id. +/// +/// Checks if a UUri has a wildcard resoruce id, returns true if yes. +[[nodiscard]] bool has_wildcard_resource_id(const v1::UUri& uuri); + +/// @brief Checks if a UUri uses no wildcards +/// +/// Checks for all types of wildcards, returns true if no wildcards are found. +[[nodiscard]] bool verify_no_wildcards(const v1::UUri&); /// @brief This exception indicates that a UUri object was provided that /// did not contain valid UUri data. diff --git a/include/up-cpp/utils/ProtoConverter.h b/include/up-cpp/utils/ProtoConverter.h index ee4123936..8f5c3deaa 100644 --- a/include/up-cpp/utils/ProtoConverter.h +++ b/include/up-cpp/utils/ProtoConverter.h @@ -3,11 +3,24 @@ #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 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; @@ -53,6 +66,125 @@ struct ProtoConverter { /// @return the built UnsubscribeRequest static UnsubscribeRequest BuildUnSubscribeRequest( const v1::UUri& subscription_topic); + + /// @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."); + 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_: { + 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."); + return TOrStatus(UnexpectedStatus(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; + + 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 0217b63ea..18a6d1c65 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..ea383d697 --- /dev/null +++ b/src/client/usubscription/v3/RequestBuilder.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/RequestBuilder.h" + +#include + +namespace uprotocol::core::usubscription::v3 { + +SubscriptionRequest RequestBuilder::buildSubscriptionRequest( + const v1::UUri& topic, const USubscriptionOptions& options) { + 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) { + return utils::ProtoConverter::BuildUnSubscribeRequest(topic); +} + +FetchSubscriptionsRequest RequestBuilder::buildFetchSubscriptionsRequest( + const v1::UUri& topic) { + return utils::ProtoConverter::BuildFetchSubscriptionsRequest(topic); +} + +FetchSubscriptionsRequest RequestBuilder::buildFetchSubscriptionsRequest( + const SubscriberInfo& subscriber) { + return utils::ProtoConverter::BuildFetchSubscriptionsRequest(subscriber); +} + +FetchSubscribersRequest RequestBuilder::buildFetchSubscribersRequest( + const v1::UUri& topic) { + return utils::ProtoConverter::BuildFetchSubscribersRequest(topic); +} + +NotificationsRequest RequestBuilder::buildNotificationsRequest( + const v1::UUri& topic) { + 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 new file mode 100644 index 000000000..5f459de31 --- /dev/null +++ b/src/client/usubscription/v3/RpcClientUSubscription.cpp @@ -0,0 +1,125 @@ +// 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 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( + 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( + 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( + 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/datamodel/validator/UUri.cpp b/src/datamodel/validator/UUri.cpp index f623b0f66..0a9e1e117 100644 --- a/src/datamodel/validator/UUri.cpp +++ b/src/datamodel/validator/UUri.cpp @@ -72,29 +72,34 @@ std::string_view message(Reason reason) { } } -bool uses_wildcards(const v1::UUri& uuri) { - constexpr auto LOWER_8_BIT_MASK = 0xFF; +bool has_wildcard_authority(const v1::UUri& uuri) { + return uuri.authority_name() == "*"; +} + +bool has_wildcard_service_id(const v1::UUri& uuri) { constexpr auto LOWER_16_BIT_MASK = 0xFFFF; + return (uuri.ue_id() & LOWER_16_BIT_MASK) == LOWER_16_BIT_MASK; +} + +bool has_wildcard_service_instance_id(const v1::UUri& uuri) { constexpr auto UPPER_16_BIT_MASK = 0xFFFF0000; + return (uuri.ue_id() & UPPER_16_BIT_MASK) == UPPER_16_BIT_MASK; +} - if (uuri.authority_name().find_first_of('*') != std::string::npos) { - return true; - } - if ((uuri.ue_id() & LOWER_16_BIT_MASK) == - LOWER_16_BIT_MASK) { // service ID - return true; - } - if ((uuri.ue_id() & UPPER_16_BIT_MASK) == - UPPER_16_BIT_MASK) { // service instance ID - return true; - } - if (uuri.ue_version_major() == LOWER_8_BIT_MASK) { - return true; - } - if (uuri.resource_id() == LOWER_16_BIT_MASK) { - return true; - } - return false; +bool has_wildcard_version(const v1::UUri& uuri) { + constexpr auto LOWER_8_BIT_MASK = 0xFF; + return uuri.ue_version_major() == LOWER_8_BIT_MASK; +} + +bool has_wildcard_resource_id(const v1::UUri& uuri) { + constexpr auto LOWER_16_BIT_MASK = 0xFFFF; + return uuri.resource_id() == LOWER_16_BIT_MASK; +} + +bool verify_no_wildcards(const v1::UUri& uuri) { + return !has_wildcard_authority(uuri) && !has_wildcard_service_id(uuri) && + !has_wildcard_service_instance_id(uuri) && + !has_wildcard_version(uuri) && !has_wildcard_resource_id(uuri); } ValidationResult isValid(const v1::UUri& uuri) { @@ -145,7 +150,7 @@ ValidationResult isValidFilter(const v1::UUri& uuri) { ValidationResult isValidRpcMethod(const v1::UUri& uuri) { // disallow wildcards - if (uses_wildcards(uuri)) { + if (!verify_no_wildcards(uuri)) { return {false, Reason::DISALLOWED_WILDCARD}; } @@ -159,7 +164,7 @@ ValidationResult isValidRpcMethod(const v1::UUri& uuri) { ValidationResult isValidRpcResponse(const v1::UUri& uuri) { // disallow wildcards - if (uses_wildcards(uuri)) { + if (!verify_no_wildcards(uuri)) { return {false, Reason::DISALLOWED_WILDCARD}; } @@ -183,7 +188,7 @@ ValidationResult isValidDefaultSource(const v1::UUri& uuri) { ValidationResult isValidPublishTopic(const v1::UUri& uuri) { // disallow wildcards - if (uses_wildcards(uuri)) { + if (!verify_no_wildcards(uuri)) { return {false, Reason::DISALLOWED_WILDCARD}; } @@ -197,7 +202,7 @@ ValidationResult isValidPublishTopic(const v1::UUri& uuri) { ValidationResult isValidNotificationSource(const v1::UUri& uuri) { // disallow wildcards - if (uses_wildcards(uuri)) { + if (!verify_no_wildcards(uuri)) { return {false, Reason::DISALLOWED_WILDCARD}; } @@ -211,7 +216,7 @@ ValidationResult isValidNotificationSource(const v1::UUri& uuri) { ValidationResult isValidNotificationSink(const v1::UUri& uuri) { // disallow wildcards - if (uses_wildcards(uuri)) { + if (!verify_no_wildcards(uuri)) { return {false, Reason::DISALLOWED_WILDCARD}; } diff --git a/src/utils/ProtoConverter.cpp b/src/utils/ProtoConverter.cpp index c4159ee9f..b6ee5403a 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) { @@ -76,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 bd7e6d67f..e9059a45a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -83,6 +83,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 ########################## EXTRAS ############################################# add_extra_test("PublisherSubscriberTest" extra/PublisherSubscriberTest.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/RequestBuilderTest.cpp b/test/coverage/client/usubscription/v3/RequestBuilderTest.cpp new file mode 100644 index 000000000..a255355fd --- /dev/null +++ b/test/coverage/client/usubscription/v3/RequestBuilderTest.cpp @@ -0,0 +1,154 @@ +// 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 "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 new file mode 100644 index 000000000..1e7b1adc6 --- /dev/null +++ b/test/coverage/client/usubscription/v3/RpcClientUSubscriptionTest.cpp @@ -0,0 +1,1016 @@ +#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 { + +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.s + // Used to set up clean environments per test. + void SetUp() override { + 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); + + client_transport_ = + std::make_shared(client_uuri); + + uprotocol::v1::UUri server_uuri; + server_uuri.set_authority_name("core.usubscription"); + server_uuri.set_ue_id(1); + server_uuri.set_ue_version_major(UE_VERSION_MAJOR); + server_uuri.set_resource_id(0); + + server_transport_ = + std::make_shared(server_uuri); + + 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; + + // 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; +}; + +// +// 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()); + + 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 + 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 diff --git a/test/coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp b/test/coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp new file mode 100644 index 000000000..3958861d8 --- /dev/null +++ b/test/coverage/client/usubscription/v3/USubscriptionUUriBuilderTest.cpp @@ -0,0 +1,60 @@ +// 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 "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/coverage/datamodel/UUriValidatorTest.cpp b/test/coverage/datamodel/UUriValidatorTest.cpp index a1ffbb89f..c55b13ece 100644 --- a/test/coverage/datamodel/UUriValidatorTest.cpp +++ b/test/coverage/datamodel/UUriValidatorTest.cpp @@ -103,7 +103,8 @@ TEST_F(TestUUriValidator, Valid) { // NOLINT { auto uuri = get_u_uri(); uuri.set_resource_id(WILDCARD); - EXPECT_TRUE(uses_wildcards(uuri)); + EXPECT_TRUE(has_wildcard_resource_id(uuri)); + EXPECT_FALSE(verify_no_wildcards(uuri)); auto [valid, reason] = isValid(uuri); EXPECT_FALSE(valid); @@ -123,41 +124,50 @@ TEST_F(TestUUriValidator, Wildcards) { // NOLINT { // Check for no wildcards auto uuri = get_u_uri(); - EXPECT_FALSE(uses_wildcards(uuri)); + EXPECT_FALSE(has_wildcard_authority(uuri)); + EXPECT_FALSE(has_wildcard_service_id(uuri)); + EXPECT_FALSE(has_wildcard_service_instance_id(uuri)); + EXPECT_FALSE(has_wildcard_version(uuri)); + EXPECT_FALSE(has_wildcard_resource_id(uuri)); + EXPECT_TRUE(verify_no_wildcards(uuri)); } - { // Change Authority name to "hello*" (Any) + { // Change Authority name to "*" (Any) auto uuri = get_u_uri(); - uuri.set_authority_name("hello*"); - EXPECT_TRUE(uses_wildcards(uuri)); + uuri.set_authority_name("*"); + EXPECT_TRUE(has_wildcard_authority(uuri)); + EXPECT_FALSE(verify_no_wildcards(uuri)); } { // Set Service ID to FFFF (Any) and Instance ID to 1 constexpr uint32_t WILDCARD_SERVICE_UE_ID = 0x0001FFFF; auto uuri = get_u_uri(); uuri.set_ue_id(WILDCARD_SERVICE_UE_ID); - EXPECT_TRUE(uses_wildcards(uuri)); + EXPECT_TRUE(has_wildcard_service_id(uuri)); + EXPECT_FALSE(verify_no_wildcards(uuri)); } { // Set Service ID to 1 and Instance ID to FFFF (Any) constexpr uint32_t WILDCARD_INSTANCE_UE_ID = 0xFFFF0001; - // This changed in 581291f in up-spec auto uuri = get_u_uri(); uuri.set_ue_id(WILDCARD_INSTANCE_UE_ID); - EXPECT_TRUE(uses_wildcards(uuri)); + EXPECT_TRUE(has_wildcard_service_instance_id(uuri)); + EXPECT_FALSE(verify_no_wildcards(uuri)); } { // Set major version to FF (Any) constexpr uint32_t WILDCARD_VERSION_MAJOR = 0xFF; auto uuri = get_u_uri(); uuri.set_ue_version_major(WILDCARD_VERSION_MAJOR); - EXPECT_TRUE(uses_wildcards(uuri)); + EXPECT_TRUE(has_wildcard_version(uuri)); + EXPECT_FALSE(verify_no_wildcards(uuri)); } { // Set Resource ID to FFFF (any) auto uuri = get_u_uri(); uuri.set_resource_id(WILDCARD); - EXPECT_TRUE(uses_wildcards(uuri)); + EXPECT_TRUE(has_wildcard_resource_id(uuri)); + EXPECT_FALSE(verify_no_wildcards(uuri)); } } @@ -183,7 +193,12 @@ TEST_F(TestUUriValidator, ValidRpcMethod) { // NOLINT auto [valid, reason] = isValidRpcMethod(uuri); EXPECT_TRUE(valid); EXPECT_FALSE(reason.has_value()); - EXPECT_FALSE(uses_wildcards(uuri)); + EXPECT_FALSE(has_wildcard_authority(uuri)); + EXPECT_FALSE(has_wildcard_service_id(uuri)); + EXPECT_FALSE(has_wildcard_service_instance_id(uuri)); + EXPECT_FALSE(has_wildcard_version(uuri)); + EXPECT_FALSE(has_wildcard_resource_id(uuri)); + EXPECT_TRUE(verify_no_wildcards(uuri)); } { @@ -234,7 +249,7 @@ TEST_F(TestUUriValidator, ValidRpcResponse) { // NOLINT auto [valid, reason] = isValidRpcResponse(uuri); EXPECT_TRUE(valid); EXPECT_FALSE(reason.has_value()); - EXPECT_FALSE(uses_wildcards(uuri)); + EXPECT_TRUE(verify_no_wildcards(uuri)); } { @@ -285,7 +300,12 @@ TEST_F(TestUUriValidator, ValidPublishTopic) { // NOLINT auto [valid, reason] = isValidPublishTopic(uuri); EXPECT_TRUE(valid); EXPECT_FALSE(reason.has_value()); - EXPECT_FALSE(uses_wildcards(uuri)); + EXPECT_FALSE(has_wildcard_authority(uuri)); + EXPECT_FALSE(has_wildcard_service_id(uuri)); + EXPECT_FALSE(has_wildcard_service_instance_id(uuri)); + EXPECT_FALSE(has_wildcard_version(uuri)); + EXPECT_FALSE(has_wildcard_resource_id(uuri)); + EXPECT_TRUE(verify_no_wildcards(uuri)); } { @@ -345,7 +365,12 @@ TEST_F(TestUUriValidator, ValidNotificationSource) { // NOLINT auto [valid, reason] = isValidNotificationSource(uuri); EXPECT_TRUE(valid); EXPECT_FALSE(reason.has_value()); - EXPECT_FALSE(uses_wildcards(uuri)); + EXPECT_FALSE(has_wildcard_authority(uuri)); + EXPECT_FALSE(has_wildcard_service_id(uuri)); + EXPECT_FALSE(has_wildcard_service_instance_id(uuri)); + EXPECT_FALSE(has_wildcard_version(uuri)); + EXPECT_FALSE(has_wildcard_resource_id(uuri)); + EXPECT_TRUE(verify_no_wildcards(uuri)); } { @@ -404,7 +429,12 @@ TEST_F(TestUUriValidator, ValidNotificationSink) { // NOLINT auto [valid, reason] = isValidNotificationSink(uuri); EXPECT_TRUE(valid); EXPECT_FALSE(reason.has_value()); - EXPECT_FALSE(uses_wildcards(uuri)); + EXPECT_FALSE(has_wildcard_authority(uuri)); + EXPECT_FALSE(has_wildcard_service_id(uuri)); + EXPECT_FALSE(has_wildcard_service_instance_id(uuri)); + EXPECT_FALSE(has_wildcard_version(uuri)); + EXPECT_FALSE(has_wildcard_resource_id(uuri)); + EXPECT_TRUE(verify_no_wildcards(uuri)); } { @@ -448,7 +478,12 @@ TEST_F(TestUUriValidator, ValidSubscription) { // NOLINT auto [valid, reason] = isValidSubscription(uuri); EXPECT_TRUE(valid); EXPECT_FALSE(reason.has_value()); - EXPECT_FALSE(uses_wildcards(uuri)); + EXPECT_FALSE(has_wildcard_authority(uuri)); + EXPECT_FALSE(has_wildcard_service_id(uuri)); + EXPECT_FALSE(has_wildcard_service_instance_id(uuri)); + EXPECT_FALSE(has_wildcard_version(uuri)); + EXPECT_FALSE(has_wildcard_resource_id(uuri)); + EXPECT_TRUE(verify_no_wildcards(uuri)); } {