From fd985d23dcc792354c4d60108dc01f992f4bdbc2 Mon Sep 17 00:00:00 2001
From: apocelipes <seve3r@outlook.com>
Date: Wed, 21 Aug 2024 09:30:14 +0000
Subject: [PATCH] crypto/x509,math/rand/v2: implement the
 encoding.(Binary|Text)Appender
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Implement the encoding.(Binary|Text)Appender interfaces for "x509.OID".

Implement the encoding.BinaryAppender interface for "rand/v2.PCG" and "rand/v2.ChaCha8".

"rand/v2.ChaCha8.MarshalBinary" alse gains some performance benefits:

                           │     old      │                 new                 │
                           │    sec/op    │   sec/op     vs base                │
ChaCha8MarshalBinary-8       33.730n ± 2%   9.786n ± 1%  -70.99% (p=0.000 n=10)
ChaCha8MarshalBinaryRead-8    99.86n ± 1%   17.79n ± 0%  -82.18% (p=0.000 n=10)
geomean                       58.04n        13.19n       -77.27%

                           │    old     │                  new                   │
                           │    B/op    │   B/op     vs base                     │
ChaCha8MarshalBinary-8       48.00 ± 0%   0.00 ± 0%  -100.00% (p=0.000 n=10)
ChaCha8MarshalBinaryRead-8   83.00 ± 0%   0.00 ± 0%  -100.00% (p=0.000 n=10)

                           │    old     │                   new                   │
                           │ allocs/op  │ allocs/op   vs base                     │
ChaCha8MarshalBinary-8       1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
ChaCha8MarshalBinaryRead-8   2.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)

For #62384

Change-Id: I604bde6dad90a916012909c7260f4bb06dcf5c0a
GitHub-Last-Rev: 78abf9c5dfb74838985637798bcd5cb957541d20
GitHub-Pull-Request: golang/go#68987
Reviewed-on: https://go-review.googlesource.com/c/go/+/607079
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
---
 api/next/62384.txt                            |  4 ++
 .../6-stdlib/99-minor/crypto/x509/62384.md    |  2 +
 .../6-stdlib/99-minor/math/rand/v2/62384.md   |  1 +
 src/crypto/x509/oid.go                        | 14 ++++++-
 src/crypto/x509/oid_test.go                   | 25 ++++++++++++
 src/math/rand/v2/chacha8.go                   | 21 ++++++----
 src/math/rand/v2/chacha8_test.go              | 38 +++++++++++++++++++
 src/math/rand/v2/pcg.go                       | 18 +++++----
 src/math/rand/v2/pcg_test.go                  | 13 +++++--
 9 files changed, 116 insertions(+), 20 deletions(-)
 create mode 100644 doc/next/6-stdlib/99-minor/crypto/x509/62384.md
 create mode 100644 doc/next/6-stdlib/99-minor/math/rand/v2/62384.md

diff --git a/api/next/62384.txt b/api/next/62384.txt
index 0c6053d2a00..3a50a2792a8 100644
--- a/api/next/62384.txt
+++ b/api/next/62384.txt
@@ -11,3 +11,7 @@ pkg math/big, method (*Rat) AppendText([]uint8) ([]uint8, error) #62384
 pkg regexp, method (*Regexp) AppendText([]uint8) ([]uint8, error) #62384
 pkg time, method (Time) AppendBinary([]uint8) ([]uint8, error) #62384
 pkg time, method (Time) AppendText([]uint8) ([]uint8, error) #62384
