-
Notifications
You must be signed in to change notification settings - Fork 158
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update NUID implementation to match Go (resolves #58)
- Loading branch information
Larry McQueary
committed
Aug 30, 2016
1 parent
3108b32
commit ba07a80
Showing
2 changed files
with
251 additions
and
208 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,165 +1,214 @@ | ||
package io.nats.client; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.nio.ByteBuffer; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.security.SecureRandom; | ||
import java.util.Random; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public class NUID { | ||
|
||
final static Logger logger = LoggerFactory.getLogger(NUID.class); | ||
|
||
// NUID needs to be very fast to generate and truly unique, all while being entropy pool friendly. | ||
// We will use 12 bytes of crypto generated data (entropy draining), and 10 bytes of sequential data | ||
// that is started at a pseudo random number and increments with a pseudo-random increment. | ||
// Total is 22 bytes of base 36 ascii text :) | ||
|
||
// Constants | ||
static final char[] digits = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z' }; | ||
static final int base = 36; | ||
static final int preLen = 12; | ||
static final int seqLen = 10; | ||
static final long maxPre = 4738381338321616896L; // base^preLen == 36^12 | ||
static final long maxSeq = 3656158440062976L; // base^seqLen == 36^10 | ||
static final long minInc = 33L; | ||
static final long maxInc = 333L; | ||
static final int totalLen = preLen + seqLen; | ||
static Random srand; | ||
static Random prand; | ||
|
||
// Instance fields | ||
char[] pre; | ||
long seq; | ||
long inc; | ||
|
||
|
||
// Global NUID | ||
public static NUID globalNUID = new NUID(); | ||
private static Object lock = new Object(); | ||
|
||
static NUID getInstance() { | ||
if (globalNUID == null) { | ||
globalNUID = new NUID(); | ||
} | ||
return globalNUID; | ||
} | ||
|
||
public NUID () { | ||
if (srand == null) { | ||
try { | ||
srand = SecureRandom.getInstance("SHA1PRNG"); | ||
} catch (NoSuchAlgorithmException e) { | ||
logger.error("stan: nuid algorithm not found", e); | ||
} | ||
prand = new Random(); | ||
} | ||
seq = nextLong(prand, maxSeq); | ||
inc = minInc + nextLong(prand, maxInc-minInc); | ||
pre = new char[preLen]; | ||
for (int i = 0; i < preLen; i++) { | ||
pre[i] = '0'; | ||
} | ||
randomizePrefix(); | ||
} | ||
|
||
// Generate the next NUID string from the global locked NUID instance. | ||
public static String nextGlobal() { | ||
synchronized(lock) { | ||
return getInstance().next(); | ||
} | ||
} | ||
|
||
// Generate the next NUID string. | ||
public String next() { | ||
// Increment and capture. | ||
seq += inc; | ||
if (seq >= maxSeq) { | ||
randomizePrefix(); | ||
resetSequential(); | ||
} | ||
|
||
// Copy prefix | ||
char[] b = new char[totalLen]; | ||
System.arraycopy(pre, 0, b, 0, preLen); | ||
|
||
// copy in the seq in base36. | ||
int i = b.length; | ||
for (long l = seq; i > preLen; l /= base) { | ||
i--; | ||
b[i] = digits[(int)(l%base)]; | ||
} | ||
return new String(b); | ||
} | ||
|
||
// Resets the sequntial portion of the NUID | ||
void resetSequential() { | ||
seq = nextLong(prand, maxSeq); | ||
inc = minInc + nextLong(prand, maxInc-minInc); | ||
} | ||
|
||
// Generate a new prefix from random. | ||
// This will drain entropy and will be called automatically when we exhaust the sequential | ||
// Will panic if it gets an error from rand.Int() | ||
public void randomizePrefix() { | ||
long n = nextLong(srand, maxPre); | ||
int i = pre.length; | ||
for (long l = n; i>0; l /= base) { | ||
i--; | ||
pre[i] = digits[(int)(l % base)]; | ||
} | ||
} | ||
|
||
static long nextLong(Random rng, long n) { | ||
// error checking and 2^x checking removed for simplicity. | ||
long bits, val; | ||
do { | ||
bits = (rng.nextLong() << 1) >>> 1; | ||
val = bits % n; | ||
} while (bits-val+(n-1) < 0L); | ||
return val; | ||
} | ||
|
||
/** | ||
* @return the pre | ||
*/ | ||
char[] getPre() { | ||
return pre; | ||
} | ||
|
||
/** | ||
* @param pre the pre to set | ||
*/ | ||
void setPre(char[] pre) { | ||
this.pre = pre; | ||
} | ||
|
||
/** | ||
* @return the seq | ||
*/ | ||
long getSeq() { | ||
return seq; | ||
} | ||
|
||
/** | ||
* @param seq the seq to set | ||
*/ | ||
void setSeq(long seq) { | ||
this.seq = seq; | ||
} | ||
|
||
/** | ||
* @return the inc | ||
*/ | ||
long getInc() { | ||
return inc; | ||
} | ||
|
||
/** | ||
* @param inc the inc to set | ||
*/ | ||
void setInc(long inc) { | ||
this.inc = inc; | ||
} | ||
final static Logger logger = LoggerFactory.getLogger(NUID.class); | ||
|
||
/* | ||
* NUID needs to be very fast to generate and truly unique, all while being entropy pool | ||
* friendly. We will use 12 bytes of crypto generated data (entropy draining), and 10 bytes of | ||
* sequential data that is started at a pseudo random number and increments with a pseudo-random | ||
* increment. Total is 22 bytes of base 62 ascii text :) | ||
*/ | ||
|
||
// Constants | ||
static final char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', | ||
'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', | ||
'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', | ||
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; | ||
static final int base = 62; | ||
static final int preLen = 12; | ||
static final int seqLen = 10; | ||
static final long maxSeq = 839299365868340224L; // base^seqLen == 62^10 | ||
static final long minInc = 33L; | ||
static final long maxInc = 333L; | ||
static final int totalLen = preLen + seqLen; | ||
static SecureRandom srand; | ||
static Random prand; | ||
|
||
// Instance fields | ||
char[] pre; | ||
long seq; | ||
long inc; | ||
|
||
|
||
// Global NUID | ||
public static NUID globalNUID = new NUID(); | ||
private static Object lock = new Object(); | ||
|
||
static NUID getInstance() { | ||
if (globalNUID == null) { | ||
globalNUID = new NUID(); | ||
} | ||
return globalNUID; | ||
} | ||
|
||
public NUID() { | ||
// Generate a cryto random int, 0 <= val < max to seed pseudorandom | ||
long seed = 0L; | ||
if (srand == null) { | ||
try { | ||
srand = SecureRandom.getInstance("SHA1PRNG"); | ||
seed = bytesToLong(srand.generateSeed(8)); // seed with 8 bytes (64 bits) | ||
} catch (NoSuchAlgorithmException e) { | ||
logger.error("nats: nuid algorithm not found", e); | ||
} | ||
|
||
if (seed != 0L) { | ||
prand = new Random(seed); | ||
} else { | ||
prand = new Random(); | ||
} | ||
} | ||
seq = nextLong(prand, maxSeq); | ||
inc = minInc + nextLong(prand, maxInc - minInc); | ||
pre = new char[preLen]; | ||
for (int i = 0; i < preLen; i++) { | ||
pre[i] = '0'; | ||
} | ||
randomizePrefix(); | ||
} | ||
|
||
/** | ||
* Generate the next NUID string from the global locked NUID instance. | ||
* | ||
* @return the next NUID string from the global locked NUID instance. | ||
*/ | ||
public static String nextGlobal() { | ||
synchronized (lock) { | ||
return getInstance().next(); | ||
} | ||
} | ||
|
||
/** | ||
* Generate the next NUID string from this instance. | ||
* | ||
* @return the next NUID string from this instance. | ||
*/ | ||
public String next() { | ||
// Increment and capture. | ||
seq += inc; | ||
if (seq >= maxSeq) { | ||
randomizePrefix(); | ||
resetSequential(); | ||
} | ||
|
||
// Copy prefix | ||
char[] b = new char[totalLen]; | ||
System.arraycopy(pre, 0, b, 0, preLen); | ||
|
||
// copy in the seq in base36. | ||
int i = b.length; | ||
for (long l = seq; i > preLen; l /= base) { | ||
i--; | ||
b[i] = digits[(int) (l % base)]; | ||
} | ||
return new String(b); | ||
} | ||
|
||
// Resets the sequntial portion of the NUID | ||
void resetSequential() { | ||
seq = nextLong(prand, maxSeq); | ||
inc = minInc + nextLong(prand, maxInc - minInc); | ||
} | ||
|
||
/* | ||
* Generate a new prefix from random. This *can* drain entropy and will be called automatically | ||
* when we exhaust the sequential range. | ||
*/ | ||
|
||
void randomizePrefix() { | ||
byte[] cb = new byte[preLen]; | ||
|
||
// Use SecureRandom for prefix only | ||
srand.nextBytes(cb); | ||
|
||
for (int i = 0; i < preLen; i++) { | ||
pre[i] = digits[(cb[i] & 0xFF) % base]; | ||
} | ||
} | ||
|
||
static long nextLong(Random rng, long maxValue) { | ||
// error checking and 2^x checking removed for simplicity. | ||
long bits; | ||
long val; | ||
do { | ||
bits = (rng.nextLong() << 1) >>> 1; | ||
val = bits % maxValue; | ||
} while (bits - val + (maxValue - 1) < 0L); | ||
return val; | ||
} | ||
|
||
byte[] longToBytes(long x) { | ||
ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE); | ||
buffer.putLong(x); | ||
return buffer.array(); | ||
} | ||
|
||
long bytesToLong(byte[] bytes) { | ||
ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE); | ||
buffer.put(bytes); | ||
buffer.flip();// need flip | ||
return buffer.getLong(); | ||
} | ||
|
||
/** | ||
* @return the pre | ||
*/ | ||
char[] getPre() { | ||
return pre; | ||
} | ||
|
||
/** | ||
* Sets the prefix. | ||
* | ||
* @param pre the pre to set | ||
*/ | ||
void setPre(char[] pre) { | ||
this.pre = pre; | ||
} | ||
|
||
/** | ||
* Return the current sequence value. | ||
* | ||
* @return the seq | ||
*/ | ||
long getSeq() { | ||
return seq; | ||
} | ||
|
||
/** | ||
* Set the sequence to the supplied value. | ||
* | ||
* @param seq the seq to set | ||
*/ | ||
void setSeq(long seq) { | ||
this.seq = seq; | ||
} | ||
|
||
/** | ||
* Return the current increment. | ||
* | ||
* @return the inc | ||
*/ | ||
long getInc() { | ||
return inc; | ||
} | ||
|
||
/** | ||
* Set the increment to the supplied value. | ||
* | ||
* @param inc the inc to set | ||
*/ | ||
void setInc(long inc) { | ||
this.inc = inc; | ||
} | ||
} |
Oops, something went wrong.