├── .github ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── dependency-review.yml │ ├── go.yml │ └── scorecards.yml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── SECURITY.md ├── codec.go ├── codec_test.go ├── error.go ├── error_test.go ├── generator.go ├── generator_test.go ├── go.mod ├── sql.go ├── sql_test.go ├── uuid.go └── uuid_test.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | groups: 8 | all: 9 | patterns: 10 | - "*" # Group all updates into a single larger pull request. 11 | 12 | - package-ecosystem: gomod 13 | directory: / 14 | schedule: 15 | interval: weekly 16 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["master"] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ["master"] 20 | schedule: 21 | - cron: "0 0 * * 1" 22 | 23 | permissions: 24 | contents: read 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | language: ["go"] 39 | # CodeQL supports [ $supported-codeql-languages ] 40 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 41 | 42 | steps: 43 | - name: Checkout repository 44 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 45 | 46 | # Initializes the CodeQL tools for scanning. 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 49 | with: 50 | languages: ${{ matrix.language }} 51 | # If you wish to specify custom queries, you can do so here or in a config file. 52 | # By default, queries listed here will override any specified in a config file. 53 | # Prefix the list here with "+" to use these queries and those in the config file. 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below) 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 62 | 63 | # If the Autobuild fails above, remove it and uncomment the following three lines. 64 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 65 | 66 | # - run: | 67 | # echo "Run, Build Application using script" 68 | # ./location_of_script_within_repo/buildscript.sh 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 72 | with: 73 | category: "/language:${{matrix.language}}" 74 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, 4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR. 5 | # Once installed, if the workflow run is marked as required, 6 | # PRs introducing known-vulnerable packages will be blocked from merging. 7 | # 8 | # Source repository: https://github.com/actions/dependency-review-action 9 | name: 'Dependency Review' 10 | on: [pull_request] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | dependency-review: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: 'Checkout Repository' 20 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 21 | - name: 'Dependency Review' 22 | uses: actions/dependency-review-action@38ecb5b593bf0eb19e335c03f97670f792489a8b # v4.7.0 23 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | 14 | build: 15 | name: Build + Test Stable 16 | runs-on: ubuntu-latest 17 | env: 18 | GO111MODULE: auto 19 | steps: 20 | - name: Build 21 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 22 | with: 23 | go-version: '1.22.x' 24 | 25 | - name: Check out code into the Go module directory 26 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 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@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 36 | env: 37 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 38 | 39 | build-legacy: 40 | name: Build + Test Previous Stable 41 | runs-on: ubuntu-latest 42 | env: 43 | GO111MODULE: auto 44 | steps: 45 | - name: Build 46 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 47 | with: 48 | go-version: '1.21.x' 49 | 50 | - name: Check out code into the Go module directory 51 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 52 | 53 | - name: Build 54 | run: go build -v ./... 55 | 56 | - name: Test 57 | run: go test ./... 58 | -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '20 7 * * 2' 14 | push: 15 | branches: ["master"] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | contents: read 30 | actions: read 31 | 32 | steps: 33 | - name: "Checkout code" 34 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 35 | with: 36 | persist-credentials: false 37 | 38 | - name: "Run analysis" 39 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 40 | with: 41 | results_file: results.sarif 42 | results_format: sarif 43 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 44 | # - you want to enable the Branch-Protection check on a *public* repository, or 45 | # - you are installing Scorecards on a *private* repository 46 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 47 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 48 | 49 | # Public repositories: 50 | # - Publish results to OpenSSF REST API for easy access by consumers 51 | # - Allows the repository to include the Scorecard badge. 52 | # - See https://github.com/ossf/scorecard-action#publishing-results. 53 | # For private repositories: 54 | # - `publish_results` will always be set to `false`, regardless 55 | # of the value entered here. 56 | publish_results: true 57 | 58 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 59 | # format to the repository Actions tab. 60 | - name: "Upload artifact" 61 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 62 | with: 63 | name: SARIF file 64 | path: results.sarif 65 | retention-days: 5 66 | 67 | # Upload the results to GitHub's code scanning dashboard. 68 | - name: "Upload to code-scanning" 69 | uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 70 | with: 71 | sarif_file: results.sarif 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # binary bundle generated by go-fuzz 15 | uuid-fuzz.zip 16 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/gitleaks/gitleaks 3 | rev: v8.16.3 4 | hooks: 5 | - id: gitleaks 6 | - repo: https://github.com/golangci/golangci-lint 7 | rev: v1.52.2 8 | hooks: 9 | - id: golangci-lint 10 | - repo: https://github.com/pre-commit/pre-commit-hooks 11 | rev: v4.4.0 12 | hooks: 13 | - id: end-of-file-fixer 14 | - id: trailing-whitespace 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013-2018 by Maxim Bublis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UUID 2 | 3 | [![License](https://img.shields.io/github/license/gofrs/uuid.svg)](https://github.com/gofrs/uuid/blob/master/LICENSE) 4 | [![Build Status](https://github.com/gofrs/uuid/actions/workflows/go.yml/badge.svg)](https://github.com/gofrs/uuid/actions/workflows/go.yml) 5 | [![Go Reference](https://pkg.go.dev/badge/github.com/gofrs/uuid/v5.svg)](https://pkg.go.dev/github.com/gofrs/uuid/v5) 6 | [![Coverage Status](https://codecov.io/gh/gofrs/uuid/branch/master/graphs/badge.svg?branch=master)](https://codecov.io/gh/gofrs/uuid/) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/gofrs/uuid)](https://goreportcard.com/report/github.com/gofrs/uuid) 8 | [![CodeQL](https://github.com/gofrs/uuid/actions/workflows/codeql.yml/badge.svg)](https://github.com/gofrs/uuid/actions/workflows/codeql.yml) 9 | [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8929/badge)](https://www.bestpractices.dev/projects/8929) 10 | [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/gofrs/uuid/badge)](https://scorecard.dev/viewer/?uri=github.com/gofrs/uuid) 11 | 12 | Package uuid provides a pure Go implementation of Universally Unique Identifiers 13 | (UUID) variant as defined in RFC-9562. This package supports both the creation 14 | and parsing of UUIDs in different formats. 15 | 16 | This package supports the following UUID versions: 17 | 18 | * Version 1, based on timestamp and MAC address 19 | * Version 3, based on MD5 hashing of a named value 20 | * Version 4, based on random numbers 21 | * Version 5, based on SHA-1 hashing of a named value 22 | * Version 6, a k-sortable id based on timestamp, and field-compatible with v1 23 | * Version 7, a k-sortable id based on timestamp 24 | 25 | ## Project History 26 | 27 | This project was originally forked from the 28 | [github.com/satori/go.uuid](https://github.com/satori/go.uuid) repository after 29 | it appeared to be no longer maintained, while exhibiting [critical 30 | flaws](https://github.com/satori/go.uuid/issues/73). We have decided to take 31 | over this project to ensure it receives regular maintenance for the benefit of 32 | the larger Go community. 33 | 34 | We'd like to thank Maxim Bublis for his hard work on the original iteration of 35 | the package. 36 | 37 | ## License 38 | 39 | This source code of this package is released under the MIT License. Please see 40 | the [LICENSE](https://github.com/gofrs/uuid/blob/master/LICENSE) for the full 41 | content of the license. 42 | 43 | ## Recommended Package Version 44 | 45 | We recommend using v2.0.0+ of this package, as versions prior to 2.0.0 were 46 | created before our fork of the original package and have some known 47 | deficiencies. 48 | 49 | ## Requirements 50 | 51 | This package requires Go 1.19 or later 52 | 53 | ## Usage 54 | 55 | Here is a quick overview of how to use this package. For more detailed 56 | documentation, please see the [GoDoc Page](http://godoc.org/github.com/gofrs/uuid). 57 | 58 | ```go 59 | package main 60 | 61 | import ( 62 | "log" 63 | 64 | "github.com/gofrs/uuid/v5" 65 | ) 66 | 67 | // Create a Version 4 UUID, panicking on error. 68 | // Use this form to initialize package-level variables. 69 | var u1 = uuid.Must(uuid.NewV4()) 70 | 71 | func main() { 72 | // Create a Version 4 UUID. 73 | u2, err := uuid.NewV4() 74 | if err != nil { 75 | log.Fatalf("failed to generate UUID: %v", err) 76 | } 77 | log.Printf("generated Version 4 UUID %v", u2) 78 | 79 | // Parse a UUID from a string. 80 | s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 81 | u3, err := uuid.FromString(s) 82 | if err != nil { 83 | log.Fatalf("failed to parse UUID %q: %v", s, err) 84 | } 85 | log.Printf("successfully parsed UUID %v", u3) 86 | } 87 | ``` 88 | 89 | ## References 90 | 91 | * [RFC-9562](https://tools.ietf.org/html/rfc9562) (replaces RFC-4122) 92 | * [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01) 93 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We support the latest version of this library. We do not guarantee support of previous versions. If a defect is reported, it will generally be fixed on the latest version 6 | (provided it exists) irrespective of whether it was introduced in a prior version. 7 | 8 | ## Reporting a Vulnerability 9 | 10 | If you discover a vulnerability against this package, please report it in the issues tab with a `vulnerability` label. We will examine promptly. 11 | 12 | If you would like to disclose the vulnerability privately, you may reach the maintainers in our [channel](https://gophers.slack.com/archives/CBP4N9BEU) on the gophers slack. 13 | 14 | ## Security Scorecard 15 | 16 | This project submits security [results](https://scorecard.dev/viewer/?uri=github.com/gofrs/uuid) to the [OpenSSF Scorecard](https://securityscorecards.dev/). 17 | 18 | ### Actively Maintained 19 | 20 | One heuristic these scorecards measure to gauge whether a package is safe for consumption is an "Actively Maintained" metric. Because this library implements UUIDs, 21 | it is very stable - there is not much maintenance required other than adding/updating newer UUID versions, keeping up to date with latest versions of Go, and responding 22 | to reported exploits. As a result, periods of low active maintenance are to be expected. 23 | -------------------------------------------------------------------------------- /codec.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | package uuid 23 | 24 | import "fmt" 25 | 26 | // FromBytes returns a UUID generated from the raw byte slice input. 27 | // It will return an error if the slice isn't 16 bytes long. 28 | func FromBytes(input []byte) (UUID, error) { 29 | u := UUID{} 30 | err := u.UnmarshalBinary(input) 31 | return u, err 32 | } 33 | 34 | // FromBytesOrNil returns a UUID generated from the raw byte slice input. 35 | // Same behavior as FromBytes(), but returns uuid.Nil instead of an error. 36 | func FromBytesOrNil(input []byte) UUID { 37 | // The logic here is duplicated from UnmarshalBinary as there is unnecessary 38 | // overhead generating errors which would be checked and discarded. 39 | if len(input) != Size { 40 | return Nil 41 | } 42 | 43 | uuid := UUID{} 44 | copy(uuid[:], input) 45 | 46 | return uuid 47 | } 48 | 49 | func fromHexChar(c byte) byte { 50 | switch { 51 | case '0' <= c && c <= '9': 52 | return c - '0' 53 | case 'a' <= c && c <= 'f': 54 | return c - 'a' + 10 55 | case 'A' <= c && c <= 'F': 56 | return c - 'A' + 10 57 | } 58 | return 255 59 | } 60 | 61 | // Parse parses the UUID stored in the string text. Parsing and supported 62 | // formats are the same as UnmarshalText. 63 | func (u *UUID) Parse(s string) error { 64 | switch len(s) { 65 | case 32: // hash 66 | case 36: // canonical 67 | case 34, 38: 68 | if s[0] != '{' || s[len(s)-1] != '}' { 69 | return fmt.Errorf("%w %q", ErrIncorrectFormatInString, s) 70 | } 71 | s = s[1 : len(s)-1] 72 | case 41, 45: 73 | if s[:9] != "urn:uuid:" { 74 | return fmt.Errorf("%w %q", ErrIncorrectFormatInString, s[:9]) 75 | } 76 | s = s[9:] 77 | default: 78 | return fmt.Errorf("%w %d in string %q", ErrIncorrectLength, len(s), s) 79 | } 80 | // canonical 81 | if len(s) == 36 { 82 | if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { 83 | return fmt.Errorf("%w %q", ErrIncorrectFormatInString, s) 84 | } 85 | for i, x := range [16]byte{ 86 | 0, 2, 4, 6, 87 | 9, 11, 88 | 14, 16, 89 | 19, 21, 90 | 24, 26, 28, 30, 32, 34, 91 | } { 92 | v1 := fromHexChar(s[x]) 93 | v2 := fromHexChar(s[x+1]) 94 | if v1|v2 == 255 { 95 | return ErrInvalidFormat 96 | } 97 | u[i] = (v1 << 4) | v2 98 | } 99 | return nil 100 | } 101 | // hash like 102 | for i := 0; i < 32; i += 2 { 103 | v1 := fromHexChar(s[i]) 104 | v2 := fromHexChar(s[i+1]) 105 | if v1|v2 == 255 { 106 | return ErrInvalidFormat 107 | } 108 | u[i/2] = (v1 << 4) | v2 109 | } 110 | return nil 111 | } 112 | 113 | // FromString returns a UUID parsed from the input string. 114 | // Input is expected in a form accepted by UnmarshalText. 115 | func FromString(text string) (UUID, error) { 116 | var u UUID 117 | err := u.Parse(text) 118 | return u, err 119 | } 120 | 121 | // FromStringOrNil returns a UUID parsed from the input string. 122 | // Same behavior as FromString(), but returns uuid.Nil instead of an error. 123 | func FromStringOrNil(input string) UUID { 124 | uuid, err := FromString(input) 125 | if err != nil { 126 | return Nil 127 | } 128 | return uuid 129 | } 130 | 131 | // MarshalText implements the encoding.TextMarshaler interface. 132 | // The encoding is the same as returned by the String() method. 133 | func (u UUID) MarshalText() ([]byte, error) { 134 | var buf [36]byte 135 | encodeCanonical(buf[:], u) 136 | return buf[:], nil 137 | } 138 | 139 | // UnmarshalText implements the encoding.TextUnmarshaler interface. 140 | // Following formats are supported: 141 | // 142 | // "6ba7b810-9dad-11d1-80b4-00c04fd430c8", 143 | // "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", 144 | // "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" 145 | // "6ba7b8109dad11d180b400c04fd430c8" 146 | // "{6ba7b8109dad11d180b400c04fd430c8}", 147 | // "urn:uuid:6ba7b8109dad11d180b400c04fd430c8" 148 | // 149 | // ABNF for supported UUID text representation follows: 150 | // 151 | // URN := 'urn' 152 | // UUID-NID := 'uuid' 153 | // 154 | // hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 155 | // 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 156 | // 'A' | 'B' | 'C' | 'D' | 'E' | 'F' 157 | // 158 | // hexoct := hexdig hexdig 159 | // 2hexoct := hexoct hexoct 160 | // 4hexoct := 2hexoct 2hexoct 161 | // 6hexoct := 4hexoct 2hexoct 162 | // 12hexoct := 6hexoct 6hexoct 163 | // 164 | // hashlike := 12hexoct 165 | // canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct 166 | // 167 | // plain := canonical | hashlike 168 | // uuid := canonical | hashlike | braced | urn 169 | // 170 | // braced := '{' plain '}' | '{' hashlike '}' 171 | // urn := URN ':' UUID-NID ':' plain 172 | func (u *UUID) UnmarshalText(b []byte) error { 173 | switch len(b) { 174 | case 32: // hash 175 | case 36: // canonical 176 | case 34, 38: 177 | if b[0] != '{' || b[len(b)-1] != '}' { 178 | return fmt.Errorf("%w %q", ErrIncorrectFormatInString, b) 179 | } 180 | b = b[1 : len(b)-1] 181 | case 41, 45: 182 | if string(b[:9]) != "urn:uuid:" { 183 | return fmt.Errorf("%w %q", ErrIncorrectFormatInString, b[:9]) 184 | } 185 | b = b[9:] 186 | default: 187 | return fmt.Errorf("%w %d in string %q", ErrIncorrectLength, len(b), b) 188 | } 189 | if len(b) == 36 { 190 | if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { 191 | return fmt.Errorf("%w %q", ErrIncorrectFormatInString, b) 192 | } 193 | for i, x := range [16]byte{ 194 | 0, 2, 4, 6, 195 | 9, 11, 196 | 14, 16, 197 | 19, 21, 198 | 24, 26, 28, 30, 32, 34, 199 | } { 200 | v1 := fromHexChar(b[x]) 201 | v2 := fromHexChar(b[x+1]) 202 | if v1|v2 == 255 { 203 | return ErrInvalidFormat 204 | } 205 | u[i] = (v1 << 4) | v2 206 | } 207 | return nil 208 | } 209 | for i := 0; i < 32; i += 2 { 210 | v1 := fromHexChar(b[i]) 211 | v2 := fromHexChar(b[i+1]) 212 | if v1|v2 == 255 { 213 | return ErrInvalidFormat 214 | } 215 | u[i/2] = (v1 << 4) | v2 216 | } 217 | return nil 218 | } 219 | 220 | // MarshalBinary implements the encoding.BinaryMarshaler interface. 221 | func (u UUID) MarshalBinary() ([]byte, error) { 222 | return u.Bytes(), nil 223 | } 224 | 225 | // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. 226 | // It will return an error if the slice isn't 16 bytes long. 227 | func (u *UUID) UnmarshalBinary(data []byte) error { 228 | if len(data) != Size { 229 | return fmt.Errorf("%w, got %d bytes", ErrIncorrectByteLength, len(data)) 230 | } 231 | copy(u[:], data) 232 | 233 | return nil 234 | } 235 | -------------------------------------------------------------------------------- /codec_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | package uuid 23 | 24 | import ( 25 | "bytes" 26 | "regexp" 27 | "strings" 28 | "testing" 29 | ) 30 | 31 | // codecTestData holds []byte data for a UUID we commonly use for testing. 32 | var codecTestData = []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} 33 | 34 | // codecTestUUID is the UUID value corresponding to codecTestData. 35 | var codecTestUUID = UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} 36 | 37 | func TestFromBytes(t *testing.T) { 38 | t.Run("Valid", func(t *testing.T) { 39 | got, err := FromBytes(codecTestData) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | if got != codecTestUUID { 44 | t.Fatalf("FromBytes(%x) = %v, want %v", codecTestData, got, codecTestUUID) 45 | } 46 | }) 47 | t.Run("Invalid", func(t *testing.T) { 48 | var short [][]byte 49 | for i := 0; i < len(codecTestData); i++ { 50 | short = append(short, codecTestData[:i]) 51 | } 52 | var long [][]byte 53 | for i := 1; i < 17; i++ { 54 | tmp := append(codecTestData, make([]byte, i)...) 55 | long = append(long, tmp) 56 | } 57 | invalid := append(short, long...) 58 | for _, b := range invalid { 59 | got, err := FromBytes(b) 60 | if err == nil { 61 | t.Fatalf("FromBytes(%x): want err != nil, got %v", b, got) 62 | } 63 | } 64 | }) 65 | } 66 | 67 | func TestFromBytesOrNil(t *testing.T) { 68 | t.Run("Invalid", func(t *testing.T) { 69 | b := []byte{4, 8, 15, 16, 23, 42} 70 | got := FromBytesOrNil(b) 71 | if got != Nil { 72 | t.Errorf("FromBytesOrNil(%x): got %v, want %v", b, got, Nil) 73 | } 74 | }) 75 | t.Run("Valid", func(t *testing.T) { 76 | got := FromBytesOrNil(codecTestData) 77 | if got != codecTestUUID { 78 | t.Errorf("FromBytesOrNil(%x): got %v, want %v", codecTestData, got, codecTestUUID) 79 | } 80 | }) 81 | 82 | } 83 | 84 | type fromStringTest struct { 85 | input string 86 | variant string 87 | } 88 | 89 | // Run runs the FromString test in a subtest of t, named by fst.variant. 90 | func (fst fromStringTest) TestFromString(t *testing.T) { 91 | t.Run(fst.variant, func(t *testing.T) { 92 | got, err := FromString(fst.input) 93 | if err != nil { 94 | t.Fatalf("FromString(%q): %v", fst.input, err) 95 | } 96 | if want := codecTestUUID; got != want { 97 | t.Fatalf("FromString(%q) = %v, want %v", fst.input, got, want) 98 | } 99 | }) 100 | } 101 | 102 | func (fst fromStringTest) TestUnmarshalText(t *testing.T) { 103 | t.Run(fst.variant, func(t *testing.T) { 104 | var u UUID 105 | err := u.UnmarshalText([]byte(fst.input)) 106 | if err != nil { 107 | t.Fatalf("UnmarshalText(%q) (%s): %v", fst.input, fst.variant, err) 108 | } 109 | if want := codecTestData; !bytes.Equal(u[:], want[:]) { 110 | t.Fatalf("UnmarshalText(%q) (%s) = %v, want %v", fst.input, fst.variant, u, want) 111 | } 112 | }) 113 | } 114 | 115 | // fromStringTests contains UUID variants that are expected to be parsed 116 | // successfully by UnmarshalText / FromString. 117 | // 118 | // variants must be unique across elements of this slice. Please see the 119 | // comment in fuzz.go if you change this slice or add new tests to it. 120 | var fromStringTests = []fromStringTest{ 121 | { 122 | input: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", 123 | variant: "Canonical", 124 | }, 125 | { 126 | input: "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", 127 | variant: "BracedCanonical", 128 | }, 129 | { 130 | input: "{6ba7b8109dad11d180b400c04fd430c8}", 131 | variant: "BracedHashlike", 132 | }, 133 | { 134 | input: "6ba7b8109dad11d180b400c04fd430c8", 135 | variant: "Hashlike", 136 | }, 137 | { 138 | input: "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8", 139 | variant: "URNCanonical", 140 | }, 141 | { 142 | input: "urn:uuid:6ba7b8109dad11d180b400c04fd430c8", 143 | variant: "URNHashlike", 144 | }, 145 | } 146 | 147 | var invalidFromStringInputs = []string{ 148 | // short 149 | "6ba7b810-9dad-11d1-80b4-00c04fd430c", 150 | "6ba7b8109dad11d180b400c04fd430c", 151 | 152 | // invalid hex 153 | "6ba7b8109dad11d180b400c04fd430q8", 154 | 155 | // long 156 | "6ba7b810-9dad-11d1-80b4-00c04fd430c8=", 157 | "6ba7b810-9dad-11d1-80b4-00c04fd430c8}", 158 | "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}f", 159 | "6ba7b810-9dad-11d1-80b4-00c04fd430c800c04fd430c8", 160 | 161 | // malformed in other ways 162 | "ba7b8109dad11d180b400c04fd430c8}", 163 | "6ba7b8109dad11d180b400c04fd430c86ba7b8109dad11d180b400c04fd430c8", 164 | "urn:uuid:{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", 165 | "uuid:urn:6ba7b810-9dad-11d1-80b4-00c04fd430c8", 166 | "uuid:urn:6ba7b8109dad11d180b400c04fd430c8", 167 | "6ba7b8109-dad-11d1-80b4-00c04fd430c8", 168 | "6ba7b810-9dad1-1d1-80b4-00c04fd430c8", 169 | "6ba7b810-9dad-11d18-0b4-00c04fd430c8", 170 | "6ba7b810-9dad-11d1-80b40-0c04fd430c8", 171 | "6ba7b810+9dad+11d1+80b4+00c04fd430c8", 172 | "(6ba7b810-9dad-11d1-80b4-00c04fd430c8}", 173 | "{6ba7b810-9dad-11d1-80b4-00c04fd430c8>", 174 | "zba7b810-9dad-11d1-80b4-00c04fd430c8", 175 | "6ba7b810-9dad11d180b400c04fd430c8", 176 | "6ba7b8109dad-11d180b400c04fd430c8", 177 | "6ba7b8109dad11d1-80b400c04fd430c8", 178 | "6ba7b8109dad11d180b4-00c04fd430c8", 179 | } 180 | 181 | func TestFromString(t *testing.T) { 182 | t.Run("Valid", func(t *testing.T) { 183 | for _, fst := range fromStringTests { 184 | fst.TestFromString(t) 185 | } 186 | }) 187 | t.Run("Invalid", func(t *testing.T) { 188 | for _, s := range invalidFromStringInputs { 189 | got, err := FromString(s) 190 | if err == nil { 191 | t.Errorf("FromString(%q): want err != nil, got %v", s, got) 192 | } 193 | } 194 | }) 195 | } 196 | 197 | func TestFromStringOrNil(t *testing.T) { 198 | t.Run("Invalid", func(t *testing.T) { 199 | s := "bad" 200 | got := FromStringOrNil(s) 201 | if got != Nil { 202 | t.Errorf("FromStringOrNil(%q): got %v, want Nil", s, got) 203 | } 204 | }) 205 | t.Run("Valid", func(t *testing.T) { 206 | s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 207 | got := FromStringOrNil(s) 208 | if got != codecTestUUID { 209 | t.Errorf("FromStringOrNil(%q): got %v, want %v", s, got, codecTestUUID) 210 | } 211 | }) 212 | } 213 | 214 | func TestUnmarshalText(t *testing.T) { 215 | t.Run("Valid", func(t *testing.T) { 216 | for _, fst := range fromStringTests { 217 | fst.TestUnmarshalText(t) 218 | } 219 | }) 220 | t.Run("Invalid", func(t *testing.T) { 221 | for _, s := range invalidFromStringInputs { 222 | var u UUID 223 | err := u.UnmarshalText([]byte(s)) 224 | if err == nil { 225 | t.Errorf("FromBytes(%q): want err != nil, got %v", s, u) 226 | } 227 | } 228 | }) 229 | } 230 | 231 | // Test that UnmarshalText() and Parse() return identical errors 232 | func TestUnmarshalTextParseErrors(t *testing.T) { 233 | for _, s := range invalidFromStringInputs { 234 | var u UUID 235 | e1 := u.UnmarshalText([]byte(s)) 236 | e2 := u.Parse(s) 237 | if e1 == nil || e1.Error() != e2.Error() { 238 | t.Errorf("%q: errors don't match: UnmarshalText: %v Parse: %v", s, e1, e2) 239 | } 240 | } 241 | } 242 | 243 | func TestMarshalBinary(t *testing.T) { 244 | got, err := codecTestUUID.MarshalBinary() 245 | if err != nil { 246 | t.Fatal(err) 247 | } 248 | if !bytes.Equal(got, codecTestData) { 249 | t.Fatalf("%v.MarshalBinary() = %x, want %x", codecTestUUID, got, codecTestData) 250 | } 251 | } 252 | 253 | func TestMarshalText(t *testing.T) { 254 | want := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") 255 | got, err := codecTestUUID.MarshalText() 256 | if err != nil { 257 | t.Fatal(err) 258 | } 259 | if !bytes.Equal(got, want) { 260 | t.Errorf("%v.MarshalText(): got %s, want %s", codecTestUUID, got, want) 261 | } 262 | } 263 | 264 | func TestDecodePlainWithWrongLength(t *testing.T) { 265 | arg := []byte{'4', '2'} 266 | 267 | u := UUID{} 268 | 269 | if u.UnmarshalText(arg) == nil { 270 | t.Errorf("%v.UnmarshalText(%q): should return error, but it did not", u, arg) 271 | } 272 | } 273 | 274 | func TestFromHexChar(t *testing.T) { 275 | const hextable = "0123456789abcdef" 276 | 277 | t.Run("Valid", func(t *testing.T) { 278 | t.Run("Lower", func(t *testing.T) { 279 | for i, c := range []byte(hextable) { 280 | x := fromHexChar(c) 281 | if int(x) != i { 282 | t.Errorf("fromHexChar(%c): got %d want %d", c, x, i) 283 | } 284 | } 285 | }) 286 | t.Run("Upper", func(t *testing.T) { 287 | for i, c := range []byte(strings.ToUpper(hextable)) { 288 | x := fromHexChar(c) 289 | if int(x) != i { 290 | t.Errorf("fromHexChar(%c): got %d want %d", c, x, i) 291 | } 292 | } 293 | }) 294 | }) 295 | 296 | t.Run("Invalid", func(t *testing.T) { 297 | skip := make(map[byte]bool) 298 | for _, c := range []byte(hextable + strings.ToUpper(hextable)) { 299 | skip[c] = true 300 | } 301 | for i := 0; i < 256; i++ { 302 | c := byte(i) 303 | if !skip[c] { 304 | v := fromHexChar(c) 305 | if v != 255 { 306 | t.Errorf("fromHexChar(%c): got %d want: %d", c, v, 255) 307 | } 308 | } 309 | } 310 | }) 311 | } 312 | 313 | var stringBenchmarkSink string 314 | 315 | func BenchmarkString(b *testing.B) { 316 | for i := 0; i < b.N; i++ { 317 | stringBenchmarkSink = codecTestUUID.String() 318 | } 319 | } 320 | 321 | func BenchmarkFromBytes(b *testing.B) { 322 | for i := 0; i < b.N; i++ { 323 | FromBytes(codecTestData) 324 | } 325 | } 326 | 327 | func BenchmarkFromString(b *testing.B) { 328 | b.Run("canonical", func(b *testing.B) { 329 | for i := 0; i < b.N; i++ { 330 | FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") 331 | } 332 | }) 333 | b.Run("urn", func(b *testing.B) { 334 | for i := 0; i < b.N; i++ { 335 | FromString("urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8") 336 | } 337 | }) 338 | b.Run("braced", func(b *testing.B) { 339 | for i := 0; i < b.N; i++ { 340 | FromString("{6ba7b810-9dad-11d1-80b4-00c04fd430c8}") 341 | } 342 | }) 343 | } 344 | 345 | var FromBytesOrNilResult UUID 346 | 347 | func BenchmarkFromBytesOrNil(b *testing.B) { 348 | b.Run("valid", func(b *testing.B) { 349 | for i := 0; i < b.N; i++ { 350 | FromBytesOrNilResult = FromBytesOrNil(codecTestData) 351 | } 352 | }) 353 | 354 | b.Run("empty", func(b *testing.B) { 355 | for i := 0; i < b.N; i++ { 356 | FromBytesOrNilResult = FromBytesOrNil([]byte{}) 357 | } 358 | }) 359 | } 360 | 361 | func BenchmarkUnmarshalText(b *testing.B) { 362 | b.Run("canonical", func(b *testing.B) { 363 | text := []byte(Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")).String()) 364 | u := new(UUID) 365 | if err := u.UnmarshalText(text); err != nil { 366 | b.Fatal(err) 367 | } 368 | b.ResetTimer() 369 | for i := 0; i < b.N; i++ { 370 | _ = u.UnmarshalText(text) 371 | } 372 | }) 373 | b.Run("urn", func(b *testing.B) { 374 | text := []byte(Must(FromString("urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8")).String()) 375 | u := new(UUID) 376 | if err := u.UnmarshalText(text); err != nil { 377 | b.Fatal(err) 378 | } 379 | b.ResetTimer() 380 | for i := 0; i < b.N; i++ { 381 | _ = u.UnmarshalText(text) 382 | } 383 | }) 384 | b.Run("braced", func(b *testing.B) { 385 | text := []byte(Must(FromString("{6ba7b810-9dad-11d1-80b4-00c04fd430c8}")).String()) 386 | u := new(UUID) 387 | if err := u.UnmarshalText(text); err != nil { 388 | b.Fatal(err) 389 | } 390 | b.ResetTimer() 391 | for i := 0; i < b.N; i++ { 392 | _ = u.UnmarshalText(text) 393 | } 394 | }) 395 | } 396 | 397 | func BenchmarkMarshalBinary(b *testing.B) { 398 | for i := 0; i < b.N; i++ { 399 | codecTestUUID.MarshalBinary() 400 | } 401 | } 402 | 403 | func BenchmarkMarshalText(b *testing.B) { 404 | for i := 0; i < b.N; i++ { 405 | codecTestUUID.MarshalText() 406 | } 407 | } 408 | 409 | func BenchmarkParseV4(b *testing.B) { 410 | const text = "f52a747a-983f-45f7-90b5-e84d70f470dd" 411 | for i := 0; i < b.N; i++ { 412 | var u UUID 413 | if err := u.Parse(text); err != nil { 414 | b.Fatal(err) 415 | } 416 | } 417 | } 418 | 419 | const uuidPattern = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" 420 | 421 | var fromBytesCorpus = [][]byte{ 422 | {0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}, 423 | {4, 8, 15, 16, 23, 42}, 424 | } 425 | 426 | // FuzzFromBytesFunc is a fuzz testing suite that exercises the FromBytes function 427 | func FuzzFromBytesFunc(f *testing.F) { 428 | for _, seed := range fromBytesCorpus { 429 | f.Add(seed) 430 | } 431 | uuidRegexp, err := regexp.Compile(uuidPattern) 432 | if err != nil { 433 | f.Fatal("uuid regexp failed to compile") 434 | } 435 | f.Fuzz(func(t *testing.T, payload []byte) { 436 | u, err := FromBytes(payload) 437 | if len(payload) != Size && err == nil { 438 | t.Errorf("%v did not result in an error", payload) 439 | } 440 | if len(payload) == Size && u == Nil { 441 | t.Errorf("%v resulted in Nil uuid", payload) 442 | } 443 | if len(payload) == Size && !uuidRegexp.MatchString(u.String()) { 444 | t.Errorf("%v resulted in invalid uuid %s", payload, u.String()) 445 | } 446 | // otherwise, allow to pass if no panic 447 | }) 448 | } 449 | 450 | // FuzzFromBytesOrNilFunc is a fuzz testing suite that exercises the FromBytesOrNil function 451 | func FuzzFromBytesOrNilFunc(f *testing.F) { 452 | for _, seed := range fromBytesCorpus { 453 | f.Add(seed) 454 | } 455 | uuidRegexp, err := regexp.Compile(uuidPattern) 456 | if err != nil { 457 | f.Error("uuid regexp failed to compile") 458 | } 459 | f.Fuzz(func(t *testing.T, payload []byte) { 460 | u := FromBytesOrNil(payload) 461 | if len(payload) != Size && u != Nil { 462 | t.Errorf("%v resulted in non Nil uuid %s", payload, u.String()) 463 | } 464 | if len(payload) == Size && u == Nil { 465 | t.Errorf("%v resulted Nil uuid", payload) 466 | } 467 | if len(payload) == Size && !uuidRegexp.MatchString(u.String()) { 468 | t.Errorf("%v resulted in invalid uuid %s", payload, u.String()) 469 | } 470 | // otherwise, allow to pass if no panic 471 | }) 472 | } 473 | 474 | var fromStringCorpus = []string{ 475 | "6ba7b810-9dad-11d1-80b4-00c04fd430c8", 476 | "6BA7B810-9DAD-11D1-80B4-00C04FD430C8", 477 | "{6BA7B810-9DAD-11D1-80B4-00C04FD430C8}", 478 | "urn:uuid:6BA7B810-9DAD-11D1-80B4-00C04FD430C8", 479 | "6BA7B8109DAD11D180B400C04FD430C8", 480 | "{6BA7B8109DAD11D180B400C04FD430C8}", 481 | "urn:uuid:6BA7B8109DAD11D180B400C04FD430C8", 482 | } 483 | 484 | // FuzzFromStringFunc is a fuzz testing suite that exercises the FromString function 485 | func FuzzFromStringFunc(f *testing.F) { 486 | for _, seed := range fromStringCorpus { 487 | f.Add(seed) 488 | } 489 | uuidRegexp, err := regexp.Compile(uuidPattern) 490 | if err != nil { 491 | f.Fatal("uuid regexp failed to compile") 492 | } 493 | f.Fuzz(func(t *testing.T, payload string) { 494 | u, err := FromString(payload) 495 | if err != nil { 496 | if u == Nil { 497 | t.Errorf("%s resulted in Nil uuid", payload) 498 | } 499 | if !uuidRegexp.MatchString(u.String()) { 500 | t.Errorf("%s resulted in invalid uuid %s", payload, u.String()) 501 | } 502 | } 503 | // otherwise, allow to pass if no panic 504 | }) 505 | } 506 | 507 | // FuzzFromStringOrNil is a fuzz testing suite that exercises the FromStringOrNil function 508 | func FuzzFromStringOrNilFunc(f *testing.F) { 509 | for _, seed := range fromStringCorpus { 510 | f.Add(seed) 511 | } 512 | uuidRegexp, err := regexp.Compile(uuidPattern) 513 | if err != nil { 514 | f.Error("uuid regexp failed to compile") 515 | } 516 | f.Fuzz(func(t *testing.T, payload string) { 517 | u := FromStringOrNil(payload) 518 | if u != Nil { 519 | if !uuidRegexp.MatchString(u.String()) { 520 | t.Errorf("%s resulted in invalid uuid %s", payload, u.String()) 521 | } 522 | } 523 | // otherwise, allow to pass if no panic 524 | }) 525 | } 526 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | // Error is a custom error type for UUID-related errors 4 | type Error string 5 | 6 | // The strings defined in the errors is matching the previous behavior before 7 | // the custom error type was implemented. The reason is that some people might 8 | // be relying on the exact string representation to handle errors in their code. 9 | const ( 10 | // ErrInvalidFormat is returned when the UUID string representation does not 11 | // match the expected format. See also ErrIncorrectFormatInString. 12 | ErrInvalidFormat = Error("uuid: invalid UUID format") 13 | 14 | // ErrIncorrectFormatInString can be returned instead of ErrInvalidFormat. 15 | // A separate error type is used because of how errors used to be formatted 16 | // before custom error types were introduced. 17 | ErrIncorrectFormatInString = Error("uuid: incorrect UUID format in string") 18 | 19 | // ErrIncorrectLength is returned when the UUID does not have the 20 | // appropriate string length for parsing the UUID. 21 | ErrIncorrectLength = Error("uuid: incorrect UUID length") 22 | 23 | // ErrIncorrectByteLength indicates the UUID byte slice length is invalid. 24 | ErrIncorrectByteLength = Error("uuid: UUID must be exactly 16 bytes long") 25 | 26 | // ErrNoHwAddressFound is returned when a hardware (MAC) address cannot be 27 | // found for UUID generation. 28 | ErrNoHwAddressFound = Error("uuid: no HW address found") 29 | 30 | // ErrTypeConvertError is returned for type conversion operation fails. 31 | ErrTypeConvertError = Error("uuid: cannot convert") 32 | 33 | // ErrInvalidVersion indicates an unsupported or invalid UUID version. 34 | ErrInvalidVersion = Error("uuid:") 35 | ) 36 | 37 | // Error returns the string representation of the UUID error. 38 | func (e Error) Error() string { 39 | return string(e) 40 | } 41 | -------------------------------------------------------------------------------- /error_test.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "testing" 8 | ) 9 | 10 | func TestIsAsError(t *testing.T) { 11 | tcs := []struct { 12 | err error 13 | expected string 14 | expectedErr error 15 | }{ 16 | { 17 | err: fmt.Errorf("%w sample error: %v", ErrInvalidVersion, 123), 18 | expected: "uuid: sample error: 123", 19 | expectedErr: ErrInvalidVersion, 20 | }, 21 | { 22 | err: fmt.Errorf("%w", ErrInvalidFormat), 23 | expected: "uuid: invalid UUID format", 24 | expectedErr: ErrInvalidFormat, 25 | }, 26 | { 27 | err: fmt.Errorf("%w %q", ErrIncorrectFormatInString, "test"), 28 | expected: "uuid: incorrect UUID format in string \"test\"", 29 | expectedErr: ErrIncorrectFormatInString, 30 | }, 31 | } 32 | for i, tc := range tcs { 33 | t.Run(fmt.Sprintf("Test case %d", i), func(t *testing.T) { 34 | if tc.err.Error() != tc.expected { 35 | t.Errorf("expected err.Error() to be '%s' but was '%s'", tc.expected, tc.err.Error()) 36 | } 37 | var uuidErr Error 38 | if !errors.As(tc.err, &uuidErr) { 39 | t.Error("expected errors.As() to work") 40 | } 41 | if !errors.Is(tc.err, tc.expectedErr) { 42 | t.Errorf("expected error to be, or wrap, the %v sentinel error", tc.expectedErr) 43 | } 44 | }) 45 | } 46 | } 47 | 48 | func TestParseErrors(t *testing.T) { 49 | tcs := []struct { 50 | function string 51 | uuidStr string 52 | expected string 53 | }{ 54 | { // 34 chars - With brackets 55 | function: "parse", 56 | uuidStr: "..................................", 57 | expected: "uuid: incorrect UUID format in string \"..................................\"", 58 | }, 59 | { // 41 chars - urn:uuid: 60 | function: "parse", 61 | uuidStr: "123456789................................", 62 | expected: "uuid: incorrect UUID format in string \"123456789\"", 63 | }, 64 | { // other 65 | function: "parse", 66 | uuidStr: "....", 67 | expected: "uuid: incorrect UUID length 4 in string \"....\"", 68 | }, 69 | { // 36 chars - canonical, but not correct format 70 | function: "parse", 71 | uuidStr: "....................................", 72 | expected: "uuid: incorrect UUID format in string \"....................................\"", 73 | }, 74 | { // 36 chars - canonical, invalid data 75 | function: "parse", 76 | uuidStr: "xx00ae9e-dae3-459f-ad0e-6b574be3f950", 77 | expected: "uuid: invalid UUID format", 78 | }, 79 | { // Hash like 80 | function: "parse", 81 | uuidStr: "................................", 82 | expected: "uuid: invalid UUID format", 83 | }, 84 | { // Hash like, invalid 85 | function: "parse", 86 | uuidStr: "xx00ae9edae3459fad0e6b574be3f950", 87 | expected: "uuid: invalid UUID format", 88 | }, 89 | { // Hash like, invalid 90 | function: "parse", 91 | uuidStr: "xx00ae9edae3459fad0e6b574be3f950", 92 | expected: "uuid: invalid UUID format", 93 | }, 94 | } 95 | for i, tc := range tcs { 96 | t.Run(fmt.Sprintf("Test case %d", i), func(t *testing.T) { 97 | id := UUID{} 98 | err := id.Parse(tc.uuidStr) 99 | if err == nil { 100 | t.Error("expected an error") 101 | return 102 | } 103 | if err.Error() != tc.expected { 104 | t.Errorf("unexpected error '%s' != '%s'", err.Error(), tc.expected) 105 | } 106 | err = id.UnmarshalText([]byte(tc.uuidStr)) 107 | if err == nil { 108 | t.Error("expected an error") 109 | return 110 | } 111 | if err.Error() != tc.expected { 112 | t.Errorf("unexpected error '%s' != '%s'", err.Error(), tc.expected) 113 | } 114 | }) 115 | } 116 | } 117 | 118 | func TestUnmarshalBinaryError(t *testing.T) { 119 | id := UUID{} 120 | b := make([]byte, 33) 121 | expectedErr := "uuid: UUID must be exactly 16 bytes long, got 33 bytes" 122 | err := id.UnmarshalBinary([]byte(b)) 123 | if err == nil { 124 | t.Error("expected an error") 125 | return 126 | } 127 | if err.Error() != expectedErr { 128 | t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) 129 | } 130 | } 131 | 132 | func TestScanError(t *testing.T) { 133 | id := UUID{} 134 | err := id.Scan(123) 135 | if err == nil { 136 | t.Error("expected an error") 137 | return 138 | } 139 | expectedErr := "uuid: cannot convert int to UUID" 140 | if err.Error() != expectedErr { 141 | t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) 142 | } 143 | } 144 | 145 | func TestUUIDVersionErrors(t *testing.T) { 146 | // UUId V1 Version 147 | id := FromStringOrNil("e86160d3-beff-443c-b9b5-1f8197ccb12e") 148 | _, err := TimestampFromV1(id) 149 | if err == nil { 150 | t.Error("expected an error") 151 | return 152 | } 153 | expectedErr := "uuid: e86160d3-beff-443c-b9b5-1f8197ccb12e is version 4, not version 1" 154 | if err.Error() != expectedErr { 155 | t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) 156 | } 157 | 158 | // UUId V2 Version 159 | id = FromStringOrNil("e86160d3-beff-443c-b9b5-1f8197ccb12e") 160 | _, err = TimestampFromV6(id) 161 | if err == nil { 162 | t.Error("expected an error") 163 | return 164 | } 165 | expectedErr = "uuid: e86160d3-beff-443c-b9b5-1f8197ccb12e is version 4, not version 6" 166 | if err.Error() != expectedErr { 167 | t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) 168 | } 169 | 170 | // UUId V7 Version 171 | id = FromStringOrNil("e86160d3-beff-443c-b9b5-1f8197ccb12e") 172 | _, err = TimestampFromV7(id) 173 | if err == nil { 174 | t.Error("expected an error") 175 | return 176 | } 177 | expectedErr = "uuid: e86160d3-beff-443c-b9b5-1f8197ccb12e is version 4, not version 7" 178 | if err.Error() != expectedErr { 179 | t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) 180 | } 181 | } 182 | 183 | // This test cannot be run in parallel with other tests since it modifies the 184 | // global state 185 | func TestErrNoHwAddressFound(t *testing.T) { 186 | netInterfaces = func() ([]net.Interface, error) { 187 | return nil, nil 188 | } 189 | defer func() { 190 | netInterfaces = net.Interfaces 191 | }() 192 | _, err := defaultHWAddrFunc() 193 | if err == nil { 194 | t.Error("expected an error") 195 | return 196 | } 197 | expectedErr := "uuid: no HW address found" 198 | if err.Error() != expectedErr { 199 | t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /generator.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | package uuid 23 | 24 | import ( 25 | "crypto/md5" 26 | "crypto/rand" 27 | "crypto/sha1" 28 | "encoding/binary" 29 | "hash" 30 | "io" 31 | "net" 32 | "sync" 33 | "time" 34 | ) 35 | 36 | // Difference in 100-nanosecond intervals between 37 | // UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). 38 | const epochStart = 122192928000000000 39 | 40 | // EpochFunc is the function type used to provide the current time. 41 | type EpochFunc func() time.Time 42 | 43 | // HWAddrFunc is the function type used to provide hardware (MAC) addresses. 44 | type HWAddrFunc func() (net.HardwareAddr, error) 45 | 46 | // DefaultGenerator is the default UUID Generator used by this package. 47 | var DefaultGenerator Generator = NewGen() 48 | 49 | // NewV1 returns a UUID based on the current timestamp and MAC address. 50 | func NewV1() (UUID, error) { 51 | return DefaultGenerator.NewV1() 52 | } 53 | 54 | // NewV1 returns a UUID based on the provided timestamp and MAC address. 55 | func NewV1AtTime(atTime time.Time) (UUID, error) { 56 | return DefaultGenerator.NewV1AtTime(atTime) 57 | } 58 | 59 | // NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name. 60 | func NewV3(ns UUID, name string) UUID { 61 | return DefaultGenerator.NewV3(ns, name) 62 | } 63 | 64 | // NewV4 returns a randomly generated UUID. 65 | func NewV4() (UUID, error) { 66 | return DefaultGenerator.NewV4() 67 | } 68 | 69 | // NewV5 returns a UUID based on SHA-1 hash of the namespace UUID and name. 70 | func NewV5(ns UUID, name string) UUID { 71 | return DefaultGenerator.NewV5(ns, name) 72 | } 73 | 74 | // NewV6 returns a k-sortable UUID based on the current timestamp and 48 bits of 75 | // pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit 76 | // order being adjusted to allow the UUID to be k-sortable. 77 | func NewV6() (UUID, error) { 78 | return DefaultGenerator.NewV6() 79 | } 80 | 81 | // NewV6 returns a k-sortable UUID based on the provided timestamp and 48 bits of 82 | // pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit 83 | // order being adjusted to allow the UUID to be k-sortable. 84 | func NewV6AtTime(atTime time.Time) (UUID, error) { 85 | return DefaultGenerator.NewV6AtTime(atTime) 86 | } 87 | 88 | // NewV7 returns a k-sortable UUID based on the current millisecond-precision 89 | // UNIX epoch and 74 bits of pseudorandom data. It supports single-node batch 90 | // generation (multiple UUIDs in the same timestamp) with a Monotonic Random counter. 91 | func NewV7() (UUID, error) { 92 | return DefaultGenerator.NewV7() 93 | } 94 | 95 | // NewV7 returns a k-sortable UUID based on the provided millisecond-precision 96 | // UNIX epoch and 74 bits of pseudorandom data. It supports single-node batch 97 | // generation (multiple UUIDs in the same timestamp) with a Monotonic Random counter. 98 | func NewV7AtTime(atTime time.Time) (UUID, error) { 99 | return DefaultGenerator.NewV7AtTime(atTime) 100 | } 101 | 102 | // Generator provides an interface for generating UUIDs. 103 | type Generator interface { 104 | NewV1() (UUID, error) 105 | NewV1AtTime(time.Time) (UUID, error) 106 | NewV3(ns UUID, name string) UUID 107 | NewV4() (UUID, error) 108 | NewV5(ns UUID, name string) UUID 109 | NewV6() (UUID, error) 110 | NewV6AtTime(time.Time) (UUID, error) 111 | NewV7() (UUID, error) 112 | NewV7AtTime(time.Time) (UUID, error) 113 | } 114 | 115 | // Gen is a reference UUID generator based on the specifications laid out in 116 | // RFC-9562 and DCE 1.1: Authentication and Security Services. This type 117 | // satisfies the Generator interface as defined in this package. 118 | // 119 | // For consumers who are generating V1 UUIDs, but don't want to expose the MAC 120 | // address of the node generating the UUIDs, the NewGenWithHWAF() function has been 121 | // provided as a convenience. See the function's documentation for more info. 122 | // 123 | // The authors of this package do not feel that the majority of users will need 124 | // to obfuscate their MAC address, and so we recommend using NewGen() to create 125 | // a new generator. 126 | type Gen struct { 127 | clockSequenceOnce sync.Once 128 | hardwareAddrOnce sync.Once 129 | storageMutex sync.Mutex 130 | 131 | rand io.Reader 132 | 133 | epochFunc EpochFunc 134 | hwAddrFunc HWAddrFunc 135 | lastTime uint64 136 | clockSequence uint16 137 | hardwareAddr [6]byte 138 | } 139 | 140 | // GenOption is a function type that can be used to configure a Gen generator. 141 | type GenOption func(*Gen) 142 | 143 | // interface check -- build will fail if *Gen doesn't satisfy Generator 144 | var _ Generator = (*Gen)(nil) 145 | 146 | // NewGen returns a new instance of Gen with some default values set. Most 147 | // people should use this. 148 | func NewGen() *Gen { 149 | return NewGenWithHWAF(defaultHWAddrFunc) 150 | } 151 | 152 | // NewGenWithHWAF builds a new UUID generator with the HWAddrFunc provided. Most 153 | // consumers should use NewGen() instead. 154 | // 155 | // This is used so that consumers can generate their own MAC addresses, for use 156 | // in the generated UUIDs, if there is some concern about exposing the physical 157 | // address of the machine generating the UUID. 158 | // 159 | // The Gen generator will only invoke the HWAddrFunc once, and cache that MAC 160 | // address for all the future UUIDs generated by it. If you'd like to switch the 161 | // MAC address being used, you'll need to create a new generator using this 162 | // function. 163 | func NewGenWithHWAF(hwaf HWAddrFunc) *Gen { 164 | return NewGenWithOptions(WithHWAddrFunc(hwaf)) 165 | } 166 | 167 | // NewGenWithOptions returns a new instance of Gen with the options provided. 168 | // Most people should use NewGen() or NewGenWithHWAF() instead. 169 | // 170 | // To customize the generator, you can pass in one or more GenOption functions. 171 | // For example: 172 | // 173 | // gen := NewGenWithOptions( 174 | // WithHWAddrFunc(myHWAddrFunc), 175 | // WithEpochFunc(myEpochFunc), 176 | // WithRandomReader(myRandomReader), 177 | // ) 178 | // 179 | // NewGenWithOptions(WithHWAddrFunc(myHWAddrFunc)) is equivalent to calling 180 | // NewGenWithHWAF(myHWAddrFunc) 181 | // NewGenWithOptions() is equivalent to calling NewGen() 182 | func NewGenWithOptions(opts ...GenOption) *Gen { 183 | gen := &Gen{ 184 | epochFunc: time.Now, 185 | hwAddrFunc: defaultHWAddrFunc, 186 | rand: rand.Reader, 187 | } 188 | 189 | for _, opt := range opts { 190 | opt(gen) 191 | } 192 | 193 | return gen 194 | } 195 | 196 | // WithHWAddrFunc is a GenOption that allows you to provide your own HWAddrFunc 197 | // function. 198 | // When this option is nil, the defaultHWAddrFunc is used. 199 | func WithHWAddrFunc(hwaf HWAddrFunc) GenOption { 200 | return func(gen *Gen) { 201 | if hwaf == nil { 202 | hwaf = defaultHWAddrFunc 203 | } 204 | 205 | gen.hwAddrFunc = hwaf 206 | } 207 | } 208 | 209 | // WithEpochFunc is a GenOption that allows you to provide your own EpochFunc 210 | // function. 211 | // When this option is nil, time.Now is used. 212 | func WithEpochFunc(epochf EpochFunc) GenOption { 213 | return func(gen *Gen) { 214 | if epochf == nil { 215 | epochf = time.Now 216 | } 217 | 218 | gen.epochFunc = epochf 219 | } 220 | } 221 | 222 | // WithRandomReader is a GenOption that allows you to provide your own random 223 | // reader. 224 | // When this option is nil, the default rand.Reader is used. 225 | func WithRandomReader(reader io.Reader) GenOption { 226 | return func(gen *Gen) { 227 | if reader == nil { 228 | reader = rand.Reader 229 | } 230 | 231 | gen.rand = reader 232 | } 233 | } 234 | 235 | // NewV1 returns a UUID based on the current timestamp and MAC address. 236 | func (g *Gen) NewV1() (UUID, error) { 237 | return g.NewV1AtTime(g.epochFunc()) 238 | } 239 | 240 | // NewV1AtTime returns a UUID based on the provided timestamp and current MAC address. 241 | func (g *Gen) NewV1AtTime(atTime time.Time) (UUID, error) { 242 | u := UUID{} 243 | 244 | timeNow, clockSeq, err := g.getClockSequence(false, atTime) 245 | if err != nil { 246 | return Nil, err 247 | } 248 | binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) 249 | binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) 250 | binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) 251 | binary.BigEndian.PutUint16(u[8:], clockSeq) 252 | 253 | hardwareAddr, err := g.getHardwareAddr() 254 | if err != nil { 255 | return Nil, err 256 | } 257 | copy(u[10:], hardwareAddr) 258 | 259 | u.SetVersion(V1) 260 | u.SetVariant(VariantRFC9562) 261 | 262 | return u, nil 263 | } 264 | 265 | // NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name. 266 | func (g *Gen) NewV3(ns UUID, name string) UUID { 267 | u := newFromHash(md5.New(), ns, name) 268 | u.SetVersion(V3) 269 | u.SetVariant(VariantRFC9562) 270 | 271 | return u 272 | } 273 | 274 | // NewV4 returns a randomly generated UUID. 275 | func (g *Gen) NewV4() (UUID, error) { 276 | u := UUID{} 277 | if _, err := io.ReadFull(g.rand, u[:]); err != nil { 278 | return Nil, err 279 | } 280 | u.SetVersion(V4) 281 | u.SetVariant(VariantRFC9562) 282 | 283 | return u, nil 284 | } 285 | 286 | // NewV5 returns a UUID based on SHA-1 hash of the namespace UUID and name. 287 | func (g *Gen) NewV5(ns UUID, name string) UUID { 288 | u := newFromHash(sha1.New(), ns, name) 289 | u.SetVersion(V5) 290 | u.SetVariant(VariantRFC9562) 291 | 292 | return u 293 | } 294 | 295 | // NewV6 returns a k-sortable UUID based on the current timestamp and 48 bits of 296 | // pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit 297 | // order being adjusted to allow the UUID to be k-sortable. 298 | func (g *Gen) NewV6() (UUID, error) { 299 | return g.NewV6AtTime(g.epochFunc()) 300 | } 301 | 302 | // NewV6 returns a k-sortable UUID based on the provided timestamp and 48 bits of 303 | // pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit 304 | // order being adjusted to allow the UUID to be k-sortable. 305 | func (g *Gen) NewV6AtTime(atTime time.Time) (UUID, error) { 306 | /* https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-6 307 | 0 1 2 3 308 | 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 309 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 310 | | time_high | 311 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 312 | | time_mid | ver | time_low | 313 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 314 | |var| clock_seq | node | 315 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 316 | | node | 317 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ 318 | var u UUID 319 | 320 | timeNow, _, err := g.getClockSequence(false, atTime) 321 | if err != nil { 322 | return Nil, err 323 | } 324 | 325 | binary.BigEndian.PutUint32(u[0:], uint32(timeNow>>28)) // set time_high 326 | binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>12)) // set time_mid 327 | binary.BigEndian.PutUint16(u[6:], uint16(timeNow&0xfff)) // set time_low (minus four version bits) 328 | 329 | // Based on the RFC 9562 recommendation that this data be fully random and not a monotonic counter, 330 | //we do NOT support batching version 6 UUIDs. 331 | //set clock_seq (14 bits) and node (48 bits) pseudo-random bits (first 2 bits will be overridden) 332 | if _, err = io.ReadFull(g.rand, u[8:]); err != nil { 333 | return Nil, err 334 | } 335 | 336 | u.SetVersion(V6) 337 | 338 | //overwrite first 2 bits of byte[8] for the variant 339 | u.SetVariant(VariantRFC9562) 340 | 341 | return u, nil 342 | } 343 | 344 | // NewV7 returns a k-sortable UUID based on the current millisecond-precision 345 | // UNIX epoch and 74 bits of pseudorandom data. 346 | func (g *Gen) NewV7() (UUID, error) { 347 | return g.NewV7AtTime(g.epochFunc()) 348 | } 349 | 350 | // NewV7 returns a k-sortable UUID based on the provided millisecond-precision 351 | // UNIX epoch and 74 bits of pseudorandom data. 352 | func (g *Gen) NewV7AtTime(atTime time.Time) (UUID, error) { 353 | var u UUID 354 | /* https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7 355 | 0 1 2 3 356 | 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 357 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 358 | | unix_ts_ms | 359 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 360 | | unix_ts_ms | ver | rand_a | 361 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 362 | |var| rand_b | 363 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 364 | | rand_b | 365 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ 366 | 367 | ms, clockSeq, err := g.getClockSequence(true, atTime) 368 | if err != nil { 369 | return Nil, err 370 | } 371 | //UUIDv7 features a 48 bit timestamp. First 32bit (4bytes) represents seconds since 1970, followed by 2 bytes for the ms granularity. 372 | u[0] = byte(ms >> 40) //1-6 bytes: big-endian unsigned number of Unix epoch timestamp 373 | u[1] = byte(ms >> 32) 374 | u[2] = byte(ms >> 24) 375 | u[3] = byte(ms >> 16) 376 | u[4] = byte(ms >> 8) 377 | u[5] = byte(ms) 378 | 379 | //Support batching by using a monotonic pseudo-random sequence, 380 | //as described in RFC 9562 section 6.2, Method 1. 381 | //The 6th byte contains the version and partially rand_a data. 382 | //We will lose the most significant bites from the clockSeq (with SetVersion), but it is ok, 383 | //we need the least significant that contains the counter to ensure the monotonic property 384 | binary.BigEndian.PutUint16(u[6:8], clockSeq) // set rand_a with clock seq which is random and monotonic 385 | 386 | //override first 4bits of u[6]. 387 | u.SetVersion(V7) 388 | 389 | //set rand_b 64bits of pseudo-random bits (first 2 will be overridden) 390 | if _, err = io.ReadFull(g.rand, u[8:16]); err != nil { 391 | return Nil, err 392 | } 393 | //override first 2 bits of byte[8] for the variant 394 | u.SetVariant(VariantRFC9562) 395 | 396 | return u, nil 397 | } 398 | 399 | // getClockSequence returns the epoch and clock sequence of the provided time, 400 | // used for generating V1,V6 and V7 UUIDs. 401 | // 402 | // When useUnixTSMs is false, it uses the Coordinated Universal Time (UTC) as a count of 403 | // 100-nanosecond intervals since 00:00:00.00, 15 October 1582 (the date of Gregorian 404 | // reform to the Christian calendar). 405 | func (g *Gen) getClockSequence(useUnixTSMs bool, atTime time.Time) (uint64, uint16, error) { 406 | var err error 407 | g.clockSequenceOnce.Do(func() { 408 | buf := make([]byte, 2) 409 | if _, err = io.ReadFull(g.rand, buf); err != nil { 410 | return 411 | } 412 | g.clockSequence = binary.BigEndian.Uint16(buf) 413 | }) 414 | if err != nil { 415 | return 0, 0, err 416 | } 417 | 418 | g.storageMutex.Lock() 419 | defer g.storageMutex.Unlock() 420 | 421 | var timeNow uint64 422 | if useUnixTSMs { 423 | timeNow = uint64(atTime.UnixMilli()) 424 | } else { 425 | timeNow = g.getEpoch(atTime) 426 | } 427 | // Clock didn't change since last UUID generation. 428 | // Should increase clock sequence. 429 | if timeNow <= g.lastTime { 430 | g.clockSequence++ 431 | } 432 | g.lastTime = timeNow 433 | 434 | return timeNow, g.clockSequence, nil 435 | } 436 | 437 | // Returns the hardware address. 438 | func (g *Gen) getHardwareAddr() ([]byte, error) { 439 | var err error 440 | g.hardwareAddrOnce.Do(func() { 441 | var hwAddr net.HardwareAddr 442 | if hwAddr, err = g.hwAddrFunc(); err == nil { 443 | copy(g.hardwareAddr[:], hwAddr) 444 | return 445 | } 446 | 447 | // Initialize hardwareAddr randomly in case 448 | // of real network interfaces absence. 449 | if _, err = io.ReadFull(g.rand, g.hardwareAddr[:]); err != nil { 450 | return 451 | } 452 | // Set multicast bit as recommended by RFC-9562 453 | g.hardwareAddr[0] |= 0x01 454 | }) 455 | if err != nil { 456 | return []byte{}, err 457 | } 458 | return g.hardwareAddr[:], nil 459 | } 460 | 461 | // Returns the difference between UUID epoch (October 15, 1582) 462 | // and the provided time in 100-nanosecond intervals. 463 | func (g *Gen) getEpoch(atTime time.Time) uint64 { 464 | return epochStart + uint64(atTime.UnixNano()/100) 465 | } 466 | 467 | // Returns the UUID based on the hashing of the namespace UUID and name. 468 | func newFromHash(h hash.Hash, ns UUID, name string) UUID { 469 | u := UUID{} 470 | h.Write(ns[:]) 471 | h.Write([]byte(name)) 472 | copy(u[:], h.Sum(nil)) 473 | 474 | return u 475 | } 476 | 477 | var netInterfaces = net.Interfaces 478 | 479 | // Returns the hardware address. 480 | func defaultHWAddrFunc() (net.HardwareAddr, error) { 481 | ifaces, err := netInterfaces() 482 | if err != nil { 483 | return []byte{}, err 484 | } 485 | for _, iface := range ifaces { 486 | if len(iface.HardwareAddr) >= 6 { 487 | return iface.HardwareAddr, nil 488 | } 489 | } 490 | return []byte{}, ErrNoHwAddressFound 491 | } 492 | -------------------------------------------------------------------------------- /generator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | package uuid 23 | 24 | import ( 25 | "bytes" 26 | "crypto/rand" 27 | "encoding/binary" 28 | "errors" 29 | "fmt" 30 | "net" 31 | "strings" 32 | "testing" 33 | "time" 34 | ) 35 | 36 | func TestGenerator(t *testing.T) { 37 | t.Run("NewV1", testNewV1) 38 | t.Run("NewV3", testNewV3) 39 | t.Run("NewV4", testNewV4) 40 | t.Run("NewV5", testNewV5) 41 | t.Run("NewV6", testNewV6) 42 | t.Run("NewV7", testNewV7) 43 | } 44 | 45 | func testNewV1(t *testing.T) { 46 | t.Run("Basic", testNewV1Basic) 47 | t.Run("BasicWithOptions", testNewV1BasicWithOptions) 48 | t.Run("DifferentAcrossCalls", testNewV1DifferentAcrossCalls) 49 | t.Run("StaleEpoch", testNewV1StaleEpoch) 50 | t.Run("FaultyRand", testNewV1FaultyRand) 51 | t.Run("FaultyRandWithOptions", testNewV1FaultyRandWithOptions) 52 | t.Run("MissingNetwork", testNewV1MissingNetwork) 53 | t.Run("MissingNetworkWithOptions", testNewV1MissingNetworkWithOptions) 54 | t.Run("MissingNetworkFaultyRand", testNewV1MissingNetworkFaultyRand) 55 | t.Run("MissingNetworkFaultyRandWithOptions", testNewV1MissingNetworkFaultyRandWithOptions) 56 | t.Run("AtSpecificTime", testNewV1AtTime) 57 | } 58 | 59 | func TestNewGenWithHWAF(t *testing.T) { 60 | addr := []byte{0, 1, 2, 3, 4, 42} 61 | 62 | fn := func() (net.HardwareAddr, error) { 63 | return addr, nil 64 | } 65 | 66 | var g *Gen 67 | var err error 68 | var uuid UUID 69 | 70 | g = NewGenWithHWAF(fn) 71 | 72 | if g == nil { 73 | t.Fatal("g is unexpectedly nil") 74 | } 75 | 76 | uuid, err = g.NewV1() 77 | if err != nil { 78 | t.Fatalf("g.NewV1() err = %v, want ", err) 79 | } 80 | 81 | node := uuid[10:] 82 | 83 | if !bytes.Equal(addr, node) { 84 | t.Fatalf("node = %v, want %v", node, addr) 85 | } 86 | } 87 | 88 | func testNewV1Basic(t *testing.T) { 89 | u, err := NewV1() 90 | if err != nil { 91 | t.Fatal(err) 92 | } 93 | if got, want := u.Version(), V1; got != want { 94 | t.Errorf("generated UUID with version %d, want %d", got, want) 95 | } 96 | if got, want := u.Variant(), VariantRFC9562; got != want { 97 | t.Errorf("generated UUID with variant %d, want %d", got, want) 98 | } 99 | } 100 | 101 | func testNewV1BasicWithOptions(t *testing.T) { 102 | g := NewGenWithOptions( 103 | WithHWAddrFunc(nil), 104 | WithEpochFunc(nil), 105 | WithRandomReader(nil), 106 | ) 107 | u, err := g.NewV1() 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | if got, want := u.Version(), V1; got != want { 112 | t.Errorf("generated UUID with version %d, want %d", got, want) 113 | } 114 | if got, want := u.Variant(), VariantRFC9562; got != want { 115 | t.Errorf("generated UUID with variant %d, want %d", got, want) 116 | } 117 | } 118 | 119 | func testNewV1DifferentAcrossCalls(t *testing.T) { 120 | u1, err := NewV1() 121 | if err != nil { 122 | t.Fatal(err) 123 | } 124 | u2, err := NewV1() 125 | if err != nil { 126 | t.Fatal(err) 127 | } 128 | if u1 == u2 { 129 | t.Errorf("generated identical UUIDs across calls: %v", u1) 130 | } 131 | } 132 | 133 | func testNewV1StaleEpoch(t *testing.T) { 134 | g := &Gen{ 135 | epochFunc: func() time.Time { 136 | return time.Unix(0, 0) 137 | }, 138 | hwAddrFunc: defaultHWAddrFunc, 139 | rand: rand.Reader, 140 | } 141 | u1, err := g.NewV1() 142 | if err != nil { 143 | t.Fatal(err) 144 | } 145 | u2, err := g.NewV1() 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | if u1 == u2 { 150 | t.Errorf("generated identical UUIDs across calls: %v", u1) 151 | } 152 | } 153 | 154 | func testNewV1FaultyRand(t *testing.T) { 155 | g := &Gen{ 156 | epochFunc: time.Now, 157 | hwAddrFunc: defaultHWAddrFunc, 158 | rand: &faultyReader{ 159 | readToFail: 0, // fail immediately 160 | }, 161 | } 162 | u, err := g.NewV1() 163 | if err == nil { 164 | t.Fatalf("got %v, want error", u) 165 | } 166 | if u != Nil { 167 | t.Fatalf("got %v on error, want Nil", u) 168 | } 169 | } 170 | 171 | func testNewV1MissingNetwork(t *testing.T) { 172 | g := &Gen{ 173 | epochFunc: time.Now, 174 | hwAddrFunc: func() (net.HardwareAddr, error) { 175 | return []byte{}, fmt.Errorf("uuid: no hw address found") 176 | }, 177 | rand: rand.Reader, 178 | } 179 | _, err := g.NewV1() 180 | if err != nil { 181 | t.Errorf("did not handle missing network interfaces: %v", err) 182 | } 183 | } 184 | 185 | func testNewV1MissingNetworkWithOptions(t *testing.T) { 186 | g := NewGenWithOptions( 187 | WithHWAddrFunc(func() (net.HardwareAddr, error) { 188 | return []byte{}, fmt.Errorf("uuid: no hw address found") 189 | }), 190 | ) 191 | _, err := g.NewV1() 192 | if err != nil { 193 | t.Errorf("did not handle missing network interfaces: %v", err) 194 | } 195 | } 196 | 197 | func testNewV1MissingNetworkFaultyRand(t *testing.T) { 198 | g := &Gen{ 199 | epochFunc: time.Now, 200 | hwAddrFunc: func() (net.HardwareAddr, error) { 201 | return []byte{}, fmt.Errorf("uuid: no hw address found") 202 | }, 203 | rand: &faultyReader{ 204 | readToFail: 1, 205 | }, 206 | } 207 | u, err := g.NewV1() 208 | if err == nil { 209 | t.Errorf("did not error on faulty reader and missing network, got %v", u) 210 | } 211 | } 212 | 213 | func testNewV1MissingNetworkFaultyRandWithOptions(t *testing.T) { 214 | g := NewGenWithOptions( 215 | WithHWAddrFunc(func() (net.HardwareAddr, error) { 216 | return []byte{}, fmt.Errorf("uuid: no hw address found") 217 | }), 218 | WithRandomReader(&faultyReader{ 219 | readToFail: 1, 220 | }), 221 | ) 222 | 223 | u, err := g.NewV1() 224 | if err == nil { 225 | t.Errorf("did not error on faulty reader and missing network, got %v", u) 226 | } 227 | } 228 | 229 | func testNewV1AtTime(t *testing.T) { 230 | atTime := time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC) 231 | 232 | u1, err := NewV1AtTime(atTime) 233 | if err != nil { 234 | t.Fatal(err) 235 | } 236 | 237 | u2, err := NewV1AtTime(atTime) 238 | if err != nil { 239 | t.Fatal(err) 240 | } 241 | 242 | // Even with the same timestamp, there is still a monotonically increasing portion, 243 | // so they should not be 100% identical. Bytes 0-7 and 10-16 should be identical. 244 | u1Bytes := u1.Bytes() 245 | u2Bytes := u2.Bytes() 246 | binary.BigEndian.PutUint16(u1Bytes[8:], 0) 247 | binary.BigEndian.PutUint16(u2Bytes[8:], 0) 248 | if !bytes.Equal(u1Bytes, u2Bytes) { 249 | t.Errorf("generated different UUIDs across calls with same timestamp: %v / %v", u1, u2) 250 | } 251 | 252 | ts1, err := TimestampFromV1(u1) 253 | if err != nil { 254 | t.Fatal(err) 255 | } 256 | time1, err := ts1.Time() 257 | if err != nil { 258 | t.Fatal(err) 259 | } 260 | if time1.Equal(atTime) { 261 | t.Errorf("extracted time is incorrect: was %v, expected %v", time1, atTime) 262 | } 263 | ts2, err := TimestampFromV1(u2) 264 | if err != nil { 265 | t.Fatal(err) 266 | } 267 | time2, err := ts2.Time() 268 | if err != nil { 269 | t.Fatal(err) 270 | } 271 | if time2.Equal(atTime) { 272 | t.Errorf("extracted time is incorrect: was %v, expected %v", time1, atTime) 273 | } 274 | } 275 | 276 | func testNewV1FaultyRandWithOptions(t *testing.T) { 277 | g := NewGenWithOptions(WithRandomReader(&faultyReader{ 278 | readToFail: 0, // fail immediately 279 | }), 280 | ) 281 | u, err := g.NewV1() 282 | if err == nil { 283 | t.Errorf("did not error on faulty reader and missing network, got %v", u) 284 | } 285 | } 286 | 287 | func testNewV3(t *testing.T) { 288 | t.Run("Basic", testNewV3Basic) 289 | t.Run("EqualNames", testNewV3EqualNames) 290 | t.Run("DifferentNamespaces", testNewV3DifferentNamespaces) 291 | } 292 | 293 | func testNewV3Basic(t *testing.T) { 294 | ns := NamespaceDNS 295 | name := "www.example.com" 296 | u := NewV3(ns, name) 297 | if got, want := u.Version(), V3; got != want { 298 | t.Errorf("NewV3(%v, %q): got version %d, want %d", ns, name, got, want) 299 | } 300 | if got, want := u.Variant(), VariantRFC9562; got != want { 301 | t.Errorf("NewV3(%v, %q): got variant %d, want %d", ns, name, got, want) 302 | } 303 | want := "5df41881-3aed-3515-88a7-2f4a814cf09e" 304 | if got := u.String(); got != want { 305 | t.Errorf("NewV3(%v, %q) = %q, want %q", ns, name, got, want) 306 | } 307 | } 308 | 309 | func testNewV3EqualNames(t *testing.T) { 310 | ns := NamespaceDNS 311 | name := "example.com" 312 | u1 := NewV3(ns, name) 313 | u2 := NewV3(ns, name) 314 | if u1 != u2 { 315 | t.Errorf("NewV3(%v, %q) generated %v and %v across two calls", ns, name, u1, u2) 316 | } 317 | } 318 | 319 | func testNewV3DifferentNamespaces(t *testing.T) { 320 | name := "example.com" 321 | ns1 := NamespaceDNS 322 | ns2 := NamespaceURL 323 | u1 := NewV3(ns1, name) 324 | u2 := NewV3(ns2, name) 325 | if u1 == u2 { 326 | t.Errorf("NewV3(%v, %q) == NewV3(%d, %q) (%v)", ns1, name, ns2, name, u1) 327 | } 328 | } 329 | 330 | func testNewV4(t *testing.T) { 331 | t.Run("Basic", testNewV4Basic) 332 | t.Run("DifferentAcrossCalls", testNewV4DifferentAcrossCalls) 333 | t.Run("FaultyRand", testNewV4FaultyRand) 334 | t.Run("FaultyRandWithOptions", testNewV4FaultyRandWithOptions) 335 | t.Run("ShortRandomRead", testNewV4ShortRandomRead) 336 | t.Run("ShortRandomReadWithOptions", testNewV4ShortRandomReadWithOptions) 337 | } 338 | 339 | func testNewV4Basic(t *testing.T) { 340 | u, err := NewV4() 341 | if err != nil { 342 | t.Fatal(err) 343 | } 344 | if got, want := u.Version(), V4; got != want { 345 | t.Errorf("got version %d, want %d", got, want) 346 | } 347 | if got, want := u.Variant(), VariantRFC9562; got != want { 348 | t.Errorf("got variant %d, want %d", got, want) 349 | } 350 | } 351 | 352 | func testNewV4DifferentAcrossCalls(t *testing.T) { 353 | u1, err := NewV4() 354 | if err != nil { 355 | t.Fatal(err) 356 | } 357 | u2, err := NewV4() 358 | if err != nil { 359 | t.Fatal(err) 360 | } 361 | if u1 == u2 { 362 | t.Errorf("generated identical UUIDs across calls: %v", u1) 363 | } 364 | } 365 | 366 | func testNewV4FaultyRand(t *testing.T) { 367 | g := &Gen{ 368 | epochFunc: time.Now, 369 | hwAddrFunc: defaultHWAddrFunc, 370 | rand: &faultyReader{ 371 | readToFail: 0, // fail immediately 372 | }, 373 | } 374 | u, err := g.NewV4() 375 | if err == nil { 376 | t.Errorf("got %v, nil error", u) 377 | } 378 | } 379 | 380 | func testNewV4FaultyRandWithOptions(t *testing.T) { 381 | g := NewGenWithOptions( 382 | WithRandomReader(&faultyReader{ 383 | readToFail: 0, // fail immediately 384 | }), 385 | ) 386 | u, err := g.NewV4() 387 | if err == nil { 388 | t.Errorf("got %v, nil error", u) 389 | } 390 | } 391 | 392 | func testNewV4ShortRandomRead(t *testing.T) { 393 | g := &Gen{ 394 | epochFunc: time.Now, 395 | hwAddrFunc: func() (net.HardwareAddr, error) { 396 | return []byte{}, fmt.Errorf("uuid: no hw address found") 397 | }, 398 | rand: bytes.NewReader([]byte{42}), 399 | } 400 | u, err := g.NewV4() 401 | if err == nil { 402 | t.Errorf("got %v, nil error", u) 403 | } 404 | } 405 | 406 | func testNewV4ShortRandomReadWithOptions(t *testing.T) { 407 | g := NewGenWithOptions( 408 | WithHWAddrFunc(func() (net.HardwareAddr, error) { 409 | return []byte{}, fmt.Errorf("uuid: no hw address found") 410 | }), 411 | WithRandomReader(&faultyReader{ 412 | readToFail: 0, // fail immediately 413 | }), 414 | ) 415 | u, err := g.NewV4() 416 | if err == nil { 417 | t.Errorf("got %v, nil error", u) 418 | } 419 | } 420 | 421 | func testNewV5(t *testing.T) { 422 | t.Run("Basic", testNewV5Basic) 423 | t.Run("EqualNames", testNewV5EqualNames) 424 | t.Run("DifferentNamespaces", testNewV5DifferentNamespaces) 425 | } 426 | 427 | func testNewV5Basic(t *testing.T) { 428 | ns := NamespaceDNS 429 | name := "www.example.com" 430 | u := NewV5(ns, name) 431 | if got, want := u.Version(), V5; got != want { 432 | t.Errorf("NewV5(%v, %q): got version %d, want %d", ns, name, got, want) 433 | } 434 | if got, want := u.Variant(), VariantRFC9562; got != want { 435 | t.Errorf("NewV5(%v, %q): got variant %d, want %d", ns, name, got, want) 436 | } 437 | want := "2ed6657d-e927-568b-95e1-2665a8aea6a2" 438 | if got := u.String(); got != want { 439 | t.Errorf("NewV5(%v, %q) = %q, want %q", ns, name, got, want) 440 | } 441 | } 442 | 443 | func testNewV5EqualNames(t *testing.T) { 444 | ns := NamespaceDNS 445 | name := "example.com" 446 | u1 := NewV5(ns, name) 447 | u2 := NewV5(ns, name) 448 | if u1 != u2 { 449 | t.Errorf("NewV5(%v, %q) generated %v and %v across two calls", ns, name, u1, u2) 450 | } 451 | } 452 | 453 | func testNewV5DifferentNamespaces(t *testing.T) { 454 | name := "example.com" 455 | ns1 := NamespaceDNS 456 | ns2 := NamespaceURL 457 | u1 := NewV5(ns1, name) 458 | u2 := NewV5(ns2, name) 459 | if u1 == u2 { 460 | t.Errorf("NewV5(%v, %q) == NewV5(%v, %q) (%v)", ns1, name, ns2, name, u1) 461 | } 462 | } 463 | 464 | func testNewV6(t *testing.T) { 465 | t.Run("Basic", testNewV6Basic) 466 | t.Run("DifferentAcrossCalls", testNewV6DifferentAcrossCalls) 467 | t.Run("StaleEpoch", testNewV6StaleEpoch) 468 | t.Run("StaleEpochWithOptions", testNewV6StaleEpochWithOptions) 469 | t.Run("FaultyRand", testNewV6FaultyRand) 470 | t.Run("FaultyRandWithOptions", testNewV6FaultyRandWithOptions) 471 | t.Run("ShortRandomRead", testNewV6ShortRandomRead) 472 | t.Run("ShortRandomReadWithOptions", testNewV6ShortRandomReadWithOptions) 473 | t.Run("KSortable", testNewV6KSortable) 474 | t.Run("AtSpecificTime", testNewV6AtTime) 475 | } 476 | 477 | func testNewV6Basic(t *testing.T) { 478 | u, err := NewV6() 479 | if err != nil { 480 | t.Fatal(err) 481 | } 482 | if got, want := u.Version(), V6; got != want { 483 | t.Errorf("generated UUID with version %d, want %d", got, want) 484 | } 485 | if got, want := u.Variant(), VariantRFC9562; got != want { 486 | t.Errorf("generated UUID with variant %d, want %d", got, want) 487 | } 488 | } 489 | 490 | func testNewV6DifferentAcrossCalls(t *testing.T) { 491 | u1, err := NewV6() 492 | if err != nil { 493 | t.Fatal(err) 494 | } 495 | u2, err := NewV6() 496 | if err != nil { 497 | t.Fatal(err) 498 | } 499 | if u1 == u2 { 500 | t.Errorf("generated identical UUIDs across calls: %v", u1) 501 | } 502 | } 503 | 504 | func testNewV6StaleEpoch(t *testing.T) { 505 | g := &Gen{ 506 | epochFunc: func() time.Time { 507 | return time.Unix(0, 0) 508 | }, 509 | hwAddrFunc: defaultHWAddrFunc, 510 | rand: rand.Reader, 511 | } 512 | u1, err := g.NewV6() 513 | if err != nil { 514 | t.Fatal(err) 515 | } 516 | u2, err := g.NewV6() 517 | if err != nil { 518 | t.Fatal(err) 519 | } 520 | if u1 == u2 { 521 | t.Errorf("generated identical UUIDs across calls: %v", u1) 522 | } 523 | } 524 | 525 | func testNewV6StaleEpochWithOptions(t *testing.T) { 526 | g := NewGenWithOptions( 527 | WithEpochFunc(func() time.Time { 528 | return time.Unix(0, 0) 529 | }), 530 | ) 531 | u1, err := g.NewV6() 532 | if err != nil { 533 | t.Fatal(err) 534 | } 535 | u2, err := g.NewV6() 536 | if err != nil { 537 | t.Fatal(err) 538 | } 539 | if u1 == u2 { 540 | t.Errorf("generated identical UUIDs across calls: %v", u1) 541 | } 542 | } 543 | 544 | func testNewV6FaultyRand(t *testing.T) { 545 | t.Run("randomData", func(t *testing.T) { 546 | g := &Gen{ 547 | epochFunc: time.Now, 548 | hwAddrFunc: defaultHWAddrFunc, 549 | rand: &faultyReader{ 550 | readToFail: 0, // fail immediately 551 | }, 552 | } 553 | u, err := g.NewV6() 554 | if err == nil { 555 | t.Fatalf("got %v, want error", u) 556 | } 557 | if u != Nil { 558 | t.Fatalf("got %v on error, want Nil", u) 559 | } 560 | }) 561 | 562 | t.Run("clockSequence", func(t *testing.T) { 563 | g := &Gen{ 564 | epochFunc: time.Now, 565 | hwAddrFunc: defaultHWAddrFunc, 566 | rand: &faultyReader{ 567 | readToFail: 1, // fail immediately 568 | }, 569 | } 570 | u, err := g.NewV6() 571 | if err == nil { 572 | t.Fatalf("got %v, want error", u) 573 | } 574 | if u != Nil { 575 | t.Fatalf("got %v on error, want Nil", u) 576 | } 577 | }) 578 | } 579 | 580 | func testNewV6FaultyRandWithOptions(t *testing.T) { 581 | t.Run("randomData", func(t *testing.T) { 582 | g := NewGenWithOptions( 583 | WithRandomReader(&faultyReader{ 584 | readToFail: 0, // fail immediately 585 | }), 586 | ) 587 | u, err := g.NewV6() 588 | if err == nil { 589 | t.Fatalf("got %v, want error", u) 590 | } 591 | if u != Nil { 592 | t.Fatalf("got %v on error, want Nil", u) 593 | } 594 | }) 595 | 596 | t.Run("clockSequence", func(t *testing.T) { 597 | g := NewGenWithOptions( 598 | WithRandomReader(&faultyReader{ 599 | readToFail: 1, // fail immediately 600 | }), 601 | ) 602 | u, err := g.NewV6() 603 | if err == nil { 604 | t.Fatalf("got %v, want error", u) 605 | } 606 | if u != Nil { 607 | t.Fatalf("got %v on error, want Nil", u) 608 | } 609 | }) 610 | } 611 | 612 | func testNewV6ShortRandomRead(t *testing.T) { 613 | g := &Gen{ 614 | epochFunc: time.Now, 615 | rand: bytes.NewReader([]byte{42}), 616 | } 617 | u, err := g.NewV6() 618 | if err == nil { 619 | t.Errorf("got %v, nil error", u) 620 | } 621 | } 622 | 623 | func testNewV6ShortRandomReadWithOptions(t *testing.T) { 624 | g := NewGenWithOptions( 625 | WithRandomReader(bytes.NewReader([]byte{42})), 626 | ) 627 | u, err := g.NewV6() 628 | if err == nil { 629 | t.Errorf("got %v, nil error", u) 630 | } 631 | } 632 | 633 | func testNewV6KSortable(t *testing.T) { 634 | uuids := make([]UUID, 10) 635 | for i := range uuids { 636 | u, err := NewV6() 637 | testErrCheck(t, "NewV6()", "", err) 638 | 639 | uuids[i] = u 640 | 641 | time.Sleep(time.Microsecond) 642 | } 643 | 644 | for i := 1; i < len(uuids); i++ { 645 | p, n := uuids[i-1], uuids[i] 646 | isLess := p.String() < n.String() 647 | if !isLess { 648 | t.Errorf("uuids[%d] (%s) not less than uuids[%d] (%s)", i-1, p, i, n) 649 | } 650 | } 651 | } 652 | 653 | func testNewV6AtTime(t *testing.T) { 654 | atTime := time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC) 655 | 656 | u1, err := NewV6AtTime(atTime) 657 | if err != nil { 658 | t.Fatal(err) 659 | } 660 | 661 | u2, err := NewV6AtTime(atTime) 662 | if err != nil { 663 | t.Fatal(err) 664 | } 665 | 666 | // Even with the same timestamp, there is still a random portion, 667 | // so they should not be 100% identical. Bytes 0-8 are the timestamp so they should be identical. 668 | u1Bytes := u1.Bytes()[:8] 669 | u2Bytes := u2.Bytes()[:8] 670 | if !bytes.Equal(u1Bytes, u2Bytes) { 671 | t.Errorf("generated different UUIDs across calls with same timestamp: %v / %v", u1, u2) 672 | } 673 | 674 | ts1, err := TimestampFromV6(u1) 675 | if err != nil { 676 | t.Fatal(err) 677 | } 678 | time1, err := ts1.Time() 679 | if err != nil { 680 | t.Fatal(err) 681 | } 682 | if time1.Equal(atTime) { 683 | t.Errorf("extracted time is incorrect: was %v, expected %v", time1, atTime) 684 | } 685 | ts2, err := TimestampFromV6(u2) 686 | if err != nil { 687 | t.Fatal(err) 688 | } 689 | time2, err := ts2.Time() 690 | if err != nil { 691 | t.Fatal(err) 692 | } 693 | if time2.Equal(atTime) { 694 | t.Errorf("extracted time is incorrect: was %v, expected %v", time1, atTime) 695 | } 696 | } 697 | 698 | func testNewV7(t *testing.T) { 699 | t.Run("Basic", makeTestNewV7Basic()) 700 | t.Run("TestVector", makeTestNewV7TestVector()) 701 | t.Run("Basic10000000", makeTestNewV7Basic10000000()) 702 | t.Run("DifferentAcrossCalls", makeTestNewV7DifferentAcrossCalls()) 703 | t.Run("StaleEpoch", makeTestNewV7StaleEpoch()) 704 | t.Run("StaleEpochWithOptions", makeTestNewV7StaleEpochWithOptions()) 705 | t.Run("FaultyRand", makeTestNewV7FaultyRand()) 706 | t.Run("FaultyRandWithOptions", makeTestNewV7FaultyRandWithOptions()) 707 | t.Run("ShortRandomRead", makeTestNewV7ShortRandomRead()) 708 | t.Run("ShortRandomReadWithOptions", makeTestNewV7ShortRandomReadWithOptions()) 709 | t.Run("KSortable", makeTestNewV7KSortable()) 710 | t.Run("ClockSequence", makeTestNewV7ClockSequence()) 711 | t.Run("AtSpecificTime", makeTestNewV7AtTime()) 712 | } 713 | 714 | func makeTestNewV7Basic() func(t *testing.T) { 715 | return func(t *testing.T) { 716 | u, err := NewV7() 717 | if err != nil { 718 | t.Fatal(err) 719 | } 720 | if got, want := u.Version(), V7; got != want { 721 | t.Errorf("got version %d, want %d", got, want) 722 | } 723 | if got, want := u.Variant(), VariantRFC9562; got != want { 724 | t.Errorf("got variant %d, want %d", got, want) 725 | } 726 | } 727 | } 728 | 729 | // makeTestNewV7TestVector as defined in Draft04 730 | func makeTestNewV7TestVector() func(t *testing.T) { 731 | return func(t *testing.T) { 732 | pRand := make([]byte, 10) 733 | //first 2 bytes will be read by clockSeq. First 4 bits will be overridden by Version. The next bits should be 0xCC3(3267) 734 | binary.LittleEndian.PutUint16(pRand[:2], uint16(0xCC3)) 735 | //8bytes will be read for rand_b. First 2 bits will be overridden by Variant 736 | binary.LittleEndian.PutUint64(pRand[2:], uint64(0x18C4DC0C0C07398F)) 737 | 738 | g := &Gen{ 739 | epochFunc: func() time.Time { 740 | return time.UnixMilli(1645557742000) 741 | }, 742 | rand: bytes.NewReader(pRand), 743 | } 744 | u, err := g.NewV7() 745 | if err != nil { 746 | t.Fatal(err) 747 | } 748 | if got, want := u.Version(), V7; got != want { 749 | t.Errorf("got version %d, want %d", got, want) 750 | } 751 | if got, want := u.Variant(), VariantRFC9562; got != want { 752 | t.Errorf("got variant %d, want %d", got, want) 753 | } 754 | if got, want := u.String()[:15], "017f22e2-79b0-7"; got != want { 755 | t.Errorf("got version %q, want %q", got, want) 756 | } 757 | } 758 | } 759 | 760 | func makeTestNewV7Basic10000000() func(t *testing.T) { 761 | return func(t *testing.T) { 762 | if testing.Short() { 763 | t.Skip("skipping test in short mode.") 764 | } 765 | 766 | g := NewGen() 767 | 768 | for i := 0; i < 10000000; i++ { 769 | u, err := g.NewV7() 770 | if err != nil { 771 | t.Fatal(err) 772 | } 773 | if got, want := u.Version(), V7; got != want { 774 | t.Errorf("got version %d, want %d", got, want) 775 | } 776 | if got, want := u.Variant(), VariantRFC9562; got != want { 777 | t.Errorf("got variant %d, want %d", got, want) 778 | } 779 | } 780 | } 781 | } 782 | 783 | func makeTestNewV7DifferentAcrossCalls() func(t *testing.T) { 784 | return func(t *testing.T) { 785 | g := NewGen() 786 | 787 | u1, err := g.NewV7() 788 | if err != nil { 789 | t.Fatal(err) 790 | } 791 | u2, err := g.NewV7() 792 | if err != nil { 793 | t.Fatal(err) 794 | } 795 | if u1 == u2 { 796 | t.Errorf("generated identical UUIDs across calls: %v", u1) 797 | } 798 | } 799 | } 800 | 801 | func makeTestNewV7StaleEpoch() func(t *testing.T) { 802 | return func(t *testing.T) { 803 | g := &Gen{ 804 | epochFunc: func() time.Time { 805 | return time.Unix(0, 0) 806 | }, 807 | rand: rand.Reader, 808 | } 809 | u1, err := g.NewV7() 810 | if err != nil { 811 | t.Fatal(err) 812 | } 813 | u2, err := g.NewV7() 814 | if err != nil { 815 | t.Fatal(err) 816 | } 817 | if u1 == u2 { 818 | t.Errorf("generated identical UUIDs across calls: %v", u1) 819 | } 820 | } 821 | } 822 | 823 | func makeTestNewV7StaleEpochWithOptions() func(t *testing.T) { 824 | return func(t *testing.T) { 825 | g := NewGenWithOptions( 826 | WithEpochFunc(func() time.Time { 827 | return time.Unix(0, 0) 828 | }), 829 | ) 830 | u1, err := g.NewV7() 831 | if err != nil { 832 | t.Fatal(err) 833 | } 834 | u2, err := g.NewV7() 835 | if err != nil { 836 | t.Fatal(err) 837 | } 838 | if u1 == u2 { 839 | t.Errorf("generated identical UUIDs across calls: %v", u1) 840 | } 841 | } 842 | } 843 | 844 | func makeTestNewV7FaultyRand() func(t *testing.T) { 845 | return func(t *testing.T) { 846 | g := &Gen{ 847 | epochFunc: time.Now, 848 | rand: &faultyReader{ 849 | readToFail: 0, 850 | }, 851 | } 852 | u, err := g.NewV7() 853 | if err == nil { 854 | t.Errorf("got %v, nil error for clockSequence", u) 855 | } 856 | 857 | g = &Gen{ 858 | epochFunc: time.Now, 859 | rand: &faultyReader{ 860 | readToFail: 1, 861 | }, 862 | } 863 | u, err = g.NewV7() 864 | if err == nil { 865 | t.Errorf("got %v, nil error rand_b", u) 866 | } 867 | } 868 | } 869 | 870 | func makeTestNewV7FaultyRandWithOptions() func(t *testing.T) { 871 | return func(t *testing.T) { 872 | g := NewGenWithOptions( 873 | WithRandomReader(&faultyReader{ 874 | readToFail: 0, // fail immediately 875 | }), 876 | ) 877 | u, err := g.NewV7() 878 | if err == nil { 879 | t.Errorf("got %v, nil error", u) 880 | } 881 | } 882 | } 883 | 884 | func makeTestNewV7ShortRandomRead() func(t *testing.T) { 885 | return func(t *testing.T) { 886 | g := &Gen{ 887 | epochFunc: time.Now, 888 | rand: bytes.NewReader([]byte{42}), 889 | } 890 | u, err := g.NewV7() 891 | if err == nil { 892 | t.Errorf("got %v, nil error", u) 893 | } 894 | } 895 | } 896 | 897 | func makeTestNewV7ShortRandomReadWithOptions() func(t *testing.T) { 898 | return func(t *testing.T) { 899 | g := NewGenWithOptions( 900 | WithRandomReader(bytes.NewReader([]byte{42})), 901 | ) 902 | u, err := g.NewV7() 903 | if err == nil { 904 | t.Errorf("got %v, nil error", u) 905 | } 906 | } 907 | } 908 | 909 | func makeTestNewV7KSortable() func(t *testing.T) { 910 | return func(t *testing.T) { 911 | uuids := make([]UUID, 10) 912 | for i := range uuids { 913 | u, err := NewV7() 914 | testErrCheck(t, "NewV7()", "", err) 915 | 916 | uuids[i] = u 917 | time.Sleep(time.Millisecond) 918 | } 919 | 920 | for i := 1; i < len(uuids); i++ { 921 | p, n := uuids[i-1], uuids[i] 922 | isLess := p.String() < n.String() 923 | if !isLess { 924 | t.Errorf("uuids[%d] (%s) not less than uuids[%d] (%s)", i-1, p, i, n) 925 | } 926 | } 927 | } 928 | } 929 | 930 | func makeTestNewV7ClockSequence() func(t *testing.T) { 931 | return func(t *testing.T) { 932 | if testing.Short() { 933 | t.Skip("skipping test in short mode.") 934 | } 935 | 936 | g := NewGen() 937 | //always return the same TS 938 | g.epochFunc = func() time.Time { 939 | return time.UnixMilli(1645557742000) 940 | } 941 | //by being KSortable with the same timestamp, it means the sequence is Not empty, and it is monotonic 942 | uuids := make([]UUID, 10) 943 | for i := range uuids { 944 | u, err := g.NewV7() 945 | testErrCheck(t, "NewV7()", "", err) 946 | uuids[i] = u 947 | } 948 | 949 | for i := 1; i < len(uuids); i++ { 950 | p, n := uuids[i-1], uuids[i] 951 | isLess := p.String() < n.String() 952 | if !isLess { 953 | t.Errorf("uuids[%d] (%s) not less than uuids[%d] (%s)", i-1, p, i, n) 954 | } 955 | } 956 | } 957 | } 958 | 959 | func makeTestNewV7AtTime() func(t *testing.T) { 960 | return func(t *testing.T) { 961 | atTime := time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC) 962 | 963 | u1, err := NewV7AtTime(atTime) 964 | if err != nil { 965 | t.Fatal(err) 966 | } 967 | 968 | u2, err := NewV7AtTime(atTime) 969 | if err != nil { 970 | t.Fatal(err) 971 | } 972 | 973 | // Even with the same timestamp, there is still a random portion, 974 | // so they should not be 100% identical. Bytes 0-6 are the timestamp so they should be identical. 975 | u1Bytes := u1.Bytes()[:7] 976 | u2Bytes := u2.Bytes()[:7] 977 | if !bytes.Equal(u1Bytes, u2Bytes) { 978 | t.Errorf("generated different UUIDs across calls with same timestamp: %v / %v", u1, u2) 979 | } 980 | 981 | ts1, err := TimestampFromV7(u1) 982 | if err != nil { 983 | t.Fatal(err) 984 | } 985 | time1, err := ts1.Time() 986 | if err != nil { 987 | t.Fatal(err) 988 | } 989 | if time1.Equal(atTime) { 990 | t.Errorf("extracted time is incorrect: was %v, expected %v", time1, atTime) 991 | } 992 | ts2, err := TimestampFromV7(u2) 993 | if err != nil { 994 | t.Fatal(err) 995 | } 996 | time2, err := ts2.Time() 997 | if err != nil { 998 | t.Fatal(err) 999 | } 1000 | if time2.Equal(atTime) { 1001 | t.Errorf("extracted time is incorrect: was %v, expected %v", time1, atTime) 1002 | } 1003 | } 1004 | } 1005 | 1006 | func TestDefaultHWAddrFunc(t *testing.T) { 1007 | tests := []struct { 1008 | n string 1009 | fn func() ([]net.Interface, error) 1010 | hw net.HardwareAddr 1011 | e string 1012 | }{ 1013 | { 1014 | n: "Error", 1015 | fn: func() ([]net.Interface, error) { 1016 | return nil, errors.New("controlled failure") 1017 | }, 1018 | e: "controlled failure", 1019 | }, 1020 | { 1021 | n: "NoValidHWAddrReturned", 1022 | fn: func() ([]net.Interface, error) { 1023 | s := []net.Interface{ 1024 | { 1025 | Index: 1, 1026 | MTU: 1500, 1027 | Name: "test0", 1028 | HardwareAddr: net.HardwareAddr{1, 2, 3, 4}, 1029 | }, 1030 | { 1031 | Index: 2, 1032 | MTU: 1500, 1033 | Name: "lo0", 1034 | HardwareAddr: net.HardwareAddr{5, 6, 7, 8}, 1035 | }, 1036 | } 1037 | 1038 | return s, nil 1039 | }, 1040 | e: "uuid: no HW address found", 1041 | }, 1042 | { 1043 | n: "ValidHWAddrReturned", 1044 | fn: func() ([]net.Interface, error) { 1045 | s := []net.Interface{ 1046 | { 1047 | Index: 1, 1048 | MTU: 1500, 1049 | Name: "test0", 1050 | HardwareAddr: net.HardwareAddr{1, 2, 3, 4}, 1051 | }, 1052 | { 1053 | Index: 2, 1054 | MTU: 1500, 1055 | Name: "lo0", 1056 | HardwareAddr: net.HardwareAddr{5, 6, 7, 8, 9, 0}, 1057 | }, 1058 | } 1059 | 1060 | return s, nil 1061 | }, 1062 | hw: net.HardwareAddr{5, 6, 7, 8, 9, 0}, 1063 | }, 1064 | } 1065 | 1066 | for _, tt := range tests { 1067 | t.Run(tt.n, func(t *testing.T) { 1068 | // set the netInterfaces variable (function) for the test 1069 | // and then set it back to default in the deferred function 1070 | netInterfaces = tt.fn 1071 | defer func() { 1072 | netInterfaces = net.Interfaces 1073 | }() 1074 | 1075 | var hw net.HardwareAddr 1076 | var err error 1077 | 1078 | hw, err = defaultHWAddrFunc() 1079 | 1080 | if len(tt.e) > 0 { 1081 | if err == nil { 1082 | t.Fatalf("defaultHWAddrFunc() error = , should contain %q", tt.e) 1083 | } 1084 | 1085 | if !strings.Contains(err.Error(), tt.e) { 1086 | t.Fatalf("defaultHWAddrFunc() error = %q, should contain %q", err.Error(), tt.e) 1087 | } 1088 | 1089 | return 1090 | } 1091 | 1092 | if err != nil && tt.e == "" { 1093 | t.Fatalf("defaultHWAddrFunc() error = %q, want ", err.Error()) 1094 | } 1095 | 1096 | if !bytes.Equal(hw, tt.hw) { 1097 | t.Fatalf("hw = %#v, want %#v", hw, tt.hw) 1098 | } 1099 | }) 1100 | } 1101 | } 1102 | 1103 | func BenchmarkGenerator(b *testing.B) { 1104 | b.Run("NewV1", func(b *testing.B) { 1105 | for i := 0; i < b.N; i++ { 1106 | NewV1() 1107 | } 1108 | }) 1109 | b.Run("NewV3", func(b *testing.B) { 1110 | for i := 0; i < b.N; i++ { 1111 | NewV3(NamespaceDNS, "www.example.com") 1112 | } 1113 | }) 1114 | b.Run("NewV4", func(b *testing.B) { 1115 | for i := 0; i < b.N; i++ { 1116 | NewV4() 1117 | } 1118 | }) 1119 | b.Run("NewV5", func(b *testing.B) { 1120 | for i := 0; i < b.N; i++ { 1121 | NewV5(NamespaceDNS, "www.example.com") 1122 | } 1123 | }) 1124 | b.Run("NewV6", func(b *testing.B) { 1125 | for i := 0; i < b.N; i++ { 1126 | NewV6() 1127 | } 1128 | }) 1129 | b.Run("NewV7", func(b *testing.B) { 1130 | for i := 0; i < b.N; i++ { 1131 | NewV7() 1132 | } 1133 | }) 1134 | } 1135 | 1136 | type faultyReader struct { 1137 | callsNum int 1138 | readToFail int // Read call number to fail 1139 | } 1140 | 1141 | func (r *faultyReader) Read(dest []byte) (int, error) { 1142 | r.callsNum++ 1143 | if (r.callsNum - 1) == r.readToFail { 1144 | return 0, fmt.Errorf("io: reader is faulty") 1145 | } 1146 | return rand.Read(dest) 1147 | } 1148 | 1149 | // testErrCheck looks to see if errContains is a substring of err.Error(). If 1150 | // not, this calls t.Fatal(). It also calls t.Fatal() if there was an error, but 1151 | // errContains is empty. Returns true if you should continue running the test, 1152 | // or false if you should stop the test. 1153 | func testErrCheck(t *testing.T, name string, errContains string, err error) bool { 1154 | t.Helper() 1155 | 1156 | if len(errContains) > 0 { 1157 | if err == nil { 1158 | t.Fatalf("%s error = , should contain %q", name, errContains) 1159 | return false 1160 | } 1161 | 1162 | if errStr := err.Error(); !strings.Contains(errStr, errContains) { 1163 | t.Fatalf("%s error = %q, should contain %q", name, errStr, errContains) 1164 | return false 1165 | } 1166 | 1167 | return false 1168 | } 1169 | 1170 | if err != nil && len(errContains) == 0 { 1171 | t.Fatalf("%s unexpected error: %v", name, err) 1172 | return false 1173 | } 1174 | 1175 | return true 1176 | } 1177 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofrs/uuid/v5 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /sql.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | package uuid 23 | 24 | import ( 25 | "database/sql" 26 | "database/sql/driver" 27 | "fmt" 28 | ) 29 | 30 | var _ driver.Valuer = UUID{} 31 | var _ sql.Scanner = (*UUID)(nil) 32 | 33 | // Value implements the driver.Valuer interface. 34 | func (u UUID) Value() (driver.Value, error) { 35 | return u.String(), nil 36 | } 37 | 38 | // Scan implements the sql.Scanner interface. 39 | // A 16-byte slice will be handled by UnmarshalBinary, while 40 | // a longer byte slice or a string will be handled by UnmarshalText. 41 | func (u *UUID) Scan(src interface{}) error { 42 | switch src := src.(type) { 43 | case UUID: // support gorm convert from UUID to NullUUID 44 | *u = src 45 | return nil 46 | 47 | case []byte: 48 | if len(src) == Size { 49 | return u.UnmarshalBinary(src) 50 | } 51 | return u.UnmarshalText(src) 52 | 53 | case string: 54 | uu, err := FromString(src) 55 | *u = uu 56 | return err 57 | } 58 | 59 | return fmt.Errorf("%w %T to UUID", ErrTypeConvertError, src) 60 | } 61 | 62 | // NullUUID can be used with the standard sql package to represent a 63 | // UUID value that can be NULL in the database. 64 | type NullUUID struct { 65 | UUID UUID 66 | Valid bool 67 | } 68 | 69 | // Value implements the driver.Valuer interface. 70 | func (u NullUUID) Value() (driver.Value, error) { 71 | if !u.Valid { 72 | return nil, nil 73 | } 74 | // Delegate to UUID Value function 75 | return u.UUID.Value() 76 | } 77 | 78 | // Scan implements the sql.Scanner interface. 79 | func (u *NullUUID) Scan(src interface{}) error { 80 | if src == nil { 81 | u.UUID, u.Valid = Nil, false 82 | return nil 83 | } 84 | 85 | // Delegate to UUID Scan function 86 | u.Valid = true 87 | return u.UUID.Scan(src) 88 | } 89 | 90 | var nullJSON = []byte("null") 91 | 92 | // MarshalJSON marshals the NullUUID as null or the nested UUID 93 | func (u NullUUID) MarshalJSON() ([]byte, error) { 94 | if !u.Valid { 95 | return nullJSON, nil 96 | } 97 | var buf [38]byte 98 | buf[0] = '"' 99 | encodeCanonical(buf[1:37], u.UUID) 100 | buf[37] = '"' 101 | return buf[:], nil 102 | } 103 | 104 | // UnmarshalJSON unmarshals a NullUUID 105 | func (u *NullUUID) UnmarshalJSON(b []byte) error { 106 | if string(b) == "null" { 107 | u.UUID, u.Valid = Nil, false 108 | return nil 109 | } 110 | if n := len(b); n >= 2 && b[0] == '"' { 111 | b = b[1 : n-1] 112 | } 113 | err := u.UUID.UnmarshalText(b) 114 | u.Valid = (err == nil) 115 | return err 116 | } 117 | -------------------------------------------------------------------------------- /sql_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | package uuid 23 | 24 | import ( 25 | "encoding/json" 26 | "fmt" 27 | "testing" 28 | ) 29 | 30 | func TestSQL(t *testing.T) { 31 | t.Run("Value", testSQLValue) 32 | t.Run("Scan", func(t *testing.T) { 33 | t.Run("Binary", testSQLScanBinary) 34 | t.Run("String", testSQLScanString) 35 | t.Run("Text", testSQLScanText) 36 | t.Run("Unsupported", testSQLScanUnsupported) 37 | t.Run("Nil", testSQLScanNil) 38 | }) 39 | } 40 | 41 | func testSQLValue(t *testing.T) { 42 | v, err := codecTestUUID.Value() 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | got, ok := v.(string) 47 | if !ok { 48 | t.Fatalf("Value() returned %T, want string", v) 49 | } 50 | if want := codecTestUUID.String(); got != want { 51 | t.Errorf("Value() == %q, want %q", got, want) 52 | } 53 | } 54 | 55 | func testSQLScanBinary(t *testing.T) { 56 | got := UUID{} 57 | err := got.Scan(codecTestData) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | if got != codecTestUUID { 62 | t.Errorf("Scan(%x): got %v, want %v", codecTestData, got, codecTestUUID) 63 | } 64 | } 65 | 66 | func testSQLScanString(t *testing.T) { 67 | s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 68 | got := UUID{} 69 | err := got.Scan(s) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | if got != codecTestUUID { 74 | t.Errorf("Scan(%q): got %v, want %v", s, got, codecTestUUID) 75 | } 76 | } 77 | 78 | func testSQLScanText(t *testing.T) { 79 | text := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") 80 | got := UUID{} 81 | err := got.Scan(text) 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | if got != codecTestUUID { 86 | t.Errorf("Scan(%q): got %v, want %v", text, got, codecTestUUID) 87 | } 88 | } 89 | 90 | func testSQLScanUnsupported(t *testing.T) { 91 | unsupported := []interface{}{ 92 | true, 93 | 42, 94 | } 95 | for _, v := range unsupported { 96 | got := UUID{} 97 | err := got.Scan(v) 98 | if err == nil { 99 | t.Errorf("Scan(%T) succeeded, got %v", v, got) 100 | } 101 | } 102 | } 103 | 104 | func testSQLScanNil(t *testing.T) { 105 | got := UUID{} 106 | err := got.Scan(nil) 107 | if err == nil { 108 | t.Errorf("Scan(nil) succeeded, got %v", got) 109 | } 110 | } 111 | 112 | func TestNullUUID(t *testing.T) { 113 | t.Run("Value", func(t *testing.T) { 114 | t.Run("Nil", testNullUUIDValueNil) 115 | t.Run("Valid", testNullUUIDValueValid) 116 | }) 117 | 118 | t.Run("Scan", func(t *testing.T) { 119 | t.Run("Nil", testNullUUIDScanNil) 120 | t.Run("Valid", testNullUUIDScanValid) 121 | t.Run("UUID", testNullUUIDScanUUID) 122 | }) 123 | 124 | t.Run("MarshalJSON", func(t *testing.T) { 125 | t.Run("Nil", testNullUUIDMarshalJSONNil) 126 | t.Run("Null", testNullUUIDMarshalJSONNull) 127 | t.Run("Valid", testNullUUIDMarshalJSONValid) 128 | }) 129 | 130 | t.Run("UnmarshalJSON", func(t *testing.T) { 131 | t.Run("Nil", testNullUUIDUnmarshalJSONNil) 132 | t.Run("Null", testNullUUIDUnmarshalJSONNull) 133 | t.Run("Valid", testNullUUIDUnmarshalJSONValid) 134 | t.Run("Malformed", testNullUUIDUnmarshalJSONMalformed) 135 | }) 136 | } 137 | 138 | func testNullUUIDValueNil(t *testing.T) { 139 | nu := NullUUID{} 140 | got, err := nu.Value() 141 | if got != nil { 142 | t.Errorf("null NullUUID.Value returned non-nil driver.Value") 143 | } 144 | if err != nil { 145 | t.Errorf("null NullUUID.Value returned non-nil error") 146 | } 147 | } 148 | 149 | func testNullUUIDValueValid(t *testing.T) { 150 | nu := NullUUID{ 151 | Valid: true, 152 | UUID: codecTestUUID, 153 | } 154 | got, err := nu.Value() 155 | if err != nil { 156 | t.Fatal(err) 157 | } 158 | s, ok := got.(string) 159 | if !ok { 160 | t.Errorf("Value() returned %T, want string", got) 161 | } 162 | want := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 163 | if s != want { 164 | t.Errorf("%v.Value() == %s, want %s", nu, s, want) 165 | } 166 | } 167 | 168 | func testNullUUIDScanNil(t *testing.T) { 169 | u := NullUUID{} 170 | err := u.Scan(nil) 171 | if err != nil { 172 | t.Fatal(err) 173 | } 174 | if u.Valid { 175 | t.Error("NullUUID is valid after Scan(nil)") 176 | } 177 | if u.UUID != Nil { 178 | t.Errorf("NullUUID.UUID is %v after Scan(nil) want Nil", u.UUID) 179 | } 180 | } 181 | 182 | func testNullUUIDScanValid(t *testing.T) { 183 | s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 184 | u := NullUUID{} 185 | err := u.Scan(s) 186 | if err != nil { 187 | t.Fatal(err) 188 | } 189 | if !u.Valid { 190 | t.Errorf("Valid == false after Scan(%q)", s) 191 | } 192 | if u.UUID != codecTestUUID { 193 | t.Errorf("UUID == %v after Scan(%q), want %v", u.UUID, s, codecTestUUID) 194 | } 195 | } 196 | 197 | func testNullUUIDScanUUID(t *testing.T) { 198 | u := NullUUID{} 199 | err := u.Scan(codecTestUUID) 200 | if err != nil { 201 | t.Fatal(err) 202 | } 203 | if !u.Valid { 204 | t.Errorf("Valid == false after scan(%v)", codecTestUUID) 205 | } 206 | if u.UUID != codecTestUUID { 207 | t.Errorf("UUID == %v after Scan(%v), want %v", u.UUID, codecTestUUID, codecTestUUID) 208 | } 209 | } 210 | 211 | func testNullUUIDMarshalJSONNil(t *testing.T) { 212 | u := NullUUID{Valid: true} 213 | 214 | data, err := u.MarshalJSON() 215 | if err != nil { 216 | t.Fatalf("(%#v).MarshalJSON err want: , got: %v", u, err) 217 | } 218 | 219 | dataStr := string(data) 220 | 221 | if dataStr != fmt.Sprintf("%q", Nil) { 222 | t.Fatalf("(%#v).MarshalJSON value want: %s, got: %s", u, Nil, dataStr) 223 | } 224 | } 225 | 226 | func testNullUUIDMarshalJSONValid(t *testing.T) { 227 | u := NullUUID{ 228 | Valid: true, 229 | UUID: codecTestUUID, 230 | } 231 | 232 | data, err := u.MarshalJSON() 233 | if err != nil { 234 | t.Fatalf("(%#v).MarshalJSON err want: , got: %v", u, err) 235 | } 236 | 237 | dataStr := string(data) 238 | 239 | if dataStr != fmt.Sprintf("%q", codecTestUUID) { 240 | t.Fatalf("(%#v).MarshalJSON value want: %s, got: %s", u, codecTestUUID, dataStr) 241 | } 242 | } 243 | 244 | func testNullUUIDMarshalJSONNull(t *testing.T) { 245 | u := NullUUID{} 246 | 247 | data, err := u.MarshalJSON() 248 | if err != nil { 249 | t.Fatalf("(%#v).MarshalJSON err want: , got: %v", u, err) 250 | } 251 | 252 | dataStr := string(data) 253 | 254 | if dataStr != "null" { 255 | t.Fatalf("(%#v).MarshalJSON value want: %s, got: %s", u, "null", dataStr) 256 | } 257 | } 258 | 259 | func testNullUUIDUnmarshalJSONNil(t *testing.T) { 260 | var u NullUUID 261 | 262 | data := []byte(`"00000000-0000-0000-0000-000000000000"`) 263 | 264 | if err := json.Unmarshal(data, &u); err != nil { 265 | t.Fatalf("json.Unmarshal err = %v, want ", err) 266 | } 267 | 268 | if !u.Valid { 269 | t.Fatalf("u.Valid = false, want true") 270 | } 271 | 272 | if u.UUID != Nil { 273 | t.Fatalf("u.UUID = %v, want %v", u.UUID, Nil) 274 | } 275 | } 276 | 277 | func testNullUUIDUnmarshalJSONNull(t *testing.T) { 278 | var u NullUUID 279 | 280 | data := []byte(`null`) 281 | 282 | if err := json.Unmarshal(data, &u); err != nil { 283 | t.Fatalf("json.Unmarshal err = %v, want ", err) 284 | } 285 | 286 | if u.Valid { 287 | t.Fatalf("u.Valid = true, want false") 288 | } 289 | 290 | if u.UUID != Nil { 291 | t.Fatalf("u.UUID = %v, want %v", u.UUID, Nil) 292 | } 293 | } 294 | 295 | func testNullUUIDUnmarshalJSONValid(t *testing.T) { 296 | var u NullUUID 297 | 298 | data := []byte(`"6ba7b810-9dad-11d1-80b4-00c04fd430c8"`) 299 | 300 | if err := json.Unmarshal(data, &u); err != nil { 301 | t.Fatalf("json.Unmarshal err = %v, want ", err) 302 | } 303 | 304 | if !u.Valid { 305 | t.Fatalf("u.Valid = false, want true") 306 | } 307 | 308 | if u.UUID != codecTestUUID { 309 | t.Fatalf("u.UUID = %v, want %v", u.UUID, Nil) 310 | } 311 | } 312 | 313 | func testNullUUIDUnmarshalJSONMalformed(t *testing.T) { 314 | var u NullUUID 315 | 316 | data := []byte(`257`) 317 | 318 | if err := json.Unmarshal(data, &u); err == nil { 319 | t.Fatal("json.Unmarshal err = , want error") 320 | } 321 | } 322 | 323 | func BenchmarkNullMarshalJSON(b *testing.B) { 324 | b.Run("Valid", func(b *testing.B) { 325 | u, err := FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") 326 | if err != nil { 327 | b.Fatal(err) 328 | } 329 | n := NullUUID{UUID: u, Valid: true} 330 | for i := 0; i < b.N; i++ { 331 | n.MarshalJSON() 332 | } 333 | }) 334 | b.Run("Invalid", func(b *testing.B) { 335 | n := NullUUID{Valid: false} 336 | for i := 0; i < b.N; i++ { 337 | n.MarshalJSON() 338 | } 339 | }) 340 | } 341 | 342 | func BenchmarkNullUnmarshalJSON(b *testing.B) { 343 | baseUUID, err := FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") 344 | if err != nil { 345 | b.Fatal(err) 346 | } 347 | data, err := json.Marshal(&baseUUID) 348 | if err != nil { 349 | b.Fatal(err) 350 | } 351 | 352 | b.Run("Valid", func(b *testing.B) { 353 | var u NullUUID 354 | for i := 0; i < b.N; i++ { 355 | u.UnmarshalJSON(data) 356 | } 357 | }) 358 | b.Run("Invalid", func(b *testing.B) { 359 | invalid := []byte("null") 360 | var n NullUUID 361 | for i := 0; i < b.N; i++ { 362 | n.UnmarshalJSON(invalid) 363 | } 364 | }) 365 | } 366 | -------------------------------------------------------------------------------- /uuid.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | // Package uuid provides implementations of the Universally Unique Identifier 23 | // (UUID), as specified in RFC-9562 (formerly RFC-4122). 24 | // 25 | // RFC-9562[1] provides the specification for versions 1, 3, 4, 5, 6 and 7. 26 | // 27 | // DCE 1.1[2] provides the specification for version 2, but version 2 support 28 | // was removed from this package in v4 due to some concerns with the 29 | // specification itself. Reading the spec, it seems that it would result in 30 | // generating UUIDs that aren't very unique. In having read the spec it seemed 31 | // that our implementation did not meet the spec. It also seems to be at-odds 32 | // with RFC 9562, meaning we would need quite a bit of special code to support 33 | // it. Lastly, there were no Version 2 implementations that we could find to 34 | // ensure we were understanding the specification correctly. 35 | // 36 | // [1] https://tools.ietf.org/html/rfc9562 37 | // [2] http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 38 | package uuid 39 | 40 | import ( 41 | "encoding/binary" 42 | "encoding/hex" 43 | "fmt" 44 | "time" 45 | ) 46 | 47 | // Size of a UUID in bytes. 48 | const Size = 16 49 | 50 | // UUID is an array type to represent the value of a UUID, as defined in RFC-9562. 51 | type UUID [Size]byte 52 | 53 | // UUID versions. 54 | const ( 55 | _ byte = iota 56 | V1 // Version 1 (date-time and MAC address) 57 | _ // Version 2 (date-time and MAC address, DCE security version) [removed] 58 | V3 // Version 3 (namespace name-based) 59 | V4 // Version 4 (random) 60 | V5 // Version 5 (namespace name-based) 61 | V6 // Version 6 (k-sortable timestamp and random data, field-compatible with v1) 62 | V7 // Version 7 (k-sortable timestamp and random data) 63 | _ // Version 8 (k-sortable timestamp, meant for custom implementations) [not implemented] 64 | ) 65 | 66 | // UUID layout variants. 67 | const ( 68 | VariantNCS byte = iota 69 | VariantRFC9562 70 | VariantMicrosoft 71 | VariantFuture 72 | ) 73 | 74 | // Backward-compatible variant for RFC 4122 75 | const VariantRFC4122 = VariantRFC9562 76 | 77 | // UUID DCE domains. 78 | const ( 79 | DomainPerson = iota 80 | DomainGroup 81 | DomainOrg 82 | ) 83 | 84 | // Timestamp is the count of 100-nanosecond intervals since 00:00:00.00, 85 | // 15 October 1582 within a V1 or V6 UUID, or as a common intermediate 86 | // representation of the (Unix Millisecond) timestamp within a V7 UUID. 87 | // This type has no meaning for other UUID versions since they don't 88 | // have an embedded timestamp. 89 | type Timestamp uint64 90 | 91 | const _100nsPerSecond = 10000000 92 | 93 | // Time returns the time.Time representation of a Timestamp. 94 | // 95 | // The resulting time.Time: 96 | // - Contains only wall clock time (no monotonic clock component) 97 | // - Uses the local system timezone for the location 98 | func (t Timestamp) Time() (time.Time, error) { 99 | secs := uint64(t) / _100nsPerSecond 100 | nsecs := 100 * (uint64(t) % _100nsPerSecond) 101 | 102 | return time.Unix(int64(secs)-(epochStart/_100nsPerSecond), int64(nsecs)), nil 103 | } 104 | 105 | // TimestampFromV1 returns the Timestamp embedded within a V1 UUID. 106 | // Returns an error if the UUID is any version other than 1. 107 | func TimestampFromV1(u UUID) (Timestamp, error) { 108 | if u.Version() != 1 { 109 | err := fmt.Errorf("%w %s is version %d, not version 1", ErrInvalidVersion, u, u.Version()) 110 | return 0, err 111 | } 112 | 113 | low := binary.BigEndian.Uint32(u[0:4]) 114 | mid := binary.BigEndian.Uint16(u[4:6]) 115 | hi := binary.BigEndian.Uint16(u[6:8]) & 0xfff 116 | 117 | return Timestamp(uint64(low) + (uint64(mid) << 32) + (uint64(hi) << 48)), nil 118 | } 119 | 120 | // TimestampFromV6 returns the Timestamp embedded within a V6 UUID. This 121 | // function returns an error if the UUID is any version other than 6. 122 | func TimestampFromV6(u UUID) (Timestamp, error) { 123 | if u.Version() != 6 { 124 | return 0, fmt.Errorf("%w %s is version %d, not version 6", ErrInvalidVersion, u, u.Version()) 125 | } 126 | 127 | hi := binary.BigEndian.Uint32(u[0:4]) 128 | mid := binary.BigEndian.Uint16(u[4:6]) 129 | low := binary.BigEndian.Uint16(u[6:8]) & 0xfff 130 | 131 | return Timestamp(uint64(low) + (uint64(mid) << 12) + (uint64(hi) << 28)), nil 132 | } 133 | 134 | // TimestampFromV7 returns the Timestamp embedded within a V7 UUID. This 135 | // function returns an error if the UUID is any version other than 7. 136 | func TimestampFromV7(u UUID) (Timestamp, error) { 137 | if u.Version() != 7 { 138 | return 0, fmt.Errorf("%w %s is version %d, not version 7", ErrInvalidVersion, u, u.Version()) 139 | } 140 | 141 | t := 0 | 142 | (int64(u[0]) << 40) | 143 | (int64(u[1]) << 32) | 144 | (int64(u[2]) << 24) | 145 | (int64(u[3]) << 16) | 146 | (int64(u[4]) << 8) | 147 | int64(u[5]) 148 | 149 | // UUIDv7 stores MS since 1970-01-01 00:00:00, but the Timestamp 150 | // type stores 100-nanosecond increments since 1582-10-15 00:00:00. 151 | // This conversion multiplies ms by 10,000 to get 100-ns chunks and adds 152 | // the difference between October 1582 and January 1970. 153 | tsNanos := epochStart + (t * 10000) 154 | return Timestamp(tsNanos), nil 155 | } 156 | 157 | // Nil is the nil UUID, as specified in RFC-9562, that has all 128 bits set to 158 | // zero. 159 | var Nil = UUID{} 160 | 161 | // Max is the maximum UUID, as specified in RFC-9562, that has all 128 bits 162 | // set to one. 163 | var Max = UUID{ 164 | 0xFF, 165 | 0xFF, 166 | 0xFF, 167 | 0xFF, 168 | 0xFF, 169 | 0xFF, 170 | 0xFF, 171 | 0xFF, 172 | 0xFF, 173 | 0xFF, 174 | 0xFF, 175 | 0xFF, 176 | 0xFF, 177 | 0xFF, 178 | 0xFF, 179 | 0xFF, 180 | } 181 | 182 | // Predefined namespace UUIDs. 183 | var ( 184 | NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) 185 | NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) 186 | NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) 187 | NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) 188 | ) 189 | 190 | // IsNil returns if the UUID is equal to the nil UUID 191 | func (u UUID) IsNil() bool { 192 | return u == Nil 193 | } 194 | 195 | // Version returns the algorithm version used to generate the UUID. 196 | func (u UUID) Version() byte { 197 | return u[6] >> 4 198 | } 199 | 200 | // Variant returns the UUID layout variant. 201 | func (u UUID) Variant() byte { 202 | switch { 203 | case (u[8] >> 7) == 0x00: 204 | return VariantNCS 205 | case (u[8] >> 6) == 0x02: 206 | return VariantRFC9562 207 | case (u[8] >> 5) == 0x06: 208 | return VariantMicrosoft 209 | case (u[8] >> 5) == 0x07: 210 | fallthrough 211 | default: 212 | return VariantFuture 213 | } 214 | } 215 | 216 | // Bytes returns a byte slice representation of the UUID. 217 | func (u UUID) Bytes() []byte { 218 | return u[:] 219 | } 220 | 221 | // encodeCanonical encodes the canonical RFC-9562 form of UUID u into the 222 | // first 36 bytes dst. 223 | func encodeCanonical(dst []byte, u UUID) { 224 | const hextable = "0123456789abcdef" 225 | dst[8] = '-' 226 | dst[13] = '-' 227 | dst[18] = '-' 228 | dst[23] = '-' 229 | for i, x := range [16]byte{ 230 | 0, 2, 4, 6, 231 | 9, 11, 232 | 14, 16, 233 | 19, 21, 234 | 24, 26, 28, 30, 32, 34, 235 | } { 236 | c := u[i] 237 | dst[x] = hextable[c>>4] 238 | dst[x+1] = hextable[c&0x0f] 239 | } 240 | } 241 | 242 | // String returns a canonical RFC-9562 string representation of the UUID: 243 | // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. 244 | func (u UUID) String() string { 245 | var buf [36]byte 246 | encodeCanonical(buf[:], u) 247 | return string(buf[:]) 248 | } 249 | 250 | // Format implements fmt.Formatter for UUID values. 251 | // 252 | // The behavior is as follows: 253 | // The 'x' and 'X' verbs output only the hex digits of the UUID, using a-f for 'x' and A-F for 'X'. 254 | // The 'v', '+v', 's' and 'q' verbs return the canonical RFC-9562 string representation. 255 | // The 'S' verb returns the RFC-9562 format, but with capital hex digits. 256 | // The '#v' verb returns the "Go syntax" representation, which is a 16 byte array initializer. 257 | // All other verbs not handled directly by the fmt package (like '%p') are unsupported and will return 258 | // "%!verb(uuid.UUID=value)" as recommended by the fmt package. 259 | func (u UUID) Format(f fmt.State, c rune) { 260 | if c == 'v' && f.Flag('#') { 261 | fmt.Fprintf(f, "%#v", [Size]byte(u)) 262 | return 263 | } 264 | switch c { 265 | case 'x', 'X': 266 | b := make([]byte, 32) 267 | hex.Encode(b, u[:]) 268 | if c == 'X' { 269 | toUpperHex(b) 270 | } 271 | _, _ = f.Write(b) 272 | case 'v', 's', 'S': 273 | b, _ := u.MarshalText() 274 | if c == 'S' { 275 | toUpperHex(b) 276 | } 277 | _, _ = f.Write(b) 278 | case 'q': 279 | b := make([]byte, 38) 280 | b[0] = '"' 281 | encodeCanonical(b[1:], u) 282 | b[37] = '"' 283 | _, _ = f.Write(b) 284 | default: 285 | // invalid/unsupported format verb 286 | fmt.Fprintf(f, "%%!%c(uuid.UUID=%s)", c, u.String()) 287 | } 288 | } 289 | 290 | func toUpperHex(b []byte) { 291 | for i, c := range b { 292 | if 'a' <= c && c <= 'f' { 293 | b[i] = c - ('a' - 'A') 294 | } 295 | } 296 | } 297 | 298 | // SetVersion sets the version bits. 299 | func (u *UUID) SetVersion(v byte) { 300 | u[6] = (u[6] & 0x0f) | (v << 4) 301 | } 302 | 303 | // SetVariant sets the variant bits. 304 | func (u *UUID) SetVariant(v byte) { 305 | switch v { 306 | case VariantNCS: 307 | u[8] = (u[8]&(0xff>>1) | (0x00 << 7)) 308 | case VariantRFC9562: 309 | u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) 310 | case VariantMicrosoft: 311 | u[8] = (u[8]&(0xff>>3) | (0x06 << 5)) 312 | case VariantFuture: 313 | fallthrough 314 | default: 315 | u[8] = (u[8]&(0xff>>3) | (0x07 << 5)) 316 | } 317 | } 318 | 319 | // Must is a helper that wraps a call to a function returning (UUID, error) 320 | // and panics if the error is non-nil. It is intended for use in variable 321 | // initializations such as 322 | // 323 | // var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")) 324 | func Must(u UUID, err error) UUID { 325 | if err != nil { 326 | panic(err) 327 | } 328 | return u 329 | } 330 | -------------------------------------------------------------------------------- /uuid_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013-2018 by Maxim Bublis 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | package uuid 23 | 24 | import ( 25 | "bytes" 26 | "fmt" 27 | "io" 28 | "testing" 29 | "time" 30 | ) 31 | 32 | func TestUUID(t *testing.T) { 33 | t.Run("IsNil", testUUIDIsNil) 34 | t.Run("Bytes", testUUIDBytes) 35 | t.Run("String", testUUIDString) 36 | t.Run("Version", testUUIDVersion) 37 | t.Run("Variant", testUUIDVariant) 38 | t.Run("SetVersion", testUUIDSetVersion) 39 | t.Run("SetVariant", testUUIDSetVariant) 40 | t.Run("Format", testUUIDFormat) 41 | } 42 | 43 | func testUUIDIsNil(t *testing.T) { 44 | u := UUID{} 45 | got := u.IsNil() 46 | want := true 47 | if got != want { 48 | t.Errorf("%v.IsNil() = %t, want %t", u, got, want) 49 | } 50 | } 51 | 52 | func testUUIDBytes(t *testing.T) { 53 | got := codecTestUUID.Bytes() 54 | want := codecTestData 55 | if !bytes.Equal(got, want) { 56 | t.Errorf("%v.Bytes() = %x, want %x", codecTestUUID, got, want) 57 | } 58 | } 59 | 60 | func testUUIDString(t *testing.T) { 61 | got := NamespaceDNS.String() 62 | want := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 63 | if got != want { 64 | t.Errorf("%v.String() = %q, want %q", NamespaceDNS, got, want) 65 | } 66 | } 67 | 68 | func testUUIDVersion(t *testing.T) { 69 | u := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 70 | if got, want := u.Version(), V1; got != want { 71 | t.Errorf("%v.Version() == %d, want %d", u, got, want) 72 | } 73 | } 74 | 75 | func testUUIDVariant(t *testing.T) { 76 | tests := []struct { 77 | u UUID 78 | want byte 79 | }{ 80 | { 81 | u: UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 82 | want: VariantNCS, 83 | }, 84 | { 85 | u: UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 86 | want: VariantRFC9562, 87 | }, 88 | { 89 | u: UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 90 | want: VariantMicrosoft, 91 | }, 92 | { 93 | u: UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 94 | want: VariantFuture, 95 | }, 96 | } 97 | for _, tt := range tests { 98 | if got := tt.u.Variant(); got != tt.want { 99 | t.Errorf("%v.Variant() == %d, want %d", tt.u, got, tt.want) 100 | } 101 | } 102 | } 103 | 104 | func testUUIDSetVersion(t *testing.T) { 105 | u := UUID{} 106 | want := V4 107 | u.SetVersion(want) 108 | if got := u.Version(); got != want { 109 | t.Errorf("%v.Version() == %d after SetVersion(%d)", u, got, want) 110 | } 111 | } 112 | 113 | func testUUIDSetVariant(t *testing.T) { 114 | variants := []byte{ 115 | VariantNCS, 116 | VariantRFC9562, 117 | VariantMicrosoft, 118 | VariantFuture, 119 | } 120 | for _, want := range variants { 121 | u := UUID{} 122 | u.SetVariant(want) 123 | if got := u.Variant(); got != want { 124 | t.Errorf("%v.Variant() == %d after SetVariant(%d)", u, got, want) 125 | } 126 | } 127 | } 128 | 129 | func testUUIDFormat(t *testing.T) { 130 | val := Must(FromString("12345678-90ab-cdef-1234-567890abcdef")) 131 | tests := []struct { 132 | u UUID 133 | f string 134 | want string 135 | }{ 136 | {u: val, f: "%s", want: "12345678-90ab-cdef-1234-567890abcdef"}, 137 | {u: val, f: "%S", want: "12345678-90AB-CDEF-1234-567890ABCDEF"}, 138 | {u: val, f: "%q", want: `"12345678-90ab-cdef-1234-567890abcdef"`}, 139 | {u: val, f: "%x", want: "1234567890abcdef1234567890abcdef"}, 140 | {u: val, f: "%X", want: "1234567890ABCDEF1234567890ABCDEF"}, 141 | {u: val, f: "%v", want: "12345678-90ab-cdef-1234-567890abcdef"}, 142 | {u: val, f: "%+v", want: "12345678-90ab-cdef-1234-567890abcdef"}, 143 | {u: val, f: "%#v", want: "[16]uint8{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}"}, 144 | {u: val, f: "%T", want: "uuid.UUID"}, 145 | {u: val, f: "%t", want: "%!t(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 146 | {u: val, f: "%b", want: "%!b(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 147 | {u: val, f: "%c", want: "%!c(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 148 | {u: val, f: "%d", want: "%!d(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 149 | {u: val, f: "%e", want: "%!e(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 150 | {u: val, f: "%E", want: "%!E(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 151 | {u: val, f: "%f", want: "%!f(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 152 | {u: val, f: "%F", want: "%!F(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 153 | {u: val, f: "%g", want: "%!g(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 154 | {u: val, f: "%G", want: "%!G(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 155 | {u: val, f: "%o", want: "%!o(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 156 | {u: val, f: "%U", want: "%!U(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, 157 | } 158 | for _, tt := range tests { 159 | got := fmt.Sprintf(tt.f, tt.u) 160 | if tt.want != got { 161 | t.Errorf(`Format("%s") got %s, want %s`, tt.f, got, tt.want) 162 | } 163 | } 164 | } 165 | 166 | func TestMust(t *testing.T) { 167 | sentinel := fmt.Errorf("uuid: sentinel error") 168 | defer func() { 169 | r := recover() 170 | if r == nil { 171 | t.Fatalf("did not panic, want %v", sentinel) 172 | } 173 | err, ok := r.(error) 174 | if !ok { 175 | t.Fatalf("panicked with %T, want error (%v)", r, sentinel) 176 | } 177 | if err != sentinel { 178 | t.Fatalf("panicked with %v, want %v", err, sentinel) 179 | } 180 | }() 181 | fn := func() (UUID, error) { 182 | return Nil, sentinel 183 | } 184 | Must(fn()) 185 | } 186 | 187 | func TestTimeFromTimestamp(t *testing.T) { 188 | tests := []struct { 189 | t Timestamp 190 | want time.Time 191 | }{ 192 | // a zero timestamp represents October 15, 1582 at midnight UTC 193 | {t: Timestamp(0), want: time.Date(1582, 10, 15, 0, 0, 0, 0, time.UTC)}, 194 | // a one value is 100ns later 195 | {t: Timestamp(1), want: time.Date(1582, 10, 15, 0, 0, 0, 100, time.UTC)}, 196 | // 10 million 100ns intervals later is one second 197 | {t: Timestamp(10000000), want: time.Date(1582, 10, 15, 0, 0, 1, 0, time.UTC)}, 198 | {t: Timestamp(60 * 10000000), want: time.Date(1582, 10, 15, 0, 1, 0, 0, time.UTC)}, 199 | {t: Timestamp(60 * 60 * 10000000), want: time.Date(1582, 10, 15, 1, 0, 0, 0, time.UTC)}, 200 | {t: Timestamp(24 * 60 * 60 * 10000000), want: time.Date(1582, 10, 16, 0, 0, 0, 0, time.UTC)}, 201 | {t: Timestamp(365 * 24 * 60 * 60 * 10000000), want: time.Date(1583, 10, 15, 0, 0, 0, 0, time.UTC)}, 202 | // maximum timestamp value in a UUID is the year 5236 203 | {t: Timestamp(uint64(1<<60 - 1)), want: time.Date(5236, 03, 31, 21, 21, 0, 684697500, time.UTC)}, 204 | } 205 | for _, tt := range tests { 206 | got, _ := tt.t.Time() 207 | if !got.Equal(tt.want) { 208 | t.Errorf("%v.Time() == %v, want %v", tt.t, got, tt.want) 209 | } 210 | } 211 | } 212 | 213 | func TestTimestampFromV1(t *testing.T) { 214 | tests := []struct { 215 | u UUID 216 | want Timestamp 217 | wanterr bool 218 | }{ 219 | {u: Must(NewV4()), wanterr: true}, 220 | {u: Must(FromString("00000000-0000-1000-0000-000000000000")), want: 0}, 221 | {u: Must(FromString("424f137e-a2aa-11e8-98d0-529269fb1459")), want: 137538640775418750}, 222 | {u: Must(FromString("ffffffff-ffff-1fff-ffff-ffffffffffff")), want: Timestamp(1<<60 - 1)}, 223 | } 224 | for _, tt := range tests { 225 | got, goterr := TimestampFromV1(tt.u) 226 | if tt.wanterr && goterr == nil { 227 | t.Errorf("TimestampFromV1(%v) want error, got %v", tt.u, got) 228 | } else if tt.want != got { 229 | t.Errorf("TimestampFromV1(%v) got %v, want %v", tt.u, got, tt.want) 230 | } 231 | } 232 | } 233 | 234 | func TestTimestampFromV6(t *testing.T) { 235 | tests := []struct { 236 | u UUID 237 | want Timestamp 238 | wanterr bool 239 | }{ 240 | {u: Must(NewV1()), wanterr: true}, 241 | {u: Must(FromString("00000000-0000-6000-0000-000000000000")), want: 0}, 242 | {u: Must(FromString("1ec06cff-e9b1-621c-8627-ba3fd7e551c9")), want: 138493178941215260}, 243 | {u: Must(FromString("ffffffff-ffff-6fff-ffff-ffffffffffff")), want: Timestamp(1<<60 - 1)}, 244 | } 245 | 246 | for _, tt := range tests { 247 | got, err := TimestampFromV6(tt.u) 248 | 249 | switch { 250 | case tt.wanterr && err == nil: 251 | t.Errorf("TimestampFromV6(%v) want error, got %v", tt.u, got) 252 | 253 | case tt.want != got: 254 | t.Errorf("TimestampFromV6(%v) got %v, want %v", tt.u, got, tt.want) 255 | } 256 | } 257 | } 258 | 259 | func TestTimestampFromV7(t *testing.T) { 260 | tests := []struct { 261 | u UUID 262 | want Timestamp 263 | wanterr bool 264 | }{ 265 | // These non-V7 versions should not be able to be provided to TimestampFromV7 266 | {u: Must(NewV1()), wanterr: true}, 267 | {u: NewV3(NamespaceDNS, "a.example.com"), wanterr: true}, 268 | // v7 is unix_ts_ms, so zero value time is unix epoch 269 | {u: Must(FromString("00000000-0000-7000-0000-000000000000")), want: 122192928000000000}, 270 | {u: Must(FromString("018a8fec-3ced-7164-995f-93c80cbdc575")), want: 139139245386050000}, 271 | // Calculated as `(1<<48)-1` milliseconds, times 10,000 (100-ns units per ms), plus epoch offset from 1970 to 1582. 272 | {u: Must(FromString("ffffffff-ffff-7fff-bfff-ffffffffffff")), want: 2936942695106550000}, 273 | } 274 | for _, tt := range tests { 275 | got, err := TimestampFromV7(tt.u) 276 | 277 | switch { 278 | case tt.wanterr && err == nil: 279 | t.Errorf("TimestampFromV7(%v) want error, got %v", tt.u, got) 280 | 281 | case tt.want != got: 282 | t.Errorf("TimestampFromV7(%v) got %v, want %v", tt.u, got, tt.want) 283 | } 284 | } 285 | } 286 | 287 | func TestMinMaxTimestamps(t *testing.T) { 288 | tests := []struct { 289 | u UUID 290 | want time.Time 291 | }{ 292 | 293 | // v1 min and max 294 | {u: Must(FromString("00000000-0000-1000-8000-000000000000")), want: time.Date(1582, 10, 15, 0, 0, 0, 0, time.UTC)}, //1582-10-15 0:00:00 (UTC) 295 | {u: Must(FromString("ffffffff-ffff-1fff-bfff-ffffffffffff")), want: time.Date(5236, 3, 31, 21, 21, 00, 684697500, time.UTC)}, //5236-03-31 21:21:00 (UTC) 296 | 297 | // v6 min and max 298 | {u: Must(FromString("00000000-0000-6000-8000-000000000000")), want: time.Date(1582, 10, 15, 0, 0, 0, 0, time.UTC)}, //1582-10-15 0:00:00 (UTC) 299 | {u: Must(FromString("ffffffff-ffff-6fff-bfff-ffffffffffff")), want: time.Date(5236, 3, 31, 21, 21, 00, 684697500, time.UTC)}, //5236-03-31 21:21:00 (UTC) 300 | 301 | // v7 min and max 302 | {u: Must(FromString("00000000-0000-7000-8000-000000000000")), want: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, //1970-01-01 0:00:00 (UTC) 303 | {u: Must(FromString("ffffffff-ffff-7fff-bfff-ffffffffffff")), want: time.Date(10889, 8, 2, 5, 31, 50, 655000000, time.UTC)}, //10889-08-02 5:31:50.655 (UTC) 304 | } 305 | for _, tt := range tests { 306 | var got Timestamp 307 | var err error 308 | var functionName string 309 | 310 | switch tt.u.Version() { 311 | case V1: 312 | functionName = "TimestampFromV1" 313 | got, err = TimestampFromV1(tt.u) 314 | case V6: 315 | functionName = "TimestampFromV6" 316 | got, err = TimestampFromV6(tt.u) 317 | case V7: 318 | functionName = "TimestampFromV7" 319 | got, err = TimestampFromV7(tt.u) 320 | } 321 | 322 | if err != nil { 323 | t.Errorf(functionName+"(%v) got error %v, want %v", tt.u, err, tt.want) 324 | } 325 | 326 | tm, err := got.Time() 327 | if err != nil { 328 | t.Errorf(functionName+"(%v) got error %v, want %v", tt.u, err, tt.want) 329 | } 330 | 331 | if !tt.want.Equal(tm) { 332 | t.Errorf(functionName+"(%v) got %v, want %v", tt.u, tm.UTC(), tt.want) 333 | } 334 | } 335 | } 336 | 337 | func BenchmarkFormat(b *testing.B) { 338 | var tests = []string{ 339 | "%s", 340 | "%S", 341 | "%q", 342 | "%x", 343 | "%X", 344 | "%v", 345 | "%+v", 346 | "%#v", 347 | } 348 | for _, x := range tests { 349 | b.Run(x[1:], func(b *testing.B) { 350 | for i := 0; i < b.N; i++ { 351 | fmt.Fprintf(io.Discard, x, &codecTestUUID) 352 | } 353 | }) 354 | } 355 | } 356 | 357 | var uuidBenchmarkSink UUID 358 | var timestampBenchmarkSink Timestamp 359 | var timeBenchmarkSink time.Time 360 | 361 | func BenchmarkTimestampFrom(b *testing.B) { 362 | var err error 363 | numbUUIDs := 1000 364 | if testing.Short() { 365 | numbUUIDs = 10 366 | } 367 | 368 | funcs := []struct { 369 | name string 370 | create func() (UUID, error) 371 | timestamp func(UUID) (Timestamp, error) 372 | }{ 373 | {"v1", NewV1, TimestampFromV1}, 374 | {"v6", NewV6, TimestampFromV6}, 375 | {"v7", NewV7, TimestampFromV7}, 376 | } 377 | 378 | for _, fns := range funcs { 379 | b.Run(fns.name, func(b *testing.B) { 380 | // Make sure we don't just encode the same string over and over again as that will hit memory caches unrealistically 381 | uuids := make([]UUID, numbUUIDs) 382 | for i := 0; i < numbUUIDs; i++ { 383 | uuids[i] = Must(fns.create()) 384 | if !testing.Short() { 385 | time.Sleep(1 * time.Millisecond) 386 | } 387 | } 388 | b.ResetTimer() 389 | for i := 0; i < b.N; i++ { 390 | timestampBenchmarkSink, err = fns.timestamp(uuids[i%numbUUIDs]) 391 | 392 | if err != nil { 393 | b.Fatal(err) 394 | } 395 | } 396 | }) 397 | } 398 | } 399 | 400 | func BenchmarkTimestampTime(b *testing.B) { 401 | var err error 402 | numbUUIDs := 1000 403 | if testing.Short() { 404 | numbUUIDs = 10 405 | } 406 | 407 | funcs := []struct { 408 | name string 409 | create func() (UUID, error) 410 | timestamp func(UUID) (Timestamp, error) 411 | }{ 412 | {"v1", NewV1, TimestampFromV1}, 413 | {"v6", NewV6, TimestampFromV6}, 414 | {"v7", NewV7, TimestampFromV7}, 415 | } 416 | 417 | for _, fns := range funcs { 418 | b.Run(fns.name, func(b *testing.B) { 419 | // Make sure we don't just encode the same string over and over again as that will hit memory caches unrealistically 420 | uuids := make([]UUID, numbUUIDs) 421 | timestamps := make([]Timestamp, numbUUIDs) 422 | for i := 0; i < numbUUIDs; i++ { 423 | uuids[i] = Must(fns.create()) 424 | timestamps[i], err = fns.timestamp(uuids[i]) 425 | if err != nil { 426 | b.Fatal(err) 427 | } 428 | if !testing.Short() { 429 | time.Sleep(1 * time.Millisecond) 430 | } 431 | } 432 | b.ResetTimer() 433 | for i := 0; i < b.N; i++ { 434 | timeBenchmarkSink, err = timestamps[i%numbUUIDs].Time() 435 | if err != nil { 436 | b.Fatal(err) 437 | } 438 | } 439 | }) 440 | } 441 | 442 | } 443 | --------------------------------------------------------------------------------