Skip to content

Commit 04794ee

Browse files
committed
port original code
1 parent 0d3a45d commit 04794ee

File tree

5 files changed

+760
-0
lines changed

5 files changed

+760
-0
lines changed

entropy.go

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package ulid
2+
3+
import (
4+
"bufio"
5+
"encoding/binary"
6+
"io"
7+
"math"
8+
"math/bits"
9+
"math/rand"
10+
"sync"
11+
"time"
12+
)
13+
14+
var defaultEntropy = func() io.Reader {
15+
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
16+
return &LockedMonotonicReader{MonotonicReader: Monotonic(rng, 0)}
17+
}()
18+
19+
// DefaultEntropy returns a thread-safe per process monotonically increasing
20+
// entropy source.
21+
func DefaultEntropy() io.Reader {
22+
return defaultEntropy
23+
}
24+
25+
// MonotonicReader is an interface that should yield monotonically increasing
26+
// entropy into the provided slice for all calls with the same ms parameter. If
27+
// a MonotonicReader is provided to the New constructor, its MonotonicRead
28+
// method will be used instead of Read.
29+
type MonotonicReader interface {
30+
io.Reader
31+
MonotonicRead(ms uint64, p []byte) error
32+
}
33+
34+
// Monotonic returns a source of entropy that yields strictly increasing entropy
35+
// bytes, to a limit governeed by the `inc` parameter.
36+
//
37+
// Specifically, calls to MonotonicRead within the same ULID timestamp return
38+
// entropy incremented by a random number between 1 and `inc` inclusive. If an
39+
// increment results in entropy that would overflow available space,
40+
// MonotonicRead returns ErrMonotonicOverflow.
41+
//
42+
// Passing `inc == 0` results in the reasonable default `math.MaxUint32`. Lower
43+
// values of `inc` provide more monotonic entropy in a single millisecond, at
44+
// the cost of easier "guessability" of generated ULIDs. If your code depends on
45+
// ULIDs having secure entropy bytes, then it's recommended to use the secure
46+
// default value of `inc == 0`, unless you know what you're doing.
47+
//
48+
// The provided entropy source must actually yield random bytes. Otherwise,
49+
// monotonic reads are not guaranteed to terminate, since there isn't enough
50+
// randomness to compute an increment number.
51+
//
52+
// The returned type isn't safe for concurrent use.
53+
func Monotonic(entropy io.Reader, inc uint64) *MonotonicEntropy {
54+
m := MonotonicEntropy{
55+
Reader: bufio.NewReader(entropy),
56+
inc: inc,
57+
}
58+
59+
if m.inc == 0 {
60+
m.inc = math.MaxUint32
61+
}
62+
63+
if rng, ok := entropy.(rng); ok {
64+
m.rng = rng
65+
}
66+
67+
return &m
68+
}
69+
70+
type rng interface{ Int63n(n int64) int64 }
71+
72+
// LockedMonotonicReader wraps a MonotonicReader with a sync.Mutex for safe
73+
// concurrent use.
74+
type LockedMonotonicReader struct {
75+
mu sync.Mutex
76+
MonotonicReader
77+
}
78+
79+
// MonotonicRead synchronizes calls to the wrapped MonotonicReader.
80+
func (r *LockedMonotonicReader) MonotonicRead(ms uint64, p []byte) (err error) {
81+
r.mu.Lock()
82+
err = r.MonotonicReader.MonotonicRead(ms, p)
83+
r.mu.Unlock()
84+
return err
85+
}
86+
87+
// MonotonicEntropy is an opaque type that provides monotonic entropy.
88+
type MonotonicEntropy struct {
89+
io.Reader
90+
ms uint64
91+
inc uint64
92+
entropy uint80
93+
rand [8]byte
94+
rng rng
95+
}
96+
97+
// MonotonicRead implements the MonotonicReader interface.
98+
func (m *MonotonicEntropy) MonotonicRead(ms uint64, entropy []byte) (err error) {
99+
if !m.entropy.IsZero() && m.ms == ms {
100+
err = m.increment()
101+
m.entropy.AppendTo(entropy)
102+
} else if _, err = io.ReadFull(m.Reader, entropy); err == nil {
103+
m.ms = ms
104+
m.entropy.SetBytes(entropy)
105+
}
106+
return err
107+
}
108+
109+
// increment the previous entropy number with a random number
110+
// of up to m.inc (inclusive).
111+
func (m *MonotonicEntropy) increment() error {
112+
if inc, err := m.random(); err != nil {
113+
return err
114+
} else if m.entropy.Add(inc) {
115+
return ErrMonotonicOverflow
116+
}
117+
return nil
118+
}
119+
120+
// random returns a uniform random value in [1, m.inc), reading entropy
121+
// from m.Reader. When m.inc == 0 || m.inc == 1, it returns 1.
122+
// Adapted from: https://golang.org/pkg/crypto/rand/#Int
123+
func (m *MonotonicEntropy) random() (inc uint64, err error) {
124+
if m.inc <= 1 {
125+
return 1, nil
126+
}
127+
128+
// Fast path for using a underlying rand.Rand directly.
129+
if m.rng != nil {
130+
// Range: [1, m.inc)
131+
return 1 + uint64(m.rng.Int63n(int64(m.inc))), nil
132+
}
133+
134+
// bitLen is the maximum bit length needed to encode a value < m.inc.
135+
bitLen := bits.Len64(m.inc)
136+
137+
// byteLen is the maximum byte length needed to encode a value < m.inc.
138+
byteLen := uint(bitLen+7) / 8
139+
140+
// msbitLen is the number of bits in the most significant byte of m.inc-1.
141+
msbitLen := uint(bitLen % 8)
142+
if msbitLen == 0 {
143+
msbitLen = 8
144+
}
145+
146+
for inc == 0 || inc >= m.inc {
147+
if _, err = io.ReadFull(m.Reader, m.rand[:byteLen]); err != nil {
148+
return 0, err
149+
}
150+
151+
// Clear bits in the first byte to increase the probability
152+
// that the candidate is < m.inc.
153+
m.rand[0] &= uint8(int(1<<msbitLen) - 1)
154+
155+
// Convert the read bytes into an uint64 with byteLen
156+
// Optimized unrolled loop.
157+
switch byteLen {
158+
case 1:
159+
inc = uint64(m.rand[0])
160+
case 2:
161+
inc = uint64(binary.LittleEndian.Uint16(m.rand[:2]))
162+
case 3, 4:
163+
inc = uint64(binary.LittleEndian.Uint32(m.rand[:4]))
164+
case 5, 6, 7, 8:
165+
inc = uint64(binary.LittleEndian.Uint64(m.rand[:8]))
166+
}
167+
}
168+
169+
// Range: [1, m.inc)
170+
return 1 + inc, nil
171+
}