+pkg math/rand/v2, method (*ChaCha8) AppendBinary([]uint8) ([]uint8, error) #62384
+pkg math/rand/v2, method (*PCG) AppendBinary([]uint8) ([]uint8, error) #62384
+pkg crypto/x509, method (OID) AppendBinary([]uint8) ([]uint8, error) #62384
+pkg crypto/x509, method (OID) AppendText([]uint8) ([]uint8, error) #62384
diff --git a/doc/next/6-stdlib/99-minor/crypto/x509/62384.md b/doc/next/6-stdlib/99-minor/crypto/x509/62384.md
new file mode 100644
index 00000000000..a8c6a29d9a1
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/crypto/x509/62384.md
@@ -0,0 +1,2 @@
+[OID] now implements the [encoding.BinaryAppender] and [encoding.TextAppender]
+interfaces.
diff --git a/doc/next/6-stdlib/99-minor/math/rand/v2/62384.md b/doc/next/6-stdlib/99-minor/math/rand/v2/62384.md
new file mode 100644
index 00000000000..04dcb8b88a8
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/math/rand/v2/62384.md
@@ -0,0 +1 @@
+[ChaCha8] and [PCG] now implement the [encoding.BinaryAppender] interface.
diff --git a/src/crypto/x509/oid.go b/src/crypto/x509/oid.go
index fd438eacf95..b1464346b6b 100644
--- a/src/crypto/x509/oid.go
+++ b/src/crypto/x509/oid.go
@@ -112,9 +112,14 @@ func appendBase128BigInt(dst []byte, n *big.Int) []byte {
 	return dst
 }
 
+// AppendText implements [encoding.TextAppender]
+func (o OID) AppendText(b []byte) ([]byte, error) {
+	return append(b, o.String()...), nil
+}
+
 // MarshalText implements [encoding.TextMarshaler]
 func (o OID) MarshalText() ([]byte, error) {
-	return []byte(o.String()), nil
+	return o.AppendText(nil)
 }
 
 // UnmarshalText implements [encoding.TextUnmarshaler]
@@ -180,9 +185,14 @@ func (o *OID) unmarshalOIDText(oid string) error {
 	return nil
 }
 
+// AppendBinary implements [encoding.BinaryAppender]
+func (o OID) AppendBinary(b []byte) ([]byte, error) {
+	return append(b, o.der...), nil
+}
+
 // MarshalBinary implements [encoding.BinaryMarshaler]
 func (o OID) MarshalBinary() ([]byte, error) {
-	return bytes.Clone(o.der), nil
+	return o.AppendBinary(nil)
 }
 
 // UnmarshalBinary implements [encoding.BinaryUnmarshaler]
diff --git a/src/crypto/x509/oid_test.go b/src/crypto/x509/oid_test.go
index 4d5803d4273..0b60895a121 100644
--- a/src/crypto/x509/oid_test.go
+++ b/src/crypto/x509/oid_test.go
@@ -228,6 +228,14 @@ func TestOIDMarshal(t *testing.T) {
 			continue
 		}
 
+		textAppend := make([]byte, 4)
+		textAppend, err = o.AppendText(textAppend)
+		textAppend = textAppend[4:]
+		if string(textAppend) != tt.in || err != nil {
+			t.Errorf("(%#v).AppendText() = (%v, %v); want = (%v, nil)", o, string(textAppend), err, tt.in)
+			continue
+		}
+
 		binary, err := o.MarshalBinary()
 		if err != nil {
 			t.Errorf("(%#v).MarshalBinary() = %v; want = nil", o, err)
@@ -242,6 +250,23 @@ func TestOIDMarshal(t *testing.T) {
 			t.Errorf("(*OID).UnmarshalBinary(%v) = %v; want = %v", binary, o3, tt.out)
 			continue
 		}
+
+		binaryAppend := make([]byte, 4)
+		binaryAppend, err = o.AppendBinary(binaryAppend)
+		binaryAppend = binaryAppend[4:]
+		if err != nil {
+			t.Errorf("(%#v).AppendBinary() = %v; want = nil", o, err)
+		}
+
+		var o4 OID
+		if err := o4.UnmarshalBinary(binaryAppend); err != nil {
+			t.Errorf("(*OID).UnmarshalBinary(%v) = %v; want = nil", binaryAppend, err)
+		}
+
+		if !o4.Equal(tt.out) {
+			t.Errorf("(*OID).UnmarshalBinary(%v) = %v; want = %v", binaryAppend, o4, tt.out)
+			continue
+		}
 	}
 }
 
