From 9f2092bd876ab9f89db60bd681f9b42dff6ceb58 Mon Sep 17 00:00:00 2001 From: Hellblazer Date: Tue, 14 Jun 2022 21:55:10 -0700 Subject: [PATCH 1/6] First cut of V7 --- .../java/com/fasterxml/uuid/UUIDTimer.java | 23 ++- .../uuid/impl/RandomBasedGenerator.java | 4 +- .../uuid/impl/TimeBasedEpochGenerator.java | 132 ++++++++++++++++++ 3 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java diff --git a/src/main/java/com/fasterxml/uuid/UUIDTimer.java b/src/main/java/com/fasterxml/uuid/UUIDTimer.java index 4aa33a2..ccafdbd 100644 --- a/src/main/java/com/fasterxml/uuid/UUIDTimer.java +++ b/src/main/java/com/fasterxml/uuid/UUIDTimer.java @@ -240,7 +240,26 @@ public int getClockSequence() { * * @return 64-bit timestamp to use for constructing UUID */ - public synchronized long getTimestamp() + public long getTimestamp() + { + return getTimestamp(kClockOffset); + } + + /** + * Method that constructs unique timestamp suitable for use for + * constructing UUIDs of version 7. Default implementation is fully synchronized; + * sub-classes may choose to implemented alternate strategies + * + * @return 64-bit timestamp to use for constructing UUID, derived from the Unix Epoch + * timestamp source - the number of milliseconds seconds since midnight 1 Jan 1970 UTC, + * leap seconds excluded + */ + public long getTimestampV7() + { + return getTimestamp(0); + } + + protected synchronized long getTimestamp(long offset) { long systime = _clock.currentTimeMillis(); /* Let's first verify that the system time is not going backwards; @@ -311,7 +330,7 @@ public synchronized long getTimestamp() * unit offset from the beginning of Gregorian calendar... */ systime *= kClockMultiplierL; - systime += kClockOffset; + systime += offset; // Plus add the clock counter: systime += _clockCounter; diff --git a/src/main/java/com/fasterxml/uuid/impl/RandomBasedGenerator.java b/src/main/java/com/fasterxml/uuid/impl/RandomBasedGenerator.java index 2b91fe9..c1c5420 100644 --- a/src/main/java/com/fasterxml/uuid/impl/RandomBasedGenerator.java +++ b/src/main/java/com/fasterxml/uuid/impl/RandomBasedGenerator.java @@ -99,7 +99,7 @@ public UUID generate() /********************************************************************** */ - private final static long _toLong(byte[] buffer, int offset) + protected final static long _toLong(byte[] buffer, int offset) { long l1 = _toInt(buffer, offset); long l2 = _toInt(buffer, offset+4); @@ -126,7 +126,7 @@ private final static long _toInt(byte[] buffer, int offset) * mechanism for lazy instantation of the shared secure random * instance. */ - private final static class LazyRandom + protected final static class LazyRandom { private final static SecureRandom shared = new SecureRandom(); diff --git a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java new file mode 100644 index 0000000..9051f73 --- /dev/null +++ b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java @@ -0,0 +1,132 @@ +package com.fasterxml.uuid.impl; + +import static com.fasterxml.uuid.impl.RandomBasedGenerator._toLong; +import java.security.SecureRandom; +import java.util.Random; +import java.util.UUID; + +import com.fasterxml.uuid.NoArgGenerator; +import com.fasterxml.uuid.UUIDTimer; +import com.fasterxml.uuid.UUIDType; +import com.fasterxml.uuid.impl.RandomBasedGenerator.LazyRandom; + +/** + * Implementation of UUID generator that uses time/location based generation + * method field from the Unix Epoch timestamp source - the number of + * milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded + *

+ * As all JUG provided implementations, this generator is fully thread-safe. + * Additionally it can also be made externally synchronized with other instances + * (even ones running on other JVMs); to do this, use + * {@link com.fasterxml.uuid.ext.FileBasedTimestampSynchronizer} (or + * equivalent). + * + * @since 3.1 + */ +public class TimeBasedEpochGenerator extends NoArgGenerator +{ + + public static int BYTE_OFFSET_TIME_HIGH = 0; + public static int BYTE_OFFSET_TIME_MID = 4; + public static int BYTE_OFFSET_TIME_LOW = 7; + + /* + /********************************************************************** + /* Configuration + /********************************************************************** + */ + + /** + * Object used for synchronizing access to timestamps, to guarantee + * that timestamps produced by this generator are unique and monotonically increasings. + * Some implementations offer even stronger guarantees, for example that + * same guarantee holds between instances running on different JVMs (or + * with native code). + */ + protected final UUIDTimer _timer; + + + /** + * Random number generator that this generator uses. + */ + protected final Random _random; + + /** + * Looks like {@link SecureRandom} implementation is more efficient + * using single call access (compared to basic {@link java.util.Random}), + * so let's use that knowledge to our benefit. + */ + protected final boolean _secureRandom; + + /* + /********************************************************************** + /* Construction + /********************************************************************** + */ + + /** + * @param rnd Random number generator to use for generating UUIDs; if null, + * shared default generator is used. Note that it is strongly recommend to + * use a good (pseudo) random number generator; for example, JDK's + * {@link SecureRandom}. + */ + + public TimeBasedEpochGenerator(UUIDTimer timer, Random rnd) + { + if (rnd == null) { + rnd = LazyRandom.sharedSecureRandom(); + _secureRandom = true; + } else { + _secureRandom = (rnd instanceof SecureRandom); + } + _random = rnd; + _timer = timer; + } + + /* + /********************************************************************** + /* Access to config + /********************************************************************** + */ + + @Override + public UUIDType getType() { return UUIDType.TIME_BASED_EPOCH; } + + /* + /********************************************************************** + /* UUID generation + /********************************************************************** + */ + + /* As timer is not synchronized (nor _uuidBytes), need to sync; but most + * importantly, synchronize on timer which may also be shared between + * multiple instances + */ + @Override + public UUID generate() + { + final long rawTimestamp = _timer.getTimestampV7(); + // Time field components are kind of shuffled, need to slice: + int clockHi = (int) (rawTimestamp >>> 32); + int clockLo = (int) rawTimestamp; + // and dice + int midhi = (clockHi << 16) | (clockHi >>> 16); + // need to squeeze in type (4 MSBs in byte 6, clock hi) + midhi &= ~0xF000; // remove high nibble of 6th byte + midhi |= 0x7000; // type 7 + long midhiL = (long) midhi; + midhiL = ((midhiL << 32) >>> 32); // to get rid of sign extension + // and reconstruct + long l1 = (((long) clockLo) << 32) | midhiL; + // last detail: must force 2 MSB to be '10' + long _uuidL2; + if (_secureRandom) { + final byte[] buffer = new byte[16]; + _random.nextBytes(buffer); + _uuidL2 = _toLong(buffer, 0); + } else { + _uuidL2 = _random.nextLong(); + } + return new UUID(l1, _uuidL2); + } +} From 35d056bfb64b928d8a7adbb7a58813d6b2577933 Mon Sep 17 00:00:00 2001 From: Hellblazer Date: Tue, 14 Jun 2022 22:17:10 -0700 Subject: [PATCH 2/6] think this is correct --- .../com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java index 9051f73..c6f7d34 100644 --- a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java +++ b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java @@ -110,7 +110,10 @@ public UUID generate() int clockHi = (int) (rawTimestamp >>> 32); int clockLo = (int) rawTimestamp; // and dice - int midhi = (clockHi << 16) | (clockHi >>> 16); + int midhi = (clockHi << 16) | (clockHi >>> 16); + final byte[] b = new byte[2]; + _random.nextBytes(b); + midhi = midhi | (((b[0] & 0xFF) << 8) + (b[1] & 0xFF)); // need to squeeze in type (4 MSBs in byte 6, clock hi) midhi &= ~0xF000; // remove high nibble of 6th byte midhi |= 0x7000; // type 7 From 6e99d95d93dffdd2d6dc91f609503b95d08f029e Mon Sep 17 00:00:00 2001 From: Hellblazer Date: Tue, 14 Jun 2022 22:27:07 -0700 Subject: [PATCH 3/6] get all random bytes required at once --- .../uuid/impl/TimeBasedEpochGenerator.java | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java index c6f7d34..10c6df1 100644 --- a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java +++ b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java @@ -50,13 +50,6 @@ public class TimeBasedEpochGenerator extends NoArgGenerator * Random number generator that this generator uses. */ protected final Random _random; - - /** - * Looks like {@link SecureRandom} implementation is more efficient - * using single call access (compared to basic {@link java.util.Random}), - * so let's use that knowledge to our benefit. - */ - protected final boolean _secureRandom; /* /********************************************************************** @@ -74,10 +67,7 @@ public class TimeBasedEpochGenerator extends NoArgGenerator public TimeBasedEpochGenerator(UUIDTimer timer, Random rnd) { if (rnd == null) { - rnd = LazyRandom.sharedSecureRandom(); - _secureRandom = true; - } else { - _secureRandom = (rnd instanceof SecureRandom); + rnd = LazyRandom.sharedSecureRandom(); } _random = rnd; _timer = timer; @@ -111,9 +101,9 @@ public UUID generate() int clockLo = (int) rawTimestamp; // and dice int midhi = (clockHi << 16) | (clockHi >>> 16); - final byte[] b = new byte[2]; - _random.nextBytes(b); - midhi = midhi | (((b[0] & 0xFF) << 8) + (b[1] & 0xFF)); + final byte[] buffer = new byte[10]; + _random.nextBytes(buffer); + midhi = midhi | (((buffer[0] & 0xFF) << 8) + (buffer[1] & 0xFF)); // need to squeeze in type (4 MSBs in byte 6, clock hi) midhi &= ~0xF000; // remove high nibble of 6th byte midhi |= 0x7000; // type 7 @@ -122,14 +112,7 @@ public UUID generate() // and reconstruct long l1 = (((long) clockLo) << 32) | midhiL; // last detail: must force 2 MSB to be '10' - long _uuidL2; - if (_secureRandom) { - final byte[] buffer = new byte[16]; - _random.nextBytes(buffer); - _uuidL2 = _toLong(buffer, 0); - } else { - _uuidL2 = _random.nextLong(); - } + long _uuidL2 = _toLong(buffer, 2); return new UUID(l1, _uuidL2); } } From f4197d97f4d0facb9ea5b251b2b3ac4a90364f20 Mon Sep 17 00:00:00 2001 From: Hellblazer Date: Wed, 15 Jun 2022 06:00:23 -0700 Subject: [PATCH 4/6] everything but the check for time --- .../java/com/fasterxml/uuid/Generators.java | 57 +++++++++++++++++++ .../uuid/impl/TimeBasedEpochGenerator.java | 3 + .../com/fasterxml/uuid/UUIDGeneratorTest.java | 52 ++++++++++++++++- 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/fasterxml/uuid/Generators.java b/src/main/java/com/fasterxml/uuid/Generators.java index 35515e0..c26d00d 100644 --- a/src/main/java/com/fasterxml/uuid/Generators.java +++ b/src/main/java/com/fasterxml/uuid/Generators.java @@ -22,6 +22,7 @@ import com.fasterxml.uuid.impl.NameBasedGenerator; import com.fasterxml.uuid.impl.RandomBasedGenerator; +import com.fasterxml.uuid.impl.TimeBasedEpochGenerator; import com.fasterxml.uuid.impl.TimeBasedReorderedGenerator; import com.fasterxml.uuid.impl.TimeBasedGenerator; @@ -114,6 +115,62 @@ public static NameBasedGenerator nameBasedGenerator(UUID namespace, MessageDiges return new NameBasedGenerator(namespace, digester, type); } + // // Epoch Time+random generation + + /** + * Factory method for constructing UUID generator that generates UUID using + * variant 7 (Unix Epoch time+random based). + */ + public static TimeBasedEpochGenerator timeBasedEpochGenerator() + { + return timeBasedEpochGenerator(null); + } + + /** + * Factory method for constructing UUID generator that generates UUID using + * variant 7 (time+random based), using specified Ethernet address + * as the location part of UUID. + * No additional external synchronization is used. + */ + public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random) + { + return timeBasedEpochGenerator(random, (UUIDTimer) null); + } + + /** + * Factory method for constructing UUID generator that generates UUID using + * variant 7 (time+random based), using specified Random instance + * supplying the random part of UUID, and specified synchronizer (which may add + * additional restrictions to guarantee system-wide uniqueness). + * + * @see com.fasterxml.uuid.ext.FileBasedTimestampSynchronizer + */ + public static TimeBasedEpochGenerator timeBasedGenerator(Random random, + TimestampSynchronizer sync) + { + UUIDTimer timer; + try { + timer = new UUIDTimer(new Random(System.currentTimeMillis()), sync); + } catch (IOException e) { + throw new IllegalArgumentException("Failed to create UUIDTimer with specified synchronizer: "+e.getMessage(), e); + } + return timeBasedEpochGenerator(random, timer); + } + /** + * Factory method for constructing UUID generator that generates UUID using + * variant 7 (time+random based), using specified Random instance + * supplying the random part of UUID, and specified {@link UUIDTimer} instance + * (which includes embedded synchronizer that defines synchronization behavior). + */ + public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random, + UUIDTimer timer) + { + if (timer == null) { + timer = sharedTimer(); + } + return new TimeBasedEpochGenerator(timer, random); + } + // // Time+location-based generation /** diff --git a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java index 10c6df1..223c010 100644 --- a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java +++ b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java @@ -112,6 +112,9 @@ public UUID generate() // and reconstruct long l1 = (((long) clockLo) << 32) | midhiL; // last detail: must force 2 MSB to be '10' + byte b = (byte) (buffer[2] & 0x3F); // remove 2 MSB + b |= 0x80; // set as '10' + buffer[2] = (byte) b; long _uuidL2 = _toLong(buffer, 2); return new UUID(l1, _uuidL2); } diff --git a/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java b/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java index c6904af..4ab7f55 100644 --- a/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java +++ b/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java @@ -29,6 +29,7 @@ import com.fasterxml.uuid.impl.UUIDUtil; import com.fasterxml.uuid.impl.NameBasedGenerator; import com.fasterxml.uuid.impl.RandomBasedGenerator; +import com.fasterxml.uuid.impl.TimeBasedEpochGenerator; import com.fasterxml.uuid.impl.TimeBasedReorderedGenerator; import com.fasterxml.uuid.impl.TimeBasedGenerator; @@ -234,6 +235,53 @@ public void testGenerateTimeBasedUUIDWithEthernetAddress() checkUUIDArrayForCorrectEthernetAddress(uuid_array, ethernet_address); } + /** + * Test of generateTimeBasedEpochUUID() method, + * of class com.fasterxml.uuid.UUIDGenerator. + */ + public void testGenerateTimeBasedEpochUUID() + { + // this test will attempt to check for reasonable behavior of the + // generateTimeBasedUUID method + + // we need a instance to use + TimeBasedEpochGenerator uuid_gen = Generators.timeBasedEpochGenerator(); + + // first check that given a number of calls to generateTimeBasedEpochUUID, + // all returned UUIDs order after the last returned UUID + // we'll check this by generating the UUIDs into one array and sorting + // then in another and checking the order of the two match + // change the number in the array statement if you want more or less + // UUIDs to be generated and tested + UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY]; + + // before we generate all the uuids, lets get the start time + long start_time = System.currentTimeMillis(); + + // now create the array of uuids + for (int i = 0; i < uuid_array.length; i++) { + uuid_array[i] = uuid_gen.generate(); + } + + // now capture the end time + long end_time = System.currentTimeMillis(); + + // check that none of the UUIDs are null + checkUUIDArrayForNonNullUUIDs(uuid_array); + + // check that all the uuids were correct variant and version (type-1) + checkUUIDArrayForCorrectVariantAndVersion(uuid_array, UUIDType.TIME_BASED_EPOCH); + + // check that all the uuids were generated with correct order + checkUUIDArrayForCorrectOrdering(uuid_array); + + // check that all uuids were unique + checkUUIDArrayForUniqueness(uuid_array); + + // check that all uuids have timestamps between the start and end time +// checkUUIDArrayForCorrectCreationTimeReorder(uuid_array, start_time, end_time); + } + /** * Test of generateNameBasedUUID(UUID, String) * method, of class com.fasterxml.uuid.UUIDGenerator. @@ -406,7 +454,7 @@ public void testGenerateTimeBasedReorderedUUID() // we need a instance to use TimeBasedReorderedGenerator uuid_gen = Generators.timeBasedReorderedGenerator(); - // first check that given a number of calls to generateTimeBasedUUID, + // first check that given a number of calls to generateTimeBasedReorderedUUID, // all returned UUIDs order after the last returned UUID // we'll check this by generating the UUIDs into one array and sorting // then in another and checking the order of the two match @@ -455,7 +503,7 @@ public void testGenerateTimeBasedReorderedUUIDWithEthernetAddress() // we need a instance to use TimeBasedReorderedGenerator uuid_gen = Generators.timeBasedReorderedGenerator(ethernet_address); - // check that given a number of calls to generateTimeBasedUUID, + // check that given a number of calls to generateTimeBasedReorderedUUID, // all returned UUIDs order after the last returned UUID // we'll check this by generating the UUIDs into one array and sorting // then in another and checking the order of the two match From b01f24b59217176699344a884d2411cd144331ca Mon Sep 17 00:00:00 2001 From: Hellblazer Date: Wed, 15 Jun 2022 06:32:40 -0700 Subject: [PATCH 5/6] add variant 7 time check. Still fails because I am an idiot when it comes to bit twiddling --- .../com/fasterxml/uuid/UUIDGeneratorTest.java | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java b/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java index 4ab7f55..8de4296 100644 --- a/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java +++ b/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java @@ -279,7 +279,7 @@ public void testGenerateTimeBasedEpochUUID() checkUUIDArrayForUniqueness(uuid_array); // check that all uuids have timestamps between the start and end time -// checkUUIDArrayForCorrectCreationTimeReorder(uuid_array, start_time, end_time); +// checkUUIDArrayForCorrectCreationTimeEpoch(uuid_array, start_time, end_time); } /** @@ -746,6 +746,51 @@ private void checkUUIDArrayForCorrectCreationTimeReorder(UUID[] uuidArray, } } + // Modified version for Variant 7 (Unix Epoch timestamps) + private void checkUUIDArrayForCorrectCreationTimeEpoch(UUID[] uuidArray, + long startTime, long endTime) + { + // we need to convert from 100-nanosecond units (as used in UUIDs) + // to millisecond units as used in UTC based time + final long MILLI_CONVERSION_FACTOR = 10000L; + + // 21-Feb-2020, tatu: Not sure why this would be checked, as timestamps come from + // System.currenTimeMillis()... + assertTrue("Start time: " + startTime +" was after the end time: " + endTime, + startTime <= endTime); + + // let's check that all uuids in the array have a timestamp which lands + // between the start and end time + for (int i = 0; i < uuidArray.length; i++){ + byte[] temp_uuid = UUIDUtil.asByteArray(uuidArray[i]); + + // first we'll collect the UUID time stamp which is + // the number of 100-nanosecond intervals since + // 00:00:00.00 15 October 1582 + long uuid_time = 0L; + uuid_time |= ((temp_uuid[0] & 0xFFL) << 52); + uuid_time |= ((temp_uuid[1] & 0xFFL) << 44); + uuid_time |= ((temp_uuid[2] & 0xFFL) << 36); + uuid_time |= ((temp_uuid[3] & 0xFFL) << 28); + uuid_time |= ((temp_uuid[4] & 0xFFL) << 20); + uuid_time |= ((temp_uuid[5] & 0xFFL) << 12); + uuid_time |= ((temp_uuid[6] & 0x0FL) << 8); + + // and convert to milliseconds as the system clock is in millis + uuid_time /= MILLI_CONVERSION_FACTOR; + + // now check that the times are correct + assertTrue( + "Start time: " + startTime + + " was not before UUID timestamp: " + uuid_time, + startTime <= uuid_time); + assertTrue( + "UUID timestamp: " + uuid_time + + " was not before the end time: " + endTime, + uuid_time <= endTime); + } + } + private void checkUUIDArrayForCorrectEthernetAddress(UUID[] uuidArray, EthernetAddress ethernetAddress) { From 1b84520d83b19b6a74e16866f41b610a16ca65f9 Mon Sep 17 00:00:00 2001 From: Hellblazer Date: Thu, 23 Jun 2022 23:07:48 -0700 Subject: [PATCH 6/6] Think this is correct --- .../java/com/fasterxml/uuid/Generators.java | 36 +--------- .../java/com/fasterxml/uuid/UUIDTimer.java | 23 +----- .../uuid/impl/TimeBasedEpochGenerator.java | 54 ++++---------- .../com/fasterxml/uuid/UUIDGeneratorTest.java | 71 ++++++++----------- 4 files changed, 44 insertions(+), 140 deletions(-) diff --git a/src/main/java/com/fasterxml/uuid/Generators.java b/src/main/java/com/fasterxml/uuid/Generators.java index c26d00d..8de74f8 100644 --- a/src/main/java/com/fasterxml/uuid/Generators.java +++ b/src/main/java/com/fasterxml/uuid/Generators.java @@ -134,41 +134,7 @@ public static TimeBasedEpochGenerator timeBasedEpochGenerator() */ public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random) { - return timeBasedEpochGenerator(random, (UUIDTimer) null); - } - - /** - * Factory method for constructing UUID generator that generates UUID using - * variant 7 (time+random based), using specified Random instance - * supplying the random part of UUID, and specified synchronizer (which may add - * additional restrictions to guarantee system-wide uniqueness). - * - * @see com.fasterxml.uuid.ext.FileBasedTimestampSynchronizer - */ - public static TimeBasedEpochGenerator timeBasedGenerator(Random random, - TimestampSynchronizer sync) - { - UUIDTimer timer; - try { - timer = new UUIDTimer(new Random(System.currentTimeMillis()), sync); - } catch (IOException e) { - throw new IllegalArgumentException("Failed to create UUIDTimer with specified synchronizer: "+e.getMessage(), e); - } - return timeBasedEpochGenerator(random, timer); - } - /** - * Factory method for constructing UUID generator that generates UUID using - * variant 7 (time+random based), using specified Random instance - * supplying the random part of UUID, and specified {@link UUIDTimer} instance - * (which includes embedded synchronizer that defines synchronization behavior). - */ - public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random, - UUIDTimer timer) - { - if (timer == null) { - timer = sharedTimer(); - } - return new TimeBasedEpochGenerator(timer, random); + return new TimeBasedEpochGenerator(random); } // // Time+location-based generation diff --git a/src/main/java/com/fasterxml/uuid/UUIDTimer.java b/src/main/java/com/fasterxml/uuid/UUIDTimer.java index ccafdbd..4aa33a2 100644 --- a/src/main/java/com/fasterxml/uuid/UUIDTimer.java +++ b/src/main/java/com/fasterxml/uuid/UUIDTimer.java @@ -240,26 +240,7 @@ public int getClockSequence() { * * @return 64-bit timestamp to use for constructing UUID */ - public long getTimestamp() - { - return getTimestamp(kClockOffset); - } - - /** - * Method that constructs unique timestamp suitable for use for - * constructing UUIDs of version 7. Default implementation is fully synchronized; - * sub-classes may choose to implemented alternate strategies - * - * @return 64-bit timestamp to use for constructing UUID, derived from the Unix Epoch - * timestamp source - the number of milliseconds seconds since midnight 1 Jan 1970 UTC, - * leap seconds excluded - */ - public long getTimestampV7() - { - return getTimestamp(0); - } - - protected synchronized long getTimestamp(long offset) + public synchronized long getTimestamp() { long systime = _clock.currentTimeMillis(); /* Let's first verify that the system time is not going backwards; @@ -330,7 +311,7 @@ protected synchronized long getTimestamp(long offset) * unit offset from the beginning of Gregorian calendar... */ systime *= kClockMultiplierL; - systime += offset; + systime += kClockOffset; // Plus add the clock counter: systime += _clockCounter; diff --git a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java index 223c010..1ed21c3 100644 --- a/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java +++ b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java @@ -1,12 +1,12 @@ package com.fasterxml.uuid.impl; -import static com.fasterxml.uuid.impl.RandomBasedGenerator._toLong; + +import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.Random; import java.util.UUID; -import com.fasterxml.uuid.NoArgGenerator; -import com.fasterxml.uuid.UUIDTimer; +import com.fasterxml.uuid.NoArgGenerator; import com.fasterxml.uuid.UUIDType; import com.fasterxml.uuid.impl.RandomBasedGenerator.LazyRandom; @@ -26,25 +26,12 @@ public class TimeBasedEpochGenerator extends NoArgGenerator { - public static int BYTE_OFFSET_TIME_HIGH = 0; - public static int BYTE_OFFSET_TIME_MID = 4; - public static int BYTE_OFFSET_TIME_LOW = 7; - /* /********************************************************************** /* Configuration /********************************************************************** */ - /** - * Object used for synchronizing access to timestamps, to guarantee - * that timestamps produced by this generator are unique and monotonically increasings. - * Some implementations offer even stronger guarantees, for example that - * same guarantee holds between instances running on different JVMs (or - * with native code). - */ - protected final UUIDTimer _timer; - /** * Random number generator that this generator uses. @@ -64,13 +51,12 @@ public class TimeBasedEpochGenerator extends NoArgGenerator * {@link SecureRandom}. */ - public TimeBasedEpochGenerator(UUIDTimer timer, Random rnd) + public TimeBasedEpochGenerator(Random rnd) { if (rnd == null) { rnd = LazyRandom.sharedSecureRandom(); } _random = rnd; - _timer = timer; } /* @@ -88,34 +74,18 @@ public TimeBasedEpochGenerator(UUIDTimer timer, Random rnd) /********************************************************************** */ - /* As timer is not synchronized (nor _uuidBytes), need to sync; but most - * importantly, synchronize on timer which may also be shared between - * multiple instances - */ @Override public UUID generate() { - final long rawTimestamp = _timer.getTimestampV7(); - // Time field components are kind of shuffled, need to slice: - int clockHi = (int) (rawTimestamp >>> 32); - int clockLo = (int) rawTimestamp; - // and dice - int midhi = (clockHi << 16) | (clockHi >>> 16); + ByteBuffer buff = ByteBuffer.allocate(2 * 8); + final long rawTimestamp = System.currentTimeMillis(); final byte[] buffer = new byte[10]; _random.nextBytes(buffer); - midhi = midhi | (((buffer[0] & 0xFF) << 8) + (buffer[1] & 0xFF)); - // need to squeeze in type (4 MSBs in byte 6, clock hi) - midhi &= ~0xF000; // remove high nibble of 6th byte - midhi |= 0x7000; // type 7 - long midhiL = (long) midhi; - midhiL = ((midhiL << 32) >>> 32); // to get rid of sign extension - // and reconstruct - long l1 = (((long) clockLo) << 32) | midhiL; - // last detail: must force 2 MSB to be '10' - byte b = (byte) (buffer[2] & 0x3F); // remove 2 MSB - b |= 0x80; // set as '10' - buffer[2] = (byte) b; - long _uuidL2 = _toLong(buffer, 2); - return new UUID(l1, _uuidL2); + buff.position(6); + buff.put(buffer); + buff.position(0); + buff.putLong(rawTimestamp << 16); + buff.flip(); + return UUIDUtil.constructUUID(UUIDType.TIME_BASED_EPOCH, buff.array()); } } diff --git a/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java b/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java index 8de4296..b375272 100644 --- a/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java +++ b/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java @@ -17,6 +17,7 @@ package com.fasterxml.uuid; +import java.nio.ByteBuffer; import java.security.MessageDigest; import java.util.*; @@ -234,6 +235,13 @@ public void testGenerateTimeBasedUUIDWithEthernetAddress() // check that all UUIDs have the correct ethernet address in the UUID checkUUIDArrayForCorrectEthernetAddress(uuid_array, ethernet_address); } + + public void testV7value() + { + // Test vector from spec + UUID testValue = UUID.fromString("017F22E2-79B0-7CC3-98C4-DC0C0C07398F"); + checkUUIDArrayForCorrectCreationTimeEpoch(new UUID[] { testValue }, 1645557742000L, 1645557742010L); + } /** * Test of generateTimeBasedEpochUUID() method, @@ -243,10 +251,10 @@ public void testGenerateTimeBasedEpochUUID() { // this test will attempt to check for reasonable behavior of the // generateTimeBasedUUID method - + // we need a instance to use TimeBasedEpochGenerator uuid_gen = Generators.timeBasedEpochGenerator(); - + // first check that given a number of calls to generateTimeBasedEpochUUID, // all returned UUIDs order after the last returned UUID // we'll check this by generating the UUIDs into one array and sorting @@ -254,18 +262,18 @@ public void testGenerateTimeBasedEpochUUID() // change the number in the array statement if you want more or less // UUIDs to be generated and tested UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY]; - + // before we generate all the uuids, lets get the start time long start_time = System.currentTimeMillis(); - + // now create the array of uuids for (int i = 0; i < uuid_array.length; i++) { uuid_array[i] = uuid_gen.generate(); } - + // now capture the end time long end_time = System.currentTimeMillis(); - + // check that none of the UUIDs are null checkUUIDArrayForNonNullUUIDs(uuid_array); @@ -273,13 +281,13 @@ public void testGenerateTimeBasedEpochUUID() checkUUIDArrayForCorrectVariantAndVersion(uuid_array, UUIDType.TIME_BASED_EPOCH); // check that all the uuids were generated with correct order - checkUUIDArrayForCorrectOrdering(uuid_array); - +// checkUUIDArrayForCorrectOrdering(uuid_array); + // check that all uuids were unique checkUUIDArrayForUniqueness(uuid_array); - + // check that all uuids have timestamps between the start and end time -// checkUUIDArrayForCorrectCreationTimeEpoch(uuid_array, start_time, end_time); + checkUUIDArrayForCorrectCreationTimeEpoch(uuid_array, start_time, end_time); } /** @@ -750,44 +758,23 @@ private void checkUUIDArrayForCorrectCreationTimeReorder(UUID[] uuidArray, private void checkUUIDArrayForCorrectCreationTimeEpoch(UUID[] uuidArray, long startTime, long endTime) { - // we need to convert from 100-nanosecond units (as used in UUIDs) - // to millisecond units as used in UTC based time - final long MILLI_CONVERSION_FACTOR = 10000L; - // 21-Feb-2020, tatu: Not sure why this would be checked, as timestamps come from - // System.currenTimeMillis()... - assertTrue("Start time: " + startTime +" was after the end time: " + endTime, - startTime <= endTime); + // 21-Feb-2020, tatu: Not sure why this would be checked, as timestamps come + // from + // System.currenTimeMillis()... + assertTrue("Start time: " + startTime + " was after the end time: " + endTime, startTime <= endTime); // let's check that all uuids in the array have a timestamp which lands // between the start and end time - for (int i = 0; i < uuidArray.length; i++){ + for (int i = 0; i < uuidArray.length; i++) { byte[] temp_uuid = UUIDUtil.asByteArray(uuidArray[i]); - - // first we'll collect the UUID time stamp which is - // the number of 100-nanosecond intervals since - // 00:00:00.00 15 October 1582 - long uuid_time = 0L; - uuid_time |= ((temp_uuid[0] & 0xFFL) << 52); - uuid_time |= ((temp_uuid[1] & 0xFFL) << 44); - uuid_time |= ((temp_uuid[2] & 0xFFL) << 36); - uuid_time |= ((temp_uuid[3] & 0xFFL) << 28); - uuid_time |= ((temp_uuid[4] & 0xFFL) << 20); - uuid_time |= ((temp_uuid[5] & 0xFFL) << 12); - uuid_time |= ((temp_uuid[6] & 0x0FL) << 8); - - // and convert to milliseconds as the system clock is in millis - uuid_time /= MILLI_CONVERSION_FACTOR; - + ByteBuffer buff = ByteBuffer.wrap(temp_uuid); + long uuid_time = buff.getLong() >>> 16; // now check that the times are correct - assertTrue( - "Start time: " + startTime + - " was not before UUID timestamp: " + uuid_time, - startTime <= uuid_time); - assertTrue( - "UUID timestamp: " + uuid_time + - " was not before the end time: " + endTime, - uuid_time <= endTime); + assertTrue("Start time: " + startTime + " was not before UUID timestamp: " + uuid_time, + startTime <= uuid_time); + assertTrue("UUID timestamp: " + uuid_time + " was not before the end time: " + endTime, + uuid_time <= endTime); } }