New upstream release.
Debian Janitor
11 months ago
21 | 21 | |
22 | 22 | - name: Check out code into the Go module directory |
23 | 23 | uses: actions/checkout@v2 |
24 | ||
25 | - name: Get Coverage | |
26 | run: go get golang.org/x/tools/cmd/cover | |
27 | 24 | |
28 | 25 | - name: Build |
29 | 26 | run: go build -v ./... |
16 | 16 | * Version 5, based on SHA-1 hashing of a named value (RFC-4122) |
17 | 17 | |
18 | 18 | This package also supports experimental Universally Unique Identifier implementations based on a |
19 | [draft RFC](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03) that updates RFC-4122 | |
19 | [draft RFC](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html) that updates RFC-4122 | |
20 | 20 | * Version 6, a k-sortable id based on timestamp, and field-compatible with v1 (draft-peabody-dispatch-new-uuid-format, RFC-4122) |
21 | 21 | * Version 7, a k-sortable id based on timestamp (draft-peabody-dispatch-new-uuid-format, RFC-4122) |
22 | 22 | |
47 | 47 | created before our fork of the original package and have some known |
48 | 48 | deficiencies. |
49 | 49 | |
50 | ## Installation | |
51 | ||
52 | It is recommended to use a package manager like `dep` that understands tagged | |
53 | releases of a package, as well as semantic versioning. | |
54 | ||
55 | If you are unable to make use of a dependency manager with your project, you can | |
56 | use the `go get` command to download it directly: | |
57 | ||
58 | ```Shell | |
59 | $ go get github.com/gofrs/uuid | |
60 | ``` | |
61 | ||
62 | 50 | ## Requirements |
63 | 51 | |
64 | Due to subtests not being supported in older versions of Go, this package is | |
65 | only regularly tested against Go 1.7+. This package may work perfectly fine with | |
66 | Go 1.2+, but support for these older versions is not actively maintained. | |
67 | ||
68 | ## Go 1.11 Modules | |
69 | ||
70 | As of v3.2.0, this repository no longer adopts Go modules, and v3.2.0 no longer has a `go.mod` file. As a result, v3.2.0 also drops support for the `github.com/gofrs/uuid/v3` import path. Only module-based consumers are impacted. With the v3.2.0 release, _all_ gofrs/uuid consumers should use the `github.com/gofrs/uuid` import path. | |
71 | ||
72 | An existing module-based consumer will continue to be able to build using the `github.com/gofrs/uuid/v3` import path using any valid consumer `go.mod` that worked prior to the publishing of v3.2.0, but any module-based consumer should start using the `github.com/gofrs/uuid` import path when possible and _must_ use the `github.com/gofrs/uuid` import path prior to upgrading to v3.2.0. | |
73 | ||
74 | Please refer to [Issue #61](https://github.com/gofrs/uuid/issues/61) and [Issue #66](https://github.com/gofrs/uuid/issues/66) for more details. | |
52 | This package requres Go 1.17 or later | |
75 | 53 | |
76 | 54 | ## Usage |
77 | 55 | |
113 | 91 | |
114 | 92 | * [RFC-4122](https://tools.ietf.org/html/rfc4122) |
115 | 93 | * [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01) |
116 | * [New UUID Formats RFC Draft (Peabody) Rev 03](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03) | |
94 | * [New UUID Formats RFC Draft (Peabody) Rev 04](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#) |
21 | 21 | package uuid |
22 | 22 | |
23 | 23 | import ( |
24 | "bytes" | |
25 | "encoding/hex" | |
24 | "errors" | |
26 | 25 | "fmt" |
27 | 26 | ) |
28 | 27 | |
44 | 43 | return uuid |
45 | 44 | } |
46 | 45 | |
46 | var errInvalidFormat = errors.New("uuid: invalid UUID format") | |
47 | ||
48 | func fromHexChar(c byte) byte { | |
49 | switch { | |
50 | case '0' <= c && c <= '9': | |
51 | return c - '0' | |
52 | case 'a' <= c && c <= 'f': | |
53 | return c - 'a' + 10 | |
54 | case 'A' <= c && c <= 'F': | |
55 | return c - 'A' + 10 | |
56 | } | |
57 | return 255 | |
58 | } | |
59 | ||
60 | // Parse parses the UUID stored in the string text. Parsing and supported | |
61 | // formats are the same as UnmarshalText. | |
62 | func (u *UUID) Parse(s string) error { | |
63 | switch len(s) { | |
64 | case 32: // hash | |
65 | case 36: // canonical | |
66 | case 34, 38: | |
67 | if s[0] != '{' || s[len(s)-1] != '}' { | |
68 | return fmt.Errorf("uuid: incorrect UUID format in string %q", s) | |
69 | } | |
70 | s = s[1 : len(s)-1] | |
71 | case 41, 45: | |
72 | if s[:9] != "urn:uuid:" { | |
73 | return fmt.Errorf("uuid: incorrect UUID format in string %q", s[:9]) | |
74 | } | |
75 | s = s[9:] | |
76 | default: | |
77 | return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(s), s) | |
78 | } | |
79 | // canonical | |
80 | if len(s) == 36 { | |
81 | if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { | |
82 | return fmt.Errorf("uuid: incorrect UUID format in string %q", s) | |
83 | } | |
84 | for i, x := range [16]byte{ | |
85 | 0, 2, 4, 6, | |
86 | 9, 11, | |
87 | 14, 16, | |
88 | 19, 21, | |
89 | 24, 26, 28, 30, 32, 34, | |
90 | } { | |
91 | v1 := fromHexChar(s[x]) | |
92 | v2 := fromHexChar(s[x+1]) | |
93 | if v1|v2 == 255 { | |
94 | return errInvalidFormat | |
95 | } | |
96 | u[i] = (v1 << 4) | v2 | |
97 | } | |
98 | return nil | |
99 | } | |
100 | // hash like | |
101 | for i := 0; i < 32; i += 2 { | |
102 | v1 := fromHexChar(s[i]) | |
103 | v2 := fromHexChar(s[i+1]) | |
104 | if v1|v2 == 255 { | |
105 | return errInvalidFormat | |
106 | } | |
107 | u[i/2] = (v1 << 4) | v2 | |
108 | } | |
109 | return nil | |
110 | } | |
111 | ||
47 | 112 | // FromString returns a UUID parsed from the input string. |
48 | 113 | // Input is expected in a form accepted by UnmarshalText. |
49 | func FromString(input string) (UUID, error) { | |
50 | u := UUID{} | |
51 | err := u.UnmarshalText([]byte(input)) | |
114 | func FromString(text string) (UUID, error) { | |
115 | var u UUID | |
116 | err := u.Parse(text) | |
52 | 117 | return u, err |
53 | 118 | } |
54 | 119 | |
65 | 130 | // MarshalText implements the encoding.TextMarshaler interface. |
66 | 131 | // The encoding is the same as returned by the String() method. |
67 | 132 | func (u UUID) MarshalText() ([]byte, error) { |
68 | return []byte(u.String()), nil | |
133 | var buf [36]byte | |
134 | encodeCanonical(buf[:], u) | |
135 | return buf[:], nil | |
69 | 136 | } |
70 | 137 | |
71 | 138 | // UnmarshalText implements the encoding.TextUnmarshaler interface. |
72 | 139 | // Following formats are supported: |
73 | 140 | // |
74 | // "6ba7b810-9dad-11d1-80b4-00c04fd430c8", | |
75 | // "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", | |
76 | // "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" | |
77 | // "6ba7b8109dad11d180b400c04fd430c8" | |
78 | // "{6ba7b8109dad11d180b400c04fd430c8}", | |
79 | // "urn:uuid:6ba7b8109dad11d180b400c04fd430c8" | |
141 | // "6ba7b810-9dad-11d1-80b4-00c04fd430c8", | |
142 | // "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", | |
143 | // "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" | |
144 | // "6ba7b8109dad11d180b400c04fd430c8" | |
145 | // "{6ba7b8109dad11d180b400c04fd430c8}", | |
146 | // "urn:uuid:6ba7b8109dad11d180b400c04fd430c8" | |
80 | 147 | // |
81 | 148 | // ABNF for supported UUID text representation follows: |
82 | 149 | // |
83 | // URN := 'urn' | |
84 | // UUID-NID := 'uuid' | |
85 | // | |
86 | // hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | | |
87 | // 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | | |
88 | // 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | |
89 | // | |
90 | // hexoct := hexdig hexdig | |
91 | // 2hexoct := hexoct hexoct | |
92 | // 4hexoct := 2hexoct 2hexoct | |
93 | // 6hexoct := 4hexoct 2hexoct | |
94 | // 12hexoct := 6hexoct 6hexoct | |
95 | // | |
96 | // hashlike := 12hexoct | |
97 | // canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct | |
98 | // | |
99 | // plain := canonical | hashlike | |
100 | // uuid := canonical | hashlike | braced | urn | |
101 | // | |
102 | // braced := '{' plain '}' | '{' hashlike '}' | |
103 | // urn := URN ':' UUID-NID ':' plain | |
104 | // | |
105 | func (u *UUID) UnmarshalText(text []byte) error { | |
106 | switch len(text) { | |
107 | case 32: | |
108 | return u.decodeHashLike(text) | |
150 | // URN := 'urn' | |
151 | // UUID-NID := 'uuid' | |
152 | // | |
153 | // hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | | |
154 | // 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | | |
155 | // 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | |
156 | // | |
157 | // hexoct := hexdig hexdig | |
158 | // 2hexoct := hexoct hexoct | |
159 | // 4hexoct := 2hexoct 2hexoct | |
160 | // 6hexoct := 4hexoct 2hexoct | |
161 | // 12hexoct := 6hexoct 6hexoct | |
162 | // | |
163 | // hashlike := 12hexoct | |
164 | // canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct | |
165 | // | |
166 | // plain := canonical | hashlike | |
167 | // uuid := canonical | hashlike | braced | urn | |
168 | // | |
169 | // braced := '{' plain '}' | '{' hashlike '}' | |
170 | // urn := URN ':' UUID-NID ':' plain | |
171 | func (u *UUID) UnmarshalText(b []byte) error { | |
172 | switch len(b) { | |
173 | case 32: // hash | |
174 | case 36: // canonical | |
109 | 175 | case 34, 38: |
110 | return u.decodeBraced(text) | |
111 | case 36: | |
112 | return u.decodeCanonical(text) | |
176 | if b[0] != '{' || b[len(b)-1] != '}' { | |
177 | return fmt.Errorf("uuid: incorrect UUID format in string %q", b) | |
178 | } | |
179 | b = b[1 : len(b)-1] | |
113 | 180 | case 41, 45: |
114 | return u.decodeURN(text) | |
181 | if string(b[:9]) != "urn:uuid:" { | |
182 | return fmt.Errorf("uuid: incorrect UUID format in string %q", b[:9]) | |
183 | } | |
184 | b = b[9:] | |
115 | 185 | default: |
116 | return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(text), text) | |
117 | } | |
118 | } | |
119 | ||
120 | // decodeCanonical decodes UUID strings that are formatted as defined in RFC-4122 (section 3): | |
121 | // "6ba7b810-9dad-11d1-80b4-00c04fd430c8". | |
122 | func (u *UUID) decodeCanonical(t []byte) error { | |
123 | if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' { | |
124 | return fmt.Errorf("uuid: incorrect UUID format in string %q", t) | |
125 | } | |
126 | ||
127 | src := t | |
128 | dst := u[:] | |
129 | ||
130 | for i, byteGroup := range byteGroups { | |
131 | if i > 0 { | |
132 | src = src[1:] // skip dash | |
133 | } | |
134 | _, err := hex.Decode(dst[:byteGroup/2], src[:byteGroup]) | |
135 | if err != nil { | |
136 | return err | |
137 | } | |
138 | src = src[byteGroup:] | |
139 | dst = dst[byteGroup/2:] | |
140 | } | |
141 | ||
186 | return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(b), b) | |
187 | } | |
188 | if len(b) == 36 { | |
189 | if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { | |
190 | return fmt.Errorf("uuid: incorrect UUID format in string %q", b) | |
191 | } | |
192 | for i, x := range [16]byte{ | |
193 | 0, 2, 4, 6, | |
194 | 9, 11, | |
195 | 14, 16, | |
196 | 19, 21, | |
197 | 24, 26, 28, 30, 32, 34, | |
198 | } { | |
199 | v1 := fromHexChar(b[x]) | |
200 | v2 := fromHexChar(b[x+1]) | |
201 | if v1|v2 == 255 { | |
202 | return errInvalidFormat | |
203 | } | |
204 | u[i] = (v1 << 4) | v2 | |
205 | } | |
206 | return nil | |
207 | } | |
208 | for i := 0; i < 32; i += 2 { | |
209 | v1 := fromHexChar(b[i]) | |
210 | v2 := fromHexChar(b[i+1]) | |
211 | if v1|v2 == 255 { | |
212 | return errInvalidFormat | |
213 | } | |
214 | u[i/2] = (v1 << 4) | v2 | |
215 | } | |
142 | 216 | return nil |
143 | } | |
144 | ||
145 | // decodeHashLike decodes UUID strings that are using the following format: | |
146 | // "6ba7b8109dad11d180b400c04fd430c8". | |
147 | func (u *UUID) decodeHashLike(t []byte) error { | |
148 | src := t[:] | |
149 | dst := u[:] | |
150 | ||
151 | _, err := hex.Decode(dst, src) | |
152 | return err | |
153 | } | |
154 | ||
155 | // decodeBraced decodes UUID strings that are using the following formats: | |
156 | // "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" | |
157 | // "{6ba7b8109dad11d180b400c04fd430c8}". | |
158 | func (u *UUID) decodeBraced(t []byte) error { | |
159 | l := len(t) | |
160 | ||
161 | if t[0] != '{' || t[l-1] != '}' { | |
162 | return fmt.Errorf("uuid: incorrect UUID format in string %q", t) | |
163 | } | |
164 | ||
165 | return u.decodePlain(t[1 : l-1]) | |
166 | } | |
167 | ||
168 | // decodeURN decodes UUID strings that are using the following formats: | |
169 | // "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" | |
170 | // "urn:uuid:6ba7b8109dad11d180b400c04fd430c8". | |
171 | func (u *UUID) decodeURN(t []byte) error { | |
172 | total := len(t) | |
173 | ||
174 | urnUUIDPrefix := t[:9] | |
175 | ||
176 | if !bytes.Equal(urnUUIDPrefix, urnPrefix) { | |
177 | return fmt.Errorf("uuid: incorrect UUID format in string %q", t) | |
178 | } | |
179 | ||
180 | return u.decodePlain(t[9:total]) | |
181 | } | |
182 | ||
183 | // decodePlain decodes UUID strings that are using the following formats: | |
184 | // "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format | |
185 | // "6ba7b8109dad11d180b400c04fd430c8". | |
186 | func (u *UUID) decodePlain(t []byte) error { | |
187 | switch len(t) { | |
188 | case 32: | |
189 | return u.decodeHashLike(t) | |
190 | case 36: | |
191 | return u.decodeCanonical(t) | |
192 | default: | |
193 | return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(t), t) | |
194 | } | |
195 | 217 | } |
196 | 218 | |
197 | 219 | // MarshalBinary implements the encoding.BinaryMarshaler interface. |
27 | 27 | "io/ioutil" |
28 | 28 | "os" |
29 | 29 | "path/filepath" |
30 | "strings" | |
30 | 31 | "testing" |
31 | 32 | ) |
32 | 33 | |
89 | 90 | } |
90 | 91 | |
91 | 92 | // Run runs the FromString test in a subtest of t, named by fst.variant. |
92 | func (fst fromStringTest) Run(t *testing.T) { | |
93 | func (fst fromStringTest) TestFromString(t *testing.T) { | |
93 | 94 | t.Run(fst.variant, func(t *testing.T) { |
94 | 95 | got, err := FromString(fst.input) |
95 | 96 | if err != nil { |
97 | 98 | } |
98 | 99 | if want := codecTestUUID; got != want { |
99 | 100 | t.Fatalf("FromString(%q) = %v, want %v", fst.input, got, want) |
101 | } | |
102 | }) | |
103 | } | |
104 | ||
105 | func (fst fromStringTest) TestUnmarshalText(t *testing.T) { | |
106 | t.Run(fst.variant, func(t *testing.T) { | |
107 | var u UUID | |
108 | err := u.UnmarshalText([]byte(fst.input)) | |
109 | if err != nil { | |
110 | t.Fatalf("UnmarshalText(%q) (%s): %v", fst.input, fst.variant, err) | |
111 | } | |
112 | if want := codecTestData; !bytes.Equal(u[:], want[:]) { | |
113 | t.Fatalf("UnmarshalText(%q) (%s) = %v, want %v", fst.input, fst.variant, u, want) | |
100 | 114 | } |
101 | 115 | }) |
102 | 116 | } |
170 | 184 | func TestFromString(t *testing.T) { |
171 | 185 | t.Run("Valid", func(t *testing.T) { |
172 | 186 | for _, fst := range fromStringTests { |
173 | fst.Run(t) | |
187 | fst.TestFromString(t) | |
174 | 188 | } |
175 | 189 | }) |
176 | 190 | t.Run("Invalid", func(t *testing.T) { |
198 | 212 | t.Errorf("FromStringOrNil(%q): got %v, want %v", s, got, codecTestUUID) |
199 | 213 | } |
200 | 214 | }) |
215 | } | |
216 | ||
217 | func TestUnmarshalText(t *testing.T) { | |
218 | t.Run("Valid", func(t *testing.T) { | |
219 | for _, fst := range fromStringTests { | |
220 | fst.TestUnmarshalText(t) | |
221 | } | |
222 | }) | |
223 | t.Run("Invalid", func(t *testing.T) { | |
224 | for _, s := range invalidFromStringInputs { | |
225 | var u UUID | |
226 | err := u.UnmarshalText([]byte(s)) | |
227 | if err == nil { | |
228 | t.Errorf("FromBytes(%q): want err != nil, got %v", s, u) | |
229 | } | |
230 | } | |
231 | }) | |
232 | } | |
233 | ||
234 | // Test that UnmarshalText() and Parse() return identical errors | |
235 | func TestUnmarshalTextParseErrors(t *testing.T) { | |
236 | for _, s := range invalidFromStringInputs { | |
237 | var u UUID | |
238 | e1 := u.UnmarshalText([]byte(s)) | |
239 | e2 := u.Parse(s) | |
240 | if e1 == nil || e1.Error() != e2.Error() { | |
241 | t.Errorf("%q: errors don't match: UnmarshalText: %v Parse: %v", s, e1, e2) | |
242 | } | |
243 | } | |
201 | 244 | } |
202 | 245 | |
203 | 246 | func TestMarshalBinary(t *testing.T) { |
226 | 269 | |
227 | 270 | u := UUID{} |
228 | 271 | |
229 | if u.decodePlain(arg) == nil { | |
230 | t.Errorf("%v.decodePlain(%q): should return error, but it did not", u, arg) | |
231 | } | |
272 | if u.UnmarshalText(arg) == nil { | |
273 | t.Errorf("%v.UnmarshalText(%q): should return error, but it did not", u, arg) | |
274 | } | |
275 | } | |
276 | ||
277 | func TestFromHexChar(t *testing.T) { | |
278 | const hextable = "0123456789abcdef" | |
279 | ||
280 | t.Run("Valid", func(t *testing.T) { | |
281 | t.Run("Lower", func(t *testing.T) { | |
282 | for i, c := range []byte(hextable) { | |
283 | x := fromHexChar(c) | |
284 | if int(x) != i { | |
285 | t.Errorf("fromHexChar(%c): got %d want %d", c, x, i) | |
286 | } | |
287 | } | |
288 | }) | |
289 | t.Run("Upper", func(t *testing.T) { | |
290 | for i, c := range []byte(strings.ToUpper(hextable)) { | |
291 | x := fromHexChar(c) | |
292 | if int(x) != i { | |
293 | t.Errorf("fromHexChar(%c): got %d want %d", c, x, i) | |
294 | } | |
295 | } | |
296 | }) | |
297 | }) | |
298 | ||
299 | t.Run("Invalid", func(t *testing.T) { | |
300 | skip := make(map[byte]bool) | |
301 | for _, c := range []byte(hextable + strings.ToUpper(hextable)) { | |
302 | skip[c] = true | |
303 | } | |
304 | for i := 0; i < 256; i++ { | |
305 | c := byte(i) | |
306 | if !skip[c] { | |
307 | v := fromHexChar(c) | |
308 | if v != 255 { | |
309 | t.Errorf("fromHexChar(%c): got %d want: %d", c, v, 255) | |
310 | } | |
311 | } | |
312 | } | |
313 | }) | |
232 | 314 | } |
233 | 315 | |
234 | 316 | var stringBenchmarkSink string |
263 | 345 | }) |
264 | 346 | } |
265 | 347 | |
348 | func BenchmarkUnmarshalText(b *testing.B) { | |
349 | b.Run("canonical", func(b *testing.B) { | |
350 | text := []byte(Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")).String()) | |
351 | u := new(UUID) | |
352 | if err := u.UnmarshalText(text); err != nil { | |
353 | b.Fatal(err) | |
354 | } | |
355 | b.ResetTimer() | |
356 | for i := 0; i < b.N; i++ { | |
357 | _ = u.UnmarshalText(text) | |
358 | } | |
359 | }) | |
360 | b.Run("urn", func(b *testing.B) { | |
361 | text := []byte(Must(FromString("urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8")).String()) | |
362 | u := new(UUID) | |
363 | if err := u.UnmarshalText(text); err != nil { | |
364 | b.Fatal(err) | |
365 | } | |
366 | b.ResetTimer() | |
367 | for i := 0; i < b.N; i++ { | |
368 | _ = u.UnmarshalText(text) | |
369 | } | |
370 | }) | |
371 | b.Run("braced", func(b *testing.B) { | |
372 | text := []byte(Must(FromString("{6ba7b810-9dad-11d1-80b4-00c04fd430c8}")).String()) | |
373 | u := new(UUID) | |
374 | if err := u.UnmarshalText(text); err != nil { | |
375 | b.Fatal(err) | |
376 | } | |
377 | b.ResetTimer() | |
378 | for i := 0; i < b.N; i++ { | |
379 | _ = u.UnmarshalText(text) | |
380 | } | |
381 | }) | |
382 | } | |
383 | ||
266 | 384 | func BenchmarkMarshalBinary(b *testing.B) { |
267 | 385 | for i := 0; i < b.N; i++ { |
268 | 386 | codecTestUUID.MarshalBinary() |
272 | 390 | func BenchmarkMarshalText(b *testing.B) { |
273 | 391 | for i := 0; i < b.N; i++ { |
274 | 392 | codecTestUUID.MarshalText() |
393 | } | |
394 | } | |
395 | ||
396 | func BenchmarkParseV4(b *testing.B) { | |
397 | const text = "f52a747a-983f-45f7-90b5-e84d70f470dd" | |
398 | for i := 0; i < b.N; i++ { | |
399 | var u UUID | |
400 | if err := u.Parse(text); err != nil { | |
401 | b.Fatal(err) | |
402 | } | |
275 | 403 | } |
276 | 404 | } |
277 | 405 |
0 | golang-github-gofrs-uuid (5.0.0-1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream release. | |
3 | ||
4 | -- Debian Janitor <janitor@jelmer.uk> Fri, 09 Jun 2023 06:41:37 -0000 | |
5 | ||
0 | 6 | golang-github-gofrs-uuid (4.3.1-1) unstable; urgency=medium |
1 | 7 | |
2 | 8 | * New upstream release |
18 | 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
19 | 19 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
20 | 20 | |
21 | //go:build gofuzz | |
21 | 22 | // +build gofuzz |
22 | 23 | |
23 | 24 | package uuid |
26 | 27 | // |
27 | 28 | // To run: |
28 | 29 | // |
29 | // $ go get github.com/dvyukov/go-fuzz/... | |
30 | // $ cd $GOPATH/src/github.com/gofrs/uuid | |
31 | // $ go-fuzz-build github.com/gofrs/uuid | |
32 | // $ go-fuzz -bin=uuid-fuzz.zip -workdir=./testdata | |
30 | // $ go get github.com/dvyukov/go-fuzz/... | |
31 | // $ cd $GOPATH/src/github.com/gofrs/uuid | |
32 | // $ go-fuzz-build github.com/gofrs/uuid | |
33 | // $ go-fuzz -bin=uuid-fuzz.zip -workdir=./testdata | |
33 | 34 | // |
34 | 35 | // If you make significant changes to FromString / UnmarshalText and add |
35 | 36 | // new cases to fromStringTests (in codec_test.go), please run |
36 | 37 | // |
37 | // $ go test -seed_fuzz_corpus | |
38 | // $ go test -seed_fuzz_corpus | |
38 | 39 | // |
39 | 40 | // to seed the corpus with the new interesting inputs, then run the fuzzer. |
40 | 41 | func Fuzz(data []byte) int { |
37 | 37 | // UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). |
38 | 38 | const epochStart = 122192928000000000 |
39 | 39 | |
40 | type epochFunc func() time.Time | |
40 | // EpochFunc is the function type used to provide the current time. | |
41 | type EpochFunc func() time.Time | |
41 | 42 | |
42 | 43 | // HWAddrFunc is the function type used to provide hardware (MAC) addresses. |
43 | 44 | type HWAddrFunc func() (net.HardwareAddr, error) |
79 | 80 | } |
80 | 81 | |
81 | 82 | // NewV7 returns a k-sortable UUID based on the current millisecond precision |
82 | // UNIX epoch and 74 bits of pseudorandom data. | |
83 | // | |
84 | // This is implemented based on revision 03 of the Peabody UUID draft, and may | |
83 | // UNIX epoch and 74 bits of pseudorandom data. It supports single-node batch generation (multiple UUIDs in the same timestamp) with a Monotonic Random counter. | |
84 | // | |
85 | // This is implemented based on revision 04 of the Peabody UUID draft, and may | |
85 | 86 | // be subject to change pending further revisions. Until the final specification |
86 | 87 | // revision is finished, changes required to implement updates to the spec will |
87 | 88 | // not be considered a breaking change. They will happen as a minor version |
118 | 119 | |
119 | 120 | rand io.Reader |
120 | 121 | |
121 | epochFunc epochFunc | |
122 | epochFunc EpochFunc | |
122 | 123 | hwAddrFunc HWAddrFunc |
123 | 124 | lastTime uint64 |
124 | 125 | clockSequence uint16 |
125 | 126 | hardwareAddr [6]byte |
126 | 127 | } |
128 | ||
129 | // GenOption is a function type that can be used to configure a Gen generator. | |
130 | type GenOption func(*Gen) | |
127 | 131 | |
128 | 132 | // interface check -- build will fail if *Gen doesn't satisfy Generator |
129 | 133 | var _ Generator = (*Gen)(nil) |
146 | 150 | // MAC address being used, you'll need to create a new generator using this |
147 | 151 | // function. |
148 | 152 | func NewGenWithHWAF(hwaf HWAddrFunc) *Gen { |
149 | return &Gen{ | |
153 | return NewGenWithOptions(WithHWAddrFunc(hwaf)) | |
154 | } | |
155 | ||
156 | // NewGenWithOptions returns a new instance of Gen with the options provided. | |
157 | // Most people should use NewGen() or NewGenWithHWAF() instead. | |
158 | // | |
159 | // To customize the generator, you can pass in one or more GenOption functions. | |
160 | // For example: | |
161 | // | |
162 | // gen := NewGenWithOptions( | |
163 | // WithHWAddrFunc(myHWAddrFunc), | |
164 | // WithEpochFunc(myEpochFunc), | |
165 | // WithRandomReader(myRandomReader), | |
166 | // ) | |
167 | // | |
168 | // NewGenWithOptions(WithHWAddrFunc(myHWAddrFunc)) is equivalent to calling | |
169 | // NewGenWithHWAF(myHWAddrFunc) | |
170 | // NewGenWithOptions() is equivalent to calling NewGen() | |
171 | func NewGenWithOptions(opts ...GenOption) *Gen { | |
172 | gen := &Gen{ | |
150 | 173 | epochFunc: time.Now, |
151 | hwAddrFunc: hwaf, | |
174 | hwAddrFunc: defaultHWAddrFunc, | |
152 | 175 | rand: rand.Reader, |
176 | } | |
177 | ||
178 | for _, opt := range opts { | |
179 | opt(gen) | |
180 | } | |
181 | ||
182 | return gen | |
183 | } | |
184 | ||
185 | // WithHWAddrFunc is a GenOption that allows you to provide your own HWAddrFunc | |
186 | // function. | |
187 | // When this option is nil, the defaultHWAddrFunc is used. | |
188 | func WithHWAddrFunc(hwaf HWAddrFunc) GenOption { | |
189 | return func(gen *Gen) { | |
190 | if hwaf == nil { | |
191 | hwaf = defaultHWAddrFunc | |
192 | } | |
193 | ||
194 | gen.hwAddrFunc = hwaf | |
195 | } | |
196 | } | |
197 | ||
198 | // WithEpochFunc is a GenOption that allows you to provide your own EpochFunc | |
199 | // function. | |
200 | // When this option is nil, time.Now is used. | |
201 | func WithEpochFunc(epochf EpochFunc) GenOption { | |
202 | return func(gen *Gen) { | |
203 | if epochf == nil { | |
204 | epochf = time.Now | |
205 | } | |
206 | ||
207 | gen.epochFunc = epochf | |
208 | } | |
209 | } | |
210 | ||
211 | // WithRandomReader is a GenOption that allows you to provide your own random | |
212 | // reader. | |
213 | // When this option is nil, the default rand.Reader is used. | |
214 | func WithRandomReader(reader io.Reader) GenOption { | |
215 | return func(gen *Gen) { | |
216 | if reader == nil { | |
217 | reader = rand.Reader | |
218 | } | |
219 | ||
220 | gen.rand = reader | |
153 | 221 | } |
154 | 222 | } |
155 | 223 | |
157 | 225 | func (g *Gen) NewV1() (UUID, error) { |
158 | 226 | u := UUID{} |
159 | 227 | |
160 | timeNow, clockSeq, err := g.getClockSequence() | |
228 | timeNow, clockSeq, err := g.getClockSequence(false) | |
161 | 229 | if err != nil { |
162 | 230 | return Nil, err |
163 | 231 | } |
224 | 292 | return Nil, err |
225 | 293 | } |
226 | 294 | |
227 | timeNow, clockSeq, err := g.getClockSequence() | |
295 | timeNow, clockSeq, err := g.getClockSequence(false) | |
228 | 296 | if err != nil { |
229 | 297 | return Nil, err |
230 | 298 | } |
240 | 308 | return u, nil |
241 | 309 | } |
242 | 310 | |
243 | // getClockSequence returns the epoch and clock sequence for V1 and V6 UUIDs. | |
244 | func (g *Gen) getClockSequence() (uint64, uint16, error) { | |
311 | // getClockSequence returns the epoch and clock sequence for V1,V6 and V7 UUIDs. | |
312 | // | |
313 | // When useUnixTSMs is false, it uses the Coordinated Universal Time (UTC) as a count of 100- | |
314 | // | |
315 | // nanosecond intervals since 00:00:00.00, 15 October 1582 (the date of Gregorian reform to the Christian calendar). | |
316 | func (g *Gen) getClockSequence(useUnixTSMs bool) (uint64, uint16, error) { | |
245 | 317 | var err error |
246 | 318 | g.clockSequenceOnce.Do(func() { |
247 | 319 | buf := make([]byte, 2) |
257 | 329 | g.storageMutex.Lock() |
258 | 330 | defer g.storageMutex.Unlock() |
259 | 331 | |
260 | timeNow := g.getEpoch() | |
332 | var timeNow uint64 | |
333 | if useUnixTSMs { | |
334 | timeNow = uint64(g.epochFunc().UnixMilli()) | |
335 | } else { | |
336 | timeNow = g.getEpoch() | |
337 | } | |
261 | 338 | // Clock didn't change since last UUID generation. |
262 | 339 | // Should increase clock sequence. |
263 | 340 | if timeNow <= g.lastTime { |
271 | 348 | // NewV7 returns a k-sortable UUID based on the current millisecond precision |
272 | 349 | // UNIX epoch and 74 bits of pseudorandom data. |
273 | 350 | // |
274 | // This is implemented based on revision 03 of the Peabody UUID draft, and may | |
351 | // This is implemented based on revision 04 of the Peabody UUID draft, and may | |
275 | 352 | // be subject to change pending further revisions. Until the final specification |
276 | 353 | // revision is finished, changes required to implement updates to the spec will |
277 | 354 | // not be considered a breaking change. They will happen as a minor version |
278 | 355 | // releases until the spec is final. |
279 | 356 | func (g *Gen) NewV7() (UUID, error) { |
280 | 357 | var u UUID |
281 | ||
282 | if _, err := io.ReadFull(g.rand, u[6:]); err != nil { | |
283 | return Nil, err | |
284 | } | |
285 | ||
286 | tn := g.epochFunc() | |
287 | ms := uint64(tn.Unix())*1e3 + uint64(tn.Nanosecond())/1e6 | |
288 | u[0] = byte(ms >> 40) | |
358 | /* https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-7 | |
359 | 0 1 2 3 | |
360 | 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 | |
361 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
362 | | unix_ts_ms | | |
363 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
364 | | unix_ts_ms | ver | rand_a | | |
365 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
366 | |var| rand_b | | |
367 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
368 | | rand_b | | |
369 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ | |
370 | ||
371 | ms, clockSeq, err := g.getClockSequence(true) | |
372 | if err != nil { | |
373 | return Nil, err | |
374 | } | |
375 | //UUIDv7 features a 48 bit timestamp. First 32bit (4bytes) represents seconds since 1970, followed by 2 bytes for the ms granularity. | |
376 | u[0] = byte(ms >> 40) //1-6 bytes: big-endian unsigned number of Unix epoch timestamp | |
289 | 377 | u[1] = byte(ms >> 32) |
290 | 378 | u[2] = byte(ms >> 24) |
291 | 379 | u[3] = byte(ms >> 16) |
292 | 380 | u[4] = byte(ms >> 8) |
293 | 381 | u[5] = byte(ms) |
294 | 382 | |
383 | //support batching by using a monotonic pseudo-random sequence | |
384 | //The 6th byte contains the version and partially rand_a data. | |
385 | //We will lose the most significant bites from the clockSeq (with SetVersion), but it is ok, we need the least significant that contains the counter to ensure the monotonic property | |
386 | binary.BigEndian.PutUint16(u[6:8], clockSeq) // set rand_a with clock seq which is random and monotonic | |
387 | ||
388 | //override first 4bits of u[6]. | |
295 | 389 | u.SetVersion(V7) |
390 | ||
391 | //set rand_b 64bits of pseudo-random bits (first 2 will be overridden) | |
392 | if _, err = io.ReadFull(g.rand, u[8:16]); err != nil { | |
393 | return Nil, err | |
394 | } | |
395 | //override first 2 bits of byte[8] for the variant | |
296 | 396 | u.SetVariant(VariantRFC4122) |
297 | 397 | |
298 | 398 | return u, nil |
43 | 43 | |
44 | 44 | func testNewV1(t *testing.T) { |
45 | 45 | t.Run("Basic", testNewV1Basic) |
46 | t.Run("BasicWithOptions", testNewV1BasicWithOptions) | |
46 | 47 | t.Run("DifferentAcrossCalls", testNewV1DifferentAcrossCalls) |
47 | 48 | t.Run("StaleEpoch", testNewV1StaleEpoch) |
48 | 49 | t.Run("FaultyRand", testNewV1FaultyRand) |
50 | t.Run("FaultyRandWithOptions", testNewV1FaultyRandWithOptions) | |
49 | 51 | t.Run("MissingNetwork", testNewV1MissingNetwork) |
52 | t.Run("MissingNetworkWithOptions", testNewV1MissingNetworkWithOptions) | |
50 | 53 | t.Run("MissingNetworkFaultyRand", testNewV1MissingNetworkFaultyRand) |
54 | t.Run("MissingNetworkFaultyRandWithOptions", testNewV1MissingNetworkFaultyRandWithOptions) | |
51 | 55 | } |
52 | 56 | |
53 | 57 | func TestNewGenWithHWAF(t *testing.T) { |
81 | 85 | |
82 | 86 | func testNewV1Basic(t *testing.T) { |
83 | 87 | u, err := NewV1() |
88 | if err != nil { | |
89 | t.Fatal(err) | |
90 | } | |
91 | if got, want := u.Version(), V1; got != want { | |
92 | t.Errorf("generated UUID with version %d, want %d", got, want) | |
93 | } | |
94 | if got, want := u.Variant(), VariantRFC4122; got != want { | |
95 | t.Errorf("generated UUID with variant %d, want %d", got, want) | |
96 | } | |
97 | } | |
98 | ||
99 | func testNewV1BasicWithOptions(t *testing.T) { | |
100 | g := NewGenWithOptions( | |
101 | WithHWAddrFunc(nil), | |
102 | WithEpochFunc(nil), | |
103 | WithRandomReader(nil), | |
104 | ) | |
105 | u, err := g.NewV1() | |
84 | 106 | if err != nil { |
85 | 107 | t.Fatal(err) |
86 | 108 | } |
152 | 174 | }, |
153 | 175 | rand: rand.Reader, |
154 | 176 | } |
177 | _, err := g.NewV1() | |
178 | if err != nil { | |
179 | t.Errorf("did not handle missing network interfaces: %v", err) | |
180 | } | |
181 | } | |
182 | ||
183 | func testNewV1MissingNetworkWithOptions(t *testing.T) { | |
184 | g := NewGenWithOptions( | |
185 | WithHWAddrFunc(func() (net.HardwareAddr, error) { | |
186 | return []byte{}, fmt.Errorf("uuid: no hw address found") | |
187 | }), | |
188 | ) | |
155 | 189 | _, err := g.NewV1() |
156 | 190 | if err != nil { |
157 | 191 | t.Errorf("did not handle missing network interfaces: %v", err) |
174 | 208 | } |
175 | 209 | } |
176 | 210 | |
211 | func testNewV1MissingNetworkFaultyRandWithOptions(t *testing.T) { | |
212 | g := NewGenWithOptions( | |
213 | WithHWAddrFunc(func() (net.HardwareAddr, error) { | |
214 | return []byte{}, fmt.Errorf("uuid: no hw address found") | |
215 | }), | |
216 | WithRandomReader(&faultyReader{ | |
217 | readToFail: 1, | |
218 | }), | |
219 | ) | |
220 | ||
221 | u, err := g.NewV1() | |
222 | if err == nil { | |
223 | t.Errorf("did not error on faulty reader and missing network, got %v", u) | |
224 | } | |
225 | } | |
226 | ||
227 | func testNewV1FaultyRandWithOptions(t *testing.T) { | |
228 | g := NewGenWithOptions(WithRandomReader(&faultyReader{ | |
229 | readToFail: 0, // fail immediately | |
230 | }), | |
231 | ) | |
232 | u, err := g.NewV1() | |
233 | if err == nil { | |
234 | t.Errorf("did not error on faulty reader and missing network, got %v", u) | |
235 | } | |
236 | } | |
237 | ||
177 | 238 | func testNewV3(t *testing.T) { |
178 | 239 | t.Run("Basic", testNewV3Basic) |
179 | 240 | t.Run("EqualNames", testNewV3EqualNames) |
221 | 282 | t.Run("Basic", testNewV4Basic) |
222 | 283 | t.Run("DifferentAcrossCalls", testNewV4DifferentAcrossCalls) |
223 | 284 | t.Run("FaultyRand", testNewV4FaultyRand) |
285 | t.Run("FaultyRandWithOptions", testNewV4FaultyRandWithOptions) | |
224 | 286 | t.Run("ShortRandomRead", testNewV4ShortRandomRead) |
287 | t.Run("ShortRandomReadWithOptions", testNewV4ShortRandomReadWithOptions) | |
225 | 288 | } |
226 | 289 | |
227 | 290 | func testNewV4Basic(t *testing.T) { |
265 | 328 | } |
266 | 329 | } |
267 | 330 | |
331 | func testNewV4FaultyRandWithOptions(t *testing.T) { | |
332 | g := NewGenWithOptions( | |
333 | WithRandomReader(&faultyReader{ | |
334 | readToFail: 0, // fail immediately | |
335 | }), | |
336 | ) | |
337 | u, err := g.NewV4() | |
338 | if err == nil { | |
339 | t.Errorf("got %v, nil error", u) | |
340 | } | |
341 | } | |
342 | ||
268 | 343 | func testNewV4ShortRandomRead(t *testing.T) { |
269 | 344 | g := &Gen{ |
270 | 345 | epochFunc: time.Now, |
273 | 348 | }, |
274 | 349 | rand: bytes.NewReader([]byte{42}), |
275 | 350 | } |
351 | u, err := g.NewV4() | |
352 | if err == nil { | |
353 | t.Errorf("got %v, nil error", u) | |
354 | } | |
355 | } | |
356 | ||
357 | func testNewV4ShortRandomReadWithOptions(t *testing.T) { | |
358 | g := NewGenWithOptions( | |
359 | WithHWAddrFunc(func() (net.HardwareAddr, error) { | |
360 | return []byte{}, fmt.Errorf("uuid: no hw address found") | |
361 | }), | |
362 | WithRandomReader(&faultyReader{ | |
363 | readToFail: 0, // fail immediately | |
364 | }), | |
365 | ) | |
276 | 366 | u, err := g.NewV4() |
277 | 367 | if err == nil { |
278 | 368 | t.Errorf("got %v, nil error", u) |
326 | 416 | t.Run("Basic", testNewV6Basic) |
327 | 417 | t.Run("DifferentAcrossCalls", testNewV6DifferentAcrossCalls) |
328 | 418 | t.Run("StaleEpoch", testNewV6StaleEpoch) |
419 | t.Run("StaleEpochWithOptions", testNewV6StaleEpochWithOptions) | |
329 | 420 | t.Run("FaultyRand", testNewV6FaultyRand) |
421 | t.Run("FaultyRandWithOptions", testNewV6FaultyRandWithOptions) | |
330 | 422 | t.Run("ShortRandomRead", testNewV6ShortRandomRead) |
423 | t.Run("ShortRandomReadWithOptions", testNewV6ShortRandomReadWithOptions) | |
331 | 424 | t.Run("KSortable", testNewV6KSortable) |
332 | 425 | } |
333 | 426 | |
366 | 459 | hwAddrFunc: defaultHWAddrFunc, |
367 | 460 | rand: rand.Reader, |
368 | 461 | } |
462 | u1, err := g.NewV6() | |
463 | if err != nil { | |
464 | t.Fatal(err) | |
465 | } | |
466 | u2, err := g.NewV6() | |
467 | if err != nil { | |
468 | t.Fatal(err) | |
469 | } | |
470 | if u1 == u2 { | |
471 | t.Errorf("generated identical UUIDs across calls: %v", u1) | |
472 | } | |
473 | } | |
474 | ||
475 | func testNewV6StaleEpochWithOptions(t *testing.T) { | |
476 | g := NewGenWithOptions( | |
477 | WithEpochFunc(func() time.Time { | |
478 | return time.Unix(0, 0) | |
479 | }), | |
480 | ) | |
369 | 481 | u1, err := g.NewV6() |
370 | 482 | if err != nil { |
371 | 483 | t.Fatal(err) |
415 | 527 | }) |
416 | 528 | } |
417 | 529 | |
530 | func testNewV6FaultyRandWithOptions(t *testing.T) { | |
531 | t.Run("randomData", func(t *testing.T) { | |
532 | g := NewGenWithOptions( | |
533 | WithRandomReader(&faultyReader{ | |
534 | readToFail: 0, // fail immediately | |
535 | }), | |
536 | ) | |
537 | u, err := g.NewV6() | |
538 | if err == nil { | |
539 | t.Fatalf("got %v, want error", u) | |
540 | } | |
541 | if u != Nil { | |
542 | t.Fatalf("got %v on error, want Nil", u) | |
543 | } | |
544 | }) | |
545 | ||
546 | t.Run("clockSequence", func(t *testing.T) { | |
547 | g := NewGenWithOptions( | |
548 | WithRandomReader(&faultyReader{ | |
549 | readToFail: 1, // fail immediately | |
550 | }), | |
551 | ) | |
552 | u, err := g.NewV6() | |
553 | if err == nil { | |
554 | t.Fatalf("got %v, want error", u) | |
555 | } | |
556 | if u != Nil { | |
557 | t.Fatalf("got %v on error, want Nil", u) | |
558 | } | |
559 | }) | |
560 | } | |
561 | ||
418 | 562 | func testNewV6ShortRandomRead(t *testing.T) { |
419 | 563 | g := &Gen{ |
420 | 564 | epochFunc: time.Now, |
421 | 565 | rand: bytes.NewReader([]byte{42}), |
422 | 566 | } |
567 | u, err := g.NewV6() | |
568 | if err == nil { | |
569 | t.Errorf("got %v, nil error", u) | |
570 | } | |
571 | } | |
572 | ||
573 | func testNewV6ShortRandomReadWithOptions(t *testing.T) { | |
574 | g := NewGenWithOptions( | |
575 | WithRandomReader(bytes.NewReader([]byte{42})), | |
576 | ) | |
423 | 577 | u, err := g.NewV6() |
424 | 578 | if err == nil { |
425 | 579 | t.Errorf("got %v, nil error", u) |
448 | 602 | |
449 | 603 | func testNewV7(t *testing.T) { |
450 | 604 | t.Run("Basic", makeTestNewV7Basic()) |
605 | t.Run("TestVector", makeTestNewV7TestVector()) | |
451 | 606 | t.Run("Basic10000000", makeTestNewV7Basic10000000()) |
452 | 607 | t.Run("DifferentAcrossCalls", makeTestNewV7DifferentAcrossCalls()) |
453 | 608 | t.Run("StaleEpoch", makeTestNewV7StaleEpoch()) |
609 | t.Run("StaleEpochWithOptions", makeTestNewV7StaleEpochWithOptions()) | |
454 | 610 | t.Run("FaultyRand", makeTestNewV7FaultyRand()) |
611 | t.Run("FaultyRandWithOptions", makeTestNewV7FaultyRandWithOptions()) | |
455 | 612 | t.Run("ShortRandomRead", makeTestNewV7ShortRandomRead()) |
456 | 613 | t.Run("KSortable", makeTestNewV7KSortable()) |
614 | t.Run("ClockSequence", makeTestNewV7ClockSequence()) | |
457 | 615 | } |
458 | 616 | |
459 | 617 | func makeTestNewV7Basic() func(t *testing.T) { |
467 | 625 | } |
468 | 626 | if got, want := u.Variant(), VariantRFC4122; got != want { |
469 | 627 | t.Errorf("got variant %d, want %d", got, want) |
628 | } | |
629 | } | |
630 | } | |
631 | ||
632 | // makeTestNewV7TestVector as defined in Draft04 | |
633 | func makeTestNewV7TestVector() func(t *testing.T) { | |
634 | return func(t *testing.T) { | |
635 | pRand := make([]byte, 10) | |
636 | //first 2 bytes will be read by clockSeq. First 4 bits will be overridden by Version. The next bits should be 0xCC3(3267) | |
637 | binary.LittleEndian.PutUint16(pRand[:2], uint16(0xCC3)) | |
638 | //8bytes will be read for rand_b. First 2 bits will be overridden by Variant | |
639 | binary.LittleEndian.PutUint64(pRand[2:], uint64(0x18C4DC0C0C07398F)) | |
640 | ||
641 | g := &Gen{ | |
642 | epochFunc: func() time.Time { | |
643 | return time.UnixMilli(1645557742000) | |
644 | }, | |
645 | rand: bytes.NewReader(pRand), | |
646 | } | |
647 | u, err := g.NewV7() | |
648 | if err != nil { | |
649 | t.Fatal(err) | |
650 | } | |
651 | if got, want := u.Version(), V7; got != want { | |
652 | t.Errorf("got version %d, want %d", got, want) | |
653 | } | |
654 | if got, want := u.Variant(), VariantRFC4122; got != want { | |
655 | t.Errorf("got variant %d, want %d", got, want) | |
656 | } | |
657 | if got, want := u.String()[:15], "017f22e2-79b0-7"; got != want { | |
658 | t.Errorf("got version %q, want %q", got, want) | |
470 | 659 | } |
471 | 660 | } |
472 | 661 | } |
534 | 723 | } |
535 | 724 | } |
536 | 725 | |
726 | func makeTestNewV7StaleEpochWithOptions() func(t *testing.T) { | |
727 | return func(t *testing.T) { | |
728 | g := NewGenWithOptions( | |
729 | WithEpochFunc(func() time.Time { | |
730 | return time.Unix(0, 0) | |
731 | }), | |
732 | ) | |
733 | u1, err := g.NewV7() | |
734 | if err != nil { | |
735 | t.Fatal(err) | |
736 | } | |
737 | u2, err := g.NewV7() | |
738 | if err != nil { | |
739 | t.Fatal(err) | |
740 | } | |
741 | if u1 == u2 { | |
742 | t.Errorf("generated identical UUIDs across calls: %v", u1) | |
743 | } | |
744 | } | |
745 | } | |
746 | ||
537 | 747 | func makeTestNewV7FaultyRand() func(t *testing.T) { |
538 | 748 | return func(t *testing.T) { |
539 | 749 | g := &Gen{ |
540 | 750 | epochFunc: time.Now, |
541 | 751 | rand: &faultyReader{ |
752 | readToFail: 0, | |
753 | }, | |
754 | } | |
755 | u, err := g.NewV7() | |
756 | if err == nil { | |
757 | t.Errorf("got %v, nil error for clockSequence", u) | |
758 | } | |
759 | ||
760 | g = &Gen{ | |
761 | epochFunc: time.Now, | |
762 | rand: &faultyReader{ | |
763 | readToFail: 1, | |
764 | }, | |
765 | } | |
766 | u, err = g.NewV7() | |
767 | if err == nil { | |
768 | t.Errorf("got %v, nil error rand_b", u) | |
769 | } | |
770 | } | |
771 | } | |
772 | ||
773 | func makeTestNewV7FaultyRandWithOptions() func(t *testing.T) { | |
774 | return func(t *testing.T) { | |
775 | g := NewGenWithOptions( | |
776 | WithRandomReader(&faultyReader{ | |
542 | 777 | readToFail: 0, // fail immediately |
543 | }, | |
544 | } | |
778 | }), | |
779 | ) | |
545 | 780 | u, err := g.NewV7() |
546 | 781 | if err == nil { |
547 | 782 | t.Errorf("got %v, nil error", u) |
555 | 790 | epochFunc: time.Now, |
556 | 791 | rand: bytes.NewReader([]byte{42}), |
557 | 792 | } |
793 | u, err := g.NewV7() | |
794 | if err == nil { | |
795 | t.Errorf("got %v, nil error", u) | |
796 | } | |
797 | } | |
798 | } | |
799 | ||
800 | func makeTestNewV7ShortRandomReadWithOptions() func(t *testing.T) { | |
801 | return func(t *testing.T) { | |
802 | g := NewGenWithOptions( | |
803 | WithRandomReader(bytes.NewReader([]byte{42})), | |
804 | ) | |
558 | 805 | u, err := g.NewV7() |
559 | 806 | if err == nil { |
560 | 807 | t.Errorf("got %v, nil error", u) |
583 | 830 | } |
584 | 831 | } |
585 | 832 | |
586 | func testNewV7ClockSequence(t *testing.T) { | |
587 | if testing.Short() { | |
588 | t.Skip("skipping test in short mode.") | |
589 | } | |
590 | ||
591 | g := NewGen() | |
592 | ||
593 | // hack to try and reduce race conditions based on when the test starts | |
594 | nsec := time.Now().Nanosecond() | |
595 | sleepDur := int(time.Second) - nsec | |
596 | time.Sleep(time.Duration(sleepDur)) | |
597 | ||
598 | u1, err := g.NewV7() | |
599 | if err != nil { | |
600 | t.Fatalf("failed to generate V7 UUID #1: %v", err) | |
601 | } | |
602 | ||
603 | u2, err := g.NewV7() | |
604 | if err != nil { | |
605 | t.Fatalf("failed to generate V7 UUID #2: %v", err) | |
606 | } | |
607 | ||
608 | time.Sleep(time.Millisecond) | |
609 | ||
610 | u3, err := g.NewV7() | |
611 | if err != nil { | |
612 | t.Fatalf("failed to generate V7 UUID #3: %v", err) | |
613 | } | |
614 | ||
615 | time.Sleep(time.Second) | |
616 | ||
617 | u4, err := g.NewV7() | |
618 | if err != nil { | |
619 | t.Fatalf("failed to generate V7 UUID #3: %v", err) | |
620 | } | |
621 | ||
622 | s1 := binary.BigEndian.Uint16(u1[6:8]) & 0xfff | |
623 | s2 := binary.BigEndian.Uint16(u2[6:8]) & 0xfff | |
624 | s3 := binary.BigEndian.Uint16(u3[6:8]) & 0xfff | |
625 | s4 := binary.BigEndian.Uint16(u4[6:8]) & 0xfff | |
626 | ||
627 | if s1 != 0 { | |
628 | t.Errorf("sequence 1 should be zero, was %d", s1) | |
629 | } | |
630 | ||
631 | if s2 != s1+1 { | |
632 | t.Errorf("sequence 2 expected to be one above sequence 1; seq 1: %d, seq 2: %d", s1, s2) | |
633 | } | |
634 | ||
635 | if s3 != 0 { | |
636 | t.Errorf("sequence 3 should be zero, was %d", s3) | |
637 | } | |
638 | ||
639 | if s4 != 0 { | |
640 | t.Errorf("sequence 4 should be zero, was %d", s4) | |
833 | func makeTestNewV7ClockSequence() func(t *testing.T) { | |
834 | return func(t *testing.T) { | |
835 | if testing.Short() { | |
836 | t.Skip("skipping test in short mode.") | |
837 | } | |
838 | ||
839 | g := NewGen() | |
840 | //always return the same TS | |
841 | g.epochFunc = func() time.Time { | |
842 | return time.UnixMilli(1645557742000) | |
843 | } | |
844 | //by being KSortable with the same timestamp, it means the sequence is Not empty, and it is monotonic | |
845 | uuids := make([]UUID, 10) | |
846 | for i := range uuids { | |
847 | u, err := g.NewV7() | |
848 | testErrCheck(t, "NewV7()", "", err) | |
849 | uuids[i] = u | |
850 | } | |
851 | ||
852 | for i := 1; i < len(uuids); i++ { | |
853 | p, n := uuids[i-1], uuids[i] | |
854 | isLess := p.String() < n.String() | |
855 | if !isLess { | |
856 | t.Errorf("uuids[%d] (%s) not less than uuids[%d] (%s)", i-1, p, i, n) | |
857 | } | |
858 | } | |
641 | 859 | } |
642 | 860 | } |
643 | 861 |
21 | 21 | package uuid |
22 | 22 | |
23 | 23 | import ( |
24 | "bytes" | |
24 | "database/sql" | |
25 | 25 | "database/sql/driver" |
26 | "encoding/json" | |
27 | 26 | "fmt" |
28 | 27 | ) |
28 | ||
29 | var _ driver.Valuer = UUID{} | |
30 | var _ sql.Scanner = (*UUID)(nil) | |
29 | 31 | |
30 | 32 | // Value implements the driver.Valuer interface. |
31 | 33 | func (u UUID) Value() (driver.Value, error) { |
48 | 50 | return u.UnmarshalText(src) |
49 | 51 | |
50 | 52 | case string: |
51 | return u.UnmarshalText([]byte(src)) | |
53 | uu, err := FromString(src) | |
54 | *u = uu | |
55 | return err | |
52 | 56 | } |
53 | 57 | |
54 | 58 | return fmt.Errorf("uuid: cannot convert %T to UUID", src) |
82 | 86 | return u.UUID.Scan(src) |
83 | 87 | } |
84 | 88 | |
89 | var nullJSON = []byte("null") | |
90 | ||
85 | 91 | // MarshalJSON marshals the NullUUID as null or the nested UUID |
86 | 92 | func (u NullUUID) MarshalJSON() ([]byte, error) { |
87 | 93 | if !u.Valid { |
88 | return json.Marshal(nil) | |
94 | return nullJSON, nil | |
89 | 95 | } |
90 | ||
91 | return json.Marshal(u.UUID) | |
96 | var buf [38]byte | |
97 | buf[0] = '"' | |
98 | encodeCanonical(buf[1:37], u.UUID) | |
99 | buf[37] = '"' | |
100 | return buf[:], nil | |
92 | 101 | } |
93 | 102 | |
94 | 103 | // UnmarshalJSON unmarshals a NullUUID |
95 | 104 | func (u *NullUUID) UnmarshalJSON(b []byte) error { |
96 | if bytes.Equal(b, []byte("null")) { | |
105 | if string(b) == "null" { | |
97 | 106 | u.UUID, u.Valid = Nil, false |
98 | 107 | return nil |
99 | 108 | } |
100 | ||
101 | if err := json.Unmarshal(b, &u.UUID); err != nil { | |
102 | return err | |
109 | if n := len(b); n >= 2 && b[0] == '"' { | |
110 | b = b[1 : n-1] | |
103 | 111 | } |
104 | ||
105 | u.Valid = true | |
106 | ||
107 | return nil | |
112 | err := u.UUID.UnmarshalText(b) | |
113 | u.Valid = (err == nil) | |
114 | return err | |
108 | 115 | } |
290 | 290 | t.Fatalf("u.UUID = %v, want %v", u.UUID, Nil) |
291 | 291 | } |
292 | 292 | } |
293 | ||
293 | 294 | func testNullUUIDUnmarshalJSONValid(t *testing.T) { |
294 | 295 | var u NullUUID |
295 | 296 | |
317 | 318 | t.Fatal("json.Unmarshal err = <nil>, want error") |
318 | 319 | } |
319 | 320 | } |
321 | ||
322 | func BenchmarkNullMarshalJSON(b *testing.B) { | |
323 | b.Run("Valid", func(b *testing.B) { | |
324 | u, err := FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") | |
325 | if err != nil { | |
326 | b.Fatal(err) | |
327 | } | |
328 | n := NullUUID{UUID: u, Valid: true} | |
329 | for i := 0; i < b.N; i++ { | |
330 | n.MarshalJSON() | |
331 | } | |
332 | }) | |
333 | b.Run("Invalid", func(b *testing.B) { | |
334 | n := NullUUID{Valid: false} | |
335 | for i := 0; i < b.N; i++ { | |
336 | n.MarshalJSON() | |
337 | } | |
338 | }) | |
339 | } | |
340 | ||
341 | func BenchmarkNullUnmarshalJSON(b *testing.B) { | |
342 | baseUUID, err := FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") | |
343 | if err != nil { | |
344 | b.Fatal(err) | |
345 | } | |
346 | data, err := json.Marshal(&baseUUID) | |
347 | if err != nil { | |
348 | b.Fatal(err) | |
349 | } | |
350 | ||
351 | b.Run("Valid", func(b *testing.B) { | |
352 | var u NullUUID | |
353 | for i := 0; i < b.N; i++ { | |
354 | u.UnmarshalJSON(data) | |
355 | } | |
356 | }) | |
357 | b.Run("Invalid", func(b *testing.B) { | |
358 | invalid := []byte("null") | |
359 | var n NullUUID | |
360 | for i := 0; i < b.N; i++ { | |
361 | n.UnmarshalJSON(invalid) | |
362 | } | |
363 | }) | |
364 | } |
43 | 43 | "encoding/binary" |
44 | 44 | "encoding/hex" |
45 | 45 | "fmt" |
46 | "io" | |
47 | "strings" | |
48 | 46 | "time" |
49 | 47 | ) |
50 | 48 | |
131 | 129 | |
132 | 130 | return Timestamp(uint64(low) + (uint64(mid) << 12) + (uint64(hi) << 28)), nil |
133 | 131 | } |
134 | ||
135 | // String parse helpers. | |
136 | var ( | |
137 | urnPrefix = []byte("urn:uuid:") | |
138 | byteGroups = []int{8, 4, 4, 4, 12} | |
139 | ) | |
140 | 132 | |
141 | 133 | // Nil is the nil UUID, as specified in RFC-4122, that has all 128 bits set to |
142 | 134 | // zero. |
181 | 173 | return u[:] |
182 | 174 | } |
183 | 175 | |
176 | // encodeCanonical encodes the canonical RFC-4122 form of UUID u into the | |
177 | // first 36 bytes dst. | |
178 | func encodeCanonical(dst []byte, u UUID) { | |
179 | const hextable = "0123456789abcdef" | |
180 | dst[8] = '-' | |
181 | dst[13] = '-' | |
182 | dst[18] = '-' | |
183 | dst[23] = '-' | |
184 | for i, x := range [16]byte{ | |
185 | 0, 2, 4, 6, | |
186 | 9, 11, | |
187 | 14, 16, | |
188 | 19, 21, | |
189 | 24, 26, 28, 30, 32, 34, | |
190 | } { | |
191 | c := u[i] | |
192 | dst[x] = hextable[c>>4] | |
193 | dst[x+1] = hextable[c&0x0f] | |
194 | } | |
195 | } | |
196 | ||
184 | 197 | // String returns a canonical RFC-4122 string representation of the UUID: |
185 | 198 | // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. |
186 | 199 | func (u UUID) String() string { |
187 | buf := make([]byte, 36) | |
188 | ||
189 | hex.Encode(buf[0:8], u[0:4]) | |
190 | buf[8] = '-' | |
191 | hex.Encode(buf[9:13], u[4:6]) | |
192 | buf[13] = '-' | |
193 | hex.Encode(buf[14:18], u[6:8]) | |
194 | buf[18] = '-' | |
195 | hex.Encode(buf[19:23], u[8:10]) | |
196 | buf[23] = '-' | |
197 | hex.Encode(buf[24:], u[10:]) | |
198 | ||
199 | return string(buf) | |
200 | var buf [36]byte | |
201 | encodeCanonical(buf[:], u) | |
202 | return string(buf[:]) | |
200 | 203 | } |
201 | 204 | |
202 | 205 | // Format implements fmt.Formatter for UUID values. |
209 | 212 | // All other verbs not handled directly by the fmt package (like '%p') are unsupported and will return |
210 | 213 | // "%!verb(uuid.UUID=value)" as recommended by the fmt package. |
211 | 214 | func (u UUID) Format(f fmt.State, c rune) { |
215 | if c == 'v' && f.Flag('#') { | |
216 | fmt.Fprintf(f, "%#v", [Size]byte(u)) | |
217 | return | |
218 | } | |
212 | 219 | switch c { |
213 | 220 | case 'x', 'X': |
214 | s := hex.EncodeToString(u.Bytes()) | |
221 | b := make([]byte, 32) | |
222 | hex.Encode(b, u[:]) | |
215 | 223 | if c == 'X' { |
216 | s = strings.Map(toCapitalHexDigits, s) | |
224 | toUpperHex(b) | |
217 | 225 | } |
218 | _, _ = io.WriteString(f, s) | |
219 | case 'v': | |
220 | var s string | |
221 | if f.Flag('#') { | |
222 | s = fmt.Sprintf("%#v", [Size]byte(u)) | |
223 | } else { | |
224 | s = u.String() | |
226 | _, _ = f.Write(b) | |
227 | case 'v', 's', 'S': | |
228 | b, _ := u.MarshalText() | |
229 | if c == 'S' { | |
230 | toUpperHex(b) | |
225 | 231 | } |
226 | _, _ = io.WriteString(f, s) | |
227 | case 's', 'S': | |
228 | s := u.String() | |
229 | if c == 'S' { | |
230 | s = strings.Map(toCapitalHexDigits, s) | |
231 | } | |
232 | _, _ = io.WriteString(f, s) | |
232 | _, _ = f.Write(b) | |
233 | 233 | case 'q': |
234 | _, _ = io.WriteString(f, `"`+u.String()+`"`) | |
234 | b := make([]byte, 38) | |
235 | b[0] = '"' | |
236 | encodeCanonical(b[1:], u) | |
237 | b[37] = '"' | |
238 | _, _ = f.Write(b) | |
235 | 239 | default: |
236 | 240 | // invalid/unsupported format verb |
237 | 241 | fmt.Fprintf(f, "%%!%c(uuid.UUID=%s)", c, u.String()) |
238 | 242 | } |
239 | 243 | } |
240 | 244 | |
241 | func toCapitalHexDigits(ch rune) rune { | |
242 | // convert a-f hex digits to A-F | |
243 | switch ch { | |
244 | case 'a': | |
245 | return 'A' | |
246 | case 'b': | |
247 | return 'B' | |
248 | case 'c': | |
249 | return 'C' | |
250 | case 'd': | |
251 | return 'D' | |
252 | case 'e': | |
253 | return 'E' | |
254 | case 'f': | |
255 | return 'F' | |
256 | default: | |
257 | return ch | |
245 | func toUpperHex(b []byte) { | |
246 | for i, c := range b { | |
247 | if 'a' <= c && c <= 'f' { | |
248 | b[i] = c - ('a' - 'A') | |
249 | } | |
258 | 250 | } |
259 | 251 | } |
260 | 252 | |
282 | 274 | // Must is a helper that wraps a call to a function returning (UUID, error) |
283 | 275 | // and panics if the error is non-nil. It is intended for use in variable |
284 | 276 | // initializations such as |
285 | // var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")) | |
277 | // | |
278 | // var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")) | |
286 | 279 | func Must(u UUID, err error) UUID { |
287 | 280 | if err != nil { |
288 | 281 | panic(err) |
23 | 23 | import ( |
24 | 24 | "bytes" |
25 | 25 | "fmt" |
26 | "io" | |
26 | 27 | "testing" |
27 | 28 | "time" |
28 | 29 | ) |
253 | 254 | } |
254 | 255 | } |
255 | 256 | } |
257 | ||
258 | func BenchmarkFormat(b *testing.B) { | |
259 | var tests = []string{ | |
260 | "%s", | |
261 | "%S", | |
262 | "%q", | |
263 | "%x", | |
264 | "%X", | |
265 | "%v", | |
266 | "%+v", | |
267 | "%#v", | |
268 | } | |
269 | for _, x := range tests { | |
270 | b.Run(x[1:], func(b *testing.B) { | |
271 | for i := 0; i < b.N; i++ { | |
272 | fmt.Fprintf(io.Discard, x, &codecTestUUID) | |
273 | } | |
274 | }) | |
275 | } | |
276 | } |