From c2dca76803a31a8b51e222e7202b633f92ee1080 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Tue, 22 Sep 2015 01:26:02 -0700 Subject: [PATCH] Add sbuf. --- sbuf/LICENSE | 15 +++ sbuf/sbuf.go | 144 ++++++++++++++++++++++++++++ sbuf/sbuf_test.go | 240 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 399 insertions(+) create mode 100644 sbuf/LICENSE create mode 100644 sbuf/sbuf.go create mode 100644 sbuf/sbuf_test.go diff --git a/sbuf/LICENSE b/sbuf/LICENSE new file mode 100644 index 0000000..5261918 --- /dev/null +++ b/sbuf/LICENSE @@ -0,0 +1,15 @@ +My source code has the following license: + +Copyright (c) 2015 Kyle Isom + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/sbuf/sbuf.go b/sbuf/sbuf.go new file mode 100644 index 0000000..e50564f --- /dev/null +++ b/sbuf/sbuf.go @@ -0,0 +1,144 @@ +// Package sbuf implements a byte buffer that can be wiped. The underlying +// byte slice is wiped on read before being declaimed, and when the +// buffer is closed, its storage is zeroised. +package sbuf + +import "io" + +func zero(in []byte, n int) { + if in == nil { + return + } + + stop := n + if stop > len(in) || stop == 0 { + stop = len(in) + } + + for i := 0; i < stop; i++ { + in[i] ^= in[i] + } +} + +// A Buffer is a variable-sized buffer of bytes with Read and Write +// methods. The zero value for Buffer is an empty buffer ready to use. +type Buffer struct { + buf []byte +} + +// NewBuffer creates a new buffer with the specified capacity. +func NewBuffer(n int) *Buffer { + return &Buffer{ + buf: make([]byte, 0, n), + } +} + +// NewBufferFrom creates a new buffer from the byte slice passed in. The +// original data will be wiped. +func NewBufferFrom(p []byte) *Buffer { + buf := NewBuffer(len(p)) + buf.Write(p) + zero(p, len(p)) + return buf +} + +// Read reads the next len(p) bytes from the buffer or until the buffer +// is drained. The return value n is the number of bytes read. If the +// buffer has no data to return, err is io.EOF (unless len(p) is zero); +// otherwise it is nil. +func (buf *Buffer) Read(p []byte) (int, error) { + if len(buf.buf) == 0 { + if len(p) == 0 { + return 0, nil + } + return 0, io.EOF + } + + copyLength := len(p) + if copyLength > len(buf.buf) { + copyLength = len(buf.buf) + } + + copy(p, buf.buf) + zero(buf.buf, len(p)) + buf.buf = buf.buf[copyLength:] + return copyLength, nil +} + +// ReadByte reads the next byte from the buffer. If the buffer has no +// data to return, err is io.EOF; otherwise it is nil. +func (buf *Buffer) ReadByte() (byte, error) { + if len(buf.buf) == 0 { + return 0, io.EOF + } + + c := buf.buf[0] + buf.buf[0] = 0 + buf.buf = buf.buf[1:] + return c, nil +} + +func (buf *Buffer) grow(n int) { + tmp := make([]byte, len(buf.buf), len(buf.buf)+n) + copy(tmp, buf.buf) + zero(buf.buf, len(buf.buf)) + buf.buf = tmp +} + +// Write appends the contents of p to the buffer, growing the buffer +// as needed. The return value n is the length of p; err is always nil. +func (buf *Buffer) Write(p []byte) (int, error) { + r := len(buf.buf) + len(p) + if cap(buf.buf) < r { + l := r + for { + if l > r { + break + } + l *= 2 + } + buf.grow(l - cap(buf.buf)) + } + buf.buf = append(buf.buf, p...) + return len(p), nil +} + +// WriteByte adds the byte c to the buffer, growing the buffer as needed. +func (buf *Buffer) WriteByte(c byte) error { + r := len(buf.buf) + 1 + if cap(buf.buf) < r { + l := r * 2 + buf.grow(l - cap(buf.buf)) + } + buf.buf = append(buf.buf, c) + return nil +} + +// Close destroys and zeroises the buffer. The buffer will be re-opened +// on the next write. +func (buf *Buffer) Close() { + zero(buf.buf, len(buf.buf)) + buf.buf = nil +} + +// Len returns the length of the buffer. +func (buf *Buffer) Len() int { + return len(buf.buf) +} + +// Cap returns the capacity of the buffer. +func (buf *Buffer) Cap() int { + return cap(buf.buf) +} + +// Bytes returns the bytes currently in the buffer, and closes itself. +func (buf *Buffer) Bytes() []byte { + if buf.buf == nil { + return nil + } + + p := make([]byte, buf.Len()) + buf.Read(p) + buf.Close() + return p +} diff --git a/sbuf/sbuf_test.go b/sbuf/sbuf_test.go new file mode 100644 index 0000000..aa38fd0 --- /dev/null +++ b/sbuf/sbuf_test.go @@ -0,0 +1,240 @@ +package sbuf + +import ( + "bytes" + "crypto/rand" + "testing" + + "golang.org/x/crypto/nacl/box" +) + +var ( + buf = &Buffer{} + testMessage1 = []byte("round and round and round we go, where we stop, no one knows") + testMessage2 = []byte("the deconstruction of falling stars") +) + +func TestWrite(t *testing.T) { + n, err := buf.Write(testMessage1) + if err != nil { + t.Fatalf("%v", err) + } + + if n != len(testMessage1) { + t.Fatalf("expected to write %d bytes, but only wrote %d bytes", len(testMessage1), n) + } + + if buf.Len() != len(testMessage1) { + t.Fatalf("expected a length of %d, but have a length of %d", len(testMessage1), buf.Len()) + } + + if buf.Cap() != (len(testMessage1) * 2) { + t.Fatalf("expected a capacity of %d, but have a capacity of %d", len(testMessage1)*2, buf.Cap()) + } + + n, err = buf.Write(testMessage2) + if err != nil { + t.Fatalf("%v", err) + } + + if n != len(testMessage2) { + t.Fatalf("expected to write %d bytes, but only wrote %d bytes", len(testMessage2), n) + } + + n, err = buf.Write(testMessage2) + if err != nil { + t.Fatalf("%v", err) + } + + if n != len(testMessage2) { + t.Fatalf("expected to write %d bytes, but only wrote %d bytes", len(testMessage2), n) + } +} + +func TestRead(t *testing.T) { + var p = make([]byte, len(testMessage1)) + n, err := buf.Read(p) + if err != nil { + t.Fatalf("%v", err) + } + + if n != len(testMessage1) { + t.Fatalf("expected to read %d bytes, but only read %d bytes", len(testMessage1), n) + } + + if !bytes.Equal(p, testMessage1) { + t.Fatalf("expected p='%s', but p='%s'", testMessage1, p) + } + + p = make([]byte, len(testMessage2)) + n, err = buf.Read(p) + if err != nil { + t.Fatalf("%v", err) + } + + if n != len(testMessage2) { + t.Fatalf("expected to read %d bytes, but only read %d bytes", len(testMessage2), n) + } + + if !bytes.Equal(p, testMessage2) { + t.Fatalf("expected p='%s', but p='%s'", testMessage2, p) + } + + buf.Close() + n, err = buf.Read(p) + if err == nil { + t.Fatal("expected EOF") + } else if n != 0 { + t.Fatalf("expect n=0, but n=%d", n) + } + + p = nil + n, err = buf.Read(p) + if err != nil { + t.Fatalf("%v", err) + } else if n != 0 { + t.Fatalf("expect n=0, but n=%d", n) + } +} + +func TestShortRead(t *testing.T) { + tmpMessage := []byte("hello, world") + buf.Write(tmpMessage) + + var p = make([]byte, len(testMessage1)) + n, err := buf.Read(p) + if err != nil { + t.Fatalf("%v", err) + } + + if n != len(tmpMessage) { + t.Fatalf("expected to read %d bytes, but only read %d bytes", len(testMessage1), n) + } +} + +func TestNewBuffer(t *testing.T) { + buf := NewBuffer(32) + if len(buf.buf) != 0 { + t.Fatalf("expected new buffer length to be 0, have %d", + len(buf.buf)) + } + + if cap(buf.buf) != 32 { + t.Fatalf("expected new buffer capacity to be 0, have %d", + cap(buf.buf)) + } +} + +func TestNewBufferFrom(t *testing.T) { + p := make([]byte, len(testMessage1)) + copy(p, testMessage1) + buf := NewBufferFrom(p) + if !bytes.Equal(buf.buf, testMessage1) { + t.Fatal("new buffer wasn't constructed properly") + } +} + +func TestBytes(t *testing.T) { + p := make([]byte, len(testMessage1)) + copy(p, testMessage1) + buf := NewBufferFrom(p) + + out := buf.Bytes() + if buf.buf != nil { + t.Fatal("buffer was not closed") + } + + if !bytes.Equal(out, testMessage1) { + t.Fatal("buffer did not return the right data") + } + + out = buf.Bytes() + if out != nil { + t.Fatal("a closed buffer should return nil for Bytes") + } +} + +func TestRWByte(t *testing.T) { + buf := NewBuffer(0) + c := byte(42) + err := buf.WriteByte(c) + if err != nil { + t.Fatalf("%v", err) + } + + c, err = buf.ReadByte() + if err != nil { + t.Fatalf("%v", err) + } + + if c != 42 { + t.Fatal("Expected 42, have %d", c) + } + + _, err = buf.ReadByte() + if err == nil { + t.Fatal("Expected EOF") + } +} + +func BenchmarkRead(b *testing.B) { + b.N = 2000 + pub, priv, err := box.GenerateKey(rand.Reader) + if err != nil { + b.Fatalf("%v", err) + } + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _, err := buf.Write(priv[:]) + if err != nil { + b.Fatalf("%v", err) + } + _, err = buf.Write(pub[:]) + if err != nil { + b.Fatalf("%v", err) + } + b.SetBytes(64) + } +} + +func BenchmarkFixed(b *testing.B) { + pub, priv, err := box.GenerateKey(rand.Reader) + if err != nil { + b.Fatalf("%v", err) + } + + buf = NewBuffer(64 * b.N) + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _, err := buf.Write(priv[:]) + if err != nil { + b.Fatalf("%v", err) + } + _, err = buf.Write(pub[:]) + if err != nil { + b.Fatalf("%v", err) + } + b.SetBytes(64) + } +} + +func BenchmarkWrite(b *testing.B) { + var pub = new([32]byte) + var priv = new([32]byte) + + b.ReportAllocs() + + for i := 0; i < b.N && buf.Len() >= 64; i++ { + _, err := buf.Read(priv[:]) + if err != nil { + b.Fatalf("%v", err) + } + _, err = buf.Read(pub[:]) + if err != nil { + b.Fatalf("%v", err) + } + b.SetBytes(64) + } +}