diff --git a/.github/workflows/apidiff.yaml b/.github/workflows/apidiff.yaml index ca2e787..37c0b5f 100644 --- a/.github/workflows/apidiff.yaml +++ b/.github/workflows/apidiff.yaml @@ -4,6 +4,8 @@ on: pull_request: branches: - master +permissions: + contents: read jobs: compat: runs-on: ubuntu-latest diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 39d1811..138397c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -4,6 +4,8 @@ on: pull_request: branches: - master +permissions: + contents: read jobs: unit-tests: strategy: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dc68c6..7ec5ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,41 @@ # Changelog +## [1.6.0](https://github.com/google/uuid/compare/v1.5.0...v1.6.0) (2024-01-16) + + +### Features + +* add Max UUID constant ([#149](https://github.com/google/uuid/issues/149)) ([c58770e](https://github.com/google/uuid/commit/c58770eb495f55fe2ced6284f93c5158a62e53e3)) + + +### Bug Fixes + +* fix typo in version 7 uuid documentation ([#153](https://github.com/google/uuid/issues/153)) ([016b199](https://github.com/google/uuid/commit/016b199544692f745ffc8867b914129ecb47ef06)) +* Monotonicity in UUIDv7 ([#150](https://github.com/google/uuid/issues/150)) ([a2b2b32](https://github.com/google/uuid/commit/a2b2b32373ff0b1a312b7fdf6d38a977099698a6)) + +## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12) + + +### Features + +* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29)) + +## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26) + + +### Features + +* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4)) + +### Fixes + +* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior) + +## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18) + + +### Bug Fixes + +* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0)) + +## Changelog diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5566888..a502fdc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ please explain why in the pull request description. ### Releasing -Commits that would precipitate a SemVer change, as desrcibed in the Conventional +Commits that would precipitate a SemVer change, as described in the Conventional Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action) to create a release candidate pull request. Once submitted, `release-please` will create a release. diff --git a/README.md b/README.md index 3e9a618..a63ef61 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,4 @@ go get github.com/google/uuid Full `go doc` style documentation for the package can be viewed online without installing this package by using the GoDoc site here: -http://pkg.go.dev/github.com/google/uuid +https://pkg.go.dev/github.com/google/uuid diff --git a/hash.go b/hash.go index be1c27d..35338cd 100644 --- a/hash.go +++ b/hash.go @@ -17,6 +17,12 @@ var ( NameSpaceOID = MustParse("6ba7b812-9dad-11d1-80b4-00c04fd430c8") NameSpaceX500 = MustParse("6ba7b814-9dad-11d1-80b4-00c04fd430c8") Nil UUID // empty UUID, all zeros + + // The Max UUID is special form of UUID that is specified to have all 128 bits set to 1. + Max = UUID{ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + } ) // NewHash returns a new UUID derived from the hash of space concatenated with diff --git a/json_test.go b/json_test.go index 4140cee..db2d1cc 100644 --- a/json_test.go +++ b/json_test.go @@ -31,6 +31,57 @@ func TestJSON(t *testing.T) { } } +func TestJSONUnmarshal(t *testing.T) { + type S struct { + ID1 UUID + ID2 UUID `json:"ID2,omitempty"` + } + + testCases := map[string]struct { + data []byte + expectedError error + expectedResult UUID + }{ + "success": { + data: []byte(`{"ID1": "f47ac10b-58cc-0372-8567-0e02b2c3d479"}`), + expectedError: nil, + expectedResult: testUUID, + }, + "zero": { + data: []byte(`{"ID1": "00000000-0000-0000-0000-000000000000"}`), + expectedError: nil, + expectedResult: Nil, + }, + "null": { + data: []byte(`{"ID1": null}`), + expectedError: nil, + expectedResult: Nil, + }, + "empty": { + data: []byte(`{"ID1": ""}`), + expectedError: invalidLengthError{len: 0}, + expectedResult: Nil, + }, + "omitempty": { + data: []byte(`{"ID2": ""}`), + expectedError: invalidLengthError{len: 0}, + expectedResult: Nil, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + var s S + if err := json.Unmarshal(tc.data, &s); err != tc.expectedError { + t.Errorf("unexpected error: got %v, want %v", err, tc.expectedError) + } + if !reflect.DeepEqual(s.ID1, tc.expectedResult) { + t.Errorf("got %#v, want %#v", s.ID1, tc.expectedResult) + } + }) + } +} + func BenchmarkUUID_MarshalJSON(b *testing.B) { x := &struct { UUID UUID `json:"uuid"` diff --git a/null_test.go b/null_test.go index c6e5e69..fe0fe8d 100644 --- a/null_test.go +++ b/null_test.go @@ -205,10 +205,10 @@ func TestNullUUIDUnmarshalJSON(t *testing.T) { var nu NullUUID err := json.Unmarshal(jsonNull, &nu) if err != nil || nu.Valid { - t.Errorf("expected nil when unmarshaling null, got %s", err) + t.Errorf("expected nil when unmarshalling null, got %s", err) } err = json.Unmarshal(jsonUUID, &nu) if err != nil || !nu.Valid { - t.Errorf("expected nil when unmarshaling null, got %s", err) + t.Errorf("expected nil when unmarshalling null, got %s", err) } } diff --git a/time.go b/time.go index e6ef06c..c351129 100644 --- a/time.go +++ b/time.go @@ -108,12 +108,23 @@ func setClockSequence(seq int) { } // Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in -// uuid. The time is only defined for version 1 and 2 UUIDs. +// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs. func (uuid UUID) Time() Time { - time := int64(binary.BigEndian.Uint32(uuid[0:4])) - time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 - time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 - return Time(time) + var t Time + switch uuid.Version() { + case 6: + time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110 + t = Time(time) + case 7: + time := binary.BigEndian.Uint64(uuid[:8]) + t = Time((time>>16)*10000 + g1582ns100) + default: // forward compatible + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + t = Time(time) + } + return t } // ClockSequence returns the clock sequence encoded in uuid. diff --git a/uuid.go b/uuid.go index a56138c..5232b48 100644 --- a/uuid.go +++ b/uuid.go @@ -56,11 +56,15 @@ func IsInvalidLengthError(err error) bool { return ok } -// Parse decodes s into a UUID or returns an error. Both the standard UUID -// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and -// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the -// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex -// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. +// Parse decodes s into a UUID or returns an error if it cannot be parsed. Both +// the standard UUID forms defined in RFC 4122 +// (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) are decoded. In addition, +// Parse accepts non-standard strings such as the raw hex encoding +// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and 38 byte "Microsoft style" encodings, +// e.g. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. Only the middle 36 bytes are +// examined in the latter case. Parse should not be used to validate strings as +// it parses non-standard encodings as indicated above. func Parse(s string) (UUID, error) { var uuid UUID switch len(s) { @@ -182,6 +186,59 @@ func Must(uuid UUID, err error) UUID { return uuid } +// Validate returns an error if s is not a properly formatted UUID in one of the following formats: +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} +// It returns an error if the format is invalid, otherwise nil. +func Validate(s string) error { + switch len(s) { + // Standard UUID format + case 36: + + // UUID with "urn:uuid:" prefix + case 36 + 9: + if !strings.EqualFold(s[:9], "urn:uuid:") { + return fmt.Errorf("invalid urn prefix: %q", s[:9]) + } + s = s[9:] + + // UUID enclosed in braces + case 36 + 2: + if s[0] != '{' || s[len(s)-1] != '}' { + return fmt.Errorf("invalid bracketed UUID format") + } + s = s[1 : len(s)-1] + + // UUID without hyphens + case 32: + for i := 0; i < len(s); i += 2 { + _, ok := xtob(s[i], s[i+1]) + if !ok { + return errors.New("invalid UUID format") + } + } + + default: + return invalidLengthError{len(s)} + } + + // Check for standard UUID format + if len(s) == 36 { + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return errors.New("invalid UUID format") + } + for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} { + if _, ok := xtob(s[x], s[x+1]); !ok { + return errors.New("invalid UUID format") + } + } + } + + return nil +} + // String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx // , or "" if uuid is invalid. func (uuid UUID) String() string { @@ -294,3 +351,15 @@ func DisableRandPool() { poolMu.Lock() poolPos = randPoolSize } + +// UUIDs is a slice of UUID types. +type UUIDs []UUID + +// Strings returns a string slice containing the string form of each UUID in uuids. +func (uuids UUIDs) Strings() []string { + var uuidStrs = make([]string, len(uuids)) + for i, uuid := range uuids { + uuidStrs[i] = uuid.String() + } + return uuidStrs +} diff --git a/uuid_test.go b/uuid_test.go index e98d0fe..c1c6001 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -6,6 +6,7 @@ package uuid import ( "bytes" + "errors" "fmt" "os" "runtime" @@ -73,6 +74,9 @@ var tests = []test{ {"f47ac10b58cc037285670e02b2c3d479", 0, RFC4122, true}, {"f47ac10b58cc037285670e02b2c3d4790", 0, Invalid, false}, {"f47ac10b58cc037285670e02b2c3d47", 0, Invalid, false}, + + {"01ee836c-e7c9-619d-929a-525400475911", 6, RFC4122, true}, + {"018bd12c-58b0-7683-8a5b-8752d0e86651", 7, RFC4122, true}, } var constants = []struct { @@ -569,6 +573,67 @@ func TestIsWrongLength(t *testing.T) { } } +func FuzzParse(f *testing.F) { + for _, tt := range tests { + f.Add(tt.in) + f.Add(strings.ToUpper(tt.in)) + } + f.Fuzz(func(t *testing.T, in string) { + Parse(in) + }) +} + +func FuzzParseBytes(f *testing.F) { + for _, tt := range tests { + f.Add([]byte(tt.in)) + } + f.Fuzz(func(t *testing.T, in []byte) { + ParseBytes(in) + }) +} + +func FuzzFromBytes(f *testing.F) { + // Copied from TestFromBytes. + f.Add([]byte{ + 0x7d, 0x44, 0x48, 0x40, + 0x9d, 0xc0, + 0x11, 0xd1, + 0xb2, 0x45, + 0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2, + }) + f.Fuzz(func(t *testing.T, in []byte) { + FromBytes(in) + }) +} + +// TestValidate checks various scenarios for the Validate function +func TestValidate(t *testing.T) { + testCases := []struct { + name string + input string + expect error + }{ + {"Valid UUID", "123e4567-e89b-12d3-a456-426655440000", nil}, + {"Valid UUID with URN", "urn:uuid:123e4567-e89b-12d3-a456-426655440000", nil}, + {"Valid UUID with Braces", "{123e4567-e89b-12d3-a456-426655440000}", nil}, + {"Valid UUID No Hyphens", "123e4567e89b12d3a456426655440000", nil}, + {"Invalid UUID", "invalid-uuid", errors.New("invalid UUID length: 12")}, + {"Invalid Length", "123", fmt.Errorf("invalid UUID length: %d", len("123"))}, + {"Invalid URN Prefix", "urn:test:123e4567-e89b-12d3-a456-426655440000", fmt.Errorf("invalid urn prefix: %q", "urn:test:")}, + {"Invalid Brackets", "[123e4567-e89b-12d3-a456-426655440000]", fmt.Errorf("invalid bracketed UUID format")}, + {"Invalid UUID Format", "12345678gabc1234abcd1234abcd1234", fmt.Errorf("invalid UUID format")}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := Validate(tc.input) + if (err != nil) != (tc.expect != nil) || (err != nil && err.Error() != tc.expect.Error()) { + t.Errorf("Validate(%q) = %v, want %v", tc.input, err, tc.expect) + } + }) + } +} + var asString = "f47ac10b-58cc-0372-8567-0e02b2c3d479" var asBytes = []byte(asString) @@ -700,3 +765,166 @@ func BenchmarkUUID_NewPooled(b *testing.B) { } }) } + +func BenchmarkUUIDs_Strings(b *testing.B) { + uuid1, err := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if err != nil { + b.Fatal(err) + } + uuid2, err := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2") + if err != nil { + b.Fatal(err) + } + uuids := UUIDs{uuid1, uuid2} + for i := 0; i < b.N; i++ { + uuids.Strings() + } +} + +func TestVersion6(t *testing.T) { + uuid1, err := NewV6() + if err != nil { + t.Fatalf("could not create UUID: %v", err) + } + uuid2, err := NewV6() + if err != nil { + t.Fatalf("could not create UUID: %v", err) + } + + if uuid1 == uuid2 { + t.Errorf("%s:duplicate uuid", uuid1) + } + if v := uuid1.Version(); v != 6 { + t.Errorf("%s: version %s expected 6", uuid1, v) + } + if v := uuid2.Version(); v != 6 { + t.Errorf("%s: version %s expected 6", uuid2, v) + } + n1 := uuid1.NodeID() + n2 := uuid2.NodeID() + if !bytes.Equal(n1, n2) { + t.Errorf("Different nodes %x != %x", n1, n2) + } + t1 := uuid1.Time() + t2 := uuid2.Time() + q1 := uuid1.ClockSequence() + q2 := uuid2.ClockSequence() + + switch { + case t1 == t2 && q1 == q2: + t.Error("time stopped") + case t1 > t2 && q1 == q2: + t.Error("time reversed") + case t1 < t2 && q1 != q2: + t.Error("clock sequence changed unexpectedly") + } +} + +// uuid v7 time is only unix milliseconds, so +// uuid1.Time() == uuid2.Time() is right, but uuid1 must != uuid2 +func TestVersion7(t *testing.T) { + SetRand(nil) + m := make(map[string]bool) + for x := 1; x < 128; x++ { + uuid, err := NewV7() + if err != nil { + t.Fatalf("could not create UUID: %v", err) + } + s := uuid.String() + if m[s] { + t.Errorf("NewV7 returned duplicated UUID %s", s) + } + m[s] = true + if v := uuid.Version(); v != 7 { + t.Errorf("UUID of version %s", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("UUID is variant %d", uuid.Variant()) + } + } +} + +// uuid v7 time is only unix milliseconds, so +// uuid1.Time() == uuid2.Time() is right, but uuid1 must != uuid2 +func TestVersion7_pooled(t *testing.T) { + SetRand(nil) + EnableRandPool() + defer DisableRandPool() + + m := make(map[string]bool) + for x := 1; x < 128; x++ { + uuid, err := NewV7() + if err != nil { + t.Fatalf("could not create UUID: %v", err) + } + s := uuid.String() + if m[s] { + t.Errorf("NewV7 returned duplicated UUID %s", s) + } + m[s] = true + if v := uuid.Version(); v != 7 { + t.Errorf("UUID of version %s", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("UUID is variant %d", uuid.Variant()) + } + } +} + +func TestVersion7FromReader(t *testing.T) { + myString := "8059ddhdle77cb52" + r := bytes.NewReader([]byte(myString)) + _, err := NewV7FromReader(r) + if err != nil { + t.Errorf("failed generating UUID from a reader") + } + _, err = NewV7FromReader(r) + if err == nil { + t.Errorf("expecting an error as reader has no more bytes. Got uuid. NewV7FromReader may not be using the provided reader") + } +} + +func TestVersion7Monotonicity(t *testing.T) { + length := 10000 + u1 := Must(NewV7()).String() + for i := 0; i < length; i++ { + u2 := Must(NewV7()).String() + if u2 <= u1 { + t.Errorf("monotonicity failed at #%d: %s(next) < %s(before)", i, u2, u1) + break + } + u1 = u2 + } +} + +type fakeRand struct{} + +func (g fakeRand) Read(bs []byte) (int, error) { + for i, _ := range bs { + bs[i] = 0x88 + } + return len(bs), nil +} + +func TestVersion7MonotonicityStrict(t *testing.T) { + timeNow = func() time.Time { + return time.Date(2008, 8, 8, 8, 8, 8, 8, time.UTC) + } + defer func() { + timeNow = time.Now + }() + + SetRand(fakeRand{}) + defer SetRand(nil) + + length := 100000 // > 3906 + u1 := Must(NewV7()).String() + for i := 0; i < length; i++ { + u2 := Must(NewV7()).String() + if u2 <= u1 { + t.Errorf("monotonicity failed at #%d: %s(next) < %s(before)", i, u2, u1) + break + } + u1 = u2 + } +} diff --git a/version6.go b/version6.go new file mode 100644 index 0000000..339a959 --- /dev/null +++ b/version6.go @@ -0,0 +1,56 @@ +// Copyright 2023 Google Inc. All rights reserved. +// 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" + +// UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality. +// It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs. +// Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead. +// +// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#uuidv6 +// +// 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 set NodeID is random bits automatically . If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewV6 returns Nil and an error. +func NewV6() (UUID, error) { + var uuid UUID + now, seq, err := GetTime() + if err != nil { + return uuid, err + } + + /* + 0 1 2 3 + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | time_high | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | time_mid | time_low_and_version | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |clk_seq_hi_res | clk_seq_low | node (0-1) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | node (2-5) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + 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 +} diff --git a/version7.go b/version7.go new file mode 100644 index 0000000..0f5d994 --- /dev/null +++ b/version7.go @@ -0,0 +1,104 @@ +// Copyright 2023 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "io" +) + +// UUID version 7 features a time-ordered value field derived from the widely +// implemented and well known Unix Epoch timestamp source, +// the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded. +// As well as improved entropy characteristics over versions 1 or 6. +// +// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#name-uuid-version-7 +// +// Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible. +// +// NewV7 returns a Version 7 UUID based on the current time(Unix Epoch). +// Uses the randomness pool if it was enabled with EnableRandPool. +// On error, NewV7 returns Nil and an error +func NewV7() (UUID, error) { + uuid, err := NewRandom() + if err != nil { + return uuid, err + } + makeV7(uuid[:]) + return uuid, nil +} + +// NewV7FromReader returns a Version 7 UUID based on the current time(Unix Epoch). +// it use NewRandomFromReader fill random bits. +// On error, NewV7FromReader returns Nil and an error. +func NewV7FromReader(r io.Reader) (UUID, error) { + uuid, err := NewRandomFromReader(r) + if err != nil { + return uuid, err + } + + makeV7(uuid[:]) + return uuid, nil +} + +// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6]) +// uuid[8] already has the right version number (Variant is 10) +// see function NewV7 and NewV7FromReader +func makeV7(uuid []byte) { + /* + 0 1 2 3 + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | unix_ts_ms | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | unix_ts_ms | ver | rand_a (12 bit seq) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |var| rand_b | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | rand_b | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + _ = uuid[15] // bounds check + + t, s := getV7Time() + + uuid[0] = byte(t >> 40) + uuid[1] = byte(t >> 32) + uuid[2] = byte(t >> 24) + uuid[3] = byte(t >> 16) + uuid[4] = byte(t >> 8) + uuid[5] = byte(t) + + uuid[6] = 0x70 | (0x0F & byte(s>>8)) + uuid[7] = byte(s) +} + +// lastV7time is the last time we returned stored as: +// +// 52 bits of time in milliseconds since epoch +// 12 bits of (fractional nanoseconds) >> 8 +var lastV7time int64 + +const nanoPerMilli = 1000000 + +// getV7Time returns the time in milliseconds and nanoseconds / 256. +// The returned (milli << 12 + seq) is guaranteed to be greater than +// (milli << 12 + seq) returned by any previous call to getV7Time. +func getV7Time() (milli, seq int64) { + timeMu.Lock() + defer timeMu.Unlock() + + nano := timeNow().UnixNano() + milli = nano / nanoPerMilli + // Sequence number is between 0 and 3906 (nanoPerMilli>>8) + seq = (nano - milli*nanoPerMilli) >> 8 + now := milli<<12 + seq + if now <= lastV7time { + now = lastV7time + 1 + milli = now >> 12 + seq = now & 0xfff + } + lastV7time = now + return milli, seq +}