* A publish message is used to notify all interested consumers of an event that has occurred. * Consumers usually indicate their interest by subscribing to a particular topic. * @@ -75,7 +75,7 @@ public static UMessageBuilder publish(UUri source) { /** * Gets a builder for a notification message. - * + *
* A notification is used to inform a specific consumer about an event that has occurred. * * @param source The component that the notification originates from. @@ -135,13 +135,6 @@ public static UMessageBuilder request(UUri source, UUri sink, int ttl) { *
* The builder will be initialized with {@link UPriority#UPRIORITY_CS4}. * - * # Arguments - * - * * `reply_to_address` - The URI that the sender of the request expects to receive the response message at. - * * `request_id` - The identifier of the request that this is the response to. - * * `invoked_method` - The URI identifying the method that has been invoked and which the created message is - * the outcome of. - * * @param source The URI identifying the method that has been invoked and which the created message is * the outcome of. * @param sink The URI that the sender of the request expects to receive the response message at. @@ -169,16 +162,9 @@ public static UMessageBuilder response(UUri source, UUri sink, UUID reqid) { *
* A response message is used to send the outcome of processing a request message * to the original sender of the request message. - *
- * The builder will be initialized with values from the given request attributes. * - * # Arguments - * - * * `request_attributes` - The attributes from the request message. The response message - * builder will be initialized with the corresponding attribute values. - * * @param request The attributes from the request message. The response message - * builder will be initialized with the corresponding attribute values. + * builder will be initialized with the corresponding attribute values. * @return The builder. * @throws NullPointerException if request is {@code null}. * @throws IllegalArgumentException if the request does not contain valid request attributes. @@ -219,7 +205,7 @@ private UMessageBuilder(UUri source, UUID id, UMessageType type) { /** * Sets the message's identifier. - * + *
* Every message must have an identifier. If this method is not used, an identifier will be * generated and set on the message when one of the build methods is invoked. * @@ -241,14 +227,11 @@ public UMessageBuilder withMessageId(UUID id) { /** * Sets the message's time-to-live. * - * @param ttl The time-to-live in milliseconds. + * @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 ttl is negative. */ public UMessageBuilder withTtl(int ttl) { - if (ttl < 0) { - throw new IllegalArgumentException("TTL must be a non-negative integer."); - } this.ttl = ttl; return this; } @@ -296,16 +279,12 @@ public UMessageBuilder withPriority(UPriority priority) { /** * Sets the message's permission level. * - * @param plevel The level. + * @param plevel The level. Note that the value is interpreted as an unsigned integer. * @return The builder with the configured permission level. - * @throws IllegalArgumentException if the permission level is less than 0. * @throws IllegalStateException if the message is not an RPC request message. */ public UMessageBuilder withPermissionLevel(int plevel) { // [impl->dsn~up-attributes-permission-level~1] - if (plevel < 0) { - throw new IllegalArgumentException("Permission level must be greater than or equal to 0."); - } if (this.type != UMessageType.UMESSAGE_TYPE_REQUEST) { throw new IllegalStateException("Permission level can only be set for RPC request messages."); } @@ -372,11 +351,6 @@ public UMessage build(UPayload payload) { /** * Creates the message based on the builder's state. * - * # Errors - * - * If the properties set on the builder do not represent a consistent set of [`UAttributes`], - * a [`UMessageError::AttributesValidationError`] is returned. - * * @return A message ready to be sent using a transport implementation. * @throws ValidationException if the properties set on the builder do not represent a * consistent set of attributes as determined by {@link UAttributesValidator#validate(UAttributes)}. 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 3adecd3..df0e5ef 100644 --- a/src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java +++ b/src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java @@ -129,19 +129,42 @@ public final void validateRpcPriority(UAttributes attributes) { } } + /** + * Validates the time-to-live configuration of RPC messages. + * + * @param attributes The attributes to check. + * @throws IllegalArgumentException if the attributes do not represent an RPC message. + * @throws ValidationException if the attributes do not contain a TTL or if its value is 0. + */ + public final void validateRpcTtl(UAttributes attributes) { + if (attributes.getType() != UMessageType.UMESSAGE_TYPE_REQUEST + && attributes.getType() != UMessageType.UMESSAGE_TYPE_RESPONSE) { + throw new IllegalArgumentException("Attributes do not represent an RPC message"); + } + if (!attributes.hasTtl()) { + throw new ValidationException("RPC messages must contain a TTL"); + } + int ttl = attributes.getTtl(); + // TTL is interpreted as an unsigned integer, so negative values are not possible + if (ttl == 0) { + throw new ValidationException("RPC message's TTL must not be 0"); + } + } + /** * Checks if a given set of attributes belong to a message that has expired. *
* The message is considered expired if the message's creation time plus the * duration indicated by the ttl attribute is before the current - * instant in time. + * instant in (system) time. * * @param attributes The attributes to check. * @return {@code true} if the given attributes should be considered expired. */ public final boolean isExpired(UAttributes attributes) { final int ttl = attributes.getTtl(); - return ttl > 0 && UuidUtils.isExpired(attributes.getId(), ttl, Instant.now()); + // TTL is interpreted as an unsigned integer, so negative values are not possible + return Integer.compareUnsigned(ttl, 0) > 0 && UuidUtils.isExpired(attributes.getId(), ttl, Instant.now()); } /* @@ -178,43 +201,6 @@ public final boolean isExpired(UAttributes attributes) { */ public abstract void validate(UAttributes attributes); - /** - * Validates the time-to-live configuration. - *
- * If the UAttributes does not contain a time to live then the ValidationResult is ok.
- *
- * @param attributes The attributes to check.
- * @throws ValidationException if the attributes contain a negative TTL.
- */
- public void validateTtl(UAttributes attributes) {
- if (!attributes.hasTtl()) {
- return;
- }
- int ttl = attributes.getTtl();
- if (ttl < 0) {
- throw new ValidationException(String.format("TTL must be a non-negative integer [%s]", ttl));
- }
- }
-
- /**
- * Validate the permissionLevel for the default case. If the UAttributes does
- * not contain a permission level then
- * the ValidationResult is ok.
- *
- * @param attributes The attributes to check.
- * @throws ValidationException if the attributes contain a negative permission level.
- */
- public final void validatePermissionLevel(UAttributes attributes) {
- if (!attributes.hasPermissionLevel()) {
- return;
- }
- final var level = attributes.getPermissionLevel();
- if (level < 0) {
- throw new ValidationException(
- String.format("Permission level must be a non-negative integer [%d]", level));
- }
- }
-
/**
* Validators for the message types defined by uProtocol.
*/
@@ -294,7 +280,6 @@ public void validate(UAttributes attributes) {
this::validateId,
this::validateSource,
this::validateSink,
- this::validateTtl,
this::validatePriority
);
if (!errors.isEmpty()) {
@@ -366,7 +351,6 @@ public void validate(UAttributes attributes) {
this::validateId,
this::validateSource,
this::validateSink,
- this::validateTtl,
this::validatePriority
);
if (!errors.isEmpty()) {
@@ -435,27 +419,6 @@ public void validateSink(UAttributes attributes) {
}
}
- /**
- * Verifies that a set of attributes representing an RPC request contain a valid time-to-live.
- *
- * @param attributes The attributes to check.
- * @throws ValidationException if the attributes do not contain a time-to-live,
- * or if the time-to-live is <= 0.
- */
- @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");
- }
- int ttl = attributes.getTtl();
- if (ttl <= 0) {
- throw new ValidationException(String.format(
- "RPC request message's TTL must be a positive integer [%d]",
- ttl));
- }
- }
-
@Override
public void validate(UAttributes attributes) {
final var errors = ValidationUtils.collectErrors(attributes,
@@ -463,9 +426,8 @@ public void validate(UAttributes attributes) {
this::validateId,
this::validateSource,
this::validateSink,
- this::validateTtl,
- this::validateRpcPriority,
- this::validatePermissionLevel
+ this::validateRpcTtl,
+ this::validateRpcPriority
);
if (!errors.isEmpty()) {
throw new ValidationException(errors);
@@ -575,7 +537,7 @@ public void validate(UAttributes attributes) {
this::validateSource,
this::validateSink,
this::validateReqId,
- this::validateTtl,
+ this::validateRpcTtl,
this::validateRpcPriority,
this::validateCommstatus
);
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 33b40f6..9b7d174 100644
--- a/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidUtils.java
+++ b/src/main/java/org/eclipse/uprotocol/uuid/factory/UuidUtils.java
@@ -76,7 +76,7 @@ public static boolean isUProtocol(UUID uuid) {
* @throws NullPointerException if the UUID is {@code null}.
* @throws IllegalArgumentException if the UUID is not a uProtocol UUID.
*/
- public static long getTime(UUID uuid) {
+ public static long getTimestamp(UUID uuid) {
Objects.requireNonNull(uuid);
if (!isUProtocol(uuid)) {
throw new IllegalArgumentException("UUID is not a uProtocol UUID");
@@ -101,7 +101,7 @@ public static long getElapsedTime(UUID id, Instant now) {
if (!isUProtocol(id)) {
throw new IllegalArgumentException("UUID is not a uProtocol UUID");
}
- final var creationTime = getTime(id);
+ final var creationTime = getTimestamp(id);
final var referenceTime = now != null ? now.toEpochMilli() : Instant.now().toEpochMilli();
return referenceTime - creationTime;
}
@@ -118,21 +118,16 @@ public static long getElapsedTime(UUID id, Instant now) {
* 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.
+ * @throws IllegalArgumentException if the TTL is 0.
*/
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");
+ if (ttl == 0) {
+ throw new IllegalArgumentException("TTL must not be 0");
}
- 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;
+ final long elapsedTime = getElapsedTime(id, now);
+ if (Long.compareUnsigned(elapsedTime, ttl) > 0) {
+ return 0; // Expired
} else {
return ttl - elapsedTime;
}
@@ -142,22 +137,19 @@ public static long getRemainingTime(UUID id, int ttl, Instant now) {
* Checks if an object identified by a given UUID should be considered expired.
*
* @param id The UUID.
- * @param ttl The object's time-to-live (TTL) in milliseconds.
+ * @param ttl The object's time-to-live (TTL) in milliseconds. Note that the TTL is
+ * interpreted as an unsigned integer.
* @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, 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;
+ return Integer.compareUnsigned(ttl, 0) > 0 && getRemainingTime(id, ttl, now) == 0;
}
}
diff --git a/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcClientTest.java b/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcClientTest.java
index bed348e..80fe153 100644
--- a/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcClientTest.java
+++ b/src/test/java/org/eclipse/uprotocol/communication/InMemoryRpcClientTest.java
@@ -187,6 +187,7 @@ void testHandleUnexpectedResponse(Consumer