github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/uuid/generator.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
    12  // Use of this source code is governed by a MIT-style
    13  // license that can be found in licenses/MIT-gofrs.txt.
    14  
    15  // This code originated in github.com/gofrs/uuid.
    16  
    17  package uuid
    18  
    19  import (
    20  	"crypto/md5"
    21  	"crypto/rand"
    22  	"crypto/sha1"
    23  	"encoding/binary"
    24  	"fmt"
    25  	"hash"
    26  	"io"
    27  	"net"
    28  	"sync"
    29  	"time"
    30  
    31  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    32  	"github.com/cockroachdb/errors"
    33  )
    34  
    35  // Difference in 100-nanosecond intervals between
    36  // UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970).
    37  const epochStart = 122192928000000000
    38  
    39  type epochFunc func() time.Time
    40  
    41  // HWAddrFunc is the function type used to provide hardware (MAC) addresses.
    42  type HWAddrFunc func() (net.HardwareAddr, error)
    43  
    44  // DefaultGenerator is the default UUID Generator used by this package.
    45  var DefaultGenerator Generator = NewGen()
    46  
    47  // NewV1 returns a UUID based on the current timestamp and MAC address.
    48  func NewV1() (UUID, error) {
    49  	return DefaultGenerator.NewV1()
    50  }
    51  
    52  // NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name.
    53  func NewV3(ns UUID, name string) UUID {
    54  	return DefaultGenerator.NewV3(ns, name)
    55  }
    56  
    57  // NewV4 returns a randomly generated UUID.
    58  func NewV4() (UUID, error) {
    59  	return DefaultGenerator.NewV4()
    60  }
    61  
    62  // NewV5 returns a UUID based on SHA-1 hash of the namespace UUID and name.
    63  func NewV5(ns UUID, name string) UUID {
    64  	return DefaultGenerator.NewV5(ns, name)
    65  }
    66  
    67  // Generator provides an interface for generating UUIDs.
    68  type Generator interface {
    69  	NewV1() (UUID, error)
    70  	// NewV2(domain byte) (UUID, error) // CRL: Removed support for V2.
    71  	NewV3(ns UUID, name string) UUID
    72  	NewV4() (UUID, error)
    73  	NewV5(ns UUID, name string) UUID
    74  }
    75  
    76  // Gen is a reference UUID generator based on the specifications laid out in
    77  // RFC-4122 and DCE 1.1: Authentication and Security Services. This type
    78  // satisfies the Generator interface as defined in this package.
    79  //
    80  // For consumers who are generating V1 UUIDs, but don't want to expose the MAC
    81  // address of the node generating the UUIDs, the NewGenWithHWAF() function has been
    82  // provided as a convenience. See the function's documentation for more info.
    83  //
    84  // The authors of this package do not feel that the majority of users will need
    85  // to obfuscate their MAC address, and so we recommend using NewGen() to create
    86  // a new generator.
    87  type Gen struct {
    88  	clockSequenceOnce sync.Once
    89  	hardwareAddrOnce  sync.Once
    90  	storageMutex      syncutil.Mutex
    91  
    92  	rand io.Reader
    93  
    94  	epochFunc     epochFunc
    95  	hwAddrFunc    HWAddrFunc
    96  	lastTime      uint64
    97  	clockSequence uint16
    98  	hardwareAddr  [6]byte
    99  }
   100  
   101  // interface check -- build will fail if *Gen doesn't satisfy Generator
   102  var _ Generator = (*Gen)(nil)
   103  
   104  // NewGen returns a new instance of Gen with some default values set. Most
   105  // people should use this.
   106  // NewGen by default uses crypto/rand.Reader as its source of randomness.
   107  func NewGen() *Gen {
   108  	return NewGenWithHWAF(defaultHWAddrFunc)
   109  }
   110  
   111  // NewGenWithReader returns a new instance of gen which uses r as its source of
   112  // randomness.
   113  func NewGenWithReader(r io.Reader) *Gen {
   114  	g := NewGen()
   115  	g.rand = r
   116  	return g
   117  }
   118  
   119  // NewGenWithHWAF builds a new UUID generator with the HWAddrFunc provided. Most
   120  // consumers should use NewGen() instead.
   121  //
   122  // This is used so that consumers can generate their own MAC addresses, for use
   123  // in the generated UUIDs, if there is some concern about exposing the physical
   124  // address of the machine generating the UUID.
   125  //
   126  // The Gen generator will only invoke the HWAddrFunc once, and cache that MAC
   127  // address for all the future UUIDs generated by it. If you'd like to switch the
   128  // MAC address being used, you'll need to create a new generator using this
   129  // function.
   130  func NewGenWithHWAF(hwaf HWAddrFunc) *Gen {
   131  	return &Gen{
   132  		epochFunc:  time.Now,
   133  		hwAddrFunc: hwaf,
   134  		rand:       rand.Reader,
   135  	}
   136  }
   137  
   138  // NewV1 returns a UUID based on the current timestamp and MAC address.
   139  func (g *Gen) NewV1() (UUID, error) {
   140  	u := UUID{}
   141  
   142  	timeNow, clockSeq, err := g.getClockSequence()
   143  	if err != nil {
   144  		return Nil, err
   145  	}
   146  	binary.BigEndian.PutUint32(u[0:], uint32(timeNow))
   147  	binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
   148  	binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
   149  	binary.BigEndian.PutUint16(u[8:], clockSeq)
   150  
   151  	hardwareAddr, err := g.getHardwareAddr()
   152  	if err != nil {
   153  		return Nil, err
   154  	}
   155  	copy(u[10:], hardwareAddr)
   156  
   157  	u.SetVersion(V1)
   158  	u.SetVariant(VariantRFC4122)
   159  
   160  	return u, nil
   161  }
   162  
   163  // NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name.
   164  func (g *Gen) NewV3(ns UUID, name string) UUID {
   165  	u := newFromHash(md5.New(), ns, name)
   166  	u.SetVersion(V3)
   167  	u.SetVariant(VariantRFC4122)
   168  
   169  	return u
   170  }
   171  
   172  // NewV4 returns a randomly generated UUID.
   173  func (g *Gen) NewV4() (UUID, error) {
   174  	u := UUID{}
   175  	if r, ok := g.rand.(defaultRandReader); ok {
   176  		if n, err := r.Read(u[:]); n != len(u) {
   177  			panic("math/rand.Read always returns len(p)")
   178  		} else if err != nil {
   179  			panic("math/rand.Read always returns a nil error")
   180  		}
   181  	} else {
   182  		willEscape := UUID{}
   183  		if _, err := io.ReadFull(g.rand, willEscape[:]); err != nil {
   184  			return Nil, err
   185  		}
   186  		u = willEscape
   187  	}
   188  	u.SetVersion(V4)
   189  	u.SetVariant(VariantRFC4122)
   190  
   191  	return u, nil
   192  }
   193  
   194  // NewV5 returns a UUID based on SHA-1 hash of the namespace UUID and name.
   195  func (g *Gen) NewV5(ns UUID, name string) UUID {
   196  	u := newFromHash(sha1.New(), ns, name)
   197  	u.SetVersion(V5)
   198  	u.SetVariant(VariantRFC4122)
   199  
   200  	return u
   201  }
   202  
   203  // Returns the epoch and clock sequence.
   204  func (g *Gen) getClockSequence() (uint64, uint16, error) {
   205  	var err error
   206  	g.clockSequenceOnce.Do(func() {
   207  		buf := make([]byte, 2)
   208  		if _, err = io.ReadFull(g.rand, buf); err != nil {
   209  			return
   210  		}
   211  		g.clockSequence = binary.BigEndian.Uint16(buf)
   212  	})
   213  	if err != nil {
   214  		return 0, 0, err
   215  	}
   216  
   217  	g.storageMutex.Lock()
   218  	defer g.storageMutex.Unlock()
   219  
   220  	timeNow := g.getEpoch()
   221  	// Clock didn't change since last UUID generation.
   222  	// Should increase clock sequence.
   223  	if timeNow <= g.lastTime {
   224  		g.clockSequence++
   225  	}
   226  	g.lastTime = timeNow
   227  
   228  	return timeNow, g.clockSequence, nil
   229  }
   230  
   231  // Returns the hardware address.
   232  func (g *Gen) getHardwareAddr() ([]byte, error) {
   233  	var err error
   234  	g.hardwareAddrOnce.Do(func() {
   235  		var hwAddr net.HardwareAddr
   236  		if hwAddr, err = g.hwAddrFunc(); err == nil {
   237  			copy(g.hardwareAddr[:], hwAddr)
   238  			return
   239  		}
   240  
   241  		// Initialize hardwareAddr randomly in case
   242  		// of real network interfaces absence.
   243  		if _, err = io.ReadFull(g.rand, g.hardwareAddr[:]); err != nil {
   244  			return
   245  		}
   246  		// Set multicast bit as recommended by RFC-4122
   247  		g.hardwareAddr[0] |= 0x01
   248  	})
   249  	if err != nil {
   250  		return []byte{}, err
   251  	}
   252  	return g.hardwareAddr[:], nil
   253  }
   254  
   255  // Returns the difference between UUID epoch (October 15, 1582)
   256  // and current time in 100-nanosecond intervals.
   257  func (g *Gen) getEpoch() uint64 {
   258  	return epochStart + uint64(g.epochFunc().UnixNano()/100)
   259  }
   260  
   261  // Returns the UUID based on the hashing of the namespace UUID and name.
   262  func newFromHash(h hash.Hash, ns UUID, name string) UUID {
   263  	u := UUID{}
   264  	mustWrite := func(data []byte) {
   265  		if _, err := h.Write(data); err != nil {
   266  			panic(errors.Wrap(err, "failed to write to hash"))
   267  		}
   268  	}
   269  	mustWrite(ns[:])
   270  	mustWrite([]byte(name))
   271  	copy(u[:], h.Sum(nil))
   272  	return u
   273  }
   274  
   275  // Returns the hardware address.
   276  func defaultHWAddrFunc() (net.HardwareAddr, error) {
   277  	ifaces, err := net.Interfaces()
   278  	if err != nil {
   279  		return []byte{}, err
   280  	}
   281  	for _, iface := range ifaces {
   282  		if len(iface.HardwareAddr) >= 6 {
   283  			return iface.HardwareAddr, nil
   284  		}
   285  	}
   286  	return []byte{}, fmt.Errorf("uuid: no HW address found")
   287  }