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)
+ );
}
}