Skip to content

Commit 0bd0b33

Browse files
authored
Update to RFC 9562 (#117)
1 parent 7930207 commit 0bd0b33

File tree

5 files changed

+115
-139
lines changed

5 files changed

+115
-139
lines changed

README.md

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,17 @@
77
[![Go Report Card](https://goreportcard.com/badge/github.com/gofrs/uuid)](https://goreportcard.com/report/github.com/gofrs/uuid)
88

99
Package uuid provides a pure Go implementation of Universally Unique Identifiers
10-
(UUID) variant as defined in RFC-4122. This package supports both the creation
10+
(UUID) variant as defined in RFC-9562. This package supports both the creation
1111
and parsing of UUIDs in different formats.
1212

1313
This package supports the following UUID versions:
14-
* Version 1, based on timestamp and MAC address (RFC-4122)
15-
* Version 3, based on MD5 hashing of a named value (RFC-4122)
16-
* Version 4, based on random numbers (RFC-4122)
17-
* Version 5, based on SHA-1 hashing of a named value (RFC-4122)
1814

19-
This package also supports experimental Universally Unique Identifier implementations based on a
20-
[draft RFC](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html) that updates RFC-4122
21-
* Version 6, a k-sortable id based on timestamp, and field-compatible with v1 (draft-peabody-dispatch-new-uuid-format, RFC-4122)
22-
* Version 7, a k-sortable id based on timestamp (draft-peabody-dispatch-new-uuid-format, RFC-4122)
23-
24-
The v6 and v7 IDs are **not** considered a part of the stable API, and may be subject to behavior or API changes as part of minor releases
25-
to this package. They will be updated as the draft RFC changes, and will become stable if and when the draft RFC is accepted.
15+
* Version 1, based on timestamp and MAC address
16+
* Version 3, based on MD5 hashing of a named value
17+
* Version 4, based on random numbers
18+
* Version 5, based on SHA-1 hashing of a named value
19+
* Version 6, a k-sortable id based on timestamp, and field-compatible with v1
20+
* Version 7, a k-sortable id based on timestamp
2621

2722
## Project History
2823

@@ -50,7 +45,7 @@ deficiencies.
5045

5146
## Requirements
5247

53-
This package requires Go 1.17 or later
48+
This package requires Go 1.19 or later
5449

5550
## Usage
5651

@@ -90,6 +85,5 @@ func main() {
9085

9186
## References
9287

93-
* [RFC-4122](https://tools.ietf.org/html/rfc4122)
88+
* [RFC-9562](https://tools.ietf.org/html/rfc9563) (replaces RFC-4122)
9489
* [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01)
95-
* [New UUID Formats RFC Draft (Peabody) Rev 04](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#)

generator.go

Lines changed: 72 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,12 @@ func NewV5(ns UUID, name string) UUID {
7070
// NewV6 returns a k-sortable UUID based on a timestamp and 48 bits of
7171
// pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit
7272
// order being adjusted to allow the UUID to be k-sortable.
73-
//
74-
// This is implemented based on revision 03 of the Peabody UUID draft, and may
75-
// be subject to change pending further revisions. Until the final specification
76-
// revision is finished, changes required to implement updates to the spec will
77-
// not be considered a breaking change. They will happen as a minor version
78-
// releases until the spec is final.
7973
func NewV6() (UUID, error) {
8074
return DefaultGenerator.NewV6()
8175
}
8276

8377
// NewV7 returns a k-sortable UUID based on the current millisecond precision
8478
// UNIX epoch and 74 bits of pseudorandom data. It supports single-node batch generation (multiple UUIDs in the same timestamp) with a Monotonic Random counter.
85-
//
86-
// This is implemented based on revision 04 of the Peabody UUID draft, and may
87-
// be subject to change pending further revisions. Until the final specification
88-
// revision is finished, changes required to implement updates to the spec will
89-
// not be considered a breaking change. They will happen as a minor version
90-
// releases until the spec is final.
9179
func NewV7() (UUID, error) {
9280
return DefaultGenerator.NewV7()
9381
}
@@ -103,7 +91,7 @@ type Generator interface {
10391
}
10492

10593
// Gen is a reference UUID generator based on the specifications laid out in
106-
// RFC-4122 and DCE 1.1: Authentication and Security Services. This type
94+
// RFC-9562 and DCE 1.1: Authentication and Security Services. This type
10795
// satisfies the Generator interface as defined in this package.
10896
//
10997
// For consumers who are generating V1 UUIDs, but don't want to expose the MAC
@@ -242,7 +230,7 @@ func (g *Gen) NewV1() (UUID, error) {
242230
copy(u[10:], hardwareAddr)
243231

244232
u.SetVersion(V1)
245-
u.SetVariant(VariantRFC4122)
233+
u.SetVariant(VariantRFC9562)
246234

247235
return u, nil
248236
}
@@ -251,7 +239,7 @@ func (g *Gen) NewV1() (UUID, error) {
251239
func (g *Gen) NewV3(ns UUID, name string) UUID {
252240
u := newFromHash(md5.New(), ns, name)
253241
u.SetVersion(V3)
254-
u.SetVariant(VariantRFC4122)
242+
u.SetVariant(VariantRFC9562)
255243

256244
return u
257245
}
@@ -263,7 +251,7 @@ func (g *Gen) NewV4() (UUID, error) {
263251
return Nil, err
264252
}
265253
u.SetVersion(V4)
266-
u.SetVariant(VariantRFC4122)
254+
u.SetVariant(VariantRFC9562)
267255

268256
return u, nil
269257
}
@@ -272,92 +260,59 @@ func (g *Gen) NewV4() (UUID, error) {
272260
func (g *Gen) NewV5(ns UUID, name string) UUID {
273261
u := newFromHash(sha1.New(), ns, name)
274262
u.SetVersion(V5)
275-
u.SetVariant(VariantRFC4122)
263+
u.SetVariant(VariantRFC9562)
276264

277265
return u
278266
}
279267

280268
// NewV6 returns a k-sortable UUID based on a timestamp and 48 bits of
281269
// pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit
282270
// order being adjusted to allow the UUID to be k-sortable.
283-
//
284-
// This is implemented based on revision 03 of the Peabody UUID draft, and may
285-
// be subject to change pending further revisions. Until the final specification
286-
// revision is finished, changes required to implement updates to the spec will
287-
// not be considered a breaking change. They will happen as a minor version
288-
// releases until the spec is final.
289271
func (g *Gen) NewV6() (UUID, error) {
272+
/* https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-6
273+
0 1 2 3
274+
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
275+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
276+
| time_high |
277+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
278+
| time_mid | ver | time_low |
279+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
280+
|var| clock_seq | node |
281+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
282+
| node |
283+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */
290284
var u UUID
291285

292-
if _, err := io.ReadFull(g.rand, u[10:]); err != nil {
293-
return Nil, err
294-
}
295-
296-
timeNow, clockSeq, err := g.getClockSequence(false)
286+
timeNow, _, err := g.getClockSequence(false)
297287
if err != nil {
298288
return Nil, err
299289
}
300290

301291
binary.BigEndian.PutUint32(u[0:], uint32(timeNow>>28)) // set time_high
302292
binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>12)) // set time_mid
303293
binary.BigEndian.PutUint16(u[6:], uint16(timeNow&0xfff)) // set time_low (minus four version bits)
304-
binary.BigEndian.PutUint16(u[8:], clockSeq&0x3fff) // set clk_seq_hi_res (minus two variant bits)
305-
306-
u.SetVersion(V6)
307-
u.SetVariant(VariantRFC4122)
308-
309-
return u, nil
310-
}
311294

312-
// getClockSequence returns the epoch and clock sequence for V1,V6 and V7 UUIDs.
313-
//
314-
// When useUnixTSMs is false, it uses the Coordinated Universal Time (UTC) as a count of 100-
315-
//
316-
// nanosecond intervals since 00:00:00.00, 15 October 1582 (the date of Gregorian reform to the Christian calendar).
317-
func (g *Gen) getClockSequence(useUnixTSMs bool) (uint64, uint16, error) {
318-
var err error
319-
g.clockSequenceOnce.Do(func() {
320-
buf := make([]byte, 2)
321-
if _, err = io.ReadFull(g.rand, buf); err != nil {
322-
return
323-
}
324-
g.clockSequence = binary.BigEndian.Uint16(buf)
325-
})
326-
if err != nil {
327-
return 0, 0, err
295+
// Based on the RFC 9562 recommendation that this data be fully random and not a monotonic counter,
296+
//we do NOT support batching version 6 UUIDs.
297+
//set clock_seq (14 bits) and node (48 bits) pseudo-random bits (first 2 bits will be overridden)
298+
if _, err = io.ReadFull(g.rand, u[8:]); err != nil {
299+
return Nil, err
328300
}
329301

330-
g.storageMutex.Lock()
331-
defer g.storageMutex.Unlock()
302+
u.SetVersion(V6)
332303

333-
var timeNow uint64
334-
if useUnixTSMs {
335-
timeNow = uint64(g.epochFunc().UnixMilli())
336-
} else {
337-
timeNow = g.getEpoch()
338-
}
339-
// Clock didn't change since last UUID generation.
340-
// Should increase clock sequence.
341-
if timeNow <= g.lastTime {
342-
g.clockSequence++
343-
}
344-
g.lastTime = timeNow
304+
//overwrite first 2 bits of byte[8] for the variant
305+
u.SetVariant(VariantRFC9562)
345306

346-
return timeNow, g.clockSequence, nil
307+
return u, nil
347308
}
348309

349310
// NewV7 returns a k-sortable UUID based on the current millisecond precision
350311
// UNIX epoch and 74 bits of pseudorandom data.
351-
//
352-
// This is implemented based on revision 04 of the Peabody UUID draft, and may
353-
// be subject to change pending further revisions. Until the final specification
354-
// revision is finished, changes required to implement updates to the spec will
355-
// not be considered a breaking change. They will happen as a minor version
356-
// releases until the spec is final.
357312
func (g *Gen) NewV7() (UUID, error) {
358313
var u UUID
359-
/* https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-7
360-
0 1 2 3
314+
/* https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7
315+
0 1 2 3
361316
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
362317
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
363318
| unix_ts_ms |
@@ -381,9 +336,11 @@ func (g *Gen) NewV7() (UUID, error) {
381336
u[4] = byte(ms >> 8)
382337
u[5] = byte(ms)
383338

384-
//support batching by using a monotonic pseudo-random sequence
339+
//Support batching by using a monotonic pseudo-random sequence,
340+
//as described in RFC 9562 section 6.2, Method 1.
385341
//The 6th byte contains the version and partially rand_a data.
386-
//We will lose the most significant bites from the clockSeq (with SetVersion), but it is ok, we need the least significant that contains the counter to ensure the monotonic property
342+
//We will lose the most significant bites from the clockSeq (with SetVersion), but it is ok,
343+
//we need the least significant that contains the counter to ensure the monotonic property
387344
binary.BigEndian.PutUint16(u[6:8], clockSeq) // set rand_a with clock seq which is random and monotonic
388345

389346
//override first 4bits of u[6].
@@ -394,11 +351,48 @@ func (g *Gen) NewV7() (UUID, error) {
394351
return Nil, err
395352
}
396353
//override first 2 bits of byte[8] for the variant
397-
u.SetVariant(VariantRFC4122)
354+
u.SetVariant(VariantRFC9562)
398355

399356
return u, nil
400357
}
401358

359+
// getClockSequence returns the epoch and clock sequence for V1,V6 and V7 UUIDs.
360+
//
361+
// When useUnixTSMs is false, it uses the Coordinated Universal Time (UTC) as a count of 100-
362+
//
363+
// nanosecond intervals since 00:00:00.00, 15 October 1582 (the date of Gregorian reform to the Christian calendar).
364+
func (g *Gen) getClockSequence(useUnixTSMs bool) (uint64, uint16, error) {
365+
var err error
366+
g.clockSequenceOnce.Do(func() {
367+
buf := make([]byte, 2)
368+
if _, err = io.ReadFull(g.rand, buf); err != nil {
369+
return
370+
}
371+
g.clockSequence = binary.BigEndian.Uint16(buf)
372+
})
373+
if err != nil {
374+
return 0, 0, err
375+
}
376+
377+
g.storageMutex.Lock()
378+
defer g.storageMutex.Unlock()
379+
380+
var timeNow uint64
381+
if useUnixTSMs {
382+
timeNow = uint64(g.epochFunc().UnixMilli())
383+
} else {
384+
timeNow = g.getEpoch()
385+
}
386+
// Clock didn't change since last UUID generation.
387+
// Should increase clock sequence.
388+
if timeNow <= g.lastTime {
389+
g.clockSequence++
390+
}
391+
g.lastTime = timeNow
392+
393+
return timeNow, g.clockSequence, nil
394+
}
395+
402396
// Returns the hardware address.
403397
func (g *Gen) getHardwareAddr() ([]byte, error) {
404398
var err error
@@ -414,7 +408,7 @@ func (g *Gen) getHardwareAddr() ([]byte, error) {
414408
if _, err = io.ReadFull(g.rand, g.hardwareAddr[:]); err != nil {
415409
return
416410
}
417-
// Set multicast bit as recommended by RFC-4122
411+
// Set multicast bit as recommended by RFC-9562
418412
g.hardwareAddr[0] |= 0x01
419413
})
420414
if err != nil {

generator_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func testNewV1Basic(t *testing.T) {
9292
if got, want := u.Version(), V1; got != want {
9393
t.Errorf("generated UUID with version %d, want %d", got, want)
9494
}
95-
if got, want := u.Variant(), VariantRFC4122; got != want {
95+
if got, want := u.Variant(), VariantRFC9562; got != want {
9696
t.Errorf("generated UUID with variant %d, want %d", got, want)
9797
}
9898
}
@@ -110,7 +110,7 @@ func testNewV1BasicWithOptions(t *testing.T) {
110110
if got, want := u.Version(), V1; got != want {
111111
t.Errorf("generated UUID with version %d, want %d", got, want)
112112
}
113-
if got, want := u.Variant(), VariantRFC4122; got != want {
113+
if got, want := u.Variant(), VariantRFC9562; got != want {
114114
t.Errorf("generated UUID with variant %d, want %d", got, want)
115115
}
116116
}
@@ -249,7 +249,7 @@ func testNewV3Basic(t *testing.T) {
249249
if got, want := u.Version(), V3; got != want {
250250
t.Errorf("NewV3(%v, %q): got version %d, want %d", ns, name, got, want)
251251
}
252-
if got, want := u.Variant(), VariantRFC4122; got != want {
252+
if got, want := u.Variant(), VariantRFC9562; got != want {
253253
t.Errorf("NewV3(%v, %q): got variant %d, want %d", ns, name, got, want)
254254
}
255255
want := "5df41881-3aed-3515-88a7-2f4a814cf09e"
@@ -296,7 +296,7 @@ func testNewV4Basic(t *testing.T) {
296296
if got, want := u.Version(), V4; got != want {
297297
t.Errorf("got version %d, want %d", got, want)
298298
}
299-
if got, want := u.Variant(), VariantRFC4122; got != want {
299+
if got, want := u.Variant(), VariantRFC9562; got != want {
300300
t.Errorf("got variant %d, want %d", got, want)
301301
}
302302
}
@@ -383,7 +383,7 @@ func testNewV5Basic(t *testing.T) {
383383
if got, want := u.Version(), V5; got != want {
384384
t.Errorf("NewV5(%v, %q): got version %d, want %d", ns, name, got, want)
385385
}
386-
if got, want := u.Variant(), VariantRFC4122; got != want {
386+
if got, want := u.Variant(), VariantRFC9562; got != want {
387387
t.Errorf("NewV5(%v, %q): got variant %d, want %d", ns, name, got, want)
388388
}
389389
want := "2ed6657d-e927-568b-95e1-2665a8aea6a2"
@@ -433,7 +433,7 @@ func testNewV6Basic(t *testing.T) {
433433
if got, want := u.Version(), V6; got != want {
434434
t.Errorf("generated UUID with version %d, want %d", got, want)
435435
}
436-
if got, want := u.Variant(), VariantRFC4122; got != want {
436+
if got, want := u.Variant(), VariantRFC9562; got != want {
437437
t.Errorf("generated UUID with variant %d, want %d", got, want)
438438
}
439439
}
@@ -624,7 +624,7 @@ func makeTestNewV7Basic() func(t *testing.T) {
624624
if got, want := u.Version(), V7; got != want {
625625
t.Errorf("got version %d, want %d", got, want)
626626
}
627-
if got, want := u.Variant(), VariantRFC4122; got != want {
627+
if got, want := u.Variant(), VariantRFC9562; got != want {
628628
t.Errorf("got variant %d, want %d", got, want)
629629
}
630630
}
@@ -652,7 +652,7 @@ func makeTestNewV7TestVector() func(t *testing.T) {
652652
if got, want := u.Version(), V7; got != want {
653653
t.Errorf("got version %d, want %d", got, want)
654654
}
655-
if got, want := u.Variant(), VariantRFC4122; got != want {
655+
if got, want := u.Variant(), VariantRFC9562; got != want {
656656
t.Errorf("got variant %d, want %d", got, want)
657657
}
658658
if got, want := u.String()[:15], "017f22e2-79b0-7"; got != want {
@@ -677,7 +677,7 @@ func makeTestNewV7Basic10000000() func(t *testing.T) {
677677
if got, want := u.Version(), V7; got != want {
678678
t.Errorf("got version %d, want %d", got, want)
679679
}
680-
if got, want := u.Variant(), VariantRFC4122; got != want {
680+
if got, want := u.Variant(), VariantRFC9562; got != want {
681681
t.Errorf("got variant %d, want %d", got, want)
682682
}
683683
}

0 commit comments

Comments
 (0)