pax_global_header00006660000000000000000000000064146201525750014521gustar00rootroot0000000000000052 comment=4a2de11450acc855a8ffceafdfabdf2edb91d945 uuid-5.2.0/000077500000000000000000000000001462015257500124735ustar00rootroot00000000000000uuid-5.2.0/.github/000077500000000000000000000000001462015257500140335ustar00rootroot00000000000000uuid-5.2.0/.github/dependabot.yml000066400000000000000000000003041462015257500166600ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: weekly - package-ecosystem: gomod directory: / schedule: interval: weekly uuid-5.2.0/.github/workflows/000077500000000000000000000000001462015257500160705ustar00rootroot00000000000000uuid-5.2.0/.github/workflows/codeql.yml000066400000000000000000000055061462015257500200700ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: ["master"] pull_request: # The branches below must be a subset of the branches above branches: ["master"] schedule: - cron: "0 0 * * 1" permissions: contents: read jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: ["go"] # CodeQL supports [ $supported-codeql-languages ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Harden Runner uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@4f0ceda83afa9bc55df7b6c611b81435fa53d987 # v2.25.4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@4f0ceda83afa9bc55df7b6c611b81435fa53d987 # v2.25.4 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@4f0ceda83afa9bc55df7b6c611b81435fa53d987 # v2.25.4 with: category: "/language:${{matrix.language}}" uuid-5.2.0/.github/workflows/dependency-review.yml000066400000000000000000000017111462015257500222300ustar00rootroot00000000000000# Dependency Review Action # # This Action will scan dependency manifest files that change as part of a Pull Request, # surfacing known-vulnerable versions of the packages declared or updated in the PR. # Once installed, if the workflow run is marked as required, # PRs introducing known-vulnerable packages will be blocked from merging. # # Source repository: https://github.com/actions/dependency-review-action name: 'Dependency Review' on: [pull_request] permissions: contents: read jobs: dependency-review: runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit - name: 'Checkout Repository' uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: 'Dependency Review' uses: actions/dependency-review-action@0c155c5e8556a497adf53f2c18edabf945ed8e70 # v4.3.2 uuid-5.2.0/.github/workflows/go.yml000066400000000000000000000030511462015257500172170ustar00rootroot00000000000000name: Go on: push: branches: [ master ] pull_request: branches: [ master ] permissions: contents: read jobs: build: name: Build + Test Stable runs-on: ubuntu-latest env: GO111MODULE: auto steps: - name: Harden Runner uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit - name: Build uses: actions/setup-go@bfdd3570ce990073878bf10f6b2d79082de49492 # v2.2.0 with: go-version: '1.19.x' - name: Check out code into the Go module directory uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Build run: go build -v ./... - name: Test run: go test ./... -race -coverprofile=coverage.txt -covermode=atomic - name: Coverage uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # v4.3.1 build-legacy: name: Build + Test Previous Stable runs-on: ubuntu-latest env: GO111MODULE: auto steps: - name: Harden Runner uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit - name: Build uses: actions/setup-go@bfdd3570ce990073878bf10f6b2d79082de49492 # v2.2.0 with: go-version: '1.18.x' - name: Check out code into the Go module directory uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Build run: go build -v ./... - name: Test run: go test ./... uuid-5.2.0/.github/workflows/scorecards.yml000066400000000000000000000055201462015257500207450ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '20 7 * * 2' push: branches: ["master"] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write contents: read actions: read steps: - name: "Checkout code" uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecards on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@4f0ceda83afa9bc55df7b6c611b81435fa53d987 # v2.25.4 with: sarif_file: results.sarif uuid-5.2.0/.gitignore000066400000000000000000000003641462015257500144660ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # binary bundle generated by go-fuzz uuid-fuzz.zip uuid-5.2.0/.pre-commit-config.yaml000066400000000000000000000005011462015257500167500ustar00rootroot00000000000000repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.16.3 hooks: - id: gitleaks - repo: https://github.com/golangci/golangci-lint rev: v1.52.2 hooks: - id: golangci-lint - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace uuid-5.2.0/LICENSE000066400000000000000000000020721462015257500135010ustar00rootroot00000000000000Copyright (C) 2013-2018 by Maxim Bublis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. uuid-5.2.0/README.md000066400000000000000000000061211462015257500137520ustar00rootroot00000000000000# UUID [![License](https://img.shields.io/github/license/gofrs/uuid.svg)](https://github.com/gofrs/uuid/blob/master/LICENSE) [![Build Status](https://github.com/gofrs/uuid/actions/workflows/go.yml/badge.svg)](https://github.com/gofrs/uuid/actions/workflows/go.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/gofrs/uuid/v5.svg)](https://pkg.go.dev/github.com/gofrs/uuid/v5) [![Coverage Status](https://codecov.io/gh/gofrs/uuid/branch/master/graphs/badge.svg?branch=master)](https://codecov.io/gh/gofrs/uuid/) [![Go Report Card](https://goreportcard.com/badge/github.com/gofrs/uuid)](https://goreportcard.com/report/github.com/gofrs/uuid) Package uuid provides a pure Go implementation of Universally Unique Identifiers (UUID) variant as defined in RFC-9562. This package supports both the creation and parsing of UUIDs in different formats. This package supports the following UUID versions: * Version 1, based on timestamp and MAC address * Version 3, based on MD5 hashing of a named value * Version 4, based on random numbers * Version 5, based on SHA-1 hashing of a named value * Version 6, a k-sortable id based on timestamp, and field-compatible with v1 * Version 7, a k-sortable id based on timestamp ## Project History This project was originally forked from the [github.com/satori/go.uuid](https://github.com/satori/go.uuid) repository after it appeared to be no longer maintained, while exhibiting [critical flaws](https://github.com/satori/go.uuid/issues/73). We have decided to take over this project to ensure it receives regular maintenance for the benefit of the larger Go community. We'd like to thank Maxim Bublis for his hard work on the original iteration of the package. ## License This source code of this package is released under the MIT License. Please see the [LICENSE](https://github.com/gofrs/uuid/blob/master/LICENSE) for the full content of the license. ## Recommended Package Version We recommend using v2.0.0+ of this package, as versions prior to 2.0.0 were created before our fork of the original package and have some known deficiencies. ## Requirements This package requires Go 1.19 or later ## Usage Here is a quick overview of how to use this package. For more detailed documentation, please see the [GoDoc Page](http://godoc.org/github.com/gofrs/uuid). ```go package main import ( "log" "github.com/gofrs/uuid/v5" ) // Create a Version 4 UUID, panicking on error. // Use this form to initialize package-level variables. var u1 = uuid.Must(uuid.NewV4()) func main() { // Create a Version 4 UUID. u2, err := uuid.NewV4() if err != nil { log.Fatalf("failed to generate UUID: %v", err) } log.Printf("generated Version 4 UUID %v", u2) // Parse a UUID from a string. s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" u3, err := uuid.FromString(s) if err != nil { log.Fatalf("failed to parse UUID %q: %v", s, err) } log.Printf("successfully parsed UUID %v", u3) } ``` ## References * [RFC-9562](https://tools.ietf.org/html/rfc9563) (replaces RFC-4122) * [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01) uuid-5.2.0/codec.go000066400000000000000000000143301462015257500141000ustar00rootroot00000000000000// Copyright (C) 2013-2018 by Maxim Bublis // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package uuid import "fmt" // FromBytes returns a UUID generated from the raw byte slice input. // It will return an error if the slice isn't 16 bytes long. func FromBytes(input []byte) (UUID, error) { u := UUID{} err := u.UnmarshalBinary(input) return u, err } // FromBytesOrNil returns a UUID generated from the raw byte slice input. // Same behavior as FromBytes(), but returns uuid.Nil instead of an error. func FromBytesOrNil(input []byte) UUID { uuid, err := FromBytes(input) if err != nil { return Nil } return uuid } func fromHexChar(c byte) byte { switch { case '0' <= c && c <= '9': return c - '0' case 'a' <= c && c <= 'f': return c - 'a' + 10 case 'A' <= c && c <= 'F': return c - 'A' + 10 } return 255 } // Parse parses the UUID stored in the string text. Parsing and supported // formats are the same as UnmarshalText. func (u *UUID) Parse(s string) error { switch len(s) { case 32: // hash case 36: // canonical case 34, 38: if s[0] != '{' || s[len(s)-1] != '}' { return fmt.Errorf("%w %q", ErrIncorrectFormatInString, s) } s = s[1 : len(s)-1] case 41, 45: if s[:9] != "urn:uuid:" { return fmt.Errorf("%w %q", ErrIncorrectFormatInString, s[:9]) } s = s[9:] default: return fmt.Errorf("%w %d in string %q", ErrIncorrectLength, len(s), s) } // canonical if len(s) == 36 { if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { return fmt.Errorf("%w %q", ErrIncorrectFormatInString, s) } for i, x := range [16]byte{ 0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34, } { v1 := fromHexChar(s[x]) v2 := fromHexChar(s[x+1]) if v1|v2 == 255 { return ErrInvalidFormat } u[i] = (v1 << 4) | v2 } return nil } // hash like for i := 0; i < 32; i += 2 { v1 := fromHexChar(s[i]) v2 := fromHexChar(s[i+1]) if v1|v2 == 255 { return ErrInvalidFormat } u[i/2] = (v1 << 4) | v2 } return nil } // FromString returns a UUID parsed from the input string. // Input is expected in a form accepted by UnmarshalText. func FromString(text string) (UUID, error) { var u UUID err := u.Parse(text) return u, err } // FromStringOrNil returns a UUID parsed from the input string. // Same behavior as FromString(), but returns uuid.Nil instead of an error. func FromStringOrNil(input string) UUID { uuid, err := FromString(input) if err != nil { return Nil } return uuid } // MarshalText implements the encoding.TextMarshaler interface. // The encoding is the same as returned by the String() method. func (u UUID) MarshalText() ([]byte, error) { var buf [36]byte encodeCanonical(buf[:], u) return buf[:], nil } // UnmarshalText implements the encoding.TextUnmarshaler interface. // Following formats are supported: // // "6ba7b810-9dad-11d1-80b4-00c04fd430c8", // "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", // "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" // "6ba7b8109dad11d180b400c04fd430c8" // "{6ba7b8109dad11d180b400c04fd430c8}", // "urn:uuid:6ba7b8109dad11d180b400c04fd430c8" // // ABNF for supported UUID text representation follows: // // URN := 'urn' // UUID-NID := 'uuid' // // hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | // 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | // 'A' | 'B' | 'C' | 'D' | 'E' | 'F' // // hexoct := hexdig hexdig // 2hexoct := hexoct hexoct // 4hexoct := 2hexoct 2hexoct // 6hexoct := 4hexoct 2hexoct // 12hexoct := 6hexoct 6hexoct // // hashlike := 12hexoct // canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct // // plain := canonical | hashlike // uuid := canonical | hashlike | braced | urn // // braced := '{' plain '}' | '{' hashlike '}' // urn := URN ':' UUID-NID ':' plain func (u *UUID) UnmarshalText(b []byte) error { switch len(b) { case 32: // hash case 36: // canonical case 34, 38: if b[0] != '{' || b[len(b)-1] != '}' { return fmt.Errorf("%w %q", ErrIncorrectFormatInString, b) } b = b[1 : len(b)-1] case 41, 45: if string(b[:9]) != "urn:uuid:" { return fmt.Errorf("%w %q", ErrIncorrectFormatInString, b[:9]) } b = b[9:] default: return fmt.Errorf("%w %d in string %q", ErrIncorrectLength, len(b), b) } if len(b) == 36 { if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { return fmt.Errorf("%w %q", ErrIncorrectFormatInString, b) } for i, x := range [16]byte{ 0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34, } { v1 := fromHexChar(b[x]) v2 := fromHexChar(b[x+1]) if v1|v2 == 255 { return ErrInvalidFormat } u[i] = (v1 << 4) | v2 } return nil } for i := 0; i < 32; i += 2 { v1 := fromHexChar(b[i]) v2 := fromHexChar(b[i+1]) if v1|v2 == 255 { return ErrInvalidFormat } u[i/2] = (v1 << 4) | v2 } return nil } // MarshalBinary implements the encoding.BinaryMarshaler interface. func (u UUID) MarshalBinary() ([]byte, error) { return u.Bytes(), nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. // It will return an error if the slice isn't 16 bytes long. func (u *UUID) UnmarshalBinary(data []byte) error { if len(data) != Size { return fmt.Errorf("%w, got %d bytes", ErrIncorrectByteLength, len(data)) } copy(u[:], data) return nil } uuid-5.2.0/codec_test.go000066400000000000000000000265061462015257500151470ustar00rootroot00000000000000// Copyright (C) 2013-2018 by Maxim Bublis // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package uuid import ( "bytes" "flag" "fmt" "os" "path/filepath" "strings" "testing" ) // codecTestData holds []byte data for a UUID we commonly use for testing. var codecTestData = []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} // codecTestUUID is the UUID value corresponding to codecTestData. var codecTestUUID = UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} func TestFromBytes(t *testing.T) { t.Run("Valid", func(t *testing.T) { got, err := FromBytes(codecTestData) if err != nil { t.Fatal(err) } if got != codecTestUUID { t.Fatalf("FromBytes(%x) = %v, want %v", codecTestData, got, codecTestUUID) } }) t.Run("Invalid", func(t *testing.T) { var short [][]byte for i := 0; i < len(codecTestData); i++ { short = append(short, codecTestData[:i]) } var long [][]byte for i := 1; i < 17; i++ { tmp := append(codecTestData, make([]byte, i)...) long = append(long, tmp) } invalid := append(short, long...) for _, b := range invalid { got, err := FromBytes(b) if err == nil { t.Fatalf("FromBytes(%x): want err != nil, got %v", b, got) } } }) } func TestFromBytesOrNil(t *testing.T) { t.Run("Invalid", func(t *testing.T) { b := []byte{4, 8, 15, 16, 23, 42} got := FromBytesOrNil(b) if got != Nil { t.Errorf("FromBytesOrNil(%x): got %v, want %v", b, got, Nil) } }) t.Run("Valid", func(t *testing.T) { got := FromBytesOrNil(codecTestData) if got != codecTestUUID { t.Errorf("FromBytesOrNil(%x): got %v, want %v", codecTestData, got, codecTestUUID) } }) } type fromStringTest struct { input string variant string } // Run runs the FromString test in a subtest of t, named by fst.variant. func (fst fromStringTest) TestFromString(t *testing.T) { t.Run(fst.variant, func(t *testing.T) { got, err := FromString(fst.input) if err != nil { t.Fatalf("FromString(%q): %v", fst.input, err) } if want := codecTestUUID; got != want { t.Fatalf("FromString(%q) = %v, want %v", fst.input, got, want) } }) } func (fst fromStringTest) TestUnmarshalText(t *testing.T) { t.Run(fst.variant, func(t *testing.T) { var u UUID err := u.UnmarshalText([]byte(fst.input)) if err != nil { t.Fatalf("UnmarshalText(%q) (%s): %v", fst.input, fst.variant, err) } if want := codecTestData; !bytes.Equal(u[:], want[:]) { t.Fatalf("UnmarshalText(%q) (%s) = %v, want %v", fst.input, fst.variant, u, want) } }) } // fromStringTests contains UUID variants that are expected to be parsed // successfully by UnmarshalText / FromString. // // variants must be unique across elements of this slice. Please see the // comment in fuzz.go if you change this slice or add new tests to it. var fromStringTests = []fromStringTest{ { input: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", variant: "Canonical", }, { input: "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", variant: "BracedCanonical", }, { input: "{6ba7b8109dad11d180b400c04fd430c8}", variant: "BracedHashlike", }, { input: "6ba7b8109dad11d180b400c04fd430c8", variant: "Hashlike", }, { input: "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8", variant: "URNCanonical", }, { input: "urn:uuid:6ba7b8109dad11d180b400c04fd430c8", variant: "URNHashlike", }, } var invalidFromStringInputs = []string{ // short "6ba7b810-9dad-11d1-80b4-00c04fd430c", "6ba7b8109dad11d180b400c04fd430c", // invalid hex "6ba7b8109dad11d180b400c04fd430q8", // long "6ba7b810-9dad-11d1-80b4-00c04fd430c8=", "6ba7b810-9dad-11d1-80b4-00c04fd430c8}", "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}f", "6ba7b810-9dad-11d1-80b4-00c04fd430c800c04fd430c8", // malformed in other ways "ba7b8109dad11d180b400c04fd430c8}", "6ba7b8109dad11d180b400c04fd430c86ba7b8109dad11d180b400c04fd430c8", "urn:uuid:{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", "uuid:urn:6ba7b810-9dad-11d1-80b4-00c04fd430c8", "uuid:urn:6ba7b8109dad11d180b400c04fd430c8", "6ba7b8109-dad-11d1-80b4-00c04fd430c8", "6ba7b810-9dad1-1d1-80b4-00c04fd430c8", "6ba7b810-9dad-11d18-0b4-00c04fd430c8", "6ba7b810-9dad-11d1-80b40-0c04fd430c8", "6ba7b810+9dad+11d1+80b4+00c04fd430c8", "(6ba7b810-9dad-11d1-80b4-00c04fd430c8}", "{6ba7b810-9dad-11d1-80b4-00c04fd430c8>", "zba7b810-9dad-11d1-80b4-00c04fd430c8", "6ba7b810-9dad11d180b400c04fd430c8", "6ba7b8109dad-11d180b400c04fd430c8", "6ba7b8109dad11d1-80b400c04fd430c8", "6ba7b8109dad11d180b4-00c04fd430c8", } func TestFromString(t *testing.T) { t.Run("Valid", func(t *testing.T) { for _, fst := range fromStringTests { fst.TestFromString(t) } }) t.Run("Invalid", func(t *testing.T) { for _, s := range invalidFromStringInputs { got, err := FromString(s) if err == nil { t.Errorf("FromString(%q): want err != nil, got %v", s, got) } } }) } func TestFromStringOrNil(t *testing.T) { t.Run("Invalid", func(t *testing.T) { s := "bad" got := FromStringOrNil(s) if got != Nil { t.Errorf("FromStringOrNil(%q): got %v, want Nil", s, got) } }) t.Run("Valid", func(t *testing.T) { s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" got := FromStringOrNil(s) if got != codecTestUUID { t.Errorf("FromStringOrNil(%q): got %v, want %v", s, got, codecTestUUID) } }) } func TestUnmarshalText(t *testing.T) { t.Run("Valid", func(t *testing.T) { for _, fst := range fromStringTests { fst.TestUnmarshalText(t) } }) t.Run("Invalid", func(t *testing.T) { for _, s := range invalidFromStringInputs { var u UUID err := u.UnmarshalText([]byte(s)) if err == nil { t.Errorf("FromBytes(%q): want err != nil, got %v", s, u) } } }) } // Test that UnmarshalText() and Parse() return identical errors func TestUnmarshalTextParseErrors(t *testing.T) { for _, s := range invalidFromStringInputs { var u UUID e1 := u.UnmarshalText([]byte(s)) e2 := u.Parse(s) if e1 == nil || e1.Error() != e2.Error() { t.Errorf("%q: errors don't match: UnmarshalText: %v Parse: %v", s, e1, e2) } } } func TestMarshalBinary(t *testing.T) { got, err := codecTestUUID.MarshalBinary() if err != nil { t.Fatal(err) } if !bytes.Equal(got, codecTestData) { t.Fatalf("%v.MarshalBinary() = %x, want %x", codecTestUUID, got, codecTestData) } } func TestMarshalText(t *testing.T) { want := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") got, err := codecTestUUID.MarshalText() if err != nil { t.Fatal(err) } if !bytes.Equal(got, want) { t.Errorf("%v.MarshalText(): got %s, want %s", codecTestUUID, got, want) } } func TestDecodePlainWithWrongLength(t *testing.T) { arg := []byte{'4', '2'} u := UUID{} if u.UnmarshalText(arg) == nil { t.Errorf("%v.UnmarshalText(%q): should return error, but it did not", u, arg) } } func TestFromHexChar(t *testing.T) { const hextable = "0123456789abcdef" t.Run("Valid", func(t *testing.T) { t.Run("Lower", func(t *testing.T) { for i, c := range []byte(hextable) { x := fromHexChar(c) if int(x) != i { t.Errorf("fromHexChar(%c): got %d want %d", c, x, i) } } }) t.Run("Upper", func(t *testing.T) { for i, c := range []byte(strings.ToUpper(hextable)) { x := fromHexChar(c) if int(x) != i { t.Errorf("fromHexChar(%c): got %d want %d", c, x, i) } } }) }) t.Run("Invalid", func(t *testing.T) { skip := make(map[byte]bool) for _, c := range []byte(hextable + strings.ToUpper(hextable)) { skip[c] = true } for i := 0; i < 256; i++ { c := byte(i) if !skip[c] { v := fromHexChar(c) if v != 255 { t.Errorf("fromHexChar(%c): got %d want: %d", c, v, 255) } } } }) } var stringBenchmarkSink string func BenchmarkString(b *testing.B) { for i := 0; i < b.N; i++ { stringBenchmarkSink = codecTestUUID.String() } } func BenchmarkFromBytes(b *testing.B) { for i := 0; i < b.N; i++ { FromBytes(codecTestData) } } func BenchmarkFromString(b *testing.B) { b.Run("canonical", func(b *testing.B) { for i := 0; i < b.N; i++ { FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") } }) b.Run("urn", func(b *testing.B) { for i := 0; i < b.N; i++ { FromString("urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8") } }) b.Run("braced", func(b *testing.B) { for i := 0; i < b.N; i++ { FromString("{6ba7b810-9dad-11d1-80b4-00c04fd430c8}") } }) } func BenchmarkUnmarshalText(b *testing.B) { b.Run("canonical", func(b *testing.B) { text := []byte(Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")).String()) u := new(UUID) if err := u.UnmarshalText(text); err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _ = u.UnmarshalText(text) } }) b.Run("urn", func(b *testing.B) { text := []byte(Must(FromString("urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8")).String()) u := new(UUID) if err := u.UnmarshalText(text); err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _ = u.UnmarshalText(text) } }) b.Run("braced", func(b *testing.B) { text := []byte(Must(FromString("{6ba7b810-9dad-11d1-80b4-00c04fd430c8}")).String()) u := new(UUID) if err := u.UnmarshalText(text); err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _ = u.UnmarshalText(text) } }) } func BenchmarkMarshalBinary(b *testing.B) { for i := 0; i < b.N; i++ { codecTestUUID.MarshalBinary() } } func BenchmarkMarshalText(b *testing.B) { for i := 0; i < b.N; i++ { codecTestUUID.MarshalText() } } func BenchmarkParseV4(b *testing.B) { const text = "f52a747a-983f-45f7-90b5-e84d70f470dd" for i := 0; i < b.N; i++ { var u UUID if err := u.Parse(text); err != nil { b.Fatal(err) } } } var seedFuzzCorpus = flag.Bool("seed_fuzz_corpus", false, "seed fuzz test corpus") func TestSeedFuzzCorpus(t *testing.T) { // flag.Parse() is called for us by the test binary. if !*seedFuzzCorpus { t.Skip("seeding fuzz test corpus only on demand") } corpusDir := filepath.Join(".", "testdata", "corpus") writeSeedFile := func(name, data string) error { path := filepath.Join(corpusDir, name) return os.WriteFile(path, []byte(data), os.ModePerm) } for _, fst := range fromStringTests { name := "seed_valid_" + fst.variant if err := writeSeedFile(name, fst.input); err != nil { t.Fatal(err) } } for i, s := range invalidFromStringInputs { name := fmt.Sprintf("seed_invalid_%d", i) if err := writeSeedFile(name, s); err != nil { t.Fatal(err) } } } uuid-5.2.0/error.go000066400000000000000000000032011462015257500141470ustar00rootroot00000000000000package uuid // Error is a custom error type for UUID-related errors type Error string // The strings defined in the errors is matching the previous behavior before // the custom error type was implemented. The reason is that some people might // be relying on the exact string representation to handle errors in their code. const ( // ErrInvalidFormat is returned when the UUID string representation does not // match the expected format. See also ErrIncorrectFormatInString. ErrInvalidFormat = Error("uuid: invalid UUID format") // ErrIncorrectFormatInString can be returned instead of ErrInvalidFormat. // A separate error type is used because of how errors used to be formatted // before custom error types were introduced. ErrIncorrectFormatInString = Error("uuid: incorrect UUID format in string") // ErrIncorrectLength is returned when the UUID does not have the // appropriate string length for parsing the UUID. ErrIncorrectLength = Error("uuid: incorrect UUID length") // ErrIncorrectByteLength indicates the UUID byte slice length is invalid. ErrIncorrectByteLength = Error("uuid: UUID must be exactly 16 bytes long") // ErrNoHwAddressFound is returned when a hardware (MAC) address cannot be // found for UUID generation. ErrNoHwAddressFound = Error("uuid: no HW address found") // ErrTypeConvertError is returned for type conversion operation fails. ErrTypeConvertError = Error("uuid: cannot convert") // ErrInvalidVersion indicates an unsupported or invalid UUID version. ErrInvalidVersion = Error("uuid:") ) // Error returns the string representation of the UUID error. func (e Error) Error() string { return string(e) } uuid-5.2.0/error_test.go000066400000000000000000000125121462015257500152130ustar00rootroot00000000000000package uuid import ( "errors" "fmt" "net" "testing" ) func TestIsAsError(t *testing.T) { tcs := []struct { err error expected string expectedErr error }{ { err: fmt.Errorf("%w sample error: %v", ErrInvalidVersion, 123), expected: "uuid: sample error: 123", expectedErr: ErrInvalidVersion, }, { err: fmt.Errorf("%w", ErrInvalidFormat), expected: "uuid: invalid UUID format", expectedErr: ErrInvalidFormat, }, { err: fmt.Errorf("%w %q", ErrIncorrectFormatInString, "test"), expected: "uuid: incorrect UUID format in string \"test\"", expectedErr: ErrIncorrectFormatInString, }, } for i, tc := range tcs { t.Run(fmt.Sprintf("Test case %d", i), func(t *testing.T) { if tc.err.Error() != tc.expected { t.Errorf("expected err.Error() to be '%s' but was '%s'", tc.expected, tc.err.Error()) } var uuidErr Error if !errors.As(tc.err, &uuidErr) { t.Error("expected errors.As() to work") } if !errors.Is(tc.err, tc.expectedErr) { t.Errorf("expected error to be, or wrap, the %v sentinel error", tc.expectedErr) } }) } } func TestParseErrors(t *testing.T) { tcs := []struct { function string uuidStr string expected string }{ { // 34 chars - With brackets function: "parse", uuidStr: "..................................", expected: "uuid: incorrect UUID format in string \"..................................\"", }, { // 41 chars - urn:uuid: function: "parse", uuidStr: "123456789................................", expected: "uuid: incorrect UUID format in string \"123456789\"", }, { // other function: "parse", uuidStr: "....", expected: "uuid: incorrect UUID length 4 in string \"....\"", }, { // 36 chars - canonical, but not correct format function: "parse", uuidStr: "....................................", expected: "uuid: incorrect UUID format in string \"....................................\"", }, { // 36 chars - canonical, invalid data function: "parse", uuidStr: "xx00ae9e-dae3-459f-ad0e-6b574be3f950", expected: "uuid: invalid UUID format", }, { // Hash like function: "parse", uuidStr: "................................", expected: "uuid: invalid UUID format", }, { // Hash like, invalid function: "parse", uuidStr: "xx00ae9edae3459fad0e6b574be3f950", expected: "uuid: invalid UUID format", }, { // Hash like, invalid function: "parse", uuidStr: "xx00ae9edae3459fad0e6b574be3f950", expected: "uuid: invalid UUID format", }, } for i, tc := range tcs { t.Run(fmt.Sprintf("Test case %d", i), func(t *testing.T) { id := UUID{} err := id.Parse(tc.uuidStr) if err == nil { t.Error("expected an error") return } if err.Error() != tc.expected { t.Errorf("unexpected error '%s' != '%s'", err.Error(), tc.expected) } err = id.UnmarshalText([]byte(tc.uuidStr)) if err == nil { t.Error("expected an error") return } if err.Error() != tc.expected { t.Errorf("unexpected error '%s' != '%s'", err.Error(), tc.expected) } }) } } func TestUnmarshalBinaryError(t *testing.T) { id := UUID{} b := make([]byte, 33) expectedErr := "uuid: UUID must be exactly 16 bytes long, got 33 bytes" err := id.UnmarshalBinary([]byte(b)) if err == nil { t.Error("expected an error") return } if err.Error() != expectedErr { t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) } } func TestScanError(t *testing.T) { id := UUID{} err := id.Scan(123) if err == nil { t.Error("expected an error") return } expectedErr := "uuid: cannot convert int to UUID" if err.Error() != expectedErr { t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) } } func TestUUIDVersionErrors(t *testing.T) { // UUId V1 Version id := FromStringOrNil("e86160d3-beff-443c-b9b5-1f8197ccb12e") _, err := TimestampFromV1(id) if err == nil { t.Error("expected an error") return } expectedErr := "uuid: e86160d3-beff-443c-b9b5-1f8197ccb12e is version 4, not version 1" if err.Error() != expectedErr { t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) } // UUId V2 Version id = FromStringOrNil("e86160d3-beff-443c-b9b5-1f8197ccb12e") _, err = TimestampFromV6(id) if err == nil { t.Error("expected an error") return } expectedErr = "uuid: e86160d3-beff-443c-b9b5-1f8197ccb12e is version 4, not version 6" if err.Error() != expectedErr { t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) } // UUId V7 Version id = FromStringOrNil("e86160d3-beff-443c-b9b5-1f8197ccb12e") _, err = TimestampFromV7(id) if err == nil { t.Error("expected an error") return } expectedErr = "uuid: e86160d3-beff-443c-b9b5-1f8197ccb12e is version 4, not version 7" if err.Error() != expectedErr { t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) } } // This test cannot be run in parallel with other tests since it modifies the // global state func TestErrNoHwAddressFound(t *testing.T) { netInterfaces = func() ([]net.Interface, error) { return nil, nil } defer func() { netInterfaces = net.Interfaces }() _, err := defaultHWAddrFunc() if err == nil { t.Error("expected an error") return } expectedErr := "uuid: no HW address found" if err.Error() != expectedErr { t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr) } } uuid-5.2.0/fuzz.go000066400000000000000000000034231462015257500140220ustar00rootroot00000000000000// Copyright (c) 2018 Andrei Tudor Călin // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. //go:build gofuzz // +build gofuzz package uuid // Fuzz implements a simple fuzz test for FromString / UnmarshalText. // // To run: // // $ go get github.com/dvyukov/go-fuzz/... // $ cd $GOPATH/src/github.com/gofrs/uuid // $ go-fuzz-build github.com/gofrs/uuid // $ go-fuzz -bin=uuid-fuzz.zip -workdir=./testdata // // If you make significant changes to FromString / UnmarshalText and add // new cases to fromStringTests (in codec_test.go), please run // // $ go test -seed_fuzz_corpus // // to seed the corpus with the new interesting inputs, then run the fuzzer. func Fuzz(data []byte) int { _, err := FromString(string(data)) if err != nil { return 0 } return 1 } uuid-5.2.0/generator.go000066400000000000000000000342431462015257500150160ustar00rootroot00000000000000// Copyright (C) 2013-2018 by Maxim Bublis // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package uuid import ( "crypto/md5" "crypto/rand" "crypto/sha1" "encoding/binary" "hash" "io" "net" "sync" "time" ) // Difference in 100-nanosecond intervals between // UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). const epochStart = 122192928000000000 // EpochFunc is the function type used to provide the current time. type EpochFunc func() time.Time // HWAddrFunc is the function type used to provide hardware (MAC) addresses. type HWAddrFunc func() (net.HardwareAddr, error) // DefaultGenerator is the default UUID Generator used by this package. var DefaultGenerator Generator = NewGen() // NewV1 returns a UUID based on the current timestamp and MAC address. func NewV1() (UUID, error) { return DefaultGenerator.NewV1() } // NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name. func NewV3(ns UUID, name string) UUID { return DefaultGenerator.NewV3(ns, name) } // NewV4 returns a randomly generated UUID. func NewV4() (UUID, error) { return DefaultGenerator.NewV4() } // NewV5 returns a UUID based on SHA-1 hash of the namespace UUID and name. func NewV5(ns UUID, name string) UUID { return DefaultGenerator.NewV5(ns, name) } // NewV6 returns a k-sortable UUID based on a timestamp and 48 bits of // pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit // order being adjusted to allow the UUID to be k-sortable. func NewV6() (UUID, error) { return DefaultGenerator.NewV6() } // NewV7 returns a k-sortable UUID based on the current millisecond precision // UNIX epoch and 74 bits of pseudorandom data. It supports single-node batch generation (multiple UUIDs in the same timestamp) with a Monotonic Random counter. func NewV7() (UUID, error) { return DefaultGenerator.NewV7() } // Generator provides an interface for generating UUIDs. type Generator interface { NewV1() (UUID, error) NewV3(ns UUID, name string) UUID NewV4() (UUID, error) NewV5(ns UUID, name string) UUID NewV6() (UUID, error) NewV7() (UUID, error) } // Gen is a reference UUID generator based on the specifications laid out in // RFC-9562 and DCE 1.1: Authentication and Security Services. This type // satisfies the Generator interface as defined in this package. // // For consumers who are generating V1 UUIDs, but don't want to expose the MAC // address of the node generating the UUIDs, the NewGenWithHWAF() function has been // provided as a convenience. See the function's documentation for more info. // // The authors of this package do not feel that the majority of users will need // to obfuscate their MAC address, and so we recommend using NewGen() to create // a new generator. type Gen struct { clockSequenceOnce sync.Once hardwareAddrOnce sync.Once storageMutex sync.Mutex rand io.Reader epochFunc EpochFunc hwAddrFunc HWAddrFunc lastTime uint64 clockSequence uint16 hardwareAddr [6]byte } // GenOption is a function type that can be used to configure a Gen generator. type GenOption func(*Gen) // interface check -- build will fail if *Gen doesn't satisfy Generator var _ Generator = (*Gen)(nil) // NewGen returns a new instance of Gen with some default values set. Most // people should use this. func NewGen() *Gen { return NewGenWithHWAF(defaultHWAddrFunc) } // NewGenWithHWAF builds a new UUID generator with the HWAddrFunc provided. Most // consumers should use NewGen() instead. // // This is used so that consumers can generate their own MAC addresses, for use // in the generated UUIDs, if there is some concern about exposing the physical // address of the machine generating the UUID. // // The Gen generator will only invoke the HWAddrFunc once, and cache that MAC // address for all the future UUIDs generated by it. If you'd like to switch the // MAC address being used, you'll need to create a new generator using this // function. func NewGenWithHWAF(hwaf HWAddrFunc) *Gen { return NewGenWithOptions(WithHWAddrFunc(hwaf)) } // NewGenWithOptions returns a new instance of Gen with the options provided. // Most people should use NewGen() or NewGenWithHWAF() instead. // // To customize the generator, you can pass in one or more GenOption functions. // For example: // // gen := NewGenWithOptions( // WithHWAddrFunc(myHWAddrFunc), // WithEpochFunc(myEpochFunc), // WithRandomReader(myRandomReader), // ) // // NewGenWithOptions(WithHWAddrFunc(myHWAddrFunc)) is equivalent to calling // NewGenWithHWAF(myHWAddrFunc) // NewGenWithOptions() is equivalent to calling NewGen() func NewGenWithOptions(opts ...GenOption) *Gen { gen := &Gen{ epochFunc: time.Now, hwAddrFunc: defaultHWAddrFunc, rand: rand.Reader, } for _, opt := range opts { opt(gen) } return gen } // WithHWAddrFunc is a GenOption that allows you to provide your own HWAddrFunc // function. // When this option is nil, the defaultHWAddrFunc is used. func WithHWAddrFunc(hwaf HWAddrFunc) GenOption { return func(gen *Gen) { if hwaf == nil { hwaf = defaultHWAddrFunc } gen.hwAddrFunc = hwaf } } // WithEpochFunc is a GenOption that allows you to provide your own EpochFunc // function. // When this option is nil, time.Now is used. func WithEpochFunc(epochf EpochFunc) GenOption { return func(gen *Gen) { if epochf == nil { epochf = time.Now } gen.epochFunc = epochf } } // WithRandomReader is a GenOption that allows you to provide your own random // reader. // When this option is nil, the default rand.Reader is used. func WithRandomReader(reader io.Reader) GenOption { return func(gen *Gen) { if reader == nil { reader = rand.Reader } gen.rand = reader } } // NewV1 returns a UUID based on the current timestamp and MAC address. func (g *Gen) NewV1() (UUID, error) { u := UUID{} timeNow, clockSeq, err := g.getClockSequence(false) if err != nil { return Nil, err } binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) binary.BigEndian.PutUint16(u[8:], clockSeq) hardwareAddr, err := g.getHardwareAddr() if err != nil { return Nil, err } copy(u[10:], hardwareAddr) u.SetVersion(V1) u.SetVariant(VariantRFC9562) return u, nil } // NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name. func (g *Gen) NewV3(ns UUID, name string) UUID { u := newFromHash(md5.New(), ns, name) u.SetVersion(V3) u.SetVariant(VariantRFC9562) return u } // NewV4 returns a randomly generated UUID. func (g *Gen) NewV4() (UUID, error) { u := UUID{} if _, err := io.ReadFull(g.rand, u[:]); err != nil { return Nil, err } u.SetVersion(V4) u.SetVariant(VariantRFC9562) return u, nil } // NewV5 returns a UUID based on SHA-1 hash of the namespace UUID and name. func (g *Gen) NewV5(ns UUID, name string) UUID { u := newFromHash(sha1.New(), ns, name) u.SetVersion(V5) u.SetVariant(VariantRFC9562) return u } // NewV6 returns a k-sortable UUID based on a timestamp and 48 bits of // pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit // order being adjusted to allow the UUID to be k-sortable. func (g *Gen) NewV6() (UUID, error) { /* https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-6 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | time_high | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | time_mid | ver | time_low | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |var| clock_seq | node | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | node | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ var u UUID timeNow, _, err := g.getClockSequence(false) if err != nil { return Nil, err } binary.BigEndian.PutUint32(u[0:], uint32(timeNow>>28)) // set time_high binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>12)) // set time_mid binary.BigEndian.PutUint16(u[6:], uint16(timeNow&0xfff)) // set time_low (minus four version bits) // Based on the RFC 9562 recommendation that this data be fully random and not a monotonic counter, //we do NOT support batching version 6 UUIDs. //set clock_seq (14 bits) and node (48 bits) pseudo-random bits (first 2 bits will be overridden) if _, err = io.ReadFull(g.rand, u[8:]); err != nil { return Nil, err } u.SetVersion(V6) //overwrite first 2 bits of byte[8] for the variant u.SetVariant(VariantRFC9562) return u, nil } // NewV7 returns a k-sortable UUID based on the current millisecond precision // UNIX epoch and 74 bits of pseudorandom data. func (g *Gen) NewV7() (UUID, error) { var u UUID /* https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | unix_ts_ms | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | unix_ts_ms | ver | rand_a | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |var| rand_b | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | rand_b | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ ms, clockSeq, err := g.getClockSequence(true) if err != nil { return Nil, err } //UUIDv7 features a 48 bit timestamp. First 32bit (4bytes) represents seconds since 1970, followed by 2 bytes for the ms granularity. u[0] = byte(ms >> 40) //1-6 bytes: big-endian unsigned number of Unix epoch timestamp u[1] = byte(ms >> 32) u[2] = byte(ms >> 24) u[3] = byte(ms >> 16) u[4] = byte(ms >> 8) u[5] = byte(ms) //Support batching by using a monotonic pseudo-random sequence, //as described in RFC 9562 section 6.2, Method 1. //The 6th byte contains the version and partially rand_a data. //We will lose the most significant bites from the clockSeq (with SetVersion), but it is ok, //we need the least significant that contains the counter to ensure the monotonic property binary.BigEndian.PutUint16(u[6:8], clockSeq) // set rand_a with clock seq which is random and monotonic //override first 4bits of u[6]. u.SetVersion(V7) //set rand_b 64bits of pseudo-random bits (first 2 will be overridden) if _, err = io.ReadFull(g.rand, u[8:16]); err != nil { return Nil, err } //override first 2 bits of byte[8] for the variant u.SetVariant(VariantRFC9562) return u, nil } // getClockSequence returns the epoch and clock sequence for V1,V6 and V7 UUIDs. // // When useUnixTSMs is false, it uses the Coordinated Universal Time (UTC) as a count of 100- // // nanosecond intervals since 00:00:00.00, 15 October 1582 (the date of Gregorian reform to the Christian calendar). func (g *Gen) getClockSequence(useUnixTSMs bool) (uint64, uint16, error) { var err error g.clockSequenceOnce.Do(func() { buf := make([]byte, 2) if _, err = io.ReadFull(g.rand, buf); err != nil { return } g.clockSequence = binary.BigEndian.Uint16(buf) }) if err != nil { return 0, 0, err } g.storageMutex.Lock() defer g.storageMutex.Unlock() var timeNow uint64 if useUnixTSMs { timeNow = uint64(g.epochFunc().UnixMilli()) } else { timeNow = g.getEpoch() } // Clock didn't change since last UUID generation. // Should increase clock sequence. if timeNow <= g.lastTime { g.clockSequence++ } g.lastTime = timeNow return timeNow, g.clockSequence, nil } // Returns the hardware address. func (g *Gen) getHardwareAddr() ([]byte, error) { var err error g.hardwareAddrOnce.Do(func() { var hwAddr net.HardwareAddr if hwAddr, err = g.hwAddrFunc(); err == nil { copy(g.hardwareAddr[:], hwAddr) return } // Initialize hardwareAddr randomly in case // of real network interfaces absence. if _, err = io.ReadFull(g.rand, g.hardwareAddr[:]); err != nil { return } // Set multicast bit as recommended by RFC-9562 g.hardwareAddr[0] |= 0x01 }) if err != nil { return []byte{}, err } return g.hardwareAddr[:], nil } // Returns the difference between UUID epoch (October 15, 1582) // and current time in 100-nanosecond intervals. func (g *Gen) getEpoch() uint64 { return epochStart + uint64(g.epochFunc().UnixNano()/100) } // Returns the UUID based on the hashing of the namespace UUID and name. func newFromHash(h hash.Hash, ns UUID, name string) UUID { u := UUID{} h.Write(ns[:]) h.Write([]byte(name)) copy(u[:], h.Sum(nil)) return u } var netInterfaces = net.Interfaces // Returns the hardware address. func defaultHWAddrFunc() (net.HardwareAddr, error) { ifaces, err := netInterfaces() if err != nil { return []byte{}, err } for _, iface := range ifaces { if len(iface.HardwareAddr) >= 6 { return iface.HardwareAddr, nil } } return []byte{}, ErrNoHwAddressFound } uuid-5.2.0/generator_test.go000066400000000000000000000570141462015257500160560ustar00rootroot00000000000000// Copyright (C) 2013-2018 by Maxim Bublis // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package uuid import ( "bytes" "crypto/rand" "encoding/binary" "errors" "fmt" "net" "strings" "testing" "time" ) func TestGenerator(t *testing.T) { t.Run("NewV1", testNewV1) t.Run("NewV3", testNewV3) t.Run("NewV4", testNewV4) t.Run("NewV5", testNewV5) t.Run("NewV6", testNewV6) t.Run("NewV7", testNewV7) } func testNewV1(t *testing.T) { t.Run("Basic", testNewV1Basic) t.Run("BasicWithOptions", testNewV1BasicWithOptions) t.Run("DifferentAcrossCalls", testNewV1DifferentAcrossCalls) t.Run("StaleEpoch", testNewV1StaleEpoch) t.Run("FaultyRand", testNewV1FaultyRand) t.Run("FaultyRandWithOptions", testNewV1FaultyRandWithOptions) t.Run("MissingNetwork", testNewV1MissingNetwork) t.Run("MissingNetworkWithOptions", testNewV1MissingNetworkWithOptions) t.Run("MissingNetworkFaultyRand", testNewV1MissingNetworkFaultyRand) t.Run("MissingNetworkFaultyRandWithOptions", testNewV1MissingNetworkFaultyRandWithOptions) } func TestNewGenWithHWAF(t *testing.T) { addr := []byte{0, 1, 2, 3, 4, 42} fn := func() (net.HardwareAddr, error) { return addr, nil } var g *Gen var err error var uuid UUID g = NewGenWithHWAF(fn) if g == nil { t.Fatal("g is unexpectedly nil") } uuid, err = g.NewV1() if err != nil { t.Fatalf("g.NewV1() err = %v, want ", err) } node := uuid[10:] if !bytes.Equal(addr, node) { t.Fatalf("node = %v, want %v", node, addr) } } func testNewV1Basic(t *testing.T) { u, err := NewV1() if err != nil { t.Fatal(err) } if got, want := u.Version(), V1; got != want { t.Errorf("generated UUID with version %d, want %d", got, want) } if got, want := u.Variant(), VariantRFC9562; got != want { t.Errorf("generated UUID with variant %d, want %d", got, want) } } func testNewV1BasicWithOptions(t *testing.T) { g := NewGenWithOptions( WithHWAddrFunc(nil), WithEpochFunc(nil), WithRandomReader(nil), ) u, err := g.NewV1() if err != nil { t.Fatal(err) } if got, want := u.Version(), V1; got != want { t.Errorf("generated UUID with version %d, want %d", got, want) } if got, want := u.Variant(), VariantRFC9562; got != want { t.Errorf("generated UUID with variant %d, want %d", got, want) } } func testNewV1DifferentAcrossCalls(t *testing.T) { u1, err := NewV1() if err != nil { t.Fatal(err) } u2, err := NewV1() if err != nil { t.Fatal(err) } if u1 == u2 { t.Errorf("generated identical UUIDs across calls: %v", u1) } } func testNewV1StaleEpoch(t *testing.T) { g := &Gen{ epochFunc: func() time.Time { return time.Unix(0, 0) }, hwAddrFunc: defaultHWAddrFunc, rand: rand.Reader, } u1, err := g.NewV1() if err != nil { t.Fatal(err) } u2, err := g.NewV1() if err != nil { t.Fatal(err) } if u1 == u2 { t.Errorf("generated identical UUIDs across calls: %v", u1) } } func testNewV1FaultyRand(t *testing.T) { g := &Gen{ epochFunc: time.Now, hwAddrFunc: defaultHWAddrFunc, rand: &faultyReader{ readToFail: 0, // fail immediately }, } u, err := g.NewV1() if err == nil { t.Fatalf("got %v, want error", u) } if u != Nil { t.Fatalf("got %v on error, want Nil", u) } } func testNewV1MissingNetwork(t *testing.T) { g := &Gen{ epochFunc: time.Now, hwAddrFunc: func() (net.HardwareAddr, error) { return []byte{}, fmt.Errorf("uuid: no hw address found") }, rand: rand.Reader, } _, err := g.NewV1() if err != nil { t.Errorf("did not handle missing network interfaces: %v", err) } } func testNewV1MissingNetworkWithOptions(t *testing.T) { g := NewGenWithOptions( WithHWAddrFunc(func() (net.HardwareAddr, error) { return []byte{}, fmt.Errorf("uuid: no hw address found") }), ) _, err := g.NewV1() if err != nil { t.Errorf("did not handle missing network interfaces: %v", err) } } func testNewV1MissingNetworkFaultyRand(t *testing.T) { g := &Gen{ epochFunc: time.Now, hwAddrFunc: func() (net.HardwareAddr, error) { return []byte{}, fmt.Errorf("uuid: no hw address found") }, rand: &faultyReader{ readToFail: 1, }, } u, err := g.NewV1() if err == nil { t.Errorf("did not error on faulty reader and missing network, got %v", u) } } func testNewV1MissingNetworkFaultyRandWithOptions(t *testing.T) { g := NewGenWithOptions( WithHWAddrFunc(func() (net.HardwareAddr, error) { return []byte{}, fmt.Errorf("uuid: no hw address found") }), WithRandomReader(&faultyReader{ readToFail: 1, }), ) u, err := g.NewV1() if err == nil { t.Errorf("did not error on faulty reader and missing network, got %v", u) } } func testNewV1FaultyRandWithOptions(t *testing.T) { g := NewGenWithOptions(WithRandomReader(&faultyReader{ readToFail: 0, // fail immediately }), ) u, err := g.NewV1() if err == nil { t.Errorf("did not error on faulty reader and missing network, got %v", u) } } func testNewV3(t *testing.T) { t.Run("Basic", testNewV3Basic) t.Run("EqualNames", testNewV3EqualNames) t.Run("DifferentNamespaces", testNewV3DifferentNamespaces) } func testNewV3Basic(t *testing.T) { ns := NamespaceDNS name := "www.example.com" u := NewV3(ns, name) if got, want := u.Version(), V3; got != want { t.Errorf("NewV3(%v, %q): got version %d, want %d", ns, name, got, want) } if got, want := u.Variant(), VariantRFC9562; got != want { t.Errorf("NewV3(%v, %q): got variant %d, want %d", ns, name, got, want) } want := "5df41881-3aed-3515-88a7-2f4a814cf09e" if got := u.String(); got != want { t.Errorf("NewV3(%v, %q) = %q, want %q", ns, name, got, want) } } func testNewV3EqualNames(t *testing.T) { ns := NamespaceDNS name := "example.com" u1 := NewV3(ns, name) u2 := NewV3(ns, name) if u1 != u2 { t.Errorf("NewV3(%v, %q) generated %v and %v across two calls", ns, name, u1, u2) } } func testNewV3DifferentNamespaces(t *testing.T) { name := "example.com" ns1 := NamespaceDNS ns2 := NamespaceURL u1 := NewV3(ns1, name) u2 := NewV3(ns2, name) if u1 == u2 { t.Errorf("NewV3(%v, %q) == NewV3(%d, %q) (%v)", ns1, name, ns2, name, u1) } } func testNewV4(t *testing.T) { t.Run("Basic", testNewV4Basic) t.Run("DifferentAcrossCalls", testNewV4DifferentAcrossCalls) t.Run("FaultyRand", testNewV4FaultyRand) t.Run("FaultyRandWithOptions", testNewV4FaultyRandWithOptions) t.Run("ShortRandomRead", testNewV4ShortRandomRead) t.Run("ShortRandomReadWithOptions", testNewV4ShortRandomReadWithOptions) } func testNewV4Basic(t *testing.T) { u, err := NewV4() if err != nil { t.Fatal(err) } if got, want := u.Version(), V4; got != want { t.Errorf("got version %d, want %d", got, want) } if got, want := u.Variant(), VariantRFC9562; got != want { t.Errorf("got variant %d, want %d", got, want) } } func testNewV4DifferentAcrossCalls(t *testing.T) { u1, err := NewV4() if err != nil { t.Fatal(err) } u2, err := NewV4() if err != nil { t.Fatal(err) } if u1 == u2 { t.Errorf("generated identical UUIDs across calls: %v", u1) } } func testNewV4FaultyRand(t *testing.T) { g := &Gen{ epochFunc: time.Now, hwAddrFunc: defaultHWAddrFunc, rand: &faultyReader{ readToFail: 0, // fail immediately }, } u, err := g.NewV4() if err == nil { t.Errorf("got %v, nil error", u) } } func testNewV4FaultyRandWithOptions(t *testing.T) { g := NewGenWithOptions( WithRandomReader(&faultyReader{ readToFail: 0, // fail immediately }), ) u, err := g.NewV4() if err == nil { t.Errorf("got %v, nil error", u) } } func testNewV4ShortRandomRead(t *testing.T) { g := &Gen{ epochFunc: time.Now, hwAddrFunc: func() (net.HardwareAddr, error) { return []byte{}, fmt.Errorf("uuid: no hw address found") }, rand: bytes.NewReader([]byte{42}), } u, err := g.NewV4() if err == nil { t.Errorf("got %v, nil error", u) } } func testNewV4ShortRandomReadWithOptions(t *testing.T) { g := NewGenWithOptions( WithHWAddrFunc(func() (net.HardwareAddr, error) { return []byte{}, fmt.Errorf("uuid: no hw address found") }), WithRandomReader(&faultyReader{ readToFail: 0, // fail immediately }), ) u, err := g.NewV4() if err == nil { t.Errorf("got %v, nil error", u) } } func testNewV5(t *testing.T) { t.Run("Basic", testNewV5Basic) t.Run("EqualNames", testNewV5EqualNames) t.Run("DifferentNamespaces", testNewV5DifferentNamespaces) } func testNewV5Basic(t *testing.T) { ns := NamespaceDNS name := "www.example.com" u := NewV5(ns, name) if got, want := u.Version(), V5; got != want { t.Errorf("NewV5(%v, %q): got version %d, want %d", ns, name, got, want) } if got, want := u.Variant(), VariantRFC9562; got != want { t.Errorf("NewV5(%v, %q): got variant %d, want %d", ns, name, got, want) } want := "2ed6657d-e927-568b-95e1-2665a8aea6a2" if got := u.String(); got != want { t.Errorf("NewV5(%v, %q) = %q, want %q", ns, name, got, want) } } func testNewV5EqualNames(t *testing.T) { ns := NamespaceDNS name := "example.com" u1 := NewV5(ns, name) u2 := NewV5(ns, name) if u1 != u2 { t.Errorf("NewV5(%v, %q) generated %v and %v across two calls", ns, name, u1, u2) } } func testNewV5DifferentNamespaces(t *testing.T) { name := "example.com" ns1 := NamespaceDNS ns2 := NamespaceURL u1 := NewV5(ns1, name) u2 := NewV5(ns2, name) if u1 == u2 { t.Errorf("NewV5(%v, %q) == NewV5(%v, %q) (%v)", ns1, name, ns2, name, u1) } } func testNewV6(t *testing.T) { t.Run("Basic", testNewV6Basic) t.Run("DifferentAcrossCalls", testNewV6DifferentAcrossCalls) t.Run("StaleEpoch", testNewV6StaleEpoch) t.Run("StaleEpochWithOptions", testNewV6StaleEpochWithOptions) t.Run("FaultyRand", testNewV6FaultyRand) t.Run("FaultyRandWithOptions", testNewV6FaultyRandWithOptions) t.Run("ShortRandomRead", testNewV6ShortRandomRead) t.Run("ShortRandomReadWithOptions", testNewV6ShortRandomReadWithOptions) t.Run("KSortable", testNewV6KSortable) } func testNewV6Basic(t *testing.T) { u, err := NewV6() if err != nil { t.Fatal(err) } if got, want := u.Version(), V6; got != want { t.Errorf("generated UUID with version %d, want %d", got, want) } if got, want := u.Variant(), VariantRFC9562; got != want { t.Errorf("generated UUID with variant %d, want %d", got, want) } } func testNewV6DifferentAcrossCalls(t *testing.T) { u1, err := NewV6() if err != nil { t.Fatal(err) } u2, err := NewV6() if err != nil { t.Fatal(err) } if u1 == u2 { t.Errorf("generated identical UUIDs across calls: %v", u1) } } func testNewV6StaleEpoch(t *testing.T) { g := &Gen{ epochFunc: func() time.Time { return time.Unix(0, 0) }, hwAddrFunc: defaultHWAddrFunc, rand: rand.Reader, } u1, err := g.NewV6() if err != nil { t.Fatal(err) } u2, err := g.NewV6() if err != nil { t.Fatal(err) } if u1 == u2 { t.Errorf("generated identical UUIDs across calls: %v", u1) } } func testNewV6StaleEpochWithOptions(t *testing.T) { g := NewGenWithOptions( WithEpochFunc(func() time.Time { return time.Unix(0, 0) }), ) u1, err := g.NewV6() if err != nil { t.Fatal(err) } u2, err := g.NewV6() if err != nil { t.Fatal(err) } if u1 == u2 { t.Errorf("generated identical UUIDs across calls: %v", u1) } } func testNewV6FaultyRand(t *testing.T) { t.Run("randomData", func(t *testing.T) { g := &Gen{ epochFunc: time.Now, hwAddrFunc: defaultHWAddrFunc, rand: &faultyReader{ readToFail: 0, // fail immediately }, } u, err := g.NewV6() if err == nil { t.Fatalf("got %v, want error", u) } if u != Nil { t.Fatalf("got %v on error, want Nil", u) } }) t.Run("clockSequence", func(t *testing.T) { g := &Gen{ epochFunc: time.Now, hwAddrFunc: defaultHWAddrFunc, rand: &faultyReader{ readToFail: 1, // fail immediately }, } u, err := g.NewV6() if err == nil { t.Fatalf("got %v, want error", u) } if u != Nil { t.Fatalf("got %v on error, want Nil", u) } }) } func testNewV6FaultyRandWithOptions(t *testing.T) { t.Run("randomData", func(t *testing.T) { g := NewGenWithOptions( WithRandomReader(&faultyReader{ readToFail: 0, // fail immediately }), ) u, err := g.NewV6() if err == nil { t.Fatalf("got %v, want error", u) } if u != Nil { t.Fatalf("got %v on error, want Nil", u) } }) t.Run("clockSequence", func(t *testing.T) { g := NewGenWithOptions( WithRandomReader(&faultyReader{ readToFail: 1, // fail immediately }), ) u, err := g.NewV6() if err == nil { t.Fatalf("got %v, want error", u) } if u != Nil { t.Fatalf("got %v on error, want Nil", u) } }) } func testNewV6ShortRandomRead(t *testing.T) { g := &Gen{ epochFunc: time.Now, rand: bytes.NewReader([]byte{42}), } u, err := g.NewV6() if err == nil { t.Errorf("got %v, nil error", u) } } func testNewV6ShortRandomReadWithOptions(t *testing.T) { g := NewGenWithOptions( WithRandomReader(bytes.NewReader([]byte{42})), ) u, err := g.NewV6() if err == nil { t.Errorf("got %v, nil error", u) } } func testNewV6KSortable(t *testing.T) { uuids := make([]UUID, 10) for i := range uuids { u, err := NewV6() testErrCheck(t, "NewV6()", "", err) uuids[i] = u time.Sleep(time.Microsecond) } for i := 1; i < len(uuids); i++ { p, n := uuids[i-1], uuids[i] isLess := p.String() < n.String() if !isLess { t.Errorf("uuids[%d] (%s) not less than uuids[%d] (%s)", i-1, p, i, n) } } } func testNewV7(t *testing.T) { t.Run("Basic", makeTestNewV7Basic()) t.Run("TestVector", makeTestNewV7TestVector()) t.Run("Basic10000000", makeTestNewV7Basic10000000()) t.Run("DifferentAcrossCalls", makeTestNewV7DifferentAcrossCalls()) t.Run("StaleEpoch", makeTestNewV7StaleEpoch()) t.Run("StaleEpochWithOptions", makeTestNewV7StaleEpochWithOptions()) t.Run("FaultyRand", makeTestNewV7FaultyRand()) t.Run("FaultyRandWithOptions", makeTestNewV7FaultyRandWithOptions()) t.Run("ShortRandomRead", makeTestNewV7ShortRandomRead()) t.Run("KSortable", makeTestNewV7KSortable()) t.Run("ClockSequence", makeTestNewV7ClockSequence()) } func makeTestNewV7Basic() func(t *testing.T) { return func(t *testing.T) { u, err := NewV7() if err != nil { t.Fatal(err) } if got, want := u.Version(), V7; got != want { t.Errorf("got version %d, want %d", got, want) } if got, want := u.Variant(), VariantRFC9562; got != want { t.Errorf("got variant %d, want %d", got, want) } } } // makeTestNewV7TestVector as defined in Draft04 func makeTestNewV7TestVector() func(t *testing.T) { return func(t *testing.T) { pRand := make([]byte, 10) //first 2 bytes will be read by clockSeq. First 4 bits will be overridden by Version. The next bits should be 0xCC3(3267) binary.LittleEndian.PutUint16(pRand[:2], uint16(0xCC3)) //8bytes will be read for rand_b. First 2 bits will be overridden by Variant binary.LittleEndian.PutUint64(pRand[2:], uint64(0x18C4DC0C0C07398F)) g := &Gen{ epochFunc: func() time.Time { return time.UnixMilli(1645557742000) }, rand: bytes.NewReader(pRand), } u, err := g.NewV7() if err != nil { t.Fatal(err) } if got, want := u.Version(), V7; got != want { t.Errorf("got version %d, want %d", got, want) } if got, want := u.Variant(), VariantRFC9562; got != want { t.Errorf("got variant %d, want %d", got, want) } if got, want := u.String()[:15], "017f22e2-79b0-7"; got != want { t.Errorf("got version %q, want %q", got, want) } } } func makeTestNewV7Basic10000000() func(t *testing.T) { return func(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } g := NewGen() for i := 0; i < 10000000; i++ { u, err := g.NewV7() if err != nil { t.Fatal(err) } if got, want := u.Version(), V7; got != want { t.Errorf("got version %d, want %d", got, want) } if got, want := u.Variant(), VariantRFC9562; got != want { t.Errorf("got variant %d, want %d", got, want) } } } } func makeTestNewV7DifferentAcrossCalls() func(t *testing.T) { return func(t *testing.T) { g := NewGen() u1, err := g.NewV7() if err != nil { t.Fatal(err) } u2, err := g.NewV7() if err != nil { t.Fatal(err) } if u1 == u2 { t.Errorf("generated identical UUIDs across calls: %v", u1) } } } func makeTestNewV7StaleEpoch() func(t *testing.T) { return func(t *testing.T) { g := &Gen{ epochFunc: func() time.Time { return time.Unix(0, 0) }, rand: rand.Reader, } u1, err := g.NewV7() if err != nil { t.Fatal(err) } u2, err := g.NewV7() if err != nil { t.Fatal(err) } if u1 == u2 { t.Errorf("generated identical UUIDs across calls: %v", u1) } } } func makeTestNewV7StaleEpochWithOptions() func(t *testing.T) { return func(t *testing.T) { g := NewGenWithOptions( WithEpochFunc(func() time.Time { return time.Unix(0, 0) }), ) u1, err := g.NewV7() if err != nil { t.Fatal(err) } u2, err := g.NewV7() if err != nil { t.Fatal(err) } if u1 == u2 { t.Errorf("generated identical UUIDs across calls: %v", u1) } } } func makeTestNewV7FaultyRand() func(t *testing.T) { return func(t *testing.T) { g := &Gen{ epochFunc: time.Now, rand: &faultyReader{ readToFail: 0, }, } u, err := g.NewV7() if err == nil { t.Errorf("got %v, nil error for clockSequence", u) } g = &Gen{ epochFunc: time.Now, rand: &faultyReader{ readToFail: 1, }, } u, err = g.NewV7() if err == nil { t.Errorf("got %v, nil error rand_b", u) } } } func makeTestNewV7FaultyRandWithOptions() func(t *testing.T) { return func(t *testing.T) { g := NewGenWithOptions( WithRandomReader(&faultyReader{ readToFail: 0, // fail immediately }), ) u, err := g.NewV7() if err == nil { t.Errorf("got %v, nil error", u) } } } func makeTestNewV7ShortRandomRead() func(t *testing.T) { return func(t *testing.T) { g := &Gen{ epochFunc: time.Now, rand: bytes.NewReader([]byte{42}), } u, err := g.NewV7() if err == nil { t.Errorf("got %v, nil error", u) } } } func makeTestNewV7ShortRandomReadWithOptions() func(t *testing.T) { return func(t *testing.T) { g := NewGenWithOptions( WithRandomReader(bytes.NewReader([]byte{42})), ) u, err := g.NewV7() if err == nil { t.Errorf("got %v, nil error", u) } } } func makeTestNewV7KSortable() func(t *testing.T) { return func(t *testing.T) { uuids := make([]UUID, 10) for i := range uuids { u, err := NewV7() testErrCheck(t, "NewV7()", "", err) uuids[i] = u time.Sleep(time.Millisecond) } for i := 1; i < len(uuids); i++ { p, n := uuids[i-1], uuids[i] isLess := p.String() < n.String() if !isLess { t.Errorf("uuids[%d] (%s) not less than uuids[%d] (%s)", i-1, p, i, n) } } } } func makeTestNewV7ClockSequence() func(t *testing.T) { return func(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } g := NewGen() //always return the same TS g.epochFunc = func() time.Time { return time.UnixMilli(1645557742000) } //by being KSortable with the same timestamp, it means the sequence is Not empty, and it is monotonic uuids := make([]UUID, 10) for i := range uuids { u, err := g.NewV7() testErrCheck(t, "NewV7()", "", err) uuids[i] = u } for i := 1; i < len(uuids); i++ { p, n := uuids[i-1], uuids[i] isLess := p.String() < n.String() if !isLess { t.Errorf("uuids[%d] (%s) not less than uuids[%d] (%s)", i-1, p, i, n) } } } } func TestDefaultHWAddrFunc(t *testing.T) { tests := []struct { n string fn func() ([]net.Interface, error) hw net.HardwareAddr e string }{ { n: "Error", fn: func() ([]net.Interface, error) { return nil, errors.New("controlled failure") }, e: "controlled failure", }, { n: "NoValidHWAddrReturned", fn: func() ([]net.Interface, error) { s := []net.Interface{ { Index: 1, MTU: 1500, Name: "test0", HardwareAddr: net.HardwareAddr{1, 2, 3, 4}, }, { Index: 2, MTU: 1500, Name: "lo0", HardwareAddr: net.HardwareAddr{5, 6, 7, 8}, }, } return s, nil }, e: "uuid: no HW address found", }, { n: "ValidHWAddrReturned", fn: func() ([]net.Interface, error) { s := []net.Interface{ { Index: 1, MTU: 1500, Name: "test0", HardwareAddr: net.HardwareAddr{1, 2, 3, 4}, }, { Index: 2, MTU: 1500, Name: "lo0", HardwareAddr: net.HardwareAddr{5, 6, 7, 8, 9, 0}, }, } return s, nil }, hw: net.HardwareAddr{5, 6, 7, 8, 9, 0}, }, } for _, tt := range tests { t.Run(tt.n, func(t *testing.T) { // set the netInterfaces variable (function) for the test // and then set it back to default in the deferred function netInterfaces = tt.fn defer func() { netInterfaces = net.Interfaces }() var hw net.HardwareAddr var err error hw, err = defaultHWAddrFunc() if len(tt.e) > 0 { if err == nil { t.Fatalf("defaultHWAddrFunc() error = , should contain %q", tt.e) } if !strings.Contains(err.Error(), tt.e) { t.Fatalf("defaultHWAddrFunc() error = %q, should contain %q", err.Error(), tt.e) } return } if err != nil && tt.e == "" { t.Fatalf("defaultHWAddrFunc() error = %q, want ", err.Error()) } if !bytes.Equal(hw, tt.hw) { t.Fatalf("hw = %#v, want %#v", hw, tt.hw) } }) } } func BenchmarkGenerator(b *testing.B) { b.Run("NewV1", func(b *testing.B) { for i := 0; i < b.N; i++ { NewV1() } }) b.Run("NewV3", func(b *testing.B) { for i := 0; i < b.N; i++ { NewV3(NamespaceDNS, "www.example.com") } }) b.Run("NewV4", func(b *testing.B) { for i := 0; i < b.N; i++ { NewV4() } }) b.Run("NewV5", func(b *testing.B) { for i := 0; i < b.N; i++ { NewV5(NamespaceDNS, "www.example.com") } }) } type faultyReader struct { callsNum int readToFail int // Read call number to fail } func (r *faultyReader) Read(dest []byte) (int, error) { r.callsNum++ if (r.callsNum - 1) == r.readToFail { return 0, fmt.Errorf("io: reader is faulty") } return rand.Read(dest) } // testErrCheck looks to see if errContains is a substring of err.Error(). If // not, this calls t.Fatal(). It also calls t.Fatal() if there was an error, but // errContains is empty. Returns true if you should continue running the test, // or false if you should stop the test. func testErrCheck(t *testing.T, name string, errContains string, err error) bool { t.Helper() if len(errContains) > 0 { if err == nil { t.Fatalf("%s error = , should contain %q", name, errContains) return false } if errStr := err.Error(); !strings.Contains(errStr, errContains) { t.Fatalf("%s error = %q, should contain %q", name, errStr, errContains) return false } return false } if err != nil && len(errContains) == 0 { t.Fatalf("%s unexpected error: %v", name, err) return false } return true } uuid-5.2.0/go.mod000066400000000000000000000000511462015257500135750ustar00rootroot00000000000000module github.com/gofrs/uuid/v5 go 1.19 uuid-5.2.0/sql.go000066400000000000000000000061271462015257500136270ustar00rootroot00000000000000// Copyright (C) 2013-2018 by Maxim Bublis // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package uuid import ( "database/sql" "database/sql/driver" "fmt" ) var _ driver.Valuer = UUID{} var _ sql.Scanner = (*UUID)(nil) // Value implements the driver.Valuer interface. func (u UUID) Value() (driver.Value, error) { return u.String(), nil } // Scan implements the sql.Scanner interface. // A 16-byte slice will be handled by UnmarshalBinary, while // a longer byte slice or a string will be handled by UnmarshalText. func (u *UUID) Scan(src interface{}) error { switch src := src.(type) { case UUID: // support gorm convert from UUID to NullUUID *u = src return nil case []byte: if len(src) == Size { return u.UnmarshalBinary(src) } return u.UnmarshalText(src) case string: uu, err := FromString(src) *u = uu return err } return fmt.Errorf("%w %T to UUID", ErrTypeConvertError, src) } // NullUUID can be used with the standard sql package to represent a // UUID value that can be NULL in the database. type NullUUID struct { UUID UUID Valid bool } // Value implements the driver.Valuer interface. func (u NullUUID) Value() (driver.Value, error) { if !u.Valid { return nil, nil } // Delegate to UUID Value function return u.UUID.Value() } // Scan implements the sql.Scanner interface. func (u *NullUUID) Scan(src interface{}) error { if src == nil { u.UUID, u.Valid = Nil, false return nil } // Delegate to UUID Scan function u.Valid = true return u.UUID.Scan(src) } var nullJSON = []byte("null") // MarshalJSON marshals the NullUUID as null or the nested UUID func (u NullUUID) MarshalJSON() ([]byte, error) { if !u.Valid { return nullJSON, nil } var buf [38]byte buf[0] = '"' encodeCanonical(buf[1:37], u.UUID) buf[37] = '"' return buf[:], nil } // UnmarshalJSON unmarshals a NullUUID func (u *NullUUID) UnmarshalJSON(b []byte) error { if string(b) == "null" { u.UUID, u.Valid = Nil, false return nil } if n := len(b); n >= 2 && b[0] == '"' { b = b[1 : n-1] } err := u.UUID.UnmarshalText(b) u.Valid = (err == nil) return err } uuid-5.2.0/sql_test.go000066400000000000000000000203561462015257500146660ustar00rootroot00000000000000// Copyright (C) 2013-2018 by Maxim Bublis // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package uuid import ( "encoding/json" "fmt" "testing" ) func TestSQL(t *testing.T) { t.Run("Value", testSQLValue) t.Run("Scan", func(t *testing.T) { t.Run("Binary", testSQLScanBinary) t.Run("String", testSQLScanString) t.Run("Text", testSQLScanText) t.Run("Unsupported", testSQLScanUnsupported) t.Run("Nil", testSQLScanNil) }) } func testSQLValue(t *testing.T) { v, err := codecTestUUID.Value() if err != nil { t.Fatal(err) } got, ok := v.(string) if !ok { t.Fatalf("Value() returned %T, want string", v) } if want := codecTestUUID.String(); got != want { t.Errorf("Value() == %q, want %q", got, want) } } func testSQLScanBinary(t *testing.T) { got := UUID{} err := got.Scan(codecTestData) if err != nil { t.Fatal(err) } if got != codecTestUUID { t.Errorf("Scan(%x): got %v, want %v", codecTestData, got, codecTestUUID) } } func testSQLScanString(t *testing.T) { s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" got := UUID{} err := got.Scan(s) if err != nil { t.Fatal(err) } if got != codecTestUUID { t.Errorf("Scan(%q): got %v, want %v", s, got, codecTestUUID) } } func testSQLScanText(t *testing.T) { text := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") got := UUID{} err := got.Scan(text) if err != nil { t.Fatal(err) } if got != codecTestUUID { t.Errorf("Scan(%q): got %v, want %v", text, got, codecTestUUID) } } func testSQLScanUnsupported(t *testing.T) { unsupported := []interface{}{ true, 42, } for _, v := range unsupported { got := UUID{} err := got.Scan(v) if err == nil { t.Errorf("Scan(%T) succeeded, got %v", v, got) } } } func testSQLScanNil(t *testing.T) { got := UUID{} err := got.Scan(nil) if err == nil { t.Errorf("Scan(nil) succeeded, got %v", got) } } func TestNullUUID(t *testing.T) { t.Run("Value", func(t *testing.T) { t.Run("Nil", testNullUUIDValueNil) t.Run("Valid", testNullUUIDValueValid) }) t.Run("Scan", func(t *testing.T) { t.Run("Nil", testNullUUIDScanNil) t.Run("Valid", testNullUUIDScanValid) t.Run("UUID", testNullUUIDScanUUID) }) t.Run("MarshalJSON", func(t *testing.T) { t.Run("Nil", testNullUUIDMarshalJSONNil) t.Run("Null", testNullUUIDMarshalJSONNull) t.Run("Valid", testNullUUIDMarshalJSONValid) }) t.Run("UnmarshalJSON", func(t *testing.T) { t.Run("Nil", testNullUUIDUnmarshalJSONNil) t.Run("Null", testNullUUIDUnmarshalJSONNull) t.Run("Valid", testNullUUIDUnmarshalJSONValid) t.Run("Malformed", testNullUUIDUnmarshalJSONMalformed) }) } func testNullUUIDValueNil(t *testing.T) { nu := NullUUID{} got, err := nu.Value() if got != nil { t.Errorf("null NullUUID.Value returned non-nil driver.Value") } if err != nil { t.Errorf("null NullUUID.Value returned non-nil error") } } func testNullUUIDValueValid(t *testing.T) { nu := NullUUID{ Valid: true, UUID: codecTestUUID, } got, err := nu.Value() if err != nil { t.Fatal(err) } s, ok := got.(string) if !ok { t.Errorf("Value() returned %T, want string", got) } want := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" if s != want { t.Errorf("%v.Value() == %s, want %s", nu, s, want) } } func testNullUUIDScanNil(t *testing.T) { u := NullUUID{} err := u.Scan(nil) if err != nil { t.Fatal(err) } if u.Valid { t.Error("NullUUID is valid after Scan(nil)") } if u.UUID != Nil { t.Errorf("NullUUID.UUID is %v after Scan(nil) want Nil", u.UUID) } } func testNullUUIDScanValid(t *testing.T) { s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" u := NullUUID{} err := u.Scan(s) if err != nil { t.Fatal(err) } if !u.Valid { t.Errorf("Valid == false after Scan(%q)", s) } if u.UUID != codecTestUUID { t.Errorf("UUID == %v after Scan(%q), want %v", u.UUID, s, codecTestUUID) } } func testNullUUIDScanUUID(t *testing.T) { u := NullUUID{} err := u.Scan(codecTestUUID) if err != nil { t.Fatal(err) } if !u.Valid { t.Errorf("Valid == false after scan(%v)", codecTestUUID) } if u.UUID != codecTestUUID { t.Errorf("UUID == %v after Scan(%v), want %v", u.UUID, codecTestUUID, codecTestUUID) } } func testNullUUIDMarshalJSONNil(t *testing.T) { u := NullUUID{Valid: true} data, err := u.MarshalJSON() if err != nil { t.Fatalf("(%#v).MarshalJSON err want: , got: %v", u, err) } dataStr := string(data) if dataStr != fmt.Sprintf("%q", Nil) { t.Fatalf("(%#v).MarshalJSON value want: %s, got: %s", u, Nil, dataStr) } } func testNullUUIDMarshalJSONValid(t *testing.T) { u := NullUUID{ Valid: true, UUID: codecTestUUID, } data, err := u.MarshalJSON() if err != nil { t.Fatalf("(%#v).MarshalJSON err want: , got: %v", u, err) } dataStr := string(data) if dataStr != fmt.Sprintf("%q", codecTestUUID) { t.Fatalf("(%#v).MarshalJSON value want: %s, got: %s", u, codecTestUUID, dataStr) } } func testNullUUIDMarshalJSONNull(t *testing.T) { u := NullUUID{} data, err := u.MarshalJSON() if err != nil { t.Fatalf("(%#v).MarshalJSON err want: , got: %v", u, err) } dataStr := string(data) if dataStr != "null" { t.Fatalf("(%#v).MarshalJSON value want: %s, got: %s", u, "null", dataStr) } } func testNullUUIDUnmarshalJSONNil(t *testing.T) { var u NullUUID data := []byte(`"00000000-0000-0000-0000-000000000000"`) if err := json.Unmarshal(data, &u); err != nil { t.Fatalf("json.Unmarshal err = %v, want ", err) } if !u.Valid { t.Fatalf("u.Valid = false, want true") } if u.UUID != Nil { t.Fatalf("u.UUID = %v, want %v", u.UUID, Nil) } } func testNullUUIDUnmarshalJSONNull(t *testing.T) { var u NullUUID data := []byte(`null`) if err := json.Unmarshal(data, &u); err != nil { t.Fatalf("json.Unmarshal err = %v, want ", err) } if u.Valid { t.Fatalf("u.Valid = true, want false") } if u.UUID != Nil { t.Fatalf("u.UUID = %v, want %v", u.UUID, Nil) } } func testNullUUIDUnmarshalJSONValid(t *testing.T) { var u NullUUID data := []byte(`"6ba7b810-9dad-11d1-80b4-00c04fd430c8"`) if err := json.Unmarshal(data, &u); err != nil { t.Fatalf("json.Unmarshal err = %v, want ", err) } if !u.Valid { t.Fatalf("u.Valid = false, want true") } if u.UUID != codecTestUUID { t.Fatalf("u.UUID = %v, want %v", u.UUID, Nil) } } func testNullUUIDUnmarshalJSONMalformed(t *testing.T) { var u NullUUID data := []byte(`257`) if err := json.Unmarshal(data, &u); err == nil { t.Fatal("json.Unmarshal err = , want error") } } func BenchmarkNullMarshalJSON(b *testing.B) { b.Run("Valid", func(b *testing.B) { u, err := FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") if err != nil { b.Fatal(err) } n := NullUUID{UUID: u, Valid: true} for i := 0; i < b.N; i++ { n.MarshalJSON() } }) b.Run("Invalid", func(b *testing.B) { n := NullUUID{Valid: false} for i := 0; i < b.N; i++ { n.MarshalJSON() } }) } func BenchmarkNullUnmarshalJSON(b *testing.B) { baseUUID, err := FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") if err != nil { b.Fatal(err) } data, err := json.Marshal(&baseUUID) if err != nil { b.Fatal(err) } b.Run("Valid", func(b *testing.B) { var u NullUUID for i := 0; i < b.N; i++ { u.UnmarshalJSON(data) } }) b.Run("Invalid", func(b *testing.B) { invalid := []byte("null") var n NullUUID for i := 0; i < b.N; i++ { n.UnmarshalJSON(invalid) } }) } uuid-5.2.0/testdata/000077500000000000000000000000001462015257500143045ustar00rootroot00000000000000uuid-5.2.0/testdata/corpus/000077500000000000000000000000001462015257500156175ustar00rootroot00000000000000uuid-5.2.0/testdata/corpus/1416586f4a34d02bcb506f6107b40df512b9f2f9000066400000000000000000000000401462015257500230220ustar00rootroot00000000000000zba7b810-9dad-11d1-80b4-00c04fd4uuid-5.2.0/testdata/corpus/3b46a7e7b02ec193581e6c9fa2c8a72f50a64e08-1000066400000000000000000000000441462015257500233370ustar00rootroot000000000000006ba7b810-9dad-11d1-80F4-00c"4fd430c8uuid-5.2.0/testdata/corpus/50c54bb75fcfdc488f162bf2f0c6dec6103bfa18-5000066400000000000000000000000401462015257500236300ustar00rootroot000000000000006ad1DdE8dda91DdE80F400c0Bool30t:uuid-5.2.0/testdata/corpus/69c581ab749cbd56be8684d3a58ac2cfab9af0f4-5000066400000000000000000000000401462015257500236520ustar00rootroot000000000000006ba7b810Edad1DdE80F400c0Bool30c8uuid-5.2.0/testdata/corpus/752bf000e0bff06777dd0d6f0be6353844de678a-3000066400000000000000000000000401462015257500233360ustar00rootroot000000000000006ba7b8109dad1Dd180F400c0Bool30c8uuid-5.2.0/testdata/corpus/a4483762d4ece8466d82cca5cacd35a0829c4e60-2000066400000000000000000000000441462015257500234210ustar00rootroot000000000000006ba7b810-9dad-11d1-80F4-F0c"4fd430c8uuid-5.2.0/testdata/corpus/d0952c45e0c823fc5cc12bcf7d9b877d150ab523-1000066400000000000000000000000401462015257500234030ustar00rootroot000000000000006ba7b8109dad11d180b400c0Bool30c8uuid-5.2.0/testdata/corpus/da39a3ee5e6b4b0d3255bfef95601890afd80709000066400000000000000000000000001462015257500232550ustar00rootroot00000000000000uuid-5.2.0/testdata/corpus/e2b84d2065846891f18ae109b12e01d224e1c7c3-4000066400000000000000000000000401462015257500231020ustar00rootroot000000000000006ba7b8109dad1DdE80F400c0Bool30c8uuid-5.2.0/testdata/corpus/e320d749435115e874f77420e17d0937e07f69f3-2000066400000000000000000000000401462015257500227170ustar00rootroot000000000000006ba7b8109dad1Dd180b400c0Bool30c8uuid-5.2.0/testdata/corpus/ed132d47d757f6468443a22df8a2a965efb34098-7000066400000000000000000000000401462015257500232210ustar00rootroot000000000000006ba1DdE8dDAE8DdE80F400c0BoUl30touuid-5.2.0/testdata/corpus/eeefb01f7bb3c627aedb292c994b20f739ffd613-6000066400000000000000000000000401462015257500236450ustar00rootroot000000000000006ad1DdE8dDdE8DdE80F400c0Bool30t:uuid-5.2.0/testdata/corpus/seed_invalid_0000077500000000000000000000000431462015257500204070ustar00rootroot000000000000006ba7b810-9dad-11d1-80b4-00c04fd430cuuid-5.2.0/testdata/corpus/seed_invalid_1000077500000000000000000000000371462015257500204130ustar00rootroot000000000000006ba7b8109dad11d180b400c04fd430cuuid-5.2.0/testdata/corpus/seed_invalid_10000077500000000000000000000000551462015257500204730ustar00rootroot00000000000000uuid:urn:6ba7b810-9dad-11d1-80b4-00c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_11000077500000000000000000000000511462015257500204700ustar00rootroot00000000000000uuid:urn:6ba7b8109dad11d180b400c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_12000077500000000000000000000000441462015257500204730ustar00rootroot000000000000006ba7b8109-dad-11d1-80b4-00c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_13000077500000000000000000000000441462015257500204740ustar00rootroot000000000000006ba7b810-9dad1-1d1-80b4-00c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_14000077500000000000000000000000441462015257500204750ustar00rootroot000000000000006ba7b810-9dad-11d18-0b4-00c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_15000077500000000000000000000000441462015257500204760ustar00rootroot000000000000006ba7b810-9dad-11d1-80b40-0c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_16000077500000000000000000000000441462015257500204770ustar00rootroot000000000000006ba7b810+9dad+11d1+80b4+00c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_17000077500000000000000000000000461462015257500205020ustar00rootroot00000000000000(6ba7b810-9dad-11d1-80b4-00c04fd430c8}uuid-5.2.0/testdata/corpus/seed_invalid_18000077500000000000000000000000461462015257500205030ustar00rootroot00000000000000{6ba7b810-9dad-11d1-80b4-00c04fd430c8>uuid-5.2.0/testdata/corpus/seed_invalid_19000077500000000000000000000000441462015257500205020ustar00rootroot00000000000000zba7b810-9dad-11d1-80b4-00c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_2000077500000000000000000000000401462015257500204060ustar00rootroot000000000000006ba7b8109dad11d180b400c04fd430q8uuid-5.2.0/testdata/corpus/seed_invalid_20000077500000000000000000000000411462015257500204670ustar00rootroot000000000000006ba7b810-9dad11d180b400c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_21000077500000000000000000000000411462015257500204700ustar00rootroot000000000000006ba7b8109dad-11d180b400c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_22000077500000000000000000000000411462015257500204710ustar00rootroot000000000000006ba7b8109dad11d1-80b400c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_23000077500000000000000000000000411462015257500204720ustar00rootroot000000000000006ba7b8109dad11d180b4-00c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_3000077500000000000000000000000451462015257500204140ustar00rootroot000000000000006ba7b810-9dad-11d1-80b4-00c04fd430c8=uuid-5.2.0/testdata/corpus/seed_invalid_4000077500000000000000000000000451462015257500204150ustar00rootroot000000000000006ba7b810-9dad-11d1-80b4-00c04fd430c8}uuid-5.2.0/testdata/corpus/seed_invalid_5000077500000000000000000000000471462015257500204200ustar00rootroot00000000000000{6ba7b810-9dad-11d1-80b4-00c04fd430c8}fuuid-5.2.0/testdata/corpus/seed_invalid_6000077500000000000000000000000601462015257500204140ustar00rootroot000000000000006ba7b810-9dad-11d1-80b4-00c04fd430c800c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_7000077500000000000000000000000401462015257500204130ustar00rootroot00000000000000ba7b8109dad11d180b400c04fd430c8}uuid-5.2.0/testdata/corpus/seed_invalid_8000077500000000000000000000001001462015257500204110ustar00rootroot000000000000006ba7b8109dad11d180b400c04fd430c86ba7b8109dad11d180b400c04fd430c8uuid-5.2.0/testdata/corpus/seed_invalid_9000077500000000000000000000000571462015257500204250ustar00rootroot00000000000000urn:uuid:{6ba7b810-9dad-11d1-80b4-00c04fd430c8}uuid-5.2.0/testdata/corpus/seed_valid_BracedCanonical000077500000000000000000000000461462015257500227140ustar00rootroot00000000000000{6ba7b810-9dad-11d1-80b4-00c04fd430c8}uuid-5.2.0/testdata/corpus/seed_valid_BracedHashlike000077500000000000000000000000421462015257500225510ustar00rootroot00000000000000{6ba7b8109dad11d180b400c04fd430c8}uuid-5.2.0/testdata/corpus/seed_valid_Canonical000077500000000000000000000000441462015257500216110ustar00rootroot000000000000006ba7b810-9dad-11d1-80b4-00c04fd430c8uuid-5.2.0/testdata/corpus/seed_valid_Hashlike000077500000000000000000000000401462015257500214460ustar00rootroot000000000000006ba7b8109dad11d180b400c04fd430c8uuid-5.2.0/testdata/corpus/seed_valid_URNCanonical000077500000000000000000000000551462015257500222000ustar00rootroot00000000000000urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8uuid-5.2.0/testdata/corpus/seed_valid_URNHashlike000077500000000000000000000000511462015257500220350ustar00rootroot00000000000000urn:uuid:6ba7b8109dad11d180b400c04fd430c8uuid-5.2.0/uuid.go000066400000000000000000000222431462015257500137730ustar00rootroot00000000000000// Copyright (C) 2013-2018 by Maxim Bublis // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // Package uuid provides implementations of the Universally Unique Identifier // (UUID), as specified in RFC-9562 (formerly RFC-4122). // // RFC-9562[1] provides the specification for versions 1, 3, 4, 5, 6 and 7. // // DCE 1.1[2] provides the specification for version 2, but version 2 support // was removed from this package in v4 due to some concerns with the // specification itself. Reading the spec, it seems that it would result in // generating UUIDs that aren't very unique. In having read the spec it seemed // that our implementation did not meet the spec. It also seems to be at-odds // with RFC 9562, meaning we would need quite a bit of special code to support // it. Lastly, there were no Version 2 implementations that we could find to // ensure we were understanding the specification correctly. // // [1] https://tools.ietf.org/html/rfc9562 // [2] http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 package uuid import ( "encoding/binary" "encoding/hex" "fmt" "time" ) // Size of a UUID in bytes. const Size = 16 // UUID is an array type to represent the value of a UUID, as defined in RFC-9562. type UUID [Size]byte // UUID versions. const ( _ byte = iota V1 // Version 1 (date-time and MAC address) _ // Version 2 (date-time and MAC address, DCE security version) [removed] V3 // Version 3 (namespace name-based) V4 // Version 4 (random) V5 // Version 5 (namespace name-based) V6 // Version 6 (k-sortable timestamp and random data, field-compatible with v1) V7 // Version 7 (k-sortable timestamp and random data) _ // Version 8 (k-sortable timestamp, meant for custom implementations) [not implemented] ) // UUID layout variants. const ( VariantNCS byte = iota VariantRFC9562 VariantMicrosoft VariantFuture ) // Backward-compatible variant for RFC 4122 const VariantRFC4122 = VariantRFC9562 // UUID DCE domains. const ( DomainPerson = iota DomainGroup DomainOrg ) // Timestamp is the count of 100-nanosecond intervals since 00:00:00.00, // 15 October 1582 within a V1 UUID. This type has no meaning for other // UUID versions since they don't have an embedded timestamp. type Timestamp uint64 const _100nsPerSecond = 10000000 // Time returns the UTC time.Time representation of a Timestamp func (t Timestamp) Time() (time.Time, error) { secs := uint64(t) / _100nsPerSecond nsecs := 100 * (uint64(t) % _100nsPerSecond) return time.Unix(int64(secs)-(epochStart/_100nsPerSecond), int64(nsecs)), nil } // TimestampFromV1 returns the Timestamp embedded within a V1 UUID. // Returns an error if the UUID is any version other than 1. func TimestampFromV1(u UUID) (Timestamp, error) { if u.Version() != 1 { err := fmt.Errorf("%w %s is version %d, not version 1", ErrInvalidVersion, u, u.Version()) return 0, err } low := binary.BigEndian.Uint32(u[0:4]) mid := binary.BigEndian.Uint16(u[4:6]) hi := binary.BigEndian.Uint16(u[6:8]) & 0xfff return Timestamp(uint64(low) + (uint64(mid) << 32) + (uint64(hi) << 48)), nil } // TimestampFromV6 returns the Timestamp embedded within a V6 UUID. This // function returns an error if the UUID is any version other than 6. func TimestampFromV6(u UUID) (Timestamp, error) { if u.Version() != 6 { return 0, fmt.Errorf("%w %s is version %d, not version 6", ErrInvalidVersion, u, u.Version()) } hi := binary.BigEndian.Uint32(u[0:4]) mid := binary.BigEndian.Uint16(u[4:6]) low := binary.BigEndian.Uint16(u[6:8]) & 0xfff return Timestamp(uint64(low) + (uint64(mid) << 12) + (uint64(hi) << 28)), nil } // TimestampFromV7 returns the Timestamp embedded within a V7 UUID. This // function returns an error if the UUID is any version other than 7. func TimestampFromV7(u UUID) (Timestamp, error) { if u.Version() != 7 { return 0, fmt.Errorf("%w %s is version %d, not version 7", ErrInvalidVersion, u, u.Version()) } t := 0 | (int64(u[0]) << 40) | (int64(u[1]) << 32) | (int64(u[2]) << 24) | (int64(u[3]) << 16) | (int64(u[4]) << 8) | int64(u[5]) // convert to format expected by Timestamp tsNanos := epochStart + time.UnixMilli(t).UTC().UnixNano()/100 return Timestamp(tsNanos), nil } // Nil is the nil UUID, as specified in RFC-9562, that has all 128 bits set to // zero. var Nil = UUID{} // Max is the maximum UUID, as specified in RFC-9562, that has all 128 bits // set to one. var Max = UUID{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, } // Predefined namespace UUIDs. var ( NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) ) // IsNil returns if the UUID is equal to the nil UUID func (u UUID) IsNil() bool { return u == Nil } // Version returns the algorithm version used to generate the UUID. func (u UUID) Version() byte { return u[6] >> 4 } // Variant returns the UUID layout variant. func (u UUID) Variant() byte { switch { case (u[8] >> 7) == 0x00: return VariantNCS case (u[8] >> 6) == 0x02: return VariantRFC9562 case (u[8] >> 5) == 0x06: return VariantMicrosoft case (u[8] >> 5) == 0x07: fallthrough default: return VariantFuture } } // Bytes returns a byte slice representation of the UUID. func (u UUID) Bytes() []byte { return u[:] } // encodeCanonical encodes the canonical RFC-9562 form of UUID u into the // first 36 bytes dst. func encodeCanonical(dst []byte, u UUID) { const hextable = "0123456789abcdef" dst[8] = '-' dst[13] = '-' dst[18] = '-' dst[23] = '-' for i, x := range [16]byte{ 0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34, } { c := u[i] dst[x] = hextable[c>>4] dst[x+1] = hextable[c&0x0f] } } // String returns a canonical RFC-9562 string representation of the UUID: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. func (u UUID) String() string { var buf [36]byte encodeCanonical(buf[:], u) return string(buf[:]) } // Format implements fmt.Formatter for UUID values. // // The behavior is as follows: // The 'x' and 'X' verbs output only the hex digits of the UUID, using a-f for 'x' and A-F for 'X'. // The 'v', '+v', 's' and 'q' verbs return the canonical RFC-9562 string representation. // The 'S' verb returns the RFC-9562 format, but with capital hex digits. // The '#v' verb returns the "Go syntax" representation, which is a 16 byte array initializer. // All other verbs not handled directly by the fmt package (like '%p') are unsupported and will return // "%!verb(uuid.UUID=value)" as recommended by the fmt package. func (u UUID) Format(f fmt.State, c rune) { if c == 'v' && f.Flag('#') { fmt.Fprintf(f, "%#v", [Size]byte(u)) return } switch c { case 'x', 'X': b := make([]byte, 32) hex.Encode(b, u[:]) if c == 'X' { toUpperHex(b) } _, _ = f.Write(b) case 'v', 's', 'S': b, _ := u.MarshalText() if c == 'S' { toUpperHex(b) } _, _ = f.Write(b) case 'q': b := make([]byte, 38) b[0] = '"' encodeCanonical(b[1:], u) b[37] = '"' _, _ = f.Write(b) default: // invalid/unsupported format verb fmt.Fprintf(f, "%%!%c(uuid.UUID=%s)", c, u.String()) } } func toUpperHex(b []byte) { for i, c := range b { if 'a' <= c && c <= 'f' { b[i] = c - ('a' - 'A') } } } // SetVersion sets the version bits. func (u *UUID) SetVersion(v byte) { u[6] = (u[6] & 0x0f) | (v << 4) } // SetVariant sets the variant bits. func (u *UUID) SetVariant(v byte) { switch v { case VariantNCS: u[8] = (u[8]&(0xff>>1) | (0x00 << 7)) case VariantRFC9562: u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) case VariantMicrosoft: u[8] = (u[8]&(0xff>>3) | (0x06 << 5)) case VariantFuture: fallthrough default: u[8] = (u[8]&(0xff>>3) | (0x07 << 5)) } } // Must is a helper that wraps a call to a function returning (UUID, error) // and panics if the error is non-nil. It is intended for use in variable // initializations such as // // var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")) func Must(u UUID, err error) UUID { if err != nil { panic(err) } return u } uuid-5.2.0/uuid_test.go000066400000000000000000000227331462015257500150360ustar00rootroot00000000000000// Copyright (C) 2013-2018 by Maxim Bublis // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package uuid import ( "bytes" "fmt" "io" "testing" "time" ) func TestUUID(t *testing.T) { t.Run("IsNil", testUUIDIsNil) t.Run("Bytes", testUUIDBytes) t.Run("String", testUUIDString) t.Run("Version", testUUIDVersion) t.Run("Variant", testUUIDVariant) t.Run("SetVersion", testUUIDSetVersion) t.Run("SetVariant", testUUIDSetVariant) t.Run("Format", testUUIDFormat) } func testUUIDIsNil(t *testing.T) { u := UUID{} got := u.IsNil() want := true if got != want { t.Errorf("%v.IsNil() = %t, want %t", u, got, want) } } func testUUIDBytes(t *testing.T) { got := codecTestUUID.Bytes() want := codecTestData if !bytes.Equal(got, want) { t.Errorf("%v.Bytes() = %x, want %x", codecTestUUID, got, want) } } func testUUIDString(t *testing.T) { got := NamespaceDNS.String() want := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" if got != want { t.Errorf("%v.String() = %q, want %q", NamespaceDNS, got, want) } } func testUUIDVersion(t *testing.T) { u := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} if got, want := u.Version(), V1; got != want { t.Errorf("%v.Version() == %d, want %d", u, got, want) } } func testUUIDVariant(t *testing.T) { tests := []struct { u UUID want byte }{ { u: UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, want: VariantNCS, }, { u: UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, want: VariantRFC9562, }, { u: UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, want: VariantMicrosoft, }, { u: UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, want: VariantFuture, }, } for _, tt := range tests { if got := tt.u.Variant(); got != tt.want { t.Errorf("%v.Variant() == %d, want %d", tt.u, got, tt.want) } } } func testUUIDSetVersion(t *testing.T) { u := UUID{} want := V4 u.SetVersion(want) if got := u.Version(); got != want { t.Errorf("%v.Version() == %d after SetVersion(%d)", u, got, want) } } func testUUIDSetVariant(t *testing.T) { variants := []byte{ VariantNCS, VariantRFC9562, VariantMicrosoft, VariantFuture, } for _, want := range variants { u := UUID{} u.SetVariant(want) if got := u.Variant(); got != want { t.Errorf("%v.Variant() == %d after SetVariant(%d)", u, got, want) } } } func testUUIDFormat(t *testing.T) { val := Must(FromString("12345678-90ab-cdef-1234-567890abcdef")) tests := []struct { u UUID f string want string }{ {u: val, f: "%s", want: "12345678-90ab-cdef-1234-567890abcdef"}, {u: val, f: "%S", want: "12345678-90AB-CDEF-1234-567890ABCDEF"}, {u: val, f: "%q", want: `"12345678-90ab-cdef-1234-567890abcdef"`}, {u: val, f: "%x", want: "1234567890abcdef1234567890abcdef"}, {u: val, f: "%X", want: "1234567890ABCDEF1234567890ABCDEF"}, {u: val, f: "%v", want: "12345678-90ab-cdef-1234-567890abcdef"}, {u: val, f: "%+v", want: "12345678-90ab-cdef-1234-567890abcdef"}, {u: val, f: "%#v", want: "[16]uint8{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}"}, {u: val, f: "%T", want: "uuid.UUID"}, {u: val, f: "%t", want: "%!t(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, {u: val, f: "%b", want: "%!b(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, {u: val, f: "%c", want: "%!c(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, {u: val, f: "%d", want: "%!d(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, {u: val, f: "%e", want: "%!e(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, {u: val, f: "%E", want: "%!E(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, {u: val, f: "%f", want: "%!f(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, {u: val, f: "%F", want: "%!F(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, {u: val, f: "%g", want: "%!g(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, {u: val, f: "%G", want: "%!G(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, {u: val, f: "%o", want: "%!o(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, {u: val, f: "%U", want: "%!U(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, } for _, tt := range tests { got := fmt.Sprintf(tt.f, tt.u) if tt.want != got { t.Errorf(`Format("%s") got %s, want %s`, tt.f, got, tt.want) } } } func TestMust(t *testing.T) { sentinel := fmt.Errorf("uuid: sentinel error") defer func() { r := recover() if r == nil { t.Fatalf("did not panic, want %v", sentinel) } err, ok := r.(error) if !ok { t.Fatalf("panicked with %T, want error (%v)", r, sentinel) } if err != sentinel { t.Fatalf("panicked with %v, want %v", err, sentinel) } }() fn := func() (UUID, error) { return Nil, sentinel } Must(fn()) } func TestTimeFromTimestamp(t *testing.T) { tests := []struct { t Timestamp want time.Time }{ // a zero timestamp represents October 15, 1582 at midnight UTC {t: Timestamp(0), want: time.Date(1582, 10, 15, 0, 0, 0, 0, time.UTC)}, // a one value is 100ns later {t: Timestamp(1), want: time.Date(1582, 10, 15, 0, 0, 0, 100, time.UTC)}, // 10 million 100ns intervals later is one second {t: Timestamp(10000000), want: time.Date(1582, 10, 15, 0, 0, 1, 0, time.UTC)}, {t: Timestamp(60 * 10000000), want: time.Date(1582, 10, 15, 0, 1, 0, 0, time.UTC)}, {t: Timestamp(60 * 60 * 10000000), want: time.Date(1582, 10, 15, 1, 0, 0, 0, time.UTC)}, {t: Timestamp(24 * 60 * 60 * 10000000), want: time.Date(1582, 10, 16, 0, 0, 0, 0, time.UTC)}, {t: Timestamp(365 * 24 * 60 * 60 * 10000000), want: time.Date(1583, 10, 15, 0, 0, 0, 0, time.UTC)}, // maximum timestamp value in a UUID is the year 5236 {t: Timestamp(uint64(1<<60 - 1)), want: time.Date(5236, 03, 31, 21, 21, 0, 684697500, time.UTC)}, } for _, tt := range tests { got, _ := tt.t.Time() if !got.Equal(tt.want) { t.Errorf("%v.Time() == %v, want %v", tt.t, got, tt.want) } } } func TestTimestampFromV1(t *testing.T) { tests := []struct { u UUID want Timestamp wanterr bool }{ {u: Must(NewV4()), wanterr: true}, {u: Must(FromString("00000000-0000-1000-0000-000000000000")), want: 0}, {u: Must(FromString("424f137e-a2aa-11e8-98d0-529269fb1459")), want: 137538640775418750}, {u: Must(FromString("ffffffff-ffff-1fff-ffff-ffffffffffff")), want: Timestamp(1<<60 - 1)}, } for _, tt := range tests { got, goterr := TimestampFromV1(tt.u) if tt.wanterr && goterr == nil { t.Errorf("TimestampFromV1(%v) want error, got %v", tt.u, got) } else if tt.want != got { t.Errorf("TimestampFromV1(%v) got %v, want %v", tt.u, got, tt.want) } } } func TestTimestampFromV6(t *testing.T) { tests := []struct { u UUID want Timestamp wanterr bool }{ {u: Must(NewV1()), wanterr: true}, {u: Must(FromString("00000000-0000-6000-0000-000000000000")), want: 0}, {u: Must(FromString("1ec06cff-e9b1-621c-8627-ba3fd7e551c9")), want: 138493178941215260}, {u: Must(FromString("ffffffff-ffff-6fff-ffff-ffffffffffff")), want: Timestamp(1<<60 - 1)}, } for _, tt := range tests { got, err := TimestampFromV6(tt.u) switch { case tt.wanterr && err == nil: t.Errorf("TimestampFromV6(%v) want error, got %v", tt.u, got) case tt.want != got: t.Errorf("TimestampFromV6(%v) got %v, want %v", tt.u, got, tt.want) } } } func TestTimestampFromV7(t *testing.T) { tests := []struct { u UUID want Timestamp wanterr bool }{ {u: Must(NewV1()), wanterr: true}, // v7 is unix_ts_ms, so zero value time is unix epoch {u: Must(FromString("00000000-0000-7000-0000-000000000000")), want: 122192928000000000}, {u: Must(FromString("018a8fec-3ced-7164-995f-93c80cbdc575")), want: 139139245386050000}, {u: Must(FromString("ffffffff-ffff-7fff-ffff-ffffffffffff")), want: Timestamp(epochStart + time.UnixMilli((1<<48)-1).UTC().UnixNano()/100)}, } for _, tt := range tests { got, err := TimestampFromV7(tt.u) switch { case tt.wanterr && err == nil: t.Errorf("TimestampFromV7(%v) want error, got %v", tt.u, got) case tt.want != got: t.Errorf("TimestampFromV7(%v) got %v, want %v", tt.u, got, tt.want) } } } func BenchmarkFormat(b *testing.B) { var tests = []string{ "%s", "%S", "%q", "%x", "%X", "%v", "%+v", "%#v", } for _, x := range tests { b.Run(x[1:], func(b *testing.B) { for i := 0; i < b.N; i++ { fmt.Fprintf(io.Discard, x, &codecTestUUID) } }) } }