memguard/buffer_test.go
Ethan Heilman 4ed2841e90
Removes drop based finalizer (#157)
This PR addresses an issue in memguard where the go garbage collector
(GC) will trigger the finalizer on a LockedBuffer, zeroing out and
freeing this buffer while the running code may still have a pointer to
this buffer. This can result in the code being run against partially or
fully zero'd out memory.

This issue was originally privately disclosed to memguard and it was
agreed that due to the minor security impact, a public PR should be
opened to track one possible solution to this problem.
 

The Fix
====

We fix this issue by removing the finalizer from the LockedBuffer. To
wipe the LockedBuffer and free the associated memory the developer must
call `Destroy()`. This is a reasonable fix because memguard provides
additional security at the cost of requiring the developer managing this
memory. The finalizer on the LockedBuffer mixes the approach of placing
the responsibility on the developer with sometimes having the go GC also
handle this responsibility.

This fix simplifies this so that the developer is always responsible for
calling Destroy() on their LockedBuffer.

The Issue
====

Consider the following code:
```go
  dataToLock := []byte(`abcdefghijklmnopqrstuvwxyz`)
  lb := memguard.NewBufferFromBytes(dataToLock)
  lbBytes := lb.Bytes()
  for i := 1; i < 30000; i++ {
      fmt.Printf("i=%d, len(lbBytes)=%d, lbBytes=%d\n", i, len(lbBytes), lbBytes)
      if lbBytes[0] != byte(97) {
          fmt.Printf("error: i=%d, len(lbBytes)=%d, lbBytes=%d\n", i, len(lbBytes), lbBytes)
      }
   }
```

At some point in the for loop lbBytes will eventually no longer equal
'a' (97) but will have its value changed to 0x0. Soon afterwards
attempts to write to lbBytes will result in a fault.

i=9837, len(lbBytes)=26, lbBytes=[97 98 99 100 101 102 103 104 105 106
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122]
unexpected fault address 0x29af8971fe6
fatal error: fault
[signal 0xc0000005 code=0x0 addr=0x29af8971fe6 pc=0x28fb0e]

Cause
====

When memguard creates a LockedBuffer, it associates a 16 byte value
named drop with the LockedBuffer. This drop value acts as a standin for
the data in the LockedBuffer so that when the go garbage collector (GC)
detects there are zero references to the drop it calls the custom
finalizer on the drop value and then the finalizer called Destroy on the
LockedBuffer which in turn deletes the data in the LockedBuffer and then
frees the page.

https://github.com/awnumar/memguard/blob/master/buffer.go#L23C1-L35C2

In the above code, once you enter the for loop, the LockedBuffer is out
of a scope and this means the drop is out of scope as well. When the
Garbage Collector does a sweep, it will call the custom finalizer on the
drop, which will in turn delete the buffer, this will zero out lbBytes
and then deallocate the memory for lbBytes.

Since the timing of this depends on the GC (garbage collector) this can
result in:
(a). no error (most of the time) :
(b). a memory fault error (sometimes) ,
(c). the buffer being fully or partially overwritten with zeros
(rarely).

(a) and (b) are likely not at all security critical, but the randomness
and rareness of this occurring makes it a tricky problem to debug. It is
case (c) that could represent a security issue.

(c) could be understood as working as intended since by zeroing out the
buffer when the LockedBuffer goes out of scope memguard achieves the
feature: "Accidental memory leaks are mitigated against by harnessing
the garbage-collector to automatically destroy containers that have
become unreachable". However it can have a limited security impact (see
below).

Security impact
====

If memguard LockedBuffers are used for keying material then encrypting a
message with a secret key which is partially or fully changed to zero
may result in a loss of security. It may not be clear to an implementer
the security difference between:

```go
// This is insecure, very rarely you might get an all zero key
key := lb.Bytes()
ct := Encrypt(key, msg)
```
```go
// This is secure
ct := Encrypt(lb.Bytes(), msg)
```

I consider the security impact here to be minimal because:
1. Most projects create a LockedBuffer that lives in a struct that
exists for the lifetime of the process so GC is never called until the
process ends. Looking at all the projects that use memguard on github I
was unable to find a single one that this behavior would impact.
2. If they are working with ephemeral secrets then they should be seting
`defer lb.Destroy()` this prevents the GC from eating the buffer. The
defer keeps a reference to lb and GC isn't called until it leaves the
function. I experimentally tested this.
3. If they are not working with ephemeral secrets, but instead using the
LockedBuffer to manage secrets needed through out the lifetime of the
process, then the LockedBuffer shouldn't be garbage collected at all.
2024-03-28 15:16:02 -04:00

