-
Notifications
You must be signed in to change notification settings - Fork 100
/
random.go
86 lines (74 loc) · 1.95 KB
/
random.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package random
import (
"bufio"
"crypto/rand"
"io"
"strings"
"sync"
)
type (
Random struct {
readerPool sync.Pool
}
)
// Charsets
const (
Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Lowercase = "abcdefghijklmnopqrstuvwxyz"
Alphabetic = Uppercase + Lowercase
Numeric = "0123456789"
Alphanumeric = Alphabetic + Numeric
Symbols = "`" + `~!@#$%^&*()-_+={}[]|\;:"<>,./?`
Hex = Numeric + "abcdef"
)
var (
global = New()
)
func New() *Random {
// https://tip.golang.org/doc/go1.19#:~:text=Read%20no%20longer%20buffers%20random%20data%20obtained%20from%20the%20operating%20system%20between%20calls
p := sync.Pool{New: func() interface{} {
return bufio.NewReader(rand.Reader)
}}
return &Random{readerPool: p}
}
func (r *Random) String(length uint8, charsets ...string) string {
charset := strings.Join(charsets, "")
if charset == "" {
charset = Alphanumeric
}
charsetLen := len(charset)
if charsetLen > 255 {
charsetLen = 255
}
maxByte := 255 - (256 % charsetLen)
reader := r.readerPool.Get().(*bufio.Reader)
defer r.readerPool.Put(reader)
b := make([]byte, length)
rs := make([]byte, length+(length/4)) // perf: avoid read from rand.Reader many times
var i uint8 = 0
// security note:
// we can't just simply do b[i]=charset[rb%byte(charsetLen)],
// for example, when charsetLen is 52, and rb is [0, 255], 256 = 52 * 4 + 48.
// this will make the first 48 characters more possibly to be generated then others.
// so we have to skip bytes when rb > maxByte
for {
_, err := io.ReadFull(reader, rs)
if err != nil {
panic("unexpected error happened when reading from bufio.NewReader(crypto/rand.Reader)")
}
for _, rb := range rs {
if rb > byte(maxByte) {
// Skip this number to avoid bias.
continue
}
b[i] = charset[rb%byte(charsetLen)]
i++
if i == length {
return string(b)
}
}
}
}
func String(length uint8, charsets ...string) string {
return global.String(length, charsets...)
}