uuid.*UUID.decodeCanonical   B
last analyzed

Complexity

Conditions 8

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 14
dl 0
loc 21
rs 7.3333
c 0
b 0
f 0
nop 1
1
// Copyright (C) 2013-2018 by Maxim Bublis <[email protected]>
2
//
3
// Permission is hereby granted, free of charge, to any person obtaining
4
// a copy of this software and associated documentation files (the
5
// "Software"), to deal in the Software without restriction, including
6
// without limitation the rights to use, copy, modify, merge, publish,
7
// distribute, sublicense, and/or sell copies of the Software, and to
8
// permit persons to whom the Software is furnished to do so, subject to
9
// the following conditions:
10
//
11
// The above copyright notice and this permission notice shall be
12
// included in all copies or substantial portions of the Software.
13
//
14
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22
// Package uuid is a port of https://github.com/gofrs/uuid with functions
23
// only for parsing UUID from string.
24
package uuid
25
26
import (
27
	"bytes"
28
	"encoding/hex"
29
	"errors"
30
	"fmt"
31
)
32
33
var (
34
	ErrTooShort               = errors.New("too short")
35
	ErrTooLong                = errors.New("too long")
36
	ErrInvalidHyphenPlacement = errors.New("invalid hyphen placement")
37
	ErrInvalid                = errors.New("invalid")
38
)
39
40
// Size of a UUID in bytes.
41
const Size = 16
42
43
// UUID is an array type to represent the value of a UUID, as defined in RFC-4122.
44
type UUID [Size]byte
45
46
// UUID versions.
47
const (
48
	_  byte = iota
49
	V1      // Version 1 (date-time and MAC address)
50
	_       // Version 2 (date-time and MAC address, DCE security version) [removed]
51
	V3      // Version 3 (namespace name-based)
52
	V4      // Version 4 (random)
53
	V5      // Version 5 (namespace name-based)
54
	V6      // Version 6 (k-sortable timestamp and random data, field-compatible with v1) [peabody draft]
55
	V7      // Version 7 (k-sortable timestamp and random data) [peabody draft]
56
	_       // Version 8 (k-sortable timestamp, meant for custom implementations) [peabody draft] [not implemented]
57
)
58
59
// UUID layout variants.
60
const (
61
	VariantNCS byte = iota
62
	VariantRFC4122
63
	VariantMicrosoft
64
	VariantFuture
65
)
66
67
// FromString returns a UUID parsed from the input string.
68
// Input is expected in a form accepted by UnmarshalText.
69
func FromString(input string) (UUID, error) {
70
	u := UUID{}
71
	err := u.UnmarshalText([]byte(input))
72
	return u, err
73
}
74
75
func CanonicalFromString(input string) (UUID, error) {
76
	u := UUID{}
77
	if len(input) < 36 {
78
		return u, ErrTooShort
79
	}
80
	if len(input) > 36 {
81
		return u, ErrTooLong
82
	}
83
84
	err := u.decodeCanonical([]byte(input))
85
	return u, err
86
}
87
88
// String parse helpers.
89
var (
90
	urnPrefix  = []byte("urn:uuid:")
91
	byteGroups = []int{8, 4, 4, 4, 12}
92
)
93
94
// Nil is the nil UUID, as specified in RFC-4122, that has all 128 bits set to
95
// zero.
96
var Nil = UUID{}
97
98
// IsNil returns if the UUID is equal to the nil UUID.
99
func (u UUID) IsNil() bool {
100
	return u == Nil
101
}
102
103
// Version returns the algorithm version used to generate the UUID.
104
func (u UUID) Version() byte {
105
	return u[6] >> 4
106
}
107
108
func (u UUID) ValidVersion(versions ...byte) bool {
109
	if len(versions) == 0 {
110
		versions = []byte{1, 2, 3, 4, 5, 6, 7}
111
	}
112
113
	for _, version := range versions {
114
		if u.Version() == version {
115
			return true
116
		}
117
	}
118
119
	return false
120
}
121
122
// Variant returns the UUID layout variant.
123
func (u UUID) Variant() byte {
124
	switch {
125
	case (u[8] >> 7) == 0x00:
126
		return VariantNCS
127
	case (u[8] >> 6) == 0x02:
128
		return VariantRFC4122
129
	case (u[8] >> 5) == 0x06:
130
		return VariantMicrosoft
131
	case (u[8] >> 5) == 0x07:
132
		fallthrough
133
	default:
134
		return VariantFuture
135
	}
136
}
137
138
// UnmarshalText implements the encoding.TextUnmarshaler interface.
139
// Following formats are supported:
140
//
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"
147
//
148
// ABNF for supported UUID text representation follows:
149
//
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(text []byte) error {
172
	if len(text) < 32 {
173
		return ErrTooShort
174
	}
175
	if len(text) > 45 {
176
		return ErrTooLong
177
	}
178
179
	switch len(text) {
180
	case 32:
181
		return u.decodeHashLike(text)
182
	case 34, 38:
183
		return u.decodeBraced(text)
184
	case 36:
185
		return u.decodeCanonical(text)
186
	case 41, 45:
187
		return u.decodeURN(text)
188
	default:
189
		return fmt.Errorf("%w: incorrect UUID length %d in string %q", ErrInvalid, len(text), text)
190
	}
191
}
192
193
// decodeCanonical decodes UUID strings that are formatted as defined in RFC-4122 (section 3):
194
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8".
195
func (u *UUID) decodeCanonical(t []byte) error {
196
	if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' {
197
		return ErrInvalidHyphenPlacement
198
	}