1302 lines
26 KiB
Go

package memguard
import (
"bytes"
"crypto/rand"
"errors"
"io"
mrand "math/rand"
"os"
"runtime"
"testing"
"unsafe"
)
func TestPtrSafetyWithGC(t *testing.T) {
dataToLock := []byte(`abcdefgh`)
b := NewBufferFromBytes(dataToLock)
dataPtr := b.Bytes()
finalizerCalledChan := make(chan bool)
runtime.SetFinalizer(b, func(_ *LockedBuffer) {
finalizerCalledChan <- true
})
// b is now unreachable
runtime.GC()
finalizerCalled := <-finalizerCalledChan
if finalizerCalled == false {
t.Error("this should never occur")
}
// Check that data hasn't been garbage collected
if !bytes.Equal(dataPtr, []byte(`abcdefgh`)) {
t.Error("data does not have the value we set")
}
}
func TestNewBuffer(t *testing.T) {
b := NewBuffer(32)
if b == nil {
t.Error("buffer should not be nil")
}
if len(b.Bytes()) != 32 || cap(b.Bytes()) != 32 {
t.Error("buffer sizes incorrect")
}
if !bytes.Equal(b.Bytes(), make([]byte, 32)) {
t.Error("buffer is not zeroed")
}
if !b.IsMutable() {
t.Error("buffer should be mutable")
}
if !b.IsAlive() {
t.Error("buffer should not be destroyed")
}
b.Destroy()
b = NewBuffer(0)
if b.Bytes() != nil {
t.Error("data slice should be nil")
}
if b.Size() != 0 {
t.Error("size should be zero", b.Size())
}
if b.IsAlive() {
t.Error("buffer should be destroyed")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
}
func TestNewBufferFromBytes(t *testing.T) {
data := []byte("yellow submarine")
b := NewBufferFromBytes(data)
if b == nil {
t.Error("buffer should not be nil")
}
if len(b.Bytes()) != 16 || cap(b.Bytes()) != 16 {
t.Error("buffer sizes invalid")
}
if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) {
t.Error("data does not match\n", b.Bytes(), "\n", data)
}
if !bytes.Equal(data, make([]byte, 16)) {
t.Error("source buffer not wiped")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
if !b.IsAlive() {
t.Error("buffer should not be destroyed")
}
b.Destroy()
b = NewBufferFromBytes([]byte{})
if b.Bytes() != nil {
t.Error("data slice should be nil")
}
if b.Size() != 0 {
t.Error("size should be zero", b.Size())
}
if b.IsAlive() {
t.Error("buffer should be destroyed")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
}
func TestNewBufferFromReader(t *testing.T) {
b, err := NewBufferFromReader(rand.Reader, 4096)
if err != nil {
t.Error(err)
}
if b.Size() != 4096 {
t.Error("buffer of incorrect size")
}
if bytes.Equal(b.Bytes(), make([]byte, 4096)) {
t.Error("didn't read from reader")
}
if b.IsMutable() {
t.Error("expected buffer to be immutable")
}
b.Destroy()
r := bytes.NewReader([]byte("yellow submarine"))
b, err = NewBufferFromReader(r, 16)
if err != nil {
t.Error(err)
}
if b.Size() != 16 {
t.Error("buffer of incorrect size")
}
if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) {
t.Error("incorrect data")
}
if b.IsMutable() {
t.Error("expected buffer to be immutable")
}
b.Destroy()
r = bytes.NewReader([]byte("yellow submarine"))
b, err = NewBufferFromReader(r, 17)
if err == nil {
t.Error("expected error got nil;", err)
}
if b.Size() != 16 {
t.Error("incorrect size")
}
if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) {
t.Error("incorrect data")
}
if b.IsMutable() {
t.Error("expected buffer to be immutable")
}
b.Destroy()
r = bytes.NewReader([]byte(""))
b, err = NewBufferFromReader(r, 32)
if err == nil {
t.Error("expected error got nil")
}
if b.IsAlive() {
t.Error("expected destroyed buffer")
}
if b.IsMutable() {
t.Error("expected immutable buffer")
}
if b.Size() != 0 {
t.Error("expected nul sized buffer")
}
r = bytes.NewReader([]byte("yellow submarine"))
b, err = NewBufferFromReader(r, 0)
if err != nil {
t.Error(err)
}
if b.Bytes() != nil {
t.Error("data slice should be nil")
}
if b.Size() != 0 {
t.Error("size should be zero", b.Size())
}
if b.IsAlive() {
t.Error("buffer should be destroyed")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
}
type s struct {
count int
}
func (reader *s) Read(p []byte) (n int, err error) {
if mrand.Intn(2) == 0 {
return 0, nil
}
reader.count++
if reader.count == 5000 {
copy(p, []byte{1})
return 1, nil
}
copy(p, []byte{0})
return 1, nil
}
func TestNewBufferFromReaderUntil(t *testing.T) {
data := make([]byte, 5000)
data[4999] = 1
r := bytes.NewReader(data)
b, err := NewBufferFromReaderUntil(r, 1)
if err != nil {
t.Error(err)
}
if b.Size() != 4999 {
t.Error("buffer has incorrect size")
}
for i := range b.Bytes() {
if b.Bytes()[i] != 0 {
t.Error("incorrect data")
}
}
if b.IsMutable() {
t.Error("expected buffer to be immutable")
}
b.Destroy()
r = bytes.NewReader(data[:32])
b, err = NewBufferFromReaderUntil(r, 1)
if err == nil {
t.Error("expected error got nil")
}
if b.Size() != 32 {
t.Error("invalid size")
}
for i := range b.Bytes() {
if b.Bytes()[i] != 0 {
t.Error("incorrect data")
}
}
if b.IsMutable() {
t.Error("expected buffer to be immutable")
}
b.Destroy()
r = bytes.NewReader([]byte{'x'})
b, err = NewBufferFromReaderUntil(r, 'x')
if err != nil {
t.Error(err)
}
if b.Size() != 0 {
t.Error("expected no data")
}
if b.IsAlive() {
t.Error("expected dead buffer")
}
r = bytes.NewReader([]byte(""))
b, err = NewBufferFromReaderUntil(r, 1)
if err == nil {
t.Error("expected error got nil")
}
if b.IsAlive() {
t.Error("expected destroyed buffer")
}
if b.IsMutable() {
t.Error("expected immutable buffer")
}
if b.Size() != 0 {
t.Error("expected nul sized buffer")
}
rr := new(s)
b, err = NewBufferFromReaderUntil(rr, 1)
if err != nil {
t.Error(err)
}
if b.Size() != 4999 {
t.Error("invalid size")
}
for i := range b.Bytes() {
if b.Bytes()[i] != 0 {
t.Error("invalid data")
}
}
if b.IsMutable() {
t.Error("expected buffer to be immutable")
}
b.Destroy()
}
type ss struct {
count int
}
func (reader *ss) Read(p []byte) (n int, err error) {
if mrand.Intn(2) == 0 {
return 0, nil
}
reader.count++
if reader.count == 5000 {
return 0, io.EOF
}
copy(p, []byte{0})
return 1, nil
}
type se struct {
count int
}
func (reader *se) Read(p []byte) (n int, err error) {
copy(p, []byte{0})
reader.count++
if reader.count == 5000 {
return 1, errors.New("shut up bro")
}
return 1, nil
}
func TestNewBufferFromEntireReader(t *testing.T) {
r := bytes.NewReader([]byte("yellow submarine"))
b, err := NewBufferFromEntireReader(r)
if err != nil {
t.Error(err)
}
if b.Size() != 16 {
t.Error("incorrect size", b.Size())
}
if !b.EqualTo([]byte("yellow submarine")) {
t.Error("incorrect data", b.String())
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
b.Destroy()
data := make([]byte, 16000)
ScrambleBytes(data)
r = bytes.NewReader(data)
b, err = NewBufferFromEntireReader(r)
if err != nil {
t.Error(err)
}
if b.Size() != len(data) {
t.Error("incorrect size", b.Size())
}
if !b.EqualTo(data) {
t.Error("incorrect data")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
b.Destroy()
r = bytes.NewReader([]byte{})
b, err = NewBufferFromEntireReader(r)
if err != nil {
t.Error(err)
}
if b.Size() != 0 {
t.Error("buffer should be nil size")
}
if b.IsAlive() {
t.Error("buffer should appear destroyed")
}
rr := new(ss)
b, err = NewBufferFromEntireReader(rr)
if err != nil {
t.Error(err)
}
if b.Size() != 4999 {
t.Error("incorrect size", b.Size())
}
if !b.EqualTo(make([]byte, 4999)) {
t.Error("incorrect data")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
b.Destroy()
re := new(se)
b, err = NewBufferFromEntireReader(re)
if err == nil {
t.Error("expected error got nil")
}
if b.Size() != 5000 {
t.Error(b.Size())
}
if !b.EqualTo(make([]byte, 5000)) {
t.Error("incorrect data")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
b.Destroy()
// real world test
f, err := os.Open("LICENSE")
if err != nil {
t.Error(err)
}
data, err = io.ReadAll(f)
if err != nil {
t.Error(err)
}
_, err = f.Seek(0, 0)
if err != nil {
t.Error(err)
}
b, err = NewBufferFromEntireReader(f)
if err != nil {
t.Error(err)
}
if !b.EqualTo(data) {
t.Error("incorrect data")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
b.Destroy()
f.Close()
}
func TestNewBufferRandom(t *testing.T) {
b := NewBufferRandom(32)
if b == nil {
t.Error("buffer is nil")
}
if len(b.Bytes()) != 32 || cap(b.Bytes()) != 32 {
t.Error("buffer sizes incorrect")
}
if bytes.Equal(b.Bytes(), make([]byte, 32)) {
t.Error("buffer is zeroed")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
if !b.IsAlive() {
t.Error("buffer should not be destroyed")
}
b.Destroy()
b = NewBufferRandom(0)
if b.Bytes() != nil {
t.Error("data slice should be nil")
}
if b.Size() != 0 {
t.Error("size should be zero", b.Size())
}
if b.IsAlive() {
t.Error("buffer should be destroyed")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
}
func TestFreeze(t *testing.T) {
b := NewBuffer(8)
if b == nil {
t.Error("buffer is nil")
}
if !b.IsMutable() {
t.Error("buffer isn't mutable")
}
b.Freeze()
if b.IsMutable() {
t.Error("buffer did not change to immutable")
}
if !bytes.Equal(b.Bytes(), make([]byte, 8)) {
t.Error("buffer changed value") // also tests readability
}
b.Freeze() // Test idempotency
if b.IsMutable() {
t.Error("buffer should be immutable")
}
if !bytes.Equal(b.Bytes(), make([]byte, 8)) {
t.Error("buffer changed value") // also tests readability
}
b.Destroy()
b.Freeze()
if b.IsMutable() {
t.Error("buffer is mutable")
}
if b.IsAlive() {
t.Error("buffer should be destroyed")
}
b = newNullBuffer()
b.Freeze()
if b.IsMutable() {
t.Error("buffer should be immutable")
}
}
func TestMelt(t *testing.T) {
b := NewBuffer(8)
if b == nil {
t.Error("buffer is nil")
}
b.Freeze()
if b.IsMutable() {
t.Error("buffer is mutable")
}
b.Melt()
if !b.IsMutable() {
t.Error("buffer did not become mutable")
}
if !bytes.Equal(b.Bytes(), make([]byte, 8)) {
t.Error("buffer changed value") // also tests readability
}
b.Bytes()[0] = 0x1 // test writability
if b.Bytes()[0] != 0x1 {
t.Error("buffer value not changed")
}
b.Melt() // Test idempotency
if !b.IsMutable() {
t.Error("buffer should be mutable")
}
b.Bytes()[0] = 0x2
if b.Bytes()[0] != 0x2 {
t.Error("buffer value not changed")
}
b.Destroy()
b.Melt()
if b.IsMutable() {
t.Error("buffer shouldn't be mutable")
}
if b.IsAlive() {
t.Error("buffer should be destroyed")
}
b = newNullBuffer()
b.Melt()
if b.IsMutable() {
t.Error("buffer should be immutable")
}
}
func TestSeal(t *testing.T) {
b := NewBufferRandom(32)
if b == nil {
t.Error("buffer is nil")
}
data := make([]byte, 32)
copy(data, b.Bytes())
e := b.Seal()
if e == nil {
t.Error("got nil enclave")
}
if b.IsAlive() {
t.Error("buffer should be destroyed")
}
b, err := e.Open()
if err != nil {
t.Error("unexpected error;", err)
}
if !bytes.Equal(b.Bytes(), data) {
t.Error("data does not match")
}
b.Destroy()
e = b.Seal() // call on destroyed buffer
if e != nil {
t.Error("expected nil enclave")
}
}
func TestCopy(t *testing.T) {
b := NewBuffer(16)
if b == nil {
t.Error("buffer is nil")
}
b.Copy([]byte("yellow submarine"))
if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) {
t.Error("copy unsuccessful")
}
b.Destroy()
b.Copy([]byte("yellow submarine"))
if b.Bytes() != nil {
t.Error("buffer should be destroyed")
}
b = newNullBuffer()
b.Copy([]byte("yellow submarine"))
}
func TestCopyAt(t *testing.T) {
b := NewBuffer(8)
if b == nil {
t.Error("got nil buffer")
}
b.CopyAt(0, []byte("1234"))
if !bytes.Equal(b.Bytes()[:4], []byte("1234")) {
t.Error("copy unsuccessful")
}
if !bytes.Equal(b.Bytes()[4:], []byte{0, 0, 0, 0}) {
t.Error("copy overflow")
}
b.CopyAt(4, []byte("5678"))
if !bytes.Equal(b.Bytes(), []byte("12345678")) {
t.Error("copy unsuccessful")
}
b.Destroy()
b.CopyAt(4, []byte("hmmm"))
if b.Bytes() != nil {
t.Error("buffer should be destroyed")
}
b = newNullBuffer()
b.CopyAt(4, []byte("yellow submarine"))
}
func TestMove(t *testing.T) {
b := NewBuffer(16)
if b == nil {
t.Error("buffer is nil")
}
b.Move([]byte("yellow submarine"))
if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) {
t.Error("copy unsuccessful")
}
data := []byte("yellow submarine")
b.Move(data)
for b := range data {
if data[b] != 0x0 {
t.Error("buffer was not wiped", b)
}
}
b.Destroy()
b.Move(data)
if b.Bytes() != nil {
t.Error("buffer should be destroyed")
}
b = newNullBuffer()
b.Move([]byte("yellow submarine"))
}
func TestMoveAt(t *testing.T) {
b := NewBuffer(8)
if b == nil {
t.Error("got nil buffer")
}
data := []byte("12345678")
b.MoveAt(0, data[:4])
if !bytes.Equal(b.Bytes()[:4], []byte("1234")) {
t.Error("copy unsuccessful")
}
if !bytes.Equal(b.Bytes()[4:], []byte{0, 0, 0, 0}) {
t.Error("copy overflow")
}
b.MoveAt(4, data[4:])
if !bytes.Equal(b.Bytes(), []byte("12345678")) {
t.Error("copy unsuccessful")
}
if !bytes.Equal(data, make([]byte, 8)) {
t.Error("buffer not wiped")
}
b.Destroy()
b.MoveAt(4, []byte("hmmm"))
if b.Bytes() != nil {
t.Error("buffer should be destroyed")
}
b = newNullBuffer()
b.MoveAt(4, []byte("yellow submarine"))
}
func TestScramble(t *testing.T) {
b := NewBuffer(32)
if b == nil {
t.Error("buffer is nil")
}
b.Scramble()
if bytes.Equal(b.Bytes(), make([]byte, 32)) {
t.Error("buffer was not randomised")
}
one := make([]byte, 32)
copy(one, b.Bytes())
b.Scramble()
if bytes.Equal(b.Bytes(), make([]byte, 32)) {
t.Error("buffer was not randomised")
}
if bytes.Equal(b.Bytes(), one) {
t.Error("buffer did not change")
}
b.Destroy()
b.Scramble()
if b.Bytes() != nil {
t.Error("buffer should be destroyed")
}
b = newNullBuffer()
b.Scramble()
}
func TestWipe(t *testing.T) {
b := NewBufferRandom(32)
if b == nil {
t.Error("got nil buffer")
}
b.Melt()
if bytes.Equal(b.Bytes(), make([]byte, 32)) {
t.Error("buffer was not randomised")
}
b.Wipe()
for i := range b.Bytes() {
if b.Bytes()[i] != 0 {
t.Error("buffer was not wiped; index", i)
}
}
b.Destroy()
b.Wipe()
if b.Bytes() != nil {
t.Error("buffer should be destroyed")
}
b = newNullBuffer()
b.Wipe()
}
func TestSize(t *testing.T) {
b := NewBuffer(1234)
if b == nil {
t.Error("got nil buffer")
}
if b.Size() != 1234 {
t.Error("size does not match expected")
}
b.Destroy()
if b.Size() != 0 {
t.Error("destroyed buffer size should be zero")
}
b = newNullBuffer()
if b.Size() != 0 {
t.Error("size should be zero")
}
}
func TestDestroy(t *testing.T) {
b := NewBuffer(32)
if b == nil {
t.Error("got nil buffer")
}
if b.Bytes() == nil {
t.Error("expected buffer to not be nil")
}
if len(b.Bytes()) != 32 || cap(b.Bytes()) != 32 {
t.Error("buffer sizes incorrect")
}
if !b.IsAlive() {
t.Error("buffer should be alive")
}
if !b.IsMutable() {
t.Error("buffer should be mutable")
}
b.Destroy()
if b.Bytes() != nil {
t.Error("expected buffer to be nil")
}
if len(b.Bytes()) != 0 || cap(b.Bytes()) != 0 {
t.Error("buffer sizes incorrect")
}
if b.IsAlive() {
t.Error("buffer should be destroyed")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
b.Destroy()
if b.Bytes() != nil {
t.Error("expected buffer to be nil")
}
if len(b.Bytes()) != 0 || cap(b.Bytes()) != 0 {
t.Error("buffer sizes incorrect")
}
if b.IsAlive() {
t.Error("buffer should be destroyed")
}
if b.IsMutable() {
t.Error("buffer should be immutable")
}
b = newNullBuffer()
b.Destroy()
if b.IsAlive() {
t.Error("buffer should be dead")
}
}
func TestIsAlive(t *testing.T) {
b := NewBuffer(8)
if b == nil {
t.Error("got nil buffer")
}
if !b.IsAlive() {
t.Error("invalid state")
}
if b.IsAlive() != b.IsAlive() {
t.Error("states don't match")
}
b.Destroy()
if b.IsAlive() {
t.Error("invalid state")
}
if b.IsAlive() != b.IsAlive() {
t.Error("states don't match")
}
b = newNullBuffer()
if b.IsAlive() {
t.Error("buffer should be dead")
}
}
func TestIsMutable(t *testing.T) {
b := NewBuffer(8)
if b == nil {
t.Error("got nil buffer")
}
if !b.IsMutable() {
t.Error("invalid state")
}
if b.IsMutable() != b.IsMutable() {
t.Error("states don't match")
}
b.Freeze()
if b.IsMutable() {
t.Error("invalid state")
}
if b.IsMutable() != b.IsMutable() {
t.Error("states don't match")
}
b.Destroy()
if b.IsMutable() {
t.Error("invalid state")
}
if b.IsMutable() != b.IsMutable() {
t.Error("states don't match")
}
b = newNullBuffer()
if b.IsMutable() {
t.Error("buffer should be immutable")
}
}
func TestEqualTo(t *testing.T) {
b := NewBufferFromBytes([]byte("yellow submarine"))
if !b.EqualTo([]byte("yellow submarine")) {
t.Error("comparison incorrect")
}
if b.EqualTo([]byte("yellow")) {
t.Error("comparison incorrect")
}
b.Destroy()
if b.EqualTo([]byte("yellow submarine")) {
t.Error("comparison with destroyed should be false")
}
b = newNullBuffer()
if !b.EqualTo([]byte{}) {
t.Error("buffer should be size zero")
}
}
func TestBytes(t *testing.T) {
b := NewBufferFromBytes([]byte("yellow submarine"))
if b == nil {
t.Error("got nil buffer")
}
if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) {
t.Error("not equal contents")
}
b.Melt()
b.Bytes()[8] = ^b.Bytes()[8]
if !bytes.Equal(b.Buffer.Data(), b.Bytes()) {
t.Error("methods disagree")
}
b.Destroy()
if b.Bytes() != nil {
t.Error("expected nil buffer")
}
b = newNullBuffer()
if b.Bytes() != nil {
t.Error("buffer should be nil")
}
}
func TestReader(t *testing.T) {
b := NewBufferRandom(32)
c, err := NewBufferFromReader(b.Reader(), 32)
if err != nil {
t.Error(err)
}
if !bytes.Equal(b.Bytes(), c.Bytes()) {
t.Error("data not equal")
}
b.Destroy()
c.Destroy()
if c.Reader().Size() != 0 {
t.Error("expected nul reader")
}
b = newNullBuffer()
if c.Reader().Size() != 0 {
t.Error("expected nul reader")
}
}
func TestString(t *testing.T) {
b := NewBufferRandom(32)
b.Melt()
s := b.String()
for i := range b.Bytes() {
b.Bytes()[i] = 'x'
if string(b.Bytes()) != s {
t.Error("string does not map same memory")
}
}
b.Destroy()
s = b.String()
if s != "" {
t.Error("string should be empty")
}
b = newNullBuffer()
if s != "" {
t.Error("string should be empty")
}
}
func TestUint16(t *testing.T) {
b := NewBuffer(32)
if b == nil {
t.Error("got nil buffer")
}
u16 := b.Uint16()
if len(u16) != 16 || cap(u16) != 16 {
t.Error("sizes incorrect")
}
if uintptr(unsafe.Pointer(&u16[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(3)
if b == nil {
t.Error("got nil buffer")
}
u16 = b.Uint16()
if len(u16) != 1 || cap(u16) != 1 {
t.Error("sizes should be 1")
}
if uintptr(unsafe.Pointer(&u16[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(1)
if b == nil {
t.Error("got nil buffer")
}
u16 = b.Uint16()
if u16 != nil {
t.Error("expected nil slice")
}
b.Destroy()
if b.Uint16() != nil {
t.Error("expected nil slice as buffer destroyed")
}
b = newNullBuffer()
if b.Uint16() != nil {
t.Error("should be nil")
}
}
func TestUint32(t *testing.T) {
b := NewBuffer(32)
if b == nil {
t.Error("got nil buffer")
}
u32 := b.Uint32()
if len(u32) != 8 || cap(u32) != 8 {
t.Error("sizes incorrect")
}
if uintptr(unsafe.Pointer(&u32[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(5)
if b == nil {
t.Error("got nil buffer")
}
u32 = b.Uint32()
if len(u32) != 1 || cap(u32) != 1 {
t.Error("sizes should be 1")
}
if uintptr(unsafe.Pointer(&u32[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(3)
if b == nil {
t.Error("got nil buffer")
}
u32 = b.Uint32()
if u32 != nil {
t.Error("expected nil slice")
}
b.Destroy()
if b.Uint32() != nil {
t.Error("expected nil slice as buffer destroyed")
}
b = newNullBuffer()
if b.Uint32() != nil {
t.Error("should be nil")
}
}
func TestUint64(t *testing.T) {
b := NewBuffer(32)
if b == nil {
t.Error("got nil buffer")
}
u64 := b.Uint64()
if len(u64) != 4 || cap(u64) != 4 {
t.Error("sizes incorrect")
}
if uintptr(unsafe.Pointer(&u64[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(9)
if b == nil {
t.Error("got nil buffer")
}
u64 = b.Uint64()
if len(u64) != 1 || cap(u64) != 1 {
t.Error("sizes should be 1")
}
if uintptr(unsafe.Pointer(&u64[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(7)
if b == nil {
t.Error("got nil buffer")
}
u64 = b.Uint64()
if u64 != nil {
t.Error("expected nil slice")
}
b.Destroy()
if b.Uint64() != nil {
t.Error("expected nil slice as buffer destroyed")
}
b = newNullBuffer()
if b.Uint64() != nil {
t.Error("should be nil")
}
}
func TestInt8(t *testing.T) {
b := NewBuffer(32)
if b == nil {
t.Error("got nil buffer")
}
i8 := b.Int8()
if len(i8) != 32 || cap(i8) != 32 {
t.Error("sizes incorrect")
}
if uintptr(unsafe.Pointer(&i8[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
if b.Int8() != nil {
t.Error("expected nil slice as buffer destroyed")
}
b = newNullBuffer()
if b.Int8() != nil {
t.Error("should be nil")
}
}
func TestInt16(t *testing.T) {
b := NewBuffer(32)
if b == nil {
t.Error("got nil buffer")
}
i16 := b.Int16()
if len(i16) != 16 || cap(i16) != 16 {
t.Error("sizes incorrect")
}
if uintptr(unsafe.Pointer(&i16[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(3)
if b == nil {
t.Error("got nil buffer")
}
i16 = b.Int16()
if len(i16) != 1 || cap(i16) != 1 {
t.Error("sizes should be 1")
}
if uintptr(unsafe.Pointer(&i16[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(1)
if b == nil {
t.Error("got nil buffer")
}
i16 = b.Int16()
if i16 != nil {
t.Error("expected nil slice")
}
b.Destroy()
if b.Int16() != nil {
t.Error("expected nil slice as buffer destroyed")
}
b = newNullBuffer()
if b.Int16() != nil {
t.Error("should be nil")
}
}
func TestInt32(t *testing.T) {
b := NewBuffer(32)
if b == nil {
t.Error("got nil buffer")
}
i32 := b.Int32()
if len(i32) != 8 || cap(i32) != 8 {
t.Error("sizes incorrect")
}
if uintptr(unsafe.Pointer(&i32[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(5)
if b == nil {
t.Error("got nil buffer")
}
i32 = b.Int32()
if len(i32) != 1 || cap(i32) != 1 {
t.Error("sizes should be 1")
}
if uintptr(unsafe.Pointer(&i32[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(3)
if b == nil {
t.Error("got nil buffer")
}
i32 = b.Int32()
if i32 != nil {
t.Error("expected nil slice")
}
b.Destroy()
if b.Int32() != nil {
t.Error("expected nil slice as buffer destroyed")
}
b = newNullBuffer()
if b.Int32() != nil {
t.Error("should be nil")
}
}
func TestInt64(t *testing.T) {
b := NewBuffer(32)
if b == nil {
t.Error("got nil buffer")
}
i64 := b.Int64()
if len(i64) != 4 || cap(i64) != 4 {
t.Error("sizes incorrect")
}
if uintptr(unsafe.Pointer(&i64[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(9)
if b == nil {
t.Error("got nil buffer")
}
i64 = b.Int64()
if len(i64) != 1 || cap(i64) != 1 {
t.Error("sizes should be 1")
}
if uintptr(unsafe.Pointer(&i64[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(7)
if b == nil {
t.Error("got nil buffer")
}
i64 = b.Int64()
if i64 != nil {
t.Error("expected nil slice")
}
b.Destroy()
if b.Int64() != nil {
t.Error("expected nil slice as buffer destroyed")
}
b = newNullBuffer()
if b.Int32() != nil {
t.Error("should be nil")
}
}
func TestByteArray8(t *testing.T) {
b := NewBuffer(8)
if b == nil {
t.Error("got nil buffer")
}
if uintptr(unsafe.Pointer(&b.ByteArray8()[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(7)
if b == nil {
t.Error("got nil buffer")
}
if b.ByteArray8() != nil {
t.Error("expected nil byte array")
}
b.Destroy()
if b.ByteArray8() != nil {
t.Error("expected nil byte array from destroyed buffer")
}
b = newNullBuffer()
if b.ByteArray8() != nil {
t.Error("should be nil")
}
}
func TestByteArray16(t *testing.T) {
b := NewBuffer(16)
if b == nil {
t.Error("got nil buffer")
}
if uintptr(unsafe.Pointer(&b.ByteArray16()[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(15)
if b == nil {
t.Error("got nil buffer")
}
if b.ByteArray16() != nil {
t.Error("expected nil byte array")
}
b.Destroy()
if b.ByteArray16() != nil {
t.Error("expected nil byte array from destroyed buffer")
}
b = newNullBuffer()
if b.ByteArray16() != nil {
t.Error("should be nil")
}
}
func TestByteArray32(t *testing.T) {
b := NewBuffer(32)
if b == nil {
t.Error("got nil buffer")
}
if uintptr(unsafe.Pointer(&b.ByteArray32()[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(31)
if b == nil {
t.Error("got nil buffer")
}
if b.ByteArray32() != nil {
t.Error("expected nil byte array")
}
b.Destroy()
if b.ByteArray32() != nil {
t.Error("expected nil byte array from destroyed buffer")
}
b = newNullBuffer()
if b.ByteArray32() != nil {
t.Error("should be nil")
}
}
func TestByteArray64(t *testing.T) {
b := NewBuffer(64)
if b == nil {
t.Error("got nil buffer")
}
if uintptr(unsafe.Pointer(&b.ByteArray64()[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) {
t.Error("pointer locations differ")
}
b.Destroy()
b = NewBuffer(63)
if b == nil {
t.Error("got nil buffer")
}
if b.ByteArray64() != nil {
t.Error("expected nil byte array")
}
b.Destroy()
if b.ByteArray64() != nil {
t.Error("expected nil byte array from destroyed buffer")
}
b = newNullBuffer()
if b.ByteArray64() != nil {
t.Error("should be nil")
}
}