From e703c296adc41146c7f8b0c3409e95bc28dbb0f0 Mon Sep 17 00:00:00 2001 From: Jesse Rittner Date: Fri, 30 Jul 2021 23:47:15 -0400 Subject: [PATCH 1/2] Revert "Add randomness pool mode for V4 UUID (#80)" This reverts commit 655bf50db9d265813a26b0fe2a5d1e80fb9e3c6b. --- uuid.go | 39 +--------------------------------- uuid_test.go | 59 ---------------------------------------------------- version4.go | 27 +----------------------- 3 files changed, 2 insertions(+), 123 deletions(-) diff --git a/uuid.go b/uuid.go index a57207a..d732ae8 100644 --- a/uuid.go +++ b/uuid.go @@ -12,7 +12,6 @@ import ( "fmt" "io" "strings" - "sync" ) // A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC @@ -34,15 +33,7 @@ const ( Future // Reserved for future definition. ) -const randPoolSize = 16 * 16 - -var ( - rander = rand.Reader // random function - poolEnabled = false - poolMu sync.Mutex - poolPos = randPoolSize // protected with poolMu - pool [randPoolSize]byte // protected with poolMu -) +var rander = rand.Reader // random function type invalidLengthError struct{ len int } @@ -264,31 +255,3 @@ func SetRand(r io.Reader) { } rander = r } - -// EnableRandPool enables internal randomness pool used for Random -// (Version 4) UUID generation. The pool contains random bytes read from -// the random number generator on demand in batches. Enabling the pool -// may improve the UUID generation throughput significantly. -// -// Since the pool is stored on the Go heap, this feature may be a bad fit -// for security sensitive applications. -// -// Both EnableRandPool and DisableRandPool are not thread-safe and should -// only be called when there is no possibility that New or any other -// UUID Version 4 generation function will be called concurrently. -func EnableRandPool() { - poolEnabled = true -} - -// DisableRandPool disables the randomness pool if it was previously -// enabled with EnableRandPool. -// -// Both EnableRandPool and DisableRandPool are not thread-safe and should -// only be called when there is no possibility that New or any other -// UUID Version 4 generation function will be called concurrently. -func DisableRandPool() { - poolEnabled = false - defer poolMu.Unlock() - poolMu.Lock() - poolPos = randPoolSize -} diff --git a/uuid_test.go b/uuid_test.go index e98d0fe..99f0bed 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -179,26 +179,6 @@ func TestRandomUUID(t *testing.T) { } } -func TestRandomUUID_Pooled(t *testing.T) { - defer DisableRandPool() - EnableRandPool() - m := make(map[string]bool) - for x := 1; x < 128; x++ { - uuid := New() - s := uuid.String() - if m[s] { - t.Errorf("NewRandom returned duplicated UUID %s", s) - } - m[s] = true - if v := uuid.Version(); v != 4 { - t.Errorf("Random UUID of version %s", v) - } - if uuid.Variant() != RFC4122 { - t.Errorf("Random UUID is variant %d", uuid.Variant()) - } - } -} - func TestNew(t *testing.T) { m := make(map[UUID]bool) for x := 1; x < 32; x++ { @@ -537,22 +517,6 @@ func TestRandomFromReader(t *testing.T) { } } -func TestRandPool(t *testing.T) { - myString := "8059ddhdle77cb52" - EnableRandPool() - SetRand(strings.NewReader(myString)) - _, err := NewRandom() - if err == nil { - t.Errorf("expecting an error as reader has no more bytes") - } - DisableRandPool() - SetRand(strings.NewReader(myString)) - _, err = NewRandom() - if err != nil { - t.Errorf("failed generating UUID from a reader") - } -} - func TestWrongLength(t *testing.T) { _, err := Parse("12345") if err == nil { @@ -677,26 +641,3 @@ func BenchmarkParseLen36Corrupted(b *testing.B) { } } } - -func BenchmarkUUID_New(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - _, err := NewRandom() - if err != nil { - b.Fatal(err) - } - } - }) -} - -func BenchmarkUUID_NewPooled(b *testing.B) { - EnableRandPool() - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - _, err := NewRandom() - if err != nil { - b.Fatal(err) - } - } - }) -} diff --git a/version4.go b/version4.go index 7697802..86160fb 100644 --- a/version4.go +++ b/version4.go @@ -27,8 +27,6 @@ func NewString() string { // The strength of the UUIDs is based on the strength of the crypto/rand // package. // -// Uses the randomness pool if it was enabled with EnableRandPool. -// // A note about uniqueness derived from the UUID Wikipedia entry: // // Randomly generated UUIDs have 122 random bits. One's annual risk of being @@ -37,10 +35,7 @@ func NewString() string { // equivalent to the odds of creating a few tens of trillions of UUIDs in a // year and having one duplicate. func NewRandom() (UUID, error) { - if !poolEnabled { - return NewRandomFromReader(rander) - } - return newRandomFromPool() + return NewRandomFromReader(rander) } // NewRandomFromReader returns a UUID based on bytes read from a given io.Reader. @@ -54,23 +49,3 @@ func NewRandomFromReader(r io.Reader) (UUID, error) { uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 return uuid, nil } - -func newRandomFromPool() (UUID, error) { - var uuid UUID - poolMu.Lock() - if poolPos == randPoolSize { - _, err := io.ReadFull(rander, pool[:]) - if err != nil { - poolMu.Unlock() - return Nil, err - } - poolPos = 0 - } - copy(uuid[:], pool[poolPos:(poolPos+16)]) - poolPos += 16 - poolMu.Unlock() - - uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 - uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 - return uuid, nil -} From 5fb00d360a3e5b4116b7f003bd705d17c068c225 Mon Sep 17 00:00:00 2001 From: Jesse Rittner Date: Sat, 31 Jul 2021 00:55:57 -0400 Subject: [PATCH 2/2] add Version4 factory to configure UUID generation Adds a Version4 factory struct, which can accumulate options for generating version 4 UUIDs. For now the only option is the io.Reader used for randomness. --- uuid.go | 13 ++++++++++ version4.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++-- version4_test.go | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 version4_test.go diff --git a/uuid.go b/uuid.go index d732ae8..f17549b 100644 --- a/uuid.go +++ b/uuid.go @@ -255,3 +255,16 @@ func SetRand(r io.Reader) { } rander = r } + +// EnableRandPool is no longer supported and is now a no-op. +// Clients should stop calling this function. +// Instead, use a Version4 with a bufio.Reader or similar as the Rand. +// +// Deprecated: This could not be used safely in general, and is now a no-op. +func EnableRandPool() { /* no-op */ } + +// DisableRandPool is no longer supported and is now a no-op. +// Clients should stop calling this function. +// +// Deprecated: This could not be used safely in general, and is now a no-op. +func DisableRandPool() { /* no-op */ } diff --git a/version4.go b/version4.go index 86160fb..1eff40d 100644 --- a/version4.go +++ b/version4.go @@ -4,12 +4,17 @@ package uuid -import "io" +import ( + "crypto/rand" + "io" +) // New creates a new random UUID or panics. New is equivalent to // the expression // // uuid.Must(uuid.NewRandom()) +// +// Deprecated: Use *Version4.NewUUID() instead. func New() UUID { return Must(NewRandom()) } @@ -18,6 +23,8 @@ func New() UUID { // NewString is equivalent to the expression // // uuid.New().String() +// +// Deprecated: Use *Version4.NewString() instead. func NewString() string { return Must(NewRandom()).String() } @@ -34,12 +41,67 @@ func NewString() string { // means the probability is about 0.00000000006 (6 × 10−11), // equivalent to the odds of creating a few tens of trillions of UUIDs in a // year and having one duplicate. +// +// Deprecated: Use *Version4.New() instead. func NewRandom() (UUID, error) { - return NewRandomFromReader(rander) + g := &Version4{Rand: rander} + return g.New() } // NewRandomFromReader returns a UUID based on bytes read from a given io.Reader. +// +// Deprecated: Use *Version4.New() instead. func NewRandomFromReader(r io.Reader) (UUID, error) { + g := &Version4{Rand: r} + return g.New() +} + +// A Version4 generates Version 4 UUIDs. +type Version4 struct { + // Rand provides the source of entropy for generation random UUIDs. + // If Rand is nil, crypto/rand.Reader will be used. + // + // The thread-safety of the Version4 is contingent upon that of the Rand. + // + // Clients wishing to sacrifice security for performance should consider + // using a bufio.Reader or similar. + Rand io.Reader +} + +// New generates a new a Version 4 UUID. +// +// The strength/randomness of the UUID is based on the strength of v4.Rand. +// +// A note about uniqueness derived from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func (v4 *Version4) New() (UUID, error) { + r := v4.Rand + if r == nil { + r = rand.Reader + } + return newV4(r) +} + +// NewUUID generates a new Version 4 UUID, or panics. +// It is equivalent to: +// uuid.Must(v4.New()) +func (v4 *Version4) NewUUID() UUID { + return Must(v4.New()) +} + +// NewString generates a new Version 4 UUID as a string, or panics. +// It is equivalent to: +// v4.NewUUID().String() +func (v4 *Version4) NewString() string { + return v4.NewUUID().String() +} + +func newV4(r io.Reader) (UUID, error) { var uuid UUID _, err := io.ReadFull(r, uuid[:]) if err != nil { diff --git a/version4_test.go b/version4_test.go new file mode 100644 index 0000000..04be7ab --- /dev/null +++ b/version4_test.go @@ -0,0 +1,44 @@ +package uuid + +import ( + "bytes" + "testing" +) + +func TestVersion4(t *testing.T) { + v4 := &Version4{} + + m := make(map[UUID]bool) + for x := 1; x < 32; x++ { + s := v4.NewUUID() + if m[s] { + t.Errorf("NewUUID returned duplicated UUID %s", s) + } + m[s] = true + uuid, err := Parse(s.String()) + if err != nil { + t.Errorf("NewUUID.String() returned %q which does not decode", s) + continue + } + if v := uuid.Version(); v != 4 { + t.Errorf("Random UUID of version %s", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("Random UUID is variant %d", uuid.Variant()) + } + } +} + +func TestVersion4Rand(t *testing.T) { + v4 := &Version4{ + Rand: bytes.NewReader([]byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + }), + } + + uuid := v4.NewString() + if uuid != "00010203-0405-4607-8809-0a0b0c0d0e0f" { + t.Errorf("NewString returned unexpected v4 UUID: %q", uuid) + } +}