199
200
	src := t
201
	dst := u[:]
202
203
	for i, byteGroup := range byteGroups {
204
		if i > 0 {
205
			src = src[1:] // skip dash
206
		}
207
		_, err := hex.Decode(dst[:byteGroup/2], src[:byteGroup])
208
		if err != nil {
209
			return err
210
		}
211
		src = src[byteGroup:]
212
		dst = dst[byteGroup/2:]
213
	}
214
215
	return nil
216
}
217
218
// decodeHashLike decodes UUID strings that are using the following format:
219
//
220
//	"6ba7b8109dad11d180b400c04fd430c8".
221
func (u *UUID) decodeHashLike(t []byte) error {
222
	_, err := hex.Decode(u[:], t)
223
	return err
224
}
225
226
// decodeBraced decodes UUID strings that are using the following formats:
227
//
228
//	"{6ba7b810-9dad-11d1-80b4-00c04fd430c8}"
229
//	"{6ba7b8109dad11d180b400c04fd430c8}".
230
func (u *UUID) decodeBraced(t []byte) error {
231
	l := len(t)
232
233
	if t[0] != '{' || t[l-1] != '}' {
234
		return fmt.Errorf("%w: incorrect UUID format in string %q", ErrInvalid, t)
235
	}
236
237
	return u.decodePlain(t[1 : l-1])
238
}
239
240
// decodeURN decodes UUID strings that are using the following formats:
241
//
242
//	"urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
243
//	"urn:uuid:6ba7b8109dad11d180b400c04fd430c8".
244
func (u *UUID) decodeURN(t []byte) error {
245
	total := len(t)
246
247
	urnUUIDPrefix := t[:9]
248
249
	if !bytes.Equal(urnUUIDPrefix, urnPrefix) {
250
		return fmt.Errorf("%w: incorrect UUID format in string %q", ErrInvalid, t)
251
	}
252
253
	return u.decodePlain(t[9:total])
254
}
255
256
// decodePlain decodes UUID strings that are using the following formats:
257
//
258
//	"6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format
259
//	"6ba7b8109dad11d180b400c04fd430c8".
260
func (u *UUID) decodePlain(t []byte) error {
261
	switch len(t) {
262
	case 32:
263
		return u.decodeHashLike(t)
264
	case 36:
265
		return u.decodeCanonical(t)
266
	default:
267
		return fmt.Errorf("%w: incorrect UUID length %d in string %q", ErrInvalid, len(t), t)
268
	}
269
}
270