feat(ulid): supply-chain fork of github.com/oklog/ulid v2

This commit is contained in:
AJ ONeal 2025-12-13 21:45:01 -07:00
parent 7513e62a6c
commit 3893b43c8c
No known key found for this signature in database
6 changed files with 2061 additions and 0 deletions

201
3p/ulid/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

240
3p/ulid/README.md Normal file
View File

@ -0,0 +1,240 @@
# Universally Unique Lexicographically Sortable Identifier
[![Go Reference](https://pkg.go.dev/badge/github.com/therootcompany/golib/3p/ulid.svg)](https://pkg.go.dev/github.com/therootcompany/golib/3p/ulid)
[![Project status](https://img.shields.io/github/release/therootcompany/golib/3p/ulid.svg?style=flat-square)](https://github.com/therootcompany/golib/3p/ulid/releases/latest)
![Build Status](https://github.com/therootcompany/golib/3p/ulid/actions/workflows/test.yml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/therootcompany/golib/3p/ulid?cache=0)](https://goreportcard.com/report/therootcompany/golib/3p/ulid)
[![Coverage Status](https://coveralls.io/repos/github/therootcompany/golib/3p/ulid/badge.svg?branch=master&cache=0)](https://coveralls.io/github/therootcompany/golib/3p/ulid?branch=master)
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/therootcompany/golib/3p/ulid/v2)
[![Apache 2 licensed](https://img.shields.io/badge/license-Apache2-blue.svg)](https://raw.githubusercontent.com/therootcompany/golib/3p/ulid/master/LICENSE)
A Go port of [ulid/javascript](https://github.com/ulid/javascript) with binary format implemented.
## Background
A GUID/UUID can be suboptimal for many use-cases because:
- It isn't the most character efficient way of encoding 128 bits
- UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address
- UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures
- UUID v4 provides no other information than randomness which can cause fragmentation in many data structures
A ULID however:
- Is compatible with UUID/GUID's
- 1.21e+24 unique ULIDs per millisecond (1,208,925,819,614,629,174,706,176 to be exact)
- Lexicographically sortable
- Canonically encoded as a 26 character string, as opposed to the 36 character UUID
- Uses Crockford's base32 for better efficiency and readability (5 bits per character)
- Case insensitive
- No special characters (URL safe)
- Monotonic sort order (correctly detects and handles the same millisecond)
## Install
This package requires Go modules.
```shell
go get github.com/therootcompany/golib/3p/ulid/v2
```
## Usage
ULIDs are constructed from two things: a timestamp with millisecond precision,
and some random data.
Timestamps are modeled as uint64 values representing a Unix time in milliseconds.
They can be produced by passing a [time.Time](https://pkg.go.dev/time#Time) to
[ulid.Timestamp](https://pkg.go.dev/github.com/therootcompany/golib/3p/ulid/v2#Timestamp),
or by calling [time.Time.UnixMilli](https://pkg.go.dev/time#Time.UnixMilli)
and converting the returned value to `uint64`.
Random data is taken from a provided [io.Reader](https://pkg.go.dev/io#Reader).
This design allows for greater flexibility when choosing trade-offs, but can be
a bit confusing to newcomers.
If you just want to generate a ULID and don't (yet) care about details like
performance, cryptographic security, etc., use the
[ulid.Make](https://pkg.go.dev/github.com/therootcompany/golib/3p/ulid/v2#Make) helper function.
This function calls [time.Now](https://pkg.go.dev/time#Now) to get a timestamp,
and uses a source of entropy which is process-global,
[pseudo-random](https://pkg.go.dev/math/rand), and
[monotonic](https://pkg.go.dev/github.com/therootcompany/golib/3p/ulid/v2#LockedMonotonicReader).
```go
fmt.Println(ulid.Make())
// 01G65Z755AFWAKHE12NY0CQ9FH
```
More advanced use cases should utilize
[ulid.New](https://pkg.go.dev/github.com/therootcompany/golib/3p/ulid/v2#New).
```go
entropy := rand.New(rand.NewSource(time.Now().UnixNano()))
ms := ulid.Timestamp(time.Now())
fmt.Println(ulid.New(ms, entropy))
// 01G65Z755AFWAKHE12NY0CQ9FH
```
Care should be taken when providing a source of entropy.
The above example utilizes [math/rand.Rand](https://pkg.go.dev/math/rand#Rand),
which is not safe for concurrent use by multiple goroutines. Consider
alternatives such as
[x/exp/rand](https://pkg.go.dev/golang.org/x/exp/rand#LockedSource).
Security-sensitive use cases should always use cryptographically secure entropy
provided by [crypto/rand](https://pkg.go.dev/crypto/rand).
Performance-sensitive use cases should avoid synchronization when generating
IDs. One option is to use a unique source of entropy for each concurrent
goroutine, which results in no lock contention, but cannot provide strong
guarantees about the random data, and does not provide monotonicity within a
given millisecond. One common performance optimization is to pool sources of
entropy using a [sync.Pool](https://pkg.go.dev/sync#Pool).
Monotonicity is a property that says each ULID is "bigger than" the previous
one. ULIDs are automatically monotonic, but only to millisecond precision. ULIDs
generated within the same millisecond are ordered by their random component,
which means they are by default un-ordered. You can use
[ulid.MonotonicEntropy](https://pkg.go.dev/github.com/therootcompany/golib/3p/ulid/v2#MonotonicEntropy) or
[ulid.LockedMonotonicEntropy](https://pkg.go.dev/github.com/therootcompany/golib/3p/ulid/v2#LockedMonotonicEntropy)
to create ULIDs that are monotonic within a given millisecond, with caveats. See
the documentation for details.
If you don't care about time-based ordering of generated IDs, then there's no
reason to use ULIDs! There are many other kinds of IDs that are easier, faster,
smaller, etc. Consider UUIDs.
## Commandline tool
This repo also provides a tool to generate and parse ULIDs at the command line.
```shell
go install github.com/therootcompany/golib/3p/ulid/v2/cmd/ulid@latest
```
Usage:
```shell
Usage: ulid [-hlqz] [-f <format>] [parameters ...]
-f, --format=<format> when parsing, show times in this format: default, rfc3339, unix, ms
-h, --help print this help text
-l, --local when parsing, show local time instead of UTC
-q, --quick when generating, use non-crypto-grade entropy
-z, --zero when generating, fix entropy to all-zeroes
```
Examples:
```shell
$ ulid
01D78XYFJ1PRM1WPBCBT3VHMNV
$ ulid -z
01D78XZ44G0000000000000000
$ ulid 01D78XZ44G0000000000000000
Sun Mar 31 03:51:23.536 UTC 2019
$ ulid --format=rfc3339 --local 01D78XZ44G0000000000000000
2019-03-31T05:51:23.536+02:00
```
## Specification
Below is the current specification of ULID as implemented in this repository.
### Components
**Timestamp**
- 48 bits
- UNIX-time in milliseconds
- Won't run out of space till the year 10889 AD
**Entropy**
- 80 bits
- User defined entropy source.
- Monotonicity within the same millisecond with [`ulid.Monotonic`](https://godoc.org/github.com/therootcompany/golib/3p/ulid#Monotonic)
### Encoding
[Crockford's Base32](http://www.crockford.com/wrmg/base32.html) is used as shown.
This alphabet excludes the letters I, L, O, and U to avoid confusion and abuse.
```
0123456789ABCDEFGHJKMNPQRSTVWXYZ
```
### Binary Layout and Byte Order
The components are encoded as 16 octets. Each component is encoded with the Most Significant Byte first (network byte order).
```
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 16_bit_uint_time_low | 16_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```
### String Representation
```
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Entropy
10 chars 16 chars
48bits 80bits
base32 base32
```
## Test
```shell
go test ./...
```
## Benchmarks
On a Intel Core i7 Ivy Bridge 2.7 GHz, MacOS 10.12.1 and Go 1.8.0beta1
```
BenchmarkNew/WithCryptoEntropy-8 2000000 771 ns/op 20.73 MB/s 16 B/op 1 allocs/op
BenchmarkNew/WithEntropy-8 20000000 65.8 ns/op 243.01 MB/s 16 B/op 1 allocs/op
BenchmarkNew/WithoutEntropy-8 50000000 30.0 ns/op 534.06 MB/s 16 B/op 1 allocs/op
BenchmarkMustNew/WithCryptoEntropy-8 2000000 781 ns/op 20.48 MB/s 16 B/op 1 allocs/op
BenchmarkMustNew/WithEntropy-8 20000000 70.0 ns/op 228.51 MB/s 16 B/op 1 allocs/op
BenchmarkMustNew/WithoutEntropy-8 50000000 34.6 ns/op 462.98 MB/s 16 B/op 1 allocs/op
BenchmarkParse-8 50000000 30.0 ns/op 866.16 MB/s 0 B/op 0 allocs/op
BenchmarkMustParse-8 50000000 35.2 ns/op 738.94 MB/s 0 B/op 0 allocs/op
BenchmarkString-8 20000000 64.9 ns/op 246.40 MB/s 32 B/op 1 allocs/op
BenchmarkMarshal/Text-8 20000000 55.8 ns/op 286.84 MB/s 32 B/op 1 allocs/op
BenchmarkMarshal/TextTo-8 100000000 22.4 ns/op 714.91 MB/s 0 B/op 0 allocs/op
BenchmarkMarshal/Binary-8 300000000 4.02 ns/op 3981.77 MB/s 0 B/op 0 allocs/op
BenchmarkMarshal/BinaryTo-8 2000000000 1.18 ns/op 13551.75 MB/s 0 B/op 0 allocs/op
BenchmarkUnmarshal/Text-8 100000000 20.5 ns/op 1265.27 MB/s 0 B/op 0 allocs/op
BenchmarkUnmarshal/Binary-8 300000000 4.94 ns/op 3240.01 MB/s 0 B/op 0 allocs/op
BenchmarkNow-8 100000000 15.1 ns/op 528.09 MB/s 0 B/op 0 allocs/op
BenchmarkTimestamp-8 2000000000 0.29 ns/op 27271.59 MB/s 0 B/op 0 allocs/op
BenchmarkTime-8 2000000000 0.58 ns/op 13717.80 MB/s 0 B/op 0 allocs/op
BenchmarkSetTime-8 2000000000 0.89 ns/op 9023.95 MB/s 0 B/op 0 allocs/op
BenchmarkEntropy-8 200000000 7.62 ns/op 1311.66 MB/s 0 B/op 0 allocs/op
BenchmarkSetEntropy-8 2000000000 0.88 ns/op 11376.54 MB/s 0 B/op 0 allocs/op
BenchmarkCompare-8 200000000 7.34 ns/op 4359.23 MB/s 0 B/op 0 allocs/op
```
## Prior Art
- [ulid/javascript](https://github.com/ulid/javascript)
- [RobThree/NUlid](https://github.com/RobThree/NUlid)
- [imdario/go-ulid](https://github.com/imdario/go-ulid)
## License
This is a supply-chain fork of <https://github.com/therootcompany/golib/3p/ulid>.

3
3p/ulid/go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/therootcompany/golib/3p/ulid/v2
go 1.15

2
3p/ulid/go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2jmMaozVcdk+sVfz0+1ZJq4zkWgw=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=

713
3p/ulid/ulid.go Normal file
View File

@ -0,0 +1,713 @@
// Copyright 2016 The Oklog Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ulid
import (
"bufio"
"bytes"
"database/sql/driver"
"encoding/binary"
"errors"
"io"
"math"
"math/bits"
"math/rand"
"sync"
"time"
)
/*
A ULID is a 16 byte Universally Unique Lexicographically Sortable Identifier
The components are encoded as 16 octets.
Each component is encoded with the MSB first (network byte order).
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 16_bit_uint_time_low | 16_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type ULID [16]byte
var (
// ErrDataSize is returned when parsing or unmarshaling ULIDs with the wrong
// data size.
ErrDataSize = errors.New("ulid: bad data size when unmarshaling")
// ErrInvalidCharacters is returned when parsing or unmarshaling ULIDs with
// invalid Base32 encodings.
ErrInvalidCharacters = errors.New("ulid: bad data characters when unmarshaling")
// ErrBufferSize is returned when marshalling ULIDs to a buffer of insufficient
// size.
ErrBufferSize = errors.New("ulid: bad buffer size when marshaling")
// ErrBigTime is returned when constructing a ULID with a time that is larger
// than MaxTime.
ErrBigTime = errors.New("ulid: time too big")
// ErrOverflow is returned when unmarshaling a ULID whose first character is
// larger than 7, thereby exceeding the valid bit depth of 128.
ErrOverflow = errors.New("ulid: overflow when unmarshaling")
// ErrMonotonicOverflow is returned by a Monotonic entropy source when
// incrementing the previous ULID's entropy bytes would result in overflow.
ErrMonotonicOverflow = errors.New("ulid: monotonic entropy overflow")
// ErrScanValue is returned when the value passed to scan cannot be unmarshaled
// into the ULID.
ErrScanValue = errors.New("ulid: source value must be a string or byte slice")
// Zero is a zero-value ULID.
Zero ULID
)
// MonotonicReader is an interface that should yield monotonically increasing
// entropy into the provided slice for all calls with the same ms parameter. If
// a MonotonicReader is provided to the New constructor, its MonotonicRead
// method will be used instead of Read.
type MonotonicReader interface {
io.Reader
MonotonicRead(ms uint64, p []byte) error
}
// New returns a ULID with the given Unix milliseconds timestamp and an
// optional entropy source. Use the Timestamp function to convert
// a time.Time to Unix milliseconds.
//
// ErrBigTime is returned when passing a timestamp bigger than MaxTime.
// Reading from the entropy source may also return an error.
//
// Safety for concurrent use is only dependent on the safety of the
// entropy source.
func New(ms uint64, entropy io.Reader) (id ULID, err error) {
if err = id.SetTime(ms); err != nil {
return id, err
}
switch e := entropy.(type) {
case nil:
return id, err
case MonotonicReader:
err = e.MonotonicRead(ms, id[6:])
default:
_, err = io.ReadFull(e, id[6:])
}
return id, err
}
// MustNew is a convenience function equivalent to New that panics on failure
// instead of returning an error.
func MustNew(ms uint64, entropy io.Reader) ULID {
id, err := New(ms, entropy)
if err != nil {
panic(err)
}
return id
}
// MustNewDefault is a convenience function equivalent to MustNew with
// DefaultEntropy as the entropy. It may panic if the given time.Time is too
// large or too small.
func MustNewDefault(t time.Time) ULID {
return MustNew(Timestamp(t), defaultEntropy)
}
var defaultEntropy = func() io.Reader {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
return &LockedMonotonicReader{MonotonicReader: Monotonic(rng, 0)}
}()
// DefaultEntropy returns a thread-safe per process monotonically increasing
// entropy source.
func DefaultEntropy() io.Reader {
return defaultEntropy
}
// Make returns a ULID with the current time in Unix milliseconds and
// monotonically increasing entropy for the same millisecond.
// It is safe for concurrent use, leveraging a sync.Pool underneath for minimal
// contention.
func Make() (id ULID) {
// NOTE: MustNew can't panic since DefaultEntropy never returns an error.
return MustNew(Now(), defaultEntropy)
}
// Parse parses an encoded ULID, returning an error in case of failure.
//
// ErrDataSize is returned if the len(ulid) is different from an encoded
// ULID's length. Invalid encodings produce undefined ULIDs. For a version that
// returns an error instead, see ParseStrict.
func Parse(ulid string) (id ULID, err error) {
return id, parse([]byte(ulid), false, &id)
}
// ParseStrict parses an encoded ULID, returning an error in case of failure.
//
// It is like Parse, but additionally validates that the parsed ULID consists
// only of valid base32 characters. It is slightly slower than Parse.
//
// ErrDataSize is returned if the len(ulid) is different from an encoded
// ULID's length. Invalid encodings return ErrInvalidCharacters.
func ParseStrict(ulid string) (id ULID, err error) {
return id, parse([]byte(ulid), true, &id)
}
func parse(v []byte, strict bool, id *ULID) error {
// Check if a base32 encoded ULID is the right length.
if len(v) != EncodedSize {
return ErrDataSize
}
// Check if all the characters in a base32 encoded ULID are part of the
// expected base32 character set.
if strict &&
(dec[v[0]] == 0xFF ||
dec[v[1]] == 0xFF ||
dec[v[2]] == 0xFF ||
dec[v[3]] == 0xFF ||
dec[v[4]] == 0xFF ||
dec[v[5]] == 0xFF ||
dec[v[6]] == 0xFF ||
dec[v[7]] == 0xFF ||
dec[v[8]] == 0xFF ||
dec[v[9]] == 0xFF ||
dec[v[10]] == 0xFF ||
dec[v[11]] == 0xFF ||
dec[v[12]] == 0xFF ||
dec[v[13]] == 0xFF ||
dec[v[14]] == 0xFF ||
dec[v[15]] == 0xFF ||
dec[v[16]] == 0xFF ||
dec[v[17]] == 0xFF ||
dec[v[18]] == 0xFF ||
dec[v[19]] == 0xFF ||
dec[v[20]] == 0xFF ||
dec[v[21]] == 0xFF ||
dec[v[22]] == 0xFF ||
dec[v[23]] == 0xFF ||
dec[v[24]] == 0xFF ||
dec[v[25]] == 0xFF) {
return ErrInvalidCharacters
}
// Check if the first character in a base32 encoded ULID will overflow. This
// happens because the base32 representation encodes 130 bits, while the
// ULID is only 128 bits.
//
// See https://github.com/therootcompany/golib/3p/ulid/issues/9 for details.
if v[0] > '7' {
return ErrOverflow
}
// Use an optimized unrolled loop (from https://github.com/RobThree/NUlid)
// to decode a base32 ULID.
// 6 bytes timestamp (48 bits)
(*id)[0] = (dec[v[0]] << 5) | dec[v[1]]
(*id)[1] = (dec[v[2]] << 3) | (dec[v[3]] >> 2)
(*id)[2] = (dec[v[3]] << 6) | (dec[v[4]] << 1) | (dec[v[5]] >> 4)
(*id)[3] = (dec[v[5]] << 4) | (dec[v[6]] >> 1)
(*id)[4] = (dec[v[6]] << 7) | (dec[v[7]] << 2) | (dec[v[8]] >> 3)
(*id)[5] = (dec[v[8]] << 5) | dec[v[9]]
// 10 bytes of entropy (80 bits)
(*id)[6] = (dec[v[10]] << 3) | (dec[v[11]] >> 2)
(*id)[7] = (dec[v[11]] << 6) | (dec[v[12]] << 1) | (dec[v[13]] >> 4)
(*id)[8] = (dec[v[13]] << 4) | (dec[v[14]] >> 1)
(*id)[9] = (dec[v[14]] << 7) | (dec[v[15]] << 2) | (dec[v[16]] >> 3)
(*id)[10] = (dec[v[16]] << 5) | dec[v[17]]
(*id)[11] = (dec[v[18]] << 3) | dec[v[19]]>>2
(*id)[12] = (dec[v[19]] << 6) | (dec[v[20]] << 1) | (dec[v[21]] >> 4)
(*id)[13] = (dec[v[21]] << 4) | (dec[v[22]] >> 1)
(*id)[14] = (dec[v[22]] << 7) | (dec[v[23]] << 2) | (dec[v[24]] >> 3)
(*id)[15] = (dec[v[24]] << 5) | dec[v[25]]
return nil
}
// MustParse is a convenience function equivalent to Parse that panics on failure
// instead of returning an error.
func MustParse(ulid string) ULID {
id, err := Parse(ulid)
if err != nil {
panic(err)
}
return id
}
// MustParseStrict is a convenience function equivalent to ParseStrict that
// panics on failure instead of returning an error.
func MustParseStrict(ulid string) ULID {
id, err := ParseStrict(ulid)
if err != nil {
panic(err)
}
return id
}
// Bytes returns bytes slice representation of ULID.
func (id ULID) Bytes() []byte {
return id[:]
}
// String returns a lexicographically sortable string encoded ULID
// (26 characters, non-standard base 32) e.g. 01AN4Z07BY79KA1307SR9X4MV3.
// Format: tttttttttteeeeeeeeeeeeeeee where t is time and e is entropy.
func (id ULID) String() string {
ulid := make([]byte, EncodedSize)
_ = id.MarshalTextTo(ulid)
return string(ulid)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface by
// returning the ULID as a byte slice.
func (id ULID) MarshalBinary() ([]byte, error) {
ulid := make([]byte, len(id))
return ulid, id.MarshalBinaryTo(ulid)
}
// MarshalBinaryTo writes the binary encoding of the ULID to the given buffer.
// ErrBufferSize is returned when the len(dst) != 16.
func (id ULID) MarshalBinaryTo(dst []byte) error {
if len(dst) != len(id) {
return ErrBufferSize
}
copy(dst, id[:])
return nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface by
// copying the passed data and converting it to a ULID. ErrDataSize is
// returned if the data length is different from ULID length.
func (id *ULID) UnmarshalBinary(data []byte) error {
if len(data) != len(*id) {
return ErrDataSize
}
copy((*id)[:], data)
return nil
}
// Encoding is the base 32 encoding alphabet used in ULID strings.
const Encoding = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
// MarshalText implements the encoding.TextMarshaler interface by
// returning the string encoded ULID.
func (id ULID) MarshalText() ([]byte, error) {
ulid := make([]byte, EncodedSize)
return ulid, id.MarshalTextTo(ulid)
}
// MarshalTextTo writes the ULID as a string to the given buffer.
// ErrBufferSize is returned when the len(dst) != 26.
func (id ULID) MarshalTextTo(dst []byte) error {
// Optimized unrolled loop ahead.
// From https://github.com/RobThree/NUlid
if len(dst) != EncodedSize {
return ErrBufferSize
}
// 10 byte timestamp
dst[0] = Encoding[(id[0]&224)>>5]
dst[1] = Encoding[id[0]&31]
dst[2] = Encoding[(id[1]&248)>>3]
dst[3] = Encoding[((id[1]&7)<<2)|((id[2]&192)>>6)]
dst[4] = Encoding[(id[2]&62)>>1]
dst[5] = Encoding[((id[2]&1)<<4)|((id[3]&240)>>4)]
dst[6] = Encoding[((id[3]&15)<<1)|((id[4]&128)>>7)]
dst[7] = Encoding[(id[4]&124)>>2]
dst[8] = Encoding[((id[4]&3)<<3)|((id[5]&224)>>5)]
dst[9] = Encoding[id[5]&31]
// 16 bytes of entropy
dst[10] = Encoding[(id[6]&248)>>3]
dst[11] = Encoding[((id[6]&7)<<2)|((id[7]&192)>>6)]
dst[12] = Encoding[(id[7]&62)>>1]
dst[13] = Encoding[((id[7]&1)<<4)|((id[8]&240)>>4)]
dst[14] = Encoding[((id[8]&15)<<1)|((id[9]&128)>>7)]
dst[15] = Encoding[(id[9]&124)>>2]
dst[16] = Encoding[((id[9]&3)<<3)|((id[10]&224)>>5)]
dst[17] = Encoding[id[10]&31]
dst[18] = Encoding[(id[11]&248)>>3]
dst[19] = Encoding[((id[11]&7)<<2)|((id[12]&192)>>6)]
dst[20] = Encoding[(id[12]&62)>>1]
dst[21] = Encoding[((id[12]&1)<<4)|((id[13]&240)>>4)]
dst[22] = Encoding[((id[13]&15)<<1)|((id[14]&128)>>7)]
dst[23] = Encoding[(id[14]&124)>>2]
dst[24] = Encoding[((id[14]&3)<<3)|((id[15]&224)>>5)]
dst[25] = Encoding[id[15]&31]
return nil
}
// Byte to index table for O(1) lookups when unmarshaling.
// We use 0xFF as sentinel value for invalid indexes.
var dec = [...]byte{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x10, 0x11, 0xFF, 0x12, 0x13, 0xFF, 0x14, 0x15, 0xFF,
0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C, 0x1D, 0x1E,
0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C,
0x0D, 0x0E, 0x0F, 0x10, 0x11, 0xFF, 0x12, 0x13, 0xFF, 0x14,
0x15, 0xFF, 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C,
0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
}
// EncodedSize is the length of a text encoded ULID.
const EncodedSize = 26
// UnmarshalText implements the encoding.TextUnmarshaler interface by
// parsing the data as string encoded ULID.
//
// ErrDataSize is returned if the len(v) is different from an encoded
// ULID's length. Invalid encodings produce undefined ULIDs.
func (id *ULID) UnmarshalText(v []byte) error {
return parse(v, false, id)
}
// Time returns the Unix time in milliseconds encoded in the ULID.
// Use the top level Time function to convert the returned value to
// a time.Time.
func (id ULID) Time() uint64 {
return uint64(id[5]) | uint64(id[4])<<8 |
uint64(id[3])<<16 | uint64(id[2])<<24 |
uint64(id[1])<<32 | uint64(id[0])<<40
}
// Timestamp returns the time encoded in the ULID as a time.Time.
func (id ULID) Timestamp() time.Time {
return Time(id.Time())
}
// IsZero returns true if the ULID is a zero-value ULID, i.e. ulid.Zero.
func (id ULID) IsZero() bool {
return id.Compare(Zero) == 0
}
// maxTime is the maximum Unix time in milliseconds that can be
// represented in a ULID.
var maxTime = ULID{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}.Time()
// MaxTime returns the maximum Unix time in milliseconds that
// can be encoded in a ULID.
func MaxTime() uint64 { return maxTime }
// Now is a convenience function that returns the current
// UTC time in Unix milliseconds. Equivalent to:
//
// Timestamp(time.Now().UTC())
func Now() uint64 { return Timestamp(time.Now().UTC()) }
// Timestamp converts a time.Time to Unix milliseconds.
//
// Because of the way ULID stores time, times from the year
// 10889 produces undefined results.
func Timestamp(t time.Time) uint64 {
return uint64(t.Unix())*1000 +
uint64(t.Nanosecond()/int(time.Millisecond))
}
// Time converts Unix milliseconds in the format
// returned by the Timestamp function to a time.Time.
func Time(ms uint64) time.Time {
s := int64(ms / 1e3)
ns := int64((ms % 1e3) * 1e6)
return time.Unix(s, ns)
}
// SetTime sets the time component of the ULID to the given Unix time
// in milliseconds.
func (id *ULID) SetTime(ms uint64) error {
if ms > maxTime {
return ErrBigTime
}
(*id)[0] = byte(ms >> 40)
(*id)[1] = byte(ms >> 32)
(*id)[2] = byte(ms >> 24)
(*id)[3] = byte(ms >> 16)
(*id)[4] = byte(ms >> 8)
(*id)[5] = byte(ms)
return nil
}
// Entropy returns the entropy from the ULID.
func (id ULID) Entropy() []byte {
e := make([]byte, 10)
copy(e, id[6:])
return e
}
// SetEntropy sets the ULID entropy to the passed byte slice.
// ErrDataSize is returned if len(e) != 10.
func (id *ULID) SetEntropy(e []byte) error {
if len(e) != 10 {
return ErrDataSize
}
copy((*id)[6:], e)
return nil
}
// Compare returns an integer comparing id and other lexicographically.
// The result will be 0 if id==other, -1 if id < other, and +1 if id > other.
func (id ULID) Compare(other ULID) int {
return bytes.Compare(id[:], other[:])
}
// Scan implements the sql.Scanner interface. It supports scanning
// a string or byte slice.
func (id *ULID) Scan(src interface{}) error {
switch x := src.(type) {
case nil:
return nil
case string:
return id.UnmarshalText([]byte(x))
case []byte:
return id.UnmarshalBinary(x)
}
return ErrScanValue
}
// Value implements the sql/driver.Valuer interface, returning the ULID as a
// slice of bytes, by invoking MarshalBinary. If your use case requires a string
// representation instead, you can create a wrapper type that calls String()
// instead.
//
// type stringValuer ulid.ULID
//
// func (v stringValuer) Value() (driver.Value, error) {
// return ulid.ULID(v).String(), nil
// }
//
// // Example usage.
// db.Exec("...", stringValuer(id))
//
// All valid ULIDs, including zero-value ULIDs, return a valid Value with a nil
// error. If your use case requires zero-value ULIDs to return a non-nil error,
// you can create a wrapper type that special-cases this behavior.
//
// var zeroValueULID ulid.ULID
//
// type invalidZeroValuer ulid.ULID
//
// func (v invalidZeroValuer) Value() (driver.Value, error) {
// if ulid.ULID(v).Compare(zeroValueULID) == 0 {
// return nil, fmt.Errorf("zero value")
// }
// return ulid.ULID(v).Value()
// }
//
// // Example usage.
// db.Exec("...", invalidZeroValuer(id))
func (id ULID) Value() (driver.Value, error) {
return id.MarshalBinary()
}
// Monotonic returns a source of entropy that yields strictly increasing entropy
// bytes, to a limit governeed by the `inc` parameter.
//
// Specifically, calls to MonotonicRead within the same ULID timestamp return
// entropy incremented by a random number between 1 and `inc` inclusive. If an
// increment results in entropy that would overflow available space,
// MonotonicRead returns ErrMonotonicOverflow.
//
// Passing `inc == 0` results in the reasonable default `math.MaxUint32`. Lower
// values of `inc` provide more monotonic entropy in a single millisecond, at
// the cost of easier "guessability" of generated ULIDs. If your code depends on
// ULIDs having secure entropy bytes, then it's recommended to use the secure
// default value of `inc == 0`, unless you know what you're doing.
//
// The provided entropy source must actually yield random bytes. Otherwise,
// monotonic reads are not guaranteed to terminate, since there isn't enough
// randomness to compute an increment number.
//
// The returned type isn't safe for concurrent use.
func Monotonic(entropy io.Reader, inc uint64) *MonotonicEntropy {
m := MonotonicEntropy{
Reader: bufio.NewReader(entropy),
inc: inc,
}
if m.inc == 0 {
m.inc = math.MaxUint32
}
if rng, ok := entropy.(rng); ok {
m.rng = rng
}
return &m
}
type rng interface{ Int63n(n int64) int64 }
// LockedMonotonicReader wraps a MonotonicReader with a sync.Mutex for safe
// concurrent use.
type LockedMonotonicReader struct {
mu sync.Mutex
MonotonicReader
}
// MonotonicRead synchronizes calls to the wrapped MonotonicReader.
func (r *LockedMonotonicReader) MonotonicRead(ms uint64, p []byte) (err error) {
r.mu.Lock()
err = r.MonotonicReader.MonotonicRead(ms, p)
r.mu.Unlock()
return err
}
// MonotonicEntropy is an opaque type that provides monotonic entropy.
type MonotonicEntropy struct {
io.Reader
ms uint64
inc uint64
entropy uint80
rand [8]byte
rng rng
}
// MonotonicRead implements the MonotonicReader interface.
func (m *MonotonicEntropy) MonotonicRead(ms uint64, entropy []byte) (err error) {
if !m.entropy.IsZero() && m.ms == ms {
err = m.increment()
m.entropy.AppendTo(entropy)
} else if _, err = io.ReadFull(m.Reader, entropy); err == nil {
m.ms = ms
m.entropy.SetBytes(entropy)
}
return err
}
// increment the previous entropy number with a random number
// of up to m.inc (inclusive).
func (m *MonotonicEntropy) increment() error {
if inc, err := m.random(); err != nil {
return err
} else if m.entropy.Add(inc) {
return ErrMonotonicOverflow
}
return nil
}
// random returns a uniform random value in [1, m.inc), reading entropy
// from m.Reader. When m.inc == 0 || m.inc == 1, it returns 1.
// Adapted from: https://golang.org/pkg/crypto/rand/#Int
func (m *MonotonicEntropy) random() (inc uint64, err error) {
if m.inc <= 1 {
return 1, nil
}
// Fast path for using a underlying rand.Rand directly.
if m.rng != nil {
// Range: [1, m.inc)
return 1 + uint64(m.rng.Int63n(int64(m.inc))), nil
}
// bitLen is the maximum bit length needed to encode a value < m.inc.
bitLen := bits.Len64(m.inc)
// byteLen is the maximum byte length needed to encode a value < m.inc.
byteLen := uint(bitLen+7) / 8
// msbitLen is the number of bits in the most significant byte of m.inc-1.
msbitLen := uint(bitLen % 8)
if msbitLen == 0 {
msbitLen = 8
}
for inc == 0 || inc >= m.inc {
if _, err = io.ReadFull(m.Reader, m.rand[:byteLen]); err != nil {
return 0, err
}
// Clear bits in the first byte to increase the probability
// that the candidate is < m.inc.
m.rand[0] &= uint8(int(1<<msbitLen) - 1)
// Convert the read bytes into an uint64 with byteLen
// Optimized unrolled loop.
switch byteLen {
case 1:
inc = uint64(m.rand[0])
case 2:
inc = uint64(binary.LittleEndian.Uint16(m.rand[:2]))
case 3, 4:
inc = uint64(binary.LittleEndian.Uint32(m.rand[:4]))
case 5, 6, 7, 8:
inc = uint64(binary.LittleEndian.Uint64(m.rand[:8]))
}
}
// Range: [1, m.inc)
return 1 + inc, nil
}
type uint80 struct {
Hi uint16
Lo uint64
}
func (u *uint80) SetBytes(bs []byte) {
u.Hi = binary.BigEndian.Uint16(bs[:2])
u.Lo = binary.BigEndian.Uint64(bs[2:])
}
func (u *uint80) AppendTo(bs []byte) {
binary.BigEndian.PutUint16(bs[:2], u.Hi)
binary.BigEndian.PutUint64(bs[2:], u.Lo)
}
func (u *uint80) Add(n uint64) (overflow bool) {
lo, hi := u.Lo, u.Hi
if u.Lo += n; u.Lo < lo {
u.Hi++
}
return u.Hi < hi
}
func (u uint80) IsZero() bool {
return u.Hi == 0 && u.Lo == 0
}

902
3p/ulid/ulid_test.go Normal file
View File

@ -0,0 +1,902 @@
// Copyright 2016 The Oklog Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ulid_test
import (
"bytes"
crand "crypto/rand"
"fmt"
"io"
"math"
"math/rand"
"strings"
"testing"
"testing/iotest"
"testing/quick"
"time"
"github.com/therootcompany/golib/3p/ulid/v2"
)
func ExampleULID() {
t := time.Unix(1000000, 0)
entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0)
fmt.Println(ulid.MustNew(ulid.Timestamp(t), entropy))
// Output: 0000XSNJG0MQJHBF4QX1EFD6Y3
}
func TestNew(t *testing.T) {
t.Parallel()
t.Run("ULID", testULID(func(ms uint64, e io.Reader) ulid.ULID {
id, err := ulid.New(ms, e)
if err != nil {
t.Fatal(err)
}
return id
}))
t.Run("Error", func(t *testing.T) {
_, err := ulid.New(ulid.MaxTime()+1, nil)
if got, want := err, ulid.ErrBigTime; got != want {
t.Errorf("got err %v, want %v", got, want)
}
_, err = ulid.New(0, strings.NewReader(""))
if got, want := err, io.EOF; got != want {
t.Errorf("got err %v, want %v", got, want)
}
})
}
func TestMake(t *testing.T) {
t.Parallel()
id := ulid.Make()
rt, err := ulid.Parse(id.String())
if err != nil {
t.Fatalf("parse %q: %v", id.String(), err)
}
if id != rt {
t.Fatalf("%q != %q", id.String(), rt.String())
}
}
func TestMustNew(t *testing.T) {
t.Parallel()
t.Run("ULID", testULID(ulid.MustNew))
t.Run("Panic", func(t *testing.T) {
defer func() {
if got, want := recover(), io.EOF; got != want {
t.Errorf("panic with err %v, want %v", got, want)
}
}()
_ = ulid.MustNew(0, strings.NewReader(""))
})
}
func TestMustNewDefault(t *testing.T) {
t.Parallel()
t.Run("ULID", func(t *testing.T) {
id := ulid.MustNewDefault(time.Now())
rt, err := ulid.Parse(id.String())
if err != nil {
t.Fatalf("parse %q: %v", id.String(), err)
}
if id != rt {
t.Fatalf("%q != %q", id.String(), rt.String())
}
})
t.Run("Panic", func(t *testing.T) {
defer func() {
if got, want := recover(), ulid.ErrBigTime; got != want {
t.Errorf("got panic %v, want %v", got, want)
}
}()
_ = ulid.MustNewDefault(time.Time{})
})
}
func TestMustParse(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
fn func(string) ulid.ULID
}{
{"MustParse", ulid.MustParse},
{"MustParseStrict", ulid.MustParseStrict},
} {
t.Run(tc.name, func(t *testing.T) {
defer func() {
if got, want := recover(), ulid.ErrDataSize; got != want {
t.Errorf("got panic %v, want %v", got, want)
}
}()
_ = tc.fn("")
})
}
}
func testULID(mk func(uint64, io.Reader) ulid.ULID) func(*testing.T) {
return func(t *testing.T) {
want := ulid.ULID{0x0, 0x0, 0x0, 0x1, 0x86, 0xa0}
if got := mk(1e5, nil); got != want { // optional entropy
t.Errorf("\ngot %#v\nwant %#v", got, want)
}
entropy := bytes.Repeat([]byte{0xFF}, 16)
copy(want[6:], entropy)
if got := mk(1e5, bytes.NewReader(entropy)); got != want {
t.Errorf("\ngot %#v\nwant %#v", got, want)
}
}
}
func TestRoundTrips(t *testing.T) {
t.Parallel()
prop := func(id ulid.ULID) bool {
bin, err := id.MarshalBinary()
if err != nil {
t.Fatal(err)
}
txt, err := id.MarshalText()
if err != nil {
t.Fatal(err)
}
var a ulid.ULID
if err = a.UnmarshalBinary(bin); err != nil {
t.Fatal(err)
}
var b ulid.ULID
if err = b.UnmarshalText(txt); err != nil {
t.Fatal(err)
}
return id == a && b == id &&
id == ulid.MustParse(id.String()) &&
id == ulid.MustParseStrict(id.String())
}
err := quick.Check(prop, &quick.Config{MaxCount: 1e5})
if err != nil {
t.Fatal(err)
}
}
func TestMarshalingErrors(t *testing.T) {
t.Parallel()
var id ulid.ULID
for _, tc := range []struct {
name string
fn func([]byte) error
err error
}{
{"UnmarshalBinary", id.UnmarshalBinary, ulid.ErrDataSize},
{"UnmarshalText", id.UnmarshalText, ulid.ErrDataSize},
{"MarshalBinaryTo", id.MarshalBinaryTo, ulid.ErrBufferSize},
{"MarshalTextTo", id.MarshalTextTo, ulid.ErrBufferSize},
} {
t.Run(tc.name, func(t *testing.T) {
if got, want := tc.fn([]byte{}), tc.err; got != want {
t.Errorf("got err %v, want %v", got, want)
}
})
}
}
func TestParseStrictInvalidCharacters(t *testing.T) {
t.Parallel()
type testCase struct {
name string
input string
}
testCases := []testCase{}
base := "0000XSNJG0MQJHBF4QX1EFD6Y3"
for i := 0; i < ulid.EncodedSize; i++ {
testCases = append(testCases, testCase{
name: fmt.Sprintf("Invalid 0xFF at index %d", i),
input: base[:i] + "\xff" + base[i+1:],
})
testCases = append(testCases, testCase{
name: fmt.Sprintf("Invalid 0x00 at index %d", i),
input: base[:i] + "\x00" + base[i+1:],
})
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
_, err := ulid.ParseStrict(tt.input)
if err != ulid.ErrInvalidCharacters {
t.Errorf("Parse(%q): got err %v, want %v", tt.input, err, ulid.ErrInvalidCharacters)
}
})
}
}
func TestAlizainCompatibility(t *testing.T) {
t.Parallel()
ts := uint64(1469918176385)
got := ulid.MustNew(ts, bytes.NewReader(make([]byte, 16)))
want := ulid.MustParse("01ARYZ6S410000000000000000")
if got != want {
t.Fatalf("with time=%d, got %q, want %q", ts, got, want)
}
}
func TestEncoding(t *testing.T) {
t.Parallel()
enc := make(map[rune]bool, len(ulid.Encoding))
for _, r := range ulid.Encoding {
enc[r] = true
}
prop := func(id ulid.ULID) bool {
for _, r := range id.String() {
if !enc[r] {
return false
}
}
return true
}
if err := quick.Check(prop, &quick.Config{MaxCount: 1e5}); err != nil {
t.Fatal(err)
}
}
func TestLexicographicalOrder(t *testing.T) {
t.Parallel()
prop := func(a, b ulid.ULID) bool {
t1, t2 := a.Time(), b.Time()
s1, s2 := a.String(), b.String()
ord := bytes.Compare(a[:], b[:])
return t1 == t2 ||
(t1 > t2 && s1 > s2 && ord == +1) ||
(t1 < t2 && s1 < s2 && ord == -1)
}
top := ulid.MustNew(ulid.MaxTime(), nil)
for i := 0; i < 10; i++ { // test upper boundary state space
next := ulid.MustNew(top.Time()-1, nil)
if !prop(top, next) {
t.Fatalf("bad lexicographical order: (%v, %q) > (%v, %q) == false",
top.Time(), top,
next.Time(), next,
)
}
top = next
}
if err := quick.Check(prop, &quick.Config{MaxCount: 1e6}); err != nil {
t.Fatal(err)
}
}
func TestCaseInsensitivity(t *testing.T) {
t.Parallel()
upper := func(id ulid.ULID) (out ulid.ULID) {
return ulid.MustParse(strings.ToUpper(id.String()))
}
lower := func(id ulid.ULID) (out ulid.ULID) {
return ulid.MustParse(strings.ToLower(id.String()))
}
err := quick.CheckEqual(upper, lower, nil)
if err != nil {
t.Fatal(err)
}
}
func TestParseRobustness(t *testing.T) {
t.Parallel()
cases := [][]byte{
{0x1, 0xc0, 0x73, 0x62, 0x4a, 0xaf, 0x39, 0x78, 0x51, 0x4e, 0xf8, 0x44, 0x3b,
0xb2, 0xa8, 0x59, 0xc7, 0x5f, 0xc3, 0xcc, 0x6a, 0xf2, 0x6d, 0x5a, 0xaa, 0x20},
}
for _, tc := range cases {
if _, err := ulid.Parse(string(tc)); err != nil {
t.Error(err)
}
}
prop := func(s [26]byte) (ok bool) {
defer func() {
if err := recover(); err != nil {
t.Error(err)
ok = false
}
}()
// quick.Check doesn't constrain input,
// so we need to do so artificially.
if s[0] > '7' {
s[0] %= '7'
}
var err error
if _, err = ulid.Parse(string(s[:])); err != nil {
t.Error(err)
}
return err == nil
}
err := quick.Check(prop, &quick.Config{MaxCount: 1e4})
if err != nil {
t.Fatal(err)
}
}
func TestNow(t *testing.T) {
t.Parallel()
before := ulid.Now()
after := ulid.Timestamp(time.Now().UTC().Add(time.Millisecond))
if before >= after {
t.Fatalf("clock went mad: before %v, after %v", before, after)
}
}
func TestTimestamp(t *testing.T) {
t.Parallel()
tm := time.Unix(1, 1000) // will be truncated
if got, want := ulid.Timestamp(tm), uint64(1000); got != want {
t.Errorf("for %v, got %v, want %v", tm, got, want)
}
mt := ulid.MaxTime()
dt := time.Unix(int64(mt/1000), int64((mt%1000)*1000000)).Truncate(time.Millisecond)
ts := ulid.Timestamp(dt)
if got, want := ts, mt; got != want {
t.Errorf("got timestamp %d, want %d", got, want)
}
}
func TestTime(t *testing.T) {
t.Parallel()
original := time.Now()
diff := original.Sub(ulid.Time(ulid.Timestamp(original)))
if diff >= time.Millisecond {
t.Errorf("difference between original and recovered time (%d) greater"+
"than a millisecond", diff)
}
}
func TestTimestampRoundTrips(t *testing.T) {
t.Parallel()
prop := func(ts uint64) bool {
return ts == ulid.Timestamp(ulid.Time(ts))
}
err := quick.Check(prop, &quick.Config{MaxCount: 1e5})
if err != nil {
t.Fatal(err)
}
}
func TestULIDTime(t *testing.T) {
t.Parallel()
maxTime := ulid.MaxTime()
var id ulid.ULID
if got, want := id.SetTime(maxTime+1), ulid.ErrBigTime; got != want {
t.Errorf("got err %v, want %v", got, want)
}
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < 1e6; i++ {
ms := uint64(rng.Int63n(int64(maxTime)))
var id ulid.ULID
if err := id.SetTime(ms); err != nil {
t.Fatal(err)
}
if got, want := id.Time(), ms; got != want {
t.Fatalf("\nfor %v:\ngot %v\nwant %v", id, got, want)
}
}
}
func TestULIDTimestamp(t *testing.T) {
t.Parallel()
{
id := ulid.Make()
ts := id.Timestamp()
tt := ulid.Time(id.Time())
if ts != tt {
t.Errorf("id.Timestamp() %s != ulid.Time(id.Time()) %s", ts, tt)
}
}
{
now := time.Now()
id := ulid.MustNew(ulid.Timestamp(now), ulid.DefaultEntropy())
if want, have := now.Truncate(time.Millisecond), id.Timestamp(); want != have {
t.Errorf("Timestamp: want %v, have %v", want, have)
}
}
}
func TestZero(t *testing.T) {
t.Parallel()
var id ulid.ULID
if ok := id.IsZero(); !ok {
t.Error(".IsZero: must return true for zero-value ULIDs, have false")
}
id = ulid.MustNew(ulid.Now(), ulid.DefaultEntropy())
if ok := id.IsZero(); ok {
t.Error(".IsZero: must return false for non-zero-value ULIDs, have true")
}
}
func TestEntropy(t *testing.T) {
t.Parallel()
var id ulid.ULID
if got, want := id.SetEntropy([]byte{}), ulid.ErrDataSize; got != want {
t.Errorf("got err %v, want %v", got, want)
}
prop := func(e [10]byte) bool {
var id ulid.ULID
if err := id.SetEntropy(e[:]); err != nil {
t.Fatalf("got err %v", err)
}
got, want := id.Entropy(), e[:]
eq := bytes.Equal(got, want)
if !eq {
t.Errorf("\n(!= %v\n %v)", got, want)
}
return eq
}
if err := quick.Check(prop, nil); err != nil {
t.Fatal(err)
}
}
func TestEntropyRead(t *testing.T) {
t.Parallel()
prop := func(e [10]byte) bool {
flakyReader := iotest.HalfReader(bytes.NewReader(e[:]))
id, err := ulid.New(ulid.Now(), flakyReader)
if err != nil {
t.Fatalf("got err %v", err)
}
got, want := id.Entropy(), e[:]
eq := bytes.Equal(got, want)
if !eq {
t.Errorf("\n(!= %v\n %v)", got, want)
}
return eq
}
if err := quick.Check(prop, &quick.Config{MaxCount: 1e4}); err != nil {
t.Fatal(err)
}
}
func TestCompare(t *testing.T) {
t.Parallel()
a := func(a, b ulid.ULID) int {
return strings.Compare(a.String(), b.String())
}
b := func(a, b ulid.ULID) int {
return a.Compare(b)
}
err := quick.CheckEqual(a, b, &quick.Config{MaxCount: 1e5})
if err != nil {
t.Error(err)
}
}
func TestOverflowHandling(t *testing.T) {
t.Parallel()
for s, want := range map[string]error{
"00000000000000000000000000": nil,
"70000000000000000000000000": nil,
"7ZZZZZZZZZZZZZZZZZZZZZZZZZ": nil,
"80000000000000000000000000": ulid.ErrOverflow,
"80000000000000000000000001": ulid.ErrOverflow,
"ZZZZZZZZZZZZZZZZZZZZZZZZZZ": ulid.ErrOverflow,
} {
if _, have := ulid.Parse(s); want != have {
t.Errorf("%s: want error %v, have %v", s, want, have)
}
}
}
func TestScan(t *testing.T) {
id := ulid.MustNew(123, crand.Reader)
for _, tc := range []struct {
name string
in interface{}
out ulid.ULID
err error
}{
{"string", id.String(), id, nil},
{"bytes", id[:], id, nil},
{"nil", nil, ulid.ULID{}, nil},
{"other", 44, ulid.ULID{}, ulid.ErrScanValue},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var out ulid.ULID
err := out.Scan(tc.in)
if got, want := out, tc.out; got.Compare(want) != 0 {
t.Errorf("got ULID %s, want %s", got, want)
}
if got, want := fmt.Sprint(err), fmt.Sprint(tc.err); got != want {
t.Errorf("got err %q, want %q", got, want)
}
})
}
}
func TestMonotonic(t *testing.T) {
now := ulid.Now()
for _, e := range []struct {
name string
mk func() io.Reader
}{
{"cryptorand", func() io.Reader { return crand.Reader }},
{"mathrand", func() io.Reader { return rand.New(rand.NewSource(int64(now))) }},
} {
for _, inc := range []uint64{
0,
1,
2,
math.MaxUint8 + 1,
math.MaxUint16 + 1,
math.MaxUint32 + 1,
} {
inc := inc
entropy := ulid.Monotonic(e.mk(), uint64(inc))
t.Run(fmt.Sprintf("entropy=%s/inc=%d", e.name, inc), func(t *testing.T) {
t.Parallel()
var prev ulid.ULID
for i := 0; i < 10000; i++ {
next, err := ulid.New(123, entropy)
if err != nil {
t.Fatal(err)
}
if prev.Compare(next) >= 0 {
t.Fatalf("prev: %v %v > next: %v %v",
prev.Time(), prev.Entropy(), next.Time(), next.Entropy())
}
prev = next
}
})
}
}
}
func TestMonotonicOverflow(t *testing.T) {
t.Parallel()
entropy := ulid.Monotonic(
io.MultiReader(
bytes.NewReader(bytes.Repeat([]byte{0xFF}, 10)), // Entropy for first ULID
crand.Reader, // Following random entropy
),
0,
)
prev, err := ulid.New(0, entropy)
if err != nil {
t.Fatal(err)
}
next, err := ulid.New(prev.Time(), entropy)
if have, want := err, ulid.ErrMonotonicOverflow; have != want {
t.Errorf("have ulid: %v %v err: %v, want err: %v",
next.Time(), next.Entropy(), have, want)
}
}
func TestMonotonicSafe(t *testing.T) {
t.Parallel()
var (
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
safe = &ulid.LockedMonotonicReader{MonotonicReader: ulid.Monotonic(rng, 0)}
t0 = ulid.Timestamp(time.Now())
)
errs := make(chan error, 100)
for i := 0; i < cap(errs); i++ {
go func() {
u0 := ulid.MustNew(t0, safe)
u1 := u0
for j := 0; j < 1024; j++ {
u0, u1 = u1, ulid.MustNew(t0, safe)
if u0.String() >= u1.String() {
errs <- fmt.Errorf(
"%s (%d %x) >= %s (%d %x)",
u0.String(), u0.Time(), u0.Entropy(),
u1.String(), u1.Time(), u1.Entropy(),
)
return
}
}
errs <- nil
}()
}
for i := 0; i < cap(errs); i++ {
if err := <-errs; err != nil {
t.Fatal(err)
}
}
}
func TestULID_Bytes(t *testing.T) {
tt := time.Unix(1000000, 0)
entropy := ulid.Monotonic(rand.New(rand.NewSource(tt.UnixNano())), 0)
id := ulid.MustNew(ulid.Timestamp(tt), entropy)
bid := id.Bytes()
bid[len(bid)-1]++
if bytes.Equal(id.Bytes(), bid) {
t.Error("Bytes() returned a reference to ulid underlying array!")
}
}
func BenchmarkNew(b *testing.B) {
benchmarkMakeULID(b, func(timestamp uint64, entropy io.Reader) {
_, _ = ulid.New(timestamp, entropy)
})
}
func BenchmarkMustNew(b *testing.B) {
benchmarkMakeULID(b, func(timestamp uint64, entropy io.Reader) {
_ = ulid.MustNew(timestamp, entropy)
})
}
func benchmarkMakeULID(b *testing.B, f func(uint64, io.Reader)) {
b.ReportAllocs()
b.SetBytes(int64(len(ulid.ULID{})))
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
for _, tc := range []struct {
name string
timestamps []uint64
entropy io.Reader
}{
{"WithCrypoEntropy", []uint64{123}, crand.Reader},
{"WithEntropy", []uint64{123}, rng},
{"WithMonotonicEntropy_SameTimestamp_Inc0", []uint64{123}, ulid.Monotonic(rng, 0)},
{"WithMonotonicEntropy_DifferentTimestamp_Inc0", []uint64{122, 123}, ulid.Monotonic(rng, 0)},
{"WithMonotonicEntropy_SameTimestamp_Inc1", []uint64{123}, ulid.Monotonic(rng, 1)},
{"WithMonotonicEntropy_DifferentTimestamp_Inc1", []uint64{122, 123}, ulid.Monotonic(rng, 1)},
{"WithCryptoMonotonicEntropy_SameTimestamp_Inc1", []uint64{123}, ulid.Monotonic(crand.Reader, 1)},
{"WithCryptoMonotonicEntropy_DifferentTimestamp_Inc1", []uint64{122, 123}, ulid.Monotonic(crand.Reader, 1)},
{"WithoutEntropy", []uint64{123}, nil},
} {
tc := tc
b.Run(tc.name, func(b *testing.B) {
b.StopTimer()
b.ResetTimer()
b.StartTimer()
for i := 0; i < b.N; i++ {
f(tc.timestamps[i%len(tc.timestamps)], tc.entropy)
}
})
}
}
func BenchmarkParse(b *testing.B) {
const s = "0000XSNJG0MQJHBF4QX1EFD6Y3"
b.SetBytes(int64(len(s)))
for i := 0; i < b.N; i++ {
_, _ = ulid.Parse(s)
}
}
func BenchmarkParseStrict(b *testing.B) {
const s = "0000XSNJG0MQJHBF4QX1EFD6Y3"
b.SetBytes(int64(len(s)))
for i := 0; i < b.N; i++ {
_, _ = ulid.ParseStrict(s)
}
}
func BenchmarkMustParse(b *testing.B) {
const s = "0000XSNJG0MQJHBF4QX1EFD6Y3"
b.SetBytes(int64(len(s)))
for i := 0; i < b.N; i++ {
_ = ulid.MustParse(s)
}
}
func BenchmarkString(b *testing.B) {
entropy := rand.New(rand.NewSource(time.Now().UnixNano()))
id := ulid.MustNew(123456, entropy)
b.SetBytes(int64(len(id)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = id.String()
}
}
func BenchmarkMarshal(b *testing.B) {
entropy := rand.New(rand.NewSource(time.Now().UnixNano()))
buf := make([]byte, ulid.EncodedSize)
id := ulid.MustNew(123456, entropy)
b.Run("Text", func(b *testing.B) {
b.SetBytes(int64(len(id)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = id.MarshalText()
}
})
b.Run("TextTo", func(b *testing.B) {
b.SetBytes(int64(len(id)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = id.MarshalTextTo(buf)
}
})
b.Run("Binary", func(b *testing.B) {
b.SetBytes(int64(len(id)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = id.MarshalBinary()
}
})
b.Run("BinaryTo", func(b *testing.B) {
b.SetBytes(int64(len(id)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = id.MarshalBinaryTo(buf)
}
})
}
func BenchmarkUnmarshal(b *testing.B) {
var id ulid.ULID
s := "0000XSNJG0MQJHBF4QX1EFD6Y3"
txt := []byte(s)
bin, _ := ulid.MustParse(s).MarshalBinary()
b.Run("Text", func(b *testing.B) {
b.SetBytes(int64(len(txt)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = id.UnmarshalText(txt)
}
})
b.Run("Binary", func(b *testing.B) {
b.SetBytes(int64(len(bin)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = id.UnmarshalBinary(bin)
}
})
}
func BenchmarkNow(b *testing.B) {
b.SetBytes(8)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ulid.Now()
}
}
func BenchmarkTimestamp(b *testing.B) {
now := time.Now()
b.SetBytes(8)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ulid.Timestamp(now)
}
}
func BenchmarkTime(b *testing.B) {
id := ulid.MustNew(123456789, nil)
b.SetBytes(8)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = id.Time()
}
}
func BenchmarkSetTime(b *testing.B) {
var id ulid.ULID
b.SetBytes(8)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = id.SetTime(123456789)
}
}
func BenchmarkEntropy(b *testing.B) {
id := ulid.MustNew(0, strings.NewReader("ABCDEFGHIJKLMNOP"))
b.SetBytes(10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = id.Entropy()
}
}
func BenchmarkSetEntropy(b *testing.B) {
var id ulid.ULID
e := []byte("ABCDEFGHIJKLMNOP")
b.SetBytes(10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = id.SetEntropy(e)
}
}
func BenchmarkCompare(b *testing.B) {
id, other := ulid.MustNew(12345, nil), ulid.MustNew(54321, nil)
b.SetBytes(int64(len(id) * 2))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = id.Compare(other)
}
}