Skip to content

Instantly share code, notes, and snippets.

@mikeapr4
Created February 12, 2016 15:17
Show Gist options
  • Save mikeapr4/1c0982da07d0afad5f30 to your computer and use it in GitHub Desktop.
Save mikeapr4/1c0982da07d0afad5f30 to your computer and use it in GitHub Desktop.
Java class for managing encrypted nonces, providing them and verifying/expiring them
import java.util.BitSet;
import org.joda.time.DateTimeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Creates unique (likely consecutive) integers which can be used
* within a 30min (to 1hr) period. Main methods are:
* * nextNonce - to return an integer.
* * validateNonce - to verify an active integer and deactivate it
* <p/>
* It is thread-safe and expires integers lazily. It uses
* java.util.BitSet storage to flag a position as active or inactive.
* There are two BitSet stores, one is active and one is dormant, each
* cycle the active/dormant stores are switched, allowing the dormant
* store numbers to be expired as a whole.
*/
public class NonceFactory {
// time in milliseconds that a nonce is valid
public static final long NONCE_TIMEOUT = 30L * 60L * 1000L;
private static final Logger log = LoggerFactory.getLogger(NonceFactory.class);
private int batchSize;
private BitSet lowSet, highSet;
private boolean usingLowSet;
private long lastRotation;
// keep track of last bit to ensure nonce increments continually
private int fromIndex;
public NonceFactory(int maxValue) {
this.batchSize = maxValue / 2;
this.lowSet = new BitSet(batchSize);
this.highSet = new BitSet(batchSize);
usingLowSet = true;
lastRotation = DateTimeUtils.currentTimeMillis();
fromIndex = 0;
}
public synchronized Integer nextNonce() {
checkRotation();
BitSet workingSet = usingLowSet ? lowSet : highSet;
if (workingSet.cardinality() == batchSize) { // batch is full (need to avoid expanding it)
log.error("NonceFactory BitSet ran out of nonce values, consider increasing the maxValue ({}).", batchSize * 2);
return null;
}
if (fromIndex >= batchSize) {
fromIndex = 0;
}
int nonce = workingSet.nextClearBit(fromIndex);
if (nonce == -1) {
//nonce = workingSet.nextClearBit(0);
return null;
}
workingSet.set(nonce);
fromIndex = nonce + 1;
return usingLowSet ? nonce : nonce + batchSize;
}
public synchronized boolean validateNonce(int nonceValue) {
checkRotation();
boolean lowNonce = nonceValue < batchSize;
BitSet workingSet = lowNonce ? lowSet : highSet;
int workingBit = lowNonce ? nonceValue : nonceValue - batchSize;
if (!workingSet.get(workingBit)) {
return false;
}
workingSet.clear(workingBit);
return true;
}
private void checkRotation() {
long now = DateTimeUtils.currentTimeMillis();
if ((now - lastRotation) >= NONCE_TIMEOUT) {
BitSet expiredSet = usingLowSet ? highSet : lowSet;
log.debug("Expiring NonceFactory BitSet: {} open nonces which will now be expired.", expiredSet.cardinality());
expiredSet.clear();
usingLowSet = !usingLowSet;
fromIndex = 0;
lastRotation = now;
}
}
}
import org.joda.time.DateTimeUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class NonceFactoryTest {
private NonceFactory factory;
@Before
public void setup() {
DateTimeUtils.setCurrentMillisSystem();
factory = new NonceFactory(1000);
}
@After
public void teardown() {
DateTimeUtils.setCurrentMillisSystem();
}
@Test
public void nonceFactory() {
assertEquals(factory.nextNonce().intValue(), 0);
assertEquals(factory.nextNonce().intValue(), 1);
assertEquals(factory.nextNonce().intValue(), 2);
assertTrue(factory.validateNonce(1));
assertFalse(factory.validateNonce(1)); // only valid once
assertEquals(factory.nextNonce().intValue(), 3); // 1 is free, but nonce must increment from last bit
DateTimeUtils.setCurrentMillisOffset(NonceFactory.NONCE_TIMEOUT);
assertEquals(factory.nextNonce().intValue(), 500); // switched batch
// 0 still valid, although last batch is now closed,
// it's still valid, just won't have anymore nonces set
assertTrue(factory.validateNonce(0));
assertTrue(factory.validateNonce(500));
assertEquals(factory.nextNonce().intValue(), 501); // even though 500 is now free, nonce must increment
assertEquals(factory.nextNonce().intValue(), 502);
DateTimeUtils.setCurrentMillisOffset(NonceFactory.NONCE_TIMEOUT * 2);
assertFalse(factory.validateNonce(3)); // 3 has now expired
assertTrue(factory.validateNonce(501)); // 501 still valid
assertEquals(factory.nextNonce().intValue(), 0); // switched batch
}
@Test
public void nonceLimit() {
for (int i = 0; i < 500; i++) {
assertEquals(factory.nextNonce().intValue(), i);
}
assertNull(factory.nextNonce());
DateTimeUtils.setCurrentMillisOffset(NonceFactory.NONCE_TIMEOUT);
for (int i = 500; i < 1000; i++) {
assertEquals(factory.nextNonce().intValue(), i);
}
assertNull(factory.nextNonce());
DateTimeUtils.setCurrentMillisOffset(NonceFactory.NONCE_TIMEOUT * 2);
assertTrue(factory.validateNonce(700)); // Higher batch still valid
assertFalse(factory.validateNonce(0)); // Lower batch wiped
assertEquals(factory.nextNonce().intValue(), 0); // Lower batch now in use
// Now fill up 300 places (1 place already filled)
for (int i = 1; i < 300; i++) {
assertEquals(factory.nextNonce().intValue(), i);
}
// Clear 200
for (int i = 0; i < 200; i++) {
assertTrue(factory.validateNonce(i));
}
assertEquals(factory.nextNonce().intValue(), 300);
// Now fill up 399 places successful (because of wrap around)
for (int i = 301; i < 500; i++) {
assertEquals(factory.nextNonce().intValue(), i);
}
for (int i = 0; i < 200; i++) {
assertEquals(factory.nextNonce().intValue(), i);
}
// Now it's all full
assertNull(factory.nextNonce());
}
@Test
public void nonceWrap() {
for (int i = 0; i < 400; i++) {
assertEquals(factory.nextNonce().intValue(), i);
}
assertTrue(factory.validateNonce(100));
assertTrue(factory.validateNonce(101));
assertTrue(factory.validateNonce(103));
for (int i = 400; i < 500; i++) {
assertEquals(factory.nextNonce().intValue(), i);
}
assertEquals(factory.nextNonce().intValue(), 100);
assertEquals(factory.nextNonce().intValue(), 101);
assertEquals(factory.nextNonce().intValue(), 103);
assertNull(factory.nextNonce());
}
}
@mikeapr4
Copy link
Author

Java class for managing encrypted nonces, providing them and verifying/expiring them

Creates unique (likely consecutive) integers which can be used within a 30min (to 1hr) period. Main methods are:

  • nextNonce - to return an integer.
  • validateNonce - to verify an active integer and deactivate it

It is thread-safe and expires integers lazily. It uses java.util.BitSet storage to flag a position as active or inactive.
There are two BitSet stores, one is active and one is dormant, each cycle the active/dormant stores are switched, allowing
the dormant store numbers to be expired as a whole.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment