diff --git a/sdk/internal/recording/recording.go b/sdk/internal/recording/recording.go index 6473e83fac15..26d34cd57a07 100644 --- a/sdk/internal/recording/recording.go +++ b/sdk/internal/recording/recording.go @@ -211,11 +211,6 @@ func (r *Recording) Now() time.Time { func (r *Recording) UUID() uuid.UUID { r.initRandomSource() - - if r.Mode == Live { - return uuid.New() - } - u := uuid.UUID{} // Set all bits to randomly (or pseudo-randomly) chosen values. // math/rand.Read() is no-fail so we omit any error checking. diff --git a/sdk/internal/recording/request_matcher_test.go b/sdk/internal/recording/request_matcher_test.go index 8a90f815e94e..c65eac65a3e0 100644 --- a/sdk/internal/recording/request_matcher_test.go +++ b/sdk/internal/recording/request_matcher_test.go @@ -51,6 +51,14 @@ func (s *requestMatcherTests) TestCompareBodies() { assert.False(isMatch) } +func newUUID(t *testing.T) string { + u, err := uuid.New() + if err != nil { + t.Fatal(err) + } + return u.String() +} + func (s *requestMatcherTests) TestCompareHeadersIgnoresIgnoredHeaders() { assert := assert.New(s.T()) context := NewTestContext(func(msg string) { assert.FailNow(msg) }, func(msg string) { s.T().Log(msg) }, func() string { return s.T().Name() }) @@ -60,8 +68,8 @@ func (s *requestMatcherTests) TestCompareHeadersIgnoresIgnoredHeaders() { reqHeaders := make(http.Header) recordedHeaders := make(http.Header) for headerName := range ignoredHeaders { - reqHeaders[headerName] = []string{uuid.New().String()} - recordedHeaders[headerName] = []string{uuid.New().String()} + reqHeaders[headerName] = []string{newUUID(s.T())} + recordedHeaders[headerName] = []string{newUUID(s.T())} } req := http.Request{Header: reqHeaders} diff --git a/sdk/internal/uuid/uuid.go b/sdk/internal/uuid/uuid.go index 4261c3ccb335..3670361c9e86 100644 --- a/sdk/internal/uuid/uuid.go +++ b/sdk/internal/uuid/uuid.go @@ -8,6 +8,7 @@ package uuid import ( "crypto/rand" + "errors" "fmt" "strconv" ) @@ -17,64 +18,59 @@ const ( reservedRFC4122 byte = 0x40 ) -// A UUID representation compliant with specification in RFC 4122 document. +// A UUID representation compliant with specification in RFC4122 document. type UUID [16]byte -// New returns a new uuid using RFC 4122 algorithm. -func New() UUID { +// New returns a new UUID using the RFC4122 algorithm. +func New() (UUID, error) { u := UUID{} // Set all bits to pseudo-random values. // NOTE: this takes a process-wide lock _, err := rand.Read(u[:]) if err != nil { - panic(err) //We never expect to get here + return u, err } u[8] = (u[8] | reservedRFC4122) & 0x7F // u.setVariant(ReservedRFC4122) var version byte = 4 u[6] = (u[6] & 0xF) | (version << 4) // u.setVersion(4) - return u + return u, nil } -// String returns an unparsed version of the generated UUID sequence. +// String returns the UUID in "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" format. func (u UUID) String() string { return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:]) } -// Parse parses a string formatted as "003020100-0504-0706-0809-0a0b0c0d0e0f" -// or "{03020100-0504-0706-0809-0a0b0c0d0e0f}" into a UUID. -func Parse(uuidStr string) UUID { - char := func(hexString string) byte { - i, _ := strconv.ParseUint(hexString, 16, 8) - return byte(i) +// Parse parses a string formatted as "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +// or "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" into a UUID. +func Parse(s string) (UUID, error) { + var uuid UUID + // ensure format + switch len(s) { + case 36: + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 38: + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + s = s[1:37] + default: + return uuid, errors.New("invalid UUID format") } - if uuidStr[0] == '{' { - uuidStr = uuidStr[1:] // Skip over the '{' + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return uuid, errors.New("invalid UUID format") } - // 03020100 - 05 04 - 07 06 - 08 09 - 0a 0b 0c 0d 0e 0f - // 1 11 1 11 11 1 12 22 2 22 22 22 33 33 33 - // 01234567 8 90 12 3 45 67 8 90 12 3 45 67 89 01 23 45 - uuidVal := UUID{ - char(uuidStr[0:2]), - char(uuidStr[2:4]), - char(uuidStr[4:6]), - char(uuidStr[6:8]), - - char(uuidStr[9:11]), - char(uuidStr[11:13]), - - char(uuidStr[14:16]), - char(uuidStr[16:18]), - - char(uuidStr[19:21]), - char(uuidStr[21:23]), - - char(uuidStr[24:26]), - char(uuidStr[26:28]), - char(uuidStr[28:30]), - char(uuidStr[30:32]), - char(uuidStr[32:34]), - char(uuidStr[34:36]), + // parse chunks + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + b, err := strconv.ParseUint(s[x:x+2], 16, 8) + if err != nil { + return uuid, fmt.Errorf("invalid UUID format: %s", err) + } + uuid[i] = byte(b) } - return uuidVal + return uuid, nil } diff --git a/sdk/internal/uuid/uuid_test.go b/sdk/internal/uuid/uuid_test.go new file mode 100644 index 000000000000..9e14d53c07f0 --- /dev/null +++ b/sdk/internal/uuid/uuid_test.go @@ -0,0 +1,56 @@ +//go:build go1.16 +// +build go1.16 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package uuid + +import ( + "reflect" + "regexp" + "testing" +) + +func TestNew(t *testing.T) { + u, err := New() + if err != nil { + t.Fatal(err) + } + if reflect.ValueOf(u).IsZero() { + t.Fatal("unexpected zero-value UUID") + } + s := u.String() + match, err := regexp.MatchString(`[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`, s) + if err != nil { + t.Fatal(err) + } + if !match { + t.Fatalf("invalid UUID string %s", s) + } +} + +func TestParse(t *testing.T) { + testCases := []string{ + "72d0f24f-82be-4016-729d-31fd13bd681e", + "{72d0f24f-82be-4016-729d-31fd13bd681e}", + } + for _, input := range testCases { + t.Run(input, func(t *testing.T) { + u, err := Parse(input) + if err != nil { + t.Fatal(err) + } + if reflect.ValueOf(u).IsZero() { + t.Fatal("unexpected zero-value UUID") + } + if len(input) > 36 { + // strip off the {} as String() doesn't output them + input = input[1:37] + } + if s := u.String(); s != input { + t.Fatalf("didn't round trip: %s", s) + } + }) + } +}