1
1
package random
2
2
3
3
import (
4
- "math/rand"
4
+ "bufio"
5
+ "crypto/rand"
6
+ "io"
5
7
"strings"
6
- "time "
8
+ "sync "
7
9
)
8
10
9
11
type (
10
12
Random struct {
13
+ readerPool sync.Pool
11
14
}
12
15
)
13
16
@@ -27,20 +30,55 @@ var (
27
30
)
28
31
29
32
func New () * Random {
30
- rand .Seed (time .Now ().UnixNano ())
31
- return new (Random )
33
+ // https://tip.golang.org/doc/go1.19#:~:text=Read%20no%20longer%20buffers%20random%20data%20obtained%20from%20the%20operating%20system%20between%20calls
34
+ p := sync.Pool {New : func () interface {} {
35
+ return bufio .NewReader (rand .Reader )
36
+ }}
37
+ return & Random {readerPool : p }
32
38
}
33
39
34
40
func (r * Random ) String (length uint8 , charsets ... string ) string {
35
41
charset := strings .Join (charsets , "" )
36
42
if charset == "" {
37
43
charset = Alphanumeric
38
44
}
45
+
46
+ charsetLen := len (charset )
47
+ if charsetLen > 255 {
48
+ charsetLen = 255
49
+ }
50
+ maxByte := 255 - (256 % charsetLen )
51
+
52
+ reader := r .readerPool .Get ().(* bufio.Reader )
53
+ defer r .readerPool .Put (reader )
54
+
39
55
b := make ([]byte , length )
40
- for i := range b {
41
- b [i ] = charset [rand .Int63 ()% int64 (len (charset ))]
56
+ rs := make ([]byte , length + (length / 4 )) // perf: avoid read from rand.Reader many times
57
+ var i uint8 = 0
58
+
59
+ // security note:
60
+ // we can't just simply do b[i]=charset[rb%byte(charsetLen)],
61
+ // for example, when charsetLen is 52, and rb is [0, 255], 256 = 52 * 4 + 48.
62
+ // this will make the first 48 characters more possibly to be generated then others.
63
+ // so we have to skip bytes when rb > maxByte
64
+
65
+ for {
66
+ _ , err := io .ReadFull (reader , rs )
67
+ if err != nil {
68
+ panic ("unexpected error happened when reading from bufio.NewReader(crypto/rand.Reader)" )
69
+ }
70
+ for _ , rb := range rs {
71
+ if rb > byte (maxByte ) {
72
+ // Skip this number to avoid bias.
73
+ continue
74
+ }
75
+ b [i ] = charset [rb % byte (charsetLen )]
76
+ i ++
77
+ if i == length {
78
+ return string (b )
79
+ }
80
+ }
42
81
}
43
- return string (b )
44
82
}
45
83
46
84
func String (length uint8 , charsets ... string ) string {
0 commit comments