errors.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package ulid
2+
3+
import "errors"
4+
5+
var (
6+
// Occurs when parsing or unmarshaling ULIDs with the wrong number of bytes.
7+
ErrDataSize = errors.New("ulid: bad data size when unmarshaling")
8+
9+
// Occurs when parsing or unmarshaling ULIDs with invalid Base32 encodings.
10+
ErrInvalidCharacters = errors.New("ulid: bad data characters when unmarshaling")
11+
12+
// Occurs when marshalling ULIDs to a buffer of insufficient size.
13+
ErrBufferSize = errors.New("ulid: bad buffer size when marshaling")
14+
15+
// Occurs when constructing a ULID with a time that is larger than MaxTime.
16+
ErrBigTime = errors.New("ulid: time too big")
17+
18+
// Occurs when unmarshaling a ULID whose first character is
19+
// larger than 7, thereby exceeding the valid bit depth of 128.
20+
ErrOverflow = errors.New("ulid: overflow when unmarshaling")
21+
22+
// Returned by a Monotonic entropy source when incrementing the previous ULID's
23+
// entropy bytes would result in overflow.
24+
ErrMonotonicOverflow = errors.New("ulid: monotonic entropy overflow")
25+
26+
// Occurs when the value passed to scan cannot be unmarshaled into the ULID.
27+
ErrScanValue = errors.New("ulid: source value must be a string or byte slice")
28+
)

numeric.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package ulid
2+
3+
import "encoding/binary"
4+
5+
type uint80 struct {
6+
Hi uint16
7+
Lo uint64
8+
}
9+
10+
func (u *uint80) SetBytes(bs []byte) {
11+
u.Hi = binary.BigEndian.Uint16(bs[:2])
12+
u.Lo = binary.BigEndian.Uint64(bs[2:])
13+
}
14+
15+
func (u *uint80) AppendTo(bs []byte) {
16+
binary.BigEndian.PutUint16(bs[:2], u.Hi)
17+
binary.BigEndian.PutUint64(bs[2:], u.Lo)
18+
}
19+
20+
func (u *uint80) Add(n uint64) (overflow bool) {
21+
lo, hi := u.Lo, u.Hi
22+
if u.Lo += n; u.Lo < lo {
23+
u.Hi++
24+
}
25+
return u.Hi < hi
26+
}
27+
28+
func (u uint80) IsZero() bool {
29+
return u.Hi == 0 && u.Lo == 0
30+
}

time.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package ulid
2+
3+
import "time"
4+
5+
// maxTime is the maximum Unix time in milliseconds that can be
6+
// represented in a ULID.
7+
var maxTime = ULID{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}.Time()
8+
9+
// MaxTime returns the maximum Unix time in milliseconds that
10+
// can be encoded in a ULID.
11+
func MaxTime() uint64 { return maxTime }
12+
13+
// Now is a convenience function that returns the current
14+
// UTC time in Unix milliseconds. Equivalent to:
15+
//
16+
// Timestamp(time.Now().UTC())
17+
func Now() uint64 { return Timestamp(time.Now().UTC()) }
18+
19+
// Timestamp converts a time.Time to Unix milliseconds.
20+
//
21+
// Because of the way ULID stores time, times from the year
22+
// 10889 produces undefined results.
23+
func Timestamp(t time.Time) uint64 {
24+
return uint64(t.Unix())*1000 +
25+
uint64(t.Nanosecond()/int(time.Millisecond))
26+
}
27+
28+
// Time converts Unix milliseconds in the format
29+
// returned by the Timestamp function to a time.Time.
30+
func Time(ms uint64) time.Time {
31+
s := int64(ms / 1e3)
32+
ns := int64((ms % 1e3) * 1e6)
33+
return time.Unix(s, ns)
34+
}

0 commit comments

Comments
 (0)