github.com/livekit/protocol@v1.39.3/utils/guid/id.go (about)

     1  // Copyright 2023 LiveKit, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package guid
    16  
    17  import (
    18  	"crypto/rand"
    19  	"crypto/sha1"
    20  	"fmt"
    21  	mrand "math/rand/v2"
    22  	"os"
    23  	"sync"
    24  	"unsafe"
    25  
    26  	"github.com/jxskiss/base62"
    27  	"github.com/lithammer/shortuuid/v4"
    28  
    29  	"github.com/livekit/protocol/livekit"
    30  	"github.com/livekit/protocol/utils/must"
    31  )
    32  
    33  const Size = 12
    34  
    35  const (
    36  	RoomPrefix              = "RM_"
    37  	NodePrefix              = "ND_"
    38  	ParticipantPrefix       = "PA_"
    39  	TrackPrefix             = "TR_"
    40  	APIKeyPrefix            = "API"
    41  	EgressPrefix            = "EG_"
    42  	IngressPrefix           = "IN_"
    43  	SIPTrunkPrefix          = "ST_"
    44  	SIPDispatchRulePrefix   = "SDR_"
    45  	SIPCallPrefix           = "SCL_"
    46  	SIPTransferPrefix       = "STR_"
    47  	RPCPrefix               = "RPC_"
    48  	WHIPResourcePrefix      = "WH_"
    49  	RTMPResourcePrefix      = "RT_"
    50  	URLResourcePrefix       = "UR_"
    51  	AgentWorkerPrefix       = "AW_"
    52  	AgentJobPrefix          = "AJ_"
    53  	AgentDispatchPrefix     = "AD_"
    54  	CloudAgentPrefix        = "CA_"
    55  	CloudAgentRegionPrefix  = "CAR_"
    56  	CloudAgentVersionPrefix = "CAV_"
    57  	CloudAgentSecretPrefix  = "CAS_"
    58  	CloudAgentWorkerPrefix  = "CAW_"
    59  )
    60  
    61  var guidGeneratorPool = sync.Pool{
    62  	New: func() any {
    63  		return must.Get(newGenerator())
    64  	},
    65  }
    66  
    67  func New(prefix string) string {
    68  	g := guidGeneratorPool.Get().(*guidGenerator)
    69  	defer guidGeneratorPool.Put(g)
    70  	return g.New(prefix)
    71  }
    72  
    73  // HashedID creates a hashed ID from a unique string
    74  func HashedID(id string) string {
    75  	h := sha1.New()
    76  	h.Write([]byte(id))
    77  	val := h.Sum(nil)
    78  
    79  	return base62.EncodeToString(val)
    80  }
    81  
    82  func LocalNodeID() (string, error) {
    83  	hostname, err := os.Hostname()
    84  	if err != nil {
    85  		return "", err
    86  	}
    87  	return fmt.Sprintf("%s%s", NodePrefix, HashedID(hostname)[:8]), nil
    88  }
    89  
    90  var b57Index = newB57Index()
    91  var b57Chars = []byte(shortuuid.DefaultAlphabet)
    92  
    93  func newB57Index() [256]byte {
    94  	var index [256]byte
    95  	for i := 0; i < len(b57Chars); i++ {
    96  		index[b57Chars[i]] = byte(i)
    97  	}
    98  	return index
    99  }
   100  
   101  type guidGenerator struct {
   102  	rng *mrand.ChaCha8
   103  }
   104  
   105  func newGenerator() (*guidGenerator, error) {
   106  	var seed [32]byte
   107  	if _, err := rand.Read(seed[:]); err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	return &guidGenerator{
   112  		rng: mrand.NewChaCha8(seed),
   113  	}, nil
   114  }
   115  
   116  func (g *guidGenerator) readIDChars(b []byte) {
   117  	var n int
   118  	for {
   119  		r := g.rng.Uint64()
   120  		for i := 0; i < 10; i++ {
   121  			if int(r&0x3f) < len(b57Chars) {
   122  				b[n] = b57Chars[r&0x3f]
   123  				n++
   124  				if n == len(b) {
   125  					return
   126  				}
   127  			}
   128  			r >>= 6
   129  		}
   130  	}
   131  }
   132  
   133  func (g *guidGenerator) New(prefix string) string {
   134  	b := make([]byte, len(prefix)+Size)
   135  	copy(b, prefix)
   136  	g.readIDChars(b[len(prefix):])
   137  	return unsafe.String(unsafe.SliceData(b), len(b))
   138  }
   139  
   140  func guidPrefix[T livekit.Guid]() string {
   141  	var id T
   142  	switch any(id).(type) {
   143  	case livekit.TrackID:
   144  		return TrackPrefix
   145  	case livekit.ParticipantID:
   146  		return ParticipantPrefix
   147  	case livekit.RoomID:
   148  		return RoomPrefix
   149  	default:
   150  		panic("unreachable")
   151  	}
   152  }
   153  
   154  func Marshal[T livekit.Guid](id T) livekit.GuidBlock {
   155  	return livekit.GuidBlock(MarshalAppend(nil, id))
   156  }
   157  
   158  func MarshalAppend[T livekit.Guid](b []byte, id T) []byte {
   159  	off := len(b)
   160  	b = append(b, make([]byte, Size*3/4)...)
   161  	idb := []byte(id)[len(id)-Size:]
   162  	for i := 0; i < 3; i++ {
   163  		j := i*3 + off
   164  		k := i * 4
   165  		b[j] = b57Index[idb[k]]<<2 | b57Index[idb[k+1]]>>4
   166  		b[j+1] = b57Index[idb[k+1]]<<4 | b57Index[idb[k+2]]>>2
   167  		b[j+2] = b57Index[idb[k+2]]<<6 | b57Index[idb[k+3]]
   168  	}
   169  	return b
   170  }
   171  
   172  func Unmarshal[T livekit.Guid](b livekit.GuidBlock) T {
   173  	prefix := guidPrefix[T]()
   174  	id := make([]byte, len(prefix)+Size)
   175  	copy(id, []byte(prefix))
   176  	idb := id[len(prefix):]
   177  	for i := 0; i < 3; i++ {
   178  		j := i * 3
   179  		k := i * 4
   180  		idb[k] = b57Chars[b[j]>>2]
   181  		idb[k+1] = b57Chars[(b[j]&3)<<4|b[j+1]>>4]
   182  		idb[k+2] = b57Chars[(b[j+1]&15)<<2|b[j+2]>>6]
   183  		idb[k+3] = b57Chars[b[j+2]&63]
   184  	}
   185  	return T(unsafe.String(unsafe.SliceData(id), len(id)))
   186  }