New upstream release.
Debian Janitor
2 years ago
0 | version: 1.0.0.{build} | |
1 | ||
2 | platform: x64 | |
3 | ||
4 | branches: | |
5 | only: | |
6 | - master | |
7 | ||
8 | clone_folder: c:\gopath\src\github.com\rs\xid | |
9 | ||
10 | environment: | |
11 | GOPATH: c:\gopath | |
12 | ||
13 | install: | |
14 | - echo %PATH% | |
15 | - echo %GOPATH% | |
16 | - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% | |
17 | - go version | |
18 | - go env | |
19 | - go get -t . | |
20 | ||
21 | build_script: | |
22 | - go build | |
23 | ||
24 | test_script: | |
25 | - go test | |
26 |
0 | 0 | language: go |
1 | 1 | go: |
2 | - 1.5 | |
3 | - tip | |
2 | - "1.9" | |
3 | - "1.10" | |
4 | - "master" | |
4 | 5 | matrix: |
5 | 6 | allow_failures: |
6 | - go: tip | |
7 | - go: "master" |
1 | 1 | |
2 | 2 | [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/xid) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/xid/master/LICENSE) [![Build Status](https://travis-ci.org/rs/xid.svg?branch=master)](https://travis-ci.org/rs/xid) [![Coverage](http://gocover.io/_badge/github.com/rs/xid)](http://gocover.io/github.com/rs/xid) |
3 | 3 | |
4 | Package xid is a globally unique id generator library, ready to be used safely directly in your server code. | |
4 | Package xid is a globally unique id generator library, ready to safely be used directly in your server code. | |
5 | 5 | |
6 | Xid is using Mongo Object ID algorithm to generate globally unique ids with a different serialization (bast64) to make it shorter when transported as a string: | |
6 | Xid uses the Mongo Object ID algorithm to generate globally unique ids with a different serialization (base64) to make it shorter when transported as a string: | |
7 | 7 | https://docs.mongodb.org/manual/reference/object-id/ |
8 | 8 | |
9 | 9 | - 4-byte value representing the seconds since the Unix epoch, |
32 | 32 | |-------------|-------------|----------------|---------------- |
33 | 33 | | [UUID] | 16 bytes | 36 chars | configuration free, not sortable |
34 | 34 | | [shortuuid] | 16 bytes | 22 chars | configuration free, not sortable |
35 | | [Snowflake] | 8 bytes | up to 20 chars | needs machin/DC configuration, needs central server, sortable | |
35 | | [Snowflake] | 8 bytes | up to 20 chars | needs machine/DC configuration, needs central server, sortable | |
36 | 36 | | [MongoID] | 12 bytes | 24 chars | configuration free, sortable |
37 | 37 | | xid | 12 bytes | 20 chars | configuration free, sortable |
38 | 38 | |
51 | 51 | - Unicity guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process |
52 | 52 | - Lock-free (i.e.: unlike UUIDv1 and v2) |
53 | 53 | |
54 | Best used with [xlog](https://github.com/rs/xlog)'s | |
55 | [RequestIDHandler](https://godoc.org/github.com/rs/xlog#RequestIDHandler). | |
54 | Best used with [zerolog](https://github.com/rs/zerolog)'s | |
55 | [RequestIDHandler](https://godoc.org/github.com/rs/zerolog/hlog#RequestIDHandler). | |
56 | ||
57 | Notes: | |
58 | ||
59 | - Xid is dependent on the system time, a monotonic counter and so is not cryptographically secure. If unpredictability of IDs is important, you should not use Xids. It is worth noting that most other UUID-like implementations are also not cryptographically secure. You should use libraries that rely on cryptographically secure sources (like /dev/urandom on unix, crypto/rand in golang), if you want a truly random ID generator. | |
56 | 60 | |
57 | 61 | References: |
58 | 62 | |
60 | 64 | - https://en.wikipedia.org/wiki/Universally_unique_identifier |
61 | 65 | - https://blog.twitter.com/2010/announcing-snowflake |
62 | 66 | - Python port by [Graham Abbott](https://github.com/graham): https://github.com/graham/python_xid |
67 | - Scala port by [Egor Kolotaev](https://github.com/kolotaev): https://github.com/kolotaev/ride | |
68 | - Rust port by [Jérôme Renard](https://github.com/jeromer/): https://github.com/jeromer/libxid | |
69 | - Ruby port by [Valar](https://github.com/valarpirai/): https://github.com/valarpirai/ruby_xid | |
70 | - Java port by [0xShamil](https://github.com/0xShamil/): https://github.com/0xShamil/java-xid | |
63 | 71 | |
64 | 72 | ## Install |
65 | 73 | |
99 | 107 | BenchmarkUUIDv4-4 1000000 1452 ns/op 64 B/op 2 allocs/op |
100 | 108 | ``` |
101 | 109 | |
102 | Note: UUIDv1 requires a global lock, hence the performence degrading as we add more CPUs. | |
110 | Note: UUIDv1 requires a global lock, hence the performance degradation as we add more CPUs. | |
103 | 111 | |
104 | 112 | ## Licenses |
105 | 113 |
0 | # Bytes storage | |
1 | ||
2 | This subpackage is there to allow storage of XIDs in a binary format in, for example, a database. | |
3 | It allows some data size optimisation as the 12 bytes will be smaller to store than a string. |
0 | package xidb | |
1 | ||
2 | import ( | |
3 | "database/sql/driver" | |
4 | "fmt" | |
5 | ||
6 | "github.com/rs/xid" | |
7 | ) | |
8 | ||
9 | type ID struct { | |
10 | xid.ID | |
11 | } | |
12 | ||
13 | // Value implements the driver.Valuer interface. | |
14 | func (id ID) Value() (driver.Value, error) { | |
15 | if id.ID.IsNil() { | |
16 | return nil, nil | |
17 | } | |
18 | return id.ID[:], nil | |
19 | } | |
20 | ||
21 | // Scan implements the sql.Scanner interface. | |
22 | func (id *ID) Scan(value interface{}) (err error) { | |
23 | switch val := value.(type) { | |
24 | case []byte: | |
25 | i, err := xid.FromBytes(val) | |
26 | if err != nil { | |
27 | return err | |
28 | } | |
29 | *id = ID{ID: i} | |
30 | return nil | |
31 | case nil: | |
32 | *id = ID{ID: xid.NilID()} | |
33 | return nil | |
34 | default: | |
35 | return fmt.Errorf("xid: scanning unsupported type: %T", value) | |
36 | } | |
37 | } |
0 | package xidb | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | ||
6 | "github.com/rs/xid" | |
7 | ) | |
8 | ||
9 | func TestIDValue(t *testing.T) { | |
10 | i, _ := xid.FromString("9m4e2mr0ui3e8a215n4g") | |
11 | ||
12 | tests := []struct { | |
13 | name string | |
14 | id ID | |
15 | expectedVal interface{} | |
16 | }{ | |
17 | { | |
18 | name: "non nil id", | |
19 | id: ID{ID: i}, | |
20 | expectedVal: i.Bytes(), | |
21 | }, | |
22 | { | |
23 | name: "nil id", | |
24 | id: ID{ID: xid.NilID()}, | |
25 | expectedVal: nil, | |
26 | }, | |
27 | } | |
28 | ||
29 | for _, tt := range tests { | |
30 | t.Run(tt.name, func(t *testing.T) { | |
31 | got, _ := tt.id.Value() | |
32 | if !reflect.DeepEqual(got, tt.expectedVal) { | |
33 | t.Errorf("wanted %v, got %v", tt.expectedVal, got) | |
34 | } | |
35 | }) | |
36 | } | |
37 | } | |
38 | ||
39 | func TestIDScan(t *testing.T) { | |
40 | i, _ := xid.FromString("9m4e2mr0ui3e8a215n4g") | |
41 | ||
42 | tests := []struct { | |
43 | name string | |
44 | val interface{} | |
45 | expectedID ID | |
46 | expectedErr bool | |
47 | }{ | |
48 | { | |
49 | name: "bytes id", | |
50 | val: i.Bytes(), | |
51 | expectedID: ID{ID: i}, | |
52 | }, | |
53 | { | |
54 | name: "nil id", | |
55 | val: nil, | |
56 | expectedID: ID{ID: xid.NilID()}, | |
57 | }, | |
58 | { | |
59 | name: "wrong bytes", | |
60 | val: []byte{0x01}, | |
61 | expectedErr: true, | |
62 | }, | |
63 | { | |
64 | name: "unknown type", | |
65 | val: 1, | |
66 | expectedErr: true, | |
67 | }, | |
68 | } | |
69 | ||
70 | for _, tt := range tests { | |
71 | t.Run(tt.name, func(t *testing.T) { | |
72 | id := &ID{} | |
73 | err := id.Scan(tt.val) | |
74 | if (err != nil) != tt.expectedErr { | |
75 | t.Errorf("error expected: %t, got %t", tt.expectedErr, (err != nil)) | |
76 | } | |
77 | if err == nil { | |
78 | if !reflect.DeepEqual(id.ID, tt.expectedID.ID) { | |
79 | t.Errorf("wanted %v, got %v", tt.expectedID, id) | |
80 | } | |
81 | } | |
82 | ||
83 | }) | |
84 | } | |
85 | } |
0 | golang-github-rs-xid (1.3.0-1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream release. | |
3 | ||
4 | -- Debian Janitor <janitor@jelmer.uk> Tue, 25 Jan 2022 21:58:36 -0000 | |
5 | ||
0 | 6 | golang-github-rs-xid (1.1-5) unstable; urgency=medium |
1 | 7 | |
2 | 8 | [ Michael Stapelberg ] |
0 | // +build darwin | |
1 | ||
2 | package xid | |
3 | ||
4 | import "syscall" | |
5 | ||
6 | func readPlatformMachineID() (string, error) { | |
7 | return syscall.Sysctl("kern.uuid") | |
8 | } |
0 | // +build !darwin,!linux,!freebsd,!windows | |
1 | ||
2 | package xid | |
3 | ||
4 | import "errors" | |
5 | ||
6 | func readPlatformMachineID() (string, error) { | |
7 | return "", errors.New("not implemented") | |
8 | } |
0 | // +build freebsd | |
1 | ||
2 | package xid | |
3 | ||
4 | import "syscall" | |
5 | ||
6 | func readPlatformMachineID() (string, error) { | |
7 | return syscall.Sysctl("kern.hostuuid") | |
8 | } |
0 | // +build linux | |
1 | ||
2 | package xid | |
3 | ||
4 | import "io/ioutil" | |
5 | ||
6 | func readPlatformMachineID() (string, error) { | |
7 | b, err := ioutil.ReadFile("/etc/machine-id") | |
8 | if err != nil || len(b) == 0 { | |
9 | b, err = ioutil.ReadFile("/sys/class/dmi/id/product_uuid") | |
10 | } | |
11 | return string(b), err | |
12 | } |
0 | // +build windows | |
1 | ||
2 | package xid | |
3 | ||
4 | import ( | |
5 | "fmt" | |
6 | "syscall" | |
7 | "unsafe" | |
8 | ) | |
9 | ||
10 | func readPlatformMachineID() (string, error) { | |
11 | // source: https://github.com/shirou/gopsutil/blob/master/host/host_syscall.go | |
12 | var h syscall.Handle | |
13 | err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, syscall.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, syscall.KEY_READ|syscall.KEY_WOW64_64KEY, &h) | |
14 | if err != nil { | |
15 | return "", err | |
16 | } | |
17 | defer syscall.RegCloseKey(h) | |
18 | ||
19 | const syscallRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16 | |
20 | const uuidLen = 36 | |
21 | ||
22 | var regBuf [syscallRegBufLen]uint16 | |
23 | bufLen := uint32(syscallRegBufLen) | |
24 | var valType uint32 | |
25 | err = syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) | |
26 | if err != nil { | |
27 | return "", err | |
28 | } | |
29 | ||
30 | hostID := syscall.UTF16ToString(regBuf[:]) | |
31 | hostIDLen := len(hostID) | |
32 | if hostIDLen != uuidLen { | |
33 | return "", fmt.Errorf("HostID incorrect: %q\n", hostID) | |
34 | } | |
35 | ||
36 | return hostID, nil | |
37 | } |
29 | 29 | // - Non configured, you don't need set a unique machine and/or data center id |
30 | 30 | // - K-ordered |
31 | 31 | // - Embedded time with 1 second precision |
32 | // - Unicity guaranted for 16,777,216 (24 bits) unique ids per second and per host/process | |
32 | // - Unicity guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process | |
33 | 33 | // |
34 | 34 | // Best used with xlog's RequestIDHandler (https://godoc.org/github.com/rs/xlog#RequestIDHandler). |
35 | 35 | // |
41 | 41 | package xid |
42 | 42 | |
43 | 43 | import ( |
44 | "bytes" | |
44 | 45 | "crypto/md5" |
45 | 46 | "crypto/rand" |
46 | 47 | "database/sql/driver" |
47 | 48 | "encoding/binary" |
48 | 49 | "errors" |
49 | 50 | "fmt" |
51 | "hash/crc32" | |
52 | "io/ioutil" | |
50 | 53 | "os" |
54 | "sort" | |
51 | 55 | "sync/atomic" |
52 | 56 | "time" |
57 | "unsafe" | |
53 | 58 | ) |
54 | 59 | |
55 | 60 | // Code inspired from mgo/bson ObjectId |
59 | 64 | |
60 | 65 | const ( |
61 | 66 | encodedLen = 20 // string encoded len |
62 | decodedLen = 15 // len after base32 decoding with the padded data | |
63 | 67 | rawLen = 12 // binary raw len |
64 | 68 | |
65 | 69 | // encoding stores a custom version of the base32 encoding with lower case |
67 | 71 | encoding = "0123456789abcdefghijklmnopqrstuv" |
68 | 72 | ) |
69 | 73 | |
70 | // ErrInvalidID is returned when trying to unmarshal an invalid ID | |
71 | var ErrInvalidID = errors.New("xid: invalid ID") | |
72 | ||
73 | // objectIDCounter is atomically incremented when generating a new ObjectId | |
74 | // using NewObjectId() function. It's used as a counter part of an id. | |
75 | // This id is initialized with a random value. | |
76 | var objectIDCounter = randInt() | |
77 | ||
78 | // machineId stores machine id generated once and used in subsequent calls | |
79 | // to NewObjectId function. | |
80 | var machineID = readMachineID() | |
81 | ||
82 | // pid stores the current process id | |
83 | var pid = os.Getpid() | |
84 | ||
85 | // dec is the decoding map for base32 encoding | |
86 | var dec [256]byte | |
74 | var ( | |
75 | // ErrInvalidID is returned when trying to unmarshal an invalid ID | |
76 | ErrInvalidID = errors.New("xid: invalid ID") | |
77 | ||
78 | // objectIDCounter is atomically incremented when generating a new ObjectId | |
79 | // using NewObjectId() function. It's used as a counter part of an id. | |
80 | // This id is initialized with a random value. | |
81 | objectIDCounter = randInt() | |
82 | ||
83 | // machineId stores machine id generated once and used in subsequent calls | |
84 | // to NewObjectId function. | |
85 | machineID = readMachineID() | |
86 | ||
87 | // pid stores the current process id | |
88 | pid = os.Getpid() | |
89 | ||
90 | nilID ID | |
91 | ||
92 | // dec is the decoding map for base32 encoding | |
93 | dec [256]byte | |
94 | ) | |
87 | 95 | |
88 | 96 | func init() { |
89 | 97 | for i := 0; i < len(dec); i++ { |
91 | 99 | } |
92 | 100 | for i := 0; i < len(encoding); i++ { |
93 | 101 | dec[encoding[i]] = byte(i) |
102 | } | |
103 | ||
104 | // If /proc/self/cpuset exists and is not /, we can assume that we are in a | |
105 | // form of container and use the content of cpuset xor-ed with the PID in | |
106 | // order get a reasonable machine global unique PID. | |
107 | b, err := ioutil.ReadFile("/proc/self/cpuset") | |
108 | if err == nil && len(b) > 1 { | |
109 | pid ^= int(crc32.ChecksumIEEE(b)) | |
94 | 110 | } |
95 | 111 | } |
96 | 112 | |
99 | 115 | // a runtime error. |
100 | 116 | func readMachineID() []byte { |
101 | 117 | id := make([]byte, 3) |
102 | if hostname, err := os.Hostname(); err == nil { | |
118 | hid, err := readPlatformMachineID() | |
119 | if err != nil || len(hid) == 0 { | |
120 | hid, err = os.Hostname() | |
121 | } | |
122 | if err == nil && len(hid) != 0 { | |
103 | 123 | hw := md5.New() |
104 | hw.Write([]byte(hostname)) | |
124 | hw.Write([]byte(hid)) | |
105 | 125 | copy(id, hw.Sum(nil)) |
106 | 126 | } else { |
107 | 127 | // Fallback to rand number if machine id can't be gathered |
121 | 141 | return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2]) |
122 | 142 | } |
123 | 143 | |
124 | // New generates a globaly unique ID | |
144 | // New generates a globally unique ID | |
125 | 145 | func New() ID { |
146 | return NewWithTime(time.Now()) | |
147 | } | |
148 | ||
149 | // NewWithTime generates a globally unique ID with the passed in time | |
150 | func NewWithTime(t time.Time) ID { | |
126 | 151 | var id ID |
127 | 152 | // Timestamp, 4 bytes, big endian |
128 | binary.BigEndian.PutUint32(id[:], uint32(time.Now().Unix())) | |
153 | binary.BigEndian.PutUint32(id[:], uint32(t.Unix())) | |
129 | 154 | // Machine, first 3 bytes of md5(hostname) |
130 | 155 | id[4] = machineID[0] |
131 | 156 | id[5] = machineID[1] |
152 | 177 | func (id ID) String() string { |
153 | 178 | text := make([]byte, encodedLen) |
154 | 179 | encode(text, id[:]) |
155 | return string(text) | |
180 | return *(*string)(unsafe.Pointer(&text)) | |
181 | } | |
182 | ||
183 | // Encode encodes the id using base32 encoding, writing 20 bytes to dst and return it. | |
184 | func (id ID) Encode(dst []byte) []byte { | |
185 | encode(dst, id[:]) | |
186 | return dst | |
156 | 187 | } |
157 | 188 | |
158 | 189 | // MarshalText implements encoding/text TextMarshaler interface |
162 | 193 | return text, nil |
163 | 194 | } |
164 | 195 | |
196 | // MarshalJSON implements encoding/json Marshaler interface | |
197 | func (id ID) MarshalJSON() ([]byte, error) { | |
198 | if id.IsNil() { | |
199 | return []byte("null"), nil | |
200 | } | |
201 | text := make([]byte, encodedLen+2) | |
202 | encode(text[1:encodedLen+1], id[:]) | |
203 | text[0], text[encodedLen+1] = '"', '"' | |
204 | return text, nil | |
205 | } | |
206 | ||
165 | 207 | // encode by unrolling the stdlib base32 algorithm + removing all safe checks |
166 | 208 | func encode(dst, id []byte) { |
209 | _ = dst[19] | |
210 | _ = id[11] | |
211 | ||
212 | dst[19] = encoding[(id[11]<<4)&0x1F] | |
213 | dst[18] = encoding[(id[11]>>1)&0x1F] | |
214 | dst[17] = encoding[(id[11]>>6)&0x1F|(id[10]<<2)&0x1F] | |
215 | dst[16] = encoding[id[10]>>3] | |
216 | dst[15] = encoding[id[9]&0x1F] | |
217 | dst[14] = encoding[(id[9]>>5)|(id[8]<<3)&0x1F] | |
218 | dst[13] = encoding[(id[8]>>2)&0x1F] | |
219 | dst[12] = encoding[id[8]>>7|(id[7]<<1)&0x1F] | |
220 | dst[11] = encoding[(id[7]>>4)&0x1F|(id[6]<<4)&0x1F] | |
221 | dst[10] = encoding[(id[6]>>1)&0x1F] | |
222 | dst[9] = encoding[(id[6]>>6)&0x1F|(id[5]<<2)&0x1F] | |
223 | dst[8] = encoding[id[5]>>3] | |
224 | dst[7] = encoding[id[4]&0x1F] | |
225 | dst[6] = encoding[id[4]>>5|(id[3]<<3)&0x1F] | |
226 | dst[5] = encoding[(id[3]>>2)&0x1F] | |
227 | dst[4] = encoding[id[3]>>7|(id[2]<<1)&0x1F] | |
228 | dst[3] = encoding[(id[2]>>4)&0x1F|(id[1]<<4)&0x1F] | |
229 | dst[2] = encoding[(id[1]>>1)&0x1F] | |
230 | dst[1] = encoding[(id[1]>>6)&0x1F|(id[0]<<2)&0x1F] | |
167 | 231 | dst[0] = encoding[id[0]>>3] |
168 | dst[1] = encoding[(id[1]>>6)&0x1F|(id[0]<<2)&0x1F] | |
169 | dst[2] = encoding[(id[1]>>1)&0x1F] | |
170 | dst[3] = encoding[(id[2]>>4)&0x1F|(id[1]<<4)&0x1F] | |
171 | dst[4] = encoding[id[3]>>7|(id[2]<<1)&0x1F] | |
172 | dst[5] = encoding[(id[3]>>2)&0x1F] | |
173 | dst[6] = encoding[id[4]>>5|(id[3]<<3)&0x1F] | |
174 | dst[7] = encoding[id[4]&0x1F] | |
175 | dst[8] = encoding[id[5]>>3] | |
176 | dst[9] = encoding[(id[6]>>6)&0x1F|(id[5]<<2)&0x1F] | |
177 | dst[10] = encoding[(id[6]>>1)&0x1F] | |
178 | dst[11] = encoding[(id[7]>>4)&0x1F|(id[6]<<4)&0x1F] | |
179 | dst[12] = encoding[id[8]>>7|(id[7]<<1)&0x1F] | |
180 | dst[13] = encoding[(id[8]>>2)&0x1F] | |
181 | dst[14] = encoding[(id[9]>>5)|(id[8]<<3)&0x1F] | |
182 | dst[15] = encoding[id[9]&0x1F] | |
183 | dst[16] = encoding[id[10]>>3] | |
184 | dst[17] = encoding[(id[11]>>6)&0x1F|(id[10]<<2)&0x1F] | |
185 | dst[18] = encoding[(id[11]>>1)&0x1F] | |
186 | dst[19] = encoding[(id[11]<<4)&0x1F] | |
187 | 232 | } |
188 | 233 | |
189 | 234 | // UnmarshalText implements encoding/text TextUnmarshaler interface |
200 | 245 | return nil |
201 | 246 | } |
202 | 247 | |
248 | // UnmarshalJSON implements encoding/json Unmarshaler interface | |
249 | func (id *ID) UnmarshalJSON(b []byte) error { | |
250 | s := string(b) | |
251 | if s == "null" { | |
252 | *id = nilID | |
253 | return nil | |
254 | } | |
255 | return id.UnmarshalText(b[1 : len(b)-1]) | |
256 | } | |
257 | ||
203 | 258 | // decode by unrolling the stdlib base32 algorithm + removing all safe checks |
204 | 259 | func decode(id *ID, src []byte) { |
260 | _ = src[19] | |
261 | _ = id[11] | |
262 | ||
263 | id[11] = dec[src[17]]<<6 | dec[src[18]]<<1 | dec[src[19]]>>4 | |
264 | id[10] = dec[src[16]]<<3 | dec[src[17]]>>2 | |
265 | id[9] = dec[src[14]]<<5 | dec[src[15]] | |
266 | id[8] = dec[src[12]]<<7 | dec[src[13]]<<2 | dec[src[14]]>>3 | |
267 | id[7] = dec[src[11]]<<4 | dec[src[12]]>>1 | |
268 | id[6] = dec[src[9]]<<6 | dec[src[10]]<<1 | dec[src[11]]>>4 | |
269 | id[5] = dec[src[8]]<<3 | dec[src[9]]>>2 | |
270 | id[4] = dec[src[6]]<<5 | dec[src[7]] | |
271 | id[3] = dec[src[4]]<<7 | dec[src[5]]<<2 | dec[src[6]]>>3 | |
272 | id[2] = dec[src[3]]<<4 | dec[src[4]]>>1 | |
273 | id[1] = dec[src[1]]<<6 | dec[src[2]]<<1 | dec[src[3]]>>4 | |
205 | 274 | id[0] = dec[src[0]]<<3 | dec[src[1]]>>2 |
206 | id[1] = dec[src[1]]<<6 | dec[src[2]]<<1 | dec[src[3]]>>4 | |
207 | id[2] = dec[src[3]]<<4 | dec[src[4]]>>1 | |
208 | id[3] = dec[src[4]]<<7 | dec[src[5]]<<2 | dec[src[6]]>>3 | |
209 | id[4] = dec[src[6]]<<5 | dec[src[7]] | |
210 | id[5] = dec[src[8]]<<3 | dec[src[9]]>>2 | |
211 | id[6] = dec[src[9]]<<6 | dec[src[10]]<<1 | dec[src[11]]>>4 | |
212 | id[7] = dec[src[11]]<<4 | dec[src[12]]>>1 | |
213 | id[8] = dec[src[12]]<<7 | dec[src[13]]<<2 | dec[src[14]]>>3 | |
214 | id[9] = dec[src[14]]<<5 | dec[src[15]] | |
215 | id[10] = dec[src[16]]<<3 | dec[src[17]]>>2 | |
216 | id[11] = dec[src[17]]<<6 | dec[src[18]]<<1 | dec[src[19]]>>4 | |
217 | 275 | } |
218 | 276 | |
219 | 277 | // Time returns the timestamp part of the id. |
246 | 304 | |
247 | 305 | // Value implements the driver.Valuer interface. |
248 | 306 | func (id ID) Value() (driver.Value, error) { |
307 | if id.IsNil() { | |
308 | return nil, nil | |
309 | } | |
249 | 310 | b, err := id.MarshalText() |
250 | 311 | return string(b), err |
251 | 312 | } |
257 | 318 | return id.UnmarshalText([]byte(val)) |
258 | 319 | case []byte: |
259 | 320 | return id.UnmarshalText(val) |
321 | case nil: | |
322 | *id = nilID | |
323 | return nil | |
260 | 324 | default: |
261 | 325 | return fmt.Errorf("xid: scanning unsupported type: %T", value) |
262 | 326 | } |
263 | 327 | } |
328 | ||
329 | // IsNil Returns true if this is a "nil" ID | |
330 | func (id ID) IsNil() bool { | |
331 | return id == nilID | |
332 | } | |
333 | ||
334 | // NilID returns a zero value for `xid.ID`. | |
335 | func NilID() ID { | |
336 | return nilID | |
337 | } | |
338 | ||
339 | // Bytes returns the byte array representation of `ID` | |
340 | func (id ID) Bytes() []byte { | |
341 | return id[:] | |
342 | } | |
343 | ||
344 | // FromBytes convert the byte array representation of `ID` back to `ID` | |
345 | func FromBytes(b []byte) (ID, error) { | |
346 | var id ID | |
347 | if len(b) != rawLen { | |
348 | return id, ErrInvalidID | |
349 | } | |
350 | copy(id[:], b) | |
351 | return id, nil | |
352 | } | |
353 | ||
354 | // Compare returns an integer comparing two IDs. It behaves just like `bytes.Compare`. | |
355 | // The result will be 0 if two IDs are identical, -1 if current id is less than the other one, | |
356 | // and 1 if current id is greater than the other. | |
357 | func (id ID) Compare(other ID) int { | |
358 | return bytes.Compare(id[:], other[:]) | |
359 | } | |
360 | ||
361 | type sorter []ID | |
362 | ||
363 | func (s sorter) Len() int { | |
364 | return len(s) | |
365 | } | |
366 | ||
367 | func (s sorter) Less(i, j int) bool { | |
368 | return s[i].Compare(s[j]) < 0 | |
369 | } | |
370 | ||
371 | func (s sorter) Swap(i, j int) { | |
372 | s[i], s[j] = s[j], s[i] | |
373 | } | |
374 | ||
375 | // Sort sorts an array of IDs inplace. | |
376 | // It works by wrapping `[]ID` and use `sort.Sort`. | |
377 | func Sort(ids []ID) { | |
378 | sort.Sort(sorter(ids)) | |
379 | } |
0 | 0 | package xid |
1 | 1 | |
2 | 2 | import ( |
3 | "bytes" | |
3 | 4 | "encoding/json" |
5 | "errors" | |
6 | "fmt" | |
7 | "reflect" | |
4 | 8 | "testing" |
5 | 9 | "time" |
6 | ||
7 | "github.com/stretchr/testify/assert" | |
8 | 10 | ) |
9 | ||
10 | const strInvalidID = "xid: invalid ID" | |
11 | 11 | |
12 | 12 | type IDParts struct { |
13 | 13 | id ID |
43 | 43 | |
44 | 44 | func TestIDPartsExtraction(t *testing.T) { |
45 | 45 | for i, v := range IDs { |
46 | assert.Equal(t, v.id.Time(), time.Unix(v.timestamp, 0), "#%d timestamp", i) | |
47 | assert.Equal(t, v.id.Machine(), v.machine, "#%d machine", i) | |
48 | assert.Equal(t, v.id.Pid(), v.pid, "#%d pid", i) | |
49 | assert.Equal(t, v.id.Counter(), v.counter, "#%d counter", i) | |
46 | t.Run(fmt.Sprintf("Test%d", i), func(t *testing.T) { | |
47 | if got, want := v.id.Time(), time.Unix(v.timestamp, 0); got != want { | |
48 | t.Errorf("Time() = %v, want %v", got, want) | |
49 | } | |
50 | if got, want := v.id.Machine(), v.machine; !bytes.Equal(got, want) { | |
51 | t.Errorf("Machine() = %v, want %v", got, want) | |
52 | } | |
53 | if got, want := v.id.Pid(), v.pid; got != want { | |
54 | t.Errorf("Pid() = %v, want %v", got, want) | |
55 | } | |
56 | if got, want := v.id.Counter(), v.counter; got != want { | |
57 | t.Errorf("Counter() = %v, want %v", got, want) | |
58 | } | |
59 | }) | |
50 | 60 | } |
51 | 61 | } |
52 | 62 | |
62 | 72 | // Test for uniqueness among all other 9 generated ids |
63 | 73 | for j, tid := range ids { |
64 | 74 | if j != i { |
65 | assert.NotEqual(t, id, tid, "Generated ID is not unique") | |
75 | if id.Compare(tid) == 0 { | |
76 | t.Errorf("generated ID is not unique (%d/%d)", i, j) | |
77 | } | |
66 | 78 | } |
67 | 79 | } |
68 | 80 | // Check that timestamp was incremented and is within 30 seconds of the previous one |
69 | 81 | secs := id.Time().Sub(prevID.Time()).Seconds() |
70 | assert.Equal(t, (secs >= 0 && secs <= 30), true, "Wrong timestamp in generated ID") | |
82 | if secs < 0 || secs > 30 { | |
83 | t.Error("wrong timestamp in generated ID") | |
84 | } | |
71 | 85 | // Check that machine ids are the same |
72 | assert.Equal(t, id.Machine(), prevID.Machine()) | |
86 | if !bytes.Equal(id.Machine(), prevID.Machine()) { | |
87 | t.Error("machine ID not equal") | |
88 | } | |
73 | 89 | // Check that pids are the same |
74 | assert.Equal(t, id.Pid(), prevID.Pid()) | |
90 | if id.Pid() != prevID.Pid() { | |
91 | t.Error("pid not equal") | |
92 | } | |
75 | 93 | // Test for proper increment |
76 | delta := int(id.Counter() - prevID.Counter()) | |
77 | assert.Equal(t, delta, 1, "Wrong increment in generated ID") | |
94 | if got, want := int(id.Counter()-prevID.Counter()), 1; got != want { | |
95 | t.Errorf("wrong increment in generated ID, delta=%v, want %v", got, want) | |
96 | } | |
78 | 97 | } |
79 | 98 | } |
80 | 99 | |
81 | 100 | func TestIDString(t *testing.T) { |
82 | 101 | id := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} |
83 | assert.Equal(t, "9m4e2mr0ui3e8a215n4g", id.String()) | |
102 | if got, want := id.String(), "9m4e2mr0ui3e8a215n4g"; got != want { | |
103 | t.Errorf("String() = %v, want %v", got, want) | |
104 | } | |
105 | } | |
106 | ||
107 | func TestIDEncode(t *testing.T) { | |
108 | id := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} | |
109 | text := make([]byte, encodedLen) | |
110 | if got, want := string(id.Encode(text)), "9m4e2mr0ui3e8a215n4g"; got != want { | |
111 | t.Errorf("Encode() = %v, want %v", got, want) | |
112 | } | |
84 | 113 | } |
85 | 114 | |
86 | 115 | func TestFromString(t *testing.T) { |
87 | id, err := FromString("9m4e2mr0ui3e8a215n4g") | |
88 | assert.NoError(t, err) | |
89 | assert.Equal(t, ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}, id) | |
116 | got, err := FromString("9m4e2mr0ui3e8a215n4g") | |
117 | if err != nil { | |
118 | t.Fatal(err) | |
119 | } | |
120 | want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} | |
121 | if got != want { | |
122 | t.Errorf("FromString() = %v, want %v", got, want) | |
123 | } | |
90 | 124 | } |
91 | 125 | |
92 | 126 | func TestFromStringInvalid(t *testing.T) { |
93 | id, err := FromString("invalid") | |
94 | assert.EqualError(t, err, strInvalidID) | |
95 | assert.Equal(t, ID{}, id) | |
127 | _, err := FromString("invalid") | |
128 | if err != ErrInvalidID { | |
129 | t.Errorf("FromString(invalid) err=%v, want %v", err, ErrInvalidID) | |
130 | } | |
96 | 131 | } |
97 | 132 | |
98 | 133 | type jsonType struct { |
104 | 139 | id := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} |
105 | 140 | v := jsonType{ID: &id, Str: "test"} |
106 | 141 | data, err := json.Marshal(&v) |
107 | assert.NoError(t, err) | |
108 | assert.Equal(t, `{"ID":"9m4e2mr0ui3e8a215n4g","Str":"test"}`, string(data)) | |
142 | if err != nil { | |
143 | t.Fatal(err) | |
144 | } | |
145 | if got, want := string(data), `{"ID":"9m4e2mr0ui3e8a215n4g","Str":"test"}`; got != want { | |
146 | t.Errorf("json.Marshal() = %v, want %v", got, want) | |
147 | } | |
109 | 148 | } |
110 | 149 | |
111 | 150 | func TestIDJSONUnmarshaling(t *testing.T) { |
112 | 151 | data := []byte(`{"ID":"9m4e2mr0ui3e8a215n4g","Str":"test"}`) |
113 | 152 | v := jsonType{} |
114 | 153 | err := json.Unmarshal(data, &v) |
115 | assert.NoError(t, err) | |
116 | assert.Equal(t, ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}, *v.ID) | |
154 | if err != nil { | |
155 | t.Fatal(err) | |
156 | } | |
157 | want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} | |
158 | if got := *v.ID; got.Compare(want) != 0 { | |
159 | t.Errorf("json.Unmarshal() = %v, want %v", got, want) | |
160 | } | |
161 | ||
117 | 162 | } |
118 | 163 | |
119 | 164 | func TestIDJSONUnmarshalingError(t *testing.T) { |
120 | 165 | v := jsonType{} |
121 | 166 | err := json.Unmarshal([]byte(`{"ID":"9M4E2MR0UI3E8A215N4G"}`), &v) |
122 | assert.EqualError(t, err, strInvalidID) | |
167 | if err != ErrInvalidID { | |
168 | t.Errorf("json.Unmarshal() err=%v, want %v", err, ErrInvalidID) | |
169 | } | |
123 | 170 | err = json.Unmarshal([]byte(`{"ID":"TYjhW2D0huQoQS"}`), &v) |
124 | assert.EqualError(t, err, strInvalidID) | |
171 | if err != ErrInvalidID { | |
172 | t.Errorf("json.Unmarshal() err=%v, want %v", err, ErrInvalidID) | |
173 | } | |
125 | 174 | err = json.Unmarshal([]byte(`{"ID":"TYjhW2D0huQoQS3kdk"}`), &v) |
126 | assert.EqualError(t, err, strInvalidID) | |
175 | if err != ErrInvalidID { | |
176 | t.Errorf("json.Unmarshal() err=%v, want %v", err, ErrInvalidID) | |
177 | } | |
127 | 178 | } |
128 | 179 | |
129 | 180 | func TestIDDriverValue(t *testing.T) { |
130 | 181 | id := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} |
131 | data, err := id.Value() | |
132 | assert.NoError(t, err) | |
133 | assert.Equal(t, "9m4e2mr0ui3e8a215n4g", data) | |
182 | got, err := id.Value() | |
183 | if err != nil { | |
184 | t.Fatal(err) | |
185 | } | |
186 | if want := "9m4e2mr0ui3e8a215n4g"; got != want { | |
187 | t.Errorf("Value() = %v, want %v", got, want) | |
188 | } | |
134 | 189 | } |
135 | 190 | |
136 | 191 | func TestIDDriverScan(t *testing.T) { |
137 | id := ID{} | |
138 | err := id.Scan("9m4e2mr0ui3e8a215n4g") | |
139 | assert.NoError(t, err) | |
140 | assert.Equal(t, ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}, id) | |
192 | got := ID{} | |
193 | err := got.Scan("9m4e2mr0ui3e8a215n4g") | |
194 | if err != nil { | |
195 | t.Fatal(err) | |
196 | } | |
197 | want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} | |
198 | if got.Compare(want) != 0 { | |
199 | t.Errorf("Scan() = %v, want %v", got, want) | |
200 | } | |
141 | 201 | } |
142 | 202 | |
143 | 203 | func TestIDDriverScanError(t *testing.T) { |
144 | 204 | id := ID{} |
145 | err := id.Scan(0) | |
146 | assert.EqualError(t, err, "xid: scanning unsupported type: int") | |
147 | err = id.Scan("0") | |
148 | assert.EqualError(t, err, strInvalidID) | |
205 | if got, want := id.Scan(0), errors.New("xid: scanning unsupported type: int"); !reflect.DeepEqual(got, want) { | |
206 | t.Errorf("Scan() err=%v, want %v", got, want) | |
207 | } | |
208 | if got, want := id.Scan("0"), ErrInvalidID; got != want { | |
209 | t.Errorf("Scan() err=%v, want %v", got, want) | |
210 | } | |
149 | 211 | } |
150 | 212 | |
151 | 213 | func TestIDDriverScanByteFromDatabase(t *testing.T) { |
152 | id := ID{} | |
214 | got := ID{} | |
153 | 215 | bs := []byte("9m4e2mr0ui3e8a215n4g") |
154 | err := id.Scan(bs) | |
155 | assert.NoError(t, err) | |
156 | assert.Equal(t, ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}, id) | |
216 | err := got.Scan(bs) | |
217 | if err != nil { | |
218 | t.Fatal(err) | |
219 | } | |
220 | want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} | |
221 | if got.Compare(want) != 0 { | |
222 | t.Errorf("Scan() = %v, want %v", got, want) | |
223 | } | |
157 | 224 | } |
158 | 225 | |
159 | 226 | func BenchmarkNew(b *testing.B) { |
195 | 262 | // } |
196 | 263 | // }) |
197 | 264 | // } |
265 | ||
266 | func TestID_IsNil(t *testing.T) { | |
267 | tests := []struct { | |
268 | name string | |
269 | id ID | |
270 | want bool | |
271 | }{ | |
272 | { | |
273 | name: "ID not nil", | |
274 | id: New(), | |
275 | want: false, | |
276 | }, | |
277 | { | |
278 | name: "Nil ID", | |
279 | id: ID{}, | |
280 | want: true, | |
281 | }, | |
282 | } | |
283 | for _, tt := range tests { | |
284 | tt := tt | |
285 | t.Run(tt.name, func(t *testing.T) { | |
286 | if got, want := tt.id.IsNil(), tt.want; got != want { | |
287 | t.Errorf("IsNil() = %v, want %v", got, want) | |
288 | } | |
289 | }) | |
290 | } | |
291 | } | |
292 | ||
293 | func TestNilID(t *testing.T) { | |
294 | got := ID{} | |
295 | if want := NilID(); !reflect.DeepEqual(got, want) { | |
296 | t.Error("NilID() not equal ID{}") | |
297 | } | |
298 | } | |
299 | ||
300 | func TestNilID_IsNil(t *testing.T) { | |
301 | if !NilID().IsNil() { | |
302 | t.Error("NilID().IsNil() is not true") | |
303 | } | |
304 | } | |
305 | ||
306 | func TestFromBytes_Invariant(t *testing.T) { | |
307 | want := New() | |
308 | got, err := FromBytes(want.Bytes()) | |
309 | if err != nil { | |
310 | t.Fatal(err) | |
311 | } | |
312 | if got.Compare(want) != 0 { | |
313 | t.Error("FromBytes(id.Bytes()) != id") | |
314 | } | |
315 | } | |
316 | ||
317 | func TestFromBytes_InvalidBytes(t *testing.T) { | |
318 | cases := []struct { | |
319 | length int | |
320 | shouldFail bool | |
321 | }{ | |
322 | {11, true}, | |
323 | {12, false}, | |
324 | {13, true}, | |
325 | } | |
326 | for _, c := range cases { | |
327 | b := make([]byte, c.length, c.length) | |
328 | _, err := FromBytes(b) | |
329 | if got, want := err != nil, c.shouldFail; got != want { | |
330 | t.Errorf("FromBytes() error got %v, want %v", got, want) | |
331 | } | |
332 | } | |
333 | } | |
334 | ||
335 | func TestID_Compare(t *testing.T) { | |
336 | pairs := []struct { | |
337 | left ID | |
338 | right ID | |
339 | expected int | |
340 | }{ | |
341 | {IDs[1].id, IDs[0].id, -1}, | |
342 | {ID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, IDs[2].id, -1}, | |
343 | {IDs[0].id, IDs[0].id, 0}, | |
344 | } | |
345 | for _, p := range pairs { | |
346 | if p.expected != p.left.Compare(p.right) { | |
347 | t.Errorf("%s Compare to %s should return %d", p.left, p.right, p.expected) | |
348 | } | |
349 | if -1*p.expected != p.right.Compare(p.left) { | |
350 | t.Errorf("%s Compare to %s should return %d", p.right, p.left, -1*p.expected) | |
351 | } | |
352 | } | |
353 | } | |
354 | ||
355 | var IDList = []ID{IDs[0].id, IDs[1].id, IDs[2].id} | |
356 | ||
357 | func TestSorter_Len(t *testing.T) { | |
358 | if got, want := sorter([]ID{}).Len(), 0; got != want { | |
359 | t.Errorf("Len() %v, want %v", got, want) | |
360 | } | |
361 | if got, want := sorter(IDList).Len(), 3; got != want { | |
362 | t.Errorf("Len() %v, want %v", got, want) | |
363 | } | |
364 | } | |
365 | ||
366 | func TestSorter_Less(t *testing.T) { | |
367 | sorter := sorter(IDList) | |
368 | if !sorter.Less(1, 0) { | |
369 | t.Errorf("Less(1, 0) not true") | |
370 | } | |
371 | if sorter.Less(2, 1) { | |
372 | t.Errorf("Less(2, 1) true") | |
373 | } | |
374 | if sorter.Less(0, 0) { | |
375 | t.Errorf("Less(0, 0) true") | |
376 | } | |
377 | } | |
378 | ||
379 | func TestSorter_Swap(t *testing.T) { | |
380 | ids := make([]ID, 0) | |
381 | ids = append(ids, IDList...) | |
382 | sorter := sorter(ids) | |
383 | sorter.Swap(0, 1) | |
384 | if got, want := ids[0], IDList[1]; !reflect.DeepEqual(got, want) { | |
385 | t.Error("ids[0] != IDList[1]") | |
386 | } | |
387 | if got, want := ids[1], IDList[0]; !reflect.DeepEqual(got, want) { | |
388 | t.Error("ids[1] != IDList[0]") | |
389 | } | |
390 | sorter.Swap(2, 2) | |
391 | if got, want := ids[2], IDList[2]; !reflect.DeepEqual(got, want) { | |
392 | t.Error("ids[2], IDList[2]") | |
393 | } | |
394 | } | |
395 | ||
396 | func TestSort(t *testing.T) { | |
397 | ids := make([]ID, 0) | |
398 | ids = append(ids, IDList...) | |
399 | Sort(ids) | |
400 | if got, want := ids, []ID{IDList[1], IDList[2], IDList[0]}; !reflect.DeepEqual(got, want) { | |
401 | t.Fail() | |
402 | } | |
403 | } |