Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Update to UUIDv7
The following change removes the v8 custom UUID to the v7 standard definition per up-spec change eclipse-uprotocol/up-spec#170
  • Loading branch information
czfdcn committed Jun 19, 2024
commit 37c08813314898ca8c3af0d5165c121b0b538069
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ static CloudEvent notification(String source, String sink, Any protoPayload, UCl
}

/**
* @return Returns a UUIDv8 id.
* @return Returns a UUIDv7 id.
*/
static String generateCloudEventId() {
UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,14 +249,14 @@ static boolean isExpiredByCloudEventCreationDate(CloudEvent cloudEvent) {
}

/**
* Calculate if a CloudEvent configured with UUIDv8 id and a ttl attribute is
* Calculate if a CloudEvent configured with UUIDv7 id and a ttl attribute is
* expired.<br>
* The ttl attribute is a configuration of how long this event should live for
* after it was generated (in milliseconds)
*
* @param cloudEvent The CloudEvent to inspect for being expired.
* @return Returns true if the CloudEvent was configured with a ttl &gt; 0 and
* UUIDv8 id to compare for expiration.
* UUIDv7 id to compare for expiration.
*/
static boolean isExpired(CloudEvent cloudEvent) {
final Optional<Integer> maybeTtl = getTtl(cloudEvent);
Expand All @@ -280,7 +280,7 @@ static boolean isExpired(CloudEvent cloudEvent) {
}

/**
* Check if a CloudEvent is a valid UUIDv6 or v8 .
* Check if a CloudEvent is a valid UUIDv6 or v7 .
*
* @param cloudEvent The CloudEvent with the id to inspect.
* @return Returns true if the CloudEvent is valid.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public static ValidationResult validateVersion(String version) {
public static ValidationResult validateId(CloudEvent cloudEvent) {
return UCloudEvent.isCloudEventId(cloudEvent) ? ValidationResult.success()
: ValidationResult.failure(
String.format("Invalid CloudEvent Id [%s]. CloudEvent Id must be of type UUIDv8.",
String.format("Invalid CloudEvent Id [%s]. CloudEvent Id must be of type UUIDv7.",
cloudEvent.getId()));
}

Expand Down
91 changes: 36 additions & 55 deletions src/main/java/org/eclipse/uprotocol/uuid/factory/UuidFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,39 @@
import java.util.Objects;
import java.util.Random;

/**
* The UuidFactory class is an abstract class that provides a factory method to
* create UUIDs based on https://www.rfc-editor.org/rfc/rfc9562[rfc9562].
* The UuidFactory class provides two implementations, UUIDv6 (used for older versions)
* of the protocol), and UUIDv7.
*/
public abstract class UuidFactory {

/**
* Create a UUID based on the current time.
*
* @return a UUID
*/
public UUID create() {
return this.create(Instant.now());
}

/**
* Create a UUID based on the given time.
*
* @param instant the time
* @return a 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.Uuidv8Factory());
UPROTOCOL(new UuidFactory.Uuidv7Factory());

private final UuidFactory factory;

Expand All @@ -44,6 +65,10 @@ public UuidFactory 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()),
Expand All @@ -53,65 +78,21 @@ public UUID create(Instant instant) {
}
}


/**
* uProtocol UUIDv8 data model
* UUIDv8 can only be built using the static factory methods of the class
* given that the UUIDv8 datamodel is based off the previous UUID generated.
* The UUID is based off the draft-ietf-uuidrev-rfc4122bis and UUIDv7 with
* some modifications that are discussed below. The diagram below shows the
* specification for the UUID:
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | unix_ts_ms |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | unix_ts_ms | ver | counter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |var| rand_b |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | rand_b |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* <p>
* | Field | RFC2119 |
* | ----- | --------|
* | unix_ts_ms | 48 bit big-endian unsigned number of Unix epoch timestamp in
* milliseconds as per Section 6.1
* of RFC
* | ver | MUST be 8 per Section 4.2 of draft-ietf-uuidrev-rfc4122bis
* | counter | MUST be a 12 bit counter field that is reset at each unix_ts_ms
* tick, and incremented for each
* UUID generated
* within the 1ms precision of unix_ts_ms The counter provides the ability to
* generate 4096 events within 1ms
* however the precision of the clock is still 1ms accuracy
* | var | MUST be the The 2 bit variant defined by Section 4.1 of RFC |
* |rand_b | MUST 62 bits random number that is generated at initialization time
* of the uE only and reused
* otherwise |
* The Uuidv7Factory class is an implementation of the UuidFactory class that
* creates UUIDs based on the UUIDv7 version of the protocol.
*/
private static class Uuidv8Factory extends UuidFactory {
public static final int UUIDV8_VERSION = 8;
private static final int MAX_COUNT = 0xfff;
private static final long LSB = (new Random().nextLong() & 0x3fffffffffffffffL) | 0x8000000000000000L;
// Keep track of the time and counters
private static long msb = UUIDV8_VERSION << 12; // Version is 8

synchronized public UUID create(Instant instant) {
private static class Uuidv7Factory extends UuidFactory {
public UUID create(Instant instant) {
final long time = Objects.requireNonNullElse(instant, Instant.now()).toEpochMilli();

// Check if the current time is the same as the previous time
if (time == msb >> 16) {
// Increment the counter if we are not at MAX_COUNT
if ((msb & 0xFFFL) < MAX_COUNT) {
msb++;
}

// The previous time is not the same tick as the current so we reset msb
} else {
msb = (time << 16) | 8L << 12;
}
final int rand_a = new Random().nextInt() & 0xfff;
final long rand_b = new Random().nextLong() & 0x3fffffffffffffffL;

return UUID.newBuilder().setMsb(msb).setLsb(LSB).build();
return UUID.newBuilder()
.setMsb((time << 16) | 7L << 12 | rand_a)
.setLsb(rand_b | 1L << 63).build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ static Optional<Integer> getVariant(UUID uuid) {
}

/**
* Verify if version is a formal UUIDv8 uProtocol ID.
* Verify if version is a formal UUIDv7 uProtocol ID.
*
* @return true if is a uProtocol UUID or false if uuid passed is null
* or the UUID is not uProtocol format.
Expand All @@ -72,7 +72,7 @@ static boolean isUuidv6(UUID uuid) {
}

/**
* Verify uuid is either v6 or v8
* Verify uuid is either v6 or v7
*
* @return true if is UUID version 6 or 8
*/
Expand Down Expand Up @@ -182,7 +182,7 @@ enum Version {
/**
* The custom or free-form version proposed by Peabody and Davis.
*/
VERSION_UPROTOCOL(8);
VERSION_UPROTOCOL(7);

private final int value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public enum Validators {
UNKNOWN(new UuidValidator.InvalidValidator()),
UUIDV6(new UuidValidator.UUIDv6Validator()),

UPROTOCOL(new UuidValidator.UUIDv8Validator());
UPROTOCOL(new UuidValidator.UUIDv7Validator());

private final UuidValidator uuidValidator;

Expand Down Expand Up @@ -108,13 +108,13 @@ public ValidationResult validateVariant(UUID uuid) {
}
}

private static class UUIDv8Validator extends UuidValidator {
private static class UUIDv7Validator extends UuidValidator {
@Override
public ValidationResult validateVersion(UUID uuid) {
final Optional<UuidUtils.Version> version = UuidUtils.getVersion(uuid);
return version.isPresent() && version.get() == UuidUtils.Version.VERSION_UPROTOCOL
? ValidationResult.success()
: ValidationResult.failure(String.format("Invalid UUIDv8 Version"));
: ValidationResult.failure(String.format("Invalid UUIDv7 Version"));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public void test_validateid_when_id_is_invalid() {
final CloudEventValidator validator = CloudEventValidator.getValidator(cloudEvent);
final ValidationResult result = validator.validate(cloudEvent);
assertFalse(result.isSuccess());
assertEquals(result.getMessage(), "Invalid CloudEvent Id [bad]. CloudEvent Id must be of type UUIDv8.");
assertEquals(result.getMessage(), "Invalid CloudEvent Id [bad]. CloudEvent Id must be of type UUIDv7.");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
public class UUIDFactoryTest {

@Test
@DisplayName("Test UUIDv8 Creation")
void test_uuidv8_creation() {
@DisplayName("Test UUIDv7 Creation")
void test_uuidv7_creation() {
final Instant now = Instant.now();
final UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create(now);
final Optional<UuidUtils.Version> version = UuidUtils.getVersion(uuid);
Expand All @@ -52,8 +52,8 @@ void test_uuidv8_creation() {
}

@Test
@DisplayName("Test UUIDv8 Creation with null Instant")
void test_uuidv8_creation_with_null_instant() {
@DisplayName("Test UUIDv7 Creation with null Instant")
void test_uuidv7_creation_with_null_instant() {
final UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create(null);
final Optional<UuidUtils.Version> version = UuidUtils.getVersion(uuid);
final Optional<Long> time = UuidUtils.getTime(uuid);
Expand All @@ -74,27 +74,6 @@ void test_uuidv8_creation_with_null_instant() {
}


@Test
@DisplayName("Test UUIDv8 overflow")
void test_uuidv8_overflow() {
final List<UUID> uuidList = new ArrayList<>();
final int MAX_COUNT = 4095;

// Build UUIDs above MAX_COUNT (4095) so we can test the limits
final Instant now = Instant.now();
for (int i = 0; i < MAX_COUNT * 2; i++) {
uuidList.add(UuidFactory.Factories.UPROTOCOL.factory().create(now));

// Time should be the same as the 1st
assertEquals(UuidUtils.getTime(uuidList.get(0)), UuidUtils.getTime(uuidList.get(i)));

// Random should always remain the same be the same
assertEquals(uuidList.get(0).getLsb(), uuidList.get(i).getLsb());
if (i > MAX_COUNT) {
assertEquals(uuidList.get(MAX_COUNT).getMsb(), uuidList.get(i).getMsb());
}
}
}

@Test
@DisplayName("Test UUIDv6 creation with Instance")
Expand Down Expand Up @@ -262,25 +241,37 @@ void test_create_uprotocol_uuid_with_different_time_values() throws InterruptedE
}

@Test
@DisplayName("Test Create both UUIDv6 and v8 to compare performance")
void test_create_both_uuidv6_and_v8_to_compare_performance() throws InterruptedException {
@DisplayName("Test Create both UUIDv6 and v7 to compare performance")
void test_create_both_uuidv6_and_v7_to_compare_performance() throws InterruptedException {
final List<UUID> uuidv6List = new ArrayList<>();
final List<UUID> uuidv8List = new ArrayList<>();
final List<UUID> uuidv7List = new ArrayList<>();
final int MAX_COUNT = 10000;

Instant start = Instant.now();
for (int i = 0; i < MAX_COUNT; i++) {
uuidv8List.add(UuidFactory.Factories.UPROTOCOL.factory().create());
uuidv7List.add(UuidFactory.Factories.UPROTOCOL.factory().create());
}
final Duration v8Diff = Duration.between(start, Instant.now());
final Duration v7Diff = Duration.between(start, Instant.now());

start = Instant.now();
for (int i = 0; i < MAX_COUNT; i++) {
uuidv6List.add(UuidFactory.Factories.UUIDV6.factory().create());
}
final Duration v6Diff = Duration.between(start, Instant.now());
System.out.println(
"UUIDv8:[" + v8Diff.toNanos() / MAX_COUNT + "ns]" + " UUIDv6:[" + v6Diff.toNanos() / MAX_COUNT + "ns]");
"UUIDv7:[" + v7Diff.toNanos() / MAX_COUNT + "ns]" + " UUIDv6:[" + v6Diff.toNanos() / MAX_COUNT + "ns]");
}

@Test
@DisplayName("Test Create UUIDv7 with the same time to confirm the UUIDs are not the same")
void test_create_uuidv7_with_the_same_time_to_confirm_the_uuids_are_not_the_same() {
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());
}


}

Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,18 @@ void test_invalid_time_uuid() {
}

@Test
@DisplayName("Test UUIDv8 validator for null UUID")
void test_uuidv8_with_invalid_uuids() {
@DisplayName("Test UUIDv7 validator for null UUID")
void test_uuidv7_with_invalid_uuids() {
final UuidValidator validator = UuidValidator.Validators.UPROTOCOL.validator();
assertNotNull(validator);
final UStatus status = validator.validate(null);
assertEquals(UCode.INVALID_ARGUMENT, status.getCode());
assertEquals("Invalid UUIDv8 Version,Invalid UUID Time", status.getMessage());
assertEquals("Invalid UUIDv7 Version,Invalid UUID Time", status.getMessage());
}

@Test
@DisplayName("Test UUIDv8 validator for invalid types")
void test_uuidv8_with_invalid_types() {
@DisplayName("Test UUIDv7 validator for invalid types")
void test_uuidv7_with_invalid_types() {
final UUID uuidv6 = UuidFactory.Factories.UUIDV6.factory().create();
final UUID uuid = UUID.newBuilder().setMsb(0L).setLsb(0L).build();
final java.util.UUID uuid_java = java.util.UUID.randomUUID();
Expand All @@ -86,15 +86,15 @@ void test_uuidv8_with_invalid_types() {

final UStatus status = validator.validate(uuidv6);
assertEquals(UCode.INVALID_ARGUMENT, status.getCode());
assertEquals("Invalid UUIDv8 Version", status.getMessage());
assertEquals("Invalid UUIDv7 Version", status.getMessage());

final UStatus status1 = validator.validate(uuid);
assertEquals(UCode.INVALID_ARGUMENT, status1.getCode());
assertEquals("Invalid UUIDv8 Version,Invalid UUID Time", status1.getMessage());
assertEquals("Invalid UUIDv7 Version,Invalid UUID Time", status1.getMessage());

final UStatus status2 = validator.validate(uuidv4);
assertEquals(UCode.INVALID_ARGUMENT, status2.getCode());
assertEquals("Invalid UUIDv8 Version,Invalid UUID Time", status2.getMessage());
assertEquals("Invalid UUIDv7 Version,Invalid UUID Time", status2.getMessage());
}

@Test
Expand Down Expand Up @@ -146,7 +146,7 @@ void test_uuidv6_with_null_uuid() {

@Test
@DisplayName("Test using UUIDv6 Validator to validate a different types of UUIDs")
void test_uuidv6_with_uuidv8() {
void test_uuidv6_with_uuidv7() {
final UUID uuid = UuidFactory.Factories.UPROTOCOL.factory().create();
final UuidValidator validator = UuidValidator.Validators.UUIDV6.validator();
assertNotNull(validator);
Expand Down