d042fc138c
Signed-off-by: Deluan <deluan@navidrome.org>
53 lines
1.3 KiB
Go
53 lines
1.3 KiB
Go
package nanoid
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"errors"
|
|
"math"
|
|
)
|
|
|
|
// Generate returns a cryptographically secure random string of `size` characters
|
|
// drawn from `alphabet`. It uses bitmask with rejection sampling to avoid modulo bias.
|
|
// The alphabet must be non-empty, contain at most 255 characters, and consist only of
|
|
// ASCII characters. Non-ASCII alphabets (e.g., multi-byte UTF-8) are not supported.
|
|
func Generate(alphabet string, size int) (string, error) {
|
|
if len(alphabet) == 0 || len(alphabet) > 255 {
|
|
return "", errors.New("alphabet must be non-empty and at most 255 characters")
|
|
}
|
|
if size <= 0 {
|
|
return "", errors.New("size must be a positive integer")
|
|
}
|
|
|
|
mask := getMask(len(alphabet))
|
|
step := int(math.Ceil(1.6 * float64(mask) * float64(size) / float64(len(alphabet))))
|
|
|
|
id := make([]byte, size)
|
|
bytes := make([]byte, step)
|
|
for j := 0; ; {
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
return "", err
|
|
}
|
|
for i := range step {
|
|
idx := int(bytes[i]) & mask
|
|
if idx < len(alphabet) {
|
|
id[j] = alphabet[idx]
|
|
j++
|
|
if j == size {
|
|
return string(id), nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getMask returns the smallest bitmask >= alphabetSize-1.
|
|
func getMask(alphabetSize int) int {
|
|
for i := 1; i <= 8; i++ {
|
|
mask := (2 << uint(i)) - 1
|
|
if mask >= alphabetSize-1 {
|
|
return mask
|
|
}
|
|
}
|
|
return 0
|
|
}
|