Skip to content

Commit b35aa6a

Browse files
authored
add uuid version 6 and 7 (#139)
* add uuid version 6 and 7 * fix comment
1 parent 8de8764 commit b35aa6a

File tree

4 files changed

+261
-5
lines changed

4 files changed

+261
-5
lines changed

Diff for: time.go

+16-5
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,23 @@ func setClockSequence(seq int) {
108108
}
109109

110110
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
111-
// uuid. The time is only defined for version 1 and 2 UUIDs.
111+
// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs.
112112
func (uuid UUID) Time() Time {
113-
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
114-
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
115-
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
116-
return Time(time)
113+
var t Time
114+
switch uuid.Version() {
115+
case 6:
116+
time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110
117+
t = Time(time)
118+
case 7:
119+
time := binary.BigEndian.Uint64(uuid[:8])
120+
t = Time((time>>16)*10000 + g1582ns100)
121+
default: // forward compatible
122+
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
123+
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
124+
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
125+
t = Time(time)
126+
}
127+
return t
117128
}
118129

119130
// ClockSequence returns the clock sequence encoded in uuid.

Diff for: uuid_test.go

+114
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ var tests = []test{
7373
{"f47ac10b58cc037285670e02b2c3d479", 0, RFC4122, true},
7474
{"f47ac10b58cc037285670e02b2c3d4790", 0, Invalid, false},
7575
{"f47ac10b58cc037285670e02b2c3d47", 0, Invalid, false},
76+
77+
{"01ee836c-e7c9-619d-929a-525400475911", 6, RFC4122, true},
78+
{"018bd12c-58b0-7683-8a5b-8752d0e86651", 7, RFC4122, true},
7679
}
7780

7881
var constants = []struct {
@@ -748,3 +751,114 @@ func BenchmarkUUIDs_Strings(b *testing.B) {
748751
uuids.Strings()
749752
}
750753
}
754+
755+
func TestVersion6(t *testing.T) {
756+
uuid1, err := NewV6()
757+
if err != nil {
758+
t.Fatalf("could not create UUID: %v", err)
759+
}
760+
uuid2, err := NewV6()
761+
if err != nil {
762+
t.Fatalf("could not create UUID: %v", err)
763+
}
764+
765+
if uuid1 == uuid2 {
766+
t.Errorf("%s:duplicate uuid", uuid1)
767+
}
768+
if v := uuid1.Version(); v != 6 {
769+
t.Errorf("%s: version %s expected 6", uuid1, v)
770+
}
771+
if v := uuid2.Version(); v != 6 {
772+
t.Errorf("%s: version %s expected 6", uuid2, v)
773+
}
774+
n1 := uuid1.NodeID()
775+
n2 := uuid2.NodeID()
776+
if !bytes.Equal(n1, n2) {
777+
t.Errorf("Different nodes %x != %x", n1, n2)
778+
}
779+
t1 := uuid1.Time()
780+
t2 := uuid2.Time()
781+
q1 := uuid1.ClockSequence()
782+
q2 := uuid2.ClockSequence()
783+
784+
switch {
785+
case t1 == t2 && q1 == q2:
786+
t.Error("time stopped")
787+
case t1 > t2 && q1 == q2:
788+
t.Error("time reversed")
789+
case t1 < t2 && q1 != q2:
790+
t.Error("clock sequence changed unexpectedly")
791+
}
792+
}
793+
794+
// uuid v7 time is only unix milliseconds, so
795+
// uuid1.Time() == uuid2.Time() is right, but uuid1 must != uuid2
796+
func TestVersion7(t *testing.T) {
797+
SetRand(nil)
798+
m := make(map[string]bool)
799+
for x := 1; x < 32; x++ {
800+
uuid, err := NewV7()
801+
if err != nil {
802+
t.Fatalf("could not create UUID: %v", err)
803+
}
804+
s := uuid.String()
805+
if m[s] {
806+
t.Errorf("NewV7 returned duplicated UUID %s", s)
807+
}
808+
m[s] = true
809+
if v := uuid.Version(); v != 7 {
810+
t.Errorf("UUID of version %s", v)
811+
}
812+
if uuid.Variant() != RFC4122 {
813+
t.Errorf("UUID is variant %d", uuid.Variant())
814+
}
815+
}
816+
}
817+
818+
// uuid v7 time is only unix milliseconds, so
819+
// uuid1.Time() == uuid2.Time() is right, but uuid1 must != uuid2
820+
func TestVersion7_pooled(t *testing.T) {
821+
SetRand(nil)
822+
EnableRandPool()
823+
defer DisableRandPool()
824+
825+
m := make(map[string]bool)
826+
for x := 1; x < 128; x++ {
827+
uuid, err := NewV7()
828+
if err != nil {
829+
t.Fatalf("could not create UUID: %v", err)
830+
}
831+
s := uuid.String()
832+
if m[s] {
833+
t.Errorf("NewV7 returned duplicated UUID %s", s)
834+
}
835+
m[s] = true
836+
if v := uuid.Version(); v != 7 {
837+
t.Errorf("UUID of version %s", v)
838+
}
839+
if uuid.Variant() != RFC4122 {
840+
t.Errorf("UUID is variant %d", uuid.Variant())
841+
}
842+
}
843+
}
844+
845+
func TestVersion7FromReader(t *testing.T) {
846+
myString := "8059ddhdle77cb52"
847+
r := bytes.NewReader([]byte(myString))
848+
r2 := bytes.NewReader([]byte(myString))
849+
uuid1, err := NewV7FromReader(r)
850+
if err != nil {
851+
t.Errorf("failed generating UUID from a reader")
852+
}
853+
_, err = NewV7FromReader(r)
854+
if err == nil {
855+
t.Errorf("expecting an error as reader has no more bytes. Got uuid. NewV7FromReader may not be using the provided reader")
856+
}
857+
uuid3, err := NewV7FromReader(r2)
858+
if err != nil {
859+
t.Errorf("failed generating UUID from a reader")
860+
}
861+
if uuid1 != uuid3 {
862+
t.Errorf("expected duplicates, got %q and %q", uuid1, uuid3)
863+
}
864+
}

Diff for: version6.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2023 Google Inc. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package uuid
6+
7+
import "encoding/binary"
8+
9+
// UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality.
10+
// It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs.
11+
// Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
12+
//
13+
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#uuidv6
14+
//
15+
// NewV6 returns a Version 6 UUID based on the current NodeID and clock
16+
// sequence, and the current time. If the NodeID has not been set by SetNodeID
17+
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
18+
// be set NewV6 set NodeID is random bits automatically . If clock sequence has not been set by
19+
// SetClockSequence then it will be set automatically. If GetTime fails to
20+
// return the current NewV6 returns Nil and an error.
21+
func NewV6() (UUID, error) {
22+
var uuid UUID
23+
now, seq, err := GetTime()
24+
if err != nil {
25+
return uuid, err
26+
}
27+
28+
/*
29+
0 1 2 3
30+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
31+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32+
| time_high |
33+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
34+
| time_mid | time_low_and_version |
35+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
36+
|clk_seq_hi_res | clk_seq_low | node (0-1) |
37+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
38+
| node (2-5) |
39+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40+
*/
41+
42+
binary.BigEndian.PutUint64(uuid[0:], uint64(now))
43+
binary.BigEndian.PutUint16(uuid[8:], seq)
44+
45+
uuid[6] = 0x60 | (uuid[6] & 0x0F)
46+
uuid[8] = 0x80 | (uuid[8] & 0x3F)
47+
48+
nodeMu.Lock()
49+
if nodeID == zeroID {
50+
setNodeInterface("")
51+
}
52+
copy(uuid[10:], nodeID[:])
53+
nodeMu.Unlock()
54+
55+
return uuid, nil
56+
}

Diff for: version7.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2023 Google Inc. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package uuid
6+
7+
import (
8+
"io"
9+
)
10+
11+
// UUID version 7 features a time-ordered value field derived from the widely
12+
// implemented and well known Unix Epoch timestamp source,
13+
// the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
14+
// As well as improved entropy characteristics over versions 1 or 6.
15+
//
16+
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#name-uuid-version-7
17+
//
18+
// Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible.
19+
//
20+
// NewV7 returns a Version 7 UUID based on the current time(Unix Epoch).
21+
// Uses the randomness pool if it was enabled with EnableRandPool.
22+
// On error, NewV7 returns Nil and an error
23+
func NewV7() (UUID, error) {
24+
uuid, err := NewRandom()
25+
if err != nil {
26+
return uuid, err
27+
}
28+
makeV7(uuid[:])
29+
return uuid, nil
30+
}
31+
32+
// NewV7FromReader returns a Version 7 UUID based on the current time(Unix Epoch).
33+
// it use NewRandomFromReader fill random bits.
34+
// On error, NewV7FromReader returns Nil and an error.
35+
func NewV7FromReader(r io.Reader) (UUID, error) {
36+
uuid, err := NewRandomFromReader(r)
37+
if err != nil {
38+
return uuid, err
39+
}
40+
41+
makeV7(uuid[:])
42+
return uuid, nil
43+
}
44+
45+
// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6])
46+
// uuid[8] already has the right version number (Variant is 10)
47+
// see function NewV7 and NewV7FromReader
48+
func makeV7(uuid []byte) {
49+
/*
50+
0 1 2 3
51+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
52+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
53+
| unix_ts_ms |
54+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
55+
| unix_ts_ms | ver | rand_a |
56+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
57+
|var| rand_b |
58+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59+
| rand_b |
60+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
61+
*/
62+
_ = uuid[15] // bounds check
63+
64+
t := timeNow().UnixMilli()
65+
66+
uuid[0] = byte(t >> 40)
67+
uuid[1] = byte(t >> 32)
68+
uuid[2] = byte(t >> 24)
69+
uuid[3] = byte(t >> 16)
70+
uuid[4] = byte(t >> 8)
71+
uuid[5] = byte(t)
72+
73+
uuid[6] = 0x70 | (uuid[6] & 0x0F)
74+
// uuid[8] has already has right version
75+
}

0 commit comments

Comments
 (0)