Return enthropy errors from UUID generation.

This commit changes signature for `NewV1`, `NewV2` and `NewV4` functions
which from now will return `(UUID, error)` instead of `UUID`.
To emulate old behavior of panicking on enthropy errors one can wrap
a call into `Must` helper similar to:
```
u := uuid.Must(uuid.NewV4())
```

Closes #18.
This commit is contained in:
Maxim Bublis 2018-01-03 16:02:28 +00:00
parent f58768cc1a
commit 0ef6afb2f6
4 changed files with 194 additions and 128 deletions

View File

@ -37,13 +37,22 @@ import (
func main() {
// Creating UUID Version 4
u1 := uuid.NewV4()
// panic on error
u1 := uuid.Must(uuid.NewV4())
fmt.Printf("UUIDv4: %s\n", u1)
// or error handling
u2, err := uuid.NewV4()
if err != nil {
fmt.Printf("Something went wrong: %s", err)
return
}
fmt.Printf("UUIDv4: %s\n", u2)
// Parsing UUID from string input
u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
if err != nil {
fmt.Printf("Something gone wrong: %s", err)
fmt.Printf("Something went wrong: %s", err)
}
fmt.Printf("Successfully parsed: %s", u2)
}

View File

@ -61,7 +61,8 @@ func (s *codecTestSuite) TestMarshalBinary(c *C) {
}
func (s *codecTestSuite) BenchmarkMarshalBinary(c *C) {
u := NewV4()
u, err := NewV4()
c.Assert(err, IsNil)
for i := 0; i < c.N; i++ {
u.MarshalBinary()
}
@ -209,7 +210,8 @@ func (s *codecTestSuite) TestMarshalText(c *C) {
}
func (s *codecTestSuite) BenchmarkMarshalText(c *C) {
u := NewV4()
u, err := NewV4()
c.Assert(err, IsNil)
for i := 0; i < c.N; i++ {
u.MarshalText()
}
@ -241,7 +243,8 @@ func (s *codecTestSuite) BenchmarkUnmarshalText(c *C) {
var sink string
func (s *codecTestSuite) BenchmarkMarshalToString(c *C) {
u := NewV4()
u, err := NewV4()
c.Assert(err, IsNil)
for i := 0; i < c.N; i++ {
sink = u.String()
}

View File

@ -27,6 +27,7 @@ import (
"crypto/sha1"
"encoding/binary"
"hash"
"io"
"net"
"os"
"sync"
@ -37,21 +38,22 @@ import (
// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970).
const epochStart = 122192928000000000
var (
global = newDefaultGenerator()
type epochFunc func() time.Time
epochFunc = unixTimeFunc
posixUID = uint32(os.Getuid())
posixGID = uint32(os.Getgid())
var (
global = newRFC4122Generator()
posixUID = uint32(os.Getuid())
posixGID = uint32(os.Getgid())
)
// NewV1 returns UUID based on current timestamp and MAC address.
func NewV1() UUID {
func NewV1() (UUID, error) {
return global.NewV1()
}
// NewV2 returns DCE Security UUID based on POSIX UID/GID.
func NewV2(domain byte) UUID {
func NewV2(domain byte) (UUID, error) {
return global.NewV2(domain)
}
@ -61,7 +63,7 @@ func NewV3(ns UUID, name string) UUID {
}
// NewV4 returns random generated UUID.
func NewV4() UUID {
func NewV4() (UUID, error) {
return global.NewV4()
}
@ -72,74 +74,83 @@ func NewV5(ns UUID, name string) UUID {
// Generator provides interface for generating UUIDs.
type Generator interface {
NewV1() UUID
NewV2(domain byte) UUID
NewV1() (UUID, error)
NewV2(domain byte) (UUID, error)
NewV3(ns UUID, name string) UUID
NewV4() UUID
NewV4() (UUID, error)
NewV5(ns UUID, name string) UUID
}
// Default generator implementation.
type generator struct {
storageOnce sync.Once
storageMutex sync.Mutex
type rfc4122Generator struct {
clockSequenceOnce sync.Once
hardwareAddrOnce sync.Once
storageMutex sync.Mutex
rand io.Reader
epochFunc epochFunc
lastTime uint64
clockSequence uint16
hardwareAddr [6]byte
}
func newDefaultGenerator() Generator {
return &generator{}
func newRFC4122Generator() Generator {
return &rfc4122Generator{
epochFunc: time.Now,
rand: rand.Reader,
}
}
// NewV1 returns UUID based on current timestamp and MAC address.
func (g *generator) NewV1() UUID {
func (g *rfc4122Generator) NewV1() (UUID, error) {
u := UUID{}
timeNow, clockSeq, hardwareAddr := g.getStorage()
timeNow, clockSeq, err := g.getClockSequence()
if err != nil {
return Nil, err
}
binary.BigEndian.PutUint32(u[0:], uint32(timeNow))
binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
binary.BigEndian.PutUint16(u[8:], clockSeq)
hardwareAddr, err := g.getHardwareAddr()
if err != nil {
return Nil, err
}
copy(u[10:], hardwareAddr)
u.SetVersion(V1)
u.SetVariant(VariantRFC4122)
return u
return u, nil
}
// NewV2 returns DCE Security UUID based on POSIX UID/GID.
func (g *generator) NewV2(domain byte) UUID {
u := UUID{}
timeNow, clockSeq, hardwareAddr := g.getStorage()
func (g *rfc4122Generator) NewV2(domain byte) (UUID, error) {
u, err := NewV1()
if err != nil {
return Nil, err
}
switch domain {
case DomainPerson:
binary.BigEndian.PutUint32(u[0:], posixUID)
binary.BigEndian.PutUint32(u[:], posixUID)
case DomainGroup:
binary.BigEndian.PutUint32(u[0:], posixGID)
binary.BigEndian.PutUint32(u[:], posixGID)
}
binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
binary.BigEndian.PutUint16(u[8:], clockSeq)
u[9] = domain
copy(u[10:], hardwareAddr)
u.SetVersion(V2)
u.SetVariant(VariantRFC4122)
return u
return u, nil
}
// NewV3 returns UUID based on MD5 hash of namespace UUID and name.
func (g *generator) NewV3(ns UUID, name string) UUID {
func (g *rfc4122Generator) NewV3(ns UUID, name string) UUID {
u := newFromHash(md5.New(), ns, name)
u.SetVersion(V3)
u.SetVariant(VariantRFC4122)
@ -148,17 +159,19 @@ func (g *generator) NewV3(ns UUID, name string) UUID {
}
// NewV4 returns random generated UUID.
func (g *generator) NewV4() UUID {
func (g *rfc4122Generator) NewV4() (UUID, error) {
u := UUID{}
g.safeRandom(u[:])
if _, err := g.rand.Read(u[:]); err != nil {
return Nil, err
}
u.SetVersion(V4)
u.SetVariant(VariantRFC4122)
return u
return u, nil
}
// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
func (g *generator) NewV5(ns UUID, name string) UUID {
func (g *rfc4122Generator) NewV5(ns UUID, name string) UUID {
u := newFromHash(sha1.New(), ns, name)
u.SetVersion(V5)
u.SetVariant(VariantRFC4122)
@ -166,66 +179,66 @@ func (g *generator) NewV5(ns UUID, name string) UUID {
return u
}
func (g *generator) initStorage() {
g.initClockSequence()
g.initHardwareAddr()
}
func (g *generator) initClockSequence() {
buf := make([]byte, 2)
g.safeRandom(buf)
g.clockSequence = binary.BigEndian.Uint16(buf)
}
func (g *generator) initHardwareAddr() {
interfaces, err := net.Interfaces()
if err == nil {
for _, iface := range interfaces {
if len(iface.HardwareAddr) >= 6 {
copy(g.hardwareAddr[:], iface.HardwareAddr)
return
}
// Returns epoch and clock sequence.
func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) {
var err error
g.clockSequenceOnce.Do(func() {
buf := make([]byte, 2)
if _, err = g.rand.Read(buf); err != nil {
return
}
g.clockSequence = binary.BigEndian.Uint16(buf)
})
if err != nil {
return 0, 0, err
}
// Initialize hardwareAddr randomly in case
// of real network interfaces absence
g.safeRandom(g.hardwareAddr[:])
// Set multicast bit as recommended in RFC 4122
g.hardwareAddr[0] |= 0x01
}
func (g *generator) safeRandom(dest []byte) {
if _, err := rand.Read(dest); err != nil {
panic(err)
}
}
// Returns UUID v1/v2 storage state.
// Returns epoch timestamp, clock sequence, and hardware address.
func (g *generator) getStorage() (uint64, uint16, []byte) {
g.storageOnce.Do(g.initStorage)
g.storageMutex.Lock()
defer g.storageMutex.Unlock()
timeNow := epochFunc()
// Clock changed backwards since last UUID generation.
timeNow := g.getEpoch()
// Clock didn't change since last UUID generation.
// Should increase clock sequence.
if timeNow <= g.lastTime {
g.clockSequence++
}
g.lastTime = timeNow
return timeNow, g.clockSequence, g.hardwareAddr[:]
return timeNow, g.clockSequence, nil
}
// Returns hardware address.
func (g *rfc4122Generator) getHardwareAddr() ([]byte, error) {
var err error
g.hardwareAddrOnce.Do(func() {
interfaces, err := net.Interfaces()
if err == nil {
for _, iface := range interfaces {
if len(iface.HardwareAddr) >= 6 {
copy(g.hardwareAddr[:], iface.HardwareAddr)
return
}
}
}
// Initialize hardwareAddr randomly in case
// of real network interfaces absence.
if _, err = g.rand.Read(g.hardwareAddr[:]); err != nil {
return
}
// Set multicast bit as recommended by RFC 4122
g.hardwareAddr[0] |= 0x01
})
if err != nil {
return []byte{}, err
}
return g.hardwareAddr[:], nil
}
// Returns difference in 100-nanosecond intervals between
// UUID epoch (October 15, 1582) and current time.
// This is default epoch calculation function.
func unixTimeFunc() uint64 {
return epochStart + uint64(time.Now().UnixNano()/100)
func (g *rfc4122Generator) getEpoch() uint64 {
return epochStart + uint64(g.epochFunc().UnixNano()/100)
}
// Returns UUID based on hashing of namespace UUID and name.

View File

@ -22,30 +22,56 @@
package uuid
import (
"crypto/rand"
"fmt"
"time"
. "gopkg.in/check.v1"
)
type faultyReader struct{}
func (f *faultyReader) Read(dest []byte) (int, error) {
return 0, fmt.Errorf("io: reader is faulty")
}
type genTestSuite struct{}
var _ = Suite(&genTestSuite{})
func (s *genTestSuite) TestNewV1(c *C) {
u := NewV1()
c.Assert(u.Version(), Equals, V1)
c.Assert(u.Variant(), Equals, VariantRFC4122)
u1, err := NewV1()
c.Assert(err, IsNil)
c.Assert(u1.Version(), Equals, V1)
c.Assert(u1.Variant(), Equals, VariantRFC4122)
u1 := NewV1()
u2 := NewV1()
u2, err := NewV1()
c.Assert(err, IsNil)
c.Assert(u1, Not(Equals), u2)
}
oldFunc := epochFunc
epochFunc = func() uint64 { return 0 }
func (s *genTestSuite) TestNewV1EpochStale(c *C) {
g := &rfc4122Generator{
epochFunc: func() time.Time {
return time.Unix(0, 0)
},
rand: rand.Reader,
}
u1, err := g.NewV1()
c.Assert(err, IsNil)
u2, err := g.NewV1()
c.Assert(err, IsNil)
c.Assert(u1, Not(Equals), u2)
}
u3 := NewV1()
u4 := NewV1()
c.Assert(u3, Not(Equals), u4)
epochFunc = oldFunc
func (s *genTestSuite) TestNewV1FaultyRand(c *C) {
g := &rfc4122Generator{
epochFunc: time.Now,
rand: &faultyReader{},
}
u1, err := g.NewV1()
c.Assert(err, NotNil)
c.Assert(u1, Equals, Nil)
}
func (s *genTestSuite) BenchmarkNewV1(c *C) {
@ -55,13 +81,20 @@ func (s *genTestSuite) BenchmarkNewV1(c *C) {
}
func (s *genTestSuite) TestNewV2(c *C) {
u1 := NewV2(DomainPerson)
u1, err := NewV2(DomainPerson)
c.Assert(err, IsNil)
c.Assert(u1.Version(), Equals, V2)
c.Assert(u1.Variant(), Equals, VariantRFC4122)
u2 := NewV2(DomainGroup)
u2, err := NewV2(DomainGroup)
c.Assert(err, IsNil)
c.Assert(u2.Version(), Equals, V2)
c.Assert(u2.Variant(), Equals, VariantRFC4122)
u3, err := NewV2(DomainOrg)
c.Assert(err, IsNil)
c.Assert(u3.Version(), Equals, V2)
c.Assert(u3.Variant(), Equals, VariantRFC4122)
}
func (s *genTestSuite) BenchmarkNewV2(c *C) {
@ -71,23 +104,19 @@ func (s *genTestSuite) BenchmarkNewV2(c *C) {
}
func (s *genTestSuite) TestNewV3(c *C) {
u := NewV3(NamespaceDNS, "www.example.com")
c.Assert(u.Version(), Equals, V3)
c.Assert(u.Variant(), Equals, VariantRFC4122)
c.Assert(u.String(), Equals, "5df41881-3aed-3515-88a7-2f4a814cf09e")
u1 := NewV3(NamespaceDNS, "www.example.com")
c.Assert(u1.Version(), Equals, V3)
c.Assert(u1.Variant(), Equals, VariantRFC4122)
c.Assert(u1.String(), Equals, "5df41881-3aed-3515-88a7-2f4a814cf09e")
u = NewV3(NamespaceDNS, "python.org")
c.Assert(u.String(), Equals, "6fa459ea-ee8a-3ca4-894e-db77e160355e")
u1 := NewV3(NamespaceDNS, "golang.org")
u2 := NewV3(NamespaceDNS, "golang.org")
c.Assert(u1, Equals, u2)
u2 := NewV3(NamespaceDNS, "example.com")
c.Assert(u2, Not(Equals), u1)
u3 := NewV3(NamespaceDNS, "example.com")
c.Assert(u1, Not(Equals), u3)
c.Assert(u3, Equals, u2)
u4 := NewV3(NamespaceURL, "golang.org")
c.Assert(u1, Not(Equals), u4)
u4 := NewV3(NamespaceURL, "example.com")
c.Assert(u4, Not(Equals), u3)
}
func (s *genTestSuite) BenchmarkNewV3(c *C) {
@ -97,9 +126,24 @@ func (s *genTestSuite) BenchmarkNewV3(c *C) {
}
func (s *genTestSuite) TestNewV4(c *C) {
u := NewV4()
c.Assert(u.Version(), Equals, V4)
c.Assert(u.Variant(), Equals, VariantRFC4122)
u1, err := NewV4()
c.Assert(err, IsNil)
c.Assert(u1.Version(), Equals, V4)
c.Assert(u1.Variant(), Equals, VariantRFC4122)
u2, err := NewV4()
c.Assert(err, IsNil)
c.Assert(u1, Not(Equals), u2)
}
func (s *genTestSuite) TestNewV4FaultyRand(c *C) {
g := &rfc4122Generator{
epochFunc: time.Now,
rand: &faultyReader{},
}
u1, err := g.NewV4()
c.Assert(err, NotNil)
c.Assert(u1, Equals, Nil)
}
func (s *genTestSuite) BenchmarkNewV4(c *C) {
@ -109,22 +153,19 @@ func (s *genTestSuite) BenchmarkNewV4(c *C) {
}
func (s *genTestSuite) TestNewV5(c *C) {
u := NewV5(NamespaceDNS, "www.example.com")
c.Assert(u.Version(), Equals, V5)
c.Assert(u.Variant(), Equals, VariantRFC4122)
u1 := NewV5(NamespaceDNS, "www.example.com")
c.Assert(u1.Version(), Equals, V5)
c.Assert(u1.Variant(), Equals, VariantRFC4122)
c.Assert(u1.String(), Equals, "2ed6657d-e927-568b-95e1-2665a8aea6a2")
u = NewV5(NamespaceDNS, "python.org")
c.Assert(u.String(), Equals, "886313e1-3b8a-5372-9b90-0c9aee199e5d")
u1 := NewV5(NamespaceDNS, "golang.org")
u2 := NewV5(NamespaceDNS, "golang.org")
c.Assert(u1, Equals, u2)
u2 := NewV5(NamespaceDNS, "example.com")
c.Assert(u2, Not(Equals), u1)
u3 := NewV5(NamespaceDNS, "example.com")
c.Assert(u1, Not(Equals), u3)
c.Assert(u3, Equals, u2)
u4 := NewV5(NamespaceURL, "golang.org")
c.Assert(u1, Not(Equals), u4)
u4 := NewV5(NamespaceURL, "example.com")
c.Assert(u4, Not(Equals), u3)
}
func (s *genTestSuite) BenchmarkNewV5(c *C) {