git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/guid/guid.go (about)

     1  package guid
     2  
     3  import (
     4  	"crypto/rand"
     5  	"database/sql/driver"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"sync"
    10  	"time"
    11  
    12  	"git.sr.ht/~pingoo/stdx/base32"
    13  	"git.sr.ht/~pingoo/stdx/crypto"
    14  )
    15  
    16  const (
    17  	Size = 16
    18  
    19  	randPoolSize = 128 * 16
    20  )
    21  
    22  // A GUID is a 128 bit (16 byte) Globally Unique IDentifier
    23  type GUID [Size]byte
    24  
    25  var (
    26  	ErrGuidIsNotValid = errors.New("GUID is not valid")
    27  	ErrUuidIsNotValid = errors.New("Not a valid UUID")
    28  )
    29  
    30  var (
    31  	Empty GUID // empty GUID, all zeros
    32  
    33  	poolEnabled  = true
    34  	randomSource = rand.Reader // random function
    35  	poolMutex    sync.Mutex
    36  	poolPosition = randPoolSize     // protected with poolMutex
    37  	pool         [randPoolSize]byte // protected with poolMutex
    38  )
    39  
    40  func NewRandom() GUID {
    41  	guid, err := NewRandomWithErr()
    42  	if err != nil {
    43  		panic(err)
    44  	}
    45  	return guid
    46  }
    47  
    48  func NewRandomWithErr() (GUID, error) {
    49  	if poolEnabled {
    50  		return newRandomFromPool()
    51  	} else {
    52  		return newRandomFromReader(randomSource)
    53  	}
    54  }
    55  
    56  func newRandomFromPool() (GUID, error) {
    57  	var guid GUID
    58  	poolMutex.Lock()
    59  	if poolPosition == randPoolSize {
    60  		_, err := io.ReadFull(randomSource, pool[:])
    61  		if err != nil {
    62  			poolMutex.Unlock()
    63  			return Empty, err
    64  		}
    65  		poolPosition = 0
    66  	}
    67  	copy(guid[:], pool[poolPosition:(poolPosition+16)])
    68  	poolPosition += 16
    69  	poolMutex.Unlock()
    70  
    71  	guid[6] = (guid[6] & 0x0f) | (0x04 << 4) // Version 4
    72  	guid[8] = (guid[8] & 0x3f) | 0x80        // Variant is 10
    73  
    74  	return guid, nil
    75  }
    76  
    77  func newRandomFromReader(reader io.Reader) (GUID, error) {
    78  	var guid GUID
    79  	_, err := io.ReadFull(reader, guid[:])
    80  	if err != nil {
    81  		return Empty, err
    82  	}
    83  
    84  	guid[6] = (guid[6] & 0x0f) | 0x40 // Version 4
    85  	guid[8] = (guid[8] & 0x3f) | 0x80 // Variant is 10
    86  	return guid, nil
    87  }
    88  
    89  func NewTimeBased() GUID {
    90  	guid, err := NewTimeBasedWithErr()
    91  	if err != nil {
    92  		panic(err)
    93  	}
    94  	return guid
    95  }
    96  
    97  // TODO: Improve by reading only 10 bytes of random data
    98  func NewTimeBasedWithErr() (guid GUID, err error) {
    99  	guid, err = NewRandomWithErr()
   100  	if err != nil {
   101  		return
   102  	}
   103  
   104  	now := time.Now().UTC()
   105  	// 48 bit timestamp
   106  	timestamp := uint64(now.UnixNano() / int64(time.Millisecond))
   107  	guid[0] = byte(timestamp >> 40)
   108  	guid[1] = byte(timestamp >> 32)
   109  	guid[2] = byte(timestamp >> 24)
   110  	guid[3] = byte(timestamp >> 16)
   111  	guid[4] = byte(timestamp >> 8)
   112  	guid[5] = byte(timestamp)
   113  
   114  	// var timestamp uint64
   115  	// timestamp += uint64(now.Unix()) * 1e3
   116  	// timestamp += uint64(now.Nanosecond()) / 1e6
   117  	// binary.BigEndian.PutUint64(guid[:8], timestamp<<16)
   118  
   119  	// var x, y byte
   120  	// timestamp := uint64(t.UnixNano() / int64(time.Millisecond))
   121  	// // Backups [6] and [7] bytes to override them with their original values later.
   122  	// x, y, ulid[6], ulid[7] = ulid[6], ulid[7], x, y
   123  	// binary.LittleEndian.PutUint64(ulid[:], timestamp)
   124  	// // Truncates at the 6th byte as designed in the original spec (48 bytes).
   125  	// ulid[6], ulid[7] = x, y
   126  
   127  	// guid[6] = (guid[6] & 0x0f) | 0x07 // Version 7
   128  	guid[6] = (guid[6] & 0x0f) | (0x07 << 4)
   129  	guid[8] = (guid[8] & 0x3f) | 0x80 // Variant is 10
   130  
   131  	return
   132  }
   133  
   134  // TODO: parse without allocs
   135  func Parse(input string) (guid GUID, err error) {
   136  	bytes, err := base32.DecodeString(input)
   137  	if err != nil {
   138  		err = ErrGuidIsNotValid
   139  		return
   140  	}
   141  
   142  	if len(bytes) != Size {
   143  		err = ErrGuidIsNotValid
   144  		return
   145  	}
   146  
   147  	return GUID(bytes), nil
   148  }
   149  
   150  // FromBytes creates a new GUID from a byte slice. Returns an error if the slice
   151  // does not have a length of 16. The bytes are copied from the slice.
   152  func FromBytes(b []byte) (guid GUID, err error) {
   153  	err = guid.UnmarshalBinary(b)
   154  	return guid, err
   155  }
   156  
   157  // String returns the string form of guid
   158  // TODO: encode without alloc
   159  func (guid GUID) String() string {
   160  	return base32.EncodeToString(guid[:])
   161  }
   162  
   163  func (guid GUID) Equal(other GUID) bool {
   164  	return crypto.ConstantTimeCompare(guid[:], other[:])
   165  }
   166  
   167  func (guid GUID) Bytes() []byte {
   168  	return guid[:]
   169  }
   170  
   171  // MarshalText implements encoding.TextMarshaler.
   172  func (guid GUID) MarshalText() ([]byte, error) {
   173  	ret := guid.String()
   174  	return []byte(ret), nil
   175  }
   176  
   177  // UnmarshalText implements encoding.TextUnmarshaler.
   178  func (guid *GUID) UnmarshalText(data []byte) error {
   179  	id, err := Parse(string(data))
   180  	if err != nil {
   181  		return err
   182  	}
   183  	*guid = id
   184  	return nil
   185  }
   186  
   187  // MarshalBinary implements encoding.BinaryMarshaler.
   188  func (guid GUID) MarshalBinary() ([]byte, error) {
   189  	return guid[:], nil
   190  }
   191  
   192  // UnmarshalBinary implements encoding.BinaryUnmarshaler.
   193  func (guid *GUID) UnmarshalBinary(data []byte) error {
   194  	if len(data) != 16 {
   195  		return fmt.Errorf("invalid GUID (got %d bytes)", len(data))
   196  	}
   197  	copy(guid[:], data)
   198  	return nil
   199  }
   200  
   201  // Scan implements sql.Scanner so GUIDs can be read from databases transparently.
   202  // Currently, database types that map to string and []byte are supported. Please
   203  // consult database-specific driver documentation for matching types.
   204  func (guid *GUID) Scan(src interface{}) error {
   205  	switch src := src.(type) {
   206  	case nil:
   207  		return nil
   208  
   209  	case string:
   210  		// if an empty GUID comes from a table, we return a null GUID
   211  		if src == "" {
   212  			return nil
   213  		}
   214  
   215  		// see Parse for required string format
   216  		u, err := ParseUuidString(src)
   217  		if err != nil {
   218  			return fmt.Errorf("Scan: %v", err)
   219  		}
   220  
   221  		*guid = u
   222  
   223  	case []byte:
   224  		// if an empty GUID comes from a table, we return a null GUID
   225  		if len(src) == 0 {
   226  			return nil
   227  		}
   228  
   229  		// assumes a simple slice of bytes if 16 bytes
   230  		// otherwise attempts to parse
   231  		if len(src) != 16 {
   232  			return guid.Scan(string(src))
   233  		}
   234  		copy((*guid)[:], src)
   235  
   236  	default:
   237  		return fmt.Errorf("Scan: unable to scan type %T into GUID", src)
   238  	}
   239  
   240  	return nil
   241  }
   242  
   243  // Value implements sql.Valuer so that GUIDs can be written to databases
   244  // transparently. Currently, GUIDs map to strings. Please consult
   245  // database-specific driver documentation for matching types.
   246  func (guid GUID) Value() (driver.Value, error) {
   247  	return guid.ToUuidString(), nil
   248  }
   249  
   250  // SetRand sets the random number generator to r, which implements io.Reader.
   251  // If r.Read returns an error when the package requests random data then
   252  // a panic will be issued.
   253  //
   254  // Calling SetRand with nil sets the random number generator to the default
   255  // generator.
   256  func SetRand(r io.Reader) {
   257  	if r == nil {
   258  		randomSource = rand.Reader
   259  		return
   260  	}
   261  	randomSource = r
   262  }
   263  
   264  func SetPoolEnabled(enabled bool) {
   265  	poolEnabled = enabled
   266  }