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

add uuid v6, v7 #138

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
18 changes: 18 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,21 @@ func xtob(x1, x2 byte) (byte, bool) {
b2 := xvalues[x2]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}

// fill uuid (16byte) from poll
func fill16BytesFromPool(b []byte) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments from version4.go

poolMu.Lock()
if poolPos == randPoolSize {
_, err := io.ReadFull(rander, pool[:])
if err != nil {
poolMu.Unlock()
return err
}
poolPos = 0
}
copy(b, pool[poolPos:(poolPos+16)])
poolPos += 16
poolMu.Unlock()

return nil
}
3 changes: 3 additions & 0 deletions uuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ var tests = []test{
{"f47ac10b58cc037285670e02b2c3d479", 0, RFC4122, true},
{"f47ac10b58cc037285670e02b2c3d4790", 0, Invalid, false},
{"f47ac10b58cc037285670e02b2c3d47", 0, Invalid, false},

{"01ee82e2-0b7f-68ce-b3a5-525400475911", 6, RFC4122, true},
{"018bcd9e-4f69-712e-9eb1-dcec29d8ebc3", 7, RFC4122, true},
}

var constants = []struct {
Expand Down
27 changes: 9 additions & 18 deletions version4.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import "io"
// New creates a new random UUID or panics. New is equivalent to
// the expression
//
// uuid.Must(uuid.NewRandom())
// uuid.Must(uuid.NewRandom())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change impacts godoc, please do not change the formatting.

func New() UUID {
return Must(NewRandom())
}

// NewString creates a new random UUID and returns it as a string or panics.
// NewString is equivalent to the expression
//
// uuid.New().String()
// uuid.New().String()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change impacts godoc, please do not change the formatting.

func NewString() string {
return Must(NewRandom()).String()
}
Expand All @@ -31,11 +31,11 @@ func NewString() string {
//
// A note about uniqueness derived from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 10−11),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 10−11),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change impacts godoc, please do not change the formatting.

func NewRandom() (UUID, error) {
if !poolEnabled {
return NewRandomFromReader(rander)
Expand All @@ -57,18 +57,9 @@ func NewRandomFromReader(r io.Reader) (UUID, error) {

func newRandomFromPool() (UUID, error) {
var uuid UUID
poolMu.Lock()
if poolPos == randPoolSize {
_, err := io.ReadFull(rander, pool[:])
if err != nil {
poolMu.Unlock()
return Nil, err
}
poolPos = 0
if err := fill16BytesFromPool(uuid[:]); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To gain the same effect, you could move lines 64 and 65 up to NewRandom:

...
    if uuid, err := newRandomFromPool(); err == nil {
        uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
        uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
    }
    return uuid, err
}

But I actually do not see the need for that change (which will also add and extra comparison to the generation of random UUIDs). I don't think any change is needed here as it will not impact the v7 code.

See the comment on generating V7 UUIDs (in which case you want not changes to this file).

return Nil, err
}
copy(uuid[:], pool[poolPos:(poolPos+16)])
poolPos += 16
poolMu.Unlock()

uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
Expand Down
36 changes: 36 additions & 0 deletions version6.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2018 Google Inc. All rights reserved.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/2018/2023

// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package uuid

import "encoding/binary"

// NewV6 returns a Version 6 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewV6 returns nil. If clock sequence has not been set by
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewV6 cannot return nil, but can return Nil (16 bytes of zero). The NodeID is always set. Rather than an error it simply returns a random set of bits as the NodeID.

// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewV6 returns nil and an error.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a reference to the standard that defines Version 6 UUIDs.

func NewV6() (UUID, error) {
var uuid UUID
now, seq, err := GetTime()
if err != nil {
return uuid, err
}

binary.BigEndian.PutUint64(uuid[0:], uint64(now))
binary.BigEndian.PutUint16(uuid[8:], seq)

uuid[6] = 0x60 | (uuid[6] & 0x0F)
uuid[8] = 0x80 | (uuid[8] & 0x3F)

nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
copy(uuid[10:], nodeID[:])
nodeMu.Unlock()

return uuid, nil
}
70 changes: 70 additions & 0 deletions version7.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2018 Google Inc. All rights reserved.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/2018/2023

// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package uuid

import (
"encoding/binary"
"io"
)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the following functions can replace these functions:

func NewV7() (UUID, error) {
        uuid, err := NewRandom()
        if err != nil {
                return uuid, err
        }
        makeV7(uuid[:])
        return uuid, nil
}

func NewV7FromReader(r *io.Reader) (UUID, error) {
        uuid, err := NewRandomFromReader(r)
        if err != nil {
                return uuid, err
        }
        makeV7(uuid[:])
        return uuid, nil
}

func makeV7(uuid []byte) {
        t := timeNow().UnixMilli()
        uuid[0] = (t >> 40)
        uuid[1] = (t >> 32)  
        uuid[2] = (t >> 24)
        uuid[3] = (t >> 16) 
        uuid[4] = (t >> 8) 
        uuid[5] = t 
        uuid[6] = 0x70 | (uuid[6] & 0x0F)
}

They need comments, of course. Putting in the bytes directly rather than using binary.BigEndian.PutUint64 is quite a bit more efficient as you only put in 6 bytes, rather than 8, and PutUint64 fills in the bytes in the same way this code does.

Note that uuid[8] already has the right version number in it.

func unixepoch() (uint64, error) {
now, _, err := GetTime()
if err != nil {
return 0, err
}

sec, nsec := now.UnixTime()
return uint64(sec*1000 + nsec/1000000), nil
}

func NewV7FromReader(r io.Reader) (UUID, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must have a godoc comment and also needs to reference the standard that defines version 7 UUIDs.

var uuid UUID

t, err := unixepoch()
if err != nil {
return uuid, err
}

binary.BigEndian.PutUint64(uuid[0:], t<<16)
_, err = io.ReadFull(r, uuid[6:])
if err != nil {
return Nil, err
}

uuid[6] = 0x70 | (uuid[6] & 0x0F)
uuid[8] = 0x80 | (uuid[8] & 0x3F)

return uuid, nil
}

func newV7FromPool() (UUID, error) {
var uuid UUID

t, err := unixepoch()
if err != nil {
return uuid, err
}

if err = fill16BytesFromPool(uuid[:]); err != nil {
return Nil, err
}

binary.BigEndian.PutUint32(uuid[0:], uint32(t>>16))
binary.BigEndian.PutUint16(uuid[4:], uint16(t))

uuid[6] = 0x70 | (uuid[6] & 0x0F)
uuid[8] = 0x80 | (uuid[8] & 0x3F)

return uuid, nil
}

// NewV7 returns a Version 7 UUID based on the current time.
// If GetTime fails to return the current NewV7 returns nil and an error.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It returns Nil, not nil, but only when it is unable to generate random data (see above while there will be no error in getting the current time).

func NewV7() (UUID, error) {
if !poolEnabled {
return NewV7FromReader(rander)
}
return newV7FromPool()
}