diff --git a/include/up-cpp/communication/NotificationSink.h b/include/up-cpp/communication/NotificationSink.h index f9de9040d..2e93b7ea5 100644 --- a/include/up-cpp/communication/NotificationSink.h +++ b/include/up-cpp/communication/NotificationSink.h @@ -41,20 +41,44 @@ struct NotificationSink { /// Resetting the unique_ptr for the NotificationSink will automatically /// unregister the callback. /// - /// @param sink URI of this uE. The authority and entity will be replaced - /// automatically with those found in the transport's default. - /// @param callback Called when a notification is received. - /// @param source_filter (Optional) URI to compare against notification - /// sources. Only notifications that match will be - /// forwarded tot he callback. + /// @param transport Shared pointer to a transport instance. + /// @param callback Called when a notification is received from the source. + /// @param source_filter Notifications matching this source pattern will + /// be passed to the callback. + /// + /// @remarks The transport's entity URI will be used as a sink filter when + /// the callback is registered with the transport. + /// + /// @throws datamodel::validator::uri::InvalidUUri if the source_filter + /// is an invalid pattern for matching notification messages. + /// @throws transport::NullTransport if the transport pointer is nullptr. /// /// @returns /// * unique_ptr to a NotificationSink if the callback was connected /// successfully. /// * UStatus containing an error state otherwise. - static SinkOrStatus create(std::shared_ptr transport, - const v1::UUri& sink, ListenCallback&& callback, - std::optional&& source_filter); + [[nodiscard]] static SinkOrStatus create( + std::shared_ptr transport, + ListenCallback&& callback, const v1::UUri& source_filter); + + /// @note DEPRECATED + /// @brief Create a notification sink to receive notifications. + /// + /// Now a wrapper for create(transport, callback, source_filter) + /// + /// @param sink URI of this uE. Must be transport->getEntityUri(). + /// @param callback Called when a notification is received. + /// @param source_filter Notifications matching this pattern will be + /// forwarded to the callback. MUST HAVE A VALUE. + /// + /// @throws InvalidUUri if sink is not transport->getEntityUri(). + /// @throws InvalidUUri if source_filter has no value. + [[deprecated( + "See alternate overload of " + "create()")]] [[nodiscard]] static SinkOrStatus + create(std::shared_ptr transport, + const v1::UUri& sink, ListenCallback&& callback, + std::optional&& source_filter); ~NotificationSink() = default; diff --git a/include/up-cpp/datamodel/builder/UMessage.h b/include/up-cpp/datamodel/builder/UMessage.h index 90063c88c..e9eaf2592 100644 --- a/include/up-cpp/datamodel/builder/UMessage.h +++ b/include/up-cpp/datamodel/builder/UMessage.h @@ -234,7 +234,10 @@ struct UMessageBuilder { /// @brief Access the attributes of the message being built. /// @return A reference to the attributes of the message being built. - [[nodiscard]] const v1::UAttributes& attributes() const { + [[deprecated( + "Created for tests. Unused now. Do not use")]] [[nodiscard]] const v1:: + UAttributes& + attributes() const { return attributes_; } diff --git a/include/up-cpp/datamodel/validator/UUri.h b/include/up-cpp/datamodel/validator/UUri.h index ee67006c1..840b543fd 100644 --- a/include/up-cpp/datamodel/validator/UUri.h +++ b/include/up-cpp/datamodel/validator/UUri.h @@ -30,7 +30,15 @@ enum class Reason { /// for this particular form. DISALLOWED_WILDCARD, /// @brief The resource ID is not in the allowed range for this URI form. - BAD_RESOURCE_ID + BAD_RESOURCE_ID, + /// @brief The URI has a blank (local) authority name + LOCAL_AUTHORITY, + /// @brief uE Major Version is greater than uint8_t max + VERSION_OVERFLOW, + /// @brief Resource ID is greater than uint16_t max + RESOURCE_OVERFLOW, + /// @brief Authority Name is longer than 128 characters + AUTHORITY_TOO_LONG }; /// @brief Get a descriptive message for a reason code. @@ -49,7 +57,13 @@ std::string_view message(Reason); /// } using ValidationResult = std::tuple>; -/// @brief Checks if UUri is a valid UUri of any format. +/// @note DEPRECATED - This check can produce misleading results. It doesn't +/// handle filters with wildcards. It also is not very useful when +/// checking URI fields in messages where a message type is already +/// available. In those cases, the appropriate type-specfic check should +/// be used instead. +/// +/// @brief Checks if UUri is a valid UUri for use as an attribute in a message /// /// A UUri is valid if: /// @@ -59,7 +73,14 @@ using ValidationResult = std::tuple>; /// * isValidRpcResponse() /// * isValidPublishTopic() /// * isValidNotification() -[[nodiscard]] ValidationResult isValid(const v1::UUri&); +[[deprecated( + "Use isValidFilter or a message-type-specific " + "validator")]] [[nodiscard]] ValidationResult +isValid(const v1::UUri&); + +/// @brief Checks if a UUri is valid as a source_filter or sink_filter when +/// registering a listener with a transport. +[[nodiscard]] ValidationResult isValidFilter(const v1::UUri&); /// @brief Checks if UUri is valid for invoking an RPC method /// @@ -73,12 +94,18 @@ using ValidationResult = std::tuple>; /// resource_id must be 0. [[nodiscard]] ValidationResult isValidRpcResponse(const v1::UUri&); -/// @brief Checks if UUri is valid as a default source on a UTransport. +/// @brief Checks if UUri is valid as an entity URI for a UTransport. /// -/// @note This just calls isValidRpcResponse() because the requirements are the -/// same in both cases. It is provided as a separate function here for -/// improved readability where it is called. -[[nodiscard]] ValidationResult isValidDefaultSource(const v1::UUri&); +/// @note The requirements for this URI are identical to isValidRpcResponse() +/// except that the authority name is not allowed to be blank. +[[nodiscard]] ValidationResult isValidDefaultEntity(const v1::UUri&); + +/// @note DEPRECATED +/// @see isValidDefaultEntity() +/// @brief Checks if UUri is valid as a default source on a UTransport. +[[deprecated( + "Use isValidDefaultEntity instead")]] [[nodiscard]] ValidationResult +isValidDefaultSource(const v1::UUri&); /// @brief Checks if UUri is valid for publishing to a topic, OR as a source /// and sink for sending notifications, OR as a sink for receiving @@ -134,8 +161,10 @@ using ValidationResult = std::tuple>; /// @remarks Generally used by L2 client interfaces. Not used by checks that /// return ValidationResult. struct InvalidUUri : public std::invalid_argument { - // Inherit constructors - using std::invalid_argument::invalid_argument; + // Forward constructors + template + InvalidUUri(Args&&... args) + : std::invalid_argument(std::forward(args)...) {} InvalidUUri(InvalidUUri&&) noexcept; InvalidUUri& operator=(InvalidUUri&&) noexcept; diff --git a/include/up-cpp/transport/UTransport.h b/include/up-cpp/transport/UTransport.h index f3d985369..299f92c9e 100644 --- a/include/up-cpp/transport/UTransport.h +++ b/include/up-cpp/transport/UTransport.h @@ -49,13 +49,13 @@ class UTransport { public: /// @brief Constructor /// - /// @param Default Authority and Entity (as a UUri) for clients using this - /// transport instance. + /// @param Default Authority and Entity ID representing the uEntity that + /// owns this transport instance. /// - /// @throws InvalidUUri if the provided UUri is not valid as a default - /// source. Validation is done with isValidDefaultSource(). + /// @throws InvalidUUri if the provided UUri is not valid as an entity URI + /// Validation is done with isValidDefaultEntityUri(). /// - /// @see uprotocol::datamodel::validator::uri::isValidDefaultSource() + /// @see uprotocol::datamodel::validator::uri::isValidEntityUri() /// @see uprotocol::datamodel::validator::uri::InvalidUUri explicit UTransport(const v1::UUri&); @@ -83,54 +83,142 @@ class UTransport { /// to check if they are connected. using ListenHandle = typename CallbackConnection::Handle; + /// @brief Return value where a ListenHandle is provided on success and a + /// v1::UStatus is provided on error. + using HandleOrStatus = utils::Expected; + /// @brief Register listener to be called when UMessage is received - /// for the given URI, filtered by message source. + /// for the given resource ID. /// - /// @remarks The UUris here can contain wildcard matches for the filters. + /// Automatically populates the sink_filter URI by calling getEntityUri() + /// then replacing that URI's resource ID with sink_resource_filter. + /// + /// @see registerListener(ListenCallback&&, const v1::UUri& source_filter, + /// std::optional&& sink_filter) + /// for more details on the filter parameters. /// - /// @param sink_filter UUri for where messages are expected to arrive via - /// the underlying transport technology. The callback - /// will be called when a message with a matching sink - /// arrives. /// @param listener Callback to be called when a message is received - /// at the given URI. The UMessage will be provided - /// as a parameter when the callback is called. - /// @param source_filter (Optional) UUri for where messages are expected to - /// have been sent from. The callback will only be - /// called for messages where the source matches. + /// matching the provided filters. The received UMessage + /// will be passed to the callback. + /// @param source_filter URI to use as a source filter for incoming + /// messages. + /// @param sink_resource_filter Resource ID that will be combined with + /// getEntityUri() to produce a sink filter. + /// + /// @throws datamodel::validator::InvalidUUri when any URI does not pass + /// validation. /// - /// @throws InvalidUUri if either UUri fails the isValid() check. + /// @returns * A connected ListenHandle if the listener was registered + /// successfully. + /// * A v1::UStatus containing a FAILSTATUS code otherwise. + [[nodiscard]] HandleOrStatus registerListener( + ListenCallback&& listener, const v1::UUri& source_filter, + uint16_t sink_resource_filter); + + /// @brief Register listener to be called when UMessage is received + /// matching the URI filters provided. + /// + /// @param listener Callback to be called when a message is received + /// matching the provided filters. The received UMessage + /// will be passed to the callback. + /// @param source_filter URI that will be compared to the source URI + /// attribute on incoming messages. This could contain + /// a URI for a specific entity/resource, or contain + /// wildcard values. + /// @param sink_filter (optional) + /// * When set, this URI will be compared to the sink + /// URI attribute on incoming messages. This would + /// typically be based on the URI retrieved from + /// getEntityUri(), but could have other values or + /// wildcards present. + /// * When unset, it will be assumed that the + /// source_filter will be used like a topic for + /// pub/sub-like messaging. This is effectively a + /// wildcard, but the precise handling of this state + /// is transport implementation specific. /// /// @see uprotocol::datamodel::validator::uri::isValid() + /// @see uprotocol::datamodel::validator::uri::isValidSubscription() /// @see uprotocol::datamodel::validator::uri::InvalidUUri /// - /// @returns * OKSTATUS and a connected ListenHandle if the listener - /// was registered successfully. - /// * FAILSTATUS with the appropriate failure and an - /// unconnected ListenHandle otherwise. - [[nodiscard]] utils::Expected registerListener( - const v1::UUri& sink_filter, ListenCallback&& listener, - std::optional&& source_filter = {}); + /// @throws datamodel::validator::InvalidUUri when any URI does not pass + /// validation with isValid(). + /// @throws datamodel::validator::InvalidUUri the sink_filter is unset and + /// source_filter does not pass isValidSubscription(). + /// + /// @returns * A connected ListenHandle if the listener was registered + /// successfully. + /// * A v1::UStatus containing a FAILSTATUS code otherwise. + [[nodiscard]] HandleOrStatus registerListener( + ListenCallback&& listener, const v1::UUri& source_filter, + std::optional&& sink_filter = {}); - /// @brief Gets the default source Authority and Entity for all clients - /// using this transport instance. + /// @note This interface has been replaced with alternatives that better + /// align with the 1.6.0 uProtocol specifications. It is no longer + /// recommended to use this method as it will be removed in a future + /// release. It has been modified for uP 1.6.0 compliance. + /// + /// @brief Register listener to be called when UMessage is received + /// matching the provided filters. + /// + /// @remarks This is a wrapper around the new, non-deprecated + /// registerListener() method. Pay special attention to the + /// parameters and their meanings. /// - /// @returns Const reference to the default source. - [[nodiscard]] const v1::UUri& getDefaultSource() const; + /// @param sink_or_topic_filter + /// Has two different meanings: + /// * When source_filter is provided, this filter will be + /// matched against the sink field of received messages. + /// * When source_filter is unset, this filter will be the + /// topic for pub/sub-like messaging, passed to the new + /// registerListener() as the source_filter. + /// @param listener Callback to be called when a message is received + /// matching the provided filters. The received UMessage + /// will be passed to the callback. + /// @param source_filter (Optional) + /// * When set, this filter will be matched against + /// the source field in received messages. + /// * When unset, the sink_or_topic_filter will be + /// treated as the topic for a pub/sub-like mode. + /// + /// @throws datamodel::validator::InvalidUUri when any URI does not pass + /// validation. + /// + /// @returns * A connected ListenHandle if the listener was registered + /// successfully. + /// * A v1::UStatus containing a FAILSTATUS code otherwise. + [[deprecated( + "See alternate overload of " + "registerListener()")]] [[nodiscard]] HandleOrStatus + registerListener(const v1::UUri& sink_or_topic_filter, + ListenCallback&& listener, + std::optional&& source_filter = {}); + + /// @brief Gets the default URI representing the entity that owns this + /// transport instance. + /// + /// @remarks This URI is consumed both by higher-layer APIs and in + /// UTransport methods locally (where documented). + /// + /// @returns Const reference to the entity URI. + [[nodiscard]] const v1::UUri& getEntityUri() const; + + /// @see getEntityUri() + [[deprecated("Replaced with getEntityUri()")]] [[nodiscard]] const v1::UUri& + getDefaultSource() const; virtual ~UTransport() = default; protected: - /// @brief Send a message. + /// @brief Send a message using the transport implementation. /// - /// Must be implemented by the transport client library. + /// @note Must be implemented by the transport library. /// - /// @param message UMessage to be sent. + /// @param UMessage to be sent. /// - /// @returns * OKSTATUS if the payload has been successfully - /// sent (ACK'ed) + /// @returns * OKSTATUS if the payload has been successfully sent (ACK'ed) /// * FAILSTATUS with the appropriate failure. - [[nodiscard]] virtual v1::UStatus sendImpl(const v1::UMessage& message) = 0; + [[nodiscard]] virtual v1::UStatus sendImpl(const v1::UMessage&) = 0; /// @brief Represents the callable end of a callback connection. /// @@ -140,44 +228,61 @@ class UTransport { /// to invoke the callback. using CallableConn = typename CallbackConnection::Callable; - /// @brief Register listener to be called when UMessage is received - /// for the given URI. + /// @brief Register a connected listener with the transport implementation. /// - /// The transport library is required to implement this. + /// @note The transport library is required to implement this. /// - /// @remarks If this doesn't return OKSTATUS, the public wrapping - /// version of registerListener() will reset the connection - /// handle before returning it to the caller. + /// @remarks If this doesn't return OKSTATUS, the outer registerListener() + /// will reset the connection and return the UStatus as an error + /// to the caller. /// - /// @param sink_filter UUri for where messages are expected to arrive via - /// the underlying transport technology. The callback - /// will be called when a message with a matching sink - /// @param listener shared_ptr to a connected callback object, to be + /// @param listener Connected CallerHandle representing the callback to be /// called when a message is received. - /// @param source_filter (Optional) UUri for where messages are expected to - /// have been sent from. The callback will only be - /// called for messages where the source matches. + /// @param source_filter Filter to compare with the source attribute on + /// incoming messages. Could be a literal match or + /// match on wildcards. + /// @param sink_filter (Optional) Filter to compare with the sink attribute + /// on incoming messages. If unset, the transport may + /// need to implement special behavior per UProtocol + /// spec. An unset sink_filter generally implies a + /// pub/sub-like messaging mode. /// /// @returns * OKSTATUS if the listener was registered successfully. /// * FAILSTATUS with the appropriate failure otherwise. [[nodiscard]] virtual v1::UStatus registerListenerImpl( - const v1::UUri& sink_filter, CallableConn&& listener, - std::optional&& source_filter) = 0; + CallableConn&& listener, const v1::UUri& source_filter, + std::optional&& sink_filter) = 0; /// @brief Clean up on listener disconnect. /// - /// The transport library can optionally implement this if it needs - /// to clean up when a callbacks::Connection is dropped. + /// The transport library can optionally implement this if it needs to + /// clean up when a callbacks::Connection is dropped. /// /// @note The default implementation does nothing. /// - /// @param listener shared_ptr of the Connection that has been broken. + /// @remarks CallerHandle instances are sortable and matchable on the + /// connection they represent. + /// + /// @param listener CallerHandle for the connection that has been broken. virtual void cleanupListener(CallableConn listener); private: - /// @brief Default source Authority and Entity for all clients using this - /// transport instance. - const v1::UUri defaultSource_; + /// @brief URI for the entity owning this transport. + /// + /// @remarks Many Layer 2 implementations will use this to form the source + /// address for their calls to UTransport. + const v1::UUri entity_uri_; +}; + +struct NullTransport : public std::invalid_argument { + template + NullTransport(Args&&... args) + : std::invalid_argument(std::forward(args)...) {} + + template + NullTransport operator=(Args&&... args) { + return std::invalid_argument::operator=(std::forward(args)...); + } }; } // namespace uprotocol::transport diff --git a/src/communication/NotificationSink.cpp b/src/communication/NotificationSink.cpp index 0f4c6ec34..27d7bafa4 100644 --- a/src/communication/NotificationSink.cpp +++ b/src/communication/NotificationSink.cpp @@ -11,35 +11,73 @@ #include "up-cpp/communication/NotificationSink.h" +#include + #include "up-cpp/datamodel/validator/UUri.h" namespace uprotocol::communication { -namespace UriValidator = uprotocol::datamodel::validator::uri; +namespace UriValidator = datamodel::validator::uri; + NotificationSink::SinkOrStatus NotificationSink::create( - std::shared_ptr transport, - const uprotocol::v1::UUri& sink, ListenCallback&& callback, - std::optional&& source_filter) { + std::shared_ptr transport, ListenCallback&& callback, + const uprotocol::v1::UUri& source_filter) { + // Standard check - transport pointer cannot be null if (!transport) { - throw std::invalid_argument("transport cannot be null"); + throw transport::NullTransport("transport cannot be null"); } - auto [sinkOk, sinkReason] = UriValidator::isValidNotificationSource(sink); - if (!sinkOk) { + + // NOTE: isValidSubscription() is documented as + // "valid ... as a source filter when subscribing to a notification" + auto [source_ok, bad_source_reason] = + UriValidator::isValidSubscription(source_filter); + if (!source_ok) { throw UriValidator::InvalidUUri( - "URI is not a valid URI | " + - std::string(UriValidator::message(*sinkReason))); + "Source filter is not a valid notification source URI | " + + std::string(UriValidator::message(*bad_source_reason))); } - auto listener = transport->registerListener(sink, std::move(callback), - std::move(source_filter)); + + auto listener = transport->registerListener( + std::move(callback), source_filter, transport->getEntityUri()); + if (!listener) { return uprotocol::utils::Unexpected(listener.error()); } + return std::make_unique( std::forward>(transport), std::forward(std::move(listener).value())); } +// NOTE: deprecated +NotificationSink::SinkOrStatus NotificationSink::create( + std::shared_ptr transport, + const uprotocol::v1::UUri& sink, ListenCallback&& callback, + std::optional&& source_filter) { + // Standard check - transport pointer cannot be null + if (!transport) { + throw transport::NullTransport("transport cannot be null"); + } + + // With the adjustments to the API to match 1.6.0, the primary filter for + // a notification is the _source_ URI. The sink will always be this entity's + // URI from the transport. + if (!source_filter) { + throw UriValidator::InvalidUUri("Source filter must be provided"); + } + + using MessageDifferencer = google::protobuf::util::MessageDifferencer; + + if (!MessageDifferencer::Equals(sink, transport->getEntityUri())) { + throw UriValidator::InvalidUUri( + "Sink filter must match transport->getEntityUri()"); + } + + return create(transport, std::move(callback), *source_filter); +} + NotificationSink::NotificationSink( std::shared_ptr transport, NotificationSink::ListenHandle&& listener) : transport_(std::move(transport)), listener_(std::move(listener)) {} -} // namespace uprotocol::communication \ No newline at end of file + +} // namespace uprotocol::communication diff --git a/src/communication/RpcClient.cpp b/src/communication/RpcClient.cpp index 318266d36..4a0b061bf 100644 --- a/src/communication/RpcClient.cpp +++ b/src/communication/RpcClient.cpp @@ -92,7 +92,7 @@ RpcClient::RpcClient(std::shared_ptr transport, : transport_(transport), ttl_(ttl), builder_(datamodel::builder::UMessageBuilder::request( - std::move(method), v1::UUri(transport_->getDefaultSource()), priority, + std::move(method), v1::UUri(transport_->getEntityUri()), priority, ttl_)), expire_service_(std::make_unique()) { if (payload_format) { @@ -166,8 +166,8 @@ RpcClient::InvokeHandle RpcClient::invokeMethod(v1::UMessage&& request, /////////////////////////////////////////////////////////////////////////// auto maybe_handle = transport_->registerListener( - request.attributes().source(), std::move(wrapper), - request.attributes().sink()); + std::move(wrapper), request.attributes().sink(), + request.attributes().source()); if (!maybe_handle) { expire(std::move(maybe_handle).error()); diff --git a/src/communication/RpcServer.cpp b/src/communication/RpcServer.cpp index 936dc7cc6..53676d27f 100644 --- a/src/communication/RpcServer.cpp +++ b/src/communication/RpcServer.cpp @@ -68,8 +68,6 @@ RpcServer::ServerOrStatus RpcServer::create( v1::UStatus RpcServer::connect(const v1::UUri& method, RpcCallback&& callback) { callback_ = std::move(callback); auto result = transport_->registerListener( - // sink_filter= - method, // listener= [this](const v1::UMessage& request) { // Validate the request message using a RPC message validator. @@ -108,7 +106,19 @@ v1::UStatus RpcServer::connect(const v1::UUri& method, RpcCallback&& callback) { // Ignoring status code for transport send std::ignore = transport_->send(response); } - }); + }, + // source_filter= + []() { + v1::UUri any_uri; + any_uri.set_authority_name("*"); + // Instance ID 0 and UE ID FFFF for wildcard + any_uri.set_ue_id(0x0000FFFF); + any_uri.set_ue_version_major(0xFF); + any_uri.set_resource_id(0xFFFF); + return any_uri; + }(), + // sink_filter= + method); if (result.has_value()) { // If result is successful, extract the UTransport::ListenHandle and @@ -122,4 +132,4 @@ v1::UStatus RpcServer::connect(const v1::UUri& method, RpcCallback&& callback) { } } -} // namespace uprotocol::communication \ No newline at end of file +} // namespace uprotocol::communication diff --git a/src/communication/Subscriber.cpp b/src/communication/Subscriber.cpp index 90ac60f84..756fb5a2d 100644 --- a/src/communication/Subscriber.cpp +++ b/src/communication/Subscriber.cpp @@ -19,19 +19,25 @@ namespace UriValidator = uprotocol::datamodel::validator::uri; [[nodiscard]] Subscriber::SubscriberOrStatus Subscriber::subscribe( std::shared_ptr transport, const v1::UUri& topic, ListenCallback&& callback) { + // Standard check - transport pointer cannot be null if (!transport) { - throw std::invalid_argument("transport cannot be null"); + throw transport::NullTransport("transport cannot be null"); } - auto [srcOk, srcReason] = UriValidator::isValidSubscription(topic); - if (!srcOk) { + + auto [source_ok, bad_source_reason] = + UriValidator::isValidSubscription(topic); + if (!source_ok) { throw UriValidator::InvalidUUri( - "URI is not a valid URI | " + - std::string(UriValidator::message(*srcReason))); + "Topic URI is not a valid topic subscription pattern | " + + std::string(UriValidator::message(*bad_source_reason))); } - auto handle = transport->registerListener(topic, std::move(callback)); + + auto handle = transport->registerListener(std::move(callback), topic); + if (!handle) { return uprotocol::utils::Unexpected(handle.error()); } + return std::make_unique( std::forward>(transport), std::forward(std::move(handle).value())); @@ -43,4 +49,4 @@ Subscriber::Subscriber(std::shared_ptr transport, // Constructor body. Any additional setup can go here. } -} // namespace uprotocol::communication \ No newline at end of file +} // namespace uprotocol::communication diff --git a/src/datamodel/builder/UMessage.cpp b/src/datamodel/builder/UMessage.cpp index 593b798f8..c2d433f25 100644 --- a/src/datamodel/builder/UMessage.cpp +++ b/src/datamodel/builder/UMessage.cpp @@ -62,7 +62,7 @@ UMessageBuilder UMessageBuilder::request(v1::UUri&& method, v1::UUri&& source, "Method URI is not a valid URI | " + std::string(UriValidator::message(*methodReason))); } - auto [srcOk, srcReason] = UriValidator::isValid(source); + auto [srcOk, srcReason] = UriValidator::isValidRpcResponse(source); if (!srcOk) { throw UriValidator::InvalidUUri( "Source URI is not a valid URI | " + @@ -89,7 +89,7 @@ UMessageBuilder UMessageBuilder::response(v1::UUri&& sink, "Method URI is not a valid URI | " + std::string(UriValidator::message(*methodReason))); } - auto [sinkOk, sinkReason] = UriValidator::isValid(sink); + auto [sinkOk, sinkReason] = UriValidator::isValidRpcResponse(sink); if (!sinkOk) { throw UriValidator::InvalidUUri( "Source URI is not a valid URI | " + @@ -251,4 +251,4 @@ void UMessageBuilder::setPayloadFormat(v1::UPayloadFormat format) { expectedPayloadFormat_ = format; } -} // namespace uprotocol::datamodel::builder \ No newline at end of file +} // namespace uprotocol::datamodel::builder diff --git a/src/datamodel/serializer/UUri.cpp b/src/datamodel/serializer/UUri.cpp index 45f2cce25..78232fd7a 100644 --- a/src/datamodel/serializer/UUri.cpp +++ b/src/datamodel/serializer/UUri.cpp @@ -19,10 +19,11 @@ namespace uprotocol::datamodel::serializer::uri { std::string AsString::serialize(const v1::UUri& uri) { using namespace uprotocol::datamodel::validator::uri; - auto [valid, reason] = isValid(uri); + // isValidFilter is the most permissive of the validators + auto [valid, reason] = isValidFilter(uri); if (!valid) { - throw std::invalid_argument("Invalid UUri For Serialization | " + - std::string(message(*reason))); + throw InvalidUUri("Invalid UUri For Serialization | " + + std::string(message(*reason))); } std::stringstream ss; ss << std::hex << std::uppercase; @@ -107,12 +108,13 @@ uprotocol::v1::UUri AsString::deserialize(const std::string& uriAsString) { { using namespace uprotocol::datamodel::validator::uri; - auto [valid, reason] = isValid(uri); + // isValidFilter is the most permissive of the validators + auto [valid, reason] = isValidFilter(uri); if (!valid) { - throw std::invalid_argument("Invalid UUri For DeSerialization | " + - std::string(message(*reason))); + throw InvalidUUri("Invalid UUri For DeSerialization | " + + std::string(message(*reason))); } } return uri; } -} // namespace uprotocol::datamodel::serializer::uri \ No newline at end of file +} // namespace uprotocol::datamodel::serializer::uri diff --git a/src/datamodel/validator/UUri.cpp b/src/datamodel/validator/UUri.cpp index 99c8dee54..de26d617b 100644 --- a/src/datamodel/validator/UUri.cpp +++ b/src/datamodel/validator/UUri.cpp @@ -11,9 +11,34 @@ #include "up-cpp/datamodel/validator/UUri.h" -namespace uprotocol::datamodel::validator::uri { +namespace { + +constexpr size_t AUTHORITY_SPEC_MAX_LENGTH = 128; + +using namespace uprotocol::datamodel::validator::uri; +ValidationResult uriCommonValidChecks(const uprotocol::v1::UUri& uuri) { + if (uuri.ue_version_major() == 0) { + return {false, Reason::RESERVED_VERSION}; + } + + if (uuri.ue_version_major() > std::numeric_limits::max()) { + return {false, Reason::VERSION_OVERFLOW}; + } -using namespace uprotocol; + if (uuri.resource_id() > std::numeric_limits::max()) { + return {false, Reason::RESOURCE_OVERFLOW}; + } + + if (uuri.authority_name().size() > AUTHORITY_SPEC_MAX_LENGTH) { + return {false, Reason::AUTHORITY_TOO_LONG}; + } + + return {true, {}}; +} + +} // namespace + +namespace uprotocol::datamodel::validator::uri { std::string_view message(Reason reason) { switch (reason) { @@ -29,6 +54,14 @@ std::string_view message(Reason reason) { case Reason::BAD_RESOURCE_ID: return "The resource ID is not in the allowed range for this URI " "form."; + case Reason::LOCAL_AUTHORITY: + return "Local (blank) authority names are not allowed here."; + case Reason::VERSION_OVERFLOW: + return "uE Major Version is greater than uint8 max"; + case Reason::RESOURCE_OVERFLOW: + return "Resource ID is greater than uint16 max"; + case Reason::AUTHORITY_TOO_LONG: + return "Authority is longer than 128 characters (spec max length)"; default: return "Unknown reason."; } @@ -85,6 +118,20 @@ ValidationResult isValid(const v1::UUri& uuri) { return isValidNotificationSink(uuri); } +ValidationResult isValidFilter(const v1::UUri& uuri) { + auto [is_empty, empty_reason] = isEmpty(uuri); + if (is_empty) { + return {false, Reason::EMPTY}; + } + + auto [common_valid, common_fail_reason] = uriCommonValidChecks(uuri); + if (!common_valid) { + return {common_valid, common_fail_reason}; + } + + return {true, {}}; +} + ValidationResult isValidRpcMethod(const v1::UUri& uuri) { // disallow wildcards if (uses_wildcards(uuri)) { @@ -111,14 +158,18 @@ ValidationResult isValidRpcResponse(const v1::UUri& uuri) { return {true, std::nullopt}; } -ValidationResult isValidDefaultSource(const v1::UUri& uuri) { - if (uuri.authority_name().empty()) { - return {false, Reason::EMPTY}; +ValidationResult isValidDefaultEntity(const v1::UUri& uuri) { + if (isLocal(uuri)) { + return {false, Reason::LOCAL_AUTHORITY}; } return isValidRpcResponse(uuri); } +ValidationResult isValidDefaultSource(const v1::UUri& uuri) { + return isValidDefaultEntity(uuri); +} + ValidationResult isValidPublishTopic(const v1::UUri& uuri) { // disallow wildcards if (uses_wildcards(uuri)) { diff --git a/src/transport/UTransport.cpp b/src/transport/UTransport.cpp index 516b3e232..0904504ae 100644 --- a/src/transport/UTransport.cpp +++ b/src/transport/UTransport.cpp @@ -20,12 +20,11 @@ namespace uprotocol::transport { namespace UriValidator = uprotocol::datamodel::validator::uri; namespace MessageValidator = uprotocol::datamodel::validator::message; -UTransport::UTransport(const v1::UUri& defaultSrc) - : defaultSource_(defaultSrc) { - auto [srcOk, reason] = UriValidator::isValidDefaultSource(defaultSource_); - if (!srcOk) { +UTransport::UTransport(const v1::UUri& entity_uri) : entity_uri_(entity_uri) { + auto [uri_ok, reason] = UriValidator::isValidDefaultEntity(entity_uri_); + if (!uri_ok) { throw UriValidator::InvalidUUri( - "Source URI is not a valid URI | " + + "Transport's entity URI is not a valid URI | " + std::string(UriValidator::message(*reason))); } } @@ -41,40 +40,82 @@ v1::UStatus UTransport::send(const v1::UMessage& message) { return sendImpl(message); } -utils::Expected -UTransport::registerListener(const v1::UUri& sink_filter, - ListenCallback&& listener, - std::optional&& source_filter) { - auto [sinkOk, reason1] = UriValidator::isValid(sink_filter); - if (!sinkOk) { - throw UriValidator::InvalidUUri( - "sink_filter is not a valid URI | " + - std::string(UriValidator::message(*reason1))); - } +UTransport::HandleOrStatus UTransport::registerListener( + ListenCallback&& listener, const v1::UUri& source_filter, + uint16_t sink_resource_filter) { + auto sink_filter = getEntityUri(); + sink_filter.set_resource_id(sink_resource_filter); + return registerListener(std::move(listener), source_filter, + std::move(sink_filter)); +} - if (source_filter.has_value()) { - auto [srcOk, reason2] = UriValidator::isValid(source_filter.value()); - if (!srcOk) { +UTransport::HandleOrStatus UTransport::registerListener( + ListenCallback&& listener, const v1::UUri& source_filter, + std::optional&& sink_filter) { + if (!sink_filter) { + // Handle the special case of publish-like messages where only a source + // address is provided. + auto [source_ok, bad_source_reason] = + UriValidator::isValidSubscription(source_filter); + if (!source_ok) { + throw UriValidator::InvalidUUri( + "source_filter is not a valid URI | " + + std::string(UriValidator::message(*bad_source_reason))); + } + } else { + auto [source_ok, bad_source_reason] = + UriValidator::isValidFilter(source_filter); + if (!source_ok) { throw UriValidator::InvalidUUri( "source_filter is not a valid URI | " + - std::string(UriValidator::message(*reason2))); + std::string(UriValidator::message(*bad_source_reason))); + } + + auto [sink_ok, bad_sink_reason] = + UriValidator::isValidFilter(*sink_filter); + if (!sink_ok) { + throw UriValidator::InvalidUUri( + "sink_filter is not a valid URI | " + + std::string(UriValidator::message(*bad_sink_reason))); } } auto [handle, callable] = CallbackConnection::establish( std::move(listener), [this](auto conn) { cleanupListener(conn); }); - v1::UStatus status = registerListenerImpl(sink_filter, std::move(callable), - std::move(source_filter)); + v1::UStatus status = registerListenerImpl( + std::move(callable), source_filter, std::move(sink_filter)); if (status.code() == v1::UCode::OK) { return std::move(handle); } else { - return uprotocol::utils::Unexpected(std::move(status)); + return utils::Unexpected(std::move(status)); } } -const v1::UUri& UTransport::getDefaultSource() const { return defaultSource_; } +// NOTE: deprecated +utils::Expected +UTransport::registerListener(const v1::UUri& sink_filter, + ListenCallback&& listener, + std::optional&& source_filter) { + if (!source_filter) { + // Workaround for deprecated API not fully reflecting the use cases + // defined in the uprotocol spec: an unset source_filter is treated + // as meaning that the sink_filter is a topic to subscribe to. This + // will then pass the sink_filter as a source filter to the updated + // registerListener(), which is then called with sink_filter unset. + return registerListener(std::move(listener), sink_filter, + std::move(source_filter)); + } else { + v1::UUri sink_filter_copy = sink_filter; + return registerListener(std::move(listener), *source_filter, + std::move(sink_filter_copy)); + } +} + +const v1::UUri& UTransport::getEntityUri() const { return entity_uri_; } + +const v1::UUri& UTransport::getDefaultSource() const { return getEntityUri(); } void UTransport::cleanupListener(CallableConn listener) { static_cast(listener); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3c9a6f573..0b04d4786 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,7 +25,7 @@ file(COPY # Invoked as add_coverage_test("SomeName" sources...) function(add_coverage_test Name) add_executable(${Name} ${ARGN}) - target_compile_options(${Name} PRIVATE -g -Og) + target_compile_options(${Name} PRIVATE -g -Og -Wno-deprecated-declarations) target_link_libraries(${Name} PUBLIC up-core-api::up-core-api @@ -85,3 +85,4 @@ add_coverage_test("NotificationSourceTest" coverage/communication/NotificationSo add_extra_test("PublisherSubscriberTest" extra/PublisherSubscriberTest.cpp) add_extra_test("NotificationTest" extra/NotificationTest.cpp) add_extra_test("RpcClientServerTest" extra/RpcClientServerTest.cpp) +add_extra_test("UTransportMockTest" extra/UTransportMockTest.cpp) diff --git a/test/coverage/communication/NotificationSinkTest.cpp b/test/coverage/communication/NotificationSinkTest.cpp index 5df8b3757..a17da8722 100644 --- a/test/coverage/communication/NotificationSinkTest.cpp +++ b/test/coverage/communication/NotificationSinkTest.cpp @@ -103,8 +103,8 @@ std::string get_random_string(size_t length) { return str; } -// Positive test case with no source filter -TEST_F(NotificationSinkTest, NotificationSinkSuccessWithoutSourceFilter) { +// Negative test case with no source filter +TEST_F(NotificationSinkTest, FailWithoutSourceFilter) { auto transport = std::make_shared( testDefaultSourceUUri_); @@ -114,31 +114,17 @@ TEST_F(NotificationSinkTest, NotificationSinkSuccessWithoutSourceFilter) { std::optional source_filter; - auto result = - NotificationSink::create(transport, testTopicUUri_, std::move(callback), - std::move(source_filter)); - - EXPECT_TRUE(transport->listener_); - EXPECT_TRUE(result.has_value()); - - auto handle = std::move(result).value(); - EXPECT_TRUE(handle); - EXPECT_TRUE(MsgDiff::Equals(testTopicUUri_, transport->sink_filter_)); - - const size_t max_count = 100; - for (size_t i = 0; i < max_count; i++) { - uprotocol::v1::UMessage msg; - auto attr = std::make_shared(); - *msg.mutable_attributes() = *attr; - msg.set_payload(get_random_string(1400)); - transport->mockMessage(msg); - EXPECT_EQ(i + 1, capture_count_); - EXPECT_TRUE(MsgDiff::Equals(msg, capture_msg_)); - } + EXPECT_THROW( + { + auto result = NotificationSink::create(transport, testTopicUUri_, + std::move(callback), + std::move(source_filter)); + }, + UriValidator::InvalidUUri); } // Negative test case with invalid notification resource ID -TEST_F(NotificationSinkTest, NotificationSinkFailWithInvalidResourceID) { +TEST_F(NotificationSinkTest, FailWithInvalidResourceID) { auto transport = std::make_shared( testDefaultSourceUUri_); @@ -156,7 +142,7 @@ TEST_F(NotificationSinkTest, NotificationSinkFailWithInvalidResourceID) { } // Positive test case with source filter -TEST_F(NotificationSinkTest, NotificationSinkSuccessWithSourceFilter) { +TEST_F(NotificationSinkTest, SuccessWithSourceFilter) { auto transport = std::make_shared( testDefaultSourceUUri_); @@ -164,24 +150,17 @@ TEST_F(NotificationSinkTest, NotificationSinkSuccessWithSourceFilter) { return this->handleCallbackMessage(arg); }; - std::optional source_filter = uprotocol::v1::UUri(); - if (source_filter) { - source_filter->set_authority_name("192.168.1.11"); - source_filter->set_ue_id(0x18001); - source_filter->set_ue_version_major(0x1); - source_filter->set_resource_id(0x0); - } - - auto result = - NotificationSink::create(transport, testTopicUUri_, std::move(callback), - std::move(source_filter)); + auto result = NotificationSink::create(transport, transport->getEntityUri(), + std::move(callback), testTopicUUri_); EXPECT_TRUE(transport->listener_); EXPECT_TRUE(result.has_value()); auto handle = std::move(result).value(); EXPECT_TRUE(handle); - EXPECT_TRUE(MsgDiff::Equals(testTopicUUri_, transport->sink_filter_)); + EXPECT_TRUE(MsgDiff::Equals(testTopicUUri_, transport->source_filter_)); + EXPECT_TRUE( + MsgDiff::Equals(transport->getEntityUri(), *transport->sink_filter_)); const size_t max_count = 100; for (size_t i = 0; i < max_count; i++) { @@ -196,7 +175,7 @@ TEST_F(NotificationSinkTest, NotificationSinkSuccessWithSourceFilter) { } // Simulate Error code from transport mock -TEST_F(NotificationSinkTest, NotificationSinkFailWithErroCode) { +TEST_F(NotificationSinkTest, FailWithErrorCode) { auto transport = std::make_shared( testDefaultSourceUUri_); @@ -204,22 +183,19 @@ TEST_F(NotificationSinkTest, NotificationSinkFailWithErroCode) { return this->handleCallbackMessage(arg); }; - std::optional source_filter; - uprotocol::v1::UStatus expectedStatus; expectedStatus.set_code(uprotocol::v1::UCode::ABORTED); transport->registerListener_status_ = expectedStatus; - auto result = - NotificationSink::create(transport, testTopicUUri_, std::move(callback), - std::move(source_filter)); + auto result = NotificationSink::create(transport, transport->getEntityUri(), + std::move(callback), testTopicUUri_); auto actualStatus = std::move(result).error(); EXPECT_EQ(actualStatus.code(), expectedStatus.code()); } // Notification sink with null transport -TEST_F(NotificationSinkTest, NotificationSinkNullTransport) { +TEST_F(NotificationSinkTest, NullTransport) { // set transport to null auto transport = nullptr; std::optional source_filter; @@ -229,22 +205,19 @@ TEST_F(NotificationSinkTest, NotificationSinkNullTransport) { }; EXPECT_THROW(auto result = NotificationSink::create( - transport, testTopicUUri_, std::move(callback), - std::move(source_filter)), - std::invalid_argument); + transport, testDefaultSourceUUri_, std::move(callback), + testTopicUUri_), + uprotocol::transport::NullTransport); } // notification sink with null callback -TEST_F(NotificationSinkTest, NotificationSinkNullCallback) { +TEST_F(NotificationSinkTest, NullCallback) { auto transport = std::make_shared( testDefaultSourceUUri_); - std::optional source_filter; - // bind to null callback - auto result = - NotificationSink::create(transport, testTopicUUri_, std::move(nullptr), - std::move(source_filter)); + auto result = NotificationSink::create(transport, transport->getEntityUri(), + std::move(nullptr), testTopicUUri_); uprotocol::v1::UMessage msg; auto attr = std::make_shared(); @@ -253,4 +226,4 @@ TEST_F(NotificationSinkTest, NotificationSinkNullCallback) { EXPECT_THROW(transport->mockMessage(msg), std::bad_function_call); } -} // namespace \ No newline at end of file +} // namespace diff --git a/test/coverage/communication/RpcClientTest.cpp b/test/coverage/communication/RpcClientTest.cpp index 0d4361075..f45f3596c 100644 --- a/test/coverage/communication/RpcClientTest.cpp +++ b/test/coverage/communication/RpcClientTest.cpp @@ -92,10 +92,10 @@ class RpcClientTest : public testing::Test { void validateLastRequest(size_t expected_send_count) { EXPECT_TRUE(transport_->listener_); - EXPECT_TRUE(transport_->sink_filter_ == defaultSourceUri()); - EXPECT_TRUE(transport_->source_filter_); - if (transport_->source_filter_) { - EXPECT_TRUE(*(transport_->source_filter_) == methodUri()); + EXPECT_TRUE(transport_->source_filter_ == methodUri()); + EXPECT_TRUE(transport_->sink_filter_); + if (transport_->sink_filter_) { + EXPECT_TRUE(*(transport_->sink_filter_) == defaultSourceUri()); } EXPECT_EQ(transport_->send_count_, expected_send_count); using namespace uprotocol::datamodel::validator; diff --git a/test/coverage/communication/RpcServerTest.cpp b/test/coverage/communication/RpcServerTest.cpp index 03b45bdaf..7eca68dcd 100644 --- a/test/coverage/communication/RpcServerTest.cpp +++ b/test/coverage/communication/RpcServerTest.cpp @@ -254,7 +254,7 @@ TEST_F(TestRpcServer, ConnectwithValidHandle) { EXPECT_NE(handle, nullptr); // Verify that the register listener uri mataches with input method uri - EXPECT_TRUE(MsgDiff::Equals(*method_uri_, mockTransport_->sink_filter_)); + EXPECT_TRUE(MsgDiff::Equals(*method_uri_, *mockTransport_->sink_filter_)); } // Test case to verify RPC request handling with return payload and TTL @@ -274,7 +274,7 @@ TEST_F(TestRpcServer, RPCRequestWithReturnPayloadAndTTL) { auto& handle = serverOrStatus.value(); EXPECT_NE(handle, nullptr); - EXPECT_TRUE(MsgDiff::Equals(*method_uri_, mockTransport_->sink_filter_)); + EXPECT_TRUE(MsgDiff::Equals(*method_uri_, *mockTransport_->sink_filter_)); // Create request umessage auto builder = uprotocol::datamodel::builder::UMessageBuilder::request( @@ -330,7 +330,7 @@ TEST_F(TestRpcServer, RPCRequestWithoutReturnPayload) { auto& handle = serverOrStatus.value(); EXPECT_NE(handle, nullptr); - EXPECT_TRUE(MsgDiff::Equals(*method_uri_, mockTransport_->sink_filter_)); + EXPECT_TRUE(MsgDiff::Equals(*method_uri_, *mockTransport_->sink_filter_)); // Create request umessage auto builder = uprotocol::datamodel::builder::UMessageBuilder::request( @@ -380,17 +380,11 @@ TEST_F(TestRpcServer, RPCRequestWithInValidRequest) { auto& handle = serverOrStatus.value(); EXPECT_NE(handle, nullptr); - // Set valid response resource id for request uri - request_uri_->set_resource_id(0x3); - - // Create UUID for request id - auto reqid = - uprotocol::datamodel::builder::UuidBuilder::getBuilder().build(); - // Create request umessage - auto builder = uprotocol::datamodel::builder::UMessageBuilder::response( - std::move(*method_uri_), std::move(reqid), - uprotocol::v1::UPriority::UPRIORITY_CS5, std::move(*request_uri_)); + using namespace std::chrono_literals; + auto builder = uprotocol::datamodel::builder::UMessageBuilder::request( + std::move(*method_uri_), std::move(*request_uri_), + uprotocol::v1::UPriority::UPRIORITY_CS5, 300ms); auto msg = builder.build(); @@ -434,4 +428,4 @@ TEST_F(TestRpcServer, RestRPCServerHandle) { EXPECT_FALSE(mockTransport_->send_count_ == 2); } -} // namespace \ No newline at end of file +} // namespace diff --git a/test/coverage/communication/SubscriberTest.cpp b/test/coverage/communication/SubscriberTest.cpp index 243da4727..bb210d236 100644 --- a/test/coverage/communication/SubscriberTest.cpp +++ b/test/coverage/communication/SubscriberTest.cpp @@ -119,7 +119,8 @@ TEST_F(SubscriberTest, SubscribeSuccess) { EXPECT_TRUE(result.has_value()); auto handle = std::move(result).value(); EXPECT_TRUE(handle); - EXPECT_TRUE(MsgDiff::Equals(testTopicUUri_, transport->sink_filter_)); + EXPECT_TRUE(MsgDiff::Equals(testTopicUUri_, transport->source_filter_)); + EXPECT_FALSE(transport->sink_filter_); const size_t max_count = 100; for (size_t i = 0; i < max_count; i++) { diff --git a/test/coverage/datamodel/UMessageBuilderTest.cpp b/test/coverage/datamodel/UMessageBuilderTest.cpp index c053f3f26..a6e1bc4e2 100644 --- a/test/coverage/datamodel/UMessageBuilderTest.cpp +++ b/test/coverage/datamodel/UMessageBuilderTest.cpp @@ -34,7 +34,7 @@ class TestUMessageBuilder : public testing::Test { UPriority priority = UPriority::UPRIORITY_CS4; std::chrono::milliseconds ttl(5000); UUri method = method_; - UUri source = source_; + UUri source = sink_; return UMessageBuilder::request(std::move(method), std::move(source), priority, ttl); @@ -152,16 +152,17 @@ TEST_F(TestUMessageBuilder, RequestValidParametersSuccess) { UPriority priority = UPriority::UPRIORITY_CS4; std::chrono::milliseconds ttl(5000); UUri method = method_; - UUri source = source_; + UUri source = sink_; + EXPECT_NO_THROW({ auto builder = UMessageBuilder::request( std::move(method), std::move(source), priority, ttl); - UAttributes attr = builder.attributes(); + auto attr = builder.build().attributes(); EXPECT_EQ(attr.type(), UMessageType::UMESSAGE_TYPE_REQUEST); EXPECT_EQ(AsString::serialize(attr.sink()), AsString::serialize(method_)); EXPECT_EQ(AsString::serialize(attr.source()), - AsString::serialize(source_)); + AsString::serialize(sink_)); EXPECT_EQ(attr.priority(), priority); EXPECT_EQ(attr.ttl(), 5000); }); @@ -195,7 +196,7 @@ TEST_F(TestUMessageBuilder, RequestInvalidSourceUriThrows) { TEST_F(TestUMessageBuilder, RequestInvalidTtlThrows) { UUri method = method_; - UUri source = source_; + UUri source = sink_; UPriority priority = UPriority::UPRIORITY_CS4; std::chrono::milliseconds ttl(-1); // Invalid TTL EXPECT_THROW( diff --git a/test/coverage/datamodel/UUriSerializerTest.cpp b/test/coverage/datamodel/UUriSerializerTest.cpp index 3976a3e8a..ccf154e41 100644 --- a/test/coverage/datamodel/UUriSerializerTest.cpp +++ b/test/coverage/datamodel/UUriSerializerTest.cpp @@ -11,9 +11,11 @@ #include #include +#include namespace { using namespace uprotocol::datamodel::serializer::uri; +using namespace uprotocol::datamodel::validator; using namespace uprotocol; class TestUUriSerializer : public testing::Test { @@ -59,37 +61,56 @@ TEST_F(TestUUriSerializer, SerializeUUriWithNoAuthorityToString) { ASSERT_EQ(expectedUUri, actualUUri); } -// Test Service ID in uEID field as a 0xFFFF to see if it thorws an exception -// for using wildcard +// Test authority name '*' to see if it serializes without an exception for +// using wildcard +TEST_F(TestUUriSerializer, SerializeUUriToStringWithAuthorityWildCard) { + v1::UUri testUUri; + testUUri.set_authority_name("*"); // Wildcard + testUUri.set_ue_id(0x1FFFE); + testUUri.set_ue_version_major(0xFE); + testUUri.set_resource_id(0x7500); + std::string serialized; + ASSERT_NO_THROW(serialized = AsString::serialize(testUUri)); + ASSERT_EQ(serialized, "//*/1FFFE/FE/7500"); +} + +// Test Service ID in uEID field as a 0xFFFF to see if it serializes without +// an exception for using wildcard TEST_F(TestUUriSerializer, SerializeUUriToStringWithServiceIDWildCard) { v1::UUri testUUri; testUUri.set_authority_name("testAuthority"); - testUUri.set_ue_id(0xFFFF); // Wildcard + testUUri.set_ue_id(0x1FFFF); // Wildcard testUUri.set_ue_version_major(0xFE); testUUri.set_resource_id(0x7500); - ASSERT_THROW(AsString::serialize(testUUri), std::invalid_argument); + std::string serialized; + ASSERT_NO_THROW(serialized = AsString::serialize(testUUri)); + ASSERT_EQ(serialized, "//testAuthority/1FFFF/FE/7500"); } -// Test Instance ID in uEID field as a 0x0 to see if it thorws an exception for -// using wildcard +// Test Instance ID in uEID field as a 0x0 to see if it serializes without an +// exception for using wildcard TEST_F(TestUUriSerializer, SerializeUUriToStringWithInstanceIDWildCard) { v1::UUri testUUri; testUUri.set_authority_name("testAuthority"); testUUri.set_ue_id(0x00001234); // Wildcard testUUri.set_ue_version_major(0xFE); testUUri.set_resource_id(0x7500); - ASSERT_THROW(AsString::serialize(testUUri), std::invalid_argument); + std::string serialized; + ASSERT_NO_THROW(serialized = AsString::serialize(testUUri)); + ASSERT_EQ(serialized, "//testAuthority/1234/FE/7500"); } -// Test major version as 0xFF to see if it thorws an exception for using -// wildcard +// Test major version as 0xFF to see if it serializes without an exception for +// using wildcard TEST_F(TestUUriSerializer, SerializeUUriToStringWithMajorVersionWildCard) { v1::UUri testUUri; testUUri.set_authority_name("testAuthority"); testUUri.set_ue_id(0x12340000); testUUri.set_ue_version_major(0xFF); // Wildcard testUUri.set_resource_id(0x7500); - ASSERT_THROW(AsString::serialize(testUUri), std::invalid_argument); + std::string serialized; + ASSERT_NO_THROW(serialized = AsString::serialize(testUUri)); + ASSERT_EQ(serialized, "//testAuthority/12340000/FF/7500"); } // Test resource id as 0xFFFF to see if it thorws an exception for using @@ -100,7 +121,47 @@ TEST_F(TestUUriSerializer, SerializeUUriToStringWithResourceIDWildCard) { testUUri.set_ue_id(0x12340000); testUUri.set_ue_version_major(0xFE); testUUri.set_resource_id(0xFFFF); // Wildcard - ASSERT_THROW(AsString::serialize(testUUri), std::invalid_argument); + std::string serialized; + ASSERT_NO_THROW(serialized = AsString::serialize(testUUri)); + ASSERT_EQ(serialized, "//testAuthority/12340000/FE/FFFF"); +} + +// Attempt to serialize invalid UUris and verify exceptions are thrown +TEST_F(TestUUriSerializer, SerializeUUriToStringWithInvalidUUri) { + const v1::UUri baseUUri = []() { + v1::UUri uuri; + uuri.set_authority_name("testAuthority"); + uuri.set_ue_id(0x12340000); + uuri.set_ue_version_major(0xFE); + uuri.set_resource_id(0xFFFE); + return uuri; + }(); + + std::string serialized; + + // Empty UUri + v1::UUri testUUri; + ASSERT_THROW(serialized = AsString::serialize(testUUri), uri::InvalidUUri); + + // Authority name too long + testUUri = baseUUri; + testUUri.set_authority_name(std::string(129, 'b')); + ASSERT_THROW(serialized = AsString::serialize(testUUri), uri::InvalidUUri); + + // Version out of uint8 range + testUUri = baseUUri; + testUUri.set_ue_version_major(0x100); + ASSERT_THROW(serialized = AsString::serialize(testUUri), uri::InvalidUUri); + + // Version reserved + testUUri = baseUUri; + testUUri.set_ue_version_major(0); + ASSERT_THROW(serialized = AsString::serialize(testUUri), uri::InvalidUUri); + + // Resource ID out of uint16 range + testUUri = baseUUri; + testUUri.set_ue_version_major(0x10000); + ASSERT_THROW(serialized = AsString::serialize(testUUri), uri::InvalidUUri); } // Test deserialize by providing scheme "up:" which is allowed to have as per @@ -212,25 +273,103 @@ TEST_F(TestUUriSerializer, DeSerializeUUriStringWithInvalidArgument) { // Resource ID provided is invalid. It should be hex numeric uuriAsString = "//192.168.1.10/10010001/FE/xyz"; ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument); + + // UE ID is outside the 32-bit int range + uuriAsString = "//192.168.1.10/110010001/FE/7500"; + ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument); + + // Major Version is outside the 8-bit int range + uuriAsString = "//192.168.1.10/10010001/100/7500"; + ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument); + + // Resiource ID is outside the 16-bit int range + uuriAsString = "//192.168.1.10/10010001/FE/10000"; + ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument); } -// Test deserializing string with eildcard arguments to see if throws exception +// Test deserializing string with wildcard arguments to see if throws exception TEST_F(TestUUriSerializer, DeSerializeUUriStringWithWildcardArgument) { + uprotocol::v1::UUri uuri; + + auto check_uri = [&uuri](auto auth, uint32_t ueid, uint32_t mv, + uint32_t resid) { + ASSERT_EQ(uuri.authority_name(), auth); + ASSERT_EQ(uuri.ue_id(), ueid); + ASSERT_EQ(uuri.ue_version_major(), mv); + ASSERT_EQ(uuri.resource_id(), resid); + }; + + // Authority name provided in ueID is wildcard as "*" + std::string uuriAsString = "//*/1FFFF/FE/7500"; + ASSERT_NO_THROW(uuri = AsString::deserialize(uuriAsString)); + { + SCOPED_TRACE("uE Authority Name Wildcard"); + check_uri("*", 0x1FFFF, 0xFE, 0x7500); + } + // Service ID provided in ueID is wildcard as 0xFFFF - std::string uuriAsString = "//192.168.1.10/FFFF/FE/7500"; - ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument); + uuriAsString = "//192.168.1.10/1FFFF/FE/7500"; + ASSERT_NO_THROW(uuri = AsString::deserialize(uuriAsString)); + { + SCOPED_TRACE("uE Service ID Wildcard"); + check_uri("192.168.1.10", 0x1FFFF, 0xFE, 0x7500); + } // Instance ID provided in ueID is wildcard as 0x0 uuriAsString = "//192.168.1.10/00001234/FE/7500"; - ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument); + ASSERT_NO_THROW(uuri = AsString::deserialize(uuriAsString)); + { + SCOPED_TRACE("uE Instance ID Wildcard"); + check_uri("192.168.1.10", 0x1234, 0xFE, 0x7500); + } // Major Version provided is wildcard as 0xFF uuriAsString = "//192.168.1.10/10010001/FF/7500"; - ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument); + ASSERT_NO_THROW(uuri = AsString::deserialize(uuriAsString)); + { + SCOPED_TRACE("uE Major Version Wildcard"); + check_uri("192.168.1.10", 0x10010001, 0xFF, 0x7500); + } // Resource ID provided is wildcard as 0xFFFF uuriAsString = "//192.168.1.10/10010001/FE/FFFF"; - ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument); + ASSERT_NO_THROW(uuri = AsString::deserialize(uuriAsString)); + { + SCOPED_TRACE("uE Resource ID Wildcard"); + check_uri("192.168.1.10", 0x10010001, 0xFE, 0xFFFF); + } +} + +// Test deserializing string with invalid field values to verify exceptions are +// thrown +TEST_F(TestUUriSerializer, DeSerializeUUriStringWithInvalidUUri) { + uprotocol::v1::UUri uuri; + + // Major Version reserved + std::string uuriAsString = "//192.168.1.10/1FFFE/0/7500"; + ASSERT_THROW(uuri = AsString::deserialize(uuriAsString), uri::InvalidUUri); + + // Empty UUri + uuriAsString = "// /0/0/0"; + ASSERT_THROW(uuri = AsString::deserialize(uuriAsString), uri::InvalidUUri); + + // Major Version reserved + uuriAsString = "//"; + uuriAsString += std::string(129, 'a'); + uuriAsString += "/1FFFE/1/7500"; + ASSERT_THROW(uuri = AsString::deserialize(uuriAsString), uri::InvalidUUri); + + // NOTE These are also checked earlier against the std::invalid_argument + // exception. They can be caught either way. These can be checked against + // InvalidUUri since they are valid uint32 values, but *not* valid for UUri + + // Major Version outside the uint8 range + uuriAsString = "//192.168.1.10/1FFFE/FFFE/7500"; + ASSERT_THROW(uuri = AsString::deserialize(uuriAsString), uri::InvalidUUri); + + // Resource ID outside the uint8 range + uuriAsString = "//192.168.1.10/1FFFE/FE/C0FFEEEE"; + ASSERT_THROW(uuri = AsString::deserialize(uuriAsString), uri::InvalidUUri); } } // namespace diff --git a/test/coverage/datamodel/UUriValidatorTest.cpp b/test/coverage/datamodel/UUriValidatorTest.cpp index 997f5e40a..797beb4eb 100644 --- a/test/coverage/datamodel/UUriValidatorTest.cpp +++ b/test/coverage/datamodel/UUriValidatorTest.cpp @@ -483,7 +483,7 @@ TEST_F(TestUUriValidator, ValidDefaultSource) { uuri.set_authority_name(""); auto [valid, reason] = isValidDefaultSource(uuri); EXPECT_FALSE(valid); - EXPECT_TRUE(reason == Reason::EMPTY); + EXPECT_TRUE(reason == Reason::LOCAL_AUTHORITY); } } @@ -545,4 +545,145 @@ TEST_F(TestUUriValidator, Empty) { } } +TEST_F(TestUUriValidator, ValidFilter) { + auto getUuri = []() { + uprotocol::v1::UUri uuri; + uuri.set_authority_name("ValidFilterTest"); + uuri.set_ue_id(10001); + uuri.set_ue_version_major(1); + uuri.set_resource_id(1); + return uuri; + }; + + ////// GOOD ////// + { + // Plain URI + auto uuri = getUuri(); + auto [valid, reason] = isValidFilter(uuri); + EXPECT_TRUE(valid); + EXPECT_FALSE(reason.has_value()); + } + + { + // Wildcard URI + uprotocol::v1::UUri uuri; + uuri.set_authority_name("*"); + uuri.set_ue_id(0xFFFF); + uuri.set_ue_version_major(0xFF); + uuri.set_resource_id(0xFFFF); + auto [valid, reason] = isValidFilter(uuri); + EXPECT_TRUE(valid); + EXPECT_FALSE(reason.has_value()); + } + + ////// BAD ////// + { + // Empty URI + uprotocol::v1::UUri uuri; + auto [valid, reason] = isValidFilter(uuri); + EXPECT_FALSE(valid); + EXPECT_EQ(reason.value_or(static_cast(-1)), Reason::EMPTY); + } + + { + // Reserved Version + auto uuri = getUuri(); + uuri.set_ue_version_major(0); + auto [valid, reason] = isValidFilter(uuri); + EXPECT_FALSE(valid); + EXPECT_EQ(reason.value_or(static_cast(-1)), + Reason::RESERVED_VERSION); + } + + { + // Overflow Version + auto uuri = getUuri(); + uuri.set_ue_version_major(0x100); + auto [valid, reason] = isValidFilter(uuri); + EXPECT_FALSE(valid); + EXPECT_EQ(reason.value_or(static_cast(-1)), + Reason::VERSION_OVERFLOW); + } + + { + // Overflow Resource + auto uuri = getUuri(); + uuri.set_resource_id(0x10000); + auto [valid, reason] = isValidFilter(uuri); + EXPECT_FALSE(valid); + EXPECT_EQ(reason.value_or(static_cast(-1)), + Reason::RESOURCE_OVERFLOW); + } + + { + // Long Authority + auto uuri = getUuri(); + uuri.set_authority_name(std::string(129, 'i')); + auto [valid, reason] = isValidFilter(uuri); + EXPECT_FALSE(valid); + EXPECT_EQ(reason.value_or(static_cast(-1)), + Reason::AUTHORITY_TOO_LONG); + } +} + +TEST_F(TestUUriValidator, ReasonMessages) { + std::array all_reasons{Reason::EMPTY, + Reason::RESERVED_VERSION, + Reason::RESERVED_RESOURCE, + Reason::DISALLOWED_WILDCARD, + Reason::BAD_RESOURCE_ID, + Reason::LOCAL_AUTHORITY, + Reason::VERSION_OVERFLOW, + Reason::RESOURCE_OVERFLOW, + Reason::AUTHORITY_TOO_LONG}; + + std::set seen_messages; + + for (auto reason : all_reasons) { + EXPECT_NE(message(reason), message(static_cast(-1))); + EXPECT_EQ(seen_messages.count(message(reason)), 0); + + // Track that we have seen a message. Each reason's message should be + // unique so that the errors reported have meaningful distinctions. + seen_messages.insert(message(reason)); + + // Using a switch so we get a warning / error if a reason is missing + // from the test. These assertions don't really matter beyond that + // purpose (they should be tautologies). + // + // If you're here adding a reason, make sure it is also in the + // all_reasons array above. + switch (reason) { + case Reason::EMPTY: + EXPECT_EQ(message(reason), message(Reason::EMPTY)); + break; + case Reason::RESERVED_VERSION: + EXPECT_EQ(message(reason), message(Reason::RESERVED_VERSION)); + break; + case Reason::RESERVED_RESOURCE: + EXPECT_EQ(message(reason), message(Reason::RESERVED_RESOURCE)); + break; + case Reason::DISALLOWED_WILDCARD: + EXPECT_EQ(message(reason), + message(Reason::DISALLOWED_WILDCARD)); + break; + case Reason::BAD_RESOURCE_ID: + EXPECT_EQ(message(reason), message(Reason::BAD_RESOURCE_ID)); + break; + case Reason::LOCAL_AUTHORITY: + EXPECT_EQ(message(reason), message(Reason::LOCAL_AUTHORITY)); + break; + case Reason::VERSION_OVERFLOW: + EXPECT_EQ(message(reason), message(Reason::VERSION_OVERFLOW)); + break; + case Reason::RESOURCE_OVERFLOW: + EXPECT_EQ(message(reason), message(Reason::RESOURCE_OVERFLOW)); + break; + case Reason::AUTHORITY_TOO_LONG: + EXPECT_EQ(message(reason), message(Reason::AUTHORITY_TOO_LONG)); + break; + }; + } +} + } // namespace diff --git a/test/coverage/transport/UTransportTest.cpp b/test/coverage/transport/UTransportTest.cpp index 74decaced..0757c1fe4 100644 --- a/test/coverage/transport/UTransportTest.cpp +++ b/test/coverage/transport/UTransportTest.cpp @@ -9,46 +9,37 @@ // // SPDX-License-Identifier: Apache-2.0 -#include -#include #include #include -#include -#include +#include +#include +#include +#include -#include -#include -#include +#include "UTransportMock.h" -using MsgDiff = google::protobuf::util::MessageDifferencer; - -static std::random_device random_dev; -static std::mt19937 random_gen(random_dev()); -static std::uniform_int_distribution char_dist('A', 'z'); +namespace { -std::string get_random_string(size_t max_len = 32) { - std::uniform_int_distribution len_dist(1, static_cast(max_len)); - size_t len = len_dist(random_gen); - std::string retval; - retval.reserve(len); - for (size_t i = 0; i < len; i++) - retval += static_cast(char_dist(random_gen)); - return retval; +bool operator==(const uprotocol::v1::UUri& lhs, + const uprotocol::v1::UUri& rhs) { + using namespace google::protobuf::util; + return MessageDifferencer::Equals(lhs, rhs); } -int get_random_int(int mn = 0, int mx = 100) { - std::uniform_int_distribution int_dist(mn, mx); - return int_dist(random_gen); +bool operator==(const uprotocol::v1::UMessage& lhs, + const uprotocol::v1::UMessage& rhs) { + using namespace google::protobuf::util; + return MessageDifferencer::Equals(lhs, rhs); } -uprotocol::v1::UUID make_uuid() { - auto id = uprotocol::datamodel::builder::UuidBuilder::getBuilder().build(); - return id; +#if 0 +bool operator==(const uprotocol::v1::UStatus& lhs, + const uprotocol::v1::UCode& rhs) { + return lhs.code() == rhs; } +#endif -namespace { - -class TestMockUTransport : public testing::Test { +class TestUTransport : public testing::Test { protected: // Run once per TEST_F. // Used to set up clean environments per test. @@ -58,118 +49,519 @@ class TestMockUTransport : public testing::Test { // Run once per execution of the test application. // Used for setup of all tests. Has access to this instance. - TestMockUTransport() = default; - ~TestMockUTransport() = default; + TestUTransport() = default; + ~TestUTransport() = default; // Run once per execution of the test application. // Used only for global setup outside of tests. static void SetUpTestSuite() {} static void TearDownTestSuite() {} + + static std::shared_ptr makeMockTransport( + const uprotocol::v1::UUri& uri) { + return std::make_shared(uri); + } + + static std::shared_ptr makeTransport( + const uprotocol::v1::UUri& uri) { + return makeMockTransport(uri); + } + + static std::shared_ptr makeTransport( + std::shared_ptr mock) { + return mock; + } + + static uprotocol::v1::UUri getValidUri() { + uprotocol::v1::UUri uri; + uri.set_authority_name("UTransportTest"); + uri.set_ue_id(0xdeadbeef); + uri.set_ue_version_major(16); + uri.set_resource_id(0); + return uri; + } + + static uprotocol::v1::UUri getWildcardUri() { + uprotocol::v1::UUri uri; + uri.set_authority_name("*"); + uri.set_ue_id(0xFFFF); + uri.set_ue_version_major(0xFF); + uri.set_resource_id(0xFFFF); + return uri; + } }; -TEST_F(TestMockUTransport, Send) { - using namespace std; - - uprotocol::v1::UUri def_src_uuri; - def_src_uuri.set_authority_name(get_random_string()); - def_src_uuri.set_ue_id(0x18000); - def_src_uuri.set_ue_version_major(1); - def_src_uuri.set_resource_id(0); - - auto transport = - std::make_shared(def_src_uuri); - EXPECT_NE(nullptr, transport); - EXPECT_TRUE(MsgDiff::Equals(def_src_uuri, transport->getDefaultSource())); - - const size_t max_count = 1000 * 100; - for (size_t i = 0; i < max_count; i++) { - auto src = new uprotocol::v1::UUri(); - src->set_authority_name("10.0.0.1"); - src->set_ue_id(0x00010001); - src->set_ue_version_major(1); - src->set_resource_id(0x8000); - - // auto sink = new uprotocol::v1::UUri(); - // sink->set_authority_name("10.0.0.2"); - // sink->set_ue_id(0x00010002); - // sink->set_ue_version_major(2); - // sink->set_resource_id(2); - - auto attr = new uprotocol::v1::UAttributes(); - attr->set_type(uprotocol::v1::UMESSAGE_TYPE_PUBLISH); - *attr->mutable_id() = make_uuid(); - attr->set_allocated_source(src); - // attr->set_allocated_sink(sink); - attr->set_payload_format(uprotocol::v1::UPAYLOAD_FORMAT_PROTOBUF); - attr->set_ttl(1000); - - uprotocol::v1::UMessage msg; - msg.set_allocated_attributes(attr); - msg.set_payload(get_random_string(1400)); - transport->send_status_.set_code( - static_cast(15 - (i % 16))); - transport->send_status_.set_message(get_random_string()); - - auto result = transport->send(msg); - EXPECT_EQ(i + 1, transport->send_count_); - EXPECT_TRUE(MsgDiff::Equals(result, transport->send_status_)); - EXPECT_TRUE(MsgDiff::Equals(msg, transport->message_)); - } -} - -TEST_F(TestMockUTransport, registerListener) { - using namespace std; - - uprotocol::v1::UUri def_src_uuri; - def_src_uuri.set_authority_name(get_random_string()); - def_src_uuri.set_ue_id(0x18000); - def_src_uuri.set_ue_version_major(1); - def_src_uuri.set_resource_id(0); - - auto transport = - std::make_shared(def_src_uuri); - EXPECT_NE(nullptr, transport); - EXPECT_TRUE(MsgDiff::Equals(def_src_uuri, transport->getDefaultSource())); - - uprotocol::v1::UUri sink_filter; - sink_filter.set_authority_name(get_random_string()); - sink_filter.set_ue_id(0x00010001); - sink_filter.set_ue_version_major(1); - sink_filter.set_resource_id(0x8000); - - uprotocol::v1::UUri source_filter; - source_filter.set_authority_name(get_random_string()); - source_filter.set_ue_id(0x00010001); - source_filter.set_ue_version_major(1); - source_filter.set_resource_id(0x8000); - - uprotocol::v1::UMessage capture_msg; - size_t capture_count = 0; - auto action = [&](const uprotocol::v1::UMessage& msg) { - capture_msg = msg; - capture_count++; - }; - auto lhandle = - transport->registerListener(sink_filter, action, source_filter); - EXPECT_TRUE(transport->listener_); - // EXPECT_EQ(*mock_info.listener, action); // need exposed target_type() to - // make comparable. - EXPECT_TRUE(lhandle.has_value()); - auto handle = std::move(lhandle).value(); +TEST_F(TestUTransport, CreateTransport) { + EXPECT_NO_THROW(auto transport = makeTransport(getValidUri())); +} + +using InvalidUUri = uprotocol::datamodel::validator::uri::InvalidUUri; + +TEST_F(TestUTransport, CreateTransportInvalidUUri) { + auto uri = getValidUri(); + uri.set_authority_name("*"); + EXPECT_THROW({ auto transport = makeTransport(uri); }, InvalidUUri); +} + +using UMessageBuilder = uprotocol::datamodel::builder::UMessageBuilder; +using PayloadBuilder = uprotocol::datamodel::builder::Payload; + +TEST_F(TestUTransport, SendOk) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + auto topic = getValidUri(); + topic.set_resource_id(0xABBA); + PayloadBuilder payload(std::string("[\"Arrival\", \"Waterloo\"]"), + uprotocol::v1::UPayloadFormat::UPAYLOAD_FORMAT_JSON); + auto message = + UMessageBuilder::publish(std::move(topic)).build(std::move(payload)); + + decltype(transport->send(message)) result; + EXPECT_NO_THROW(result = transport->send(message)); + + EXPECT_EQ(result.code(), uprotocol::v1::UCode::OK); + EXPECT_EQ(transport_mock->send_count_, 1); + EXPECT_TRUE(transport_mock->message_ == message); +} + +using InvalidUMessge = + uprotocol::datamodel::validator::message::InvalidUMessage; + +TEST_F(TestUTransport, SendInvalidMessage) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + auto topic = getValidUri(); + topic.set_resource_id(0xF00D); + auto message = UMessageBuilder::publish(std::move(topic)).build(); + message.mutable_attributes()->set_type( + uprotocol::v1::UMessageType::UMESSAGE_TYPE_REQUEST); + + decltype(transport->send(message)) result; + EXPECT_THROW({ result = transport->send(message); }, InvalidUMessge); + EXPECT_EQ(transport_mock->send_count_, 0); +} + +TEST_F(TestUTransport, SendImplStatus) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + transport_mock->send_status_.set_code( + uprotocol::v1::UCode::PERMISSION_DENIED); + + auto topic = getValidUri(); + topic.set_resource_id(0xF00D); + auto message = UMessageBuilder::publish(std::move(topic)).build(); + + decltype(transport->send(message)) result; + EXPECT_NO_THROW(result = transport->send(message)); + + EXPECT_EQ(result.code(), uprotocol::v1::UCode::PERMISSION_DENIED); +} + +TEST_F(TestUTransport, RegisterListenerOk) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto source_filter = getWildcardUri(); + + uprotocol::transport::UTransport::ListenHandle handle; + EXPECT_NO_THROW({ + auto maybe_handle = + transport->registerListener(callback, source_filter); + + EXPECT_TRUE(maybe_handle); + if (maybe_handle) { + handle = std::move(maybe_handle).value(); + } + }); + EXPECT_TRUE(handle); - EXPECT_TRUE(MsgDiff::Equals(sink_filter, transport->sink_filter_)); - EXPECT_TRUE(transport->source_filter_); - EXPECT_TRUE(MsgDiff::Equals(source_filter, *transport->source_filter_)); - - const size_t max_count = 1000 * 100; - for (size_t i = 0; i < max_count; i++) { - uprotocol::v1::UMessage msg; - auto attr = new uprotocol::v1::UAttributes(); - msg.set_allocated_attributes(attr); - msg.set_payload(get_random_string(1400)); - transport->mockMessage(msg); - EXPECT_EQ(i + 1, capture_count); - EXPECT_TRUE(MsgDiff::Equals(msg, capture_msg)); + + EXPECT_FALSE(transport_mock->sink_filter_); + EXPECT_TRUE(source_filter == transport_mock->source_filter_); + EXPECT_TRUE(transport_mock->listener_); + if (transport_mock->listener_) { + auto callable = transport_mock->listener_.value(); + EXPECT_FALSE(called); + auto topic = getValidUri(); + topic.set_resource_id(0xF00D); + auto message = UMessageBuilder::publish(std::move(topic)).build(); + callable(message); + EXPECT_TRUE(called); + } +} + +TEST_F(TestUTransport, RegisterListenerInvalidSource) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto source_filter = getWildcardUri(); + source_filter.set_resource_id(1); + + EXPECT_THROW( + { + auto maybe_handle = + transport->registerListener(callback, source_filter); + }, + InvalidUUri); + + // Did not attempt to register a callback + EXPECT_FALSE(transport_mock->sink_filter_); + EXPECT_FALSE(transport_mock->listener_); + EXPECT_FALSE(called); +} + +TEST_F(TestUTransport, RegisterListenerImplStatus) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + transport_mock->registerListener_status_.set_code( + uprotocol::v1::UCode::INTERNAL); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto source_filter = getWildcardUri(); + + uprotocol::v1::UStatus status; + EXPECT_NO_THROW({ + auto maybe_handle = + transport->registerListener(callback, source_filter); + + EXPECT_FALSE(maybe_handle); + if (!maybe_handle) { + status = maybe_handle.error(); + } + }); + + // The listener that was sent to the impl is not connected + EXPECT_TRUE(transport_mock->listener_); + if (transport_mock->listener_) { + auto callable = transport_mock->listener_.value(); + EXPECT_FALSE(callable); + auto topic = getValidUri(); + topic.set_resource_id(0xF00D); + auto message = UMessageBuilder::publish(std::move(topic)).build(); + callable(message); + EXPECT_FALSE(called); + } +} + +TEST_F(TestUTransport, RegisterListenerWithSinkFilterOk) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto source_filter = getWildcardUri(); + auto sink_filter = getValidUri(); + sink_filter.set_resource_id(0xFFFF); + + uprotocol::transport::UTransport::ListenHandle handle; + EXPECT_NO_THROW({ + auto maybe_handle = + transport->registerListener(callback, source_filter, sink_filter); + + EXPECT_TRUE(maybe_handle); + if (maybe_handle) { + handle = std::move(maybe_handle).value(); + } + }); + + EXPECT_TRUE(handle); + + EXPECT_TRUE(transport_mock->sink_filter_); + if (transport_mock->sink_filter_) { + EXPECT_TRUE(sink_filter == transport_mock->sink_filter_.value()); + } + + EXPECT_TRUE(source_filter == transport_mock->source_filter_); + EXPECT_TRUE(transport_mock->listener_); + if (transport_mock->listener_) { + auto callable = transport_mock->listener_.value(); + EXPECT_FALSE(called); + auto topic = getValidUri(); + topic.set_resource_id(0xF00D); + auto message = UMessageBuilder::publish(std::move(topic)).build(); + callable(message); + EXPECT_TRUE(called); + } +} + +TEST_F(TestUTransport, RegisterListenerWithSinkFilterInvalidSource) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto source_filter = getWildcardUri(); + auto sink_filter = getValidUri(); + sink_filter.set_resource_id(0xFFFF); + + // Make source invalid + source_filter.set_ue_version_major(0xFFFF); + + EXPECT_THROW( + { + auto maybe_handle = transport->registerListener( + callback, source_filter, sink_filter); + }, + InvalidUUri); + + // Did not attempt to register a callback + EXPECT_FALSE(transport_mock->sink_filter_); + EXPECT_FALSE(transport_mock->listener_); + EXPECT_FALSE(called); +} + +TEST_F(TestUTransport, RegisterListenerWithSinkFilterInvalidSink) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto source_filter = getWildcardUri(); + auto sink_filter = getValidUri(); + sink_filter.set_resource_id(0xFFFF); + + // Make sink invalid + sink_filter.set_ue_version_major(0xFFFF); + + EXPECT_THROW( + { + auto maybe_handle = transport->registerListener( + callback, source_filter, sink_filter); + }, + InvalidUUri); + + // Did not attempt to register a callback + EXPECT_FALSE(transport_mock->sink_filter_); + EXPECT_FALSE(transport_mock->listener_); + EXPECT_FALSE(called); +} + +TEST_F(TestUTransport, RegisterListenerWithSinkFilterImplStatus) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + transport_mock->registerListener_status_.set_code( + uprotocol::v1::UCode::NOT_FOUND); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto source_filter = getWildcardUri(); + auto sink_filter = getValidUri(); + sink_filter.set_resource_id(0xFFFF); + + uprotocol::v1::UStatus status; + EXPECT_NO_THROW({ + auto maybe_handle = + transport->registerListener(callback, source_filter, sink_filter); + + EXPECT_FALSE(maybe_handle); + if (!maybe_handle) { + status = maybe_handle.error(); + } + }); + + // The listener that was sent to the impl is not connected + EXPECT_TRUE(transport_mock->listener_); + if (transport_mock->listener_) { + auto callable = transport_mock->listener_.value(); + EXPECT_FALSE(callable); + auto topic = getValidUri(); + topic.set_resource_id(0xF00D); + auto message = UMessageBuilder::publish(std::move(topic)).build(); + callable(message); + EXPECT_FALSE(called); + } +} + +TEST_F(TestUTransport, RegisterListenerWithSinkResourceOk) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto source_filter = getWildcardUri(); + + uprotocol::transport::UTransport::ListenHandle handle; + EXPECT_NO_THROW({ + auto maybe_handle = + transport->registerListener(callback, source_filter, 0xF00D); + + EXPECT_TRUE(maybe_handle); + if (maybe_handle) { + handle = std::move(maybe_handle).value(); + } + }); + + EXPECT_TRUE(handle); + + EXPECT_TRUE(transport_mock->sink_filter_); + if (transport_mock->sink_filter_) { + auto sink_filter = getValidUri(); + sink_filter.set_resource_id(0xF00D); + EXPECT_TRUE(sink_filter == transport_mock->sink_filter_.value()); + } + + EXPECT_TRUE(source_filter == transport_mock->source_filter_); + EXPECT_TRUE(transport_mock->listener_); + if (transport_mock->listener_) { + auto callable = transport_mock->listener_.value(); + EXPECT_FALSE(called); + auto topic = getValidUri(); + topic.set_resource_id(0xF00D); + auto message = UMessageBuilder::publish(std::move(topic)).build(); + callable(message); + EXPECT_TRUE(called); + } +} + +TEST_F(TestUTransport, RegisterListenerWithSinkResourceInvalidSource) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto source_filter = getWildcardUri(); + + // Make source invalid + source_filter.set_ue_version_major(0xFFFF); + + EXPECT_THROW( + { + auto maybe_handle = + transport->registerListener(callback, source_filter, 0xABBA); + }, + InvalidUUri); + + // Did not attempt to register a callback + EXPECT_FALSE(transport_mock->sink_filter_); + EXPECT_FALSE(transport_mock->listener_); + EXPECT_FALSE(called); +} + +// NOTE: it is not possible to produce an invalid sink filter with this method +// since it constrains the sink resource parameter to uint16_t + +TEST_F(TestUTransport, RegisterListenerWithSinkResourceImplStatus) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + transport_mock->registerListener_status_.set_code( + uprotocol::v1::UCode::NOT_FOUND); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto source_filter = getWildcardUri(); + + uprotocol::v1::UStatus status; + EXPECT_NO_THROW({ + auto maybe_handle = + transport->registerListener(callback, source_filter, 0xFFFF); + + EXPECT_FALSE(maybe_handle); + if (!maybe_handle) { + status = maybe_handle.error(); + } + }); + + // The listener that was sent to the impl is not connected + EXPECT_TRUE(transport_mock->listener_); + if (transport_mock->listener_) { + auto callable = transport_mock->listener_.value(); + // The callback isn't connected... + EXPECT_FALSE(callable); + // ...but we're still going to try to call it anyway + auto topic = getValidUri(); + topic.set_resource_id(0xF00D); + auto message = UMessageBuilder::publish(std::move(topic)).build(); + callable(message); + EXPECT_FALSE(called); + } +} + +TEST_F(TestUTransport, DeprecatedRegisterListenerOk) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto topic_source_filter = getWildcardUri(); + + uprotocol::transport::UTransport::ListenHandle handle; + EXPECT_NO_THROW({ + // When source_filter is omitted, sink_filter is treated as a publish + // topic. + auto maybe_handle = + transport->registerListener(topic_source_filter, callback, {}); + + EXPECT_TRUE(maybe_handle); + if (maybe_handle) { + handle = std::move(maybe_handle).value(); + } + }); + + EXPECT_TRUE(handle); + + EXPECT_FALSE(transport_mock->sink_filter_); + EXPECT_TRUE(topic_source_filter == transport_mock->source_filter_); + EXPECT_TRUE(transport_mock->listener_); + if (transport_mock->listener_) { + auto callable = transport_mock->listener_.value(); + EXPECT_FALSE(called); + auto topic = getValidUri(); + topic.set_resource_id(0xF00D); + auto message = UMessageBuilder::publish(std::move(topic)).build(); + callable(message); + EXPECT_TRUE(called); + } +} + +TEST_F(TestUTransport, DeprecatedRegisterListenerWithSourceFilterOk) { + auto transport_mock = makeMockTransport(getValidUri()); + auto transport = makeTransport(transport_mock); + + bool called = false; + auto callback = [&called](const auto&) { called = true; }; + auto source_filter = getWildcardUri(); + auto sink_filter = getValidUri(); + sink_filter.set_resource_id(0xFFFF); + + uprotocol::transport::UTransport::ListenHandle handle; + EXPECT_NO_THROW({ + auto maybe_handle = + transport->registerListener(sink_filter, callback, source_filter); + + EXPECT_TRUE(maybe_handle); + if (maybe_handle) { + handle = std::move(maybe_handle).value(); + } + }); + + EXPECT_TRUE(handle); + + EXPECT_TRUE(transport_mock->sink_filter_); + if (transport_mock->sink_filter_) { + EXPECT_TRUE(sink_filter == transport_mock->sink_filter_.value()); + } + + EXPECT_TRUE(source_filter == transport_mock->source_filter_); + EXPECT_TRUE(transport_mock->listener_); + if (transport_mock->listener_) { + auto callable = transport_mock->listener_.value(); + EXPECT_FALSE(called); + auto topic = getValidUri(); + topic.set_resource_id(0xF00D); + auto message = UMessageBuilder::publish(std::move(topic)).build(); + callable(message); + EXPECT_TRUE(called); } } diff --git a/test/extra/UTransportMockTest.cpp b/test/extra/UTransportMockTest.cpp new file mode 100644 index 000000000..349486193 --- /dev/null +++ b/test/extra/UTransportMockTest.cpp @@ -0,0 +1,176 @@ +// 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 +#include +#include + +using MsgDiff = google::protobuf::util::MessageDifferencer; + +static std::random_device random_dev; +static std::mt19937 random_gen(random_dev()); +static std::uniform_int_distribution char_dist('A', 'z'); + +std::string get_random_string(size_t max_len = 32) { + std::uniform_int_distribution len_dist(1, static_cast(max_len)); + size_t len = len_dist(random_gen); + std::string retval; + retval.reserve(len); + for (size_t i = 0; i < len; i++) + retval += static_cast(char_dist(random_gen)); + return retval; +} + +int get_random_int(int mn = 0, int mx = 100) { + std::uniform_int_distribution int_dist(mn, mx); + return int_dist(random_gen); +} + +uprotocol::v1::UUID make_uuid() { + auto id = uprotocol::datamodel::builder::UuidBuilder::getBuilder().build(); + return id; +} + +namespace { + +class TestMockUTransport : public testing::Test { +protected: + // Run once per TEST_F. + // Used to set up clean environments per test. + void SetUp() override {} + + void TearDown() override {} + + // Run once per execution of the test application. + // Used for setup of all tests. Has access to this instance. + TestMockUTransport() = default; + ~TestMockUTransport() = default; + + // Run once per execution of the test application. + // Used only for global setup outside of tests. + static void SetUpTestSuite() {} + static void TearDownTestSuite() {} +}; + +TEST_F(TestMockUTransport, Send) { + using namespace std; + + uprotocol::v1::UUri def_src_uuri; + def_src_uuri.set_authority_name(get_random_string()); + def_src_uuri.set_ue_id(0x18000); + def_src_uuri.set_ue_version_major(1); + def_src_uuri.set_resource_id(0); + + auto transport = + std::make_shared(def_src_uuri); + EXPECT_NE(nullptr, transport); + EXPECT_TRUE(MsgDiff::Equals(def_src_uuri, transport->getDefaultSource())); + + const size_t max_count = 1000 * 100; + for (size_t i = 0; i < max_count; i++) { + auto src = new uprotocol::v1::UUri(); + src->set_authority_name("10.0.0.1"); + src->set_ue_id(0x00010001); + src->set_ue_version_major(1); + src->set_resource_id(0x8000); + + // auto sink = new uprotocol::v1::UUri(); + // sink->set_authority_name("10.0.0.2"); + // sink->set_ue_id(0x00010002); + // sink->set_ue_version_major(2); + // sink->set_resource_id(2); + + auto attr = new uprotocol::v1::UAttributes(); + attr->set_type(uprotocol::v1::UMESSAGE_TYPE_PUBLISH); + *attr->mutable_id() = make_uuid(); + attr->set_allocated_source(src); + // attr->set_allocated_sink(sink); + attr->set_payload_format(uprotocol::v1::UPAYLOAD_FORMAT_PROTOBUF); + attr->set_ttl(1000); + + uprotocol::v1::UMessage msg; + msg.set_allocated_attributes(attr); + msg.set_payload(get_random_string(1400)); + transport->send_status_.set_code( + static_cast(15 - (i % 16))); + transport->send_status_.set_message(get_random_string()); + + auto result = transport->send(msg); + EXPECT_EQ(i + 1, transport->send_count_); + EXPECT_TRUE(MsgDiff::Equals(result, transport->send_status_)); + EXPECT_TRUE(MsgDiff::Equals(msg, transport->message_)); + } +} + +TEST_F(TestMockUTransport, registerListener) { + using namespace std; + + uprotocol::v1::UUri def_src_uuri; + def_src_uuri.set_authority_name(get_random_string()); + def_src_uuri.set_ue_id(0x18000); + def_src_uuri.set_ue_version_major(1); + def_src_uuri.set_resource_id(0); + + auto transport = + std::make_shared(def_src_uuri); + EXPECT_NE(nullptr, transport); + EXPECT_TRUE(MsgDiff::Equals(def_src_uuri, transport->getDefaultSource())); + + uprotocol::v1::UUri sink_filter; + sink_filter.set_authority_name(get_random_string()); + sink_filter.set_ue_id(0x00010001); + sink_filter.set_ue_version_major(1); + sink_filter.set_resource_id(0x8000); + + uprotocol::v1::UUri source_filter; + source_filter.set_authority_name(get_random_string()); + source_filter.set_ue_id(0x00010001); + source_filter.set_ue_version_major(1); + source_filter.set_resource_id(0x8000); + + uprotocol::v1::UMessage capture_msg; + size_t capture_count = 0; + auto action = [&](const uprotocol::v1::UMessage& msg) { + capture_msg = msg; + capture_count++; + }; + auto lhandle = + transport->registerListener(sink_filter, action, source_filter); + EXPECT_TRUE(transport->listener_); + // EXPECT_EQ(*mock_info.listener, action); // need exposed target_type() to + // make comparable. + EXPECT_TRUE(lhandle.has_value()); + auto handle = std::move(lhandle).value(); + EXPECT_TRUE(handle); + EXPECT_TRUE(transport->sink_filter_); + EXPECT_TRUE(MsgDiff::Equals(sink_filter, *transport->sink_filter_)); + EXPECT_TRUE(MsgDiff::Equals(source_filter, transport->source_filter_)); + + const size_t max_count = 1000 * 100; + for (size_t i = 0; i < max_count; i++) { + uprotocol::v1::UMessage msg; + auto attr = new uprotocol::v1::UAttributes(); + msg.set_allocated_attributes(attr); + msg.set_payload(get_random_string(1400)); + transport->mockMessage(msg); + EXPECT_EQ(i + 1, capture_count); + EXPECT_TRUE(MsgDiff::Equals(msg, capture_msg)); + } +} + +} // namespace diff --git a/test/include/UTransportMock.h b/test/include/UTransportMock.h index 1791858b2..29129e6c3 100644 --- a/test/include/UTransportMock.h +++ b/test/include/UTransportMock.h @@ -44,13 +44,15 @@ class UTransportMock : public uprotocol::transport::UTransport { std::optional> cleanup_listener_; - std::optional source_filter_; - v1::UUri sink_filter_; + std::optional sink_filter_; + v1::UUri source_filter_; std::mutex register_mtx_; v1::UMessage message_; std::mutex message_mtx_; + virtual ~UTransportMock() = default; + private: [[nodiscard]] v1::UStatus sendImpl(const v1::UMessage& message) override { { @@ -62,8 +64,8 @@ class UTransportMock : public uprotocol::transport::UTransport { } [[nodiscard]] v1::UStatus registerListenerImpl( - const v1::UUri& sink_filter, CallableConn&& listener, - std::optional&& source_filter) override { + CallableConn&& listener, const v1::UUri& source_filter, + std::optional&& sink_filter) override { std::lock_guard lock(register_mtx_); listener_ = listener; source_filter_ = source_filter;