-
Notifications
You must be signed in to change notification settings - Fork 1
/
identifier.go
135 lines (120 loc) · 3.57 KB
/
identifier.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
package incr
import (
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"strings"
"sync"
)
// Identifier is a unique id.
//
// Create a new identifier with [NewIdentifier].
type Identifier [16]byte
// NewIdentifier returns a new identifier.
//
// Currently the underlying data looks like a
// uuidv4 but that shouldn't be relied upon.
//
// By default [NewIdentifier] uses [crypto/rand] to generate
// the random data for the identifier in a rotating buffer, which
// yields decent performance and uniqueness guarantees.
//
// If performance is still bottlenecked on creating identifiers for nodes
// you can swap out the algorithm for generating ids with [SetIdentifierProvider].
func NewIdentifier() (output Identifier) {
output = identifierProvider()
return
}
// MustParseIdentifier is the reverse of `.String()` that will
// panic if an error is returned by `ParseIdentifier`.
func MustParseIdentifier(raw string) (output Identifier) {
var err error
output, err = ParseIdentifier(raw)
if err != nil {
panic(err)
}
return
}
// ParseIdentifier is the reverse of `.String()`.
func ParseIdentifier(raw string) (output Identifier, err error) {
if raw == "" {
return
}
var parsed []byte
parsed, err = hex.DecodeString(raw)
if err != nil {
return
}
if len(parsed) != 16 {
err = fmt.Errorf("invalid identifier; must be 16 bytes")
return
}
copy(output[:], parsed)
return
}
// SetIdentifierProvider sets the identifier provider
// to a custom provider separate from the default.
//
// This is especially useful in performance critical use cases where
// the identifier doesn't need to be securely random, that is there are
// looser constraints on the randomness of the identifier because
// there will be few nodes over the lifetime of the program or graph.
func SetIdentifierProvider(ip func() Identifier) {
identifierProvider = ip
}
func cryptoRandIdentifierProvider() (output Identifier) {
identifierRandPoolMu.Lock()
if identifierRandPoolPos == randPoolSize {
_, _ = io.ReadFull(randomSource, identifierRandPool[:])
identifierRandPoolPos = 0
}
copy(output[:], identifierRandPool[identifierRandPoolPos:(identifierRandPoolPos+16)])
identifierRandPoolPos += 16
identifierRandPoolMu.Unlock()
output[6] = (output[6] & 0x0f) | 0x40 // Version 4
output[8] = (output[8] & 0x3f) | 0x80 // Variant is 10
return
}
const randPoolSize = 16 * 16
var (
identifierProvider = cryptoRandIdentifierProvider
identifierRandPoolMu sync.Mutex
identifierRandPoolPos = randPoolSize // protected with poolMu
identifierRandPool [randPoolSize]byte // protected with poolMu
randomSource = rand.Reader // random function
)
var zero Identifier
// IsZero returns if the identifier is unset.
func (id Identifier) IsZero() bool {
return id == zero
}
// MarshalJSON implements json.Marshaler.
func (id Identifier) MarshalJSON() ([]byte, error) {
return []byte("\"" + id.String() + "\""), nil
}
// UnmarshalJSON implements json.Unmarshaler.
func (id *Identifier) UnmarshalJSON(data []byte) error {
dataCleaned := strings.TrimPrefix(string(data), "\"")
dataCleaned = strings.TrimSuffix(dataCleaned, "\"")
parsed, err := ParseIdentifier(dataCleaned)
if err != nil {
return err
}
*id = parsed
return nil
}
// String returns the full hex representation of the id.
func (id Identifier) String() string {
var buf [32]byte
hex.Encode(buf[:], id[:])
return string(buf[:])
}
// Short returns the short hex representation of the id.
//
// In practice this is the last ~8 bytes of the identifier.
func (id Identifier) Short() string {
var buf [8]byte
hex.Encode(buf[:], id[12:])
return string(buf[:])
}