Import upstream version 4.1.0
Debian Janitor
2 years ago
0 | name: Go | |
1 | ||
2 | on: | |
3 | push: | |
4 | branches: [ master ] | |
5 | pull_request: | |
6 | branches: [ master ] | |
7 | ||
8 | jobs: | |
9 | ||
10 | build: | |
11 | name: Build + Test Stable | |
12 | runs-on: ubuntu-latest | |
13 | env: | |
14 | GO111MODULE: auto | |
15 | steps: | |
16 | ||
17 | - name: Build | |
18 | uses: actions/setup-go@v2 | |
19 | with: | |
20 | go-version: '1.17.x' | |
21 | ||
22 | - name: Check out code into the Go module directory | |
23 | uses: actions/checkout@v2 | |
24 | ||
25 | - name: Get Coverage | |
26 | run: go get golang.org/x/tools/cmd/cover | |
27 | ||
28 | - name: Build | |
29 | run: go build -v ./... | |
30 | ||
31 | - name: Test | |
32 | run: go test ./... -race -coverprofile=coverage.txt -covermode=atomic | |
33 | ||
34 | - name: Coverage | |
35 | uses: codecov/codecov-action@v2 | |
36 | ||
37 | build-legacy: | |
38 | name: Build + Test Previous Stable | |
39 | runs-on: ubuntu-latest | |
40 | env: | |
41 | GO111MODULE: auto | |
42 | steps: | |
43 | ||
44 | - name: Build | |
45 | uses: actions/setup-go@v2 | |
46 | with: | |
47 | go-version: '1.16.x' | |
48 | ||
49 | - name: Check out code into the Go module directory | |
50 | uses: actions/checkout@v2 | |
51 | ||
52 | - name: Build | |
53 | run: go build -v ./... | |
54 | ||
55 | - name: Test | |
56 | run: go test ./... |
0 | language: go | |
1 | sudo: false | |
2 | go: | |
3 | - 1.7.x | |
4 | - 1.8.x | |
5 | - 1.9.x | |
6 | - 1.10.x | |
7 | - 1.11.x | |
8 | - tip | |
9 | matrix: | |
10 | allow_failures: | |
11 | - go: tip | |
12 | fast_finish: true | |
13 | env: | |
14 | - GO111MODULE=on | |
15 | before_install: | |
16 | - go get golang.org/x/tools/cmd/cover | |
17 | script: | |
18 | - go test ./... -race -coverprofile=coverage.txt -covermode=atomic | |
19 | after_success: | |
20 | - bash <(curl -s https://codecov.io/bash) | |
21 | notifications: | |
22 | email: false |
11 | 11 | |
12 | 12 | This package supports the following UUID versions: |
13 | 13 | * Version 1, based on timestamp and MAC address (RFC-4122) |
14 | * Version 2, based on timestamp, MAC address and POSIX UID/GID (DCE 1.1) | |
15 | 14 | * Version 3, based on MD5 hashing of a named value (RFC-4122) |
16 | 15 | * Version 4, based on random numbers (RFC-4122) |
17 | 16 | * Version 5, based on SHA-1 hashing of a named value (RFC-4122) |
106 | 105 | |
107 | 106 | * [RFC-4122](https://tools.ietf.org/html/rfc4122) |
108 | 107 | * [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01) |
108 | * [New UUID Formats RFC Draft (Peabody) Rev 02](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02) |
113 | 113 | case 41, 45: |
114 | 114 | return u.decodeURN(text) |
115 | 115 | default: |
116 | return fmt.Errorf("uuid: incorrect UUID length: %s", text) | |
116 | return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(text), text) | |
117 | 117 | } |
118 | 118 | } |
119 | 119 | |
121 | 121 | // "6ba7b810-9dad-11d1-80b4-00c04fd430c8". |
122 | 122 | func (u *UUID) decodeCanonical(t []byte) error { |
123 | 123 | if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' { |
124 | return fmt.Errorf("uuid: incorrect UUID format %s", t) | |
124 | return fmt.Errorf("uuid: incorrect UUID format in string %q", t) | |
125 | 125 | } |
126 | 126 | |
127 | 127 | src := t |
159 | 159 | l := len(t) |
160 | 160 | |
161 | 161 | if t[0] != '{' || t[l-1] != '}' { |
162 | return fmt.Errorf("uuid: incorrect UUID format %s", t) | |
162 | return fmt.Errorf("uuid: incorrect UUID format in string %q", t) | |
163 | 163 | } |
164 | 164 | |
165 | 165 | return u.decodePlain(t[1 : l-1]) |
174 | 174 | urnUUIDPrefix := t[:9] |
175 | 175 | |
176 | 176 | if !bytes.Equal(urnUUIDPrefix, urnPrefix) { |
177 | return fmt.Errorf("uuid: incorrect UUID format: %s", t) | |
177 | return fmt.Errorf("uuid: incorrect UUID format in string %q", t) | |
178 | 178 | } |
179 | 179 | |
180 | 180 | return u.decodePlain(t[9:total]) |
190 | 190 | case 36: |
191 | 191 | return u.decodeCanonical(t) |
192 | 192 | default: |
193 | return fmt.Errorf("uuid: incorrect UUID length: %s", t) | |
193 | return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(t), t) | |
194 | 194 | } |
195 | 195 | } |
196 | 196 |
25 | 25 | "crypto/rand" |
26 | 26 | "crypto/sha1" |
27 | 27 | "encoding/binary" |
28 | "errors" | |
28 | 29 | "fmt" |
29 | 30 | "hash" |
30 | 31 | "io" |
31 | 32 | "net" |
32 | "os" | |
33 | 33 | "sync" |
34 | 34 | "time" |
35 | 35 | ) |
46 | 46 | // DefaultGenerator is the default UUID Generator used by this package. |
47 | 47 | var DefaultGenerator Generator = NewGen() |
48 | 48 | |
49 | var ( | |
50 | posixUID = uint32(os.Getuid()) | |
51 | posixGID = uint32(os.Getgid()) | |
52 | ) | |
53 | ||
54 | 49 | // NewV1 returns a UUID based on the current timestamp and MAC address. |
55 | 50 | func NewV1() (UUID, error) { |
56 | 51 | return DefaultGenerator.NewV1() |
57 | 52 | } |
58 | 53 | |
59 | // NewV2 returns a DCE Security UUID based on the POSIX UID/GID. | |
60 | func NewV2(domain byte) (UUID, error) { | |
61 | return DefaultGenerator.NewV2(domain) | |
62 | } | |
63 | ||
64 | 54 | // NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name. |
65 | 55 | func NewV3(ns UUID, name string) UUID { |
66 | 56 | return DefaultGenerator.NewV3(ns, name) |
76 | 66 | return DefaultGenerator.NewV5(ns, name) |
77 | 67 | } |
78 | 68 | |
69 | // NewV6 returns a k-sortable UUID based on a timestamp and 48 bits of | |
70 | // pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit | |
71 | // order being adjusted to allow the UUID to be k-sortable. | |
72 | // | |
73 | // This is implemented based on revision 02 of the Peabody UUID draft, and may | |
74 | // be subject to change pending further revisions. Until the final specification | |
75 | // revision is finished, changes required to implement updates to the spec will | |
76 | // not be considered a breaking change. They will happen as a minor version | |
77 | // releases until the spec is final. | |
78 | func NewV6() (UUID, error) { | |
79 | return DefaultGenerator.NewV6() | |
80 | } | |
81 | ||
82 | // NewV7 returns a k-sortable UUID based on the current UNIX epoch, with the | |
83 | // ability to configure the timestamp's precision from millisecond all the way | |
84 | // to nanosecond. The additional precision is supported by reducing the amount | |
85 | // of pseudorandom data that makes up the rest of the UUID. | |
86 | // | |
87 | // If an unknown Precision argument is passed to this method it will panic. As | |
88 | // such it's strongly encouraged to use the package-provided constants for this | |
89 | // value. | |
90 | // | |
91 | // This is implemented based on revision 02 of the Peabody UUID draft, and may | |
92 | // be subject to change pending further revisions. Until the final specification | |
93 | // revision is finished, changes required to implement updates to the spec will | |
94 | // not be considered a breaking change. They will happen as a minor version | |
95 | // releases until the spec is final. | |
96 | func NewV7(p Precision) (UUID, error) { | |
97 | return DefaultGenerator.NewV7(p) | |
98 | } | |
99 | ||
79 | 100 | // Generator provides an interface for generating UUIDs. |
80 | 101 | type Generator interface { |
81 | 102 | NewV1() (UUID, error) |
82 | NewV2(domain byte) (UUID, error) | |
83 | 103 | NewV3(ns UUID, name string) UUID |
84 | 104 | NewV4() (UUID, error) |
85 | 105 | NewV5(ns UUID, name string) UUID |
106 | NewV6() (UUID, error) | |
107 | NewV7(Precision) (UUID, error) | |
86 | 108 | } |
87 | 109 | |
88 | 110 | // Gen is a reference UUID generator based on the specifications laid out in |
108 | 130 | lastTime uint64 |
109 | 131 | clockSequence uint16 |
110 | 132 | hardwareAddr [6]byte |
133 | ||
134 | v7LastTime uint64 | |
135 | v7LastSubsec uint64 | |
136 | v7ClockSequence uint16 | |
111 | 137 | } |
112 | 138 | |
113 | 139 | // interface check -- build will fail if *Gen doesn't satisfy Generator |
163 | 189 | return u, nil |
164 | 190 | } |
165 | 191 | |
166 | // NewV2 returns a DCE Security UUID based on the POSIX UID/GID. | |
167 | func (g *Gen) NewV2(domain byte) (UUID, error) { | |
168 | u, err := g.NewV1() | |
169 | if err != nil { | |
170 | return Nil, err | |
171 | } | |
172 | ||
173 | switch domain { | |
174 | case DomainPerson: | |
175 | binary.BigEndian.PutUint32(u[:], posixUID) | |
176 | case DomainGroup: | |
177 | binary.BigEndian.PutUint32(u[:], posixGID) | |
178 | } | |
179 | ||
180 | u[9] = domain | |
181 | ||
182 | u.SetVersion(V2) | |
183 | u.SetVariant(VariantRFC4122) | |
184 | ||
185 | return u, nil | |
186 | } | |
187 | ||
188 | 192 | // NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name. |
189 | 193 | func (g *Gen) NewV3(ns UUID, name string) UUID { |
190 | 194 | u := newFromHash(md5.New(), ns, name) |
215 | 219 | return u |
216 | 220 | } |
217 | 221 | |
218 | // Returns the epoch and clock sequence. | |
222 | // NewV6 returns a k-sortable UUID based on a timestamp and 48 bits of | |
223 | // pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit | |
224 | // order being adjusted to allow the UUID to be k-sortable. | |
225 | // | |
226 | // This is implemented based on revision 02 of the Peabody UUID draft, and may | |
227 | // be subject to change pending further revisions. Until the final specification | |
228 | // revision is finished, changes required to implement updates to the spec will | |
229 | // not be considered a breaking change. They will happen as a minor version | |
230 | // releases until the spec is final. | |
231 | func (g *Gen) NewV6() (UUID, error) { | |
232 | var u UUID | |
233 | ||
234 | if _, err := io.ReadFull(g.rand, u[10:]); err != nil { | |
235 | return Nil, err | |
236 | } | |
237 | ||
238 | timeNow, clockSeq, err := g.getClockSequence() | |
239 | if err != nil { | |
240 | return Nil, err | |
241 | } | |
242 | ||
243 | binary.BigEndian.PutUint32(u[0:], uint32(timeNow>>28)) // set time_high | |
244 | binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>12)) // set time_mid | |
245 | binary.BigEndian.PutUint16(u[6:], uint16(timeNow&0xfff)) // set time_low (minus four version bits) | |
246 | binary.BigEndian.PutUint16(u[8:], clockSeq&0x3fff) // set clk_seq_hi_res (minus two variant bits) | |
247 | ||
248 | u.SetVersion(V6) | |
249 | u.SetVariant(VariantRFC4122) | |
250 | ||
251 | return u, nil | |
252 | } | |
253 | ||
254 | // getClockSequence returns the epoch and clock sequence for V1 and V6 UUIDs. | |
219 | 255 | func (g *Gen) getClockSequence() (uint64, uint16, error) { |
220 | 256 | var err error |
221 | 257 | g.clockSequenceOnce.Do(func() { |
243 | 279 | return timeNow, g.clockSequence, nil |
244 | 280 | } |
245 | 281 | |
282 | // Precision is used to configure the V7 generator, to specify how precise the | |
283 | // timestamp within the UUID should be. | |
284 | type Precision byte | |
285 | ||
286 | const ( | |
287 | NanosecondPrecision Precision = iota | |
288 | MicrosecondPrecision | |
289 | MillisecondPrecision | |
290 | ) | |
291 | ||
292 | func (p Precision) String() string { | |
293 | switch p { | |
294 | case NanosecondPrecision: | |
295 | return "nanosecond" | |
296 | ||
297 | case MicrosecondPrecision: | |
298 | return "microsecond" | |
299 | ||
300 | case MillisecondPrecision: | |
301 | return "millisecond" | |
302 | ||
303 | default: | |
304 | return "unknown" | |
305 | } | |
306 | } | |
307 | ||
308 | // Duration returns the time.Duration for a specific precision. If the Precision | |
309 | // value is not known, this returns 0. | |
310 | func (p Precision) Duration() time.Duration { | |
311 | switch p { | |
312 | case NanosecondPrecision: | |
313 | return time.Nanosecond | |
314 | ||
315 | case MicrosecondPrecision: | |
316 | return time.Microsecond | |
317 | ||
318 | case MillisecondPrecision: | |
319 | return time.Millisecond | |
320 | ||
321 | default: | |
322 | return 0 | |
323 | } | |
324 | } | |
325 | ||
326 | // NewV7 returns a k-sortable UUID based on the current UNIX epoch, with the | |
327 | // ability to configure the timestamp's precision from millisecond all the way | |
328 | // to nanosecond. The additional precision is supported by reducing the amount | |
329 | // of pseudorandom data that makes up the rest of the UUID. | |
330 | // | |
331 | // If an unknown Precision argument is passed to this method it will panic. As | |
332 | // such it's strongly encouraged to use the package-provided constants for this | |
333 | // value. | |
334 | // | |
335 | // This is implemented based on revision 02 of the Peabody UUID draft, and may | |
336 | // be subject to change pending further revisions. Until the final specification | |
337 | // revision is finished, changes required to implement updates to the spec will | |
338 | // not be considered a breaking change. They will happen as a minor version | |
339 | // releases until the spec is final. | |
340 | func (g *Gen) NewV7(p Precision) (UUID, error) { | |
341 | var u UUID | |
342 | var err error | |
343 | ||
344 | switch p { | |
345 | case NanosecondPrecision: | |
346 | u, err = g.newV7Nano() | |
347 | ||
348 | case MicrosecondPrecision: | |
349 | u, err = g.newV7Micro() | |
350 | ||
351 | case MillisecondPrecision: | |
352 | u, err = g.newV7Milli() | |
353 | ||
354 | default: | |
355 | panic(fmt.Sprintf("unknown precision value %d", p)) | |
356 | } | |
357 | ||
358 | if err != nil { | |
359 | return Nil, err | |
360 | } | |
361 | ||
362 | u.SetVersion(V7) | |
363 | u.SetVariant(VariantRFC4122) | |
364 | ||
365 | return u, nil | |
366 | } | |
367 | ||
368 | func (g *Gen) newV7Milli() (UUID, error) { | |
369 | var u UUID | |
370 | ||
371 | if _, err := io.ReadFull(g.rand, u[8:]); err != nil { | |
372 | return Nil, err | |
373 | } | |
374 | ||
375 | sec, nano, seq, err := g.getV7ClockSequence(MillisecondPrecision) | |
376 | if err != nil { | |
377 | return Nil, err | |
378 | } | |
379 | ||
380 | msec := (nano / 1000000) & 0xfff | |
381 | ||
382 | d := (sec << 28) // set unixts field | |
383 | d |= (msec << 16) // set msec field | |
384 | d |= (uint64(seq) & 0xfff) // set seq field | |
385 | ||
386 | binary.BigEndian.PutUint64(u[:], d) | |
387 | ||
388 | return u, nil | |
389 | } | |
390 | ||
391 | func (g *Gen) newV7Micro() (UUID, error) { | |
392 | var u UUID | |
393 | ||
394 | if _, err := io.ReadFull(g.rand, u[10:]); err != nil { | |
395 | return Nil, err | |
396 | } | |
397 | ||
398 | sec, nano, seq, err := g.getV7ClockSequence(MicrosecondPrecision) | |
399 | if err != nil { | |
400 | return Nil, err | |
401 | } | |
402 | ||
403 | usec := nano / 1000 | |
404 | usech := (usec << 4) & 0xfff0000 | |
405 | usecl := usec & 0xfff | |
406 | ||
407 | d := (sec << 28) // set unixts field | |
408 | d |= usech | usecl // set usec fields | |
409 | ||
410 | binary.BigEndian.PutUint64(u[:], d) | |
411 | binary.BigEndian.PutUint16(u[8:], seq) | |
412 | ||
413 | return u, nil | |
414 | } | |
415 | ||
416 | func (g *Gen) newV7Nano() (UUID, error) { | |
417 | var u UUID | |
418 | ||
419 | if _, err := io.ReadFull(g.rand, u[11:]); err != nil { | |
420 | return Nil, err | |
421 | } | |
422 | ||
423 | sec, nano, seq, err := g.getV7ClockSequence(NanosecondPrecision) | |
424 | if err != nil { | |
425 | return Nil, err | |
426 | } | |
427 | ||
428 | nano &= 0x3fffffffff | |
429 | nanoh := nano >> 26 | |
430 | nanom := (nano >> 14) & 0xfff | |
431 | nanol := uint16(nano & 0x3fff) | |
432 | ||
433 | d := (sec << 28) // set unixts field | |
434 | d |= (nanoh << 16) | nanom // set nsec high and med fields | |
435 | ||
436 | binary.BigEndian.PutUint64(u[:], d) | |
437 | binary.BigEndian.PutUint16(u[8:], nanol) // set nsec low field | |
438 | ||
439 | u[10] = byte(seq) // set seq field | |
440 | ||
441 | return u, nil | |
442 | } | |
443 | ||
444 | const ( | |
445 | maxSeq14 = (1 << 14) - 1 | |
446 | maxSeq12 = (1 << 12) - 1 | |
447 | maxSeq8 = (1 << 8) - 1 | |
448 | ) | |
449 | ||
450 | // getV7ClockSequence returns the unix epoch, nanoseconds of current second, and | |
451 | // the sequence for V7 UUIDs. | |
452 | func (g *Gen) getV7ClockSequence(p Precision) (epoch uint64, nano uint64, seq uint16, err error) { | |
453 | g.storageMutex.Lock() | |
454 | defer g.storageMutex.Unlock() | |
455 | ||
456 | tn := g.epochFunc() | |
457 | unix := uint64(tn.Unix()) | |
458 | nsec := uint64(tn.Nanosecond()) | |
459 | ||
460 | // V7 UUIDs have more precise requirements around how the clock sequence | |
461 | // value is generated and used. Specifically they require that the sequence | |
462 | // be zero, unless we've already generated a UUID within this unit of time | |
463 | // (millisecond, microsecond, or nanosecond) at which point you should | |
464 | // increment the sequence. Likewise if time has warped backwards for some reason (NTP | |
465 | // adjustment?), we also increment the clock sequence to reduce the risk of a | |
466 | // collision. | |
467 | switch { | |
468 | case unix < g.v7LastTime: | |
469 | g.v7ClockSequence++ | |
470 | ||
471 | case unix > g.v7LastTime: | |
472 | g.v7ClockSequence = 0 | |
473 | ||
474 | case unix == g.v7LastTime: | |
475 | switch p { | |
476 | case NanosecondPrecision: | |
477 | if nsec <= g.v7LastSubsec { | |
478 | if g.v7ClockSequence >= maxSeq8 { | |
479 | return 0, 0, 0, errors.New("generating nanosecond precision UUIDv7s too fast: internal clock sequence would roll over") | |
480 | } | |
481 | ||
482 | g.v7ClockSequence++ | |
483 | } else { | |
484 | g.v7ClockSequence = 0 | |
485 | } | |
486 | ||
487 | case MicrosecondPrecision: | |
488 | if nsec/1000 <= g.v7LastSubsec/1000 { | |
489 | if g.v7ClockSequence >= maxSeq14 { | |
490 | return 0, 0, 0, errors.New("generating microsecond precision UUIDv7s too fast: internal clock sequence would roll over") | |
491 | } | |
492 | ||
493 | g.v7ClockSequence++ | |
494 | } else { | |
495 | g.v7ClockSequence = 0 | |
496 | } | |
497 | ||
498 | case MillisecondPrecision: | |
499 | if nsec/1000000 <= g.v7LastSubsec/1000000 { | |
500 | if g.v7ClockSequence >= maxSeq12 { | |
501 | return 0, 0, 0, errors.New("generating millisecond precision UUIDv7s too fast: internal clock sequence would roll over") | |
502 | } | |
503 | ||
504 | g.v7ClockSequence++ | |
505 | } else { | |
506 | g.v7ClockSequence = 0 | |
507 | } | |
508 | ||
509 | default: | |
510 | panic(fmt.Sprintf("unknown precision value %d", p)) | |
511 | } | |
512 | } | |
513 | ||
514 | g.v7LastTime = unix | |
515 | g.v7LastSubsec = nsec | |
516 | ||
517 | return unix, nsec, g.v7ClockSequence, nil | |
518 | } | |
519 | ||
246 | 520 | // Returns the hardware address. |
247 | 521 | func (g *Gen) getHardwareAddr() ([]byte, error) { |
248 | 522 | var err error |
23 | 23 | import ( |
24 | 24 | "bytes" |
25 | 25 | "crypto/rand" |
26 | "encoding/binary" | |
26 | 27 | "fmt" |
27 | 28 | "net" |
29 | "strings" | |
28 | 30 | "testing" |
29 | 31 | "time" |
30 | 32 | ) |
31 | 33 | |
32 | 34 | func TestGenerator(t *testing.T) { |
33 | 35 | t.Run("NewV1", testNewV1) |
34 | t.Run("NewV2", testNewV2) | |
35 | 36 | t.Run("NewV3", testNewV3) |
36 | 37 | t.Run("NewV4", testNewV4) |
37 | 38 | t.Run("NewV5", testNewV5) |
39 | t.Run("NewV6", testNewV6) | |
40 | t.Run("NewV7", testNewV7) | |
38 | 41 | } |
39 | 42 | |
40 | 43 | func testNewV1(t *testing.T) { |
167 | 170 | u, err := g.NewV1() |
168 | 171 | if err == nil { |
169 | 172 | t.Errorf("did not error on faulty reader and missing network, got %v", u) |
170 | } | |
171 | } | |
172 | ||
173 | func testNewV2(t *testing.T) { | |
174 | t.Run("Basic", testNewV2Basic) | |
175 | t.Run("DifferentAcrossCalls", testNewV2DifferentAcrossCalls) | |
176 | t.Run("FaultyRand", testNewV2FaultyRand) | |
177 | } | |
178 | ||
179 | func testNewV2Basic(t *testing.T) { | |
180 | domains := []byte{ | |
181 | DomainPerson, | |
182 | DomainGroup, | |
183 | DomainOrg, | |
184 | } | |
185 | for _, domain := range domains { | |
186 | u, err := NewV2(domain) | |
187 | if err != nil { | |
188 | t.Errorf("NewV2(%d): %v", domain, err) | |
189 | } | |
190 | if got, want := u.Version(), V2; got != want { | |
191 | t.Errorf("NewV2(%d) generated UUID with version %d, want %d", domain, got, want) | |
192 | } | |
193 | if got, want := u.Variant(), VariantRFC4122; got != want { | |
194 | t.Errorf("NewV2(%d) generated UUID with variant %d, want %d", domain, got, want) | |
195 | } | |
196 | } | |
197 | } | |
198 | ||
199 | func testNewV2DifferentAcrossCalls(t *testing.T) { | |
200 | u1, err := NewV2(DomainOrg) | |
201 | if err != nil { | |
202 | t.Fatal(err) | |
203 | } | |
204 | u2, err := NewV2(DomainOrg) | |
205 | if err != nil { | |
206 | t.Fatal(err) | |
207 | } | |
208 | if u1 == u2 { | |
209 | t.Errorf("generated identical UUIDs across calls: %v", u1) | |
210 | } | |
211 | } | |
212 | ||
213 | func testNewV2FaultyRand(t *testing.T) { | |
214 | g := &Gen{ | |
215 | epochFunc: time.Now, | |
216 | hwAddrFunc: defaultHWAddrFunc, | |
217 | rand: &faultyReader{ | |
218 | readToFail: 0, // fail immediately | |
219 | }, | |
220 | } | |
221 | u, err := g.NewV2(DomainPerson) | |
222 | if err == nil { | |
223 | t.Fatalf("got %v, want error", u) | |
224 | } | |
225 | if u != Nil { | |
226 | t.Fatalf("got %v on error, want Nil", u) | |
227 | 173 | } |
228 | 174 | } |
229 | 175 | |
375 | 321 | } |
376 | 322 | } |
377 | 323 | |
324 | func testNewV6(t *testing.T) { | |
325 | t.Run("Basic", testNewV6Basic) | |
326 | t.Run("DifferentAcrossCalls", testNewV6DifferentAcrossCalls) | |
327 | t.Run("StaleEpoch", testNewV6StaleEpoch) | |
328 | t.Run("FaultyRand", testNewV6FaultyRand) | |
329 | t.Run("ShortRandomRead", testNewV6ShortRandomRead) | |
330 | t.Run("KSortable", testNewV6KSortable) | |
331 | } | |
332 | ||
333 | func testNewV6Basic(t *testing.T) { | |
334 | u, err := NewV6() | |
335 | if err != nil { | |
336 | t.Fatal(err) | |
337 | } | |
338 | if got, want := u.Version(), V6; got != want { | |
339 | t.Errorf("generated UUID with version %d, want %d", got, want) | |
340 | } | |
341 | if got, want := u.Variant(), VariantRFC4122; got != want { | |
342 | t.Errorf("generated UUID with variant %d, want %d", got, want) | |
343 | } | |
344 | } | |
345 | ||
346 | func testNewV6DifferentAcrossCalls(t *testing.T) { | |
347 | u1, err := NewV6() | |
348 | if err != nil { | |
349 | t.Fatal(err) | |
350 | } | |
351 | u2, err := NewV6() | |
352 | if err != nil { | |
353 | t.Fatal(err) | |
354 | } | |
355 | if u1 == u2 { | |
356 | t.Errorf("generated identical UUIDs across calls: %v", u1) | |
357 | } | |
358 | } | |
359 | ||
360 | func testNewV6StaleEpoch(t *testing.T) { | |
361 | g := &Gen{ | |
362 | epochFunc: func() time.Time { | |
363 | return time.Unix(0, 0) | |
364 | }, | |
365 | hwAddrFunc: defaultHWAddrFunc, | |
366 | rand: rand.Reader, | |
367 | } | |
368 | u1, err := g.NewV6() | |
369 | if err != nil { | |
370 | t.Fatal(err) | |
371 | } | |
372 | u2, err := g.NewV6() | |
373 | if err != nil { | |
374 | t.Fatal(err) | |
375 | } | |
376 | if u1 == u2 { | |
377 | t.Errorf("generated identical UUIDs across calls: %v", u1) | |
378 | } | |
379 | } | |
380 | ||
381 | func testNewV6FaultyRand(t *testing.T) { | |
382 | t.Run("randomData", func(t *testing.T) { | |
383 | g := &Gen{ | |
384 | epochFunc: time.Now, | |
385 | hwAddrFunc: defaultHWAddrFunc, | |
386 | rand: &faultyReader{ | |
387 | readToFail: 0, // fail immediately | |
388 | }, | |
389 | } | |
390 | u, err := g.NewV6() | |
391 | if err == nil { | |
392 | t.Fatalf("got %v, want error", u) | |
393 | } | |
394 | if u != Nil { | |
395 | t.Fatalf("got %v on error, want Nil", u) | |
396 | } | |
397 | }) | |
398 | ||
399 | t.Run("clockSequence", func(t *testing.T) { | |
400 | g := &Gen{ | |
401 | epochFunc: time.Now, | |
402 | hwAddrFunc: defaultHWAddrFunc, | |
403 | rand: &faultyReader{ | |
404 | readToFail: 1, // fail immediately | |
405 | }, | |
406 | } | |
407 | u, err := g.NewV6() | |
408 | if err == nil { | |
409 | t.Fatalf("got %v, want error", u) | |
410 | } | |
411 | if u != Nil { | |
412 | t.Fatalf("got %v on error, want Nil", u) | |
413 | } | |
414 | }) | |
415 | } | |
416 | ||
417 | func testNewV6ShortRandomRead(t *testing.T) { | |
418 | g := &Gen{ | |
419 | epochFunc: time.Now, | |
420 | rand: bytes.NewReader([]byte{42}), | |
421 | } | |
422 | u, err := g.NewV6() | |
423 | if err == nil { | |
424 | t.Errorf("got %v, nil error", u) | |
425 | } | |
426 | } | |
427 | ||
428 | func testNewV6KSortable(t *testing.T) { | |
429 | uuids := make([]UUID, 10) | |
430 | for i := range uuids { | |
431 | u, err := NewV6() | |
432 | testErrCheck(t, "NewV6()", "", err) | |
433 | ||
434 | uuids[i] = u | |
435 | ||
436 | time.Sleep(time.Microsecond) | |
437 | } | |
438 | ||
439 | for i := 1; i < len(uuids); i++ { | |
440 | p, n := uuids[i-1], uuids[i] | |
441 | isLess := p.String() < n.String() | |
442 | if !isLess { | |
443 | t.Errorf("uuids[%d] (%s) not less than uuids[%d] (%s)", i-1, p, i, n) | |
444 | } | |
445 | } | |
446 | } | |
447 | ||
448 | func testNewV7(t *testing.T) { | |
449 | t.Run("InvalidPrecision", testNewV7InvalidPrecision) | |
450 | ||
451 | for _, p := range []Precision{NanosecondPrecision, MicrosecondPrecision, MillisecondPrecision} { | |
452 | t.Run(p.String(), func(t *testing.T) { | |
453 | t.Run("Basic", makeTestNewV7Basic(p)) | |
454 | t.Run("Basic10000000", makeTestNewV7Basic10000000(p)) | |
455 | t.Run("DifferentAcrossCalls", makeTestNewV7DifferentAcrossCalls(p)) | |
456 | t.Run("StaleEpoch", makeTestNewV7StaleEpoch(p)) | |
457 | t.Run("FaultyRand", makeTestNewV7FaultyRand(p)) | |
458 | t.Run("ShortRandomRead", makeTestNewV7ShortRandomRead(p)) | |
459 | t.Run("ClockSequenceBehaviors", makeTestNewV7ClockSequenceBehaviors(p)) | |
460 | t.Run("KSortable", makeTestNewV7KSortable(p)) | |
461 | }) | |
462 | } | |
463 | ||
464 | t.Run("ClockSequence", testNewV7ClockSequence) | |
465 | } | |
466 | ||
467 | func testNewV7InvalidPrecision(t *testing.T) { | |
468 | t.Run("NewV7", func(t *testing.T) { | |
469 | defer func() { | |
470 | if r := recover(); r == nil { | |
471 | t.Fatal("call did not panic") | |
472 | } | |
473 | }() | |
474 | ||
475 | NewV7(255) | |
476 | }) | |
477 | ||
478 | t.Run("getV7ClockSequence", func(t *testing.T) { | |
479 | defer func() { | |
480 | if r := recover(); r == nil { | |
481 | t.Fatal("expected panic did not occur") | |
482 | } | |
483 | }() | |
484 | ||
485 | g := NewGen() | |
486 | g.epochFunc = func() time.Time { | |
487 | return time.Unix(0, 0) | |
488 | } | |
489 | ||
490 | g.getV7ClockSequence(255) | |
491 | }) | |
492 | } | |
493 | ||
494 | func makeTestNewV7Basic(p Precision) func(t *testing.T) { | |
495 | return func(t *testing.T) { | |
496 | u, err := NewV7(p) | |
497 | if err != nil { | |
498 | t.Fatal(err) | |
499 | } | |
500 | if got, want := u.Version(), V7; got != want { | |
501 | t.Errorf("got version %d, want %d", got, want) | |
502 | } | |
503 | if got, want := u.Variant(), VariantRFC4122; got != want { | |
504 | t.Errorf("got variant %d, want %d", got, want) | |
505 | } | |
506 | } | |
507 | } | |
508 | ||
509 | func makeTestNewV7Basic10000000(p Precision) func(t *testing.T) { | |
510 | return func(t *testing.T) { | |
511 | if testing.Short() { | |
512 | t.Skip("skipping test in short mode.") | |
513 | } | |
514 | ||
515 | if p == MillisecondPrecision { | |
516 | t.Skip("skipping test, see: https://github.com/uuid6/uuid6-ietf-draft/issues/40") | |
517 | } | |
518 | ||
519 | g := NewGen() | |
520 | ||
521 | for i := 0; i < 10000000; i++ { | |
522 | u, err := g.NewV7(p) | |
523 | if err != nil { | |
524 | t.Fatal(err) | |
525 | } | |
526 | if got, want := u.Version(), V7; got != want { | |
527 | t.Errorf("got version %d, want %d", got, want) | |
528 | } | |
529 | if got, want := u.Variant(), VariantRFC4122; got != want { | |
530 | t.Errorf("got variant %d, want %d", got, want) | |
531 | } | |
532 | } | |
533 | } | |
534 | } | |
535 | ||
536 | func makeTestNewV7DifferentAcrossCalls(p Precision) func(t *testing.T) { | |
537 | return func(t *testing.T) { | |
538 | g := NewGen() | |
539 | ||
540 | u1, err := g.NewV7(p) | |
541 | if err != nil { | |
542 | t.Fatal(err) | |
543 | } | |
544 | u2, err := g.NewV7(p) | |
545 | if err != nil { | |
546 | t.Fatal(err) | |
547 | } | |
548 | if u1 == u2 { | |
549 | t.Errorf("generated identical UUIDs across calls: %v", u1) | |
550 | } | |
551 | } | |
552 | } | |
553 | ||
554 | func makeTestNewV7StaleEpoch(p Precision) func(t *testing.T) { | |
555 | return func(t *testing.T) { | |
556 | g := &Gen{ | |
557 | epochFunc: func() time.Time { | |
558 | return time.Unix(0, 0) | |
559 | }, | |
560 | rand: rand.Reader, | |
561 | } | |
562 | u1, err := g.NewV7(p) | |
563 | if err != nil { | |
564 | t.Fatal(err) | |
565 | } | |
566 | u2, err := g.NewV7(p) | |
567 | if err != nil { | |
568 | t.Fatal(err) | |
569 | } | |
570 | if u1 == u2 { | |
571 | t.Errorf("generated identical UUIDs across calls: %v", u1) | |
572 | } | |
573 | } | |
574 | } | |
575 | ||
576 | func makeTestNewV7FaultyRand(p Precision) func(t *testing.T) { | |
577 | return func(t *testing.T) { | |
578 | g := &Gen{ | |
579 | epochFunc: time.Now, | |
580 | rand: &faultyReader{ | |
581 | readToFail: 0, // fail immediately | |
582 | }, | |
583 | } | |
584 | u, err := g.NewV7(p) | |
585 | if err == nil { | |
586 | t.Errorf("got %v, nil error", u) | |
587 | } | |
588 | } | |
589 | } | |
590 | ||
591 | func makeTestNewV7ShortRandomRead(p Precision) func(t *testing.T) { | |
592 | return func(t *testing.T) { | |
593 | g := &Gen{ | |
594 | epochFunc: time.Now, | |
595 | rand: bytes.NewReader([]byte{42}), | |
596 | } | |
597 | u, err := g.NewV7(p) | |
598 | if err == nil { | |
599 | t.Errorf("got %v, nil error", u) | |
600 | } | |
601 | } | |
602 | } | |
603 | ||
604 | func makeTestNewV7KSortable(p Precision) func(t *testing.T) { | |
605 | return func(t *testing.T) { | |
606 | uuids := make([]UUID, 10) | |
607 | for i := range uuids { | |
608 | u, err := NewV7(p) | |
609 | testErrCheck(t, "NewV6()", "", err) | |
610 | ||
611 | uuids[i] = u | |
612 | ||
613 | time.Sleep(p.Duration()) | |
614 | } | |
615 | ||
616 | for i := 1; i < len(uuids); i++ { | |
617 | p, n := uuids[i-1], uuids[i] | |
618 | isLess := p.String() < n.String() | |
619 | if !isLess { | |
620 | t.Errorf("uuids[%d] (%s) not less than uuids[%d] (%s)", i-1, p, i, n) | |
621 | } | |
622 | } | |
623 | } | |
624 | } | |
625 | ||
626 | // to get 100% code coverage we need to do some glass box testing | |
627 | func makeTestNewV7ClockSequenceBehaviors(p Precision) func(t *testing.T) { | |
628 | return func(t *testing.T) { | |
629 | t.Run("TimeWarp", func(t *testing.T) { | |
630 | g := NewGen() | |
631 | tn := time.Now() | |
632 | unix := uint64(tn.Unix()) + 100 | |
633 | nsec := uint64(tn.Nanosecond()) | |
634 | ||
635 | g.v7LastTime = unix | |
636 | g.v7LastSubsec = nsec | |
637 | ||
638 | _, err := g.NewV7(p) | |
639 | testErrCheck(t, "g.NewV7()", "", err) | |
640 | ||
641 | if g.v7ClockSequence != 1 { | |
642 | t.Fatalf("g.v7ClockSequence = %d, want 1", g.v7ClockSequence) | |
643 | } | |
644 | }) | |
645 | ||
646 | t.Run("NominalTime", func(t *testing.T) { | |
647 | g := NewGen() | |
648 | g.v7ClockSequence = 100 | |
649 | ||
650 | tn := time.Now() | |
651 | unix := uint64(tn.Unix()) - 100 | |
652 | nsec := uint64(tn.Nanosecond()) | |
653 | ||
654 | g.v7LastTime = unix | |
655 | g.v7LastSubsec = nsec | |
656 | ||
657 | _, err := g.NewV7(p) | |
658 | testErrCheck(t, "g.NewV7()", "", err) | |
659 | ||
660 | if g.v7ClockSequence != 0 { | |
661 | t.Fatalf("g.v7ClockSequence = %d, want 0", g.v7ClockSequence) | |
662 | } | |
663 | }) | |
664 | ||
665 | t.Run("Overflow", func(t *testing.T) { | |
666 | if testing.Short() { | |
667 | t.Skip("skipping test in short mode.") | |
668 | } | |
669 | ||
670 | wantErrStr := fmt.Sprintf("generating %s precision UUIDv7s too fast: internal clock sequence would roll over", p.String()) | |
671 | ||
672 | g := NewGen() | |
673 | ||
674 | g.epochFunc = func() time.Time { | |
675 | return time.Unix(0, 0) | |
676 | } | |
677 | ||
678 | g.v7ClockSequence = maxSeq14 + 1 | |
679 | g.v7LastTime = uint64(g.epochFunc().Unix()) | |
680 | g.v7LastSubsec = uint64(g.epochFunc().Nanosecond()) | |
681 | ||
682 | _, err := g.NewV7(p) | |
683 | testErrCheck(t, "g.NewV7()", wantErrStr, err) | |
684 | }) | |
685 | } | |
686 | } | |
687 | ||
688 | func testNewV7ClockSequence(t *testing.T) { | |
689 | if testing.Short() { | |
690 | t.Skip("skipping test in short mode.") | |
691 | } | |
692 | ||
693 | g := NewGen() | |
694 | ||
695 | // hack to try and reduce race conditions based on when the test starts | |
696 | nsec := time.Now().Nanosecond() | |
697 | sleepDur := int(time.Second) - nsec | |
698 | time.Sleep(time.Duration(sleepDur)) | |
699 | ||
700 | u1, err := g.NewV7(MillisecondPrecision) | |
701 | if err != nil { | |
702 | t.Fatalf("failed to generate V7 UUID #1: %v", err) | |
703 | } | |
704 | ||
705 | u2, err := g.NewV7(MillisecondPrecision) | |
706 | if err != nil { | |
707 | t.Fatalf("failed to generate V7 UUID #2: %v", err) | |
708 | } | |
709 | ||
710 | time.Sleep(time.Millisecond) | |
711 | ||
712 | u3, err := g.NewV7(MillisecondPrecision) | |
713 | if err != nil { | |
714 | t.Fatalf("failed to generate V7 UUID #3: %v", err) | |
715 | } | |
716 | ||
717 | time.Sleep(time.Second) | |
718 | ||
719 | u4, err := g.NewV7(MillisecondPrecision) | |
720 | if err != nil { | |
721 | t.Fatalf("failed to generate V7 UUID #3: %v", err) | |
722 | } | |
723 | ||
724 | s1 := binary.BigEndian.Uint16(u1[6:8]) & 0xfff | |
725 | s2 := binary.BigEndian.Uint16(u2[6:8]) & 0xfff | |
726 | s3 := binary.BigEndian.Uint16(u3[6:8]) & 0xfff | |
727 | s4 := binary.BigEndian.Uint16(u4[6:8]) & 0xfff | |
728 | ||
729 | if s1 != 0 { | |
730 | t.Errorf("sequence 1 should be zero, was %d", s1) | |
731 | } | |
732 | ||
733 | if s2 != s1+1 { | |
734 | t.Errorf("sequence 2 expected to be one above sequence 1; seq 1: %d, seq 2: %d", s1, s2) | |
735 | } | |
736 | ||
737 | if s3 != 0 { | |
738 | t.Errorf("sequence 3 should be zero, was %d", s3) | |
739 | } | |
740 | ||
741 | if s4 != 0 { | |
742 | t.Errorf("sequence 4 should be zero, was %d", s4) | |
743 | } | |
744 | } | |
745 | ||
746 | func TestPrecision_String(t *testing.T) { | |
747 | tests := []struct { | |
748 | p Precision | |
749 | want string | |
750 | }{ | |
751 | { | |
752 | p: NanosecondPrecision, | |
753 | want: "nanosecond", | |
754 | }, | |
755 | { | |
756 | p: MillisecondPrecision, | |
757 | want: "millisecond", | |
758 | }, | |
759 | { | |
760 | p: MicrosecondPrecision, | |
761 | want: "microsecond", | |
762 | }, | |
763 | { | |
764 | p: 0xff, | |
765 | want: "unknown", | |
766 | }, | |
767 | } | |
768 | ||
769 | for _, tt := range tests { | |
770 | t.Run(tt.want, func(t *testing.T) { | |
771 | if got := tt.p.String(); got != tt.want { | |
772 | t.Errorf("got = %s, want %s", got, tt.want) | |
773 | } | |
774 | }) | |
775 | } | |
776 | } | |
777 | ||
778 | func TestPrecision_Duration(t *testing.T) { | |
779 | tests := []struct { | |
780 | p Precision | |
781 | want time.Duration | |
782 | }{ | |
783 | { | |
784 | p: NanosecondPrecision, | |
785 | want: time.Nanosecond, | |
786 | }, | |
787 | { | |
788 | p: MillisecondPrecision, | |
789 | want: time.Millisecond, | |
790 | }, | |
791 | { | |
792 | p: MicrosecondPrecision, | |
793 | want: time.Microsecond, | |
794 | }, | |
795 | { | |
796 | p: 0xff, | |
797 | want: 0, | |
798 | }, | |
799 | } | |
800 | ||
801 | for _, tt := range tests { | |
802 | t.Run(tt.p.String(), func(t *testing.T) { | |
803 | if got := tt.p.Duration(); got != tt.want { | |
804 | t.Errorf("got = %s, want %s", got, tt.want) | |
805 | } | |
806 | }) | |
807 | } | |
808 | } | |
809 | ||
378 | 810 | func BenchmarkGenerator(b *testing.B) { |
379 | 811 | b.Run("NewV1", func(b *testing.B) { |
380 | 812 | for i := 0; i < b.N; i++ { |
381 | 813 | NewV1() |
382 | 814 | } |
383 | 815 | }) |
384 | b.Run("NewV2", func(b *testing.B) { | |
385 | for i := 0; i < b.N; i++ { | |
386 | NewV2(DomainOrg) | |
387 | } | |
388 | }) | |
389 | 816 | b.Run("NewV3", func(b *testing.B) { |
390 | 817 | for i := 0; i < b.N; i++ { |
391 | 818 | NewV3(NamespaceDNS, "www.example.com") |
415 | 842 | } |
416 | 843 | return rand.Read(dest) |
417 | 844 | } |
845 | ||
846 | // testErrCheck looks to see if errContains is a substring of err.Error(). If | |
847 | // not, this calls t.Fatal(). It also calls t.Fatal() if there was an error, but | |
848 | // errContains is empty. Returns true if you should continue running the test, | |
849 | // or false if you should stop the test. | |
850 | func testErrCheck(t *testing.T, name string, errContains string, err error) bool { | |
851 | t.Helper() | |
852 | ||
853 | if len(errContains) > 0 { | |
854 | if err == nil { | |
855 | t.Fatalf("%s error = <nil>, should contain %q", name, errContains) | |
856 | return false | |
857 | } | |
858 | ||
859 | if errStr := err.Error(); !strings.Contains(errStr, errContains) { | |
860 | t.Fatalf("%s error = %q, should contain %q", name, errStr, errContains) | |
861 | return false | |
862 | } | |
863 | ||
864 | return false | |
865 | } | |
866 | ||
867 | if err != nil && len(errContains) == 0 { | |
868 | t.Fatalf("%s unexpected error: %v", name, err) | |
869 | return false | |
870 | } | |
871 | ||
872 | return true | |
873 | } |
18 | 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
19 | 19 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
20 | 20 | |
21 | // Package uuid provides implementations of the Universally Unique Identifier (UUID), as specified in RFC-4122 and DCE 1.1. | |
22 | // | |
23 | // RFC-4122[1] provides the specification for versions 1, 3, 4, and 5. | |
24 | // | |
25 | // DCE 1.1[2] provides the specification for version 2. | |
21 | // Package uuid provides implementations of the Universally Unique Identifier | |
22 | // (UUID), as specified in RFC-4122 and the Peabody RFC Draft (revision 02). | |
23 | // | |
24 | // RFC-4122[1] provides the specification for versions 1, 3, 4, and 5. The | |
25 | // Peabody UUID RFC Draft[2] provides the specification for the new k-sortable | |
26 | // UUIDs, versions 6 and 7. | |
27 | // | |
28 | // DCE 1.1[3] provides the specification for version 2, but version 2 support | |
29 | // was removed from this package in v4 due to some concerns with the | |
30 | // specification itself. Reading the spec, it seems that it would result in | |
31 | // generating UUIDs that aren't very unique. In having read the spec it seemed | |
32 | // that our implementation did not meet the spec. It also seems to be at-odds | |
33 | // with RFC 4122, meaning we would need quite a bit of special code to support | |
34 | // it. Lastly, there were no Version 2 implementations that we could find to | |
35 | // ensure we were understanding the specification correctly. | |
26 | 36 | // |
27 | 37 | // [1] https://tools.ietf.org/html/rfc4122 |
28 | // [2] http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 | |
38 | // [2] https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02 | |
39 | // [3] http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 | |
29 | 40 | package uuid |
30 | 41 | |
31 | 42 | import ( |
32 | 43 | "encoding/binary" |
33 | 44 | "encoding/hex" |
34 | 45 | "fmt" |
46 | "io" | |
47 | "strings" | |
35 | 48 | "time" |
36 | 49 | ) |
37 | 50 | |
45 | 58 | const ( |
46 | 59 | _ byte = iota |
47 | 60 | V1 // Version 1 (date-time and MAC address) |
48 | V2 // Version 2 (date-time and MAC address, DCE security version) | |
61 | _ // Version 2 (date-time and MAC address, DCE security version) [removed] | |
49 | 62 | V3 // Version 3 (namespace name-based) |
50 | 63 | V4 // Version 4 (random) |
51 | 64 | V5 // Version 5 (namespace name-based) |
65 | V6 // Version 6 (k-sortable timestamp and random data) [peabody draft] | |
66 | V7 // Version 7 (k-sortable timestamp, with configurable precision, and random data) [peabody draft] | |
67 | _ // Version 8 (k-sortable timestamp, meant for custom implementations) [peabody draft] [not implemented] | |
52 | 68 | ) |
53 | 69 | |
54 | 70 | // UUID layout variants. |
67 | 83 | ) |
68 | 84 | |
69 | 85 | // Timestamp is the count of 100-nanosecond intervals since 00:00:00.00, |
70 | // 15 October 1582 within a V1 UUID. This type has no meaning for V2-V5 | |
71 | // UUIDs since they don't have an embedded timestamp. | |
86 | // 15 October 1582 within a V1 UUID. This type has no meaning for other | |
87 | // UUID versions since they don't have an embedded timestamp. | |
72 | 88 | type Timestamp uint64 |
73 | 89 | |
74 | 90 | const _100nsPerSecond = 10000000 |
77 | 93 | func (t Timestamp) Time() (time.Time, error) { |
78 | 94 | secs := uint64(t) / _100nsPerSecond |
79 | 95 | nsecs := 100 * (uint64(t) % _100nsPerSecond) |
96 | ||
80 | 97 | return time.Unix(int64(secs)-(epochStart/_100nsPerSecond), int64(nsecs)), nil |
81 | 98 | } |
82 | 99 | |
87 | 104 | err := fmt.Errorf("uuid: %s is version %d, not version 1", u, u.Version()) |
88 | 105 | return 0, err |
89 | 106 | } |
107 | ||
90 | 108 | low := binary.BigEndian.Uint32(u[0:4]) |
91 | 109 | mid := binary.BigEndian.Uint16(u[4:6]) |
92 | 110 | hi := binary.BigEndian.Uint16(u[6:8]) & 0xfff |
111 | ||
93 | 112 | return Timestamp(uint64(low) + (uint64(mid) << 32) + (uint64(hi) << 48)), nil |
113 | } | |
114 | ||
115 | // TimestampFromV6 returns the Timestamp embedded within a V6 UUID. This | |
116 | // function returns an error if the UUID is any version other than 6. | |
117 | // | |
118 | // This is implemented based on revision 01 of the Peabody UUID draft, and may | |
119 | // be subject to change pending further revisions. Until the final specification | |
120 | // revision is finished, changes required to implement updates to the spec will | |
121 | // not be considered a breaking change. They will happen as a minor version | |
122 | // releases until the spec is final. | |
123 | func TimestampFromV6(u UUID) (Timestamp, error) { | |
124 | if u.Version() != 6 { | |
125 | return 0, fmt.Errorf("uuid: %s is version %d, not version 6", u, u.Version()) | |
126 | } | |
127 | ||
128 | hi := binary.BigEndian.Uint32(u[0:4]) | |
129 | mid := binary.BigEndian.Uint16(u[4:6]) | |
130 | low := binary.BigEndian.Uint16(u[6:8]) & 0xfff | |
131 | ||
132 | return Timestamp(uint64(low) + (uint64(mid) << 12) + (uint64(hi) << 28)), nil | |
94 | 133 | } |
95 | 134 | |
96 | 135 | // String parse helpers. |
155 | 194 | return string(buf) |
156 | 195 | } |
157 | 196 | |
197 | // Format implements fmt.Formatter for UUID values. | |
198 | // | |
199 | // The behavior is as follows: | |
200 | // The 'x' and 'X' verbs output only the hex digits of the UUID, using a-f for 'x' and A-F for 'X'. | |
201 | // The 'v', '+v', 's' and 'q' verbs return the canonical RFC-4122 string representation. | |
202 | // The 'S' verb returns the RFC-4122 format, but with capital hex digits. | |
203 | // The '#v' verb returns the "Go syntax" representation, which is a 16 byte array initializer. | |
204 | // All other verbs not handled directly by the fmt package (like '%p') are unsupported and will return | |
205 | // "%!verb(uuid.UUID=value)" as recommended by the fmt package. | |
206 | func (u UUID) Format(f fmt.State, c rune) { | |
207 | switch c { | |
208 | case 'x', 'X': | |
209 | s := hex.EncodeToString(u.Bytes()) | |
210 | if c == 'X' { | |
211 | s = strings.Map(toCapitalHexDigits, s) | |
212 | } | |
213 | _, _ = io.WriteString(f, s) | |
214 | case 'v': | |
215 | var s string | |
216 | if f.Flag('#') { | |
217 | s = fmt.Sprintf("%#v", [Size]byte(u)) | |
218 | } else { | |
219 | s = u.String() | |
220 | } | |
221 | _, _ = io.WriteString(f, s) | |
222 | case 's', 'S': | |
223 | s := u.String() | |
224 | if c == 'S' { | |
225 | s = strings.Map(toCapitalHexDigits, s) | |
226 | } | |
227 | _, _ = io.WriteString(f, s) | |
228 | case 'q': | |
229 | _, _ = io.WriteString(f, `"`+u.String()+`"`) | |
230 | default: | |
231 | // invalid/unsupported format verb | |
232 | fmt.Fprintf(f, "%%!%c(uuid.UUID=%s)", c, u.String()) | |
233 | } | |
234 | } | |
235 | ||
236 | func toCapitalHexDigits(ch rune) rune { | |
237 | // convert a-f hex digits to A-F | |
238 | switch ch { | |
239 | case 'a': | |
240 | return 'A' | |
241 | case 'b': | |
242 | return 'B' | |
243 | case 'c': | |
244 | return 'C' | |
245 | case 'd': | |
246 | return 'D' | |
247 | case 'e': | |
248 | return 'E' | |
249 | case 'f': | |
250 | return 'F' | |
251 | default: | |
252 | return ch | |
253 | } | |
254 | } | |
255 | ||
158 | 256 | // SetVersion sets the version bits. |
159 | 257 | func (u *UUID) SetVersion(v byte) { |
160 | 258 | u[6] = (u[6] & 0x0f) | (v << 4) |
34 | 34 | t.Run("Variant", testUUIDVariant) |
35 | 35 | t.Run("SetVersion", testUUIDSetVersion) |
36 | 36 | t.Run("SetVariant", testUUIDSetVariant) |
37 | t.Run("Format", testUUIDFormat) | |
37 | 38 | } |
38 | 39 | |
39 | 40 | func testUUIDBytes(t *testing.T) { |
109 | 110 | u.SetVariant(want) |
110 | 111 | if got := u.Variant(); got != want { |
111 | 112 | t.Errorf("%v.Variant() == %d after SetVariant(%d)", u, got, want) |
113 | } | |
114 | } | |
115 | } | |
116 | ||
117 | func testUUIDFormat(t *testing.T) { | |
118 | val := Must(FromString("12345678-90ab-cdef-1234-567890abcdef")) | |
119 | tests := []struct { | |
120 | u UUID | |
121 | f string | |
122 | want string | |
123 | }{ | |
124 | {u: val, f: "%s", want: "12345678-90ab-cdef-1234-567890abcdef"}, | |
125 | {u: val, f: "%S", want: "12345678-90AB-CDEF-1234-567890ABCDEF"}, | |
126 | {u: val, f: "%q", want: `"12345678-90ab-cdef-1234-567890abcdef"`}, | |
127 | {u: val, f: "%x", want: "1234567890abcdef1234567890abcdef"}, | |
128 | {u: val, f: "%X", want: "1234567890ABCDEF1234567890ABCDEF"}, | |
129 | {u: val, f: "%v", want: "12345678-90ab-cdef-1234-567890abcdef"}, | |
130 | {u: val, f: "%+v", want: "12345678-90ab-cdef-1234-567890abcdef"}, | |
131 | {u: val, f: "%#v", want: "[16]uint8{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}"}, | |
132 | {u: val, f: "%T", want: "uuid.UUID"}, | |
133 | {u: val, f: "%t", want: "%!t(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
134 | {u: val, f: "%b", want: "%!b(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
135 | {u: val, f: "%c", want: "%!c(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
136 | {u: val, f: "%d", want: "%!d(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
137 | {u: val, f: "%e", want: "%!e(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
138 | {u: val, f: "%E", want: "%!E(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
139 | {u: val, f: "%f", want: "%!f(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
140 | {u: val, f: "%F", want: "%!F(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
141 | {u: val, f: "%g", want: "%!g(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
142 | {u: val, f: "%G", want: "%!G(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
143 | {u: val, f: "%o", want: "%!o(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
144 | {u: val, f: "%U", want: "%!U(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, | |
145 | } | |
146 | for _, tt := range tests { | |
147 | got := fmt.Sprintf(tt.f, tt.u) | |
148 | if tt.want != got { | |
149 | t.Errorf(`Format("%s") got %s, want %s`, tt.f, got, tt.want) | |
112 | 150 | } |
113 | 151 | } |
114 | 152 | } |
180 | 218 | } |
181 | 219 | } |
182 | 220 | } |
221 | ||
222 | func TestTimestampFromV6(t *testing.T) { | |
223 | tests := []struct { | |
224 | u UUID | |
225 | want Timestamp | |
226 | wanterr bool | |
227 | }{ | |
228 | {u: Must(NewV1()), wanterr: true}, | |
229 | {u: Must(FromString("00000000-0000-6000-0000-000000000000")), want: 0}, | |
230 | {u: Must(FromString("1ec06cff-e9b1-621c-8627-ba3fd7e551c9")), want: 138493178941215260}, | |
231 | {u: Must(FromString("ffffffff-ffff-6fff-ffff-ffffffffffff")), want: Timestamp(1<<60 - 1)}, | |
232 | } | |
233 | ||
234 | for _, tt := range tests { | |
235 | got, err := TimestampFromV6(tt.u) | |
236 | ||
237 | switch { | |
238 | case tt.wanterr && err == nil: | |
239 | t.Errorf("TimestampFromV6(%v) want error, got %v", tt.u, got) | |
240 | ||
241 | case tt.want != got: | |
242 | t.Errorf("TimestampFromV6(%v) got %v, want %v", tt.u, got, tt.want) | |
243 | } | |
244 | } | |
245 | } |