/
sid.go
155 lines (129 loc) · 4.17 KB
/
sid.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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package sid
import (
"crypto/sha1"
"encoding/base64"
"os"
"sync"
"time"
"github.com/chanxuehong/internal"
"github.com/chanxuehong/rand"
)
// 56bits unix100ns + 12bits pid + 12bits sequence + 48bits node + 64bits hashsum
//
// +------ 0 ------+------ 1 ------+------ 2 ------+------ 3 ------+
// +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | time_low |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | time_mid | time_hi_and_pid_low |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |clk_seq_hi_pid | clk_seq_low | node (0-1) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | node (2-5) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | hash (0-3) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | hash (4-7) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
var pid = hash(uint64(os.Getpid())) // 12-bit hashsum of os.Getpid(), read only.
// hash uint64 to a 12-bit integer value.
func hash(x uint64) uint64 {
return (x ^ x>>12 ^ x>>24 ^ x>>36 ^ x>>48 ^ x>>60) & 0xfff
}
var node = internal.MAC[:] // read only
const (
sequenceMask = 0xfff // 12bits
saltLen = 43 // see New(), 8+4+43==55<56, Best performance for sha1.
saltUpdateInterval = 3600 // seconds
)
var (
gSalt = make([]byte, saltLen)
gMutex sync.Mutex // protect following
gSequenceStart uint32 = rand.Uint32() & sequenceMask
gLastTimestamp int64 = -1
gLastSequence uint32 = gSequenceStart
gSaltLastUpdateTimestamp int64 = -saltUpdateInterval
gSaltSequence uint32 = rand.Uint32()
)
// New returns a unique 32-byte url-safe string.
func New() string {
var (
timeNow = time.Now()
timeNowUnix = timeNow.Unix()
timestamp = unix100nano(timeNow)
sequence uint32
saltShouldUpdate = false
saltSequence uint32
)
gMutex.Lock() // Lock
switch {
case timestamp > gLastTimestamp:
sequence = gSequenceStart
gLastTimestamp = timestamp
gLastSequence = sequence
case timestamp == gLastTimestamp:
sequence = (gLastSequence + 1) & sequenceMask
if sequence == gSequenceStart {
timestamp = tillNext100nano(timestamp)
gLastTimestamp = timestamp
}
gLastSequence = sequence
default:
gSequenceStart = rand.Uint32() & sequenceMask // NOTE
sequence = gSequenceStart
gLastTimestamp = timestamp
gLastSequence = sequence
}
if timeNowUnix >= gSaltLastUpdateTimestamp+saltUpdateInterval {
saltShouldUpdate = true
gSaltLastUpdateTimestamp = timeNowUnix
}
gSaltSequence++
saltSequence = gSaltSequence
gMutex.Unlock() // Unlock
// 56bits unix100ns + 12bits pid + 12bits sequence + 48bits node + 64bits hashsum
var idx [24]byte
// time_low
idx[0] = byte(timestamp >> 24)
idx[1] = byte(timestamp >> 16)
idx[2] = byte(timestamp >> 8)
idx[3] = byte(timestamp)
// time_mid
idx[4] = byte(timestamp >> 40)
idx[5] = byte(timestamp >> 32)
// time_hi_and_pid_low
idx[6] = byte(timestamp >> 48)
idx[7] = byte(pid)
// clk_seq_hi_pid
idx[8] = byte(sequence>>8) & 0x0f
idx[8] |= byte(pid>>8) << 4
// clk_seq_low
idx[9] = byte(sequence)
// node
copy(idx[10:], node)
// hashsum
if saltShouldUpdate {
rand.Read(gSalt)
copy(idx[16:], gSalt)
} else {
var src [8 + 4 + saltLen]byte // 8+4+43==55
src[0] = byte(timestamp >> 56)
src[1] = byte(timestamp >> 48)
src[2] = byte(timestamp >> 40)
src[3] = byte(timestamp >> 32)
src[4] = byte(timestamp >> 24)
src[5] = byte(timestamp >> 16)
src[6] = byte(timestamp >> 8)
src[7] = byte(timestamp)
src[8] = byte(saltSequence >> 24)
src[9] = byte(saltSequence >> 16)
src[10] = byte(saltSequence >> 8)
src[11] = byte(saltSequence)
copy(src[12:], gSalt)
hashsum := sha1.Sum(src[:])
copy(idx[16:], hashsum[:])
}
id := make([]byte, 32)
base64.URLEncoding.Encode(id, idx[:])
return string(id)
}