diff --git a/README.md b/README.md index b6aad1c..842ccdc 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/uuid.go b/uuid.go index 295f3fc..8ede3c4 100644 --- a/uuid.go +++ b/uuid.go @@ -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. @@ -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) { @@ -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) { @@ -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) @@ -438,7 +485,6 @@ func NewV2(domain byte) UUID { u.SetVersion(2) u.SetVariant() - return u } diff --git a/uuid_test.go b/uuid_test.go index 5650480..e620e2d 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -23,6 +23,7 @@ package uuid import ( "bytes" + "net" "testing" ) @@ -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) @@ -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") @@ -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{} +}