Search Apps Documentation Source Content File Folder Download Copy Actions Download

snowflake.gno

3.77 Kb · 132 lines
  1// Package snowflake provides utility functions for generating unique identifiers
  2// (Snowflake IDs) based on a custom implementation inspired by the Snowflake algorithm (https://pkg.go.dev/github.com/godruoyi/go-snowflake#section-sourcefiles).
  3// The Snowflake IDs generated by this package ensure uniqueness and are suitable for
  4// distributed systems. The package offers a mechanism to convert these IDs
  5// to standard SnowflakeID format strings, making them compatible with systems
  6// requiring UUIDs.
  7// Add import "gno.land/p/demo/entropy" to use the entropy package. For of the pseudo-random number generator.
  8// TimestampLength is 42-bit.
  9// 42 it's the maximum value that can be returned "2^42", It's represent 139 years (2^42 secondes)
 10// SequenBits allows for 4096 unique IDs to be generated per millisecond.
 11
 12package snowflake
 13
 14import (
 15	"chain/runtime"
 16	"encoding/binary"
 17	"encoding/hex"
 18	"time"
 19
 20	"gno.land/p/demo/entropy"
 21	"gno.land/p/nt/ufmt"
 22)
 23
 24const (
 25	TimestampBits uint8  = 42
 26	MachineIDBits uint64 = 10
 27	SequenceBits  uint64 = 12
 28	MaxSequence   uint16 = 1<<SequenceBits - 1
 29	MaxTimestamp  uint64 = 1<<TimestampBits - 1
 30	MaxMachineID  uint64 = 1<<MachineIDBits - 1
 31
 32	machineIDMoveLength = SequenceBits
 33	timestampMoveLength = MachineIDBits + SequenceBits
 34
 35	startTime uint64 = 1226354400000 // 10 Nov 2008 23:00:00 UTC in milliseconds (Snowflake epoch)
 36)
 37
 38// SnowflakeID struct for generating unique Snowflake-based identifiers.
 39type SnowflakeID struct {
 40	machineID     uint64
 41	sequence      uint16
 42	startTime     uint64
 43	lastTimestamp uint64
 44	entropy       *entropy.Instance
 45}
 46
 47func NewSnowflakeID() *SnowflakeID {
 48	s := &SnowflakeID{
 49		startTime: startTime,
 50		entropy:   entropy.New(),
 51	}
 52	s.SetMachineID()
 53	return s
 54}
 55
 56// GenerateID generates a unique identifier based on the Snowflake algorithm.
 57// It combines the current timestamp, machine ID, and a sequence number to ensure uniqueness
 58// across distributed systems.
 59func (s *SnowflakeID) GenerateID() uint64 {
 60	current := s.getTimenow()
 61
 62	if current == s.lastTimestamp {
 63		s.incrementSequence()
 64	} else {
 65		s.sequence = 0
 66		s.lastTimestamp = current
 67	}
 68
 69	elapsedTime := uint64(current-s.startTime) % MaxTimestamp
 70
 71	id := (uint64(elapsedTime) << timestampMoveLength) |
 72		(uint64(s.machineID) << machineIDMoveLength) |
 73		uint64(s.incrementSequence())
 74	return id
 75}
 76
 77func (s *SnowflakeID) incrementSequence() uint16 {
 78	random := uint16(s.entropy.Value())
 79	s.sequence = (s.sequence + 1 + random) & MaxSequence
 80	return s.sequence
 81}
 82
 83func (s *SnowflakeID) getTimenow() uint64 {
 84	return uint64(time.Now().UnixMilli())
 85}
 86
 87// SetMachineID sets the machine ID based on the caller’s address.
 88func (s *SnowflakeID) SetMachineID() {
 89	caller := runtime.PreviousRealm().Address() // Retrieve the caller’s address
 90	machineID := uint64(0)
 91	for _, c := range caller.String() {
 92		machineID += uint64(c)
 93	}
 94	machineID %= MaxMachineID
 95	s.machineID = machineID
 96}
 97
 98func SnowflakeIDToUUIDString(id uint64) string {
 99	bytes := make([]byte, 16)
100
101	// Copy transformed ID into the second half of the array
102	copy(bytes[8:], uint64ToBytes(id))
103
104	// Use bits from the Snowflake ID to generate the first half of the SnowflakeID
105	bytes[0] = byte(id >> 60)
106	bytes[1] = byte(id >> 52)
107	bytes[2] = byte(id >> 44)
108	bytes[3] = byte(id >> 36)
109	bytes[4] = byte(id >> 28)
110	bytes[5] = byte(id >> 20)
111	bytes[6] = byte(id >> 12)
112	bytes[7] = byte(id >> 4)
113
114	// set the version and variant bits according to SnowflakeID specification.
115	bytes[6] = (bytes[6] & 0x0f) | 0x40 // version 4 (random)
116	bytes[8] = (bytes[8] & 0x3f) | 0x80 // variant 1 (RFC 4122)
117
118	hexStr := hex.EncodeToString(bytes)
119	return ufmt.Sprintf("%s-%s-%s-%s-%s",
120		hexStr[0:8],
121		hexStr[8:12],
122		hexStr[12:16],
123		hexStr[16:20],
124		hexStr[20:],
125	)
126}
127
128func uint64ToBytes(i uint64) []byte {
129	buf := make([]byte, 8)
130	binary.BigEndian.PutUint64(buf, i)
131	return buf
132}