codec.go raw
1 // Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
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
23
24 import (
25 "errors"
26 "fmt"
27 )
28
29 // FromBytes returns a UUID generated from the raw byte slice input.
30 // It will return an error if the slice isn't 16 bytes long.
31 func FromBytes(input []byte) (UUID, error) {
32 u := UUID{}
33 err := u.UnmarshalBinary(input)
34 return u, err
35 }
36
37 // FromBytesOrNil returns a UUID generated from the raw byte slice input.
38 // Same behavior as FromBytes(), but returns uuid.Nil instead of an error.
39 func FromBytesOrNil(input []byte) UUID {
40 uuid, err := FromBytes(input)
41 if err != nil {
42 return Nil
43 }
44 return uuid
45 }
46
47 var errInvalidFormat = errors.New("uuid: invalid UUID format")
48
49 func fromHexChar(c byte) byte {
50 switch {
51 case '0' <= c && c <= '9':
52 return c - '0'
53 case 'a' <= c && c <= 'f':
54 return c - 'a' + 10
55 case 'A' <= c && c <= 'F':
56 return c - 'A' + 10
57 }
58 return 255
59 }
60
61 // Parse parses the UUID stored in the string text. Parsing and supported
62 // formats are the same as UnmarshalText.
63 func (u *UUID) Parse(s string) error {
64 switch len(s) {
65 case 32: // hash
66 case 36: // canonical
67 case 34, 38:
68 if s[0] != '{' || s[len(s)-1] != '}' {
69 return fmt.Errorf("uuid: incorrect UUID format in string %q", s)
70 }
71 s = s[1 : len(s)-1]
72 case 41, 45:
73 if s[:9] != "urn:uuid:" {
74 return fmt.Errorf("uuid: incorrect UUID format in string %q", s[:9])
75 }
76 s = s[9:]
77 default:
78 return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(s), s)
79 }
80 // canonical
81 if len(s) == 36 {
82 if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
83 return fmt.Errorf("uuid: incorrect UUID format in string %q", s)
84 }
85 for i, x := range [16]byte{
86 0, 2, 4, 6,
87 9, 11,
88 14, 16,
89 19, 21,
90 24, 26, 28, 30, 32, 34,
91 } {
92 v1 := fromHexChar(s[x])
93 v2 := fromHexChar(s[x+1])
94 if v1|v2 == 255 {
95 return errInvalidFormat
96 }
97 u[i] = (v1 << 4) | v2
98 }
99 return nil
100 }
101 // hash like
102 for i := 0; i < 32; i += 2 {
103 v1 := fromHexChar(s[i])
104 v2 := fromHexChar(s[i+1])
105 if v1|v2 == 255 {
106 return errInvalidFormat
107 }
108 u[i/2] = (v1 << 4) | v2
109 }
110 return nil
111 }
112
113 // FromString returns a UUID parsed from the input string.
114 // Input is expected in a form accepted by UnmarshalText.
115 func FromString(text string) (UUID, error) {
116 var u UUID
117 err := u.Parse(text)
118 return u, err
119 }
120
121 // FromStringOrNil returns a UUID parsed from the input string.
122 // Same behavior as FromString(), but returns uuid.Nil instead of an error.
123 func FromStringOrNil(input string) UUID {
124 uuid, err := FromString(input)
125 if err != nil {
126 return Nil
127 }
128 return uuid
129 }
130
131 // MarshalText implements the encoding.TextMarshaler interface.
132 // The encoding is the same as returned by the String() method.
133 func (u UUID) MarshalText() ([]byte, error) {
134 var buf [36]byte
135 encodeCanonical(buf[:], u)
136 return buf[:], nil
137 }
138
139 // UnmarshalText implements the encoding.TextUnmarshaler interface.
140 // Following formats are supported:
141 //
142 // "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
143 // "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}",
144 // "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
145 // "6ba7b8109dad11d180b400c04fd430c8"
146 // "{6ba7b8109dad11d180b400c04fd430c8}",
147 // "urn:uuid:6ba7b8109dad11d180b400c04fd430c8"
148 //
149 // ABNF for supported UUID text representation follows:
150 //
151 // URN := 'urn'
152 // UUID-NID := 'uuid'
153 //
154 // hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
155 // 'a' | 'b' | 'c' | 'd' | 'e' | 'f' |
156 // 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
157 //
158 // hexoct := hexdig hexdig
159 // 2hexoct := hexoct hexoct
160 // 4hexoct := 2hexoct 2hexoct
161 // 6hexoct := 4hexoct 2hexoct
162 // 12hexoct := 6hexoct 6hexoct
163 //
164 // hashlike := 12hexoct
165 // canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct
166 //
167 // plain := canonical | hashlike
168 // uuid := canonical | hashlike | braced | urn
169 //
170 // braced := '{' plain '}' | '{' hashlike '}'
171 // urn := URN ':' UUID-NID ':' plain
172 func (u *UUID) UnmarshalText(b []byte) error {
173 switch len(b) {
174 case 32: // hash
175 case 36: // canonical
176 case 34, 38:
177 if b[0] != '{' || b[len(b)-1] != '}' {
178 return fmt.Errorf("uuid: incorrect UUID format in string %q", b)
179 }
180 b = b[1 : len(b)-1]
181 case 41, 45:
182 if string(b[:9]) != "urn:uuid:" {
183 return fmt.Errorf("uuid: incorrect UUID format in string %q", b[:9])
184 }
185 b = b[9:]
186 default:
187 return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(b), b)
188 }
189 if len(b) == 36 {
190 if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
191 return fmt.Errorf("uuid: incorrect UUID format in string %q", b)
192 }
193 for i, x := range [16]byte{
194 0, 2, 4, 6,
195 9, 11,
196 14, 16,
197 19, 21,
198 24, 26, 28, 30, 32, 34,
199 } {
200 v1 := fromHexChar(b[x])
201 v2 := fromHexChar(b[x+1])
202 if v1|v2 == 255 {
203 return errInvalidFormat
204 }
205 u[i] = (v1 << 4) | v2
206 }
207 return nil
208 }
209 for i := 0; i < 32; i += 2 {
210 v1 := fromHexChar(b[i])
211 v2 := fromHexChar(b[i+1])
212 if v1|v2 == 255 {
213 return errInvalidFormat
214 }
215 u[i/2] = (v1 << 4) | v2
216 }
217 return nil
218 }
219
220 // MarshalBinary implements the encoding.BinaryMarshaler interface.
221 func (u UUID) MarshalBinary() ([]byte, error) {
222 return u.Bytes(), nil
223 }
224
225 // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
226 // It will return an error if the slice isn't 16 bytes long.
227 func (u *UUID) UnmarshalBinary(data []byte) error {
228 if len(data) != Size {
229 return fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data))
230 }
231 copy(u[:], data)
232
233 return nil
234 }
235