-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathid.go
281 lines (238 loc) · 5.17 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
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
package c4
import (
"bytes"
"crypto/sha512"
"io"
"math/big"
"sort"
"strconv"
"strings"
"github.com/xtgo/set"
)
const (
charset = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
base = 58
)
var (
lut [256]byte
lowbyte = byte('1')
prefix = []byte{'c', '4'}
idlen = 90
// Id of empty string
nilID = Identify(bytes.NewReader([]byte{}))
// Id with all bytes set to 0.
voidID ID
// Id with all bytes set to 255.
maxID ID
)
type errBadChar int
func (e errBadChar) Error() string {
return "non c4 id character at position " + strconv.Itoa(int(e))
}
type errBadLength int
func (e errBadLength) Error() string {
return "c4 ids must be 90 characters long, input length " + strconv.Itoa(int(e))
}
type errNil struct{}
func (e errNil) Error() string {
return "unexpected nil id"
}
type errInvalidTree struct{}
func (e errInvalidTree) Error() string {
return "invalid tree data"
}
func init() {
for i := range lut {
lut[i] = 0xFF
}
for i, c := range charset {
lut[c] = byte(i)
}
for i := range maxID[:] {
maxID[i] = 0xFF
}
}
// Generate an id from an io.Reader
func Identify(src io.Reader) (id ID) {
h := sha512.New()
_, err := io.Copy(h, src)
if err != nil && err != io.EOF {
return id
}
copy(id[:], h.Sum(nil))
return id
}
/*
// Encoder generates an ID for a contiguous bock of data.
type Encoder struct {
err error
h hash.Hash
}
// NewIDEncoder makes a new Encoder.
func NewEncoder() *Encoder {
return &Encoder{
h: sha512.New(),
}
}
// Write writes bytes to the hash that makes up the ID.
func (e *Encoder) Write(b []byte) (int, error) {
return e.h.Write(b)
}
// ID returns the ID for the bytes written so far.
func (e *Encoder) ID() (id ID) {
copy(id[:], e.h.Sum(nil))
return id
}
// Reset the encoder so it can identify a new block of data.
func (e *Encoder) Reset() {
e.h.Reset()
}
*/
// ID represents a C4 ID.
type ID [64]byte
// Identifiable is an interface that requires an ID() method that returns
// the c4 ID of the of the object.
type Identifiable interface {
ID() ID
}
func (id ID) IsNil() bool {
for _, b := range id[:] {
if b != 0 {
return false
}
}
return true
}
// Parse parses a C4 ID string into an ID.
func Parse(source string) (ID, error) {
var id ID
if len(source) == 0 {
return voidID, errBadLength(len(source))
}
src := []byte(source)
if len(src) != 90 {
return id, errBadLength(len(src))
}
bigNum := new(big.Int)
bigBase := big.NewInt(base)
for i := 2; i < len(src); i++ {
b := lut[src[i]]
if b == 0xFF {
return id, errBadChar(i)
}
bigNum.Mul(bigNum, bigBase)
bigNum.Add(bigNum, big.NewInt(int64(b)))
}
data := bigNum.Bytes()
if len(data) > 64 {
data = data[:64]
}
shift := 64 - len(data)
for i := 0; i < shift; i++ {
data = append(data, 0)
}
if shift > 0 {
copy(data[shift:], data)
for i := 0; i < shift; i++ {
data[i] = 0
}
}
copy(id[:], data)
return id, nil
}
// Digest returns the C4 Digest of the ID.
func (id ID) Digest() []byte {
return id[:]
}
// Cmp compares two IDs.
// There are 3 possible return values.
//
// -1 : Argument id is less than calling id.
// 0 : Argument id and calling id are identical.
// +1 : Argument id is greater than calling id.
//
// Comparison is done on the actual numerical value of the ids.
// Not the string representation.
func (l ID) Cmp(r ID) int {
if r.IsNil() {
return -1
}
return bytes.Compare(l[:], r[:])
}
// String returns the standard string representation of a C4 id.
func (id ID) String() string {
var bigID, bigNum big.Int
bigID.SetBytes(id[:])
bigNum.Set(&bigID)
bigBase := big.NewInt(base)
bigZero := big.NewInt(0)
bigMod := new(big.Int)
encoded := make([]byte, 90)
for i := range encoded {
encoded[i] = lowbyte
}
encoded[0] = 'c'
encoded[1] = '4'
for i := 89; i > 1 && bigNum.Cmp(bigZero) > 0; i-- {
bigNum.DivMod(&bigNum, bigBase, bigMod)
encoded[i] = charset[bigMod.Int64()]
}
return string(encoded)
}
// Returns true if B less than A in: A.Less(B)
func (id ID) Less(idArg ID) bool {
return id.Cmp(idArg) < 0
}
func (l ID) Sum(r ID) ID {
switch bytes.Compare(l[:], r[:]) {
case -1:
// If the left side is larger then they are already in order, do nothing
case 1: // If the right side is larger swap them
l, r = r, l
case 0: // If they are identical return no sum needed, so just return one.
return l
}
h := sha512.New()
h.Write(l[:])
h.Write(r[:])
var id ID
copy(id[:], h.Sum(nil))
return id
}
func (id *ID) UnmarshalJSON(data []byte) error {
s := strings.Trim(string(data), `"' \t`)
if len(s) == 0 {
return nil
}
i, err := Parse(s)
if err != nil {
return err
}
copy(id[:], i[:])
return nil
}
func (id ID) MarshalJSON() ([]byte, error) {
if id.IsNil() {
return []byte(`""`), nil
}
return []byte(`"` + id.String() + `"`), nil
}
type IDs []ID
func (d IDs) Len() int { return len(d) }
func (d IDs) Less(i, j int) bool { return bytes.Compare(d[i][:], d[j][:]) < 0 }
func (d IDs) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
// Provides a computed C4 Tree for the slice of digests
func (d IDs) Tree() Tree {
if !sort.IsSorted(d) {
sort.Sort(d)
}
n := set.Uniq(d)
d = d[:n]
t := NewTree(d)
t.compute()
return t
}
func (d IDs) ID() ID {
t := d.Tree()
return t.ID()
}