Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions uuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ package uuid

import (
"bytes"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"

// Uses math/rand/v2 for random number generation
// because it uses chacha8 as the default source,
// which is cryptographically secure and much faster

"strings"
"sync"
)
Expand Down Expand Up @@ -42,8 +46,8 @@ const Standard = RFC4122
const randPoolSize = 16 * 16

var (
rander = rand.Reader // random function
poolEnabled = false
rander io.Reader = defaultRandReader{}
poolEnabled = false
poolMu sync.Mutex
poolPos = randPoolSize // protected with poolMu
pool [randPoolSize]byte // protected with poolMu
Expand All @@ -52,7 +56,7 @@ var (
ErrInvalidBracketedFormat = errors.New("invalid bracketed UUID format")
)

type URNPrefixError struct { prefix string }
type URNPrefixError struct{ prefix string }

func (e URNPrefixError) Error() string {
return fmt.Sprintf("invalid urn prefix: %q", e.prefix)
Expand Down Expand Up @@ -215,10 +219,12 @@ func Must(uuid UUID, err error) UUID {
}

// Validate returns an error if s is not a properly formatted UUID in one of the following formats:
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
//
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
//
// It returns an error if the format is invalid, otherwise nil.
func Validate(s string) error {
switch len(s) {
Expand Down Expand Up @@ -346,7 +352,7 @@ func (v Variant) String() string {
// generator.
func SetRand(r io.Reader) {
if r == nil {
rander = rand.Reader
rander = defaultRandReader{}
return
}
rander = r
Expand Down
57 changes: 49 additions & 8 deletions version4.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@

package uuid

import "io"
import (
"encoding/binary"
"io"
chacha8RandV2 "math/rand/v2"
)

// New creates a new random UUID or panics. New is equivalent to
// the expression
//
// uuid.Must(uuid.NewRandom())
// uuid.Must(uuid.NewRandom())
func New() UUID {
return Must(NewRandom())
}

// NewString creates a new random UUID and returns it as a string or panics.
// NewString is equivalent to the expression
//
// uuid.New().String()
// uuid.New().String()
func NewString() string {
return Must(NewRandom()).String()
}
Expand All @@ -31,12 +35,16 @@ func NewString() string {
//
// A note about uniqueness derived from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 10−11),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 10−11),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() (UUID, error) {
if _, ok := rander.(defaultRandReader); ok {
return fastRandV4DefaultZeroAlloc(), nil
}

if !poolEnabled {
return NewRandomFromReader(rander)
}
Expand Down Expand Up @@ -74,3 +82,36 @@ func newRandomFromPool() (UUID, error) {
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}

// Singleton with no state that implements
// io.Reader and uses the default rand/v2
// package to read bytes.
type defaultRandReader struct{}

// Read fills the provided byte slice `b` with random data using a
// cryptographically secure random number generator.
func (d defaultRandReader) Read(b []byte) (n int, err error) {
var num uint64
numByteIndex := 0

for i := range b {
if numByteIndex == 0 {
num = chacha8RandV2.Uint64()
}
b[i] = byte(num >> (numByteIndex * 8))
numByteIndex = (numByteIndex + 1) % 8
}

return len(b), nil
}

func fastRandV4DefaultZeroAlloc() UUID {
var uuid UUID
hi, low := chacha8RandV2.Uint64(), chacha8RandV2.Uint64()
binary.BigEndian.PutUint64(uuid[0:8], hi)
binary.BigEndian.PutUint64(uuid[8:16], low)

uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid
}