Skip to content

Commit

Permalink
adding two methods NewV1RandomMAC() and NewV2RandomMAC() with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alessandrozucca committed Oct 26, 2017
1 parent 5bf94b6 commit d80f1d2
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 19 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ With 100% test coverage and benchmarks out of box.

Supported versions:
* Version 1, based on timestamp and MAC address (RFC 4122)
* Version 1, based on timestamp and a random MAC address (RFC 4122)
* Version 2, based on timestamp, MAC address and POSIX UID/GID (DCE 1.1)
* Version 2, based on timestamp, random MAC address and POSIX UID/GID (DCE 1.1)
* Version 3, based on MD5 hashing (RFC 4122)
* Version 4, based on random numbers (RFC 4122)
* Version 5, based on SHA-1 hashing (RFC 4122)
Expand Down
84 changes: 65 additions & 19 deletions uuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,16 @@ const dash byte = '-'

// UUID v1/v2 storage.
var (
storageMutex sync.Mutex
storageOnce sync.Once
epochFunc = unixTimeFunc
clockSequence uint16
lastTime uint64
hardwareAddr [6]byte
posixUID = uint32(os.Getuid())
posixGID = uint32(os.Getgid())
storageMutex sync.Mutex
storageOnce sync.Once
storageRandomMACAddrMutex sync.Mutex
storageRandomMACAddrOnce sync.Once
epochFunc = unixTimeFunc
clockSequence uint16
lastTime uint64
hardwareAddr [6]byte
posixUID = uint32(os.Getuid())
posixGID = uint32(os.Getgid())
)

// String parse helpers.
Expand Down Expand Up @@ -105,9 +107,12 @@ func initHardwareAddr() {
hardwareAddr[0] |= 0x01
}

