main
Raw Download raw file
  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}