New Upstream Release - golang-github-dchest-uniuri

Ready changes

Summary

Merged new upstream version: 0.0~git20221007.a87ec9d+ds (was: 0.0~git20200228.7aecb25).

Resulting package

Built on 2022-12-14T17:27 (took 3m1s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-dchest-uniuri-dev

Lintian Result

Diff

diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 245a2f5..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-language: go
-
-go:
-  - 1.3
-  - 1.4
-  - tip
diff --git a/README.md b/README.md
index b321a5f..6240bc9 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,6 @@
 Package uniuri
 =====================
 
-[![Build Status](https://travis-ci.org/dchest/uniuri.svg)](https://travis-ci.org/dchest/uniuri)
-
 ```go
 import "github.com/dchest/uniuri"
 ```
diff --git a/debian/changelog b/debian/changelog
index 4e599ca..d02abaa 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-dchest-uniuri (0.0~git20221007.a87ec9d+ds-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 14 Dec 2022 17:24:26 -0000
+
 golang-github-dchest-uniuri (0.0~git20200228.7aecb25-1) unstable; urgency=medium
 
   * New upstream release
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..f5854e7
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/dchest/uniuri
+
+go 1.19
diff --git a/uniuri.go b/uniuri.go
index 6393446..dd96592 100644
--- a/uniuri.go
+++ b/uniuri.go
@@ -22,7 +22,10 @@
 // read from it.
 package uniuri
 
-import "crypto/rand"
+import (
+	"crypto/rand"
+	"math"
+)
 
 const (
 	// StdLen is a standard length of uniuri string to achive ~95 bits of entropy.
@@ -47,35 +50,71 @@ func NewLen(length int) string {
 	return NewLenChars(length, StdChars)
 }
 
-// NewLenChars returns a new random string of the provided length, consisting
+// maxBufLen is the maximum length of a temporary buffer for random bytes.
+const maxBufLen = 2048
+
+// minRegenBufLen is the minimum length of temporary buffer for random bytes
+// to fill after the first rand.Read request didn't produce the full result.
+// If the initial buffer is smaller, this value is ignored.
+// Rationale: for performance, assume it's pointless to request fewer bytes from rand.Read.
+const minRegenBufLen = 16
+
+// estimatedBufLen returns the estimated number of random bytes to request
+// given that byte values greater than maxByte will be rejected.
+func estimatedBufLen(need, maxByte int) int {
+	return int(math.Ceil(float64(need) * (255 / float64(maxByte))))
+}
+
+// NewLenCharsBytes returns a new random byte slice of the provided length, consisting
 // of the provided byte slice of allowed characters (maximum 256).
-func NewLenChars(length int, chars []byte) string {
+func NewLenCharsBytes(length int, chars []byte) []byte {
 	if length == 0 {
-		return ""
+		return nil
 	}
 	clen := len(chars)
 	if clen < 2 || clen > 256 {
 		panic("uniuri: wrong charset length for NewLenChars")
 	}
 	maxrb := 255 - (256 % clen)
-	b := make([]byte, length)
-	r := make([]byte, length+(length/4)) // storage for random bytes.
+	buflen := estimatedBufLen(length, maxrb)
+	if buflen < length {
+		buflen = length
+	}
+	if buflen > maxBufLen {
+		buflen = maxBufLen
+	}
+	buf := make([]byte, buflen) // storage for random bytes
+	out := make([]byte, length) // storage for result
 	i := 0
 	for {
-		if _, err := rand.Read(r); err != nil {
+		if _, err := rand.Read(buf[:buflen]); err != nil {
 			panic("uniuri: error reading random bytes: " + err.Error())
 		}
-		for _, rb := range r {
+		for _, rb := range buf[:buflen] {
 			c := int(rb)
 			if c > maxrb {
 				// Skip this number to avoid modulo bias.
 				continue
 			}
-			b[i] = chars[c%clen]
+			out[i] = chars[c%clen]
 			i++
 			if i == length {
-				return string(b)
+				return out
 			}
 		}
+		// Adjust new requested length, but no smaller than minRegenBufLen.
+		buflen = estimatedBufLen(length-i, maxrb)
+		if buflen < minRegenBufLen && minRegenBufLen < cap(buf) {
+			buflen = minRegenBufLen
+		}
+		if buflen > maxBufLen {
+			buflen = maxBufLen
+		}
 	}
 }
+
+// NewLenChars returns a new random string of the provided length, consisting
+// of the provided byte slice of allowed characters (maximum 256).
+func NewLenChars(length int, chars []byte) string {
+	return string(NewLenCharsBytes(length, chars))
+}
diff --git a/uniuri_test.go b/uniuri_test.go
index 0e26efe..dd3c365 100644
--- a/uniuri_test.go
+++ b/uniuri_test.go
@@ -7,7 +7,24 @@
 
 package uniuri
 
-import "testing"
+import (
+	"bytes"
+	"testing"
+)
+
+func validateBytes(t *testing.T, u []byte, chars []byte) {
+	for _, c := range u {
+		var present bool
+		for _, a := range chars {
+			if a == c {
+				present = true
+			}
+		}
+		if !present {
+			t.Fatalf("chars not allowed in %q", u)
+		}
+	}
+}
 
 func validateChars(t *testing.T, u string, chars []byte) {
 	for _, c := range u {
@@ -55,6 +72,25 @@ func TestNewLen(t *testing.T) {
 	}
 }
 
+func TestNewLenCharsBytes(t *testing.T) {
+	length := 10
+	chars := []byte("01234567")
+	u := NewLenCharsBytes(length, chars)
+
+	// Check length
+	if len(u) != length {
+		t.Fatalf("wrong length: expected %d, got %d", StdLen, len(u))
+	}
+	// Check that only allowed characters are present
+	validateBytes(t, u, chars)
+
+	// Check that two generated strings are different
+	u2 := NewLenCharsBytes(length, chars)
+	if bytes.Equal(u, u2) {
+		t.Fatalf("not unique: %q and %q", u, u2)
+	}
+}
+
 func TestNewLenChars(t *testing.T) {
 	length := 10
 	chars := []byte("01234567")
@@ -96,7 +132,61 @@ func TestBias(t *testing.T) {
 	for k, n := range counts {
 		diff := float64(n) / avg
 		if diff < 0.95 || diff > 1.05 {
-			t.Errorf("Bias on '%c': expected average %f, got %d", k, avg, n)
+			t.Errorf("Possible bias on '%c': expected average %f, got %d", k, avg, n)
 		}
 	}
 }
+
+var (
+	sixtyFourChars = append(StdChars, []byte{'+', '/'}...)
+	sixtyFiveChars = append(sixtyFourChars, []byte{'.'}...)
+	threeChars     = []byte{'a', 'b', 'c'}
+)
+
+func BenchmarkLen16Chars65(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_ = NewLenChars(StdLen, sixtyFiveChars)
+	}
+}
+
+func BenchmarkLen16Chars64(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_ = NewLenChars(StdLen, sixtyFourChars)
+	}
+}
+
+func BenchmarkLen16Chars62(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_ = NewLenChars(StdLen, StdChars)
+	}
+}
+
+func BenchmarkLen16Chars3(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_ = NewLenChars(StdLen, threeChars)
+	}
+}
+
+func BenchmarkLen1024Chars65(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_ = NewLenChars(1024, sixtyFiveChars)
+	}
+}
+
+func BenchmarkLen1024Chars64(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_ = NewLenChars(1024, sixtyFourChars)
+	}
+}
+
+func BenchmarkLen1024Chars62(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_ = NewLenChars(1024, StdChars)
+	}
+}
+
+func BenchmarkLen1024Chars3(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_ = NewLenChars(1024, threeChars)
+	}
+}

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/dchest/uniuri/go.mod

No differences were encountered in the control files

More details

Full run details