forked from sourcegraph/appdash
-
Notifications
You must be signed in to change notification settings - Fork 0
/
id.go
122 lines (110 loc) · 2.44 KB
/
id.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
package appdash
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"strconv"
"sync"
"unsafe"
)
// An ID is a unique, uniformly distributed 64-bit ID.
type ID uint64
// String returns the ID as a hex string.
func (id ID) String() string {
return fmt.Sprintf("%016x", uint64(id))
}
// MarshalJSON encodes the ID as a hex string.
func (id ID) MarshalJSON() ([]byte, error) {
return json.Marshal(id.String())
}
// UnmarshalJSON decodes the given data as either a hexadecimal string or JSON
// integer.
func (id *ID) UnmarshalJSON(data []byte) error {
i, err := parseJSONString(data)
if err == nil {
*id = i
return nil
}
i, err = parseJSONInt(data)
if err == nil {
*id = i
return nil
}
return fmt.Errorf("%s is not a valid ID", data)
}
// ParseID parses the given string as a hexadecimal string.
func ParseID(s string) (ID, error) {
i, err := strconv.ParseUint(s, 16, 64)
if err != nil {
return 0, err
}
return ID(i), nil
}
// generateID returns a randomly-generated 64-bit ID. This function is
// thread-safe. IDs are produced by consuming an AES-CTR-128 keystream in
// 64-bit chunks. The AES key is randomly generated on initialization, as is the
// counter's initial state. On machines with AES-NI support, ID generation takes
// ~30ns and generates no garbage.
func generateID() ID {
m.Lock()
if n == aes.BlockSize {
c.Encrypt(b, ctr)
for i := aes.BlockSize - 1; i >= 0; i-- { // increment ctr
ctr[i]++
if ctr[i] != 0 {
break
}
}
n = 0
}
id := *(*ID)(unsafe.Pointer(&b[n])) // zero-copy b/c we're arch-neutral
n += idSize
m.Unlock()
return id
}
const (
idSize = aes.BlockSize / 2 // 64 bits
keySize = aes.BlockSize // 128 bits
)
var (
ctr []byte
n int
b []byte
c cipher.Block
m sync.Mutex
)
func init() {
buf := make([]byte, keySize+aes.BlockSize)
_, err := io.ReadFull(rand.Reader, buf)
if err != nil {
panic(err) // /dev/urandom had better work
}
c, err = aes.NewCipher(buf[:keySize])
if err != nil {
panic(err) // AES had better work
}
n = aes.BlockSize
ctr = buf[keySize:]
b = make([]byte, aes.BlockSize)
}
func parseJSONString(data []byte) (ID, error) {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return 0, err
}
i, err := ParseID(s)
if err != nil {
return 0, err
}
return i, nil
}
func parseJSONInt(data []byte) (ID, error) {
var i uint64
if err := json.Unmarshal(data, &i); err != nil {
return 0, err
}
return ID(i), nil
}