diff --git a/main.go b/main.go index 4dad4c8..b59cdd4 100644 --- a/main.go +++ b/main.go @@ -510,17 +510,34 @@ func generateUniqueID() (string, error) { return "", fmt.Errorf("failed to generate unique ID after %d retries", maxRetries) } +// generateID returns a random ID drawn uniformly from idCharset. Bytes landing +// in the last, partial repetition of the charset within a byte's range are +// discarded rather than folded in with a bare modulo: 256 = 7*36 + 4, so +// folding would make "a" through "d" about 14% more likely than the other 32 +// characters. Only 4 of 256 values are rejected, so a second read is rarely +// needed. func generateID() string { - bytes := make([]byte, idLength) - if _, err := rand.Read(bytes); err != nil { - panic(err) + const unbiasedLimit = byte(256 - 256%len(idCharset)) + + id := make([]byte, 0, idLength) + buf := make([]byte, idLength) + for len(id) < idLength { + if _, err := rand.Read(buf); err != nil { + panic(err) + } + + for _, b := range buf { + if b >= unbiasedLimit { + continue + } + id = append(id, idCharset[int(b)%len(idCharset)]) + if len(id) == idLength { + break + } + } } - for i, b := range bytes { - bytes[i] = idCharset[int(b)%len(idCharset)] - } - - return string(bytes) + return string(id) } func getHost(r *http.Request) string {