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 d8c038f..7b86979 100644 --- a/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java +++ b/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java @@ -106,16 +106,17 @@ public static UMessageBuilder notification(UUri source, UUri sink) { * @param source The URI that the sender of the request expects the response message at. * @param sink The URI identifying the method to invoke. * @param ttl The number of milliseconds after which the request should no longer be processed - * by the target service. + * by the target service. The given value is interpreted as an unsigned integer. * @return The builder. * @throws NullPointerException if the source or sink is {@code null}. - * @throws IllegalArgumentException if the ttl is less than or equal to 0. + * @throws IllegalArgumentException if the ttl is 0. */ public static UMessageBuilder request(UUri source, UUri sink, int ttl) { Objects.requireNonNull(source, "source cannot be null."); Objects.requireNonNull(sink, "sink cannot be null."); - if (ttl <= 0) { + if (ttl == 0) { + // [impl->dsn~up-attributes-request-ttl~1] throw new IllegalArgumentException("ttl must be greater than 0."); } return new UMessageBuilder( @@ -230,8 +231,15 @@ public UMessageBuilder withMessageId(UUID id) { * @param ttl The time-to-live in milliseconds. Note that the value is interpreted as an * unsigned integer. A value of 0 indicates that the message never expires. * @return The builder with the configured ttl. + * @throws IllegalArgumentException if the builder is used for creating an RPC message and the TTL is 0. */ public UMessageBuilder withTtl(int ttl) { + if ((this.type == UMessageType.UMESSAGE_TYPE_REQUEST + || this.type == UMessageType.UMESSAGE_TYPE_RESPONSE) + && ttl == 0) { + // [impl->dsn~up-attributes-request-ttl~1] + throw new IllegalArgumentException("TTL of RPC messages must be greater than 0."); + } this.ttl = ttl; return this; } 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 df0e5ef..87be9ae 100644 --- a/src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java +++ b/src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java @@ -147,6 +147,7 @@ public final void validateRpcTtl(UAttributes attributes) { int ttl = attributes.getTtl(); // TTL is interpreted as an unsigned integer, so negative values are not possible if (ttl == 0) { + // [impl->dsn~up-attributes-request-ttl~1] throw new ValidationException("RPC message's TTL must not be 0"); } } 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 9b7d174..e150344 100644 --- a/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidUtils.java +++ b/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidUtils.java @@ -126,7 +126,7 @@ public static long getRemainingTime(UUID id, int ttl, Instant now) { throw new IllegalArgumentException("TTL must not be 0"); } final long elapsedTime = getElapsedTime(id, now); - if (Long.compareUnsigned(elapsedTime, ttl) > 0) { + if (Long.compareUnsigned(elapsedTime, ttl) >= 0) { return 0; // Expired } else { return ttl - elapsedTime; 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 4c2d390..fa50476 100644 --- a/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java +++ b/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java @@ -130,6 +130,7 @@ void assertBuilderPanics( } @Test + // [utest->dsn~up-attributes-request-ttl~1] void testRequestRejectsInvalidTtl() { assertThrows( IllegalArgumentException.class, @@ -139,22 +140,25 @@ void testRequestRejectsInvalidTtl() { @ParameterizedTest @CsvSource(useHeadersInDisplayName = true, textBlock = """ - commStatus, priority - NOT_FOUND, + commStatus, priority, ttl + NOT_FOUND, , # // [utest->dsn~up-attributes-request-priority~1] - , UPRIORITY_UNSPECIFIED + , UPRIORITY_UNSPECIFIED, # // [utest->dsn~up-attributes-request-priority~1] - , UPRIORITY_CS0 + , UPRIORITY_CS0, # // [utest->dsn~up-attributes-request-priority~1] - , UPRIORITY_CS1 + , UPRIORITY_CS1, # // [utest->dsn~up-attributes-request-priority~1] - , UPRIORITY_CS2 + , UPRIORITY_CS2, # // [utest->dsn~up-attributes-request-priority~1] - , UPRIORITY_CS3 + , UPRIORITY_CS3, + # // [utest->dsn~up-attributes-request-ttl~1] + , , 0 """) void testRequestMessageBuilderRejectsInvalidAttributes( UCode commStatus, - UPriority priority + UPriority priority, + Integer ttl ) { var builder = UMessageBuilder.request(UURI_DEFAULT, UURI_METHOD, 5000); @@ -168,6 +172,11 @@ void testRequestMessageBuilderRejectsInvalidAttributes( IllegalArgumentException.class, () -> builder.withPriority(priority) ); + } else if (ttl != null) { + assertThrows( + IllegalArgumentException.class, + () -> builder.withTtl(ttl) + ); } }