-
Notifications
You must be signed in to change notification settings - Fork 4
/
uuid_v7.go
221 lines (181 loc) · 7.31 KB
/
uuid_v7.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package uuid
import (
"bytes"
"crypto/rand"
"time"
)
type UUIDv7 uuidBase
// Generator is a primary structure that allows you to create new UUIDv7
// SubsecondPrecisionLength is a number of bits to carry sub-second information. On a systems with a high-resulution sub-second precision (like x64 Linux/Windows/MAC) it can go up to 48 bits
// NodePrecisionBits is a number of bits for node information. If this is not set to 0 [default], a Node variable must be set to a non-zero value
// Node information about the node generating UUIDs
// CounterPrecisionBits how many bits are dedicated to a counter. If two UUIDs were generated at the same time an internal counter would increase, distinguishing those UUIDs
type UUIDv7Generator struct {
SubsecondPrecisionLength int
NodePrecisionLength int
Node uint64
CounterPrecisionLength int
//Internal constants used during the generation process.
currentTs []byte
counter uint64
currentPosition int
}
// UUIDv7FromBytes creates a new UUIDv7 from a slice of bytes and returns an error, if an array length does not equal 16.
func UUIDv7FromBytes(b []byte) (uuid UUIDv7, err error) {
var utmp uuidBase
err = utmp.UnmarshalBinary(b)
return UUIDv7(utmp), err
}
// Timestamp returns unix epoch stored in the struct without millisecond precision
func (u UUIDv7) Timestamp() uint64 {
bytes := [16]byte(u)
tmp := toUint64(bytes[0:5])
tmp = tmp >> 4 //We are off by 4 last bits of the byte there.
return tmp
}
// Timestamp returns unix epoch stored in the struct without millisecond precision
func (u UUIDv7) Time() time.Time {
bytes := [16]byte(u)
tmp := toUint64(bytes[0:5])
tmp = tmp >> 4 //We are off by 4 last bits of the byte there.
return time.Unix(int64(tmp), 0)
}
// Ver returns a version of UUID, 07 in this case
func (u UUIDv7) Ver() uint16 {
bytes := [16]byte(u)
var tmp uint16 = uint16(bytes[6:7][0])
tmp = tmp >> 4 //We are off by 4 last bits of the byte there.
return tmp
}
// Var doing something described in the draft, but I don't know what
func (u UUIDv7) Var() uint16 {
bytes := [16]byte(u)
var tmp uint16 = uint16(bytes[8:9][0])
tmp = tmp >> 6 //We are off by 4 last bits of the byte there.
return tmp
}
/*
bytes 0 1 2 3
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unixts |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
bytes 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|unixts | subsec_a | ver | subsec_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
bytes 8 9 10 11
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| subsec_seq_node |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
bytes 12 13 14 15
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| subsec_seq_node |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
// UUIDv7FromBytes creates a new UUIDv7 from a slice of bytes and returns an error, if an array length does not equal 16.
func (u *UUIDv7Generator) Next() (uuid UUIDv7) {
var retval UUIDv7
//Getting current time
then := time.Now()
tsBytes := timeToBytes(then)
//Copy unix timestamp where it suppose to go, bits 0 - 36
for i := 0; i < 36; i++ {
retval = retval.setBit(35-i, getBit(tsBytes, 63-i))
}
u.currentPosition = 36
//Getting nanoseconds
var precisitonBytes []byte
//Use the below to test your counter
// precisitonBytes := []byte{0xff, 0xff}
if u.SubsecondPrecisionLength != 0 {
// fmt.Println("Nanos:", then.Nanosecond())
precisitonBytes, _ = encodeDecimal(float64(then.Nanosecond()), u.SubsecondPrecisionLength)
}
//Adding sub-second precision length
retval, u.currentPosition = retval.stack(u.currentPosition, precisitonBytes, u.SubsecondPrecisionLength)
//Checks if we are going to use counter at all, or we don't need it
//If we are using precision and bytes on the last tick equal current bytes,
//use counter and append it.
//But do this only if we are using precision
if u.SubsecondPrecisionLength != 0 {
if bytes.Equal(u.currentTs, precisitonBytes) {
u.counter++
} else {
u.counter = 0
}
u.currentTs = precisitonBytes
}
//If we are using the counter, it goes right after precision bytes
if u.CounterPrecisionLength != 0 {
//counter bits
retval, u.currentPosition = retval.stack(u.currentPosition, toBytes(u.counter), u.CounterPrecisionLength)
}
//Adding node data after bytes
if u.NodePrecisionLength != 0 {
retval, u.currentPosition = retval.stack(u.currentPosition, toBytes(u.Node), u.NodePrecisionLength)
}
//Create some random crypto data for the tail end
rnd := make([]byte, 16)
rand.Read(rnd)
//Copy crypto data from the array to the end of the GUID
cnt := 0
limit := absoluteIndexer(u.currentPosition)
for i := 127; i > limit; i-- {
//Ommiting bits 48-51 and 64, 65. Those contain version and variant information
if i == 48 || i == 49 || i == 50 || i == 51 || i == 64 || i == 65 {
continue
}
bit := getBit(rnd, cnt)
cnt++
retval = retval.setBit(i, bit)
}
//Adding version data [0111 = 7]
retval = retval.setBit(48, false)
retval = retval.setBit(49, true)
retval = retval.setBit(50, true)
retval = retval.setBit(51, true)
//Adding variant data [10]
retval = retval.setBit(64, true)
retval = retval.setBit(65, false)
return UUIDv7(retval)
}
//Parse finds the values for precisionSeconds, counter and node for a specified UUID on a specified generator.
func (u *UUIDv7Generator) Parse(id *UUIDv7) (precisionSeconds float64, counter uint64, node uint64) {
ps := make([]byte, 8)
c := make([]byte, 8)
n := make([]byte, 8)
var j = 0
for i := 35 + u.SubsecondPrecisionLength; i > 35; i-- {
// fmt.Println("Subsecond precision:", j, absoluteIndexer(i), getBit(id[:], absoluteIndexer(i)))
setBit(ps, 63-j, getBit(id[:], absoluteIndexer(i)))
j++
}
j = 0
for i := 35 + u.SubsecondPrecisionLength + u.CounterPrecisionLength; i > 35+u.SubsecondPrecisionLength; i-- {
// fmt.Println("Counter precision:", j, absoluteIndexer(i), getBit(id[:], absoluteIndexer(i)))
setBit(c, 63-j, getBit(id[:], absoluteIndexer(i)))
j++
}
j = 0
for i := 35 + u.SubsecondPrecisionLength + u.CounterPrecisionLength + u.NodePrecisionLength; i > 35+u.SubsecondPrecisionLength+u.CounterPrecisionLength; i-- {
// fmt.Println("Node precision:", j, absoluteIndexer(i), getBit(id[:], absoluteIndexer(i)))
setBit(n, 63-j, getBit(id[:], absoluteIndexer(i)))
j++
}
precision, _ := decodeDecimal(ps[:], u.SubsecondPrecisionLength)
return precision, toUint64(c[:]), toUint64(n[:])
}
func (u UUIDv7) ToString() string {
return uuidBase(u).ToString()
}
func (u UUIDv7) ToMicrosoftString() string {
return uuidBase(u).ToMicrosoftString()
}
func (u UUIDv7) ToBinaryString() string {
return uuidBase(u).ToBinaryString()
}
func (u UUIDv7) ToBitArray() []bool {
return uuidBase(u).ToBitArray()
}