🏡 index : quine-global/quintodrome.git

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
}