Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Version4 factory to configure UUID generation #88

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 10 additions & 34 deletions uuid.go
Expand Up @@ -12,7 +12,6 @@ import (
"fmt"
"io"
"strings"
"sync"
)

// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
Expand All @@ -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 }

Expand Down Expand Up @@ -265,30 +256,15 @@ 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.
// 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.
//
// 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
}
// Deprecated: This could not be used safely in general, and is now a no-op.
func EnableRandPool() { /* no-op */ }

// DisableRandPool disables the randomness pool if it was previously
// enabled with EnableRandPool.
// DisableRandPool is no longer supported and is now a no-op.
// Clients should stop calling this function.
//
// 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
}
// Deprecated: This could not be used safely in general, and is now a no-op.
func DisableRandPool() { /* no-op */ }
59 changes: 0 additions & 59 deletions uuid_test.go
Expand Up @@ -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++ {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}
})
}
91 changes: 64 additions & 27 deletions version4.go
Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I especially object to this and the deprecation of NewRandom etc. - if the library is going to provide ctor sugar, it should at least provide the most common one as the shortest!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, "deprecation" ultimately means little in the face of semantic versioning, unless and until v2 of this library rolls around. I can alter the comment to instead suggest considering using *Version4.NewUUID() if that would make you feel better.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not deprecated. New and NewString are the two most common ways to create a UUID. Version 4 is by far the most common type of UUID due to the issues associated with Version 1 (which are documented in the RFC).

func New() UUID {
return Must(NewRandom())
}
Expand All @@ -18,6 +23,8 @@ func New() UUID {
// NewString is equivalent to the expression
//
// uuid.New().String()
//
// Deprecated: Use *Version4.NewString() instead.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not deprecated. New and NewString are the two most common ways to create a UUID. Version 4 is by far the most common type of UUID due to the issues associated with Version 1 (which are documented in the RFC).

func NewString() string {
return Must(NewRandom()).String()
}
Expand All @@ -27,49 +34,79 @@ 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
// 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.
//
// Deprecated: Use *Version4.New() instead.
func NewRandom() (UUID, error) {
if !poolEnabled {
return NewRandomFromReader(rander)
}
return newRandomFromPool()
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) {
var uuid UUID
_, err := io.ReadFull(r, uuid[:])
if err != nil {
return Nil, err
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
}
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
return newV4(r)
}

func newRandomFromPool() (UUID, error) {
// 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
poolMu.Lock()
if poolPos == randPoolSize {
_, err := io.ReadFull(rander, pool[:])
if err != nil {
poolMu.Unlock()
return Nil, err
}
poolPos = 0
_, err := io.ReadFull(r, uuid[:])
if err != nil {
return Nil, err
}
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
Expand Down
44 changes: 44 additions & 0 deletions 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)
}
}