summary refs log tree commit diff stats
path: root/internal/buffer
diff options
context:
space:
mode:
Diffstat (limited to 'internal/buffer')
-rw-r--r--internal/buffer/buffer.go95
-rw-r--r--internal/buffer/buffer_test.go88
2 files changed, 183 insertions, 0 deletions
diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go
new file mode 100644
index 0000000..055bf7f
--- /dev/null
+++ b/internal/buffer/buffer.go
@@ -0,0 +1,95 @@
+package buffer
+
+import (
+	"io"
+)
+
+type Buffer struct {
+	buf []byte
+	pos int
+	len int
+}
+
+func NewBuffer(buf []byte) *Buffer {
+	return &Buffer{
+		buf: buf,
+		pos: 0,
+		len: len(buf),
+	}
+}
+
+// Read implements io.Reader's Read method
+func (b *Buffer) Read(p []byte) (int, error) {
+	if b.pos >= b.len {
+		return 0, io.EOF
+	}
+
+	n := len(p)
+	if n > b.len-b.pos {
+		n = b.len - b.pos
+	}
+
+	copy(p[:n], b.buf[b.pos:b.pos+n])
+	b.pos += n
+
+	return n, nil
+}
+
+// Write appends the contents of p to the buffer's data.
+func (b *Buffer) Write(p []byte) (int, error) {
+	if len(b.buf) < b.len+len(p) {
+		newLen := b.len + len(p)
+		if cap(b.buf) >= newLen {
+			b.buf = b.buf[:newLen]
+		} else {
+			newBuf := make([]byte, newLen*2)
+			copy(newBuf, b.buf[:b.len])
+			b.buf = newBuf
+		}
+	}
+
+	copy(b.buf[b.len:], p)
+	b.len += len(p)
+
+	return len(p), nil
+}
+
+func (b *Buffer) Len() int {
+	return b.len
+}
+
+// Reset resets the buffer to be empty. The underlying array is reused if possible.
+func (b *Buffer) Reset() {
+	b.len = 0
+	b.pos = 0
+}
+
+// Seek moves the read position by offset bytes relative to whence (Start, Current, End)
+func (b *Buffer) Seek(offset int64, whence int) (int64, error) {
+	var newpos int
+
+	switch whence {
+	case io.SeekStart:
+		newpos = int(offset)
+	case io.SeekCurrent:
+		newpos = b.pos + int(offset)
+	case io.SeekEnd:
+		newpos = b.len + int(offset)
+	default:
+		return 0, io.EOF
+	}
+
+	if newpos < 0 {
+		newpos = 0
+	} else if newpos > b.len {
+		newpos = b.len
+	}
+
+	b.pos = newpos
+
+	return int64(newpos), nil
+}
+
+func (b *Buffer) Bytes() []byte {
+	return b.buf[:b.len]
+}
diff --git a/internal/buffer/buffer_test.go b/internal/buffer/buffer_test.go
new file mode 100644
index 0000000..9cddeca
--- /dev/null
+++ b/internal/buffer/buffer_test.go
@@ -0,0 +1,88 @@
+package buffer
+
+import (
+	"io"
+	"testing"
+)
+
+func TestWrite(t *testing.T) {
+	b := Buffer{}
+	data := []byte("test")
+
+	n, err := b.Write(data)
+	if n != len(data) || err != nil {
+		t.Errorf("Write failed: expected %d bytes, got %d, error: %v", len(data), n, err)
+	}
+
+	if b.Len() != len(data) {
+		t.Errorf("Len is incorrect after write: expected %d, got %d", len(data), b.Len())
+	}
+
+	if string(b.Bytes()) != "test" {
+		t.Error("Bytes returned after write do not match written data")
+	}
+}
+
+func TestRead(t *testing.T) {
+	b := NewBuffer([]byte("testdata"))
+	p := make([]byte, 3)
+	n, err := b.Read(p)
+
+	if n != 3 || string(p[:n]) != "tes" {
+		t.Errorf("Read returned incorrect data: expected 'tes', got '%s'", p[:n])
+	}
+
+	b.Reset()
+	b.Write([]byte("abc"))
+	p = make([]byte, 2)
+	n, err = b.Read(p)
+
+	if n != 2 || string(p) != "ab" {
+		t.Errorf("Read after reset failed: expected 'ab', got '%s'", p)
+	}
+
+	b.pos = b.len
+	n, err = b.Read(p)
+	if n != 0 || err != io.EOF {
+		t.Errorf("Reading beyond buffer did not return EOF: n=%d, err=%v", n, err)
+	}
+}
+
+func TestReset(t *testing.T) {
+	b := NewBuffer([]byte("test"))
+	b.Write([]byte("data"))
+
+	if b.Len() != 8 || b.pos != 0 {
+		t.Errorf("Initial buffer state incorrect: len=%d, pos=%d", b.Len(), b.pos)
+	}
+
+	b.Reset()
+	if b.Len() != 0 || b.pos != 0 {
+		t.Errorf("Reset did not clear buffer correctly: len=%d, pos=%d", b.Len(), b.pos)
+	}
+}
+
+func TestSeek(t *testing.T) {
+	b := NewBuffer([]byte("test"))
+	tests := []struct {
+		offset int64
+		whence int
+		expect int64
+		err    error
+	}{
+		{2, io.SeekStart, 2, nil},
+		{-1, io.SeekCurrent, 1, nil},
+		{-2, io.SeekEnd, int64(len("test") - 2), nil},
+		{5, io.SeekStart, int64(len("test")), nil},
+		{-10, io.SeekEnd, 0, nil},
+		{0, 999, 0, io.EOF}, // Invalid whence test
+	}
+
+	for _, tt := range tests {
+		pos, err := b.Seek(tt.offset, tt.whence)
+		if pos != tt.expect || (err != tt.err && !((err == nil) && (tt.err == nil))) {
+			t.Errorf("Seek(%d, %d): expected %d with error %v, got %d and %v",
+				tt.offset, tt.whence, tt.expect, tt.err, pos, err)
+		}
+	}
+}