github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/cos/uuid.go (about)

     1  // Package cos provides common low-level types and utilities for all aistore projects
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package cos
     6  
     7  import (
     8  	"fmt"
     9  	"strconv"
    10  
    11  	"github.com/NVIDIA/aistore/cmn/atomic"
    12  	"github.com/OneOfOne/xxhash"
    13  	"github.com/teris-io/shortid"
    14  )
    15  
    16  const LenShortID = 9 // UUID length, as per https://github.com/teris-io/shortid#id-length
    17  
    18  const (
    19  	// Alphabet for generating UUIDs similar to the shortid.DEFAULT_ABC
    20  	// NOTE: len(uuidABC) > 0x3f - see GenTie()
    21  	uuidABC = "-5nZJDft6LuzsjGNpPwY7rQa39vehq4i1cV2FROo8yHSlC0BUEdWbIxMmTgKXAk_"
    22  
    23  	lenDaemonID  = 8  // via cryptographic rand
    24  	lenTooLongID = 32 // suspiciously long
    25  
    26  	lenK8sProxyID = 13
    27  )
    28  
    29  const (
    30  	OnlyNice = "may only contain letters, numbers, dashes (-), underscores (_), and dots (.)"
    31  	OnlyPlus = OnlyNice + ", and dots (.)"
    32  )
    33  
    34  var (
    35  	sid  *shortid.Shortid
    36  	rtie atomic.Uint32
    37  )
    38  
    39  func InitShortID(seed uint64) {
    40  	sid = shortid.MustNew(4 /*worker*/, uuidABC, seed)
    41  }
    42  
    43  //
    44  // UUID
    45  //
    46  
    47  // compare with xreg.GenBEID
    48  func GenUUID() (uuid string) {
    49  	var h, t string
    50  	uuid = sid.MustGenerate()
    51  	if !isAlpha(uuid[0]) {
    52  		tie := int(rtie.Add(1))
    53  		h = string(rune('A' + tie%26))
    54  	}
    55  	c := uuid[len(uuid)-1]
    56  	if c == '-' || c == '_' {
    57  		tie := int(rtie.Add(1))
    58  		t = string(rune('a' + tie%26))
    59  	}
    60  	return h + uuid + t
    61  }
    62  
    63  // "best-effort ID" - to independently and locally generate globally unique ID
    64  // called by xreg.GenBEID
    65  func GenBEID(val uint64, l int) string {
    66  	b := make([]byte, l)
    67  	for i := range l {
    68  		if idx := int(val & letterIdxMask); idx < LenRunes {
    69  			b[i] = LetterRunes[idx]
    70  		} else {
    71  			b[i] = LetterRunes[idx-LenRunes]
    72  		}
    73  		val >>= letterIdxBits
    74  	}
    75  	return UnsafeS(b)
    76  }
    77  
    78  func IsValidUUID(uuid string) bool {
    79  	return len(uuid) >= LenShortID && IsAlphaNice(uuid)
    80  }
    81  
    82  func ValidateNiceID(id string, minlen int, tag string) (err error) {
    83  	if len(id) < minlen {
    84  		return fmt.Errorf("%s %q is too short", tag, id)
    85  	}
    86  	if len(id) >= lenTooLongID {
    87  		return fmt.Errorf("%s %q is too long", tag, id)
    88  	}
    89  	if !IsAlphaNice(id) {
    90  		err = fmt.Errorf("%s %q is invalid: must start with a letter and can only contain [A-Za-z0-9-_]", tag, id)
    91  	}
    92  	return
    93  }
    94  
    95  //
    96  // Daemon ID
    97  //
    98  
    99  func GenDaemonID() string { return CryptoRandS(lenDaemonID) }
   100  
   101  func HashK8sProxyID(nodeName string) (pid string) {
   102  	digest := xxhash.Checksum64S(UnsafeB(nodeName), MLCG32)
   103  	pid = strconv.FormatUint(digest, 36)
   104  	if pid[0] >= '0' && pid[0] <= '9' {
   105  		pid = pid[1:]
   106  	}
   107  	if l := lenK8sProxyID - len(pid); l > 0 {
   108  		return GenBEID(digest, l) + pid
   109  	}
   110  	return pid
   111  }
   112  
   113  func ValidateDaemonID(id string) error { return ValidateNiceID(id, lenDaemonID, "node ID") }
   114  
   115  // (when config.TestingEnv)
   116  func GenTestingDaemonID(suffix string) string {
   117  	l := Max(lenDaemonID-len(suffix), 3)
   118  	return CryptoRandS(l) + suffix
   119  }
   120  
   121  //
   122  // utility functions
   123  //
   124  
   125  func isAlpha(c byte) bool {
   126  	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
   127  }
   128  
   129  // letters and numbers w/ '-' and '_' permitted with limitations (below)
   130  // (see OnlyNice above)
   131  func IsAlphaNice(s string) bool {
   132  	l := len(s)
   133  	for i, c := range s {
   134  		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') {
   135  			continue
   136  		}
   137  		if c != '-' && c != '_' {
   138  			return false
   139  		}
   140  		if i == 0 || i == l-1 {
   141  			return false
   142  		}
   143  	}
   144  	return true
   145  }
   146  
   147  // alpha-numeric++ including letters, numbers, dashes (-), and underscores (_)
   148  // period (.) is allowed except for '..'
   149  // (see OnlyPlus above)
   150  func IsAlphaPlus(s string) bool {
   151  	for i, c := range s {
   152  		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
   153  			continue
   154  		}
   155  		if c != '.' {
   156  			return false
   157  		}
   158  		if i < len(s)-1 && s[i+1] == '.' {
   159  			return false
   160  		}
   161  	}
   162  	return true
   163  }
   164  
   165  // 3-letter tie breaker (fast)
   166  func GenTie() string {
   167  	tie := rtie.Add(1)
   168  	b0 := uuidABC[tie&0x3f]
   169  	b1 := uuidABC[-tie&0x3f]
   170  	b2 := uuidABC[(tie>>2)&0x3f]
   171  	return string([]byte{b0, b1, b2})
   172  }