|
1 | 1 | package utils |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "crypto/md5" |
| 5 | + "crypto/sha1" |
| 6 | + "encoding/binary" |
| 7 | + "sync" |
| 8 | + "time" |
| 9 | + "unsafe" |
| 10 | + |
4 | 11 | "github.com/gofrs/uuid/v5" |
5 | 12 | "github.com/metacubex/randv2" |
6 | 13 | ) |
7 | 14 |
|
8 | | -type unsafeRandReader struct{} |
9 | | - |
10 | | -func (r unsafeRandReader) Read(p []byte) (n int, err error) { |
11 | | - // modify from https://github.com/golang/go/blob/587c3847da81aa7cfc3b3db2677c8586c94df13a/src/runtime/rand.go#L70-L89 |
12 | | - // Inspired by wyrand. |
13 | | - n = len(p) |
14 | | - v := randv2.Uint64() |
| 15 | +func UnsafeRandRead(p []byte) { |
15 | 16 | for len(p) > 0 { |
16 | | - v ^= 0xa0761d6478bd642f |
17 | | - v *= 0xe7037ed1a0b428db |
18 | | - size := 8 |
19 | | - if len(p) < 8 { |
20 | | - size = len(p) |
| 17 | + v := randv2.Uint64() |
| 18 | + if v == 0 { |
| 19 | + continue |
21 | 20 | } |
22 | | - for i := 0; i < size; i++ { |
23 | | - p[i] ^= byte(v >> (8 * i)) |
24 | | - } |
25 | | - p = p[size:] |
26 | | - v = v>>32 | v<<32 |
| 21 | + i := copy(p, (*[8]byte)(unsafe.Pointer(&v))[:]) |
| 22 | + p = p[i:] |
27 | 23 | } |
| 24 | +} |
28 | 25 |
|
29 | | - return |
| 26 | +type unsafeRandReader struct{} |
| 27 | + |
| 28 | +func (r *unsafeRandReader) Read(p []byte) (n int, err error) { |
| 29 | + UnsafeRandRead(p) |
| 30 | + return len(p), nil |
30 | 31 | } |
31 | 32 |
|
32 | | -var UnsafeRandReader = unsafeRandReader{} |
| 33 | +var UnsafeRandReader = (*unsafeRandReader)(nil) |
33 | 34 |
|
34 | | -var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(UnsafeRandReader)) |
| 35 | +// NewUUIDV3 returns a UUID based on the MD5 hash of the namespace UUID and name. |
| 36 | +func NewUUIDV3(ns uuid.UUID, name string) (u uuid.UUID) { |
| 37 | + h := md5.New() |
| 38 | + h.Write(ns[:]) |
| 39 | + h.Write([]byte(name)) |
| 40 | + copy(u[:], h.Sum(make([]byte, 0, md5.Size))) |
35 | 41 |
|
36 | | -func NewUUIDV1() uuid.UUID { |
37 | | - u, _ := UnsafeUUIDGenerator.NewV1() // unsafeRandReader wouldn't cause error, so ignore err is safe |
| 42 | + u.SetVersion(uuid.V3) |
| 43 | + u.SetVariant(uuid.VariantRFC9562) |
38 | 44 | return u |
39 | 45 | } |
40 | 46 |
|
41 | | -func NewUUIDV3(ns uuid.UUID, name string) uuid.UUID { |
42 | | - return UnsafeUUIDGenerator.NewV3(ns, name) |
43 | | -} |
44 | | - |
45 | | -func NewUUIDV4() uuid.UUID { |
46 | | - u, _ := UnsafeUUIDGenerator.NewV4() // unsafeRandReader wouldn't cause error, so ignore err is safe |
| 47 | +// NewUUIDV4 returns a new version 4 UUID. |
| 48 | +// |
| 49 | +// Version 4 UUIDs contain 122 bits of random data. |
| 50 | +func NewUUIDV4() (u uuid.UUID) { |
| 51 | + UnsafeRandRead(u[:]) |
| 52 | + u.SetVersion(uuid.V4) |
| 53 | + u.SetVariant(uuid.VariantRFC9562) |
47 | 54 | return u |
48 | 55 | } |
49 | 56 |
|
50 | | -func NewUUIDV5(ns uuid.UUID, name string) uuid.UUID { |
51 | | - return UnsafeUUIDGenerator.NewV5(ns, name) |
52 | | -} |
| 57 | +// NewUUIDV5 returns a UUID based on SHA-1 hash of the namespace UUID and name. |
| 58 | +func NewUUIDV5(ns uuid.UUID, name string) (u uuid.UUID) { |
| 59 | + h := sha1.New() |
| 60 | + h.Write(ns[:]) |
| 61 | + h.Write([]byte(name)) |
| 62 | + copy(u[:], h.Sum(make([]byte, 0, sha1.Size))) |
53 | 63 |
|
54 | | -func NewUUIDV6() uuid.UUID { |
55 | | - u, _ := UnsafeUUIDGenerator.NewV6() // unsafeRandReader wouldn't cause error, so ignore err is safe |
| 64 | + u.SetVersion(uuid.V5) |
| 65 | + u.SetVariant(uuid.VariantRFC9562) |
56 | 66 | return u |
57 | 67 | } |
58 | 68 |
|
59 | | -func NewUUIDV7() uuid.UUID { |
60 | | - u, _ := UnsafeUUIDGenerator.NewV7() // unsafeRandReader wouldn't cause error, so ignore err is safe |
| 69 | +var ( |
| 70 | + v7mu sync.Mutex |
| 71 | + v7lastSecs uint64 |
| 72 | + v7lastTimestamp uint64 |
| 73 | +) |
| 74 | + |
| 75 | +// NewUUIDV7 returns a new version 7 UUID. |
| 76 | +// |
| 77 | +// Version 7 UUIDs contain a timestamp in the most significant 48 bits, |
| 78 | +// and at least 62 bits of random data. |
| 79 | +// |
| 80 | +// NewUUIDV7 always returns UUIDs which sort in increasing order, |
| 81 | +// except when the system clock moves backwards. |
| 82 | +func NewUUIDV7() (u uuid.UUID) { |
| 83 | + // UUIDv7 is defined in RFC 9562 section 5.7 as: |
| 84 | + // |
| 85 | + // 0 1 2 3 |
| 86 | + // 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 |
| 87 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 88 | + // | unix_ts_ms | |
| 89 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 90 | + // | unix_ts_ms | ver | rand_a | |
| 91 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 92 | + // |var| rand_b | |
| 93 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 94 | + // | rand_b | |
| 95 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 96 | + // |
| 97 | + // We store a 12 bit sub-millisecond timestamp fraction in the rand_a section, |
| 98 | + // as optionally permitted by the RFC. |
| 99 | + v7mu.Lock() |
| 100 | + |
| 101 | + // Generate our 60-bit timestamp: 48 bits of millisecond-resolution, |
| 102 | + // followed by 12 bits of 1/4096-millisecond resolution. |
| 103 | + now := time.Now() |
| 104 | + secs := uint64(now.Unix()) |
| 105 | + nanos := uint64(now.Nanosecond()) |
| 106 | + msecs := nanos / 1000000 |
| 107 | + frac := nanos - (1000000 * msecs) |
| 108 | + timestamp := (1000*secs + msecs) << 12 // ms shifted into position |
| 109 | + timestamp += (frac * 4096) / 1000000 // ns converted to 1/4096-ms units |
| 110 | + |
| 111 | + if v7lastSecs > secs { |
| 112 | + // Time has gone backwards. |
| 113 | + // This presumably indicates the system clock has changed. |
| 114 | + // Ignore previously-generated UUIDs. |
| 115 | + } else if timestamp <= v7lastTimestamp { |
| 116 | + // This timestamp is the same as a previously-generated UUID. |
| 117 | + // To preserve the property that we generate UUIDs in order, |
| 118 | + // use a timestamp 1/4096 millisecond later than the most recently |
| 119 | + // generated UUID. |
| 120 | + timestamp = v7lastTimestamp + 1 |
| 121 | + } |
| 122 | + |
| 123 | + v7lastSecs = secs |
| 124 | + v7lastTimestamp = timestamp |
| 125 | + v7mu.Unlock() |
| 126 | + |
| 127 | + // Insert a gap for the 4 bits of the ver field into the timestamp. |
| 128 | + hibits := ((timestamp << 4) & 0xffff_ffff_ffff_0000) | (timestamp & 0x0ffff) |
| 129 | + |
| 130 | + binary.BigEndian.PutUint64(u[0:8], hibits) |
| 131 | + UnsafeRandRead(u[8:]) |
| 132 | + |
| 133 | + u.SetVersion(uuid.V7) |
| 134 | + u.SetVariant(uuid.VariantRFC9562) |
61 | 135 | return u |
62 | 136 | } |
63 | 137 |
|
64 | 138 | // UUIDMap https://github.com/XTLS/Xray-core/issues/158#issue-783294090 |
65 | | -func UUIDMap(str string) (uuid.UUID, error) { |
| 139 | +func UUIDMap(str string) uuid.UUID { |
66 | 140 | u, err := uuid.FromString(str) |
67 | 141 | if err != nil { |
68 | | - return NewUUIDV5(uuid.Nil, str), nil |
| 142 | + return NewUUIDV5(uuid.Nil, str) |
69 | 143 | } |
70 | | - return u, nil |
| 144 | + return u |
71 | 145 | } |
0 commit comments