-
-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix #124: Manage entropy bits in TimeBasedEpochGenerator
- Loading branch information
Chad Parry
committed
Dec 26, 2024
1 parent
314366d
commit 37d02ef
Showing
2 changed files
with
120 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
src/test/java/com/fasterxml/uuid/impl/TimeBasedEpochGeneratorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package com.fasterxml.uuid.impl; | ||
|
||
import com.fasterxml.uuid.UUIDClock; | ||
import junit.framework.TestCase; | ||
|
||
import java.math.BigInteger; | ||
import java.util.Arrays; | ||
import java.util.UUID; | ||
import java.util.function.Consumer; | ||
|
||
public class TimeBasedEpochGeneratorTest extends TestCase { | ||
|
||
public void testFormat() { | ||
BigInteger minEntropy = BigInteger.ZERO; | ||
long minTimestamp = 0; | ||
TimeBasedEpochGenerator generatorEmpty = new TimeBasedEpochGenerator(staticEntropy(minEntropy), staticClock(minTimestamp)); | ||
UUID uuidEmpty = generatorEmpty.generate(); | ||
assertEquals(0x07, uuidEmpty.version()); | ||
assertEquals(0x02, uuidEmpty.variant()); | ||
assertEquals(minTimestamp, getTimestamp(uuidEmpty)); | ||
assertEquals(minEntropy, getEntropy(uuidEmpty)); | ||
|
||
Consumer<byte[]> entropyFull = bytes -> Arrays.fill(bytes, (byte) 0xFF); | ||
long maxTimestamp = rightBitmask(48); | ||
TimeBasedEpochGenerator generatorFull = new TimeBasedEpochGenerator(entropyFull, staticClock(maxTimestamp)); | ||
UUID uuidFull = generatorFull.generate(); | ||
assertEquals(0x07, uuidFull.version()); | ||
assertEquals(0x02, uuidFull.variant()); | ||
assertEquals(maxTimestamp, getTimestamp(uuidFull)); | ||
assertEquals(BigInteger.ONE.shiftLeft(73).subtract(BigInteger.ONE), getEntropy(uuidFull)); | ||
} | ||
|
||
public void testIncrement() { | ||
TimeBasedEpochGenerator generator = new TimeBasedEpochGenerator(staticEntropy(BigInteger.ZERO), staticClock(0)); | ||
assertEquals(BigInteger.valueOf(0), getEntropy(generator.generate())); | ||
assertEquals(BigInteger.valueOf(1), getEntropy(generator.generate())); | ||
assertEquals(BigInteger.valueOf(2), getEntropy(generator.generate())); | ||
assertEquals(BigInteger.valueOf(3), getEntropy(generator.generate())); | ||
} | ||
|
||
public void testCarryOnce() { | ||
TimeBasedEpochGenerator generator = new TimeBasedEpochGenerator(staticEntropy(BigInteger.valueOf(0xFF)), staticClock(0)); | ||
assertEquals(BigInteger.valueOf(0xFF), getEntropy(generator.generate())); | ||
assertEquals(BigInteger.valueOf(0x100), getEntropy(generator.generate())); | ||
} | ||
|
||
public void testCarryAll() { | ||
BigInteger largeEntropy = BigInteger.ONE.shiftLeft(73).subtract(BigInteger.ONE); | ||
TimeBasedEpochGenerator generator = new TimeBasedEpochGenerator(staticEntropy(largeEntropy), staticClock(0)); | ||
assertEquals(largeEntropy, getEntropy(generator.generate())); | ||
assertEquals(BigInteger.ONE.shiftLeft(73), getEntropy(generator.generate())); | ||
} | ||
|
||
private long getTimestamp(UUID uuid) { | ||
return uuid.getMostSignificantBits() >>> 16; | ||
} | ||
|
||
private BigInteger getEntropy(UUID uuid) { | ||
return BigInteger.valueOf(uuid.getMostSignificantBits() & rightBitmask(12)).shiftLeft(62).or( | ||
BigInteger.valueOf(uuid.getLeastSignificantBits() & rightBitmask(62))); | ||
} | ||
|
||
private Consumer<byte[]> staticEntropy(BigInteger entropy) { | ||
byte[] entropyBytes = entropy.toByteArray(); | ||
return bytes -> { | ||
int offset = bytes.length - entropyBytes.length; | ||
Arrays.fill(bytes, 0, offset, (byte) 0x00); | ||
System.arraycopy(entropyBytes, 0, bytes, offset, entropyBytes.length); | ||
}; | ||
} | ||
|
||
private UUIDClock staticClock(long timestamp) { | ||
return new UUIDClock() { | ||
@Override | ||
public long currentTimeMillis() { | ||
return timestamp; | ||
} | ||
}; | ||
} | ||
|
||
private long rightBitmask(int bits) { | ||
return (1L << bits) - 1; | ||
} | ||
} |