pax_global_header00006660000000000000000000000064144154257450014525gustar00rootroot0000000000000052 comment=47a0ac1e0b750ee1f43718be223bb07601c66a1f xid-1.5.0/000077500000000000000000000000001441542574500123145ustar00rootroot00000000000000xid-1.5.0/.appveyor.yml000066400000000000000000000005171441542574500147650ustar00rootroot00000000000000version: 1.0.0.{build} platform: x64 branches: only: - master clone_folder: c:\gopath\src\github.com\rs\xid environment: GOPATH: c:\gopath install: - echo %PATH% - echo %GOPATH% - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - go version - go env - go get -t . build_script: - go build test_script: - go test xid-1.5.0/.github/000077500000000000000000000000001441542574500136545ustar00rootroot00000000000000xid-1.5.0/.github/workflows/000077500000000000000000000000001441542574500157115ustar00rootroot00000000000000xid-1.5.0/.github/workflows/go.yml000066400000000000000000000012151441542574500170400ustar00rootroot00000000000000name: Go on: push: branches: - master pull_request: branches: - master jobs: test: name: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: go-version: "1.18" check-latest: true - run: go test -race -failfast ./... golangci-lint: name: golangci-lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: go-version: "1.18" check-latest: true - uses: golangci/golangci-lint-action@v3 with: version: latest xid-1.5.0/.golangci.yml000066400000000000000000000000621441542574500146760ustar00rootroot00000000000000run: tests: false output: sort-results: true xid-1.5.0/.travis.yml000066400000000000000000000001341441542574500144230ustar00rootroot00000000000000language: go go: - "1.9" - "1.10" - "master" matrix: allow_failures: - go: "master" xid-1.5.0/LICENSE000066400000000000000000000020701441542574500133200ustar00rootroot00000000000000Copyright (c) 2015 Olivier Poitrey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. xid-1.5.0/README.md000066400000000000000000000142461441542574500136020ustar00rootroot00000000000000# Globally Unique ID Generator [![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) Package xid is a globally unique id generator library, ready to safely be used directly in your server code. 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: https://docs.mongodb.org/manual/reference/object-id/ - 4-byte value representing the seconds since the Unix epoch, - 3-byte machine identifier, - 2-byte process id, and - 3-byte counter, starting with a random value. The binary representation of the id is compatible with Mongo 12 bytes Object IDs. The string representation is using base32 hex (w/o padding) for better space efficiency when stored in that form (20 bytes). The hex variant of base32 is used to retain the sortable property of the id. Xid doesn't use base64 because case sensitivity and the 2 non alphanum chars may be an issue when transported as a string between various systems. Base36 wasn't retained either because 1/ it's not standard 2/ the resulting size is not predictable (not bit aligned) and 3/ it would not remain sortable. To validate a base32 `xid`, expect a 20 chars long, all lowercase sequence of `a` to `v` letters and `0` to `9` numbers (`[0-9a-v]{20}`). UUIDs are 16 bytes (128 bits) and 36 chars as string representation. Twitter Snowflake ids are 8 bytes (64 bits) but require machine/data-center configuration and/or central generator servers. xid stands in between with 12 bytes (96 bits) and a more compact URL-safe string representation (20 chars). No configuration or central generator server is required so it can be used directly in server's code. | Name | Binary Size | String Size | Features |-------------|-------------|----------------|---------------- | [UUID] | 16 bytes | 36 chars | configuration free, not sortable | [shortuuid] | 16 bytes | 22 chars | configuration free, not sortable | [Snowflake] | 8 bytes | up to 20 chars | needs machine/DC configuration, needs central server, sortable | [MongoID] | 12 bytes | 24 chars | configuration free, sortable | xid | 12 bytes | 20 chars | configuration free, sortable [UUID]: https://en.wikipedia.org/wiki/Universally_unique_identifier [shortuuid]: https://github.com/stochastic-technologies/shortuuid [Snowflake]: https://blog.twitter.com/2010/announcing-snowflake [MongoID]: https://docs.mongodb.org/manual/reference/object-id/ Features: - Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake - Base32 hex encoded by default (20 chars when transported as printable string, still sortable) - Non configured, you don't need set a unique machine and/or data center id - K-ordered - Embedded time with 1 second precision - Unicity guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process - Lock-free (i.e.: unlike UUIDv1 and v2) Best used with [zerolog](https://github.com/rs/zerolog)'s [RequestIDHandler](https://godoc.org/github.com/rs/zerolog/hlog#RequestIDHandler). Notes: - 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. References: - http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems - https://en.wikipedia.org/wiki/Universally_unique_identifier - https://blog.twitter.com/2010/announcing-snowflake - Python port by [Graham Abbott](https://github.com/graham): https://github.com/graham/python_xid - Scala port by [Egor Kolotaev](https://github.com/kolotaev): https://github.com/kolotaev/ride - Rust port by [Jérôme Renard](https://github.com/jeromer/): https://github.com/jeromer/libxid - Ruby port by [Valar](https://github.com/valarpirai/): https://github.com/valarpirai/ruby_xid - Java port by [0xShamil](https://github.com/0xShamil/): https://github.com/0xShamil/java-xid - Dart port by [Peter Bwire](https://github.com/pitabwire): https://pub.dev/packages/xid - PostgreSQL port by [Rasmus Holm](https://github.com/crholm): https://github.com/modfin/pg-xid - Swift port by [Uditha Atukorala](https://github.com/uditha-atukorala): https://github.com/uditha-atukorala/swift-xid - C++ port by [Uditha Atukorala](https://github.com/uditha-atukorala): https://github.com/uditha-atukorala/libxid ## Install go get github.com/rs/xid ## Usage ```go guid := xid.New() println(guid.String()) // Output: 9m4e2mr0ui3e8a215n4g ``` Get `xid` embedded info: ```go guid.Machine() guid.Pid() guid.Time() guid.Counter() ``` ## Benchmark Benchmark against Go [Maxim Bublis](https://github.com/satori)'s [UUID](https://github.com/satori/go.uuid). ``` BenchmarkXID 20000000 91.1 ns/op 32 B/op 1 allocs/op BenchmarkXID-2 20000000 55.9 ns/op 32 B/op 1 allocs/op BenchmarkXID-4 50000000 32.3 ns/op 32 B/op 1 allocs/op BenchmarkUUIDv1 10000000 204 ns/op 48 B/op 1 allocs/op BenchmarkUUIDv1-2 10000000 160 ns/op 48 B/op 1 allocs/op BenchmarkUUIDv1-4 10000000 195 ns/op 48 B/op 1 allocs/op BenchmarkUUIDv4 1000000 1503 ns/op 64 B/op 2 allocs/op BenchmarkUUIDv4-2 1000000 1427 ns/op 64 B/op 2 allocs/op BenchmarkUUIDv4-4 1000000 1452 ns/op 64 B/op 2 allocs/op ``` Note: UUIDv1 requires a global lock, hence the performance degradation as we add more CPUs. ## Licenses All source code is licensed under the [MIT License](https://raw.github.com/rs/xid/master/LICENSE). xid-1.5.0/b/000077500000000000000000000000001441542574500125355ustar00rootroot00000000000000xid-1.5.0/b/README.md000066400000000000000000000003211441542574500140100ustar00rootroot00000000000000# Bytes storage This subpackage is there to allow storage of XIDs in a binary format in, for example, a database. It allows some data size optimisation as the 12 bytes will be smaller to store than a string. xid-1.5.0/b/id.go000066400000000000000000000011761441542574500134650ustar00rootroot00000000000000package xidb import ( "database/sql/driver" "fmt" "github.com/rs/xid" ) type ID struct { xid.ID } // Value implements the driver.Valuer interface. func (id ID) Value() (driver.Value, error) { if id.ID.IsNil() { return nil, nil } return id.ID[:], nil } // Scan implements the sql.Scanner interface. func (id *ID) Scan(value interface{}) (err error) { switch val := value.(type) { case []byte: i, err := xid.FromBytes(val) if err != nil { return err } *id = ID{ID: i} return nil case nil: *id = ID{ID: xid.NilID()} return nil default: return fmt.Errorf("xid: scanning unsupported type: %T", value) } } xid-1.5.0/b/id_test.go000066400000000000000000000030301441542574500145130ustar00rootroot00000000000000package xidb import ( "reflect" "testing" "github.com/rs/xid" ) func TestIDValue(t *testing.T) { i, _ := xid.FromString("9m4e2mr0ui3e8a215n4g") tests := []struct { name string id ID expectedVal interface{} }{ { name: "non nil id", id: ID{ID: i}, expectedVal: i.Bytes(), }, { name: "nil id", id: ID{ID: xid.NilID()}, expectedVal: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, _ := tt.id.Value() if !reflect.DeepEqual(got, tt.expectedVal) { t.Errorf("wanted %v, got %v", tt.expectedVal, got) } }) } } func TestIDScan(t *testing.T) { i, _ := xid.FromString("9m4e2mr0ui3e8a215n4g") tests := []struct { name string val interface{} expectedID ID expectedErr bool }{ { name: "bytes id", val: i.Bytes(), expectedID: ID{ID: i}, }, { name: "nil id", val: nil, expectedID: ID{ID: xid.NilID()}, }, { name: "wrong bytes", val: []byte{0x01}, expectedErr: true, }, { name: "unknown type", val: 1, expectedErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { id := &ID{} err := id.Scan(tt.val) if (err != nil) != tt.expectedErr { t.Errorf("error expected: %t, got %t", tt.expectedErr, (err != nil)) } if err == nil { if !reflect.DeepEqual(id.ID, tt.expectedID.ID) { t.Errorf("wanted %v, got %v", tt.expectedID, id) } } }) } } xid-1.5.0/error.go000066400000000000000000000004021441542574500137700ustar00rootroot00000000000000package xid const ( // ErrInvalidID is returned when trying to unmarshal an invalid ID. ErrInvalidID strErr = "xid: invalid ID" ) // strErr allows declaring errors as constants. type strErr string func (err strErr) Error() string { return string(err) } xid-1.5.0/go.mod000066400000000000000000000000421441542574500134160ustar00rootroot00000000000000module github.com/rs/xid go 1.12 xid-1.5.0/hostid_darwin.go000066400000000000000000000002061441542574500154770ustar00rootroot00000000000000// +build darwin package xid import "syscall" func readPlatformMachineID() (string, error) { return syscall.Sysctl("kern.uuid") } xid-1.5.0/hostid_fallback.go000066400000000000000000000002451441542574500157550ustar00rootroot00000000000000// +build !darwin,!linux,!freebsd,!windows package xid import "errors" func readPlatformMachineID() (string, error) { return "", errors.New("not implemented") } xid-1.5.0/hostid_freebsd.go000066400000000000000000000002131441542574500156230ustar00rootroot00000000000000// +build freebsd package xid import "syscall" func readPlatformMachineID() (string, error) { return syscall.Sysctl("kern.hostuuid") } xid-1.5.0/hostid_linux.go000066400000000000000000000004101441542574500153470ustar00rootroot00000000000000// +build linux package xid import "io/ioutil" func readPlatformMachineID() (string, error) { b, err := ioutil.ReadFile("/etc/machine-id") if err != nil || len(b) == 0 { b, err = ioutil.ReadFile("/sys/class/dmi/id/product_uuid") } return string(b), err } xid-1.5.0/hostid_windows.go000066400000000000000000000020271441542574500157100ustar00rootroot00000000000000// +build windows package xid import ( "fmt" "syscall" "unsafe" ) func readPlatformMachineID() (string, error) { // source: https://github.com/shirou/gopsutil/blob/master/host/host_syscall.go var h syscall.Handle err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, syscall.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, syscall.KEY_READ|syscall.KEY_WOW64_64KEY, &h) if err != nil { return "", err } defer syscall.RegCloseKey(h) const syscallRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16 const uuidLen = 36 var regBuf [syscallRegBufLen]uint16 bufLen := uint32(syscallRegBufLen) var valType uint32 err = syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) if err != nil { return "", err } hostID := syscall.UTF16ToString(regBuf[:]) hostIDLen := len(hostID) if hostIDLen != uuidLen { return "", fmt.Errorf("HostID incorrect: %q\n", hostID) } return hostID, nil } xid-1.5.0/id.go000066400000000000000000000261351441542574500132460ustar00rootroot00000000000000// Package xid is a globally unique id generator suited for web scale // // Xid is using Mongo Object ID algorithm to generate globally unique ids: // https://docs.mongodb.org/manual/reference/object-id/ // // - 4-byte value representing the seconds since the Unix epoch, // - 3-byte machine identifier, // - 2-byte process id, and // - 3-byte counter, starting with a random value. // // The binary representation of the id is compatible with Mongo 12 bytes Object IDs. // The string representation is using base32 hex (w/o padding) for better space efficiency // when stored in that form (20 bytes). The hex variant of base32 is used to retain the // sortable property of the id. // // Xid doesn't use base64 because case sensitivity and the 2 non alphanum chars may be an // issue when transported as a string between various systems. Base36 wasn't retained either // because 1/ it's not standard 2/ the resulting size is not predictable (not bit aligned) // and 3/ it would not remain sortable. To validate a base32 `xid`, expect a 20 chars long, // all lowercase sequence of `a` to `v` letters and `0` to `9` numbers (`[0-9a-v]{20}`). // // UUID is 16 bytes (128 bits), snowflake is 8 bytes (64 bits), xid stands in between // with 12 bytes with a more compact string representation ready for the web and no // required configuration or central generation server. // // Features: // // - Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake // - Base32 hex encoded by default (16 bytes storage when transported as printable string) // - Non configured, you don't need set a unique machine and/or data center id // - K-ordered // - Embedded time with 1 second precision // - Unicity guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process // // Best used with xlog's RequestIDHandler (https://godoc.org/github.com/rs/xlog#RequestIDHandler). // // References: // // - http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems // - https://en.wikipedia.org/wiki/Universally_unique_identifier // - https://blog.twitter.com/2010/announcing-snowflake package xid import ( "bytes" "crypto/sha256" "crypto/rand" "database/sql/driver" "encoding/binary" "fmt" "hash/crc32" "io/ioutil" "os" "sort" "sync/atomic" "time" "unsafe" ) // Code inspired from mgo/bson ObjectId // ID represents a unique request id type ID [rawLen]byte const ( encodedLen = 20 // string encoded len rawLen = 12 // binary raw len // encoding stores a custom version of the base32 encoding with lower case // letters. encoding = "0123456789abcdefghijklmnopqrstuv" ) var ( // objectIDCounter is atomically incremented when generating a new ObjectId. It's // used as the counter part of an id. This id is initialized with a random value. objectIDCounter = randInt() // machineID is generated once and used in subsequent calls to the New* functions. machineID = readMachineID() // pid stores the current process id pid = os.Getpid() nilID ID // dec is the decoding map for base32 encoding dec [256]byte ) func init() { for i := 0; i < len(dec); i++ { dec[i] = 0xFF } for i := 0; i < len(encoding); i++ { dec[encoding[i]] = byte(i) } // If /proc/self/cpuset exists and is not /, we can assume that we are in a // form of container and use the content of cpuset xor-ed with the PID in // order get a reasonable machine global unique PID. b, err := ioutil.ReadFile("/proc/self/cpuset") if err == nil && len(b) > 1 { pid ^= int(crc32.ChecksumIEEE(b)) } } // readMachineID generates a machine ID, derived from a platform-specific machine ID // value, or else the machine's hostname, or else a randomly-generated number. // It panics if all of these methods fail. func readMachineID() []byte { id := make([]byte, 3) hid, err := readPlatformMachineID() if err != nil || len(hid) == 0 { hid, err = os.Hostname() } if err == nil && len(hid) != 0 { hw := sha256.New() hw.Write([]byte(hid)) copy(id, hw.Sum(nil)) } else { // Fallback to rand number if machine id can't be gathered if _, randErr := rand.Reader.Read(id); randErr != nil { panic(fmt.Errorf("xid: cannot get hostname nor generate a random number: %v; %v", err, randErr)) } } return id } // randInt generates a random uint32 func randInt() uint32 { b := make([]byte, 3) if _, err := rand.Reader.Read(b); err != nil { panic(fmt.Errorf("xid: cannot generate random number: %v;", err)) } return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2]) } // New generates a globally unique ID func New() ID { return NewWithTime(time.Now()) } // NewWithTime generates a globally unique ID with the passed in time func NewWithTime(t time.Time) ID { var id ID // Timestamp, 4 bytes, big endian binary.BigEndian.PutUint32(id[:], uint32(t.Unix())) // Machine ID, 3 bytes id[4] = machineID[0] id[5] = machineID[1] id[6] = machineID[2] // Pid, 2 bytes, specs don't specify endianness, but we use big endian. id[7] = byte(pid >> 8) id[8] = byte(pid) // Increment, 3 bytes, big endian i := atomic.AddUint32(&objectIDCounter, 1) id[9] = byte(i >> 16) id[10] = byte(i >> 8) id[11] = byte(i) return id } // FromString reads an ID from its string representation func FromString(id string) (ID, error) { i := &ID{} err := i.UnmarshalText([]byte(id)) return *i, err } // String returns a base32 hex lowercased with no padding representation of the id (char set is 0-9, a-v). func (id ID) String() string { text := make([]byte, encodedLen) encode(text, id[:]) return *(*string)(unsafe.Pointer(&text)) } // Encode encodes the id using base32 encoding, writing 20 bytes to dst and return it. func (id ID) Encode(dst []byte) []byte { encode(dst, id[:]) return dst } // MarshalText implements encoding/text TextMarshaler interface func (id ID) MarshalText() ([]byte, error) { text := make([]byte, encodedLen) encode(text, id[:]) return text, nil } // MarshalJSON implements encoding/json Marshaler interface func (id ID) MarshalJSON() ([]byte, error) { if id.IsNil() { return []byte("null"), nil } text := make([]byte, encodedLen+2) encode(text[1:encodedLen+1], id[:]) text[0], text[encodedLen+1] = '"', '"' return text, nil } // encode by unrolling the stdlib base32 algorithm + removing all safe checks func encode(dst, id []byte) { _ = dst[19] _ = id[11] dst[19] = encoding[(id[11]<<4)&0x1F] dst[18] = encoding[(id[11]>>1)&0x1F] dst[17] = encoding[(id[11]>>6)&0x1F|(id[10]<<2)&0x1F] dst[16] = encoding[id[10]>>3] dst[15] = encoding[id[9]&0x1F] dst[14] = encoding[(id[9]>>5)|(id[8]<<3)&0x1F] dst[13] = encoding[(id[8]>>2)&0x1F] dst[12] = encoding[id[8]>>7|(id[7]<<1)&0x1F] dst[11] = encoding[(id[7]>>4)&0x1F|(id[6]<<4)&0x1F] dst[10] = encoding[(id[6]>>1)&0x1F] dst[9] = encoding[(id[6]>>6)&0x1F|(id[5]<<2)&0x1F] dst[8] = encoding[id[5]>>3] dst[7] = encoding[id[4]&0x1F] dst[6] = encoding[id[4]>>5|(id[3]<<3)&0x1F] dst[5] = encoding[(id[3]>>2)&0x1F] dst[4] = encoding[id[3]>>7|(id[2]<<1)&0x1F] dst[3] = encoding[(id[2]>>4)&0x1F|(id[1]<<4)&0x1F] dst[2] = encoding[(id[1]>>1)&0x1F] dst[1] = encoding[(id[1]>>6)&0x1F|(id[0]<<2)&0x1F] dst[0] = encoding[id[0]>>3] } // UnmarshalText implements encoding/text TextUnmarshaler interface func (id *ID) UnmarshalText(text []byte) error { if len(text) != encodedLen { return ErrInvalidID } for _, c := range text { if dec[c] == 0xFF { return ErrInvalidID } } if !decode(id, text) { *id = nilID return ErrInvalidID } return nil } // UnmarshalJSON implements encoding/json Unmarshaler interface func (id *ID) UnmarshalJSON(b []byte) error { s := string(b) if s == "null" { *id = nilID return nil } // Check the slice length to prevent panic on passing it to UnmarshalText() if len(b) < 2 { return ErrInvalidID } return id.UnmarshalText(b[1 : len(b)-1]) } // decode by unrolling the stdlib base32 algorithm + customized safe check. func decode(id *ID, src []byte) bool { _ = src[19] _ = id[11] id[11] = dec[src[17]]<<6 | dec[src[18]]<<1 | dec[src[19]]>>4 // check the last byte if encoding[(id[11]<<4)&0x1F] != src[19] { return false } id[10] = dec[src[16]]<<3 | dec[src[17]]>>2 id[9] = dec[src[14]]<<5 | dec[src[15]] id[8] = dec[src[12]]<<7 | dec[src[13]]<<2 | dec[src[14]]>>3 id[7] = dec[src[11]]<<4 | dec[src[12]]>>1 id[6] = dec[src[9]]<<6 | dec[src[10]]<<1 | dec[src[11]]>>4 id[5] = dec[src[8]]<<3 | dec[src[9]]>>2 id[4] = dec[src[6]]<<5 | dec[src[7]] id[3] = dec[src[4]]<<7 | dec[src[5]]<<2 | dec[src[6]]>>3 id[2] = dec[src[3]]<<4 | dec[src[4]]>>1 id[1] = dec[src[1]]<<6 | dec[src[2]]<<1 | dec[src[3]]>>4 id[0] = dec[src[0]]<<3 | dec[src[1]]>>2 return true } // Time returns the timestamp part of the id. // It's a runtime error to call this method with an invalid id. func (id ID) Time() time.Time { // First 4 bytes of ObjectId is 32-bit big-endian seconds from epoch. secs := int64(binary.BigEndian.Uint32(id[0:4])) return time.Unix(secs, 0) } // Machine returns the 3-byte machine id part of the id. // It's a runtime error to call this method with an invalid id. func (id ID) Machine() []byte { return id[4:7] } // Pid returns the process id part of the id. // It's a runtime error to call this method with an invalid id. func (id ID) Pid() uint16 { return binary.BigEndian.Uint16(id[7:9]) } // Counter returns the incrementing value part of the id. // It's a runtime error to call this method with an invalid id. func (id ID) Counter() int32 { b := id[9:12] // Counter is stored as big-endian 3-byte value return int32(uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])) } // Value implements the driver.Valuer interface. func (id ID) Value() (driver.Value, error) { if id.IsNil() { return nil, nil } b, err := id.MarshalText() return string(b), err } // Scan implements the sql.Scanner interface. func (id *ID) Scan(value interface{}) (err error) { switch val := value.(type) { case string: return id.UnmarshalText([]byte(val)) case []byte: return id.UnmarshalText(val) case nil: *id = nilID return nil default: return fmt.Errorf("xid: scanning unsupported type: %T", value) } } // IsNil Returns true if this is a "nil" ID func (id ID) IsNil() bool { return id == nilID } // Alias of IsNil func (id ID) IsZero() bool { return id.IsNil() } // NilID returns a zero value for `xid.ID`. func NilID() ID { return nilID } // Bytes returns the byte array representation of `ID` func (id ID) Bytes() []byte { return id[:] } // FromBytes convert the byte array representation of `ID` back to `ID` func FromBytes(b []byte) (ID, error) { var id ID if len(b) != rawLen { return id, ErrInvalidID } copy(id[:], b) return id, nil } // Compare returns an integer comparing two IDs. It behaves just like `bytes.Compare`. // The result will be 0 if two IDs are identical, -1 if current id is less than the other one, // and 1 if current id is greater than the other. func (id ID) Compare(other ID) int { return bytes.Compare(id[:], other[:]) } type sorter []ID func (s sorter) Len() int { return len(s) } func (s sorter) Less(i, j int) bool { return s[i].Compare(s[j]) < 0 } func (s sorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // Sort sorts an array of IDs inplace. // It works by wrapping `[]ID` and use `sort.Sort`. func Sort(ids []ID) { sort.Sort(sorter(ids)) } xid-1.5.0/id_test.go000066400000000000000000000264261441542574500143100ustar00rootroot00000000000000package xid import ( "bytes" "encoding/json" "errors" "fmt" "math/rand" "reflect" "testing" "testing/quick" "time" ) type IDParts struct { id ID timestamp int64 machine []byte pid uint16 counter int32 } var IDs = []IDParts{ { ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}, 1300816219, []byte{0x60, 0xf4, 0x86}, 0xe428, 4271561, }, { ID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 0, []byte{0x00, 0x00, 0x00}, 0x0000, 0, }, { ID{0x00, 0x00, 0x00, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0x00, 0x00, 0x01}, 0, []byte{0xaa, 0xbb, 0xcc}, 0xddee, 1, }, } func TestIDPartsExtraction(t *testing.T) { for i, v := range IDs { t.Run(fmt.Sprintf("Test%d", i), func(t *testing.T) { if got, want := v.id.Time(), time.Unix(v.timestamp, 0); got != want { t.Errorf("Time() = %v, want %v", got, want) } if got, want := v.id.Machine(), v.machine; !bytes.Equal(got, want) { t.Errorf("Machine() = %v, want %v", got, want) } if got, want := v.id.Pid(), v.pid; got != want { t.Errorf("Pid() = %v, want %v", got, want) } if got, want := v.id.Counter(), v.counter; got != want { t.Errorf("Counter() = %v, want %v", got, want) } }) } } func TestPadding(t *testing.T) { for i := 0; i < 100000; i++ { wantBytes := make([]byte, 20) wantBytes[19] = encoding[0] // 0 copy(wantBytes[0:13], []byte("c6e52g2mrqcjl")[:]) // c6e52g2mrqcjl44hf170 for j := 0; j < 6; j++ { wantBytes[13+j] = encoding[rand.Intn(32)] } want := string(wantBytes) id, _ := FromString(want) got := id.String() if got != want { t.Errorf("String() = %v, want %v %v", got, want, wantBytes) } } } func TestNew(t *testing.T) { // Generate 10 ids ids := make([]ID, 10) for i := 0; i < 10; i++ { ids[i] = New() } for i := 1; i < 10; i++ { prevID := ids[i-1] id := ids[i] // Test for uniqueness among all other 9 generated ids for j, tid := range ids { if j != i { if id.Compare(tid) == 0 { t.Errorf("generated ID is not unique (%d/%d)", i, j) } } } // Check that timestamp was incremented and is within 30 seconds of the previous one secs := id.Time().Sub(prevID.Time()).Seconds() if secs < 0 || secs > 30 { t.Error("wrong timestamp in generated ID") } // Check that machine ids are the same if !bytes.Equal(id.Machine(), prevID.Machine()) { t.Error("machine ID not equal") } // Check that pids are the same if id.Pid() != prevID.Pid() { t.Error("pid not equal") } // Test for proper increment if got, want := int(id.Counter()-prevID.Counter()), 1; got != want { t.Errorf("wrong increment in generated ID, delta=%v, want %v", got, want) } } } func TestIDString(t *testing.T) { id := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} if got, want := id.String(), "9m4e2mr0ui3e8a215n4g"; got != want { t.Errorf("String() = %v, want %v", got, want) } } func TestIDEncode(t *testing.T) { id := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} text := make([]byte, encodedLen) if got, want := string(id.Encode(text)), "9m4e2mr0ui3e8a215n4g"; got != want { t.Errorf("Encode() = %v, want %v", got, want) } } func TestFromString(t *testing.T) { got, err := FromString("9m4e2mr0ui3e8a215n4g") if err != nil { t.Fatal(err) } want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} if got != want { t.Errorf("FromString() = %v, want %v", got, want) } } func TestFromStringInvalid(t *testing.T) { _, err := FromString("invalid") if err != ErrInvalidID { t.Errorf("FromString(invalid) err=%v, want %v", err, ErrInvalidID) } id, err := FromString("c6e52g2mrqcjl44hf179") if id != nilID { t.Errorf("FromString() =%v, want %v", id, nilID) } } type jsonType struct { ID *ID Str string } func TestIDJSONMarshaling(t *testing.T) { id := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} v := jsonType{ID: &id, Str: "test"} data, err := json.Marshal(&v) if err != nil { t.Fatal(err) } if got, want := string(data), `{"ID":"9m4e2mr0ui3e8a215n4g","Str":"test"}`; got != want { t.Errorf("json.Marshal() = %v, want %v", got, want) } } func TestIDJSONUnmarshaling(t *testing.T) { data := []byte(`{"ID":"9m4e2mr0ui3e8a215n4g","Str":"test"}`) v := jsonType{} err := json.Unmarshal(data, &v) if err != nil { t.Fatal(err) } want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} if got := *v.ID; got.Compare(want) != 0 { t.Errorf("json.Unmarshal() = %v, want %v", got, want) } } func TestIDJSONUnmarshalingError(t *testing.T) { v := jsonType{} err := json.Unmarshal([]byte(`{"ID":"9M4E2MR0UI3E8A215N4G"}`), &v) if err != ErrInvalidID { t.Errorf("json.Unmarshal() err=%v, want %v", err, ErrInvalidID) } err = json.Unmarshal([]byte(`{"ID":"TYjhW2D0huQoQS"}`), &v) if err != ErrInvalidID { t.Errorf("json.Unmarshal() err=%v, want %v", err, ErrInvalidID) } err = json.Unmarshal([]byte(`{"ID":"TYjhW2D0huQoQS3kdk"}`), &v) if err != ErrInvalidID { t.Errorf("json.Unmarshal() err=%v, want %v", err, ErrInvalidID) } err = json.Unmarshal([]byte(`{"ID":1}`), &v) if err != ErrInvalidID { t.Errorf("json.Unmarshal() err=%v, want %v", err, ErrInvalidID) } } func TestIDDriverValue(t *testing.T) { id := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} got, err := id.Value() if err != nil { t.Fatal(err) } if want := "9m4e2mr0ui3e8a215n4g"; got != want { t.Errorf("Value() = %v, want %v", got, want) } } func TestIDDriverScan(t *testing.T) { got := ID{} err := got.Scan("9m4e2mr0ui3e8a215n4g") if err != nil { t.Fatal(err) } want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} if got.Compare(want) != 0 { t.Errorf("Scan() = %v, want %v", got, want) } } func TestIDDriverScanError(t *testing.T) { id := ID{} if got, want := id.Scan(0), errors.New("xid: scanning unsupported type: int"); !reflect.DeepEqual(got, want) { t.Errorf("Scan() err=%v, want %v", got, want) } if got, want := id.Scan("0"), ErrInvalidID; got != want { t.Errorf("Scan() err=%v, want %v", got, want) } } func TestIDDriverScanByteFromDatabase(t *testing.T) { got := ID{} bs := []byte("9m4e2mr0ui3e8a215n4g") err := got.Scan(bs) if err != nil { t.Fatal(err) } want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9} if got.Compare(want) != 0 { t.Errorf("Scan() = %v, want %v", got, want) } } func BenchmarkNew(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { _ = New() } }) } func BenchmarkNewString(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { _ = New().String() } }) } func BenchmarkFromString(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { _, _ = FromString("9m4e2mr0ui3e8a215n4g") } }) } func TestFromStringQuick(t *testing.T) { f := func(id1 ID, c byte) bool { s1 := id1.String() for i := range s1 { s2 := []byte(s1) s2[i] = c id2, err := FromString(string(s2)) if id1 == id2 && err == nil && c != s1[i] { t.Logf("comparing XIDs:\na: %q\nb: %q (index %d changed to %c)", s1, s2, i, c) return false } } return true } err := quick.Check(f, &quick.Config{ Values: func(args []reflect.Value, r *rand.Rand) { i := r.Intn(len(encoding)) args[0] = reflect.ValueOf(New()) args[1] = reflect.ValueOf(byte(encoding[i])) }, MaxCount: 1000, }) if err != nil { t.Error(err) } } func TestFromStringQuickInvalidChars(t *testing.T) { f := func(id1 ID, c byte) bool { s1 := id1.String() for i := range s1 { s2 := []byte(s1) s2[i] = c id2, err := FromString(string(s2)) if id1 == id2 && err == nil && c != s1[i] { t.Logf("comparing XIDs:\na: %q\nb: %q (index %d changed to %c)", s1, s2, i, c) return false } } return true } err := quick.Check(f, &quick.Config{ Values: func(args []reflect.Value, r *rand.Rand) { i := r.Intn(0xFF) args[0] = reflect.ValueOf(New()) args[1] = reflect.ValueOf(byte(i)) }, MaxCount: 2000, }) if err != nil { t.Error(err) } } // func BenchmarkUUIDv1(b *testing.B) { // b.RunParallel(func(pb *testing.PB) { // for pb.Next() { // _ = uuid.NewV1().String() // } // }) // } // func BenchmarkUUIDv4(b *testing.B) { // b.RunParallel(func(pb *testing.PB) { // for pb.Next() { // _ = uuid.NewV4().String() // } // }) // } func TestID_IsNil(t *testing.T) { tests := []struct { name string id ID want bool }{ { name: "ID not nil", id: New(), want: false, }, { name: "Nil ID", id: ID{}, want: true, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { if got, want := tt.id.IsNil(), tt.want; got != want { t.Errorf("IsNil() = %v, want %v", got, want) } }) } } func TestNilID(t *testing.T) { got := ID{} if want := NilID(); !reflect.DeepEqual(got, want) { t.Error("NilID() not equal ID{}") } } func TestNilID_IsNil(t *testing.T) { if !NilID().IsNil() { t.Error("NilID().IsNil() is not true") } } func TestFromBytes_Invariant(t *testing.T) { want := New() got, err := FromBytes(want.Bytes()) if err != nil { t.Fatal(err) } if got.Compare(want) != 0 { t.Error("FromBytes(id.Bytes()) != id") } } func TestFromBytes_InvalidBytes(t *testing.T) { cases := []struct { length int shouldFail bool }{ {11, true}, {12, false}, {13, true}, } for _, c := range cases { b := make([]byte, c.length) _, err := FromBytes(b) if got, want := err != nil, c.shouldFail; got != want { t.Errorf("FromBytes() error got %v, want %v", got, want) } } } func TestID_Compare(t *testing.T) { pairs := []struct { left ID right ID expected int }{ {IDs[1].id, IDs[0].id, -1}, {ID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, IDs[2].id, -1}, {IDs[0].id, IDs[0].id, 0}, } for _, p := range pairs { if p.expected != p.left.Compare(p.right) { t.Errorf("%s Compare to %s should return %d", p.left, p.right, p.expected) } if -1*p.expected != p.right.Compare(p.left) { t.Errorf("%s Compare to %s should return %d", p.right, p.left, -1*p.expected) } } } var IDList = []ID{IDs[0].id, IDs[1].id, IDs[2].id} func TestSorter_Len(t *testing.T) { if got, want := sorter([]ID{}).Len(), 0; got != want { t.Errorf("Len() %v, want %v", got, want) } if got, want := sorter(IDList).Len(), 3; got != want { t.Errorf("Len() %v, want %v", got, want) } } func TestSorter_Less(t *testing.T) { sorter := sorter(IDList) if !sorter.Less(1, 0) { t.Errorf("Less(1, 0) not true") } if sorter.Less(2, 1) { t.Errorf("Less(2, 1) true") } if sorter.Less(0, 0) { t.Errorf("Less(0, 0) true") } } func TestSorter_Swap(t *testing.T) { ids := make([]ID, 0) ids = append(ids, IDList...) sorter := sorter(ids) sorter.Swap(0, 1) if got, want := ids[0], IDList[1]; !reflect.DeepEqual(got, want) { t.Error("ids[0] != IDList[1]") } if got, want := ids[1], IDList[0]; !reflect.DeepEqual(got, want) { t.Error("ids[1] != IDList[0]") } sorter.Swap(2, 2) if got, want := ids[2], IDList[2]; !reflect.DeepEqual(got, want) { t.Error("ids[2], IDList[2]") } } func TestSort(t *testing.T) { ids := make([]ID, 0) ids = append(ids, IDList...) Sort(ids) if got, want := ids, []ID{IDList[1], IDList[2], IDList[0]}; !reflect.DeepEqual(got, want) { t.Fail() } }