github.com/Serizao/go-winio@v0.0.0-20230906082528-f02f7f4ad6e8/pkg/guid/guid.go (about) 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 6 package guid 7 8 import ( 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. 21 type Variant uint8 22 23 // The variants specified by RFC 4122 section 4.1.1. 24 const ( 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. 37 type Version uint8 38 39 func (v Version) String() string { 40 return strconv.FormatUint(uint64(v), 10) 41 } 42 43 var _ = (encoding.TextMarshaler)(GUID{}) 44 var _ = (encoding.TextUnmarshaler)(&GUID{}) 45 46 // NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122. 47 func 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. 67 func 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 83 func 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 92 func (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. 102 func 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. 108 func (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. 113 func 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. 119 func (g GUID) ToWindowsArray() [16]byte { 120 return g.toArray(binary.LittleEndian) 121 } 122 123 func (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. 136 func 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 175 func (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. 195 func (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 209 func (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. 214 func (g GUID) Version() Version { 215 return Version((g.Data3 & 0xF000) >> 12) 216 } 217 218 // MarshalText returns the textual representation of the GUID. 219 func (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. 225 func (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 }