// Package guid provides a GUID type. The backing structure for a GUID is // identical to that used by the golang.org/x/sys/windows GUID type. // There are two main binary encodings used for a GUID, the big-endian encoding, // and the Windows (mixed-endian) encoding. See here for details: // https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding package guid import ( "crypto/rand" "crypto/sha1" //nolint:gosec // not used for secure application "encoding" "encoding/binary" "fmt" "strconv" ) //go:generate go run golang.org/x/tools/cmd/stringer -type=Variant -trimprefix=Variant -linecomment // Variant specifies which GUID variant (or "type") of the GUID. It determines // how the entirety of the rest of the GUID is interpreted. type Variant uint8 // The variants specified by RFC 4122 section 4.1.1. const ( // VariantUnknown specifies a GUID variant which does not conform to one of // the variant encodings specified in RFC 4122. VariantUnknown Variant = iota VariantNCS VariantRFC4122 // RFC 4122 VariantMicrosoft VariantFuture ) // Version specifies how the bits in the GUID were generated. For instance, a // version 4 GUID is randomly generated, and a version 5 is generated from the // hash of an input string. type Version uint8 func (v Version) String() string { return strconv.FormatUint(uint64(v), 10) } var _ = (encoding.TextMarshaler)(GUID{}) var _ = (encoding.TextUnmarshaler)(&GUID{}) // NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122. func NewV4() (GUID, error) { var b [16]byte if _, err := rand.Read(b[:]); err != nil { return GUID{}, err } g := FromArray(b) g.setVersion(4) // Version 4 means randomly generated. g.setVariant(VariantRFC4122) return g, nil } // NewV5 returns a new version 5 (generated from a string via SHA-1 hashing) // GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name, // and the sample code treats it as a series of bytes, so we do the same here. // // Some implementations, such as those found on Windows, treat the name as a // big-endian UTF16 stream of bytes. If that is desired, the string can be // encoded as such before being passed to this function. func NewV5(namespace GUID, name []byte) (GUID, error) { b := sha1.New() //nolint:gosec // not used for secure application namespaceBytes := namespace.ToArray() b.Write(namespaceBytes[:]) b.Write(name) a := [16]byte{} copy(a[:], b.Sum(nil)) g := FromArray(a) g.setVersion(5) // Version 5 means generated from a string. g.setVariant(VariantRFC4122) return g, nil } func fromArray(b [16]byte, order binary.ByteOrder) GUID { var g GUID g.Data1 = order.Uint32(b[0:4]) g.Data2 = order.Uint16(b[4:6]) g.Data3 = order.Uint16(b[6:8]) copy(g.Data4[:], b[8:16]) return g } func (g GUID) toArray(order binary.ByteOrder) [16]byte { b := [16]byte{} order.PutUint32(b[0:4], g.Data1) order.PutUint16(b[4:6], g.Data2) order.PutUint16(b[6:8], g.Data3) copy(b[8:16], g.Data4[:]) return b } // FromArray constructs a GUID from a big-endian encoding array of 16 bytes. func FromArray(b [16]byte) GUID { return fromArray(b, binary.BigEndian) } // ToArray returns an array of 16 bytes representing the GUID in big-endian // encoding. func (g GUID) ToArray() [16]byte { return g.toArray(binary.BigEndian) } // FromWindowsArray constructs a GUID from a Windows encoding array of bytes. func FromWindowsArray(b [16]byte) GUID { return fromArray(b, binary.LittleEndian) } // ToWindowsArray returns an array of 16 bytes representing the GUID in Windows // encoding. func (g GUID) ToWindowsArray() [16]byte { return g.toArray(binary.LittleEndian) } func (g GUID) String() string { return fmt.Sprintf( "%08x-%04x-%04x-%04x-%012x", g.Data1, g.Data2, g.Data3, g.Data4[:2], g.Data4[2:]) } // FromString parses a string containing a GUID and returns the GUID. The only // format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` // format. func FromString(s string) (GUID, error) { if len(s) != 36 { return GUID{}, fmt.Errorf("invalid GUID %q", s) } if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { return GUID{}, fmt.Errorf("invalid GUID %q", s) } var g GUID data1, err := strconv.ParseUint(s[0:8], 16, 32) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data1 = uint32(data1) data2, err := strconv.ParseUint(s[9:13], 16, 16) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data2 = uint16(data2) data3, err := strconv.ParseUint(s[14:18], 16, 16) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data3 = uint16(data3) for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} { v, err := strconv.ParseUint(s[x:x+2], 16, 8) if err != nil { return GUID{}, fmt.Errorf("invalid GUID %q", s) } g.Data4[i] = uint8(v) } return g, nil } func (g *GUID) setVariant(v Variant) { d := g.Data4[0] switch v { case VariantNCS: d = (d & 0x7f) case VariantRFC4122: d = (d & 0x3f) | 0x80 case VariantMicrosoft: d = (d & 0x1f) | 0xc0 case VariantFuture: d = (d & 0x0f) | 0xe0 case VariantUnknown: fallthrough default: panic(fmt.Sprintf("invalid variant: %d", v)) } g.Data4[0] = d } // Variant returns the GUID variant, as defined in RFC 4122. func (g GUID) Variant() Variant { b := g.Data4[0] if b&0x80 == 0 { return VariantNCS } else if b&0xc0 == 0x80 { return VariantRFC4122 } else if b&0xe0 == 0xc0 { return VariantMicrosoft } else if b&0xe0 == 0xe0 { return VariantFuture } return VariantUnknown } func (g *GUID) setVersion(v Version) { g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12) } // Version returns the GUID version, as defined in RFC 4122. func (g GUID) Version() Version { return Version((g.Data3 & 0xF000) >> 12) } // MarshalText returns the textual representation of the GUID. func (g GUID) MarshalText() ([]byte, error) { return []byte(g.String()), nil } // UnmarshalText takes the textual representation of a GUID, and unmarhals it // into this GUID. func (g *GUID) UnmarshalText(text []byte) error { g2, err := FromString(string(text)) if err != nil { return err } *g = g2 return nil }