From ef9cb3a01d1697ea383e4cd315e1eaa84322d84a Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Tue, 16 Mar 2021 02:10:59 +0000 Subject: [PATCH 1/3] chore(docs): remove redundant RFC URLs pkg.go.dev parses "RFC 1035, section 2.3.1" style text and links it to the relevant place. So there's no need to also include a full URL for documentation purposes. --- strings.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/strings.go b/strings.go index 1bad22d..ddea864 100644 --- a/strings.go +++ b/strings.go @@ -164,11 +164,9 @@ func UnicodeString(n int, alphabet []rune) (string, error) { } // DNSLabel returns a random string of n length in a DNS label compliant format -// as defined in RFC 1035, section 2.3.1: -// https://www.rfc-editor.org/rfc/rfc1035.html#section-2.3.1 +// as defined in RFC 1035, section 2.3.1. // -// It also adheres to RFC 5891, section 4.2.3.1: -// https://www.rfc-editor.org/rfc/rfc5891.html#section-4.2.3.1 +// It also adheres to RFC 5891, section 4.2.3.1. // // In summary, the generated random string will: // From a755fe957a485e4c29b8ade474878b265785bc66 Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Tue, 16 Mar 2021 02:13:20 +0000 Subject: [PATCH 2/3] fix(strings): add missing benchmark for DNSLabel --- strings_test.go | 105 ++++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/strings_test.go b/strings_test.go index 9643c01..03b5c31 100644 --- a/strings_test.go +++ b/strings_test.go @@ -497,54 +497,55 @@ func BenchmarkUnicodeString(b *testing.B) { } } +var dnsLabelTestCases = []struct { + name string + n int + errIs error + errStr string +}{ + { + name: "n=-128", + n: -128, + errIs: errDNSLabelLength, + errStr: "rands: DNS labels must be between 1 and 63 characters " + + "in length", + }, + { + name: "n=0", + n: 0, + errIs: errDNSLabelLength, + errStr: "rands: DNS labels must be between 1 and 63 characters " + + "in length", + }, + {name: "n=1", n: 1}, + {name: "n=2", n: 2}, + {name: "n=3", n: 3}, + {name: "n=4", n: 4}, + {name: "n=5", n: 5}, + {name: "n=6", n: 6}, + {name: "n=7", n: 7}, + {name: "n=8", n: 8}, + {name: "n=16", n: 16}, + {name: "n=32", n: 32}, + {name: "n=63", n: 63}, + { + name: "n=64", + n: 64, + errIs: errDNSLabelLength, + errStr: "rands: DNS labels must be between 1 and 63 characters " + + "in length", + }, + { + name: "n=128", + n: 128, + errIs: errDNSLabelLength, + errStr: "rands: DNS labels must be between 1 and 63 characters " + + "in length", + }, +} + func TestDNSLabel(t *testing.T) { - tests := []struct { - name string - n int - errIs error - errStr string - }{ - { - name: "n=-128", - n: -128, - errIs: errDNSLabelLength, - errStr: "rands: DNS labels must be between 1 and 63 characters " + - "in length", - }, - { - name: "n=0", - n: 0, - errIs: errDNSLabelLength, - errStr: "rands: DNS labels must be between 1 and 63 characters " + - "in length", - }, - {name: "n=1", n: 1}, - {name: "n=2", n: 2}, - {name: "n=3", n: 3}, - {name: "n=4", n: 4}, - {name: "n=5", n: 5}, - {name: "n=6", n: 6}, - {name: "n=7", n: 7}, - {name: "n=8", n: 8}, - {name: "n=16", n: 16}, - {name: "n=32", n: 32}, - {name: "n=63", n: 63}, - { - name: "n=64", - n: 64, - errIs: errDNSLabelLength, - errStr: "rands: DNS labels must be between 1 and 63 characters " + - "in length", - }, - { - name: "n=128", - n: 128, - errIs: errDNSLabelLength, - errStr: "rands: DNS labels must be between 1 and 63 characters " + - "in length", - }, - } - for _, tt := range tests { + for _, tt := range dnsLabelTestCases { t.Run(tt.name, func(t *testing.T) { // generate lots of labels to increase the chances of catching any // obscure bugs @@ -568,6 +569,16 @@ func TestDNSLabel(t *testing.T) { } } +func BenchmarkDNSLabel(b *testing.B) { + for _, tt := range dnsLabelTestCases { + b.Run(tt.name, func(b *testing.B) { + for n := 0; n < b.N; n++ { + _, _ = DNSLabel(tt.n) + } + }) + } +} + // // Helpers // From 825a3c18fbc8e0497eafea9254baadb2951f23c6 Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Tue, 16 Mar 2021 02:14:56 +0000 Subject: [PATCH 3/3] feat(strings): add UUID function to generate random RFC 4122 UUID v4 strings --- strings.go | 27 +++++++++++++++++++++++++++ strings_example_test.go | 5 +++++ strings_test.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/strings.go b/strings.go index ddea864..f3152d3 100644 --- a/strings.go +++ b/strings.go @@ -18,6 +18,7 @@ const ( alphabeticChars = upperChars + lowerChars alphanumericChars = alphabeticChars + numericChars dnsLabelChars = lowerNumericChars + "-" + uuidHyphen = byte('-') ) var ( @@ -233,6 +234,32 @@ func DNSLabel(n int) (string, error) { } } +// UUID returns a random UUID v4 in string format as defined by RFC 4122, +// section 4.4. +func UUID() (string, error) { + b, err := Bytes(16) + if err != nil { + return "", err + } + + b[6] = (b[6] & 0x0f) | 0x40 // Version: 4 (random) + b[8] = (b[8] & 0x3f) | 0x80 // Variant: RFC 4122 + + // Construct a UUID v4 string according to RFC 4122 specifications. + dst := make([]byte, 36) + hex.Encode(dst[0:8], b[0:4]) // time-low + dst[8] = uuidHyphen + hex.Encode(dst[9:13], b[4:6]) // time-mid + dst[13] = uuidHyphen + hex.Encode(dst[14:18], b[6:8]) // time-high-and-version + dst[18] = uuidHyphen + hex.Encode(dst[19:23], b[8:10]) // clock-seq-and-reserved, clock-seq-low + dst[23] = uuidHyphen + hex.Encode(dst[24:], b[10:]) // node + + return string(dst), nil +} + func isASCII(s string) bool { for _, c := range s { if c > unicode.MaxASCII { diff --git a/strings_example_test.go b/strings_example_test.go index 9ca4380..e59a97a 100644 --- a/strings_example_test.go +++ b/strings_example_test.go @@ -70,3 +70,8 @@ func ExampleDNSLabel() { s, _ := rands.DNSLabel(16) fmt.Println(s) // => z0ij9o8qkbs0ru-h } + +func ExampleUUID() { + s, _ := rands.UUID() + fmt.Println(s) // => a62b8712-f238-43ba-a47e-333f5fffe785 +} diff --git a/strings_test.go b/strings_test.go index 03b5c31..1df898e 100644 --- a/strings_test.go +++ b/strings_test.go @@ -2,6 +2,7 @@ package rands import ( "encoding/base64" + "encoding/hex" "errors" "regexp" "strings" @@ -579,6 +580,34 @@ func BenchmarkDNSLabel(b *testing.B) { } } +func TestUUID(t *testing.T) { + m := regexp.MustCompile( + `^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`, + ) + + for i := 0; i < 10000; i++ { + got, err := UUID() + require.NoError(t, err) + require.Regexp(t, m, got) + + raw := strings.ReplaceAll(got, "-", "") + b := make([]byte, 16) + _, err = hex.Decode(b, []byte(raw)) + require.NoError(t, err) + + require.Equal(t, 4, int(b[6]>>4), "version is not 4") + require.Equal(t, byte(0x80), b[8]&0xc0, + "variant is not RFC 4122", + ) + } +} + +func BenchmarkUUID(b *testing.B) { + for n := 0; n < b.N; n++ { + _, _ = UUID() + } +} + // // Helpers //