diff --git a/src/math/rand/v2/chacha8.go b/src/math/rand/v2/chacha8.go
index f9eaacf6017..b06d66ffa29 100644
--- a/src/math/rand/v2/chacha8.go
+++ b/src/math/rand/v2/chacha8.go
@@ -70,7 +70,7 @@ func (c *ChaCha8) Read(p []byte) (n int, err error) {
 	return
 }
 
-// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
+// UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface.
 func (c *ChaCha8) UnmarshalBinary(data []byte) error {
 	data, ok := cutPrefix(data, []byte("readbuf:"))
 	if ok {
@@ -98,13 +98,18 @@ func readUint8LengthPrefixed(b []byte) (buf, rest []byte, ok bool) {
 	return b[1 : 1+b[0]], b[1+b[0]:], true
 }
 
-// MarshalBinary implements the encoding.BinaryMarshaler interface.
-func (c *ChaCha8) MarshalBinary() ([]byte, error) {
+// AppendBinary implements the [encoding.BinaryAppender] interface.
+func (c *ChaCha8) AppendBinary(b []byte) ([]byte, error) {
 	if c.readLen > 0 {
-		out := []byte("readbuf:")
-		out = append(out, uint8(c.readLen))
-		out = append(out, c.readBuf[len(c.readBuf)-c.readLen:]...)
-		return append(out, chacha8rand.Marshal(&c.state)...), nil
+		b = append(b, "readbuf:"...)
+		b = append(b, uint8(c.readLen))
+		b = append(b, c.readBuf[len(c.readBuf)-c.readLen:]...)
 	}
-	return chacha8rand.Marshal(&c.state), nil
+	return append(b, chacha8rand.Marshal(&c.state)...), nil
+}
+
+// MarshalBinary implements the [encoding.BinaryMarshaler] interface.
+func (c *ChaCha8) MarshalBinary() ([]byte, error) {
+	// the maximum length of (chacha8rand.Marshal + c.readBuf + "readbuf:") is 64
+	return c.AppendBinary(make([]byte, 0, 64))
 }
diff --git a/src/math/rand/v2/chacha8_test.go b/src/math/rand/v2/chacha8_test.go
index 50e83ea19a1..ba11b7cc45d 100644
--- a/src/math/rand/v2/chacha8_test.go
+++ b/src/math/rand/v2/chacha8_test.go
@@ -98,6 +98,22 @@ func TestChaCha8Read(t *testing.T) {
 	}
 }
 
+func BenchmarkChaCha8MarshalBinary(b *testing.B) {
+	p := NewChaCha8(chacha8seed)
+	for range b.N {
+		p.MarshalBinary()
+	}
+}
+
+func BenchmarkChaCha8MarshalBinaryRead(b *testing.B) {
+	p := NewChaCha8(chacha8seed)
+	buf := make([]byte, 1)
+	for range b.N {
+		p.MarshalBinary()
+		p.Read(buf)
+	}
+}
+
 func TestChaCha8Marshal(t *testing.T) {
 	p := NewChaCha8(chacha8seed)
 	for i, x := range chacha8output {
@@ -108,6 +124,17 @@ func TestChaCha8Marshal(t *testing.T) {
 		if string(enc) != chacha8marshal[i] {
 			t.Errorf("#%d: MarshalBinary=%q, want %q", i, enc, chacha8marshal[i])
 		}
+
+		b := make([]byte, 4, 32)
+		b, err = p.AppendBinary(b)
+		encAppend := b[4:]
+		if err != nil {
+			t.Fatalf("#%d: AppendBinary: %v", i, err)
+		}
+		if string(encAppend) != chacha8marshal[i] {
+			t.Errorf("#%d: AppendBinary=%q, want %q", i, encAppend, chacha8marshal[i])
+		}
+
 		*p = ChaCha8{}
 		if err := p.UnmarshalBinary(enc); err != nil {
 			t.Fatalf("#%d: UnmarshalBinary: %v", i, err)
@@ -128,6 +155,17 @@ func TestChaCha8MarshalRead(t *testing.T) {
 		if string(enc) != chacha8marshalread[i] {
 			t.Errorf("#%d: MarshalBinary=%q, want %q", i, enc, chacha8marshalread[i])
 		}
+
+		b := make([]byte, 4, 32)
+		b, err = p.AppendBinary(b)
+		encAppend := b[4:]
+		if err != nil {
+			t.Fatalf("#%d: AppendBinary: %v", i, err)
+		}
+		if string(encAppend) != chacha8marshalread[i] {
+			t.Errorf("#%d: AppendBinary=%q, want %q", i, encAppend, chacha8marshalread[i])
+		}
+
 		*p = ChaCha8{}
 		if err := p.UnmarshalBinary(enc); err != nil {
 			t.Fatalf("#%d: UnmarshalBinary: %v", i, err)
diff --git a/src/math/rand/v2/pcg.go b/src/math/rand/v2/pcg.go
index 4ccd5e320b9..a70efe8e559 100644
--- a/src/math/rand/v2/pcg.go
+++ b/src/math/rand/v2/pcg.go
@@ -31,18 +31,22 @@ func (p *PCG) Seed(seed1, seed2 uint64) {
 	p.lo = seed2
 }
 
-// MarshalBinary implements the encoding.BinaryMarshaler interface.
-func (p *PCG) MarshalBinary() ([]byte, error) {
-	b := make([]byte, 20)
-	copy(b, "pcg:")
-	byteorder.BePutUint64(b[4:], p.hi)
-	byteorder.BePutUint64(b[4+8:], p.lo)
+// AppendBinary implements the [encoding.BinaryAppender] interface.
+func (p *PCG) AppendBinary(b []byte) ([]byte, error) {
+	b = append(b, "pcg:"...)
+	b = byteorder.BeAppendUint64(b, p.hi)
+	b = byteorder.BeAppendUint64(b, p.lo)
 	return b, nil
 }
 
+// MarshalBinary implements the [encoding.BinaryMarshaler] interface.
+func (p *PCG) MarshalBinary() ([]byte, error) {
+	return p.AppendBinary(make([]byte, 0, 20))
+}
+
 var errUnmarshalPCG = errors.New("invalid PCG encoding")
 
-// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
+// UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface.
 func (p *PCG) UnmarshalBinary(data []byte) error {
 	if len(data) != 20 || string(data[:4]) != "pcg:" {
 		return errUnmarshalPCG
diff --git a/src/math/rand/v2/pcg_test.go b/src/math/rand/v2/pcg_test.go
index db866c8c856..6558dab77b7 100644
--- a/src/math/rand/v2/pcg_test.go
+++ b/src/math/rand/v2/pcg_test.go
@@ -21,9 +21,10 @@ func BenchmarkPCG_DXSM(b *testing.B) {
 func TestPCGMarshal(t *testing.T) {
 	var p PCG
 	const (
-		seed1 = 0x123456789abcdef0
-		seed2 = 0xfedcba9876543210
-		want  = "pcg:\x12\x34\x56\x78\x9a\xbc\xde\xf0\xfe\xdc\xba\x98\x76\x54\x32\x10"
+		seed1      = 0x123456789abcdef0
+		seed2      = 0xfedcba9876543210
+		want       = "pcg:\x12\x34\x56\x78\x9a\xbc\xde\xf0\xfe\xdc\xba\x98\x76\x54\x32\x10"
+		wantAppend = "\x00\x00\x00\x00" + want
 	)
 	p.Seed(seed1, seed2)
 	data, err := p.MarshalBinary()
@@ -31,6 +32,12 @@ func TestPCGMarshal(t *testing.T) {
 		t.Errorf("MarshalBinary() = %q, %v, want %q, nil", data, err, want)
 	}
 
+	dataAppend := make([]byte, 4, 32)
+	dataAppend, err = p.AppendBinary(dataAppend)
+	if string(dataAppend) != wantAppend || err != nil {
+		t.Errorf("AppendBinary() = %q, %v, want %q, nil", dataAppend, err, wantAppend)
+	}
+
 	q := PCG{}
 	if err := q.UnmarshalBinary([]byte(want)); err != nil {
 		t.Fatalf("UnmarshalBinary(): %v", err)
-- 
GitLab