Skip to content

Commit

Permalink
Added buffer abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
oxtoacart committed Feb 13, 2017
1 parent 4f748b9 commit 76e9c93
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 0 deletions.
98 changes: 98 additions & 0 deletions buffer.go
@@ -0,0 +1,98 @@
package lampshade

import (
"sync"
"time"
)

type buffer struct {
head *bufferEntry
tail *bufferEntry
onData chan bool
size int
mx sync.Mutex
}

type bufferEntry struct {
data []byte
next *bufferEntry
}

func newBuffer() *buffer {
return &buffer{}
}

func (b *buffer) write(p []byte) {
b.mx.Lock()
b.doWrite(p)
b.mx.Unlock()
}

func (b *buffer) doWrite(p []byte) {
if b.tail == nil {
b.tail = &bufferEntry{data: p}
b.head = b.tail
b.size += len(p)
return
}
b.tail.next = &bufferEntry{data: p}
b.size++
}

func (b *buffer) read(p []byte, deadline time.Time) (int, error) {
var now time.Time
var onData chan bool

b.mx.Lock()
n := b.doRead(p)
if n == 0 {
now = time.Now()
if !deadline.IsZero() && deadline.Before(now) {
b.mx.Unlock()
return 0, ErrTimeout
}
onData = make(chan bool)
b.onData = onData
}
b.mx.Unlock()
if n > 0 {
return n, nil
}
if deadline.IsZero() {
// wait indefinitely
<-onData
return b.read(p, deadline)
}
timeout := time.NewTimer(deadline.Sub(now))
select {
case <-onData:
timeout.Stop()
return b.read(p, deadline)
case <-timeout.C:
timeout.Stop()
return 0, ErrTimeout
}
}

func (b *buffer) doRead(p []byte) (totalN int) {
for {
if b.head == nil {
return
}
n := copy(p, b.head.data)
totalN += n
b.size -= n
if n < len(b.head.data) {
b.head.data = b.head.data[n:]
return
}
b.head = b.head.next
if b.head == nil {
b.tail = nil
}
if n == len(p) {
return
}
p = p[n:]
}
}
49 changes: 49 additions & 0 deletions buffer_test.go
@@ -0,0 +1,49 @@
package lampshade

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestBuffer(t *testing.T) {
buf := newBuffer()
p := make([]byte, 1000)
zeroTime := time.Time{}

_, err := buf.read(p, time.Now().Add(-1*time.Hour))
if !assert.Error(t, err, "read from empty buffer with past deadline should fail") {
return
}

_, err = buf.read(p, time.Now().Add(25*time.Millisecond))
if !assert.Error(t, err, "read from empty buffer with future deadline should fail") {
return
}

buf.write([]byte("a"))
assert.Equal(t, 1, buf.size)
n, err := buf.read(p, zeroTime)
if !assert.NoError(t, err) || !assert.Equal(t, 1, n) {
return
}
assert.Equal(t, "a", string(p[:n]))
assert.Equal(t, 0, buf.size)

buf.write([]byte("ab"))
assert.Equal(t, 2, buf.size)
buf.write([]byte("c"))
assert.Equal(t, 3, buf.size)
n, err = buf.read(p[:1], zeroTime)
if !assert.NoError(t, err) || !assert.Equal(t, 1, n) {
return
}
assert.Equal(t, 2, buf.size)
n, err = buf.read(p[1:], zeroTime)
if !assert.NoError(t, err) || !assert.Equal(t, 2, n) {
return
}
assert.Equal(t, "abc", string(p[:3]))
assert.Equal(t, 0, buf.size)
}
20 changes: 20 additions & 0 deletions crypto_test.go
Expand Up @@ -13,6 +13,26 @@ import (
"golang.org/x/crypto/chacha20poly1305"
)

func BenchmarkAlloc(b *testing.B) {
source := make([]byte, 8192)
rand.Read(source)
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf := make([]byte, 8192)
copy(buf, source)
}
}

func BenchmarkNoAlloc(b *testing.B) {
source := make([]byte, 8192)
rand.Read(source)
buf := make([]byte, 8192)
b.ResetTimer()
for i := 0; i < b.N; i++ {
copy(buf, source)
}
}

func TestInitAESCTR(t *testing.T) {
doTestInit(t, AES128CTR)
}
Expand Down

0 comments on commit 76e9c93

Please sign in to comment.