func initStorage() {
initClockSequence()
initHardwareAddr()
func initHardwareRandomAddr() {
// Initialize hardwareAddr randomly
safeRandom(hardwareAddr[:])

// Set multicast bit as recommended in RFC 4122
hardwareAddr[0] |= 0x01
}

func safeRandom(dest []byte) {
Expand Down Expand Up @@ -378,6 +383,16 @@ func FromStringOrNil(input string) UUID {
return uuid
}

func initStorage() {
initClockSequence()
initHardwareAddr()
}

func initStorageRandomAddr() {
initClockSequence()
initHardwareRandomAddr()
}

// Returns UUID v1/v2 storage state.
// Returns epoch timestamp, clock sequence, and hardware address.
func getStorage() (uint64, uint16, []byte) {
Expand All @@ -397,31 +412,63 @@ func getStorage() (uint64, uint16, []byte) {
return timeNow, clockSequence, hardwareAddr[:]
}

// Returns UUID v1/v2 storage state.
// Returns epoch timestamp, clock sequence, and a random hardware address.
func getStorageRandomAddr() (uint64, uint16, []byte) {
storageRandomMACAddrOnce.Do(initStorageRandomAddr)

storageRandomMACAddrMutex.Lock()
defer storageRandomMACAddrMutex.Unlock()

timeNow := epochFunc()
// Clock changed backwards since last UUID generation.
// Should increase clock sequence.
if timeNow <= lastTime {
clockSequence++
}
lastTime = timeNow

return timeNow, clockSequence, hardwareAddr[:]
}

// NewV1 returns UUID based on current timestamp and MAC address.
func NewV1() UUID {
u := UUID{}

timeNow, clockSeq, hardwareAddr := getStorage()
return buildV1(timeNow, clockSeq, hardwareAddr)
}

// NewV1RandomMAC returns UUID based on current timestamp and a random MAC address.
func NewV1RandomMAC() UUID {
timeNow, clockSeq, hardwareAddr := getStorageRandomAddr()
return buildV1(timeNow, clockSeq, hardwareAddr)
}

func buildV1(timeNow uint64, clockSeq uint16, hardwareAddr []byte) UUID {
u := UUID{}
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)

copy(u[10:], hardwareAddr)

u.SetVersion(1)
u.SetVariant()

return u
}

// NewV2 returns DCE Security UUID based on POSIX UID/GID.
func NewV2(domain byte) UUID {
u := UUID{}

timeNow, clockSeq, hardwareAddr := getStorage()
return buildV2(domain, timeNow, clockSeq, hardwareAddr)
}

// NewV2RandomMAC returns DCE Security UUID based on POSIX UID/GID using a random MAC
func NewV2RandomMAC(domain byte) UUID {
timeNow, clockSeq, hardwareAddr := getStorageRandomAddr()
return buildV2(domain, timeNow, clockSeq, hardwareAddr)
}

func buildV2(domain byte, timeNow uint64, clockSeq uint16, hardwareAddr []byte) UUID {
u := UUID{}
switch domain {
case DomainPerson:
binary.BigEndian.PutUint32(u[0:], posixUID)
Expand All @@ -438,7 +485,6 @@ func NewV2(domain byte) UUID {

u.SetVersion(2)
u.SetVariant()

return u
}

Expand Down
82 changes: 82 additions & 0 deletions uuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ package uuid

import (
"bytes"
"net"
"testing"
)

Expand Down Expand Up @@ -526,6 +527,45 @@ func TestNewV1(t *testing.T) {
epochFunc = oldFunc
}

func TestNewV1RandomMAC(t *testing.T) {
u := NewV1RandomMAC()

if u.Version() != 1 {
t.Errorf("UUIDv1 generated with incorrect version: %d", u.Version())
}

if u.Variant() != VariantRFC4122 {
t.Errorf("UUIDv1 generated with incorrect variant: %d", u.Variant())
}

u1 := NewV1RandomMAC()
u2 := NewV1RandomMAC()

if Equal(u1, u2) {
t.Errorf("UUIDv1 generated two equal UUIDs: %s and %s", u1, u2)
}

oldFunc := epochFunc
epochFunc = func() uint64 { return 0 }

u3 := NewV1RandomMAC()
u4 := NewV1RandomMAC()

if Equal(u3, u4) {
t.Errorf("UUIDv1 generated two equal UUIDs: %s and %s", u3, u4)
}

epochFunc = oldFunc

u5 := NewV1RandomMAC().Bytes()
u5MACAddr := u5[10:]
firstNetworkCardAddress := getNetworkCardAddress()

if bytes.Equal(u5MACAddr, firstNetworkCardAddress) {
t.Errorf("UUIDv1 generated two equal MAC addresses when should be random: %v and %v", u5MACAddr, firstNetworkCardAddress)
}
}

func TestNewV2(t *testing.T) {
u1 := NewV2(DomainPerson)

Expand All @@ -548,6 +588,36 @@ func TestNewV2(t *testing.T) {
}
}

func TestNewV2RandomMAC(t *testing.T) {
u1 := NewV2RandomMAC(DomainPerson)

if u1.Version() != 2 {
t.Errorf("UUIDv2 generated with incorrect version: %d", u1.Version())
}

if u1.Variant() != VariantRFC4122 {
t.Errorf("UUIDv2 generated with incorrect variant: %d", u1.Variant())
}

u2 := NewV2RandomMAC(DomainGroup)

if u2.Version() != 2 {
t.Errorf("UUIDv2 generated with incorrect version: %d", u2.Version())
}

if u2.Variant() != VariantRFC4122 {
t.Errorf("UUIDv2 generated with incorrect variant: %d", u2.Variant())
}

u3 := NewV2RandomMAC(DomainPerson).Bytes()
u3MACAddr := u3[10:]
firstNetworkCardAddress := getNetworkCardAddress()

if bytes.Equal(u3MACAddr, firstNetworkCardAddress) {
t.Errorf("UUIDv2 generated two equal MAC addresses when should be random: %v and %v", u3MACAddr, firstNetworkCardAddress)
}
}

func TestNewV3(t *testing.T) {
u := NewV3(NamespaceDNS, "www.example.com")

Expand Down Expand Up @@ -631,3 +701,15 @@ func TestNewV5(t *testing.T) {
t.Errorf("UUIDv3 generated same UUIDs for sane names in different namespaces: %s and %s", u1, u4)
}
}

func getNetworkCardAddress() []byte {
interfaces, err := net.Interfaces()
if err == nil {
for _, iface := range interfaces {
if len(iface.HardwareAddr) >= 6 {
return iface.HardwareAddr
}
}
}
return []byte{}
}

0 comments on commit d80f1d2

Please sign in to comment.