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

Adding methods to allow to get UUIDs with random MAC address #63

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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{}
}