-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathseed.go
177 lines (146 loc) · 5.29 KB
/
seed.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
package machina
import (
"encoding/base32"
"encoding/binary"
"net"
"github.com/gentlemanautomaton/machina/wwn"
"golang.org/x/crypto/sha3"
)
// Seed holds a seed state for generating various machina identities in
// a consistent and deterministic way.
type Seed struct {
info MachineInfo
}
// GroupID constructs a POSIX group identifier from a hash of the seed and
// components.
//
// The returned group ID will be between 65536 and 4294967295, placing it
// outside of the range of most standard tooling.
func (s Seed) GroupID(components ...[]byte) GroupID {
// Define an acceptable group ID range
const minGroupID = 65536
const maxRounds = 4096
// Build a hash from the seed and provided components
hash := s.shake128(components...)
// Squeeze data from the hash and interpret it as an integer. Repeat
// until we find a valid group ID in the range we're looking for.
var buffer [4]byte
for round := 0; round < maxRounds; round++ {
hash.Read(buffer[:])
gid := GroupID(binary.BigEndian.Uint32(buffer[:]))
if gid >= minGroupID {
return gid
}
}
// Panic if something has gone terribly wrong with our sha3 hash function
panic("failed to generate a group ID in the desired range")
}
// WWN constructs a 64-bit [World Wide Name] from a hash of the seed and
// components.
//
// The name returned will use Network Address Authority type 5 and IEEE OUI
// value 52:54:00, which identifies it as a locally administered address
// issued to a KVM virtual machine. This leaves 36 bits of unique value per
// name.
//
// For more details about locally administered addresses, see
// [RFC 5342 Section 2.1].
//
// [World Wide Name]: https://en.wikipedia.org/wiki/World_Wide_Name
// [RFC 5342 Section 2.1]: https://datatracker.ietf.org/doc/html/rfc5342#section-2.1
func (s Seed) WWN(components ...[]byte) wwn.Value {
// Build a hash from the seed and provided components
hash := s.shake128(components...)
// Copy the hashed bytes into a 64-bit WWN
var value wwn.Value
hash.Read(value[:8])
// Mark the WWN as NAA type 5 with OUI 52:54:00
value[0] = 0x55
value[1] = 0x25
value[2] = 0x40
value[3] = value[3] & 0x0f
return value
}
// SerialNumber constructs a 128-bit serial number from a hash of the seed and
// components. The value is returned as a string encoded with Base 32 Encoding
// with Extended Hex Alphabet.
//
// See [RFC 4648 Section 7] for more details about the encoding.
//
// [RFC 4648 Section 7]: https://datatracker.ietf.org/doc/html/rfc4648#section-7
func (s Seed) SerialNumber(components ...[]byte) string {
// Build a hash from the seed and provided components
hash := s.shake128(components...)
// Copy the hashed bytes into a buffer
var buffer [16]byte
hash.Read(buffer[:])
// Return the value in base32 with hex encoding and no padding
return base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(buffer[:])
}
// HardwareAddr constructs an IEEE 802 MAC-48/EUI-48 hardware address from a
// hash of the seed and components.
//
// The address returned will have the well-known prefix of 52:54:00, which
// identifies it as a locally administered address issued to a KVM virtual
// machine. This leaves 24 bits of unique value per address, which may not
// be enough to avoid collisions on a large network.
//
// For more details about locally administered addresses, see
// [RFC 5342 Section 2.1]. For an excellent treatment of MAC addresses in
// general, see the [MAC Address FAQ] from AllDataFeeds.
//
// [RFC 5342 Section 2.1]: https://datatracker.ietf.org/doc/html/rfc5342#section-2.1
// [MAC Address FAQ]: https://mac-address.alldatafeeds.com/faq
func (s Seed) HardwareAddr(components ...[]byte) net.HardwareAddr {
// Build a hash from the seed and provided components
hash := s.shake128(components...)
// Copy the hashed bytes into a 48-bit address
var address [6]byte
hash.Read(address[:])
// Apply the well-known prefix
address[0] = 0x52
address[1] = 0x54
address[2] = 0x00
return address[:]
}
// DeviceID constructs a device identifier from a hash of the seed and
// components.
func (s Seed) DeviceID(components ...[]byte) DeviceID {
return DeviceID(s.UUID(components...))
}
// UUID constructs a UUID from a hash of the seed and components.
func (s Seed) UUID(components ...[]byte) UUID {
// Build a hash from the seed and provided components
hash := s.shake128(components...)
// Copy the hashed bytes into a UUID
var uuid UUID
hash.Read(uuid[:])
// Set a few bits so that we construct a valid UUID
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid
}
// shake128 returns a sha3-128 shake hash from the seed and components.
func (s Seed) shake128(components ...[]byte) sha3.ShakeHash {
// Prepare a new shake instance
hash := sha3.NewShake128()
// Write the 128-bit machine ID
hash.Write(s.info.ID[:])
// Write the length of the machine name to avoid collisions
hash.Write(bigEndian(len(s.info.Name)))
// Write the machine name
hash.Write([]byte(s.info.Name))
// Write each of the components
for _, component := range components {
// Write the length of the component to avoid collisions
hash.Write(bigEndian(len(component)))
// Write the component itself
hash.Write(component)
}
return hash
}
func bigEndian(value int) []byte {
var b [8]byte
binary.BigEndian.PutUint64(b[:], uint64(value))
return b[:]
}