From 421072dea47846ad39e5ace5abd3b50a028bf6cf Mon Sep 17 00:00:00 2001 From: Kai Hudalla Date: Tue, 16 Sep 2025 10:58:14 +0200 Subject: [PATCH] [#194] Update to up-spec v1.6.0.alpha-5 Amended code with OpenFastTrace specitems and added configuration for tracing requirements based on up-spec v1.6.0.alpha-5. Added Cucumber based tests that implement the scenarios defined by Gherkin feature files that had been added in v1.6.0.alpha-5. Removed obsolete files and improved cohesion and readability of unit tests. Fixes #194 --- .env.oft-current | 6 +- .env.oft-latest | 2 +- .github/workflows/build_and_test.yml | 9 + .github/workflows/coverage.yml | 1 + .github/workflows/lint.yml | 1 + README.adoc | 34 ++- pom.xml | 34 ++- .../usubscription/v3/USubscriptionClient.java | 2 +- .../AbstractCommunicationLayerClient.java | 1 + .../uprotocol/communication/CallOptions.java | 1 + .../communication/InMemoryRpcClient.java | 1 + .../communication/InMemoryRpcServer.java | 1 + .../communication/InMemorySubscriber.java | 1 + .../uprotocol/communication/Notifier.java | 3 +- .../uprotocol/communication/Publisher.java | 3 +- .../communication/RequestHandler.java | 1 + .../uprotocol/communication/RpcClient.java | 3 +- .../uprotocol/communication/RpcServer.java | 3 +- .../communication/SimpleNotifier.java | 1 + .../communication/SimplePublisher.java | 1 + .../uprotocol/communication/Subscriber.java | 3 +- .../SubscriptionChangeHandler.java | 1 + .../uprotocol/communication/UClient.java | 1 + .../uprotocol/communication/UPayload.java | 1 + .../communication/UStatusException.java | 1 + .../uprotocol/communication/package-info.java | 12 + .../uprotocol/transport/LocalUriProvider.java | 1 + .../uprotocol/transport/UListener.java | 4 +- .../uprotocol/transport/UTransport.java | 7 +- .../transport/builder/UMessageBuilder.java | 24 +- .../validator/UAttributesValidator.java | 7 +- .../uri/serializer/UriSerializer.java | 71 +++++- .../uprotocol/uri/validator/UriValidator.java | 1 + .../uprotocol/uuid/factory/UuidFactory.java | 87 ++----- .../uprotocol/uuid/factory/UuidUtils.java | 162 ++++++------ .../uuid/serializer/UuidSerializer.java | 56 +++-- .../uuid/validate/UuidValidator.java | 113 --------- .../eclipse/uprotocol/v1/package-info.java | 25 ++ .../communication/CallOptionsTest.java | 2 +- .../CommunicationLayerClientTestBase.java | 1 + .../communication/InMemoryRpcClientTest.java | 3 +- .../communication/InMemoryRpcServerTest.java | 1 + .../communication/InMemorySubscriberTest.java | 3 +- .../communication/RpcServerTest.java | 89 ------- .../communication/SimpleNotifierTest.java | 1 + .../communication/SimplePublisherTest.java | 2 +- .../uprotocol/communication/UClientTest.java | 1 + .../uprotocol/communication/UPayloadTest.java | 2 +- .../communication/UStatusExceptionTest.java | 2 +- .../transport/StaticUriProviderTest.java | 64 +++++ .../builder/UMessageBuilderTest.java | 84 ++++++- .../validator/UAttributesValidatorTest.java | 175 ++++++++----- .../org/eclipse/uprotocol/uri/UuriTests.java | 157 ++++++++++++ .../uri/serializer/UriSerializerTest.java | 136 ++--------- .../uri/validator/UriValidatorTest.java | 77 ++++-- .../org/eclipse/uprotocol/uuid/UuidTests.java | 113 +++++++++ .../uuid/factory/UUIDFactoryTest.java | 231 ------------------ .../uprotocol/uuid/factory/UuidUtilsTest.java | 183 ++++++++------ .../uuid/serializer/UuidSerializerTest.java | 33 --- .../uuid/validator/UuidValidatorTest.java | 152 ------------ .../org/eclipse/uprotocol/v1/UStatusTest.java | 56 +++++ .../features/uuri_pattern_matching.feature | 71 ++++++ .../features/uuri_uri_serialization.feature | 88 +++++++ up-spec | 2 +- 64 files changed, 1275 insertions(+), 1140 deletions(-) create mode 100644 src/main/java/org/eclipse/uprotocol/communication/package-info.java delete mode 100644 src/main/java/org/eclipse/uprotocol/uuid/validate/UuidValidator.java create mode 100644 src/main/java/org/eclipse/uprotocol/v1/package-info.java delete mode 100644 src/test/java/org/eclipse/uprotocol/communication/RpcServerTest.java create mode 100644 src/test/java/org/eclipse/uprotocol/transport/StaticUriProviderTest.java create mode 100644 src/test/java/org/eclipse/uprotocol/uri/UuriTests.java create mode 100644 src/test/java/org/eclipse/uprotocol/uuid/UuidTests.java delete mode 100644 src/test/java/org/eclipse/uprotocol/uuid/factory/UUIDFactoryTest.java delete mode 100644 src/test/java/org/eclipse/uprotocol/uuid/serializer/UuidSerializerTest.java delete mode 100644 src/test/java/org/eclipse/uprotocol/uuid/validator/UuidValidatorTest.java create mode 100644 src/test/java/org/eclipse/uprotocol/v1/UStatusTest.java create mode 100644 src/test/resources/features/uuri_pattern_matching.feature create mode 100644 src/test/resources/features/uuri_uri_serialization.feature diff --git a/.env.oft-current b/.env.oft-current index 665a4aec..08a89910 100644 --- a/.env.oft-current +++ b/.env.oft-current @@ -13,11 +13,11 @@ # The file patterns that specify the relevant parts of the latest uProtocol Specification # that this component is supposed to implement -UP_SPEC_FILE_PATTERNS="up-spec/*.adoc up-spec/*.md up-spec/basics" +UP_SPEC_FILE_PATTERNS="up-spec/*.adoc up-spec/*.md up-spec/basics up-spec/up-l1/README.adoc up-spec/up-l2/api.adoc" # The file patterns that specify this component's resources which contain specification items # that cover the requirements -COMPONENT_FILE_PATTERNS="*.adoc *.md *.java pom.xml *.feature .github src" +COMPONENT_FILE_PATTERNS="*.adoc *.md .github src" OFT_FILE_PATTERNS="$UP_SPEC_FILE_PATTERNS $COMPONENT_FILE_PATTERNS" -OFT_TAGS="_" +OFT_TAGS="_,LanguageLibrary" diff --git a/.env.oft-latest b/.env.oft-latest index 9da0dc10..08a89910 100644 --- a/.env.oft-latest +++ b/.env.oft-latest @@ -17,7 +17,7 @@ UP_SPEC_FILE_PATTERNS="up-spec/*.adoc up-spec/*.md up-spec/basics up-spec/up-l1/ # The file patterns that specify this component's resources which contain specification items # that cover the requirements -COMPONENT_FILE_PATTERNS="*.adoc *.md *.java pom.xml *.feature .github src" +COMPONENT_FILE_PATTERNS="*.adoc *.md .github src" OFT_FILE_PATTERNS="$UP_SPEC_FILE_PATTERNS $COMPONENT_FILE_PATTERNS" OFT_TAGS="_,LanguageLibrary" diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index a0613954..0f870781 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -14,11 +14,16 @@ name: Verify PR on: + # the repository is configured to require a PR for + # making changes to the main branch + # so every check made here is effectively + # made before merging to main pull_request: branches: - '*' jobs: + # [impl->req~up-language-ci-build~1] verify-pr: runs-on: ubuntu-latest @@ -33,4 +38,8 @@ jobs: distribution: 'temurin' cache: maven - name: Build and Test with Maven + # the package goal includes running the tests + # [impl->req~up-language-ci-test~1] + # and also builds the JavaDocs + # [impl->req~up-language-ci-api-docs~1] run: mvn -B package --file pom.xml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 6d8a33d8..d90c5563 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -19,6 +19,7 @@ on: - main jobs: + # req~up-language-test-coverage~1 test-and-coverage: name: Test with coverage runs-on: ubuntu-latest diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 570a17f5..3a5e7e46 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,6 +20,7 @@ on: branches: [main] jobs: + # [impl->req~up-language-ci-linter~1] lint: runs-on: ubuntu-latest diff --git a/README.adoc b/README.adoc index 581773a7..92db57c6 100644 --- a/README.adoc +++ b/README.adoc @@ -1,22 +1,26 @@ = Eclipse uProtocol Java Library :toc: +[.specitem,oft-sid="uman~up-java-readme~1",oft-covers="req~up-language-documentation~1",tags="LanguageLibrary"] == Overview -This is the https://github.com/eclipse-uprotocol/uprotocol-spec/blob/v1.6.0-alpha.4/languages.adoc[uProtocol v1.6.0-alpha.4 Language Library] for the Java programming language. The library is organized into packages that are described in <> below and organized by the layers of the protocol. +This is the https://github.com/eclipse-uprotocol/uprotocol-spec/blob/v1.6.0-alpha.5/languages.adoc[uProtocol v1.6.0-alpha.5 Language Library] for the Java programming language. The library is organized into packages that are described in <> below and organized by the layers of the protocol. -Each package contains a README.adoc file that describes the purpose of the package and how to use it. - -The module contains the factory methods, serializers, and validators for all data types defined in the specifications, and any data models that either haven't or couldn't be defined in up-core-api yet. +Each package folder contains a `README.adoc` file that describes the purpose of the package and how to use it. == Getting Started -=== Importing the Library - -To pull the Library from maven central, setting ${uprotocol.version} to the latest version of this library in your pom.xml file: -[source] +=== Adding the Library + +The library is available from [Maven Central](https://search.maven.org/artifact/org.eclipse.uprotocol/up-java) and can be added as a dependency to your Maven or Gradle project. + +[.specitem,oft-sid="impl~up-java-deps-resolution~1",oft-covers="req~up-language-build-deps~1",tags="LanguageLibrary"] +---- +The following dependency needs to be added to your Maven POM file, setting `${uprotocol.version}` to the desired version of the library: +---- + +[source,xml] ---- - org.eclipse.uprotocol up-java @@ -34,21 +38,20 @@ To pull the Library from maven central, setting ${uprotocol.version} to the late | xref:src/main/java/org/eclipse/uprotocol/client/README.adoc[`*client*`] | Top level client-facing interfaces to communication with USubscription, UDiscovery, and UTwin services. -| https://github.com/eclipse-uprotocol/up-spec/blob/v1.6.0-alpha.4/up-l3/README.adoc[Application Layer (uP-L3)] +| https://github.com/eclipse-uprotocol/up-spec/blob/v1.6.0-alpha.5/up-l3/README.adoc[Application Layer (uP-L3)] | xref:src/main/java/org/eclipse/uprotocol/communication/README.adoc[`*communication*`] | Common implementation of communication messaging patterns (publisher, subscriber, RpcClient, RpcServer, etc..) that are built on top of the L1 transport interface (see below). -| https://github.com/eclipse-uprotocol/up-spec/blob/v1.6.0-alpha.4/up-l2/api.adoc[Communication Layer (uP-L2)] +| https://github.com/eclipse-uprotocol/up-spec/blob/v1.6.0-alpha.5/up-l2/api.adoc[Communication Layer (uP-L2)] | xref:src/main/java/org/eclipse/uprotocol/transport/README.adoc[`*transport*`] | Interface and data model for how to send() and receive() messages in a common way across various transport technologies (ex. zenoh, mqtt, http, etc...). the interface is implemented by transports (ex. up-transport-android-java), and the interface is then used to build the uProtocol layer 2 communication layer implementation. -| https://github.com/eclipse-uprotocol/uprotocol-spec/blob/v1.6.0-alpha.4/up-l1/README.adoc[Transport Layer (uP-L1)] +| https://github.com/eclipse-uprotocol/uprotocol-spec/blob/v1.6.0-alpha.5/up-l1/README.adoc[Transport Layer (uP-L1)] | xref:src/main/java/org/eclipse/uprotocol/uri/README.adoc[`*uuri*`] | uProtocol addressing scheme (UUri) builders, validators, and serializers. | Basics - | xref:src/main/java/org/eclipse/uprotocol/uuid/README.adoc[`*uuid*`] | uProtocol unique identifier builders, validators, and serializers. | Basics @@ -57,6 +60,11 @@ To pull the Library from maven central, setting ${uprotocol.version} to the late === Building from Source +[.specitem,oft-sid="impl~up-java-build-system~1",oft-covers="req~up-language-build-sys~1",tags="LanguageLibrary"] +---- +The library is built using the [Apache Maven](https://apache.org/maven) build system and is published to [Maven Central](https://search.maven.org/artifact/org.eclipse.uprotocol/up-java). +---- + . Clone the repository: + [source,console] diff --git a/pom.xml b/pom.xml index f8730b66..1926a61b 100644 --- a/pom.xml +++ b/pom.xml @@ -96,6 +96,13 @@ pom import + + io.cucumber + cucumber-bom + 7.25.0 + pom + import + @@ -144,6 +151,12 @@ test + + org.junit.platform + junit-platform-suite + test + + org.mockito mockito-core @@ -173,6 +186,16 @@ jul-to-slf4j test + + io.cucumber + cucumber-java + test + + + io.cucumber + cucumber-junit-platform-engine + test + @@ -294,10 +317,19 @@ maven-surefire-plugin - 3.5.3 + + 3.5.2 false + + + cucumber.junit-platform.naming-strategy=surefire + + diff --git a/src/main/java/org/eclipse/uprotocol/client/usubscription/v3/USubscriptionClient.java b/src/main/java/org/eclipse/uprotocol/client/usubscription/v3/USubscriptionClient.java index 4ba4b301..6f487724 100644 --- a/src/main/java/org/eclipse/uprotocol/client/usubscription/v3/USubscriptionClient.java +++ b/src/main/java/org/eclipse/uprotocol/client/usubscription/v3/USubscriptionClient.java @@ -32,7 +32,7 @@ /** * The client-side interface for interacting with a USubscription service instance. * - * @see + * @see * USubscription service specification */ public interface USubscriptionClient { diff --git a/src/main/java/org/eclipse/uprotocol/communication/AbstractCommunicationLayerClient.java b/src/main/java/org/eclipse/uprotocol/communication/AbstractCommunicationLayerClient.java index 567a84a5..13a6f18c 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/AbstractCommunicationLayerClient.java +++ b/src/main/java/org/eclipse/uprotocol/communication/AbstractCommunicationLayerClient.java @@ -17,6 +17,7 @@ import org.eclipse.uprotocol.transport.LocalUriProvider; import org.eclipse.uprotocol.transport.UTransport; +// [impl->dsn~communication-layer-impl-default~1] public class AbstractCommunicationLayerClient { private final UTransport transport; private final LocalUriProvider uriProvider; diff --git a/src/main/java/org/eclipse/uprotocol/communication/CallOptions.java b/src/main/java/org/eclipse/uprotocol/communication/CallOptions.java index 2c6d5b9b..10876a99 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/CallOptions.java +++ b/src/main/java/org/eclipse/uprotocol/communication/CallOptions.java @@ -21,6 +21,7 @@ /** * This class is used to pass metadata to method invocation on the client side. */ +// [impl->dsn~communication-layer-api-declaration~1] public record CallOptions (Integer timeout, UPriority priority, String token) { public static final int TIMEOUT_DEFAULT = 10000; // Default timeout of 10 seconds diff --git a/src/main/java/org/eclipse/uprotocol/communication/InMemoryRpcClient.java b/src/main/java/org/eclipse/uprotocol/communication/InMemoryRpcClient.java index e1525e37..18d3699b 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/InMemoryRpcClient.java +++ b/src/main/java/org/eclipse/uprotocol/communication/InMemoryRpcClient.java @@ -45,6 +45,7 @@ * or directly use the {@link UTransport} to send RPC requests and register listeners that * handle the RPC responses. */ +// [impl->dsn~communication-layer-impl-default~1] public class InMemoryRpcClient extends AbstractCommunicationLayerClient implements RpcClient { // Map to store the futures that needs to be completed when the response comes in private final Map> mRequests = new ConcurrentHashMap<>(); diff --git a/src/main/java/org/eclipse/uprotocol/communication/InMemoryRpcServer.java b/src/main/java/org/eclipse/uprotocol/communication/InMemoryRpcServer.java index 66402dc5..3978d29a 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/InMemoryRpcServer.java +++ b/src/main/java/org/eclipse/uprotocol/communication/InMemoryRpcServer.java @@ -48,6 +48,7 @@ * or directly use a {@link UTransport} to register listeners that handle * RPC requests and send RPC responses. */ +// [impl->dsn~communication-layer-impl-default~1] public class InMemoryRpcServer extends AbstractCommunicationLayerClient implements RpcServer { private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryRpcServer.class); diff --git a/src/main/java/org/eclipse/uprotocol/communication/InMemorySubscriber.java b/src/main/java/org/eclipse/uprotocol/communication/InMemorySubscriber.java index d0362045..f3b628ed 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/InMemorySubscriber.java +++ b/src/main/java/org/eclipse/uprotocol/communication/InMemorySubscriber.java @@ -59,6 +59,7 @@ * registered with the listener. When a subscription change notification arrives from the USubscription * service, the corresponding handler is being looked up and invoked. */ +// [impl->dsn~communication-layer-impl-default~1] public final class InMemorySubscriber implements Subscriber { private static final Logger LOGGER = LoggerFactory.getLogger(InMemorySubscriber.class); diff --git a/src/main/java/org/eclipse/uprotocol/communication/Notifier.java b/src/main/java/org/eclipse/uprotocol/communication/Notifier.java index 89862ae1..887aa87b 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/Notifier.java +++ b/src/main/java/org/eclipse/uprotocol/communication/Notifier.java @@ -21,9 +21,10 @@ /** * A client for sending Notification messages to a uEntity. * - * @see + * @see * Communication Layer API Specifications */ +// [impl->dsn~communication-layer-api-declaration~1] public interface Notifier { /** diff --git a/src/main/java/org/eclipse/uprotocol/communication/Publisher.java b/src/main/java/org/eclipse/uprotocol/communication/Publisher.java index 07833a40..42080664 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/Publisher.java +++ b/src/main/java/org/eclipse/uprotocol/communication/Publisher.java @@ -17,9 +17,10 @@ /** * A client for publishing messages to a topic. * - * @see + * @see * Communication Layer API Specifications */ +// [impl->dsn~communication-layer-api-declaration~1] public interface Publisher { /** * Publishes a message to a topic. diff --git a/src/main/java/org/eclipse/uprotocol/communication/RequestHandler.java b/src/main/java/org/eclipse/uprotocol/communication/RequestHandler.java index 63993e72..03681066 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/RequestHandler.java +++ b/src/main/java/org/eclipse/uprotocol/communication/RequestHandler.java @@ -19,6 +19,7 @@ * RequestListener is used by the RpcServer to handle incoming requests and automatically sends * back the response to the client.
*/ +// [impl->dsn~communication-layer-api-declaration~1] public interface RequestHandler { /** * Method called to handle/process request messages. diff --git a/src/main/java/org/eclipse/uprotocol/communication/RpcClient.java b/src/main/java/org/eclipse/uprotocol/communication/RpcClient.java index d3a72249..f7c7de54 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/RpcClient.java +++ b/src/main/java/org/eclipse/uprotocol/communication/RpcClient.java @@ -19,9 +19,10 @@ /** * A client for performing Remote Procedure Calls (RPC) on (other) uEntities. * - * @see + * @see * Communication Layer API specification */ +// [impl->dsn~communication-layer-api-declaration~1] public interface RpcClient { /** * Invokes a method on a service. diff --git a/src/main/java/org/eclipse/uprotocol/communication/RpcServer.java b/src/main/java/org/eclipse/uprotocol/communication/RpcServer.java index 94bff11b..3956fb77 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/RpcServer.java +++ b/src/main/java/org/eclipse/uprotocol/communication/RpcServer.java @@ -21,9 +21,10 @@ /** * A server for exposing Remote Procedure Call (RPC) endpoints. * - * @see + * @see * Communication Layer API specification */ +// [impl->dsn~communication-layer-api-declaration~1] public interface RpcServer { /** * Registers an endpoint for RPC requests. diff --git a/src/main/java/org/eclipse/uprotocol/communication/SimpleNotifier.java b/src/main/java/org/eclipse/uprotocol/communication/SimpleNotifier.java index ef425d19..d0d7d9b5 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/SimpleNotifier.java +++ b/src/main/java/org/eclipse/uprotocol/communication/SimpleNotifier.java @@ -32,6 +32,7 @@ * their own or directly use the {@link UTransport} to send notifications and register * listeners. */ +// [impl->dsn~communication-layer-impl-default~1] public class SimpleNotifier extends AbstractCommunicationLayerClient implements Notifier { /** diff --git a/src/main/java/org/eclipse/uprotocol/communication/SimplePublisher.java b/src/main/java/org/eclipse/uprotocol/communication/SimplePublisher.java index ac3f427b..f8aae995 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/SimplePublisher.java +++ b/src/main/java/org/eclipse/uprotocol/communication/SimplePublisher.java @@ -28,6 +28,7 @@ * NOTE: Developers are not required to use these APIs, they can implement their own * or directly use the {@link UTransport} to send notifications and register listeners. */ +// [impl->dsn~communication-layer-impl-default~1] public class SimplePublisher extends AbstractCommunicationLayerClient implements Publisher { /** diff --git a/src/main/java/org/eclipse/uprotocol/communication/Subscriber.java b/src/main/java/org/eclipse/uprotocol/communication/Subscriber.java index c11f3726..cb2c0620 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/Subscriber.java +++ b/src/main/java/org/eclipse/uprotocol/communication/Subscriber.java @@ -25,9 +25,10 @@ /** * A client for subscribing to topics. * - * @see + * @see * Communication Layer API Specifications */ +// [impl->dsn~communication-layer-api-declaration~1] public interface Subscriber { /** * Registers a handler to invoke for messages that have been published to a given topic. diff --git a/src/main/java/org/eclipse/uprotocol/communication/SubscriptionChangeHandler.java b/src/main/java/org/eclipse/uprotocol/communication/SubscriptionChangeHandler.java index 88f7e1c8..e429402c 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/SubscriptionChangeHandler.java +++ b/src/main/java/org/eclipse/uprotocol/communication/SubscriptionChangeHandler.java @@ -20,6 +20,7 @@ * * This interface provides APIs to handle subscription state changes for a given topic. */ +// [impl->dsn~communication-layer-api-declaration~1] public interface SubscriptionChangeHandler { /** * Method called to handle/process subscription state changes for a given topic. diff --git a/src/main/java/org/eclipse/uprotocol/communication/UClient.java b/src/main/java/org/eclipse/uprotocol/communication/UClient.java index e237d565..f19fb423 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/UClient.java +++ b/src/main/java/org/eclipse/uprotocol/communication/UClient.java @@ -23,6 +23,7 @@ /** * A client for Communication Layer APIs. */ +// [impl->dsn~communication-layer-impl-default~1] public final class UClient implements RpcServer, Notifier, Publisher, RpcClient { private final RpcServer rpcServer; diff --git a/src/main/java/org/eclipse/uprotocol/communication/UPayload.java b/src/main/java/org/eclipse/uprotocol/communication/UPayload.java index d461b038..cbb5ec67 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/UPayload.java +++ b/src/main/java/org/eclipse/uprotocol/communication/UPayload.java @@ -28,6 +28,7 @@ /** * Wrapper class that stores the payload as {@link UPayloadFormat}. */ +// [impl->dsn~communication-layer-api-declaration~1] public record UPayload (ByteString data, UPayloadFormat format) { // Empty UPayload diff --git a/src/main/java/org/eclipse/uprotocol/communication/UStatusException.java b/src/main/java/org/eclipse/uprotocol/communication/UStatusException.java index 9572c2f3..04cdcd5a 100644 --- a/src/main/java/org/eclipse/uprotocol/communication/UStatusException.java +++ b/src/main/java/org/eclipse/uprotocol/communication/UStatusException.java @@ -20,6 +20,7 @@ /** * The unchecked exception which carries uProtocol error model. */ +// [impl->dsn~communication-layer-api-declaration~1] public class UStatusException extends RuntimeException { private final UStatus mStatus; diff --git a/src/main/java/org/eclipse/uprotocol/communication/package-info.java b/src/main/java/org/eclipse/uprotocol/communication/package-info.java new file mode 100644 index 00000000..af439526 --- /dev/null +++ b/src/main/java/org/eclipse/uprotocol/communication/package-info.java @@ -0,0 +1,12 @@ +// 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 + +// [impl->dsn~communication-layer-api-namespace~1] +package org.eclipse.uprotocol.communication; diff --git a/src/main/java/org/eclipse/uprotocol/transport/LocalUriProvider.java b/src/main/java/org/eclipse/uprotocol/transport/LocalUriProvider.java index 7f99b3fc..a31a72c2 100644 --- a/src/main/java/org/eclipse/uprotocol/transport/LocalUriProvider.java +++ b/src/main/java/org/eclipse/uprotocol/transport/LocalUriProvider.java @@ -20,6 +20,7 @@ * Implementations may use arbitrary mechanisms to determine the information that * is necessary for creating URIs, e.g. environment variables, configuration files etc. */ +// [impl->dsn~localuriprovider-declaration~1] public interface LocalUriProvider { /** * Gets the authority used for URIs representing this uEntity's resources. diff --git a/src/main/java/org/eclipse/uprotocol/transport/UListener.java b/src/main/java/org/eclipse/uprotocol/transport/UListener.java index 5912cf2b..b0971d8b 100644 --- a/src/main/java/org/eclipse/uprotocol/transport/UListener.java +++ b/src/main/java/org/eclipse/uprotocol/transport/UListener.java @@ -19,10 +19,10 @@ * * Implementations contain the details for what should occur when a message is received. * - * @see + * @see * uProtocol Transport Layer specification */ -/// for details. */ +// [impl->dsn~ulistener-declaration~1] public interface UListener { /** diff --git a/src/main/java/org/eclipse/uprotocol/transport/UTransport.java b/src/main/java/org/eclipse/uprotocol/transport/UTransport.java index 84f721ad..407b70e7 100644 --- a/src/main/java/org/eclipse/uprotocol/transport/UTransport.java +++ b/src/main/java/org/eclipse/uprotocol/transport/UTransport.java @@ -26,9 +26,10 @@ * UTransport implementations contain the details for connecting to the underlying transport technology and * sending UMessage using the configured technology. * - * @see + * @see * uProtocol Transport Layer specification */ +// [impl->dsn~utransport-declaration~1] public interface UTransport { /** * Sends a message using this transport's message exchange mechanism. @@ -45,7 +46,7 @@ public interface UTransport { *

* The listener will be invoked for each message that matches the given source filter pattern * according to the rules defined by the - * UUri + * UUri * specification. *

* This default implementation invokes {@link #registerListener(UUri, UUri, UListener)} with the @@ -66,7 +67,7 @@ default CompletionStage registerListener(UUri sourceFilter, UListener list *

* The listener will be invoked for each message that matches the given source and sink filter patterns * according to the rules defined by the - * UUri + * UUri * specification. * * @param sourceFilter The source address pattern that messages need to match. diff --git a/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java b/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java index 27e81d4c..8136be60 100644 --- a/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java +++ b/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java @@ -68,8 +68,9 @@ public static UMessageBuilder publish(UUri source) { return new UMessageBuilder( source, - UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_PUBLISH); + UuidFactory.create(), // [impl->dsn~up-attributes-id~1] + UMessageType.UMESSAGE_TYPE_PUBLISH); // [impl->dsn~up-attributes-publish-type~1] + } /** @@ -88,8 +89,8 @@ public static UMessageBuilder notification(UUri source, UUri sink) { return new UMessageBuilder( source, - UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_NOTIFICATION) + UuidFactory.create(), // [impl->dsn~up-attributes-id~1] + UMessageType.UMESSAGE_TYPE_NOTIFICATION) // [impl->dsn~up-attributes-notification-type~1] .withSink(sink); } @@ -119,8 +120,8 @@ public static UMessageBuilder request(UUri source, UUri sink, int ttl) { } return new UMessageBuilder( source, - UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_REQUEST) + UuidFactory.create(), // [impl->dsn~up-attributes-id~1] + UMessageType.UMESSAGE_TYPE_REQUEST) // [impl->dsn~up-attributes-request-type~1] .withSink(sink) .withTtl(ttl) .withPriority(UPriority.UPRIORITY_CS4); @@ -155,8 +156,9 @@ public static UMessageBuilder response(UUri source, UUri sink, UUID reqid) { return new UMessageBuilder( source, - UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_RESPONSE) + UuidFactory.create(), // [impl->dsn~up-attributes-id~1] + UMessageType.UMESSAGE_TYPE_RESPONSE) // [impl->dsn~up-attributes-response-type~1] + .withSink(sink) .withReqId(reqid) .withPriority(UPriority.UPRIORITY_CS4); @@ -193,8 +195,8 @@ public static UMessageBuilder response(UAttributes request) { return new UMessageBuilder( request.getSink(), - UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_RESPONSE) + UuidFactory.create(), // [impl->dsn~up-attributes-id~1] + UMessageType.UMESSAGE_TYPE_RESPONSE) // [impl->dsn~up-attributes-response-type~1] .withPriority(request.getPriority()) .withSink(request.getSource()) .withReqId(request.getId()) @@ -226,6 +228,7 @@ private UMessageBuilder(UUri source, UUID id, UMessageType type) { * @throws NullPointerException if the id is {@code null}. * @throws IllegalArgumentException if the id is not a {@link UuidUtils#isUProtocol(UUID) valid uProtocol UUID}. */ + // [impl->dsn~up-attributes-id~1] public UMessageBuilder withMessageId(UUID id) { Objects.requireNonNull(id, "id cannot be null."); if (!UuidUtils.isUProtocol(id)) { @@ -359,6 +362,7 @@ private UMessageBuilder withSink(UUri sink) { */ public UMessage build(UPayload payload) { Objects.requireNonNull(payload, "payload cannot be null."); + // [impl->dsn~up-attributes-payload-format~1] this.format = payload.format(); this.payload = payload.data(); return build(); diff --git a/src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java b/src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java index 9cb9b0b3..3adecd3b 100644 --- a/src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java +++ b/src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java @@ -12,6 +12,8 @@ */ package org.eclipse.uprotocol.transport.validator; +import java.time.Instant; + import org.eclipse.uprotocol.uri.validator.UriValidator; import org.eclipse.uprotocol.uuid.factory.UuidUtils; import org.eclipse.uprotocol.v1.UAttributes; @@ -75,6 +77,7 @@ public static UAttributesValidator getValidator(UMessageType type) { * @throws ValidationException if the message ID is invalid. */ public final void validateId(UAttributes attributes) { + // [impl->dsn~up-attributes-id~1] if (!attributes.hasId()) { throw new ValidationException("Missing message ID"); } @@ -138,7 +141,7 @@ public final void validateRpcPriority(UAttributes attributes) { */ public final boolean isExpired(UAttributes attributes) { final int ttl = attributes.getTtl(); - return ttl > 0 && UuidUtils.isExpired(attributes.getId(), ttl); + return ttl > 0 && UuidUtils.isExpired(attributes.getId(), ttl, Instant.now()); } /* @@ -278,6 +281,7 @@ public void validateSource(UAttributes attributes) { */ @Override public void validateSink(UAttributes attributes) { + // [impl->dsn~up-attributes-publish-sink~1] if (attributes.hasSink()) { throw new ValidationException("Attributes for a publish message must not contain a sink URI"); } @@ -440,6 +444,7 @@ public void validateSink(UAttributes attributes) { */ @Override public void validateTtl(UAttributes attributes) { + // [impl->dsn~up-attributes-request-ttl~1] if (!attributes.hasTtl()) { throw new ValidationException("RPC request message must contain a TTL"); } diff --git a/src/main/java/org/eclipse/uprotocol/uri/serializer/UriSerializer.java b/src/main/java/org/eclipse/uprotocol/uri/serializer/UriSerializer.java index c9525ad3..c3272864 100644 --- a/src/main/java/org/eclipse/uprotocol/uri/serializer/UriSerializer.java +++ b/src/main/java/org/eclipse/uprotocol/uri/serializer/UriSerializer.java @@ -13,8 +13,10 @@ package org.eclipse.uprotocol.uri.serializer; import java.net.URI; +import java.net.URISyntaxException; import java.util.Objects; import java.util.Optional; +import java.util.regex.Pattern; import org.eclipse.uprotocol.uri.validator.UriValidator; import org.eclipse.uprotocol.v1.UUri; @@ -23,17 +25,49 @@ * Provides functionality for serializing and deserializing {@link UUri}s to/from their * corresponding URI representation as defined by the uProtocol specification. * - * @see + * @see * uProtocol URI Specification */ -public interface UriSerializer { +public final class UriSerializer { - String SCHEME_UP = "up"; + /** + * The scheme used for uProtocol URIs. + */ + public static final String SCHEME_UP = "up"; + + private static final Pattern AUTHORITY_PATTERN = Pattern.compile("^[a-z0-9-._~]{1,128}$"); + + private UriSerializer() { + // prevent instantiation + } + + /** + * Serializes a {@link UUri} into its URI representation. + *

+ * The returned URI does not include the "up:" scheme. If the scheme needs to be included, + * the overloaded {@link #serialize(UUri, boolean)} method with the {@code includeScheme} + * parameter set to {@code true} can be used. + * + * @param uuri The UUri to be serialized. + * @return The URI. + * @throws NullPointerException if the UUri is null. + * @throws IllegalArgumentException if the UUri does not comply with the UUri specification. + */ + // [impl->dsn~uri-authority-mapping~1] + // [impl->dsn~uri-path-mapping~1] + // [impl->req~uri-serialization~1] + public static String serialize(UUri uuri) { + return serialize(uuri, false); + } /** * Serializes a {@link UUri} into its URI representation. * * @param uuri The UUri to be serialized. + * @param includeScheme Whether to include the "up:" scheme in the serialized URI. + * If false, the scheme and the colon will be omitted. + * This can be useful when embedding the URI in contexts where + * the scheme is implied or not allowed. * @return The URI. * @throws NullPointerException if the UUri is null. * @throws IllegalArgumentException if the UUri does not comply with the UUri specification. @@ -41,11 +75,14 @@ public interface UriSerializer { // [impl->dsn~uri-authority-mapping~1] // [impl->dsn~uri-path-mapping~1] // [impl->req~uri-serialization~1] - static String serialize(UUri uuri) { + public static String serialize(UUri uuri, boolean includeScheme) { Objects.requireNonNull(uuri); UriValidator.validate(uuri); StringBuilder sb = new StringBuilder(); + if (includeScheme) { + sb.append(SCHEME_UP).append(":"); + } if (!uuri.getAuthorityName().isBlank()) { sb.append("//"); sb.append(uuri.getAuthorityName()); @@ -74,7 +111,7 @@ static String serialize(UUri uuri) { // [impl->dsn~uri-authority-mapping~1] // [impl->dsn~uri-path-mapping~1] // [impl->req~uri-serialization~1] - static UUri deserialize(String uProtocolUri) { + public static UUri deserialize(String uProtocolUri) { Objects.requireNonNull(uProtocolUri); final var parsedUri = URI.create(uProtocolUri); return deserialize(parsedUri); @@ -94,7 +131,7 @@ static UUri deserialize(String uProtocolUri) { // [impl->dsn~uri-authority-mapping~1] // [impl->dsn~uri-path-mapping~1] // [impl->req~uri-serialization~1] - static UUri deserialize(URI uProtocolUri) { + public static UUri deserialize(URI uProtocolUri) { Objects.requireNonNull(uProtocolUri); if (uProtocolUri.getScheme() != null && !SCHEME_UP.equals(uProtocolUri.getScheme())) { @@ -106,7 +143,25 @@ static UUri deserialize(URI uProtocolUri) { if (uProtocolUri.getFragment() != null) { throw new IllegalArgumentException("uProtocol URI must not contain fragment"); } - UriValidator.validateParsedAuthority(uProtocolUri); + + String authority; + try { + // this should work if authority is server-based (i.e. contains a host) + var uriWithServerAuthority = uProtocolUri.parseServerAuthority(); + // we can then verify that the authority does neither contain user info nor port + UriValidator.validateParsedAuthority(uriWithServerAuthority); + authority = uriWithServerAuthority.getHost(); + } catch (URISyntaxException e) { + // the authority is not server-based but might still be valid according to the UUri spec, + // we just need to make sure that it either is the wildcard authority or contains allowed + // characters only + authority = uProtocolUri.getAuthority(); + if (authority != null && !"*".equals(authority) && !AUTHORITY_PATTERN.matcher(authority).matches()) { + throw new IllegalArgumentException( + "uProtocol URI authority contains invalid characters", + e); + } + } final var pathSegments = uProtocolUri.getPath().split("/"); if (pathSegments.length != 4) { @@ -114,7 +169,7 @@ static UUri deserialize(URI uProtocolUri) { } final var builder = UUri.newBuilder(); - Optional.ofNullable(uProtocolUri.getAuthority()).ifPresent(builder::setAuthorityName); + Optional.ofNullable(authority).ifPresent(builder::setAuthorityName); if (pathSegments[1].isEmpty()) { throw new IllegalArgumentException("URI must contain non-empty entity ID"); diff --git a/src/main/java/org/eclipse/uprotocol/uri/validator/UriValidator.java b/src/main/java/org/eclipse/uprotocol/uri/validator/UriValidator.java index 4a117cde..d921e3a3 100644 --- a/src/main/java/org/eclipse/uprotocol/uri/validator/UriValidator.java +++ b/src/main/java/org/eclipse/uprotocol/uri/validator/UriValidator.java @@ -191,6 +191,7 @@ static boolean matchesResource(UUri pattern, UUri candidateUri) { * @return {@code true} if the candidate matches the pattern. * @throws NullPointerException if any of the arguments are {@code null}. */ + // [impl->dsn~uri-pattern-matching~2] public static boolean matches(UUri pattern, UUri candidateUri) { Objects.requireNonNull(pattern, "Pattern must not be null"); Objects.requireNonNull(candidateUri, "Candidate URI must not be null"); diff --git a/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidFactory.java b/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidFactory.java index 510c746b..9e12747b 100644 --- a/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidFactory.java +++ b/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidFactory.java @@ -12,7 +12,6 @@ */ package org.eclipse.uprotocol.uuid.factory; -import com.github.f4b6a3.uuid.UuidCreator; import org.eclipse.uprotocol.v1.UUID; import java.time.Instant; @@ -20,78 +19,42 @@ import java.util.Random; /** - * A factory for creating UUIDs based on RFC 9562. - *

- * The factory provides two implementations, UUIDv6 (used for older versions of the protocol), - * and UUIDv7. + * A factory for creating uProtocol UUIDs. + * + * @see + * uProtocol UUID Specification */ -public abstract class UuidFactory { +// [impl->dsn~uuid-spec~1] +public final class UuidFactory { - /** - * Create a UUID based on the current time. - * - * @return a UUID - */ - public UUID create() { - return this.create(Instant.now()); + private UuidFactory() { + // utility class } /** - * Create a UUID based on the given time. + * Creates a UUID based on the current system time. * - * @param instant the time - * @return a UUID + * @return The UUID. */ - public abstract UUID create(Instant instant); - - /** - * The Factories enum provides a list of factories that can be used to create - * UUIDs. - */ - public enum Factories { - UUIDV6(new UuidFactory.Uuidv6Factory()), - - UPROTOCOL(new UuidFactory.Uuidv7Factory()); - - private final UuidFactory factory; - - Factories(UuidFactory factory) { - this.factory = factory; - } - - public UuidFactory factory() { - return factory; - } - - } - - /** - * The Uuidv6Factory class is an implementation of the UuidFactory class that - * creates UUIDs based on the UUIDv6 version of the protocol. - */ - private static class Uuidv6Factory extends UuidFactory { - public UUID create(Instant instant) { - java.util.UUID uuidJava = UuidCreator.getTimeOrdered(Objects.requireNonNullElse(instant, Instant.now()), - null, null); - return UUID.newBuilder().setMsb(uuidJava.getMostSignificantBits()) - .setLsb(uuidJava.getLeastSignificantBits()).build(); - } + public static UUID create() { + return create(Instant.now()); } /** - * The Uuidv7Factory class is an implementation of the UuidFactory class that - * creates UUIDs based on the UUIDv7 version of the protocol. + * Creates a UUID for a given point in time. + * + * @param instant The timestamp to use, or {@code null} for the current time. + * @return The UUID. */ - private static class Uuidv7Factory extends UuidFactory { - public UUID create(Instant instant) { - final long time = Objects.requireNonNullElse(instant, Instant.now()).toEpochMilli(); + public static UUID create(Instant instant) { + final long time = Objects.requireNonNullElse(instant, Instant.now()).toEpochMilli(); - final int rand_a = new Random().nextInt() & 0xfff; - final long rand_b = new Random().nextLong() & 0x3fffffffffffffffL; + final int randA = new Random().nextInt() & 0x0fff; // keep 4 msb clear for version + final long randB = new Random().nextLong() & 0x3fffffffffffffffL; // keep 2 msb clear for variant - return UUID.newBuilder() - .setMsb((time << 16) | 7L << 12 | rand_a) - .setLsb(rand_b | 1L << 63).build(); - } + return UUID.newBuilder() + .setMsb((time << 16) | 0b0111L << 12 | randA) // version 7 + .setLsb(randB | 0b10L << 62) // variant RFC 4122 + .build(); } -} \ No newline at end of file +} diff --git a/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidUtils.java b/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidUtils.java index 8b3c54a0..7f536d8e 100644 --- a/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidUtils.java +++ b/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidUtils.java @@ -12,19 +12,20 @@ */ package org.eclipse.uprotocol.uuid.factory; -import com.github.f4b6a3.uuid.util.UuidTime; -import com.github.f4b6a3.uuid.util.UuidUtil; import org.eclipse.uprotocol.v1.UUID; +import java.time.Instant; +import java.util.Objects; import java.util.Optional; /** * Utility methods for uProtocol UUIDs. */ +// [impl->dsn~uuid-spec~1] +// [impl->req~uuid-type~1] public final class UuidUtils { private static final int VARIANT_RFC_9562 = 0b10; - private static final int VERSION_UUIDV6 = 6; private static final int VERSION_UPROTOCOL = 7; private UuidUtils() { @@ -42,7 +43,7 @@ private static byte getVersion(UUID uuid) { * @param uuid The UUID to check. * @return {@code true} if the UUID is in the RFC 9562 variant, {@code false} if uuid is {@code null}. */ - public static boolean isRfc9562Variant(UUID uuid) { + private static boolean isRfc9562Variant(UUID uuid) { return Optional.ofNullable(uuid) .map(id -> (byte) (id.getLsb() >>> 62)) .map(variant -> variant == VARIANT_RFC_9562) @@ -56,7 +57,7 @@ public static boolean isRfc9562Variant(UUID uuid) { * @return {@code true} if is a uProtocol UUID or {@code false} if uuid is {@code null} * or is not a v7 UUID. * @see RFC 9562 - * @see + * @see * uProtocol UUID specification */ public static boolean isUProtocol(UUID uuid) { @@ -68,108 +69,95 @@ public static boolean isUProtocol(UUID uuid) { } /** - * Checks if a UUID is a v6 UUID. - * - * @param uuid The UUID to check. - * @return {@code true} if is a v6 UUID or {@code false} if uuid is {@code null} - * or is not a v6 UUID. - * @see RFC 9562 - */ - public static boolean isUuidv6(UUID uuid) { - return Optional.ofNullable(uuid) - .filter(UuidUtils::isRfc9562Variant) - .map(UuidUtils::getVersion) - .map(version -> version == VERSION_UUIDV6) - .orElse(false); - } - - /** - * Verify uuid is either v6 or v7. - * - * @param uuid The UUID to check. - * @return true if is UUID version 6 or 7 - */ - public static boolean isUuid(UUID uuid) { - return isUProtocol(uuid) || isUuidv6(uuid); - } - - /** - * Gets the number of milliseconds since unix epoch contained in a UUID. + * Gets a UUID's (creation) timestamp. * * @param uuid The UUID. - * @return The number of milliseconds, or empty if uuid is null or does not contain - * a timestamp. + * @return The point in time as the number of milliseconds since the Unix epoch. + * @throws NullPointerException if the UUID is {@code null}. + * @throws IllegalArgumentException if the UUID is not a uProtocol UUID. */ - public static Optional getTime(UUID uuid) { - if (uuid == null) { - return Optional.empty(); - } - - final var version = getVersion(uuid); - - switch (version) { - case VERSION_UPROTOCOL: - return Optional.of(uuid.getMsb() >> 16); - case VERSION_UUIDV6: - // convert Ticks to Millis - try { - java.util.UUID uuidJava = new java.util.UUID(uuid.getMsb(), uuid.getLsb()); - return Optional.of(UuidTime.toUnixTimestamp( - UuidUtil.getTimestamp(uuidJava)) / UuidTime.TICKS_PER_MILLI); - } catch (IllegalArgumentException e) { - return Optional.empty(); - } - default: - return Optional.empty(); + public static long getTime(UUID uuid) { + Objects.requireNonNull(uuid); + if (!isUProtocol(uuid)) { + throw new IllegalArgumentException("UUID is not a uProtocol UUID"); } + return uuid.getMsb() >> 16; } /** - * Calculates the elapsed time since the creation of the specified UUID. + * Gets the amount of time that has elapsed between the creation of a UUID and a given + * point in time. * - * @param id The UUID of the object whose creation time needs to be determined. - * @return An Optional containing the elapsed time in milliseconds, - * or an empty Optional if the creation time cannot be determined. + * @param id The UUID. + * @param now The reference point in time as the number of milliseconds since the Unix epoch, + * or {@code null} to use the current point in time. + * @return The amount of time in number of milliseconds. The value will be negative if the + * given point in time is before the creation time of the UUID. + * @throws NullPointerException if the UUID is {@code null}. + * @throws IllegalArgumentException if the UUID is not a uProtocol UUID. */ - public static Optional getElapsedTime(UUID id) { - final long creationTime = getTime(id).orElse(-1L); - if (creationTime < 0) { - return Optional.empty(); + public static long getElapsedTime(UUID id, Instant now) { + Objects.requireNonNull(id); + if (!isUProtocol(id)) { + throw new IllegalArgumentException("UUID is not a uProtocol UUID"); } - final long now = System.currentTimeMillis(); - return now >= creationTime ? Optional.of(now - creationTime) : Optional.empty(); + final var creationTime = getTime(id); + final var referenceTime = now != null ? now.toEpochMilli() : Instant.now().toEpochMilli(); + return referenceTime - creationTime; } /** - * Calculates the remaining time until the expiration of the event identified by - * the given UUID. + * Gets the amount of time after which the object identified by a given UUID should + * be considered expired. * - * @param id The UUID of the object whose remaining time needs to be - * determined. - * @param ttl The time-to-live (TTL) in milliseconds. - * @return An Optional containing the remaining time in milliseconds until the - * event expires, - * or an empty Optional if the UUID is null, TTL is non-positive, or the - * creation time cannot be determined. + * @param id The UUID. + * @param ttl The object's time-to-live (TTL) in milliseconds. + * @param now The reference point in time that the calculation should be based on, given + * as the number of milliseconds since the Unix epoch, or {@code null} to use the current point in time. + * @return The amount of time in milliseconds. The value will be zero if the object + * has already expired. + * @throws NullPointerException if the UUID is {@code null}. + * @throws IllegalArgumentException if the UUID is not a uProtocol UUID. + * @throws IllegalArgumentException if the TTL is non-positive. */ - public static Optional getRemainingTime(UUID id, int ttl) { - if (id == null || ttl <= 0) { - return Optional.empty(); + public static long getRemainingTime(UUID id, int ttl, Instant now) { + Objects.requireNonNull(id); + if (!isUProtocol(id)) { + throw new IllegalArgumentException("UUID is not a uProtocol UUID"); + } + if (ttl <= 0) { + throw new IllegalArgumentException("TTL must be positive"); + } + final var creationTime = getTime(id); + final var referenceTime = now != null ? now.toEpochMilli() : Instant.now().toEpochMilli(); + final var elapsedTime = referenceTime - creationTime; + if (elapsedTime >= ttl) { + return 0; + } else { + return ttl - elapsedTime; } - return getElapsedTime(id).filter(elapsedTime -> ttl > elapsedTime).map(elapsedTime -> ttl - elapsedTime); } /** - * Checks if the event identified by the given UUID has expired based on the - * specified time-to-live (TTL). + * Checks if an object identified by a given UUID should be considered expired. * - * @param id The UUID identifying the event. - * @param ttl The time-to-live (TTL) in milliseconds for the event. - * @return true if the event has expired, false otherwise. Returns false if TTL - * is non-positive or creation time - * cannot be determined. + * @param id The UUID. + * @param ttl The object's time-to-live (TTL) in milliseconds. + * @param now The reference point in time that the calculation should be based on, given + * as the number of milliseconds since the Unix epoch, or {@code null} to use the current point in time. + * @return {@code true} if the object's TTL has already expired. + * @throws NullPointerException if the UUID is {@code null}. + * @throws IllegalArgumentException if the UUID is not a uProtocol UUID. + * @throws IllegalArgumentException if the TTL is negative. */ - public static boolean isExpired(UUID id, int ttl) { - return ttl > 0 && getRemainingTime(id, ttl).isEmpty(); + public static boolean isExpired(UUID id, int ttl, Instant now) { + Objects.requireNonNull(id); + if (!isUProtocol(id)) { + throw new IllegalArgumentException("UUID is not a uProtocol UUID"); + } + if (ttl < 0) { + throw new IllegalArgumentException("TTL must be non-negative"); + } + return ttl > 0 && getRemainingTime(id, ttl, now) == 0; } } diff --git a/src/main/java/org/eclipse/uprotocol/uuid/serializer/UuidSerializer.java b/src/main/java/org/eclipse/uprotocol/uuid/serializer/UuidSerializer.java index 9fb60e2b..ce8d15b7 100644 --- a/src/main/java/org/eclipse/uprotocol/uuid/serializer/UuidSerializer.java +++ b/src/main/java/org/eclipse/uprotocol/uuid/serializer/UuidSerializer.java @@ -12,40 +12,52 @@ */ package org.eclipse.uprotocol.uuid.serializer; +import java.util.Objects; + +import org.eclipse.uprotocol.uuid.factory.UuidUtils; import org.eclipse.uprotocol.v1.UUID; /** - * UUID Serializer interface used to serialize/deserialize UUIDs to/from - * its hyphenated string form. + * Helper for de-/serializing UUIDs from/to its hyphenated string form. */ -public interface UuidSerializer { +// [impl->req~uuid-hex-and-dash~1] +public final class UuidSerializer { + + private UuidSerializer() { + // utility class + } /** - * Deserialize from a specific serialization format to a {@link UUID}. + * Creates a uProtocol UUID from its hyphenated string format. * - * @param stringUuid The UUID in the transport serialized format. - * @return Returns the {@link UUID} object. + * @param stringUuid The hyphenated string. + * @return The UUID. + * @throws IllegalArgumentException if the string does not represent a valid uProtocol UUID. */ - static UUID deserialize(String stringUuid) { - if (stringUuid == null || stringUuid.isBlank()) { - return UUID.getDefaultInstance(); - } - try { - java.util.UUID uuidJava = java.util.UUID.fromString(stringUuid); - return UUID.newBuilder().setMsb(uuidJava.getMostSignificantBits()) - .setLsb(uuidJava.getLeastSignificantBits()).build(); - } catch (IllegalArgumentException e) { - return UUID.getDefaultInstance(); + public static UUID deserialize(String stringUuid) { + Objects.requireNonNull(stringUuid); + // will throw IllegalArgumentException if the string is not hex-and-dash + java.util.UUID uuidJava = java.util.UUID.fromString(stringUuid); + var uuid = UUID.newBuilder() + .setMsb(uuidJava.getMostSignificantBits()) + .setLsb(uuidJava.getLeastSignificantBits()) + .build(); + if (UuidUtils.isUProtocol(uuid)) { + return uuid; + } else { + throw new IllegalArgumentException("String does not represent a uProtocol UUID"); } } /** - * Serialize from a {@link UUID} to a specific serialization format. - * - * @param uuid The {@link UUID} object to serialize to a string. - * @return Returns the {@link UUID} in the transport serialized format. + * Serializes uProtocol UUID to its hyphenated string representation. + * + * @param uuid The UUID. + * @return The hyphenated string representation of the UUID. + * @throws NullPointerException if the UUID is {@code null}. */ - static String serialize(UUID uuid) { - return uuid == null ? new String() : new java.util.UUID(uuid.getMsb(), uuid.getLsb()).toString(); + public static String serialize(UUID uuid) { + Objects.requireNonNull(uuid); + return new java.util.UUID(uuid.getMsb(), uuid.getLsb()).toString(); } } diff --git a/src/main/java/org/eclipse/uprotocol/uuid/validate/UuidValidator.java b/src/main/java/org/eclipse/uprotocol/uuid/validate/UuidValidator.java deleted file mode 100644 index 6dcf05b3..00000000 --- a/src/main/java/org/eclipse/uprotocol/uuid/validate/UuidValidator.java +++ /dev/null @@ -1,113 +0,0 @@ -/** - * 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 - */ -package org.eclipse.uprotocol.uuid.validate; - -import org.eclipse.uprotocol.uuid.factory.UuidUtils; -import org.eclipse.uprotocol.v1.UUID; -import org.eclipse.uprotocol.validation.ValidationException; -import org.eclipse.uprotocol.validation.ValidationUtils; - -import java.util.Objects; -import java.util.Optional; - -/** - * A Validator for uProtocol UUIDs. - */ -public abstract class UuidValidator { - - public static UuidValidator getValidator(UUID uuid) { - if (UuidUtils.isUuidv6(uuid)) { - return UuidValidator.Validators.UUIDV6.validator(); - } else if (UuidUtils.isUProtocol(uuid)) { - return UuidValidator.Validators.UPROTOCOL.validator(); - } else { - return UuidValidator.Validators.UNKNOWN.validator(); - } - } - - public enum Validators { - UNKNOWN(new UuidValidator.InvalidValidator()), - UUIDV6(new UuidValidator.UUIDv6Validator()), - UPROTOCOL(new UuidValidator.UUIDv7Validator()); - - private final UuidValidator uuidValidator; - - public UuidValidator validator() { - return uuidValidator; - } - - Validators(UuidValidator uuidValidator) { - this.uuidValidator = uuidValidator; - } - } - - /** - * Checks if a UUID is valid according to the specific variant/version of the UUID. - * - * @param uuid The UUID to validate. - * @throws NullPointerException if the UUID is null. - * @throws ValidationException if the UUID is invalid. - */ - public void validate(UUID uuid) { - Objects.requireNonNull(uuid); - final var errors = ValidationUtils.collectErrors(uuid, - this::validateVersion, - this::validateTime - ); - if (!errors.isEmpty()) { - throw new ValidationException(errors); - } - } - - /** - * Validates the version of the UUID. - * - * @param uuid The UUID to check. - * @throws ValidationException if the UUID is invalid. - */ - public abstract void validateVersion(UUID uuid); - - public void validateTime(UUID uuid) { - final Optional time = UuidUtils.getTime(uuid); - if (time.isPresent() && (time.get() > 0)) { - return; - } - throw new ValidationException("Invalid UUID Time"); - } - - private static class InvalidValidator extends UuidValidator { - - @Override - public void validateVersion(UUID uuid) { - throw new ValidationException("Invalid UUID Version"); - } - } - - private static class UUIDv6Validator extends UuidValidator { - @Override - public void validateVersion(UUID uuid) { - if (!UuidUtils.isUuidv6(uuid)) { - throw new ValidationException("Not a UUIDv6 Version"); - } - } - } - - private static class UUIDv7Validator extends UuidValidator { - @Override - public void validateVersion(UUID uuid) { - if (!UuidUtils.isUProtocol(uuid)) { - throw new ValidationException("Invalid UUIDv7 Version"); - } - } - } -} diff --git a/src/main/java/org/eclipse/uprotocol/v1/package-info.java b/src/main/java/org/eclipse/uprotocol/v1/package-info.java new file mode 100644 index 00000000..b91770d6 --- /dev/null +++ b/src/main/java/org/eclipse/uprotocol/v1/package-info.java @@ -0,0 +1,25 @@ +// 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 + +/** + * This package contains the classes and interfaces that have been generated from the + * proto3 files contained in the uProtocol specification. + */ +// [impl->req~ustatus-data-model-impl~1] +// [impl->req~ustatus-data-model-proto~1] +// [impl->req~uattributes-data-model-impl~1] +// [impl->req~uattributes-data-model-proto~1] +// [impl->req~umessage-data-model-impl~1] +// [impl->req~umessage-data-model-proto~1] +// [impl->dsn~uri-data-model-naming~1] +// [impl->req~uri-data-model-proto~1] +// [impl->req~uuid-type~1] +// [impl->req~uuid-proto~1] +package org.eclipse.uprotocol.v1; diff --git a/src/test/java/org/eclipse/uprotocol/communication/CallOptionsTest.java b/src/test/java/org/eclipse/uprotocol/communication/CallOptionsTest.java index 274e5c2e..13316c66 100644 --- a/src/test/java/org/eclipse/uprotocol/communication/CallOptionsTest.java +++ b/src/test/java/org/eclipse/uprotocol/communication/CallOptionsTest.java @@ -23,7 +23,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; - +// [utest->dsn~communication-layer-impl-default~1] class CallOptionsTest { @Test diff --git a/src/test/java/org/eclipse/uprotocol/communication/CommunicationLayerClientTestBase.java b/src/test/java/org/eclipse/uprotocol/communication/CommunicationLayerClientTestBase.java index b494c362..848538df 100644 --- a/src/test/java/org/eclipse/uprotocol/communication/CommunicationLayerClientTestBase.java +++ b/src/test/java/org/eclipse/uprotocol/communication/CommunicationLayerClientTestBase.java @@ -27,6 +27,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +// [utest->dsn~communication-layer-impl-default~1] class CommunicationLayerClientTestBase { protected static final UUri TRANSPORT_SOURCE = UUri.newBuilder() .setAuthorityName("my-vehicle") diff --git a/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcClientTest.java b/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcClientTest.java index 1d7d5cad..4bd562c3 100644 --- a/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcClientTest.java +++ b/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcClientTest.java @@ -45,6 +45,7 @@ import com.google.common.truth.Truth; import com.google.protobuf.ByteString; +// [utest->dsn~communication-layer-impl-default~1] class InMemoryRpcClientTest extends CommunicationLayerClientTestBase { private static void assertMessageHasOptions(CallOptions options, UMessage message) { @@ -177,7 +178,7 @@ void testHandleUnexpectedResponse(Consumer unexpectedMessageHandler) { verify(transport).send(requestMessage.capture()); // create unsolicited response message - final var reqId = UuidFactory.Factories.UPROTOCOL.factory().create(); + final var reqId = UuidFactory.create(); assertNotEquals(reqId, requestMessage.getValue().getAttributes().getId()); var responseMessage = UMessageBuilder.response( METHOD_URI, diff --git a/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcServerTest.java b/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcServerTest.java index a8313c58..2798704d 100644 --- a/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcServerTest.java +++ b/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcServerTest.java @@ -51,6 +51,7 @@ import com.google.protobuf.ByteString; @ExtendWith(MockitoExtension.class) +// [utest->dsn~communication-layer-impl-default~1] class InMemoryRpcServerTest extends CommunicationLayerClientTestBase { @Mock diff --git a/src/test/java/org/eclipse/uprotocol/communication/InMemorySubscriberTest.java b/src/test/java/org/eclipse/uprotocol/communication/InMemorySubscriberTest.java index 7d102490..5fc6ee97 100644 --- a/src/test/java/org/eclipse/uprotocol/communication/InMemorySubscriberTest.java +++ b/src/test/java/org/eclipse/uprotocol/communication/InMemorySubscriberTest.java @@ -66,6 +66,7 @@ import org.eclipse.uprotocol.v1.UUri; @ExtendWith(MockitoExtension.class) +// [utest->dsn~communication-layer-impl-default~1] class InMemorySubscriberTest { private static final UUri SUBSCRIPTION_SERVICE_URI = UUri.newBuilder() .setAuthorityName("some-host") @@ -557,7 +558,7 @@ void testHandleSubscriptionChangeNotificationHandlesInvalidMessages() { // WHEN a non-notification message is received var malformedPublishMessage = UMessage.newBuilder() .setAttributes(UAttributes.newBuilder() - .setId(UuidFactory.Factories.UPROTOCOL.factory().create()) + .setId(UuidFactory.create()) .setType(UMessageType.UMESSAGE_TYPE_PUBLISH) .setSource(SUBSCRIPTION_NOTIFICATION_TOPIC_URI) .setSink(SOURCE) diff --git a/src/test/java/org/eclipse/uprotocol/communication/RpcServerTest.java b/src/test/java/org/eclipse/uprotocol/communication/RpcServerTest.java deleted file mode 100644 index 190cbeac..00000000 --- a/src/test/java/org/eclipse/uprotocol/communication/RpcServerTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * 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 - */ -package org.eclipse.uprotocol.communication; -/* -import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import org.eclipse.uprotocol.v1.UCode; -import org.eclipse.uprotocol.v1.UMessage; -import org.eclipse.uprotocol.v1.UStatus; -import org.eclipse.uprotocol.v1.UUri; -*/ -public class RpcServerTest { -/* - @Test - @DisplayName("Test registering a request listener") - public void test_registering_request_listener() { - - TestUTransport transport = new TestUTransport(); - RequestListener listener = new RequestListener(transport) { - @Override - UPayload handleRequest(UMessage request) { - return UPayload.EMPTY; - } - }; - RpcServer server = new DefaultRpcServer(); - assertEquals(server.init(transport), UStatus.newBuilder().setCode(UCode.OK).build()); - UStatus status = server.registerRequestListener(createMethodUri(), listener); - assertEquals(status.getCode(), UCode.OK); - } - - @Test - @DisplayName("Test unregistering a request listener") - public void test_unregistering_request_listener() { - - TestUTransport transport = new TestUTransport(); - RequestListener listener = new RequestListener(transport) { - @Override - UPayload handleRequest(UMessage request) { - return UPayload.packToAny(UUri.newBuilder().build()); - } - }; - RpcServer server = new DefaultRpcServer(); - assertEquals(server.init(transport), UStatus.newBuilder().setCode(UCode.OK).build()); - UStatus status = server.registerRequestListener(createMethodUri(), listener); - assertEquals(status.getCode(), UCode.OK); - - status = server.unregisterRequestListener(createMethodUri(), listener); - assertEquals(status.getCode(), UCode.OK); - } - - - @Test - @DisplayName("Test unregistering a listener that wasn't registered before") - public void test_unregistering_non_registered_listener() { - - TestUTransport transport = new TestUTransport(); - RequestListener listener = new RequestListener(transport) { - @Override - UPayload handleRequest(UMessage request) { - throw new UnsupportedOperationException("Unimplemented method 'handleRequest'"); - } - }; - RpcServer server = new DefaultRpcServer(); - assertEquals(server.init(transport), UStatus.newBuilder().setCode(UCode.OK).build()); - UStatus status = server.unregisterRequestListener(createMethodUri(), listener); - assertEquals(status.getCode(), UCode.INVALID_ARGUMENT); - } - - private UUri createMethodUri() { - return UUri.newBuilder() - .setAuthorityName("hartley") - .setUeId(10) - .setUeVersionMajor(1) - .setResourceId(3).build(); - } - */ -} diff --git a/src/test/java/org/eclipse/uprotocol/communication/SimpleNotifierTest.java b/src/test/java/org/eclipse/uprotocol/communication/SimpleNotifierTest.java index 640db6d4..cbb4ef3f 100644 --- a/src/test/java/org/eclipse/uprotocol/communication/SimpleNotifierTest.java +++ b/src/test/java/org/eclipse/uprotocol/communication/SimpleNotifierTest.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +// [utest->dsn~communication-layer-impl-default~1] class SimpleNotifierTest extends CommunicationLayerClientTestBase { private Notifier notifier; diff --git a/src/test/java/org/eclipse/uprotocol/communication/SimplePublisherTest.java b/src/test/java/org/eclipse/uprotocol/communication/SimplePublisherTest.java index fddb0bf6..17dfd374 100644 --- a/src/test/java/org/eclipse/uprotocol/communication/SimplePublisherTest.java +++ b/src/test/java/org/eclipse/uprotocol/communication/SimplePublisherTest.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; - +// [utest->dsn~communication-layer-impl-default~1] class SimplePublisherTest extends CommunicationLayerClientTestBase { private Publisher publisher; private CallOptions options; diff --git a/src/test/java/org/eclipse/uprotocol/communication/UClientTest.java b/src/test/java/org/eclipse/uprotocol/communication/UClientTest.java index 28c59418..1ed752f7 100644 --- a/src/test/java/org/eclipse/uprotocol/communication/UClientTest.java +++ b/src/test/java/org/eclipse/uprotocol/communication/UClientTest.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +// [utest->dsn~communication-layer-impl-default~1] class UClientTest { private static final UUri TRANSPORT_SOURCE = UUri.newBuilder() .setAuthorityName("my-vehicle") diff --git a/src/test/java/org/eclipse/uprotocol/communication/UPayloadTest.java b/src/test/java/org/eclipse/uprotocol/communication/UPayloadTest.java index ef5d8303..765a2bea 100644 --- a/src/test/java/org/eclipse/uprotocol/communication/UPayloadTest.java +++ b/src/test/java/org/eclipse/uprotocol/communication/UPayloadTest.java @@ -36,7 +36,7 @@ import com.google.protobuf.Message; import com.google.protobuf.StringValue; - +// [utest->dsn~communication-layer-impl-default~1] public class UPayloadTest { @Test diff --git a/src/test/java/org/eclipse/uprotocol/communication/UStatusExceptionTest.java b/src/test/java/org/eclipse/uprotocol/communication/UStatusExceptionTest.java index 05342055..e12c1a9a 100644 --- a/src/test/java/org/eclipse/uprotocol/communication/UStatusExceptionTest.java +++ b/src/test/java/org/eclipse/uprotocol/communication/UStatusExceptionTest.java @@ -20,6 +20,7 @@ import org.eclipse.uprotocol.v1.UCode; import org.eclipse.uprotocol.v1.UStatus; +// [utest->dsn~communication-layer-impl-default~1] public class UStatusExceptionTest { @Test @@ -75,5 +76,4 @@ public void testUStatusExceptionThrowable() { assertEquals("Invalid message type", exception.getMessage()); assertEquals(cause, exception.getCause()); } - } diff --git a/src/test/java/org/eclipse/uprotocol/transport/StaticUriProviderTest.java b/src/test/java/org/eclipse/uprotocol/transport/StaticUriProviderTest.java new file mode 100644 index 00000000..ff130ab7 --- /dev/null +++ b/src/test/java/org/eclipse/uprotocol/transport/StaticUriProviderTest.java @@ -0,0 +1,64 @@ +/** + * SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.uprotocol.transport; + +import static org.junit.Assert.assertEquals; + +import org.eclipse.uprotocol.v1.UUri; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class StaticUriProviderTest { + + private static UUri LOCAL_URI = UUri.newBuilder() + .setAuthorityName("my-authority") + .setUeId(0x1234) + .setUeVersionMajor(0x01) + .build(); + + private LocalUriProvider uriProvider; + + @BeforeEach + void createUriProvider() { + uriProvider = StaticUriProvider.of("my-authority", 0x1234, 0x01); + } + + @Test + void testFactoryMethod() { + var provider = StaticUriProvider.of(LOCAL_URI); + assertEquals(LOCAL_URI, provider.getSource()); + } + + @Test + void testGetAuthorityReturnsAuthorityName() { + assertEquals("my-authority", uriProvider.getAuthority()); + } + + @Test + void testGetSourceReturnsUri() { + var source = uriProvider.getSource(); + assertEquals("my-authority", source.getAuthorityName()); + assertEquals(0x1234, source.getUeId()); + assertEquals(0x01, source.getUeVersionMajor()); + assertEquals(0, source.getResourceId()); + } + + @Test + void testGetResourceReturnsUri() { + var resource = uriProvider.getResource(0xabcd); + assertEquals("my-authority", resource.getAuthorityName()); + assertEquals(0x1234, resource.getUeId()); + assertEquals(0x01, resource.getUeVersionMajor()); + assertEquals(0xabcd, resource.getResourceId()); + } +} diff --git a/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java b/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java index b17e97a6..b22187da 100644 --- a/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java +++ b/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java @@ -73,8 +73,10 @@ void testWithMessageIdPanicsForInvalidUuid() { @ParameterizedTest @CsvSource(useHeadersInDisplayName = true, textBlock = """ permLevel, commStatus, token + # // [utest->dsn~up-attributes-permission-level~1] 5, , , NOT_FOUND, + # // [utest->dsn~up-attributes-request-token~1] , , my-token """) void testPublishMessageBuilderRejectsInvalidAttributes( @@ -89,8 +91,10 @@ void testPublishMessageBuilderRejectsInvalidAttributes( @ParameterizedTest @CsvSource(useHeadersInDisplayName = true, textBlock = """ permLevel, commStatus, token + # // [utest->dsn~up-attributes-permission-level~1] 5, , , NOT_FOUND, + # // [utest->dsn~up-attributes-request-token~1] , , my-token """) void testNotificationMessageBuilderRejectsInvalidAttributes( @@ -138,12 +142,18 @@ void testRequestRejectsInvalidTtl(int ttl) { @ParameterizedTest @CsvSource(useHeadersInDisplayName = true, textBlock = """ permLevel, commStatus, priority + # // [utest->dsn~up-attributes-permission-level~1] -1, , , NOT_FOUND, + # // [utest->dsn~up-attributes-request-priority~1] , , UPRIORITY_UNSPECIFIED + # // [utest->dsn~up-attributes-request-priority~1] , , UPRIORITY_CS0 + # // [utest->dsn~up-attributes-request-priority~1] , , UPRIORITY_CS1 + # // [utest->dsn~up-attributes-request-priority~1] , , UPRIORITY_CS2 + # // [utest->dsn~up-attributes-request-priority~1] , , UPRIORITY_CS3 """) void testRequestMessageBuilderRejectsInvalidAttributes( @@ -174,13 +184,20 @@ void testRequestMessageBuilderRejectsInvalidAttributes( @ParameterizedTest @CsvSource(useHeadersInDisplayName = true, textBlock = """ ttl, permLevel, token, priority + # // [utest->dsn~up-attributes-permission-level~1] -1, , , , 5, , + # // [utest->dsn~up-attributes-request-token~1] , , my-token, + # // [utest->dsn~up-attributes-request-priority~1] , , , UPRIORITY_UNSPECIFIED + # // [utest->dsn~up-attributes-request-priority~1] , , , UPRIORITY_CS0 + # // [utest->dsn~up-attributes-request-priority~1] , , , UPRIORITY_CS1 + # // [utest->dsn~up-attributes-request-priority~1] , , , UPRIORITY_CS2 + # // [utest->dsn~up-attributes-request-priority~1] , , , UPRIORITY_CS3 """) void testResponseMessageBuilderRejectsInvalidAttributes( @@ -192,7 +209,7 @@ void testResponseMessageBuilderRejectsInvalidAttributes( var builder = UMessageBuilder.response( UURI_METHOD, UURI_DEFAULT, - UuidFactory.Factories.UPROTOCOL.factory().create()); + UuidFactory.create()); if (ttl != null) { assertThrows( @@ -221,7 +238,7 @@ void testResponseMessageBuilderRejectsInvalidAttributes( void testResponseRejectsNonRequestAttributes() { UAttributes attributes = UAttributes.newBuilder() .setType(UMessageType.UMESSAGE_TYPE_PUBLISH) - .setId(UuidFactory.Factories.UPROTOCOL.factory().create()) + .setId(UuidFactory.create()) .setSource(UURI_TOPIC) .build(); UAttributesValidator.getValidator(attributes).validate(attributes); @@ -236,10 +253,10 @@ void testResponseRejectsNonRequestAttributes() { void testBuildSupportsRepeatedInvocation() { UMessageBuilder builder = UMessageBuilder.publish(UURI_TOPIC); UMessage messageOne = builder - .withMessageId(UuidFactory.Factories.UPROTOCOL.factory().create()) + .withMessageId(UuidFactory.create()) .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT)); UMessage messageTwo = builder - .withMessageId(UuidFactory.Factories.UPROTOCOL.factory().create()) + .withMessageId(UuidFactory.create()) .build(UPayload.pack(ByteString.copyFromUtf8("unlocked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT)); assertEquals(messageOne.getAttributes().getType(), messageTwo.getAttributes().getType()); assertNotEquals(messageOne.getAttributes().getId(), messageTwo.getAttributes().getId()); @@ -248,8 +265,10 @@ void testBuildSupportsRepeatedInvocation() { } @Test + // [utest->req~uattributes-data-model-impl~1] + // [utest->req~umessage-data-model-impl~1] void testBuildRetainsAllPublishAttributes() { - var messageId = UuidFactory.Factories.UPROTOCOL.factory().create(); + var messageId = UuidFactory.create(); String traceparent = "traceparent"; UUri topic = UURI_TOPIC; UMessage message = UMessageBuilder.publish(topic) @@ -258,15 +277,23 @@ void testBuildRetainsAllPublishAttributes() { .withTraceparent(traceparent) .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT)); + // [utest->dsn~up-attributes-id~1] assertEquals(messageId, message.getAttributes().getId()); assertEquals(UPriority.UPRIORITY_UNSPECIFIED, message.getAttributes().getPriority()); + // [utest->dsn~up-attributes-publish-source~1] assertEquals(topic, message.getAttributes().getSource()); + // [utest->dsn~up-attributes-publish-sink~1] assertFalse(message.getAttributes().hasSink()); assertEquals(5000, message.getAttributes().getTtl()); + // [utest->dsn~up-attributes-traceparent~1] assertEquals(traceparent, message.getAttributes().getTraceparent()); + // [utest->dsn~up-attributes-publish-type~1] assertEquals(UMessageType.UMESSAGE_TYPE_PUBLISH, message.getAttributes().getType()); + // [utest->dsn~up-attributes-payload-format~1] assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_TEXT, message.getAttributes().getPayloadFormat()); + // [utest->req~uattributes-data-model-proto~1] + // [utest->req~umessage-data-model-proto~1] var proto = message.toByteString(); assertDoesNotThrow(() -> { var deserializedMessage = UMessage.parseFrom(proto); @@ -275,8 +302,10 @@ void testBuildRetainsAllPublishAttributes() { } @Test + // [utest->req~uattributes-data-model-impl~1] + // [utest->req~umessage-data-model-impl~1] void testBuildRetainsAllNotificationAttributes() { - var messageId = UuidFactory.Factories.UPROTOCOL.factory().create(); + var messageId = UuidFactory.create(); String traceparent = "traceparent"; var origin = UURI_TOPIC; var destination = UURI_DEFAULT; @@ -287,15 +316,21 @@ void testBuildRetainsAllNotificationAttributes() { .withTraceparent(traceparent) .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT)); + // [utest->dsn~up-attributes-id~1] assertEquals(messageId, message.getAttributes().getId()); assertEquals(UPriority.UPRIORITY_CS2, message.getAttributes().getPriority()); assertEquals(origin, message.getAttributes().getSource()); assertEquals(destination, message.getAttributes().getSink()); assertEquals(5000, message.getAttributes().getTtl()); + // [utest->dsn~up-attributes-traceparent~1] assertEquals(traceparent, message.getAttributes().getTraceparent()); + // [utest->dsn~up-attributes-notification-type~1] assertEquals(UMessageType.UMESSAGE_TYPE_NOTIFICATION, message.getAttributes().getType()); + // [utest->dsn~up-attributes-payload-format~1] assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_TEXT, message.getAttributes().getPayloadFormat()); + // [utest->req~uattributes-data-model-proto~1] + // [utest->req~umessage-data-model-proto~1] var proto = message.toByteString(); assertDoesNotThrow(() -> { var deserializedMessage = UMessage.parseFrom(proto); @@ -304,8 +339,10 @@ void testBuildRetainsAllNotificationAttributes() { } @Test + // [utest->req~uattributes-data-model-impl~1] + // [utest->req~umessage-data-model-impl~1] void testBuildRetainsAllRequestAttributes() { - var messageId = UuidFactory.Factories.UPROTOCOL.factory().create(); + var messageId = UuidFactory.create(); var token = "token"; String traceparent = "traceparent"; var methodToInvoke = UURI_METHOD; @@ -318,17 +355,25 @@ void testBuildRetainsAllRequestAttributes() { .withTraceparent(traceparent) .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT)); + // [utest->dsn~up-attributes-id~1] assertEquals(messageId, message.getAttributes().getId()); + // [utest->dsn~up-attributes-permission-level~1] assertEquals(5, message.getAttributes().getPermissionLevel()); assertEquals(UPriority.UPRIORITY_CS4, message.getAttributes().getPriority()); assertEquals(replyToAddress, message.getAttributes().getSource()); assertEquals(methodToInvoke, message.getAttributes().getSink()); + // [utest->dsn~up-attributes-request-token~1] assertEquals(token, message.getAttributes().getToken()); assertEquals(5000, message.getAttributes().getTtl()); + // [utest->dsn~up-attributes-traceparent~1] assertEquals(traceparent, message.getAttributes().getTraceparent()); + // [utest->dsn~up-attributes-request-type~1] assertEquals(UMessageType.UMESSAGE_TYPE_REQUEST, message.getAttributes().getType()); + // [utest->dsn~up-attributes-payload-format~1] assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_TEXT, message.getAttributes().getPayloadFormat()); + // [utest->req~uattributes-data-model-proto~1] + // [utest->req~umessage-data-model-proto~1] var proto = message.toByteString(); assertDoesNotThrow(() -> { var deserializedMessage = UMessage.parseFrom(proto); @@ -338,8 +383,8 @@ void testBuildRetainsAllRequestAttributes() { @Test void testBuilderCopiesRequestAttributes() { - var requestMessageId = UuidFactory.Factories.UPROTOCOL.factory().create(); - var responseMessageId = UuidFactory.Factories.UPROTOCOL.factory().create(); + var requestMessageId = UuidFactory.create(); + var responseMessageId = UuidFactory.create(); var methodToInvoke = UURI_METHOD; var replyToAddress = UURI_DEFAULT; UMessage requestMessage = UMessageBuilder.request(replyToAddress, methodToInvoke, 5000) @@ -351,22 +396,31 @@ void testBuilderCopiesRequestAttributes() { .withCommStatus(UCode.DEADLINE_EXCEEDED) .build(); + // [utest->dsn~up-attributes-id~1] assertEquals(responseMessageId, message.getAttributes().getId()); assertEquals(UCode.DEADLINE_EXCEEDED, message.getAttributes().getCommstatus()); assertEquals(UPriority.UPRIORITY_CS5, message.getAttributes().getPriority()); assertEquals(requestMessageId, message.getAttributes().getReqid()); + // [utest->dsn~up-attributes-response-source~1] assertEquals(methodToInvoke, message.getAttributes().getSource()); + // [utest->dsn~up-attributes-response-sink~1] assertEquals(replyToAddress, message.getAttributes().getSink()); assertEquals(5000, message.getAttributes().getTtl()); + // [utest->dsn~up-attributes-request-token~1] + assertFalse(message.getAttributes().hasToken()); + // [utest->dsn~up-attributes-response-type~1] assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, message.getAttributes().getType()); + // [utest->dsn~up-attributes-payload-format~1] assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_UNSPECIFIED, message.getAttributes().getPayloadFormat()); assertTrue(message.getPayload().isEmpty()); } @Test + // [utest->req~uattributes-data-model-impl~1] + // [utest->req~umessage-data-model-impl~1] void testBuildRetainsAllResponseAttributes() { - var messageId = UuidFactory.Factories.UPROTOCOL.factory().create(); - var requestId = UuidFactory.Factories.UPROTOCOL.factory().create(); + var messageId = UuidFactory.create(); + var requestId = UuidFactory.create(); var traceparent = "traceparent"; var methodToInvoke = UURI_METHOD; var replyToAddress = UURI_DEFAULT; @@ -378,18 +432,26 @@ void testBuildRetainsAllResponseAttributes() { .withTraceparent(traceparent) .build(); + // [utest->dsn~up-attributes-id~1] assertEquals(messageId, message.getAttributes().getId()); assertEquals(UCode.DEADLINE_EXCEEDED, message.getAttributes().getCommstatus()); assertEquals(UPriority.UPRIORITY_CS5, message.getAttributes().getPriority()); assertEquals(requestId, message.getAttributes().getReqid()); + // [utest->dsn~up-attributes-response-source~1] assertEquals(methodToInvoke, message.getAttributes().getSource()); + // [utest->dsn~up-attributes-response-sink~1] assertEquals(replyToAddress, message.getAttributes().getSink()); assertEquals(4000, message.getAttributes().getTtl()); + // [utest->dsn~up-attributes-traceparent~1] assertEquals(traceparent, message.getAttributes().getTraceparent()); + // [utest->dsn~up-attributes-response-type~1] assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, message.getAttributes().getType()); + // [utest->dsn~up-attributes-payload-format~1] assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_UNSPECIFIED, message.getAttributes().getPayloadFormat()); assertTrue(message.getPayload().isEmpty()); + // [utest->req~uattributes-data-model-proto~1] + // [utest->req~umessage-data-model-proto~1] var proto = message.toByteString(); assertDoesNotThrow(() -> { var deserializedMessage = UMessage.parseFrom(proto); diff --git a/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributesValidatorTest.java b/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributesValidatorTest.java index 9401d9dd..129510a9 100644 --- a/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributesValidatorTest.java +++ b/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributesValidatorTest.java @@ -15,6 +15,7 @@ import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertEquals; +import java.time.Instant; import java.util.Optional; import java.util.OptionalInt; import java.util.stream.Stream; @@ -24,6 +25,7 @@ import org.eclipse.uprotocol.v1.UMessageType; import org.eclipse.uprotocol.v1.UPriority; import org.eclipse.uprotocol.v1.UUID; +import org.eclipse.uprotocol.transport.builder.UMessageBuilder; import org.eclipse.uprotocol.uuid.factory.UuidFactory; import org.eclipse.uprotocol.v1.UUri; import org.eclipse.uprotocol.validation.ValidationException; @@ -101,18 +103,40 @@ void testGetValidatorReturnsMatchingValidator( assertEquals(expectedValidatorType, validator.messageType()); } + @ParameterizedTest + @CsvSource(useHeadersInDisplayName = true, textBlock = """ + TTL, expectedIsExpired + 0, false + 10000, true + 29999, true + 30000, true + 30001, false + """) + void testIsExpired(int ttl, boolean expectedIsExpired) { + var now = Instant.now(); + var uuid = UuidFactory.create(now.minusMillis(30_000)); + var message = UMessageBuilder.publish(UURI_TOPIC) + .withMessageId(uuid) + .withTtl(ttl) + .build(); + + var validator = UAttributesValidator.getValidator(message.getAttributes()); + assertEquals(expectedIsExpired, validator.isExpired(message.getAttributes())); + } + static Stream publishMessageArgProvider() { return Stream.of( Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_TOPIC), Optional.empty(), OptionalInt.empty(), true ), // fails for message containing destination + // [utest->dsn~up-attributes-publish-sink~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_TOPIC), Optional.of(UURI_DEFAULT), OptionalInt.empty(), @@ -120,37 +144,41 @@ static Stream publishMessageArgProvider() { ), // succeeds for valid attributes Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_TOPIC), Optional.empty(), OptionalInt.of(100), true ), // fails for missing topic + // [utest->dsn~up-attributes-publish-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.empty(), Optional.empty(), OptionalInt.empty(), false ), // fails for invalid topic + // [utest->dsn~up-attributes-publish-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.empty(), OptionalInt.empty(), false ), // fails for source with wildcard + // [utest->dsn~up-attributes-publish-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_WILDCARD_RESOURCE), Optional.empty(), OptionalInt.empty(), false ), // fails for missing message ID + // [utest->dsn~up-attributes-id~1] Arguments.of( Optional.empty(), Optional.of(UURI_TOPIC), @@ -159,6 +187,7 @@ static Stream publishMessageArgProvider() { false ), // fails for invalid message ID + // [utest->dsn~up-attributes-id~1] Arguments.of( Optional.of(UUID_INVALID), Optional.of(UURI_TOPIC), @@ -216,7 +245,7 @@ static Stream notificationMessageArgProvider() { return Stream.of( // succeeds for both origin and destination Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_TOPIC), Optional.of(UURI_DEFAULT), OptionalInt.empty(), @@ -225,7 +254,7 @@ static Stream notificationMessageArgProvider() { ), // succeeds for valid attributes Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_TOPIC), Optional.of(UURI_DEFAULT), OptionalInt.of(100), @@ -233,8 +262,9 @@ static Stream notificationMessageArgProvider() { true ), // fails for missing destination + // [utest->dsn~up-attributes-notification-sink~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_TOPIC), Optional.empty(), OptionalInt.empty(), @@ -242,8 +272,9 @@ static Stream notificationMessageArgProvider() { false ), // fails for missing origin + // [utest->dsn~up-attributes-notification-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.empty(), Optional.of(UURI_DEFAULT), OptionalInt.empty(), @@ -251,8 +282,9 @@ static Stream notificationMessageArgProvider() { false ), // fails for invalid origin + // [utest->dsn~up-attributes-notification-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.of(UURI_DEFAULT), OptionalInt.empty(), @@ -260,8 +292,9 @@ static Stream notificationMessageArgProvider() { false ), // fails for origin with wildcard + // [utest->dsn~up-attributes-notification-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_WILDCARD_RESOURCE), Optional.of(UURI_DEFAULT), OptionalInt.empty(), @@ -269,8 +302,9 @@ static Stream notificationMessageArgProvider() { false ), // fails for invalid destination + // [utest->dsn~up-attributes-notification-sink~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_TOPIC), Optional.of(UURI_METHOD), OptionalInt.empty(), @@ -278,8 +312,9 @@ static Stream notificationMessageArgProvider() { false ), // fails for destination with wildcard + // [utest->dsn~up-attributes-notification-sink~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_TOPIC), Optional.of(UURI_WILDCARD_RESOURCE), OptionalInt.empty(), @@ -287,8 +322,10 @@ static Stream notificationMessageArgProvider() { false ), // fails for neither origin nor destination + // [utest->dsn~up-attributes-notification-source~1] + // [utest->dsn~up-attributes-notification-sink~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.empty(), Optional.empty(), OptionalInt.empty(), @@ -305,6 +342,7 @@ static Stream notificationMessageArgProvider() { false ), // fails for missing message ID + // [utest->dsn~up-attributes-id~1] Arguments.of( Optional.empty(), Optional.of(UURI_TOPIC), @@ -314,6 +352,7 @@ static Stream notificationMessageArgProvider() { false ), // fails for invalid message ID + // [utest->dsn~up-attributes-id~1] Arguments.of( Optional.of(UUID_INVALID), Optional.of(UURI_TOPIC), @@ -367,7 +406,7 @@ static Stream requestMessageArgProvider() { return Stream.of( // succeeds for mandatory attributes Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.of(UURI_DEFAULT), OptionalInt.empty(), @@ -377,7 +416,7 @@ static Stream requestMessageArgProvider() { true), // succeeds for valid attributes Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.of(UURI_DEFAULT), OptionalInt.of(1), @@ -386,6 +425,7 @@ static Stream requestMessageArgProvider() { Optional.of("mytoken"), true), // fails for missing message ID + // [utest->dsn~up-attributes-id~1] Arguments.of( Optional.empty(), Optional.of(UURI_METHOD), @@ -396,6 +436,7 @@ static Stream requestMessageArgProvider() { Optional.of("mytoken"), false), // fails for invalid message ID + // [utest->dsn~up-attributes-id~1] Arguments.of( Optional.of(UUID_INVALID), Optional.of(UURI_METHOD), @@ -406,8 +447,9 @@ static Stream requestMessageArgProvider() { Optional.empty(), false), // fails for missing reply-to-address + // [utest->dsn~up-attributes-request-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.empty(), OptionalInt.empty(), @@ -416,8 +458,9 @@ static Stream requestMessageArgProvider() { Optional.empty(), false), // fails for invalid reply-to-address + // [utest->dsn~up-attributes-request-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.of(UURI_TOPIC), OptionalInt.empty(), @@ -426,8 +469,9 @@ static Stream requestMessageArgProvider() { Optional.empty(), false), // fails for reply-to-address with wildcard + // [utest->dsn~up-attributes-request-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.of(UURI_WILDCARD_RESOURCE), OptionalInt.empty(), @@ -436,8 +480,9 @@ static Stream requestMessageArgProvider() { Optional.empty(), false), // fails for missing method-to-invoke + // [utest->dsn~up-attributes-request-sink~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.empty(), Optional.of(UURI_DEFAULT), OptionalInt.empty(), @@ -446,8 +491,9 @@ static Stream requestMessageArgProvider() { Optional.empty(), false), // fails for invalid method-to-invoke + // [utest->dsn~up-attributes-request-sink~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_TOPIC), Optional.of(UURI_DEFAULT), OptionalInt.empty(), @@ -456,8 +502,9 @@ static Stream requestMessageArgProvider() { Optional.empty(), false), // fails for method-to-invoke with wildcard + // [utest->dsn~up-attributes-request-sink~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_WILDCARD_RESOURCE), Optional.of(UURI_DEFAULT), OptionalInt.empty(), @@ -467,7 +514,7 @@ static Stream requestMessageArgProvider() { false), // fails for missing priority Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.of(UURI_DEFAULT), OptionalInt.of(1), @@ -477,7 +524,7 @@ static Stream requestMessageArgProvider() { false), // fails for invalid priority Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.of(UURI_DEFAULT), OptionalInt.of(1), @@ -487,7 +534,7 @@ static Stream requestMessageArgProvider() { false), // fails for unknown priority Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.of(UURI_DEFAULT), OptionalInt.of(1), @@ -496,8 +543,9 @@ static Stream requestMessageArgProvider() { Optional.empty(), false), // fails for missing ttl + // [utest->dsn~up-attributes-request-ttl~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.of(UURI_DEFAULT), OptionalInt.empty(), @@ -506,8 +554,9 @@ static Stream requestMessageArgProvider() { Optional.empty(), false), // fails for ttl = 0 + // [utest->dsn~up-attributes-request-ttl~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.of(UURI_DEFAULT), OptionalInt.empty(), @@ -517,7 +566,7 @@ static Stream requestMessageArgProvider() { false), // fails for invalid (negative) permission level Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_METHOD), Optional.of(UURI_DEFAULT), OptionalInt.of(-1), @@ -575,157 +624,165 @@ static Stream responseMessageArgProvider() { return Stream.of( // succeeds for mandatory attributes Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.empty(), OptionalInt.empty(), Optional.of(UPriority.UPRIORITY_CS4), true), // succeeds for valid attributes Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.of(UCode.CANCELLED_VALUE), OptionalInt.of(100), Optional.of(UPriority.UPRIORITY_CS4), true), // fails for missing message ID + // [utest->dsn~up-attributes-id~1] Arguments.of( Optional.empty(), Optional.of(UURI_DEFAULT), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.of(UCode.CANCELLED_VALUE), OptionalInt.of(100), Optional.of(UPriority.UPRIORITY_CS4), false), // fails for invalid message ID + // [utest->dsn~up-attributes-id~1] Arguments.of( Optional.of(UUID_INVALID), Optional.of(UURI_DEFAULT), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.empty(), OptionalInt.empty(), Optional.of(UPriority.UPRIORITY_CS4), false), // fails for missing reply-to-address + // [utest->dsn~up-attributes-response-sink~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.empty(), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.empty(), OptionalInt.empty(), Optional.of(UPriority.UPRIORITY_CS4), false), // fails for invalid reply-to-address + // [utest->dsn~up-attributes-response-sink~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_TOPIC), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.empty(), OptionalInt.empty(), Optional.of(UPriority.UPRIORITY_CS4), false), // fails for reply-to-address with wildcard + // [utest->dsn~up-attributes-response-sink~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_WILDCARD_RESOURCE), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.empty(), OptionalInt.empty(), Optional.of(UPriority.UPRIORITY_CS4), false), // fails for missing invoked-method + // [utest->dsn~up-attributes-response-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.empty(), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.empty(), OptionalInt.empty(), Optional.of(UPriority.UPRIORITY_CS4), false), // fails for invalid invoked-method + // [utest->dsn~up-attributes-response-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.of(UURI_TOPIC), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.empty(), OptionalInt.empty(), Optional.of(UPriority.UPRIORITY_CS4), false), // fails for invoked-method with wildcard + // [utest->dsn~up-attributes-response-source~1] Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.of(UURI_WILDCARD_RESOURCE), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.empty(), OptionalInt.empty(), Optional.of(UPriority.UPRIORITY_CS4), false), // fails for invalid commstatus Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.of(-189), OptionalInt.empty(), Optional.of(UPriority.UPRIORITY_CS4), false), // succeeds for ttl > 0 Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.empty(), OptionalInt.of(100), Optional.of(UPriority.UPRIORITY_CS4), true), // succeeds for ttl = 0 Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.empty(), OptionalInt.of(0), Optional.of(UPriority.UPRIORITY_CS4), true), // fails for missing priority Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.of(UCode.CANCELLED_VALUE), OptionalInt.of(100), Optional.empty(), false), // fails for invalid priority Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.of(UURI_METHOD), - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), OptionalInt.of(UCode.CANCELLED_VALUE), OptionalInt.of(100), Optional.of(UPriority.UPRIORITY_CS3), false), // fails for missing request ID Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.of(UURI_METHOD), Optional.empty(), @@ -735,7 +792,7 @@ static Stream responseMessageArgProvider() { false), // fails for invalid request ID Arguments.of( - Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UuidFactory.create()), Optional.of(UURI_DEFAULT), Optional.of(UURI_METHOD), Optional.of(UUID_INVALID), diff --git a/src/test/java/org/eclipse/uprotocol/uri/UuriTests.java b/src/test/java/org/eclipse/uprotocol/uri/UuriTests.java new file mode 100644 index 00000000..4173593a --- /dev/null +++ b/src/test/java/org/eclipse/uprotocol/uri/UuriTests.java @@ -0,0 +1,157 @@ +/** + * SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.uprotocol.uri; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.eclipse.uprotocol.uri.serializer.UriSerializer; +import org.eclipse.uprotocol.uri.validator.UriValidator; +import org.eclipse.uprotocol.v1.UUri; +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.SelectFile; +import org.junit.platform.suite.api.Suite; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; + +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; + +@Suite +@IncludeEngines("cucumber") +// [utest->dsn~uri-pattern-matching~2] +// TODO: replace with feature from up-spec once the referenced version contains fixes +// see https://github.com/eclipse-uprotocol/up-spec/issues/302 +// @SelectFile("up-spec/basics/uuri_pattern_matching.feature") +@SelectClasspathResource("features/uuri_pattern_matching.feature") +// [utest->req~uri-data-model-proto~1] +@SelectFile("up-spec/basics/uuri_protobuf_serialization.feature") +// [utest->req~uri-serialization~1] +// TODO: replace with feature from up-spec once the referenced version contains missing examples +// see https://github.com/eclipse-uprotocol/up-spec/issues/300 +// @SelectFile("up-spec/basics/uuri_uri_serialization.feature") +@SelectClasspathResource("features/uuri_uri_serialization.feature") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "org.eclipse.uprotocol.uri") +public class UuriTests { + + private UUri.Builder builder = UUri.newBuilder(); + private UUri uuri; + private String uri; + private ByteString protobuf; + private Exception error; + + private static int getUnsignedInt(String s) { + if (s.startsWith("0x")) { + return Integer.parseUnsignedInt(s.substring(2), 16); + } else { + return Integer.parseUnsignedInt(s); + } + } + + @Given("a URI string {string}") + public void withUriString(String uriString) { + this.uri = uriString; + } + + @Given("a UUri having authority {string}") + public void withAuthority(String authorityName) { + builder.setAuthorityName(authorityName); + } + + @Given("having entity identifier {word}") + public void withEntityId(String entityId) { + builder.setUeId(getUnsignedInt(entityId)); + } + + @Given("having major version {word}") + public void withMajorVersion(String majorVersion) { + builder.setUeVersionMajor(getUnsignedInt(majorVersion)); + } + + @Given("having resource identifier {word}") + public void withResourceId(String resourceId) { + builder.setResourceId(getUnsignedInt(resourceId)); + } + + @When("serializing the UUri to its protobuf wire format") + public void serializeToProtobuf() { + uuri = builder.build(); + protobuf = uuri.toByteString(); + } + + @When("serializing the UUri to a URI") + public void serializeToUri() { + uuri = builder.build(); + uri = UriSerializer.serialize(uuri, true); + } + + @When("deserializing the URI to a UUri") + public void deserializeFromUri() { + try { + uuri = UriSerializer.deserialize(uri); + } catch (Exception e) { + error = e; + } + } + + @Then("the resulting URI string is {word}") + public void assertUriString(String expectedUri) { + assertEquals(expectedUri, uri); + } + + @Then("the original UUri can be recreated from the protobuf wire format") + public void assertOriginalUuriCanBeRecreatedFromProtobuf() throws InvalidProtocolBufferException { + var deserializedUuri = UUri.parseFrom(protobuf); + assertEquals(uuri, deserializedUuri); + } + + @Then("the same UUri can be deserialized from {word}") + public void assertUuriCanBeDeserializedFromBytes(String hexString) throws InvalidProtocolBufferException { + var proto = ByteString.fromHex(hexString); + var deserializedUuri = UUri.parseFrom(proto); + assertEquals(uuri, deserializedUuri); + } + + @Then("the original UUri can be recreated from the URI string") + public void assertOriginalUuriCanBeRecreatedFromUriString() { + var deserializedUuri = UriSerializer.deserialize(uri); + assertEquals(uuri, deserializedUuri); + } + + @Then("the UUri matches pattern {word}") + public void assertUuriMatchesPattern(String pattern) { + assertNotNull(uuri); + var patternUri = UriSerializer.deserialize(pattern); + assertTrue(UriValidator.matches(patternUri, uuri)); + } + + @Then("the UUri does not match pattern {word}") + public void assertUuriDoesNotMatchPattern(String pattern) { + assertNotNull(uuri); + var patternUri = UriSerializer.deserialize(pattern); + assertFalse(UriValidator.matches(patternUri, uuri)); + } + + @Then("the attempt fails") + public void assertFailure() { + assertNotNull(error); + } +} diff --git a/src/test/java/org/eclipse/uprotocol/uri/serializer/UriSerializerTest.java b/src/test/java/org/eclipse/uprotocol/uri/serializer/UriSerializerTest.java index efbb5ba3..2173b2b5 100644 --- a/src/test/java/org/eclipse/uprotocol/uri/serializer/UriSerializerTest.java +++ b/src/test/java/org/eclipse/uprotocol/uri/serializer/UriSerializerTest.java @@ -12,145 +12,41 @@ */ package org.eclipse.uprotocol.uri.serializer; -import org.eclipse.uprotocol.v1.UUri; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*; import java.net.URI; -import java.util.Optional; class UriSerializerTest { - @ParameterizedTest(name = "Test serializing a valid UUri succeeds [{index}] {arguments}") - @CsvSource(useHeadersInDisplayName = true, textBlock = """ - authority, ueId, ueVersion, resourceId, expectedUri - , , , , /0/0/0 - myAuthority, , , , //myAuthority/0/0/0 - myAuthority, 0x0000_abcd, , , //myAuthority/ABCD/0/0 - myAuthority, 0x0000_abcd, 0x02, , //myAuthority/ABCD/2/0 - myAuthority, 0x001f_abcd, 0x02, 0x3d4b, //myAuthority/1FABCD/2/3D4B - *, 0xb1a, 0x01, 0x8aa1, //*/B1A/1/8AA1 - myAuthority, 0x0000_ffff, 0x01, 0x8aa1, //myAuthority/FFFF/1/8AA1 - # using -62694 to represent 0xffff_0b1a which fails to be parsed by CsvSource - # because CsvSource does not support parsing hex strings as unsigned integers - myAuthority, -62694, 0x01, 0x8aa1, //myAuthority/FFFF0B1A/1/8AA1 - myAuthority, 0xb1a, 0xff, 0x8aa1, //myAuthority/B1A/FF/8AA1 - myAuthority, 0xb1a, 0x01, 0xffff, //myAuthority/B1A/1/FFFF - # using -1 (2s complement) to represent 0xffff_ffff which fails to be parsed by CsvSource - # because CsvSource does not support parsing hex strings as unsigned integers - *, -1, 0xff, 0xffff, //*/FFFFFFFF/FF/FFFF - """) - void testSerializingValidUUriSucceeds( - String authority, - Integer ueId, - Integer ueVersion, - Integer resourceId, - String expectedUri) { - final var builder = UUri.newBuilder(); - Optional.ofNullable(authority).ifPresent(builder::setAuthorityName); - Optional.ofNullable(ueId).ifPresent(builder::setUeId); - Optional.ofNullable(ueVersion).ifPresent(builder::setUeVersionMajor); - Optional.ofNullable(resourceId).ifPresent(builder::setResourceId); - UUri originalUuri = builder.build(); - - String correspondingUri = UriSerializer.serialize(originalUuri); - assertEquals(expectedUri, correspondingUri); - assertEquals(originalUuri, UriSerializer.deserialize(correspondingUri)); - } + private static final int AUTHORITY_NAME_MAX_LENGTH = 128; @Test @DisplayName("Test serializing a null UUri fails") void testSerializingANullUuri() { - assertThrows(NullPointerException.class, () -> { - UriSerializer.serialize(null); - }); - } - - // - // tests for deserializing URIs - // - - @ParameterizedTest(name = "Test deserializing a valid Uri succeeds [{index}] {arguments}") - @CsvSource(useHeadersInDisplayName = true, textBlock = """ - URI, expectedAuthority, expectedUeId, expectedVersion, expectedResourceId - /0/0/0, , 0x0000_0000, 0x00, 0x0000 - up:/0/0/0, , 0x0000_0000, 0x00, 0x0000 - //auth.dev/0/0/0, auth.dev, 0x0000_0000, 0x00, 0x0000 - //192.168.1.0/ABCD/0/0, 192.168.1.0, 0x0000_abcd, 0x00, 0x0000 - //auth/ABCD/2/0, auth, 0x0000_abcd, 0x02, 0x0000 - up://auth/ABCD/2/3D4B, auth, 0x0000_abcd, 0x02, 0x3d4b - /1234/1/5678, , 0x0000_1234, 0x01, 0x5678 - //*/1234/1/5678, *, 0x0000_1234, 0x01, 0x5678 - //auth/FFFF/1/5678, auth, 0x0000_ffff, 0x01, 0x5678 - # using -62694 to represent 0xffff_0b1a which fails to be parsed by CsvSource - # because CsvSource does not support parsing hex strings as unsigned integers - //auth/FFFF0B1A/1/5678, auth, -62694, 0x01, 0x5678 - //auth/1234/FF/5678, auth, 0x0000_1234, 0xff, 0x5678 - //auth/1234/1/FFFF, auth, 0x0000_1234, 0x01, 0xffff - # using -1 to represent 0xffff_ffff which fails to be parsed by CsvSource - # because CsvSource does not support parsing hex strings as unsigned integers - //*/FFFFFFFF/FF/FFFF, *, -1, 0xff, 0xffff - """) - void testDeserializeValidUriSucceeds( - String uri, - String expectedAuthority, - Integer expectedUeId, - Integer expectedVersion, - Integer expectedResourceId) { - - final var uuri = UriSerializer.deserialize(uri); - Optional.ofNullable(expectedAuthority).ifPresent(s -> assertEquals(s, uuri.getAuthorityName())); - Optional.ofNullable(expectedUeId).ifPresent(s -> assertEquals(s, uuri.getUeId())); - Optional.ofNullable(expectedVersion).ifPresent(s -> assertEquals(s, uuri.getUeVersionMajor())); - Optional.ofNullable(expectedResourceId).ifPresent(s -> assertEquals(s, uuri.getResourceId())); + assertThrows(NullPointerException.class, () -> UriSerializer.serialize(null)); } @Test @DisplayName("Test deserializing a null UUri fails") - public void testDeserializingANullUuriFails() { - assertThrows(NullPointerException.class, () -> { - UriSerializer.deserialize((String) null); - }); - assertThrows(NullPointerException.class, () -> { - UriSerializer.deserialize((URI) null); - }); + void testDeserializingANullUuriFails() { + assertThrows(NullPointerException.class, () -> UriSerializer.deserialize((String) null)); + assertThrows(NullPointerException.class, () -> UriSerializer.deserialize((URI) null)); } - @ParameterizedTest(name = "Test deserializing an invalid URI fails [{index}] {arguments}") - @ValueSource(strings = { - " ", - "$$", - "up://", - "up://just_an_authority", - "/ABC", - "/ABC/1", - "//myhost/ABC", - "//myhost/ABC/1", - "//myhost//1/A000", - "//myhost/ABC//A000", - "//myhost/ABC/1//", - "//myhost/not-hex/1/2341", - "//myhost/1/not-hex/2341", - "//myhost/1/1/not-hex", - "//myhost/-1/1/2341", - "invalidscheme://myhost/A000/1/2341", - "up://myhost/A000/1/2341#invalid", - "up://myhost/A000/1/2341?param=invalid", - "up://myhost/100000000/1/2341", - "//myhost/A1B/-1/2341", - "//myhost/A1B/100/2341", - "up://myhost/A1B/1/-1", - "//myhost/A1B/1/10000" - }) - void testDeserializeInvalidUriFails(String uri) { - assertThrows(IllegalArgumentException.class, () -> { - UriSerializer.deserialize(uri); - }); + @Test + @DisplayName("Test deserializing a UUri with authority name exceeding max length fails") + // [utest->dsn~uri-authority-name-length~1] + void testDeserializeRejectsAuthorityNameExceedingMaxLength() { + String authority = "a".repeat(AUTHORITY_NAME_MAX_LENGTH); + String validUri = "up://%s/ABCD/1/1001".formatted(authority); + assertDoesNotThrow(() -> UriSerializer.deserialize(validUri)); + + authority = "a".repeat(AUTHORITY_NAME_MAX_LENGTH + 1); + var invalidUri = "up://%s/ABCD/1/1001".formatted(authority); + assertThrows(IllegalArgumentException.class, () -> UriSerializer.deserialize(invalidUri)); } } diff --git a/src/test/java/org/eclipse/uprotocol/uri/validator/UriValidatorTest.java b/src/test/java/org/eclipse/uprotocol/uri/validator/UriValidatorTest.java index 100e315e..5211d219 100644 --- a/src/test/java/org/eclipse/uprotocol/uri/validator/UriValidatorTest.java +++ b/src/test/java/org/eclipse/uprotocol/uri/validator/UriValidatorTest.java @@ -166,33 +166,60 @@ void testHasWildcard(String uri, boolean shouldSucceed) { } } - @ParameterizedTest(name = "Test matches: {index} {arguments}") - @CsvSource(useHeadersInDisplayName = true, textBlock = """ - pattern, candidate, should match - //authority/A410/3/1003, //authority/A410/3/1003, true - //authority/2A410/3/1003, //authority/2A410/3/1003, true - //*/A410/3/1003, //authority/A410/3/1003, true - //*/A410/3/1003, /A410/3/1003, true - //authority/FFFF/3/1003, //authority/A410/3/1003, true - //authority/FFFFA410/3/1003, //authority/2A410/3/1003, true - //authority/A410/FF/1003, //authority/A410/3/1003, true - //authority/A410/3/FFFF, //authority/A410/3/1003, true - //Authority/A410/3/1003, //authority/A410/3/1003, false - //other/A410/3/1003, //authority/A410/3/1003, false - /A410/3/1003, //authority/A410/3/1003, false - //authority/45/3/1003, //authority/A410/3/1003, false - //authority/2A410/3/1003, //authority/A410/3/1003, false - //authority/A410/1/1003, //authority/A410/3/1003, false - //authority/A410/3/ABCD, //authority/A410/3/1003, false + @ParameterizedTest(name = "Test URI matches pattern: {index} {arguments}") + @CsvSource(useHeadersInDisplayName = true, delimiter = '|', textBlock = """ + uri | pattern + /1/1/A1FB | /1/1/A1FB + /1/1/A1FB | //*/1/1/A1FB + /1/1/A1FB | /FFFF/1/A1FB + /1/1/A1FB | //*/FFFF/1/A1FB + /1/1/A1FB | /FFFFFFFF/1/A1FB + /1/1/A1FB | //*/FFFFFFFF/1/A1FB + /1/1/A1FB | /1/FF/A1FB + /1/1/A1FB | //*/1/FF/A1FB + /1/1/A1FB | /1/1/FFFF + /1/1/A1FB | //*/1/1/FFFF + /1/1/A1FB | /FFFFFFFF/FF/FFFF + /1/1/A1FB | //*/FFFFFFFF/FF/FFFF + /10001/1/A1FB | /10001/1/A1FB + /10001/1/A1FB | //*/10001/1/A1FB + /10001/1/A1FB | /FFFFFFFF/1/A1FB + /10001/1/A1FB | //*/FFFFFFFF/1/A1FB + /10001/1/A1FB | /FFFFFFFF/FF/FFFF + /10001/1/A1FB | //*/FFFFFFFF/FF/FFFF + //vcu.my_vin/1/1/A1FB | //vcu.my_vin/1/1/A1FB + //vcu.my_vin/1/1/A1FB | //*/1/1/A1FB """ ) - void testMatches(String pattern, String candidate, boolean shouldMatch) { + // TODO: replace with Cucumber based test in UuriTests.java + // [utest->dsn~uri-pattern-matching~2] + void testMatchesSucceeds(String uri, String pattern) { UUri patternUri = UriSerializer.deserialize(pattern); - UUri candidateUri = UriSerializer.deserialize(candidate); - if (shouldMatch) { - assertTrue(UriValidator.matches(patternUri, candidateUri)); - } else { - assertFalse(UriValidator.matches(patternUri, candidateUri)); - } + UUri candidateUri = UriSerializer.deserialize(uri); + assertTrue(UriValidator.matches(patternUri, candidateUri)); + } + + @ParameterizedTest(name = "Test URI does not match pattern: {index} {arguments}") + @CsvSource(useHeadersInDisplayName = true, delimiter = '|', textBlock = """ + uri | pattern + /1/1/A1FB | //mcu1/1/1/A1FB + //vcu.my_vin/1/1/A1FB | //mcu1/1/1/A1FB + //vcu/B1A5/1/A1FB | //vc/FFFFFFFF/FF/FFFF + /B1A5/1/A1FB | //*/25B1/FF/FFFF + /B1A5/1/A1FB | //*/FFFFFFFF/2/FFFF + /B1A5/1/A1FB | //*/FFFFFFFF/FF/ABCD + /B1A5/1/A1FB | /25B1/1/A1FB + /B1A5/1/A1FB | /2B1A5/1/A1FB + /10B1A5/1/A1FB | /40B1A5/1/A1FB + /B1A5/1/A1FB | /B1A5/4/A1FB + /B1A5/1/A1FB | /B1A5/1/90FB + """ + ) + // TODO: replace with Cucumber based test in UuriTests.java + // [utest->dsn~uri-pattern-matching~2] + void testMatchesFails(String uri, String pattern) { + UUri patternUri = UriSerializer.deserialize(pattern); + UUri candidateUri = UriSerializer.deserialize(uri); + assertFalse(UriValidator.matches(patternUri, candidateUri)); } } diff --git a/src/test/java/org/eclipse/uprotocol/uuid/UuidTests.java b/src/test/java/org/eclipse/uprotocol/uuid/UuidTests.java new file mode 100644 index 00000000..c4574405 --- /dev/null +++ b/src/test/java/org/eclipse/uprotocol/uuid/UuidTests.java @@ -0,0 +1,113 @@ +/** + * SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.uprotocol.uuid; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.eclipse.uprotocol.uuid.serializer.UuidSerializer; +import org.eclipse.uprotocol.v1.UUID; +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectFile; +import org.junit.platform.suite.api.Suite; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; + +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; + +@Suite +@IncludeEngines("cucumber") +// [utest->req~uuid-proto~1] +// @SelectFile("up-spec/basics/uuid_protobuf_serialization.feature") +// [utest->req~uuid-hex-and-dash~1] +@SelectFile("up-spec/basics/uuid_string_serialization.feature") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "org.eclipse.uprotocol.uuid") +public class UuidTests { + private UUID uuid; + private String hyphenatedString; + private ByteString protobuf; + private Exception error; + + private static long getUnsignedLong(String s) { + if (s.startsWith("0x")) { + return Long.parseUnsignedLong(s.substring(2), 16); + } else { + return Long.parseUnsignedLong(s); + } + } + + @Given("a UUID having MSB {word} and LSB {word}") + public void withMsbLsb(String msbHexString, String lsbHexString) { + this.uuid = UUID.newBuilder() + .setMsb(getUnsignedLong(msbHexString)) + .setLsb(getUnsignedLong(lsbHexString)) + .build(); + } + + @Given("a UUID string representation {word}") + public void withHyphenatedString(String hyphenatedString) { + this.hyphenatedString = hyphenatedString; + } + + @When("serializing the UUID to its protobuf wire format") + public void serializeToProtobuf() { + this.protobuf = uuid.toByteString(); + } + + @When("serializing the UUID to a hyphenated string") + public void serializeToHyphenatedString() { + this.hyphenatedString = UuidSerializer.serialize(uuid); + } + + @When("deserializing the hyphenated string to a UUID") + public void deserializeFromHyphenatedString() { + try { + this.uuid = UuidSerializer.deserialize(hyphenatedString); + } catch (Exception e) { + error = e; + } + } + + @Then("the resulting hyphenated string is {word}") + public void assertHyphenatedString(String expectedString) { + assertEquals(this.hyphenatedString, expectedString); + } + + @Then("the original UUID can be recreated from the protobuf wire format") + public void assertOriginalUuidCanBeRecreatedFromProtobuf() throws InvalidProtocolBufferException { + UUID recreatedUuid = UUID.parseFrom(protobuf); + assertEquals(this.uuid, recreatedUuid); + } + + @Then("the same UUID can be deserialized from {word}") + public void assertDeserializeUuidFromProtobuf(String hexString) throws InvalidProtocolBufferException { + var deserializedUuid = UUID.parseFrom(ByteString.fromHex(hexString)); + assertEquals(this.uuid, deserializedUuid); + } + + @Then("the original UUID can be recreated from the hyphenated string") + public void assertOriginalUuidCanBeRecreatedFromHyphenatedString() { + assertEquals(this.uuid, UuidSerializer.deserialize(hyphenatedString)); + } + + @Then("the attempt fails") + public void assertFailure() { + assertNotNull(this.error); + } +} diff --git a/src/test/java/org/eclipse/uprotocol/uuid/factory/UUIDFactoryTest.java b/src/test/java/org/eclipse/uprotocol/uuid/factory/UUIDFactoryTest.java deleted file mode 100644 index 8b00a90c..00000000 --- a/src/test/java/org/eclipse/uprotocol/uuid/factory/UUIDFactoryTest.java +++ /dev/null @@ -1,231 +0,0 @@ -/** - * 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 - */ -package org.eclipse.uprotocol.uuid.factory; - -import org.eclipse.uprotocol.uuid.serializer.UuidSerializer; -import org.eclipse.uprotocol.v1.UUID; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; - -public class UUIDFactoryTest { - - @Test - @DisplayName("Test UUIDv7 Creation") - void testUuidv7Creation() { - final Instant now = Instant.now(); - final UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create(now); - final Optional time = UuidUtils.getTime(uuid); - final String uuidString = UuidSerializer.serialize(uuid); - - assertNotNull(uuid); - assertTrue(UuidUtils.isUProtocol(uuid)); - assertTrue(UuidUtils.isUuid(uuid)); - assertFalse(UuidUtils.isUuidv6(uuid)); - assertTrue(time.isPresent()); - assertEquals(time.get(), now.toEpochMilli()); - - assertFalse(uuidString.isBlank()); - - final UUID uuid2 = UuidSerializer.deserialize(uuidString); - assertFalse(uuid2.equals(UUID.getDefaultInstance())); - assertEquals(uuid, uuid2); - } - - @Test - @DisplayName("Test UUIDv7 Creation with null Instant") - void testUuidv7CreationWithNullInstant() { - final UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create(null); - final Optional time = UuidUtils.getTime(uuid); - final String uuidString = UuidSerializer.serialize(uuid); - - assertNotNull(uuid); - assertTrue(UuidUtils.isUProtocol(uuid)); - assertTrue(UuidUtils.isUuid(uuid)); - assertFalse(UuidUtils.isUuidv6(uuid)); - assertTrue(time.isPresent()); - assertFalse(uuidString.isBlank()); - - final UUID uuid2 = UuidSerializer.deserialize(uuidString); - - assertFalse(uuid2.equals(UUID.getDefaultInstance())); - assertEquals(uuid, uuid2); - } - - - - @Test - @DisplayName("Test UUIDv6 creation with Instance") - void testUuidv6CreationWithInstant() { - final Instant now = Instant.now(); - final UUID uuid = UuidFactory.Factories.UUIDV6.factory().create(now); - final Optional time = UuidUtils.getTime(uuid); - final String uuidString = UuidSerializer.serialize(uuid); - - assertNotNull(uuid); - assertTrue(UuidUtils.isUuidv6(uuid)); - assertTrue(UuidUtils.isUuid(uuid)); - assertFalse(UuidUtils.isUProtocol(uuid)); - assertTrue(time.isPresent()); - assertEquals(time.get(), now.toEpochMilli()); - assertFalse(uuidString.isBlank()); - - final UUID uuid2 = UuidSerializer.deserialize(uuidString); - assertFalse(uuid2.equals(UUID.getDefaultInstance())); - assertEquals(uuid, uuid2); } - - @Test - @DisplayName("Test UUIDv6 creation with null Instant") - void testUuidv6CreationWithNullInstant() { - final UUID uuid = UuidFactory.Factories.UUIDV6.factory().create(null); - final Optional time = UuidUtils.getTime(uuid); - final String uuidString = UuidSerializer.serialize(uuid); - - assertNotNull(uuid); - assertTrue(UuidUtils.isUuidv6(uuid)); - assertFalse(UuidUtils.isUProtocol(uuid)); - assertTrue(UuidUtils.isUuid(uuid)); - assertTrue(time.isPresent()); - assertFalse(uuidString.isBlank()); - - final UUID uuid2 = UuidSerializer.deserialize(uuidString); - assertFalse(uuid2.equals(UUID.getDefaultInstance())); - assertEquals(uuid, uuid2); - } - - @Test - @DisplayName("Test UUIDUtils for Random UUID") - void testUuidutilsForRandomUuid() { - final var uuidJava = java.util.UUID.randomUUID(); - final UUID uuid = UUID.newBuilder().setMsb(uuidJava.getMostSignificantBits()) - .setLsb(uuidJava.getLeastSignificantBits()).build(); - final Optional time = UuidUtils.getTime(uuid); - final String uuidString = UuidSerializer.serialize(uuid); - - assertNotNull(uuid); - assertFalse(UuidUtils.isUuidv6(uuid)); - assertFalse(UuidUtils.isUProtocol(uuid)); - assertFalse(UuidUtils.isUuid(uuid)); - assertFalse(time.isPresent()); - assertFalse(uuidString.isBlank()); - - final UUID uuid2 = UuidSerializer.deserialize(uuidString); - - assertFalse(uuid2.equals(UUID.getDefaultInstance())); - assertEquals(uuid, uuid2); - } - - @Test - @DisplayName("Test UUIDUtils for empty UUID") - void testUuidutilsForEmptyUuid() { - final UUID uuid = UUID.newBuilder().setMsb(0L).setLsb(0L).build(); - final Optional time = UuidUtils.getTime(uuid); - final String uuidString = UuidSerializer.serialize(uuid); - - assertNotNull(uuid); - assertFalse(UuidUtils.isUuidv6(uuid)); - assertFalse(UuidUtils.isUProtocol(uuid)); - assertFalse(time.isPresent()); - assertFalse(uuidString.isBlank()); - - final UUID uuid2 = UuidSerializer.deserialize(uuidString); - assertTrue(uuid2.equals(UUID.getDefaultInstance())); - assertEquals(uuid, uuid2); - } - - @Test - @DisplayName("Test UUIDUtils for a null UUID") - void testUuidutilsForNullUuid() { - assertTrue(UuidSerializer.serialize(null).isBlank()); - assertFalse(UuidUtils.isUuidv6(null)); - assertFalse(UuidUtils.isUProtocol(null)); - assertFalse(UuidUtils.isUuid(null)); - assertFalse(UuidUtils.getTime(null).isPresent()); - } - - @Test - @DisplayName("Test UUIDUtils fromString an invalid built UUID") - void testUuidutilsFromInvalidUuid() { - final UUID uuid = UUID.newBuilder().setMsb(9 << 12).setLsb(0L).build(); // Invalid UUID type - - assertFalse(UuidUtils.getTime(uuid).isPresent()); - assertFalse(UuidSerializer.serialize(uuid).isBlank()); - assertFalse(UuidUtils.isUuidv6(uuid)); - assertFalse(UuidUtils.isUProtocol(uuid)); - assertFalse(UuidUtils.isUuid(uuid)); - assertFalse(UuidUtils.getTime(uuid).isPresent()); - } - - - @Test - @DisplayName("Test UUIDUtils fromString with invalid string") - void testUuidutilsFromstringWithInvalidString() { - final UUID uuid = UuidSerializer.deserialize(null); - assertTrue(uuid.equals(UUID.getDefaultInstance())); - final UUID uuid1 = UuidSerializer.deserialize(""); - assertTrue(uuid1.equals(UUID.getDefaultInstance())); - } - - - @Test - @DisplayName("Test Create UProtocol UUID in the past") - void testCreateUprotocolUuidInThePast() { - - final Instant past = Instant.now().minusSeconds(10); - final UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create(past); - - final Optional time = UuidUtils.getTime(uuid); - - assertTrue(UuidUtils.isUProtocol(uuid)); - assertTrue(UuidUtils.isUuid(uuid)); - - assertTrue(time.isPresent()); - assertEquals(time.get(), past.toEpochMilli()); - - } - - @Test - @DisplayName("Test Create UProtocol UUID with different time values") - void testCreateUprotocolUuidWithDifferentTimeValues() throws InterruptedException { - - final UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create(); - Thread.sleep(10); - final UUID uuid1 = UuidFactory.Factories.UPROTOCOL.factory().create(); - - final Optional time = UuidUtils.getTime(uuid); - final Optional time1 = UuidUtils.getTime(uuid1); - - assertTrue(UuidUtils.isUProtocol(uuid)); - assertTrue(UuidUtils.isUuid(uuid)); - assertTrue(UuidUtils.isUProtocol(uuid1)); - assertTrue(UuidUtils.isUuid(uuid1)); - - assertTrue(time.isPresent()); - assertNotEquals(time.get(), time1.get()); - - } - - @Test - @DisplayName("Test Create UUIDv7 with the same time to confirm the UUIDs are not the same") - void testCreateUuidv7WithTheSameTimeToConfirmTheUuidsAreNotTheSame() { - Instant now = Instant.now(); - final UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create(now); - final UUID uuid1 = UuidFactory.Factories.UPROTOCOL.factory().create(now); - assertNotEquals(uuid, uuid1); - assertEquals(UuidUtils.getTime(uuid1).get(), UuidUtils.getTime(uuid).get()); - } -} diff --git a/src/test/java/org/eclipse/uprotocol/uuid/factory/UuidUtilsTest.java b/src/test/java/org/eclipse/uprotocol/uuid/factory/UuidUtilsTest.java index 0daba948..82c2ce3b 100644 --- a/src/test/java/org/eclipse/uprotocol/uuid/factory/UuidUtilsTest.java +++ b/src/test/java/org/eclipse/uprotocol/uuid/factory/UuidUtilsTest.java @@ -13,7 +13,6 @@ package org.eclipse.uprotocol.uuid.factory; import org.eclipse.uprotocol.v1.UUID; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -24,37 +23,16 @@ import java.time.Instant; import java.util.stream.Stream; +// [utest->dsn~uuid-spec~1] +// [utest->req~uuid-type~1] class UuidUtilsTest { - private static final int DELTA = 30; - private static final int DELAY_MS = 100; private static final int TTL = 10000; - private static Stream provideUuidsForIsUuidv6() { - return Stream.of( - Arguments.of(UUID.newBuilder() - .setMsb(0x0000000000006000L) - .setLsb(0xA000000000000000L) - .build(), true), - Arguments.of(UUID.newBuilder() - .setMsb(0x0000000000007000L) - .setLsb(0xA000000000000000L) - .build(), false), - Arguments.of(UUID.newBuilder() - .setMsb(0x0000000000006000L) - .setLsb(0xC000000000000000L) - .build(), false), - Arguments.of(UUID.newBuilder() - .setMsb(0x0000000000007000L) - .setLsb(0xC000000000000000L) - .build(), false), - Arguments.of(null, false) - ); - } - - @ParameterizedTest(name = "Test isUuidv6 {index} - {0}") - @MethodSource("provideUuidsForIsUuidv6") - void testIsUuidv6(UUID uuid, boolean expected) { - assertEquals(expected, UuidUtils.isUuidv6(uuid)); + private UUID invalidUuid() { + return UUID.newBuilder() + .setMsb(0x0000000000006000L) // version 6 + .setLsb(0xC000000000000000L) // variant not RFC 9562 + .build(); } private static Stream provideUuidsForIsUProtocol() { @@ -85,85 +63,128 @@ void testIsUProtocol(UUID uuid, boolean expected) { assertEquals(expected, UuidUtils.isUProtocol(uuid)); } - @Test - void testIsRfc9562Variant() { - // variant 0b10 (RFC9562) - UUID id = UUID.newBuilder().setLsb(0xA000000000000000L).build(); - assertTrue(UuidUtils.isRfc9562Variant(id)); - // variant 0b11 (Reserved. Microsoft Corporation backward compatibility.) - id = UUID.newBuilder().setLsb(0xC000000000000000L).build(); - assertFalse(UuidUtils.isRfc9562Variant(id)); + private static Stream provideCreationTimestamps() { + return Stream.of( + Instant.now().minusSeconds(60), + Instant.now(), + Instant.now().plusSeconds(60) + ); + } + + @ParameterizedTest(name = "Test getting timestamp from UUID {index} - {arguments}") + @MethodSource("provideCreationTimestamps") + void testGetTime(Instant timestamp) { + final UUID uuid = UuidFactory.create(timestamp); + final var time = UuidUtils.getTime(uuid); + assertEquals(timestamp.toEpochMilli(), time); } @Test - public void testGetElapsedTime() throws InterruptedException { - final UUID id = createId(); - Thread.sleep(DELAY_MS); - assertEquals(DELAY_MS, UuidUtils.getElapsedTime(id).orElseThrow(), DELTA); + void testGetTimeRejectsInvalidArgs() { + assertThrows(NullPointerException.class, () -> UuidUtils.getTime(null)); + assertThrows(IllegalArgumentException.class, () -> UuidUtils.getTime(invalidUuid())); } - private UUID createId() { - return UuidFactory.Factories.UPROTOCOL.factory().create(); + private static Stream provideElapsedTimeTestCases() { + final var now = Instant.now(); + return Stream.of( + Arguments.of(now.minusSeconds(60), now, 60000), + Arguments.of(now, now, 0), + Arguments.of(now.plusSeconds(60), now, -60000), + Arguments.of(now.minusMillis(50), now.plusMillis(100), 150) + ); } - @Test - public void testGetElapsedTimeCreationTimeUnknown() { - assertFalse(UuidUtils.getElapsedTime(UUID.getDefaultInstance()).isPresent()); + @ParameterizedTest(name = "Test getting elapsed time for UUID {index} - {arguments}") + @MethodSource("provideElapsedTimeTestCases") + void testGetElapsedTime(Instant creationTime, Instant referenceTime, long expectedElapsed) { + final UUID uuid = UuidFactory.create(creationTime); + assertEquals(expectedElapsed, UuidUtils.getElapsedTime(uuid, referenceTime)); } @Test - public void testGetRemainingTime() throws InterruptedException { - final UUID id = createId(); - assertEquals(TTL, UuidUtils.getRemainingTime(id, TTL).orElseThrow(), DELTA); - Thread.sleep(DELAY_MS); - assertEquals(TTL - DELAY_MS, UuidUtils.getRemainingTime(id, TTL).orElseThrow(), DELTA); + void testGetElapsedTimeUsesNowForReference() { + var creationTime = Instant.now().minusSeconds(30); + final UUID uuid = UuidFactory.create(creationTime); + final var elapsed = UuidUtils.getElapsedTime(uuid, null); + assertTrue(elapsed >= 30000); + assertTrue(elapsed < 31000); } @Test - public void testGetRemainingTimeNoTtl() { - final UUID id = createId(); - assertFalse(UuidUtils.getRemainingTime(id, 0).isPresent()); - assertFalse(UuidUtils.getRemainingTime(id, -1).isPresent()); + void testGetElapsedTimeRejectsInvalidArgs() { + assertThrows(NullPointerException.class, () -> UuidUtils.getElapsedTime(null, Instant.now())); + assertThrows(IllegalArgumentException.class, () -> UuidUtils.getElapsedTime(invalidUuid(), Instant.now())); } - @Test - public void testGetRemainingTimeNullUUID() { - assertFalse(UuidUtils.getRemainingTime(null, 0).isPresent()); + private static Stream provideRemainingTimeTestCases() { + final var now = Instant.now(); + return Stream.of( + Arguments.of(now.minusSeconds(60), 40_000, now, 0), + Arguments.of(now.minusSeconds(60), 60_000, now, 0), + Arguments.of(now.minusSeconds(60), 80_000, now, 20_000), + Arguments.of(now.plusSeconds(10), 70_000, now.plusSeconds(79), 1_000) + ); } - @Test - public void testGetRemainingTimeExpired() throws InterruptedException { - final UUID id = createId(); - Thread.sleep(DELAY_MS); - assertFalse(UuidUtils.getRemainingTime(id, DELAY_MS - DELTA).isPresent()); + @ParameterizedTest(name = "Test getting remaining time for UUID {index} - {arguments}") + @MethodSource("provideRemainingTimeTestCases") + void testGetRemainingTime(Instant creationTime, int ttl, Instant referenceTime, long expectedRemaining) { + final UUID id = UuidFactory.create(creationTime); + assertEquals(expectedRemaining, UuidUtils.getRemainingTime(id, ttl, referenceTime)); } @Test - public void testIsExpired() throws InterruptedException { - final UUID id = createId(); - assertFalse(UuidUtils.isExpired(id, DELAY_MS - DELTA)); - Thread.sleep(DELAY_MS); - assertTrue(UuidUtils.isExpired(id, DELAY_MS - DELTA)); + void testGetRemainingTimeUsesNowForReference() { + var creationTime = Instant.now().minusSeconds(10); + final UUID uuid = UuidFactory.create(creationTime); + final var remaining = UuidUtils.getRemainingTime(uuid, 15_000, null); + assertTrue(remaining > 4000); + assertTrue(remaining <= 5000); } @Test - public void testIsExpiredNoTtl() { - final UUID id = createId(); - assertFalse(UuidUtils.isExpired(id, 0)); - assertFalse(UuidUtils.isExpired(id, -1)); + void testGetRemainingTimeRejectsInvalidArgs() { + assertThrows(NullPointerException.class, () -> UuidUtils.getRemainingTime(null, TTL, Instant.now())); + assertThrows( + IllegalArgumentException.class, + () -> UuidUtils.getRemainingTime(invalidUuid(), TTL, Instant.now()) + ); + assertThrows( + IllegalArgumentException.class, + () -> UuidUtils.getRemainingTime(UuidFactory.create(), 0, Instant.now().plusSeconds(10)) + ); + assertThrows( + IllegalArgumentException.class, + () -> UuidUtils.getRemainingTime(UuidFactory.create(), -1, Instant.now().plusSeconds(10)) + ); } - @Test - @DisplayName("Test getElapseTime() when passed invalid UUID") - public void testGetElapsedTimeInvalidUUID() { - assertFalse(UuidUtils.getElapsedTime(null).isPresent()); + private static Stream provideIsExpiredTestCases() { + final var now = Instant.now(); + return Stream.of( + Arguments.of(now.minusSeconds(60), 0, now, false), + Arguments.of(now.minusSeconds(60), 40_000, now, true), + Arguments.of(now.minusSeconds(60), 60_000, now, true), + Arguments.of(now.minusSeconds(60), 80_000, now, false), + Arguments.of(now.plusSeconds(10), 70_000, now.plusSeconds(79), false) + ); + } + + @ParameterizedTest(name = "Test isExpired for UUID {index} - {arguments}") + @MethodSource("provideIsExpiredTestCases") + void testIsExpired(Instant creationTime, int ttl, Instant referenceTime, boolean expected) { + final UUID id = UuidFactory.create(creationTime); + assertEquals(expected, UuidUtils.isExpired(id, ttl, referenceTime)); } @Test - @DisplayName("Test getElapseTime() when UUID time is in the future") - public void testGetElapsedTimePast() throws InterruptedException { - final Instant now = Instant.now().plusMillis(DELAY_MS); - final UUID id = UuidFactory.Factories.UPROTOCOL.factory().create(now); - assertTrue(UuidUtils.getElapsedTime(id).isEmpty()); + void testIsExpiredRejectsInvalidArgs() { + assertThrows(NullPointerException.class, () -> UuidUtils.isExpired(null, TTL, Instant.now())); + assertThrows(IllegalArgumentException.class, () -> UuidUtils.isExpired(invalidUuid(), TTL, Instant.now())); + assertThrows( + IllegalArgumentException.class, + () -> UuidUtils.isExpired(UuidFactory.create(), -3, Instant.now()) + ); } } diff --git a/src/test/java/org/eclipse/uprotocol/uuid/serializer/UuidSerializerTest.java b/src/test/java/org/eclipse/uprotocol/uuid/serializer/UuidSerializerTest.java deleted file mode 100644 index 38f52002..00000000 --- a/src/test/java/org/eclipse/uprotocol/uuid/serializer/UuidSerializerTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.uuid.serializer; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.eclipse.uprotocol.v1.UUID; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EmptySource; -import org.junit.jupiter.params.provider.NullSource; -import org.junit.jupiter.params.provider.ValueSource; - -class UuidSerializerTest { - - @ParameterizedTest(name = "Test deserializing an invalid UUID string [{index}]: {arguments}") - @NullSource - @EmptySource - @ValueSource(strings = {"invalid-uuid-string"}) - void testDeserializeHandlesInvalidString(String uuidString) { - final var uuid = UuidSerializer.deserialize(uuidString); - assertEquals(UUID.getDefaultInstance(), uuid); - } -} diff --git a/src/test/java/org/eclipse/uprotocol/uuid/validator/UuidValidatorTest.java b/src/test/java/org/eclipse/uprotocol/uuid/validator/UuidValidatorTest.java deleted file mode 100644 index 58b4658f..00000000 --- a/src/test/java/org/eclipse/uprotocol/uuid/validator/UuidValidatorTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/** - * 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 - */ -package org.eclipse.uprotocol.uuid.validator; - -import org.eclipse.uprotocol.uuid.factory.UuidFactory; -import org.eclipse.uprotocol.uuid.factory.UuidUtils; -import org.eclipse.uprotocol.uuid.serializer.UuidSerializer; -import org.eclipse.uprotocol.uuid.validate.UuidValidator; -import org.eclipse.uprotocol.v1.UUID; -import org.eclipse.uprotocol.validation.ValidationException; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import static org.junit.jupiter.api.Assertions.*; - -public class UuidValidatorTest { - @Test - @DisplayName("Test validator with good uuid") - void testValidatorWithGoodUuid() { - //final UuidValidator validator = new UuidValidator(); - final UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create(); - UuidValidator.getValidator(uuid).validate(uuid); - } - - @Test - @DisplayName("Test Good uuid Check") - void testGoodUuidString() { - final var uuid = UuidFactory.Factories.UPROTOCOL.factory().create(); - UuidValidator.Validators.UPROTOCOL.validator().validate(uuid); - } - - @Test - @DisplayName("Test fetching the invalid Validator for when UUID passed is garbage") - void testInvalidUuid() { - final UUID uuid = UUID.newBuilder().setMsb(0L).setLsb(0L).build(); - final UuidValidator validator = UuidValidator.getValidator(uuid); - final var validationException = assertThrows(ValidationException.class, () -> validator.validate(uuid)); - assertEquals( - "Invalid UUID Version,Invalid UUID Time", - validationException.getMessage()); - } - - @Test - @DisplayName("Test invalid time uuid") - void testInvalidTimeUuid() { - final UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create(Instant.ofEpochSecond(0)); - final var validationException = assertThrows( - ValidationException.class, - () -> UuidValidator.Validators.UPROTOCOL.validator().validate(uuid)); - assertEquals("Invalid UUID Time", validationException.getMessage()); - } - - @Test - @DisplayName("Test UUIDv7 validator for null UUID") - void testUuidv7WithInvalidUuids() { - final UuidValidator validator = UuidValidator.Validators.UPROTOCOL.validator(); - assertNotNull(validator); - assertThrows(NullPointerException.class, () -> validator.validate(null)); - } - - @Test - @DisplayName("Test UUIDv7 validator for invalid types") - void testUuidv7WithInvalidTypes() { - final UUID uuidv6 = UuidFactory.Factories.UUIDV6.factory().create(); - final UUID uuid = UUID.newBuilder().setMsb(0L).setLsb(0L).build(); - - final java.util.UUID uuidJava = java.util.UUID.randomUUID(); - final UUID uuidv4 = UUID.newBuilder().setMsb(uuidJava.getMostSignificantBits()) - .setLsb(uuidJava.getLeastSignificantBits()).build(); - - final UuidValidator validator = UuidValidator.Validators.UPROTOCOL.validator(); - assertNotNull(validator); - - var validationException = assertThrows(ValidationException.class, () -> validator.validate(uuidv6)); - assertEquals("Invalid UUIDv7 Version", validationException.getMessage()); - - validationException = assertThrows(ValidationException.class, () -> validator.validate(uuid)); - assertEquals( - "Invalid UUIDv7 Version,Invalid UUID Time", - validationException.getMessage()); - - validationException = assertThrows(ValidationException.class, () -> validator.validate(uuidv4)); - assertEquals("Invalid UUIDv7 Version,Invalid UUID Time", validationException.getMessage()); - } - - @Test - @DisplayName("Test good UUIDv6") - void testGoodUuidv6() { - final UUID uuid = UuidFactory.Factories.UUIDV6.factory().create(); - - UuidValidator validator = UuidValidator.getValidator(uuid); - assertNotNull(validator); - assertTrue(UuidUtils.isUuidv6(uuid)); - validator.validate(uuid); - } - - @Test - @DisplayName("Test UUIDv6 with bad variant") - void testUuidv6WithBadVariant() { - final UUID uuid = UuidSerializer.deserialize("1ee57e66-d33a-65e0-4a77-3c3f061c1e9e"); - assertFalse(uuid.equals(UUID.getDefaultInstance())); - final UuidValidator validator = UuidValidator.getValidator(uuid); - assertNotNull(validator); - final var validationException = assertThrows(ValidationException.class, () -> validator.validate(uuid)); - assertEquals( - "Invalid UUID Version,Invalid UUID Time", - validationException.getMessage()); - } - - @Test - @DisplayName("Test UUIDv6 with invalid UUID") - void testUuidv6WithInvalidUuid() { - - final UUID uuid = UUID.newBuilder().setMsb(9 << 12).setLsb(0L).build(); - final UuidValidator validator = UuidValidator.Validators.UUIDV6.validator(); - assertNotNull(validator); - final var validationException = assertThrows(ValidationException.class, () -> validator.validate(uuid)); - assertEquals( - "Not a UUIDv6 Version,Invalid UUID Time", - validationException.getMessage()); - } - - - @Test - @DisplayName("Test using UUIDv6 Validator to validate null UUID") - void testUuidv6WithNullUuid() { - final UuidValidator validator = UuidValidator.Validators.UUIDV6.validator(); - assertNotNull(validator); - assertThrows(NullPointerException.class, () -> validator.validate(null)); - } - - @Test - @DisplayName("Test using UUIDv6 Validator to validate a different types of UUIDs") - void testUuidv6WithUuidv7() { - final UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create(); - final UuidValidator validator = UuidValidator.Validators.UUIDV6.validator(); - assertNotNull(validator); - final var validationException = assertThrows(ValidationException.class, () -> validator.validate(uuid)); - assertEquals("Not a UUIDv6 Version", validationException.getMessage()); - } -} diff --git a/src/test/java/org/eclipse/uprotocol/v1/UStatusTest.java b/src/test/java/org/eclipse/uprotocol/v1/UStatusTest.java new file mode 100644 index 00000000..9ee6463f --- /dev/null +++ b/src/test/java/org/eclipse/uprotocol/v1/UStatusTest.java @@ -0,0 +1,56 @@ +/** + * SPDX-FileCopyrightText: 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.uprotocol.v1; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +import com.google.protobuf.Any; +import com.google.protobuf.StringValue; + +class UStatusTest { + @Test + // [utest->req~ustatus-data-model-impl~1] + void testUStatusHasRequiredFields() { + var details = Any.pack(StringValue.of("Hello")); + var otherDetails = Any.pack(StringValue.of("there")); + UStatus status = UStatus.newBuilder() + .setCode(UCode.INTERNAL) + .setMessage("Internal error") + .addDetails(0, details) + .addDetails(otherDetails) + .build(); + assertEquals(UCode.INTERNAL, status.getCode()); + assertEquals("Internal error", status.getMessage()); + assertEquals(details, status.getDetails(0)); + assertEquals(otherDetails, status.getDetails(1)); + } + + @Test + // [utest->req~ustatus-data-model-proto~1] + void testProtoSerialization() { + var ustatus = UStatus.newBuilder() + .setCode(UCode.CANCELLED) + .setMessage("the message") + .addDetails(Any.pack(StringValue.of("Hello"))) + .build(); + var proto = ustatus.toByteString(); + assertDoesNotThrow( + () -> { + var deserializedStatus = UStatus.parseFrom(proto); + assertEquals(ustatus, deserializedStatus); + }); + } +} diff --git a/src/test/resources/features/uuri_pattern_matching.feature b/src/test/resources/features/uuri_pattern_matching.feature new file mode 100644 index 00000000..62d8d3fd --- /dev/null +++ b/src/test/resources/features/uuri_pattern_matching.feature @@ -0,0 +1,71 @@ +# +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: Apache-2.0 +# +Feature: Matching endpoint identifiers (UUri) against patterns + + Scenario Outline: + Developers using a uProtocol language library should be able to verify that a specific + endpoint identifier matches a given pattern as specified by the UUri specification. + + [utest->dsn~uri-pattern-matching~2] + + Given a URI string + When deserializing the URI to a UUri + Then the UUri matches pattern + + Examples: + | uri | pattern | + | "/1/1/A1FB" | /1/1/A1FB | + | "/1/1/A1FB" | //*/1/1/A1FB | + | "/1/1/A1FB" | /FFFF/1/A1FB | + | "/1/1/A1FB" | //*/FFFF/1/A1FB | + | "/1/1/A1FB" | /FFFFFFFF/1/A1FB | + | "/1/1/A1FB" | //*/FFFFFFFF/1/A1FB | + | "/1/1/A1FB" | /1/FF/A1FB | + | "/1/1/A1FB" | //*/1/FF/A1FB | + | "/1/1/A1FB" | /1/1/FFFF | + | "/1/1/A1FB" | //*/1/1/FFFF | + | "/1/1/A1FB" | /FFFFFFFF/FF/FFFF | + | "/1/1/A1FB" | //*/FFFFFFFF/FF/FFFF | + | "/10001/1/A1FB" | /10001/1/A1FB | + | "/10001/1/A1FB" | //*/10001/1/A1FB | + | "/10001/1/A1FB" | /FFFFFFFF/1/A1FB | + | "/10001/1/A1FB" | //*/FFFFFFFF/1/A1FB | + | "/10001/1/A1FB" | /FFFFFFFF/FF/FFFF | + | "/10001/1/A1FB" | //*/FFFFFFFF/FF/FFFF | + | "//vcu.my_vin/1/1/A1FB" | //vcu.my_vin/1/1/A1FB | + | "//vcu.my_vin/1/1/A1FB" | //*/1/1/A1FB | + + Scenario Outline: + Developers using a uProtocol language library should be able to verify that a specific + endpoint identifier does not match a given pattern as specified by the UUri specification. + + [utest->dsn~uri-pattern-matching~2] + + Given a URI string + When deserializing the URI to a UUri + Then the UUri does not match pattern + + Examples: + | uri | pattern | + | "/1/1/A1FB" | //mcu1/1/1/A1FB | + | "//vcu.my_vin/1/1/A1FB" | //mcu1/1/1/A1FB | + | "//vcu/B1A5/1/A1FB" | //vc/FFFFFFFF/FF/FFFF | + | "/B1A5/1/A1FB" | //*/25B1/FF/FFFF | + | "/B1A5/1/A1FB" | //*/FFFFFFFF/2/FFFF | + | "/B1A5/1/A1FB" | //*/FFFFFFFF/FF/ABCD | + | "/B1A5/1/A1FB" | /25B1/1/A1FB | + | "/B1A5/1/A1FB" | /2B1A5/1/A1FB | + | "/10B1A5/1/A1FB" | /40B1A5/1/A1FB | + | "/B1A5/1/A1FB" | /B1A5/4/A1FB | + | "/B1A5/1/A1FB" | /B1A5/1/90FB | diff --git a/src/test/resources/features/uuri_uri_serialization.feature b/src/test/resources/features/uuri_uri_serialization.feature new file mode 100644 index 00000000..dcd703cf --- /dev/null +++ b/src/test/resources/features/uuri_uri_serialization.feature @@ -0,0 +1,88 @@ +# +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: Apache-2.0 +# +Feature: String representation of endpoint identfiers (UUri) + + Scenario Outline: + Developers using a uProtocol language library should be able to get the URI + string representation of a UUri instance as specified by the UUri specification. + + [utest->req~uri-serialization~1] + [utest->dsn~uri-scheme~1] + [utest->dsn~uri-host-only~2] + [utest->dsn~uri-authority-mapping~1] + [utest->dsn~uri-path-mapping~1] + + Given a UUri having authority + And having entity identifier + And having major version + And having resource identifier + When serializing the UUri to a URI + Then the resulting URI string is + And the original UUri can be recreated from the URI string + + Examples: + | authority_name | entity_id | version | resource_id | uri_string | + | "" | 0x00000001 | 0x01 | 0xa1fb | up:/1/1/A1FB | + | "my_vin" | 0x10000001 | 0x02 | 0x001a | up://my_vin/10000001/2/1A | + | "*" | 0x00000101 | 0xa0 | 0xa1fb | up://*/101/A0/A1FB | + | "mcu1" | 0x0000FFFF | 0x01 | 0xa1fb | up://mcu1/FFFF/1/A1FB | + | "vcu.my_vin" | 0x01a40101 | 0x01 | 0x8000 | up://vcu.my_vin/1A40101/1/8000 | + | "vcu.my_vin" | 0xFFFF0101 | 0x01 | 0xa1fb | up://vcu.my_vin/FFFF0101/1/A1FB | + | "vcu.my_vin" | 0xFFFFFFFF | 0x01 | 0xa1fb | up://vcu.my_vin/FFFFFFFF/1/A1FB | + | "vcu.my_vin" | 0x00000101 | 0x00 | 0xa1fb | up://vcu.my_vin/101/0/A1FB | + | "vcu.my_vin" | 0x00000101 | 0xFF | 0xa1fb | up://vcu.my_vin/101/FF/A1FB | + | "vcu.my_vin" | 0x00000101 | 0x01 | 0x0000 | up://vcu.my_vin/101/1/0 | + | "vcu.my_vin" | 0x00000101 | 0x01 | 0xFFFF | up://vcu.my_vin/101/1/FFFF | + + Scenario Outline: + Developers using a uProtocol language library should not be able to create a UUri from a + URI string that does not comply with the UUri specification. + + [utest->req~uri-serialization~1] + [utest->dsn~uri-scheme~1] + [utest->dsn~uri-host-only~2] + [utest->dsn~uri-authority-mapping~1] + [utest->dsn~uri-path-mapping~1] + + Given a URI string + When deserializing the URI to a UUri + Then the attempt fails + + Examples: + | uri_string | reason for failure | + | "" | not a URI | + | " " | not a URI | + | "$$" | not a URI | + | "up:" | not a URI | + | "up:/" | not a URI | + | "/" | not a URI | + | "//" | not a URI | + | "//vcu.my_vin" | just an authority | + | "//VCU" | authority with uppercase characters | + | "//vcu.my_vin//1/A1FB" | missing entity ID | + | "//vcu.my_vin/101//A1FB" | missing version | + | "//vcu.my_vin/101/1/" | missing resource ID | + | "up://vcu.my_vin/101/1/A/unexpected" | too many path segments | + | "xy://vcu.my_vin/101/1/A" | unsupported schema | + | "//vcu.my_vin/101/1/A?foo=bar" | URI with query | + | "//vcu.my_vin/101/1/A#foo" | URI with fragment | + | "//vcu.my-vin:1516/101/1/A" | server-based authority with port | + | "//user:pwd@vcu.my-vin/101/1/A" | server-based authority with user info | + | "//reg_based:1516/101/1/A" | registry-based authority name with invalid characters | + | "up://vcu.my-vin/1G1/1/A1FB" | non-hex entity ID | + | "/123456789/1/A1FB" | entity ID exceeds max length | + | "up:/101/G/A1FB" | non-hex version | + | "//vcu.my-vin/101/123/A1FB" | version exceeds max length | + | "/101/1/G1FB" | non-hex resource ID | + | "up://vcu.my-vin/101/1/12345" | resource ID exceeds max length | diff --git a/up-spec b/up-spec index 623c5208..af556f65 160000 --- a/up-spec +++ b/up-spec @@ -1 +1 @@ -Subproject commit 623c5208bdc0a198ad6e4da00465a9d80312c739 +Subproject commit af556f65b7ebedf89fcc1a5b678346a96b6e28c5