main
1// Package guid provides a GUID type. The backing structure for a GUID is
2// identical to that used by the golang.org/x/sys/windows GUID type.
3// There are two main binary encodings used for a GUID, the big-endian encoding,
4// and the Windows (mixed-endian) encoding. See here for details:
5// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
6package guid
7
8import (
9 "crypto/rand"
10 "crypto/sha1" //nolint:gosec // not used for secure application
11 "encoding"
12 "encoding/binary"
13 "fmt"
14 "strconv"
15)
16
17//go:generate go run golang.org/x/tools/cmd/stringer -type=Variant -trimprefix=Variant -linecomment
18
19// Variant specifies which GUID variant (or "type") of the GUID. It determines
20// how the entirety of the rest of the GUID is interpreted.
21type Variant uint8
22
23// The variants specified by RFC 4122 section 4.1.1.
24const (
25 // VariantUnknown specifies a GUID variant which does not conform to one of
26 // the variant encodings specified in RFC 4122.
27 VariantUnknown Variant = iota
28 VariantNCS
29 VariantRFC4122 // RFC 4122
30 VariantMicrosoft
31 VariantFuture
32)
33
34// Version specifies how the bits in the GUID were generated. For instance, a
35// version 4 GUID is randomly generated, and a version 5 is generated from the
36// hash of an input string.
37type Version uint8
38
39func (v Version) String() string {
40 return strconv.FormatUint(uint64(v), 10)
41}
42
43var _ = (encoding.TextMarshaler)(GUID{})
44var _ = (encoding.TextUnmarshaler)(&GUID{})
45
46// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
47func NewV4() (GUID, error) {
48 var b [16]byte
49 if _, err := rand.Read(b[:]); err != nil {
50 return GUID{}, err
51 }
52
53 g := FromArray(b)
54 g.setVersion(4) // Version 4 means randomly generated.
55 g.setVariant(VariantRFC4122)
56
57 return g, nil
58}
59
60// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing)
61// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name,
62// and the sample code treats it as a series of bytes, so we do the same here.
63//
64// Some implementations, such as those found on Windows, treat the name as a
65// big-endian UTF16 stream of bytes. If that is desired, the string can be
66// encoded as such before being passed to this function.
67func NewV5(namespace GUID, name []byte) (GUID, error) {
68 b := sha1.New() //nolint:gosec // not used for secure application
69 namespaceBytes := namespace.ToArray()
70 b.Write(namespaceBytes[:])
71 b.Write(name)
72
73 a := [16]byte{}
74 copy(a[:], b.Sum(nil))
75
76 g := FromArray(a)
77 g.setVersion(5) // Version 5 means generated from a string.
78 g.setVariant(VariantRFC4122)
79
80 return g, nil
81}
82
83func fromArray(b [16]byte, order binary.ByteOrder) GUID {
84 var g GUID
85 g.Data1 = order.Uint32(b[0:4])
86 g.Data2 = order.Uint16(b[4:6])
87 g.Data3 = order.Uint16(b[6:8])
88 copy(g.Data4[:], b[8:16])
89 return g
90}
91
92func (g GUID) toArray(order binary.ByteOrder) [16]byte {
93 b := [16]byte{}
94 order.PutUint32(b[0:4], g.Data1)
95 order.PutUint16(b[4:6], g.Data2)
96 order.PutUint16(b[6:8], g.Data3)
97 copy(b[8:16], g.Data4[:])
98 return b
99}
100
101// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
102func FromArray(b [16]byte) GUID {
103 return fromArray(b, binary.BigEndian)
104}
105
106// ToArray returns an array of 16 bytes representing the GUID in big-endian
107// encoding.
108func (g GUID) ToArray() [16]byte {
109 return g.toArray(binary.BigEndian)
110}
111
112// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
113func FromWindowsArray(b [16]byte) GUID {
114 return fromArray(b, binary.LittleEndian)
115}
116
117// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
118// encoding.
119func (g GUID) ToWindowsArray() [16]byte {
120 return g.toArray(binary.LittleEndian)
121}
122
123func (g GUID) String() string {
124 return fmt.Sprintf(
125 "%08x-%04x-%04x-%04x-%012x",
126 g.Data1,
127 g.Data2,
128 g.Data3,
129 g.Data4[:2],
130 g.Data4[2:])
131}
132
133// FromString parses a string containing a GUID and returns the GUID. The only
134// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
135// format.
136func FromString(s string) (GUID, error) {
137 if len(s) != 36 {
138 return GUID{}, fmt.Errorf("invalid GUID %q", s)
139 }
140 if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
141 return GUID{}, fmt.Errorf("invalid GUID %q", s)
142 }
143
144 var g GUID
145
146 data1, err := strconv.ParseUint(s[0:8], 16, 32)
147 if err != nil {
148 return GUID{}, fmt.Errorf("invalid GUID %q", s)
149 }
150 g.Data1 = uint32(data1)
151
152 data2, err := strconv.ParseUint(s[9:13], 16, 16)
153 if err != nil {
154 return GUID{}, fmt.Errorf("invalid GUID %q", s)
155 }
156 g.Data2 = uint16(data2)
157
158 data3, err := strconv.ParseUint(s[14:18], 16, 16)
159 if err != nil {
160 return GUID{}, fmt.Errorf("invalid GUID %q", s)
161 }
162 g.Data3 = uint16(data3)
163
164 for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
165 v, err := strconv.ParseUint(s[x:x+2], 16, 8)
166 if err != nil {
167 return GUID{}, fmt.Errorf("invalid GUID %q", s)
168 }
169 g.Data4[i] = uint8(v)
170 }
171
172 return g, nil
173}
174
175func (g *GUID) setVariant(v Variant) {
176 d := g.Data4[0]
177 switch v {
178 case VariantNCS:
179 d = (d & 0x7f)
180 case VariantRFC4122:
181 d = (d & 0x3f) | 0x80
182 case VariantMicrosoft:
183 d = (d & 0x1f) | 0xc0
184 case VariantFuture:
185 d = (d & 0x0f) | 0xe0
186 case VariantUnknown:
187 fallthrough
188 default:
189 panic(fmt.Sprintf("invalid variant: %d", v))
190 }
191 g.Data4[0] = d
192}
193
194// Variant returns the GUID variant, as defined in RFC 4122.
195func (g GUID) Variant() Variant {
196 b := g.Data4[0]
197 if b&0x80 == 0 {
198 return VariantNCS
199 } else if b&0xc0 == 0x80 {
200 return VariantRFC4122
201 } else if b&0xe0 == 0xc0 {
202 return VariantMicrosoft
203 } else if b&0xe0 == 0xe0 {
204 return VariantFuture
205 }
206 return VariantUnknown
207}
208
209func (g *GUID) setVersion(v Version) {
210 g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12)
211}
212
213// Version returns the GUID version, as defined in RFC 4122.
214func (g GUID) Version() Version {
215 return Version((g.Data3 & 0xF000) >> 12)
216}
217
218// MarshalText returns the textual representation of the GUID.
219func (g GUID) MarshalText() ([]byte, error) {
220 return []byte(g.String()), nil
221}
222
223// UnmarshalText takes the textual representation of a GUID, and unmarhals it
224// into this GUID.
225func (g *GUID) UnmarshalText(text []byte) error {
226 g2, err := FromString(string(text))
227 if err != nil {
228 return err
229 }
230 *g = g2
231 return nil
232}