github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/ekatyp/uuid_private.go (about)

     1  // Copyright © 2020. All rights reserved.
     2  // Author: Ilya Stroy.
     3  // Contacts: iyuryevich@pm.me, https://github.com/qioalice
     4  // License: https://opensource.org/licenses/MIT
     5  
     6  // Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
     7  //
     8  // Permission is hereby granted, free of charge, to any person obtaining
     9  // a copy of this software and associated documentation files (the
    10  // "Software"), to deal in the Software without restriction, including
    11  // without limitation the rights to use, copy, modify, merge, publish,
    12  // distribute, sublicense, and/or sell copies of the Software, and to
    13  // permit persons to whom the Software is furnished to do so, subject to
    14  // the following conditions:
    15  //
    16  // The above copyright notice and this permission notice shall be
    17  // included in all copies or substantial portions of the Software.
    18  //
    19  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    20  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    21  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    22  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    23  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    24  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    25  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    26  
    27  package ekatyp
    28  
    29  import (
    30  	"bytes"
    31  	"crypto/md5"
    32  	"crypto/rand"
    33  	"crypto/sha1"
    34  	"encoding/binary"
    35  	"encoding/hex"
    36  	"fmt"
    37  	"hash"
    38  	"io"
    39  	"net"
    40  	"sync"
    41  	"time"
    42  
    43  	"github.com/qioalice/ekago/v3/ekasys"
    44  )
    45  
    46  //noinspection GoSnakeCaseUsage (Intellij IDEA suppress snake case warning).
    47  type (
    48  	// _UUID_Generator provides interface for generating UUIDs.
    49  	_UUID_Generator interface {
    50  		NewV1() (UUID, error)
    51  		NewV2(domain byte) (UUID, error)
    52  		NewV3(ns UUID, name string) UUID
    53  		NewV4() (UUID, error)
    54  		NewV5(ns UUID, name string) UUID
    55  	}
    56  
    57  	// Default generator implementation.
    58  	_T_UUID_RFC4122_Generator struct {
    59  		storageMutex sync.Mutex
    60  
    61  		hwAddr    [6]byte
    62  		hwAddrErr error
    63  
    64  		clockSequence    uint16
    65  		clockSequenceErr error
    66  
    67  		rand io.Reader
    68  
    69  		lastTime uint64
    70  	}
    71  )
    72  
    73  //noinspection GoSnakeCaseUsage (Intellij IDEA suppress snake case warning).
    74  const (
    75  	_UUID_SIZE = 16 // size of a UUID in bytes.
    76  
    77  	// Difference in 100-nanosecond intervals between
    78  	// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970).
    79  	_UUID_EPOCH_START = 122192928000000000
    80  )
    81  
    82  //noinspection GoSnakeCaseUsage (Intellij IDEA suppress snake case warning).
    83  var (
    84  	_UUID_RFC4122_Generator = newRFC4122Generator(rand.Reader)
    85  
    86  	// String parse helpers.
    87  	_UUID_URN_Prefix = []byte("urn:uuid:")
    88  	_UUID_ByteGroups = []int{8, 4, 4, 4, 12}
    89  
    90  	_UUID_JSON_NULL = []byte("null")
    91  )
    92  
    93  // hexEncodeTo encodes UUID as hex to dest with canonical string representation:
    94  // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (w/o quotes). Requires: len(dest) >= 36.
    95  // Returns dest.
    96  func (u UUID) hexEncodeTo(dest []byte) []byte {
    97  
    98  	dest[8] = '-'
    99  	dest[13] = '-'
   100  	dest[18] = '-'
   101  	dest[23] = '-'
   102  
   103  	hex.Encode(dest[0:8], u[0:4])
   104  	hex.Encode(dest[9:13], u[4:6])
   105  	hex.Encode(dest[14:18], u[6:8])
   106  	hex.Encode(dest[19:23], u[8:10])
   107  	hex.Encode(dest[24:], u[10:])
   108  
   109  	return dest
   110  }
   111  
   112  // jsonMarshal returns canonical string representation of UUID with double quotes:
   113  // "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".
   114  func (u UUID) jsonMarshal() []byte {
   115  
   116  	buf := make([]byte, 38)
   117  
   118  	buf[0] = '"'
   119  	buf[37] = '"'
   120  
   121  	u.hexEncodeTo(buf[1:37])
   122  
   123  	return buf
   124  }
   125  
   126  // decodeCanonical decodes UUID string in format
   127  // "6ba7b810-9dad-11d1-80b4-00c04fd430c8".
   128  func (u *UUID) decodeCanonical(t []byte) (err error) {
   129  	if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' {
   130  		return fmt.Errorf("uuid: incorrect UUID format %s", t)
   131  	}
   132  
   133  	src := t[:]
   134  	dst := u[:]
   135  
   136  	for i, byteGroup := range _UUID_ByteGroups {
   137  		if i > 0 {
   138  			src = src[1:] // skip dash
   139  		}
   140  		_, err = hex.Decode(dst[:byteGroup/2], src[:byteGroup])
   141  		if err != nil {
   142  			return
   143  		}
   144  		src = src[byteGroup:]
   145  		dst = dst[byteGroup/2:]
   146  	}
   147  
   148  	return
   149  }
   150  
   151  // decodeHashLike decodes UUID string in format
   152  // "6ba7b8109dad11d180b400c04fd430c8".
   153  func (u *UUID) decodeHashLike(t []byte) (err error) {
   154  	src := t[:]
   155  	dst := u[:]
   156  
   157  	if _, err = hex.Decode(dst, src); err != nil {
   158  		return err
   159  	}
   160  	return
   161  }
   162  
   163  // decodeBraced decodes UUID string in format
   164  // "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" or in format
   165  // "{6ba7b8109dad11d180b400c04fd430c8}".
   166  func (u *UUID) decodeBraced(t []byte) (err error) {
   167  	l := len(t)
   168  
   169  	if t[0] != '{' || t[l-1] != '}' {
   170  		return fmt.Errorf("uuid: incorrect UUID format %s", t)
   171  	}
   172  
   173  	return u.decodePlain(t[1 : l-1])
   174  }
   175  
   176  // decodeURN decodes UUID string in format
   177  // "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in format
   178  // "urn:uuid:6ba7b8109dad11d180b400c04fd430c8".
   179  func (u *UUID) decodeURN(t []byte) (err error) {
   180  	total := len(t)
   181  
   182  	urn_uuid_prefix := t[:9]
   183  
   184  	if !bytes.Equal(urn_uuid_prefix, _UUID_URN_Prefix) {
   185  		return fmt.Errorf("uuid: incorrect UUID format: %s", t)
   186  	}
   187  
   188  	return u.decodePlain(t[9:total])
   189  }
   190  
   191  // decodePlain decodes UUID string in canonical format
   192  // "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format
   193  // "6ba7b8109dad11d180b400c04fd430c8".
   194  func (u *UUID) decodePlain(t []byte) (err error) {
   195  	switch len(t) {
   196  	case 32:
   197  		return u.decodeHashLike(t)
   198  	case 36:
   199  		return u.decodeCanonical(t)
   200  	default:
   201  		return fmt.Errorf("uuid: incorrrect UUID length: %s", t)
   202  	}
   203  }
   204  
   205  // NewV1 returns UUID based on current timestamp and MAC address.
   206  func (g *_T_UUID_RFC4122_Generator) NewV1() (UUID, error) {
   207  	u := UUID{}
   208  
   209  	timeNow, clockSeq, err := g.getClockSequence()
   210  	if err != nil {
   211  		return _UUID_NULL, err
   212  	}
   213  	binary.BigEndian.PutUint32(u[0:], uint32(timeNow))
   214  	binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
   215  	binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
   216  	binary.BigEndian.PutUint16(u[8:], clockSeq)
   217  
   218  	if g.hwAddrErr != nil {
   219  		return _UUID_NULL, err
   220  	}
   221  	copy(u[10:], g.hwAddr[:])
   222  
   223  	u.SetVersion(UUID_V1)
   224  	u.SetVariant(UUID_VARIANT_RFC4122)
   225  
   226  	return u, nil
   227  }
   228  
   229  // NewV2 returns DCE Security UUID based on POSIX UID/GID.
   230  func (g *_T_UUID_RFC4122_Generator) NewV2(domain byte) (UUID, error) {
   231  	u, err := g.NewV1()
   232  	if err != nil {
   233  		return _UUID_NULL, err
   234  	}
   235  
   236  	switch domain {
   237  	case UUID_DOMAIN_PERSON:
   238  		binary.BigEndian.PutUint32(u[:], ekasys.PosixCachedUid())
   239  	case UUID_DOMAIN_GROUP:
   240  		binary.BigEndian.PutUint32(u[:], ekasys.PosixCachedGid())
   241  	}
   242  
   243  	u[9] = domain
   244  
   245  	u.SetVersion(UUID_V2)
   246  	u.SetVariant(UUID_VARIANT_RFC4122)
   247  
   248  	return u, nil
   249  }
   250  
   251  // NewV3 returns UUID based on MD5 hash of namespace UUID and name.
   252  func (g *_T_UUID_RFC4122_Generator) NewV3(ns UUID, name string) UUID {
   253  	u := g.newFromHash(md5.New(), ns, name)
   254  	u.SetVersion(UUID_V3)
   255  	u.SetVariant(UUID_VARIANT_RFC4122)
   256  
   257  	return u
   258  }
   259  
   260  // NewV4 returns random generated UUID.
   261  func (g *_T_UUID_RFC4122_Generator) NewV4() (UUID, error) {
   262  	u := UUID{}
   263  	if _, err := io.ReadFull(g.rand, u[:]); err != nil {
   264  		return _UUID_NULL, err
   265  	}
   266  	u.SetVersion(UUID_V4)
   267  	u.SetVariant(UUID_VARIANT_RFC4122)
   268  
   269  	return u, nil
   270  }
   271  
   272  // NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
   273  func (g *_T_UUID_RFC4122_Generator) NewV5(ns UUID, name string) UUID {
   274  	u := g.newFromHash(sha1.New(), ns, name)
   275  	u.SetVersion(UUID_V5)
   276  	u.SetVariant(UUID_VARIANT_RFC4122)
   277  
   278  	return u
   279  }
   280  
   281  // Returns epoch and clock sequence.
   282  func (g *_T_UUID_RFC4122_Generator) getClockSequence() (uint64, uint16, error) {
   283  
   284  	if g.clockSequenceErr != nil {
   285  		return 0, 0, g.clockSequenceErr
   286  	}
   287  
   288  	g.storageMutex.Lock()
   289  	defer g.storageMutex.Unlock()
   290  
   291  	timeNow := _UUID_EPOCH_START + uint64(time.Now().UnixNano()/100)
   292  	// Clock didn't change since last UUID generation.
   293  	// Should increase clock sequence.
   294  	if timeNow <= g.lastTime {
   295  		g.clockSequence++
   296  	}
   297  	g.lastTime = timeNow
   298  
   299  	return timeNow, g.clockSequence, nil
   300  }
   301  
   302  // Returns UUID based on hashing of namespace UUID and name.
   303  func (_ *_T_UUID_RFC4122_Generator) newFromHash(h hash.Hash, ns UUID, name string) UUID {
   304  	u := UUID{}
   305  	h.Write(ns[:])
   306  	h.Write([]byte(name))
   307  	copy(u[:], h.Sum(nil))
   308  
   309  	return u
   310  }
   311  
   312  // newRFC4122Generator creates, initializes and returns an RFC4122 UUID generator.
   313  func newRFC4122Generator(randReader io.Reader) _UUID_Generator {
   314  
   315  	var (
   316  		g      _T_UUID_RFC4122_Generator
   317  		ifaces []net.Interface
   318  	)
   319  
   320  	g.rand = randReader
   321  
   322  	ifaces, g.hwAddrErr = net.Interfaces()
   323  	// no need to check err because if err != nil, ifaces is empty
   324  
   325  	for _, iface := range ifaces {
   326  		if len(iface.HardwareAddr) >= 6 {
   327  			copy(g.hwAddr[:], iface.HardwareAddr)
   328  		}
   329  	}
   330  
   331  	// do not overwrite g.hwAddrErr
   332  	// but update it if g.hwAddr has not been set after the loop above
   333  	if g.hwAddrErr == nil && bytes.Equal(g.hwAddr[:], make([]byte, 6)) {
   334  		g.hwAddrErr = fmt.Errorf("uuid: no HW address found")
   335  	}
   336  
   337  	// Initialize g.hwAddr randomly in case of real network interfaces absence.
   338  	if g.hwAddrErr != nil {
   339  		if _, err := io.ReadFull(g.rand, g.hwAddr[:]); err == nil {
   340  			g.hwAddr[0] |= 0x01 // Set multicast bit as recommended by RFC 4122
   341  			g.hwAddrErr = nil   // Ignore all previous errors
   342  		} else {
   343  			g.hwAddrErr = fmt.Errorf("uuid: no HW address found and failed to mock it: %s", err.Error())
   344  		}
   345  	}
   346  
   347  	buf := make([]byte, 2)
   348  	if _, err := io.ReadFull(g.rand, buf); err == nil {
   349  		g.clockSequence = binary.BigEndian.Uint16(buf)
   350  	} else {
   351  		g.clockSequenceErr = fmt.Errorf("uuid: failed to generate clock sequence: %s", err.Error())
   352  	}
   353  
   354  	return &g
   355  }