Created
February 12, 2016 15:17
-
-
Save mikeapr4/1c0982da07d0afad5f30 to your computer and use it in GitHub Desktop.
Java class for managing encrypted nonces, providing them and verifying/expiring them
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
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; | |
} | |
} | |
} |
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
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()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 itIt 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.