Skip to content

Commit

Permalink
Return enthropy errors from UUID generation.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
satori committed Jan 3, 2018
1 parent f58768c commit 0ef6afb
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 127 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
9 changes: 6 additions & 3 deletions codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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()
}
Expand Down
173 changes: 93 additions & 80 deletions generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"crypto/sha1"
"encoding/binary"
"hash"
"io"
"net"
"os"
"sync"
Expand All @@ -37,21 +38,22 @@ import (
// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970).
const epochStart = 122192928000000000

type epochFunc func() time.Time

var (
global = newDefaultGenerator()
global = newRFC4122Generator()

epochFunc = unixTimeFunc
posixUID = uint32(os.Getuid())
posixGID = uint32(os.Getgid())
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)
}

Expand All @@ -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()
}

Expand All @@ -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)
Expand All @@ -148,84 +159,86 @@ 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)

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.
Expand Down
Loading

0 comments on commit 0ef6afb

Please sign in to comment.