diff --git a/api/next/67059.txt b/api/next/67059.txt new file mode 100644 index 00000000000000..e0edb69c73e58c --- /dev/null +++ b/api/next/67059.txt @@ -0,0 +1 @@ +pkg math/rand/v2, method (*ChaCha8) Read(p []byte) (n int, err error) #67059 diff --git a/src/internal/chacha8rand/chacha8.go b/src/internal/chacha8rand/chacha8.go index 8f1b4e5315c99e..c95e047d32d26c 100644 --- a/src/internal/chacha8rand/chacha8.go +++ b/src/internal/chacha8rand/chacha8.go @@ -30,6 +30,15 @@ type State struct { i uint32 n uint32 c uint32 + + // readVal contains remainder of 63-bit integer used for bytes + // generation during most recent Read call. + // It is saved so next Read call can start where the previous + // one finished. + readVal uint64 + // readPos indicates the number of low-order bytes of readVal + // that are still valid. + readPos int8 } // Next returns the next random value, along with a boolean @@ -66,6 +75,8 @@ func (s *State) Init64(seed [4]uint64) { block(&s.seed, &s.buf, 0) s.c = 0 s.i = 0 + s.readPos = 0 + s.readVal = 0 s.n = chunk } @@ -158,3 +169,28 @@ func Unmarshal(s *State, data []byte) error { } return nil } + +// Read reads random bytes from the state into p. +func Read(s *State, p []byte) (n int) { + pos := s.readPos + val := s.readVal + var ok bool + for n = 0; n < len(p); n++ { + if pos == 0 { + for { + val, ok = s.Next() + if ok { + break + } + s.Refill() + } + pos = 7 + } + p[n] = byte(val) + val >>= 8 + pos-- + } + s.readPos = pos + s.readVal = val + return +} diff --git a/src/math/rand/v2/chacha8.go b/src/math/rand/v2/chacha8.go index 6b9aa7278250f8..47400ff7c8c024 100644 --- a/src/math/rand/v2/chacha8.go +++ b/src/math/rand/v2/chacha8.go @@ -44,3 +44,8 @@ func (c *ChaCha8) UnmarshalBinary(data []byte) error { func (c *ChaCha8) MarshalBinary() ([]byte, error) { return chacha8rand.Marshal(&c.state), nil } + +// Read generates len(p) random bytes and writes them into p. +func (c *ChaCha8) Read(p []byte) (n int, err error) { + return chacha8rand.Read(&c.state, p), nil +} diff --git a/src/math/rand/v2/chacha8_test.go b/src/math/rand/v2/chacha8_test.go index 2c55b479b25fe7..3944712f4008d2 100644 --- a/src/math/rand/v2/chacha8_test.go +++ b/src/math/rand/v2/chacha8_test.go @@ -5,8 +5,11 @@ package rand_test import ( + "bytes" + "io" . "math/rand/v2" "testing" + "testing/iotest" ) func TestChaCha8(t *testing.T) { @@ -529,3 +532,82 @@ var chacha8marshal = []string{ "chacha8:\x00\x00\x00\x00\x00\x00\x00zK3\x9bB!,\x94\x9d\x975\xce'O_t\xee|\xb21\x87\xbb\xbb\xfd)\x8f\xe52\x01\vP\fk", "chacha8:\x00\x00\x00\x00\x00\x00\x00{K3\x9bB!,\x94\x9d\x975\xce'O_t\xee|\xb21\x87\xbb\xbb\xfd)\x8f\xe52\x01\vP\fk", } + +func TestReadEmpty(t *testing.T) { + r := NewChaCha8(chacha8seed) + buf := make([]byte, 0) + n, err := r.Read(buf) + if err != nil { + t.Errorf("Read err into empty buffer; %v", err) + } + if n != 0 { + t.Errorf("Read into empty buffer returned unexpected n of %d", n) + } +} + +func TestReadSeedReset(t *testing.T) { + r := NewChaCha8(chacha8seed) + b1 := make([]byte, 128) + _, err := r.Read(b1) + if err != nil { + t.Errorf("read: %v", err) + } + + r.Seed(chacha8seed) + b2 := make([]byte, 128) + _, err = r.Read(b2) + if err != nil { + t.Errorf("read: %v", err) + } + if !bytes.Equal(b1, b2) { + t.Errorf("mismatch after re-seed:\n%x\n%x", b1, b2) + } +} + +func TestReadByOneByte(t *testing.T) { + r := NewChaCha8(chacha8seed) + b1 := make([]byte, 100) + _, err := io.ReadFull(iotest.OneByteReader(r), b1) + if err != nil { + t.Errorf("read by one byte: %v", err) + } + r = NewChaCha8(chacha8seed) + b2 := make([]byte, 100) + _, err = r.Read(b2) + if err != nil { + t.Errorf("read: %v", err) + } + if !bytes.Equal(b1, b2) { + t.Errorf("read by one byte vs single read:\n%x\n%x", b1, b2) + } +} + +func BenchmarkRead3(b *testing.B) { + r := NewChaCha8(chacha8seed) + buf := make([]byte, 3) + b.ResetTimer() + b.SetBytes(3) + for n := b.N; n > 0; n-- { + r.Read(buf) + } +} + +func BenchmarkRead64(b *testing.B) { + r := NewChaCha8(chacha8seed) + buf := make([]byte, 64) + b.ResetTimer() + b.SetBytes(64) + for n := b.N; n > 0; n-- { + r.Read(buf) + } +} + +func BenchmarkRead1000(b *testing.B) { + r := NewChaCha8(chacha8seed) + buf := make([]byte, 1000) + b.ResetTimer() + b.SetBytes(1000) + for n := b.N; n > 0; n-- { + r.Read(buf) + } +}