// Package ids makes the request and approval identifiers from docs/hub-api.md. // The shape is a ULID: 26 Crockford base32 characters, time-ordered. It needs // no dependency, so the Hub stays a small static binary. package ids import ( "crypto/rand" "sync" "time" ) // Crockford base32 alphabet. ULID uses these 32 symbols. const alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" var ( mu sync.Mutex lastMS uint64 lastRnd [10]byte ) // New returns a 26-character ULID string. The first 10 characters hold the // time. The last 16 hold randomness. Two calls in the same millisecond do not // repeat. func New() string { mu.Lock() defer mu.Unlock() ms := uint64(time.Now().UnixMilli()) if ms == lastMS { // Same millisecond: increment the random part. for i := len(lastRnd) - 1; i >= 0; i-- { lastRnd[i]++ if lastRnd[i] != 0 { break } } } else { lastMS = ms if _, err := rand.Read(lastRnd[:]); err != nil { // crypto/rand does not fail in practice. Fall back to the clock. for i := range lastRnd { lastRnd[i] = byte(ms >> (8 * (i % 8))) } } } return encodeTime(ms) + encodeRandom(lastRnd) } // Approval returns an approval id, for example "ap_01J8Z...". func Approval() string { return "ap_" + New() } // encodeTime writes 10 base32 characters from the low 50 bits of ms. func encodeTime(ms uint64) string { var out [10]byte for i := 9; i >= 0; i-- { out[i] = alphabet[ms&0x1f] ms >>= 5 } return string(out[:]) } // encodeRandom writes 16 base32 characters from 10 random bytes (80 bits). func encodeRandom(rnd [10]byte) string { var out [16]byte var hi, lo uint64 hi = uint64(rnd[0])<<8 | uint64(rnd[1]) hi = hi<<8 | uint64(rnd[2]) hi = hi<<8 | uint64(rnd[3]) hi = hi<<8 | uint64(rnd[4]) lo = uint64(rnd[5])<<8 | uint64(rnd[6]) lo = lo<<8 | uint64(rnd[7]) lo = lo<<8 | uint64(rnd[8]) lo = lo<<8 | uint64(rnd[9]) for i := 15; i >= 8; i-- { out[i] = alphabet[lo&0x1f] lo >>= 5 } for i := 7; i >= 0; i-- { out[i] = alphabet[hi&0x1f] hi >>= 5 } return string(out[:]) }