veyron/lib/uniqueid: Add a package for generating probabalistically unique IDs.
Change-Id: Ic3ed80071006dc713070b90a67e72e29ccabae32
diff --git a/lib/uniqueid/uniqueid.go b/lib/uniqueid/uniqueid.go
new file mode 100644
index 0000000..c441c4c
--- /dev/null
+++ b/lib/uniqueid/uniqueid.go
@@ -0,0 +1,47 @@
+// Package uniqueid helps generate identifiers that are likely to be
+// globally unique. We want to be able to generate many IDs quickly,
+// so we make a time/space tradeoff. We reuse the same random data
+// many times with a counter appended. Note: these IDs are NOT useful
+// as a security mechanism as they will be predictable.
+package uniqueid
+
+import (
+ "crypto/rand"
+ "encoding/binary"
+ "sync"
+)
+
+type ID [16]byte
+
+var random = RandomGenerator{}
+
+// A RandomGenerator can generate random IDs.
+// The zero value of RandomGenerator is ready to use.
+type RandomGenerator struct {
+ mu sync.Mutex
+ id ID
+ count uint16
+ resets int
+}
+
+// NewID produces a new probably unique identifier.
+func (g *RandomGenerator) NewID() (ID, error) {
+ g.mu.Lock()
+ defer g.mu.Unlock()
+ if g.count == 0 {
+ // Either the generator is uninitialized or the counter
+ // has wrapped. We need a new random prefix.
+ if _, err := rand.Read(g.id[:14]); err != nil {
+ return ID{}, err
+ }
+ g.resets++
+ }
+ binary.BigEndian.PutUint16(g.id[14:], g.count)
+ g.count++
+ return g.id, nil
+}
+
+// Random produces a new probably unique identifier using the RandomGenerator.
+func Random() (ID, error) {
+ return random.NewID()
+}
diff --git a/lib/uniqueid/uniqueid_test.go b/lib/uniqueid/uniqueid_test.go
new file mode 100644
index 0000000..8c66fbe
--- /dev/null
+++ b/lib/uniqueid/uniqueid_test.go
@@ -0,0 +1,23 @@
+package uniqueid
+
+import "testing"
+
+func TestNewID(t *testing.T) {
+ g := RandomGenerator{}
+ expectedResets := 5
+ for i := 0; i < expectedResets*(1<<16); i++ {
+ g.NewID()
+ }
+ if g.resets != expectedResets {
+ t.Errorf("wrong number of resets, want %d got %d", expectedResets, g.resets)
+ }
+}
+
+func BenchmarkNewIDParallel(b *testing.B) {
+ g := RandomGenerator{}
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ g.NewID()
+ }
+ })
+}