Skip to content

Commit

Permalink
Merge pull request #153 from microsoft/guid
Browse files Browse the repository at this point in the history
guid package improvements
  • Loading branch information
jterry75 authored Jul 19, 2019
2 parents 881e3d4 + c20fb68 commit a969fb0
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 16 deletions.
27 changes: 14 additions & 13 deletions pkg/etw/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,26 +94,27 @@ func providerCallbackAdapter(sourceID *guid.GUID, state uintptr, level uintptr,
// uses the same algorithm as used by .NET's EventSource class, which is based
// on RFC 4122. More information on the algorithm can be found here:
// https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/
// The algorithm is roughly:
// Hash = Sha1(namespace + arg.ToUpper().ToUtf16be())
// Guid = Hash[0..15], with Hash[7] tweaked according to RFC 4122
//
// The algorithm is roughly the RFC 4122 algorithm for a V5 UUID, but differs in
// the following ways:
// - The input name is first upper-cased, UTF16-encoded, and converted to
// big-endian.
// - No variant is set on the result UUID.
// - The result UUID is treated as being in little-endian format, rather than
// big-endian.
func providerIDFromName(name string) guid.GUID {
buffer := sha1.New()

namespace := []byte{0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8, 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB}
buffer.Write(namespace)

namespace := guid.GUID{0x482C2DB2, 0xC390, 0x47C8, [8]byte{0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB}}
namespaceBytes := namespace.ToArray()
buffer.Write(namespaceBytes[:])
binary.Write(buffer, binary.BigEndian, utf16.Encode([]rune(strings.ToUpper(name))))

sum := buffer.Sum(nil)
sum[7] = (sum[7] & 0xf) | 0x50

return guid.GUID{
Data1: binary.LittleEndian.Uint32(sum[0:4]),
Data2: binary.LittleEndian.Uint16(sum[4:6]),
Data3: binary.LittleEndian.Uint16(sum[6:8]),
Data4: [8]byte{sum[8], sum[9], sum[10], sum[11], sum[12], sum[13], sum[14], sum[15]},
}
a := [16]byte{}
copy(a[:], sum)
return guid.FromWindowsArray(a)
}

// NewProvider creates and registers a new ETW provider. The provider ID is
Expand Down
34 changes: 34 additions & 0 deletions pkg/etw/provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package etw

import (
"testing"

"github.com/Microsoft/go-winio/pkg/guid"
)

func mustGUIDFromString(t *testing.T, s string) guid.GUID {
g, err := guid.FromString(s)
if err != nil {
t.Fatal(err)
}
return g
}

func Test_ProviderIDFromName(t *testing.T) {
type testCase struct {
name string
g guid.GUID
}
testCases := []testCase{
{"wincni", mustGUIDFromString(t, "c822b598-f4cc-5a72-7933-ce2a816d033f")},
{"Moby", mustGUIDFromString(t, "6996f090-c5de-5082-a81e-5841acc3a635")},
{"ContainerD", mustGUIDFromString(t, "2acb92c0-eb9b-571a-69cf-8f3410f383ad")},
{"Microsoft.Virtualization.RunHCS", mustGUIDFromString(t, "0B52781F-B24D-5685-DDF6-69830ED40EC3")},
}
for _, tc := range testCases {
g := providerIDFromName(tc.name)
if g != tc.g {
t.Fatalf("Incorrect provider GUID.\nExpected: %s\nActual: %s", tc.g, g)
}
}
}
54 changes: 51 additions & 3 deletions pkg/guid/guid.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package guid

import (
"crypto/rand"
"crypto/sha1"
"encoding"
"encoding/binary"
"fmt"
Expand Down Expand Up @@ -52,10 +53,34 @@ func NewV4() (GUID, error) {
return GUID{}, err
}

b[6] = (b[6] & 0x0f) | 0x40 // Version 4 (randomly generated)
b[8] = (b[8] & 0x3f) | 0x80 // RFC4122 variant
g := FromArray(b)
g.setVersion(4) // Version 4 means randomly generated.
g.setVariant(VariantRFC4122)

return FromArray(b), nil
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()
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 {
Expand Down Expand Up @@ -150,6 +175,25 @@ func FromString(s string) (GUID, error) {
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]
Expand All @@ -165,6 +209,10 @@ func (g GUID) Variant() Variant {
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)
Expand Down
127 changes: 127 additions & 0 deletions pkg/guid/guid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ func mustNewV4(t *testing.T) GUID {
return g
}

func mustNewV5(t *testing.T, namespace GUID, name []byte) GUID {
g, err := NewV5(namespace, name)
if err != nil {
t.Fatal(err)
}
return g
}

func mustFromString(t *testing.T, s string) GUID {
g, err := FromString(s)
if err != nil {
Expand All @@ -22,6 +30,78 @@ func mustFromString(t *testing.T, s string) GUID {
return g
}

func Test_Variant(t *testing.T) {
type testCase struct {
g GUID
v Variant
}
testCases := []testCase{
{mustFromString(t, "f5cbc1a9-4cba-45a0-0fdd-b6761fc7dcc0"), VariantNCS},
{mustFromString(t, "f5cbc1a9-4cba-45a0-7fdd-b6761fc7dcc0"), VariantNCS},
{mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0"), VariantRFC4122},
{mustFromString(t, "f5cbc1a9-4cba-45a0-9fdd-b6761fc7dcc0"), VariantRFC4122},
{mustFromString(t, "f5cbc1a9-4cba-45a0-cfdd-b6761fc7dcc0"), VariantMicrosoft},
{mustFromString(t, "f5cbc1a9-4cba-45a0-dfdd-b6761fc7dcc0"), VariantMicrosoft},
{mustFromString(t, "f5cbc1a9-4cba-45a0-efdd-b6761fc7dcc0"), VariantFuture},
{mustFromString(t, "f5cbc1a9-4cba-45a0-ffdd-b6761fc7dcc0"), VariantFuture},
}
for _, tc := range testCases {
actualVariant := tc.g.Variant()
if actualVariant != tc.v {
t.Fatalf("Variant is not correct.\nExpected: %d\nActual: %d\nGUID: %s", tc.v, actualVariant, tc.g)
}
}
}

func Test_SetVariant(t *testing.T) {
testCases := []Variant{
VariantNCS,
VariantRFC4122,
VariantMicrosoft,
VariantFuture,
}
g := mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0")
for _, tc := range testCases {
t.Logf("Test case: %d", tc)
g.setVariant(tc)
if g.Variant() != tc {
t.Fatalf("Variant is incorrect.\nExpected: %d\nActual: %d", tc, g.Variant())
}
}
}

func Test_Version(t *testing.T) {
type testCase struct {
g GUID
v Version
}
testCases := []testCase{
{mustFromString(t, "f5cbc1a9-4cba-15a0-0fdd-b6761fc7dcc0"), 1},
{mustFromString(t, "f5cbc1a9-4cba-25a0-0fdd-b6761fc7dcc0"), 2},
{mustFromString(t, "f5cbc1a9-4cba-35a0-0fdd-b6761fc7dcc0"), 3},
{mustFromString(t, "f5cbc1a9-4cba-45a0-0fdd-b6761fc7dcc0"), 4},
{mustFromString(t, "f5cbc1a9-4cba-55a0-0fdd-b6761fc7dcc0"), 5},
}
for _, tc := range testCases {
actualVersion := tc.g.Version()
if actualVersion != tc.v {
t.Fatalf("Version is not correct.\nExpected: %d\nActual: %d\nGUID: %s", tc.v, actualVersion, tc.g)
}
}
}

func Test_SetVersion(t *testing.T) {
g := mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0")
for tc := 0; tc < 16; tc++ {
t.Logf("Test case: %d", tc)
v := Version(tc)
g.setVersion(v)
if g.Version() != v {
t.Fatalf("Version is incorrect.\nExpected: %d\nActual: %d", v, g.Version())
}
}
}

func Test_NewV4IsUnique(t *testing.T) {
g := mustNewV4(t)
g2 := mustNewV4(t)
Expand All @@ -40,6 +120,53 @@ func Test_V4HasCorrectVersionAndVariant(t *testing.T) {
}
}

func Test_V5HasCorrectVersionAndVariant(t *testing.T) {
namespace := mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0")
g := mustNewV5(t, namespace, []byte("Foo"))
if g.Version() != 5 {
t.Fatalf("Version is not 5: %s", g)
}
if g.Variant() != VariantRFC4122 {
t.Fatalf("Variant is not RFC4122: %s", g)
}
}

func Test_V5KnownValues(t *testing.T) {
type testCase struct {
ns GUID
name string
g GUID
}
testCases := []testCase{
{
mustFromString(t, "6ba7b810-9dad-11d1-80b4-00c04fd430c8"),
"www.sample.com",
mustFromString(t, "4e4463eb-b0e8-54fa-8c28-12d1ab1d45b3"),
},
{
mustFromString(t, "6ba7b811-9dad-11d1-80b4-00c04fd430c8"),
"https://www.sample.com/test",
mustFromString(t, "9e44625a-0d85-5e0a-99bc-8e8a77df5ea2"),
},
{
mustFromString(t, "6ba7b812-9dad-11d1-80b4-00c04fd430c8"),
"1.3.6.1.4.1.343",
mustFromString(t, "6aab0456-7392-582a-b92a-ba5a7096945d"),
},
{
mustFromString(t, "6ba7b814-9dad-11d1-80b4-00c04fd430c8"),
"CN=John Smith, ou=People, o=FakeCorp, L=Seattle, S=Washington, C=US",
mustFromString(t, "badff8dd-c869-5b64-a260-00092e66be00"),
},
}
for _, tc := range testCases {
g := mustNewV5(t, tc.ns, []byte(tc.name))
if g != tc.g {
t.Fatalf("GUIDs are not equal.\nExpected: %s\nActual: %s", tc.g, g)
}
}
}

func Test_ToArray(t *testing.T) {
g := mustFromString(t, "73c39589-192e-4c64-9acf-6c5d0aa18528")
b := g.ToArray()
Expand Down

0 comments on commit a969fb0

Please sign in to comment.