-
Notifications
You must be signed in to change notification settings - Fork 1
/
nanoid.go
127 lines (112 loc) · 2.93 KB
/
nanoid.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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package nanoid
import (
"crypto/rand"
"fmt"
"math"
)
// RandomType is the type that the custom random generator has to be
type RandomType func(int) ([]byte, error)
// DefaultsType is the type of the default configuration for Nanoid
type DefaultsType struct {
Alphabet string
Size int
MaskSize int
}
// GetDefaults returns the default configuration for Nanoid
func GetDefaults() *DefaultsType {
return &DefaultsType{
Alphabet: "_~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", // len=64
Size: 22,
MaskSize: 5,
}
}
var defaults = GetDefaults()
func initMasks(params ...int) []uint {
var size int
if len(params) == 0 {
size = defaults.MaskSize
} else {
size = params[0]
}
/*
https://github.com/ai/nanoid/blob/d6ad3412147fa4c2b0d404841ade245a00c2009f/format.js#L1
As per 'var masks = [15, 31, 63, 127, 255]'
The next block initializes an array of size elements, from 2^4-1 to 2^(3 + size)-1
*/
masks := make([]uint, size)
for i := 0; i < size; i++ {
shift := 3 + i
masks[i] = (2 << uint(shift)) - 1
}
return masks
}
/*
https://github.com/ai/nanoid/blob/d6ad3412147fa4c2b0d404841ade245a00c2009f/format.js#L29-L31
var mask = masks.find(function (i) {
return i >= alphabet.length - 1
})
*/
func getMask(alphabet string, masks []uint) int {
for i := 0; i < len(masks); i++ {
curr := int(masks[i])
if curr >= len(alphabet)-1 {
return curr
}
}
return 0
}
// Random generates cryptographically strong pseudo-random data.
// The size argument is a number indicating the number of bytes to generate.
func Random(size int) ([]byte, error) {
var randomBytes = make([]byte, size)
_, err := rand.Read(randomBytes)
return randomBytes, err
}
// Format returns a secure random string with custom random generator and alphabet.
// `size` is the number of symbols in new random string
func Format(random RandomType, alphabet string, size int) (string, error) {
masks := initMasks(size)
mask := getMask(alphabet, masks)
ceilArg := 1.6 * float64(mask*size) / float64(len(alphabet))
step := int(math.Ceil(ceilArg))
id := make([]byte, size)
for j := 0; ; {
bytes, err := random(step)
if err != nil {
return "", err
}
for i := 0; i < step; i++ {
currByte := bytes[i] & byte(mask)
if currByte < byte(len(alphabet)) {
id[j] = alphabet[currByte]
j++
if j == size {
fmt.Println("id", id)
return string(id[:size]), nil
}
}
}
}
}
// Generate is a low-level function to change alphabet and ID size.
func Generate(alphabet string, size int) (string, error) {
return Format(Random, alphabet, size)
}
// Nanoid generates secure URL-friendly unique ID.
func Nanoid(param ...int) (string, error) {
var size int
if len(param) == 0 {
size = defaults.Size
} else {
size = param[0]
}
bytes, err := Random(size)
if err != nil {
return "", err
}
id := make([]byte, size)
for i := 0; i < size; i++ {
id[i] = defaults.Alphabet[bytes[i]&63]
}
return string(id[:size]), nil
}