diff --git a/src/main/java/com/fasterxml/uuid/Generators.java b/src/main/java/com/fasterxml/uuid/Generators.java index 35515e0..8de74f8 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,28 @@ 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 new TimeBasedEpochGenerator(random); + } + // // Time+location-based generation /** 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..1ed21c3 --- /dev/null +++ b/src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java @@ -0,0 +1,91 @@ +package com.fasterxml.uuid.impl; + + +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.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 +{ + + /* + /********************************************************************** + /* Configuration + /********************************************************************** + */ + + + /** + * Random number generator that this generator uses. + */ + protected final Random _random; + + /* + /********************************************************************** + /* 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(Random rnd) + { + if (rnd == null) { + rnd = LazyRandom.sharedSecureRandom(); + } + _random = rnd; + } + + /* + /********************************************************************** + /* Access to config + /********************************************************************** + */ + + @Override + public UUIDType getType() { return UUIDType.TIME_BASED_EPOCH; } + + /* + /********************************************************************** + /* UUID generation + /********************************************************************** + */ + + @Override + public UUID generate() + { + ByteBuffer buff = ByteBuffer.allocate(2 * 8); + final long rawTimestamp = System.currentTimeMillis(); + final byte[] buffer = new byte[10]; + _random.nextBytes(buffer); + 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 c6904af..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.*; @@ -29,6 +30,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; @@ -233,6 +235,60 @@ 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, + * 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 + checkUUIDArrayForCorrectCreationTimeEpoch(uuid_array, start_time, end_time); + } /** * Test of generateNameBasedUUID(UUID, String) @@ -406,7 +462,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 +511,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 @@ -698,6 +754,30 @@ private void checkUUIDArrayForCorrectCreationTimeReorder(UUID[] uuidArray, } } + // Modified version for Variant 7 (Unix Epoch timestamps) + private void checkUUIDArrayForCorrectCreationTimeEpoch(UUID[] uuidArray, + long startTime, long 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++) { + byte[] temp_uuid = UUIDUtil.asByteArray(uuidArray[i]); + 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); + } + } + private void checkUUIDArrayForCorrectEthernetAddress(UUID[] uuidArray, EthernetAddress ethernetAddress) {