Created
June 29, 2024 06:28
-
-
Save alexjoedt/7fb5bdcf7da4a94d8ef59d18d96e6712 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package bytes | |
import ( | |
"encoding/binary" | |
"io" | |
) | |
// ErrBufferOverflow is triggered when the buffer overflows. | |
var ( | |
ErrBufferOverflow = errors.New("buffer overflow") | |
) | |
// FixedBuffer is a builder for binary data with a fixed size. | |
// This builder allows for efficient management | |
// of a fixed buffer without dynamic memory allocations during runtime. | |
type FixedBuffer struct { | |
readIndex int | |
writeIndex int | |
buf []byte | |
order binary.ByteOrder | |
} | |
// NewFixedBuffer creates a new instance of FixedBuffer with the | |
// specified size and a default byte order. | |
func NewFixedBuffer(size int, order binary.ByteOrder) *FixedBuffer { | |
return &FixedBuffer{ | |
buf: make([]byte, size), | |
order: order, | |
} | |
} | |
// checkSize checks if there is enough space in the buffer to write n bytes. | |
func (bb *FixedBuffer) checkSize(n int) error { | |
if bb.writeIndex+n > len(bb.buf) { | |
return ErrBufferOverflow | |
} | |
return nil | |
} | |
// WriteData writes data to the buffer using the default byte order. | |
func (bb *FixedBuffer) WriteData(data any) error { | |
return bb.writeData(data, bb.order) | |
} | |
// WriteDataOrder writes data to the buffer using the specified byte order. | |
func (bb *FixedBuffer) WriteDataOrder(data any, order binary.ByteOrder) error { | |
return bb.writeData(data, order) | |
} | |
// writeData is a helper function that writes data to the buffer. | |
func (bb *FixedBuffer) writeData(data any, order binary.ByteOrder) error { | |
switch v := data.(type) { | |
case string: | |
_, err := bb.Write([]byte(v)) | |
if err != nil { | |
return err | |
} | |
default: | |
return binary.Write(bb, order, v) | |
} | |
return nil | |
} | |
// Bytes returns the current content of the buffer. | |
func (bb *FixedBuffer) Bytes() []byte { | |
return bb.buf[bb.readIndex:bb.writeIndex] | |
} | |
// Write implements the io.Writer interface and writes data to the buffer. | |
func (bb *FixedBuffer) Write(p []byte) (int, error) { | |
if err := bb.checkSize(len(p)); err != nil { | |
return 0, err | |
} | |
n := copy(bb.buf[bb.writeIndex:], p) | |
bb.writeIndex += n | |
return n, nil | |
} | |
// Read implements the io.Reader interface and reads data from the buffer. | |
func (bb *FixedBuffer) Read(p []byte) (int, error) { | |
if bb.readIndex >= bb.writeIndex { | |
return 0, io.EOF | |
} | |
n := copy(p, bb.buf[bb.readIndex:bb.writeIndex]) | |
bb.readIndex += n | |
return n, nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package bytes | |
import ( | |
"bytes" | |
"encoding/binary" | |
"io" | |
"testing" | |
"github.com/stretchr/testify/assert" | |
"github.com/stretchr/testify/require" | |
) | |
func TestWriteData(t *testing.T) { | |
t.Parallel() | |
testCases := []struct { | |
name string | |
size int | |
order binary.ByteOrder | |
dataToWrite []any | |
expectedBytes []byte | |
expectedError error | |
}{ | |
{ | |
name: "write data", | |
order: binary.BigEndian, | |
size: 22, | |
dataToWrite: []any{ | |
uint8(1), | |
uint16(2), | |
[]uint16{3, 4}, | |
uint32(5), | |
uint64(6), | |
[]byte{7, 8}, | |
"9", | |
}, | |
expectedBytes: []byte{1, 0, 2, 0, 3, 0, 4, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 7, 8, 57}, | |
}, | |
{ | |
name: "write data which size is too big", | |
order: binary.BigEndian, | |
size: 1, | |
dataToWrite: []any{ | |
[]byte{7, 8}, | |
}, | |
expectedBytes: []byte{}, | |
expectedError: ErrBufferOverflow, | |
}, | |
} | |
for _, tc := range testCases { | |
tc := tc | |
t.Run(tc.name, func(t *testing.T) { | |
t.Parallel() | |
a := assert.New(t) | |
bb := NewFixedBuffer(tc.size, tc.order) | |
for _, p := range tc.dataToWrite { | |
err := bb.WriteData(p) | |
a.Equal(tc.expectedError, err) | |
} | |
a.Equal(tc.expectedBytes, bb.Bytes()) | |
}) | |
} | |
} | |
func TestWriteRead(t *testing.T) { | |
t.Parallel() | |
testCases := []struct { | |
name string | |
size int | |
order binary.ByteOrder | |
toWrite []byte | |
expectedBytes []byte | |
expWriteError error | |
expReadError error | |
}{ | |
{ | |
name: "simple write and read", | |
size: 100, | |
order: binary.BigEndian, | |
toWrite: []byte("this is a test"), | |
expectedBytes: []byte("this is a test"), | |
}, | |
{ | |
name: "buffer overflow", | |
size: 10, | |
order: binary.BigEndian, | |
toWrite: []byte("this string is too long for the buffer"), | |
expectedBytes: nil, | |
expWriteError: ErrBufferOverflow, | |
}, | |
{ | |
name: "empty write and read", | |
size: 100, | |
order: binary.BigEndian, | |
toWrite: []byte(""), | |
expectedBytes: []byte(""), | |
expReadError: io.EOF, | |
}, | |
{ | |
name: "write and read with exact buffer size", | |
size: 17, | |
order: binary.BigEndian, | |
toWrite: []byte("exact buffer size"), | |
expectedBytes: []byte("exact buffer size"), | |
}, | |
} | |
for _, tc := range testCases { | |
tc := tc | |
t.Run(tc.name, func(t *testing.T) { | |
t.Parallel() | |
a := assert.New(t) | |
bb := NewFixedBuffer(tc.size, tc.order) | |
n, err := bb.Write(tc.toWrite) | |
if tc.expWriteError != nil { | |
a.Equal(tc.expWriteError, err) | |
a.Equal(0, n) | |
} else { | |
require.NoError(t, err) | |
toRead := make([]byte, n) | |
m, err := bb.Read(toRead) | |
assert.Equal(t, tc.expReadError, err) | |
a.Equal(n, m) | |
a.Equal(tc.expectedBytes, toRead) | |
} | |
}) | |
} | |
} | |
func TestReader(t *testing.T) { | |
toWrite := "123456789" | |
bb := NewFixedBuffer(10, binary.BigEndian) | |
err := bb.WriteData(toWrite) | |
require.NoError(t, err) | |
buf := new(bytes.Buffer) | |
n, err := io.Copy(buf, bb) | |
require.NoError(t, err) | |
assert.Equal(t, len(toWrite), int(n)) | |
assert.Equal(t, buf.Bytes(), []byte(toWrite)) | |
} | |
func TestWriter(t *testing.T) { | |
toWrite := "123456789" | |
buf := new(bytes.Buffer) | |
n, err := buf.WriteString(toWrite) | |
require.NoError(t, err) | |
bb := NewFixedBuffer(10, binary.BigEndian) | |
require.NoError(t, err) | |
nCopy, err := io.Copy(bb, buf) | |
require.NoError(t, err) | |
assert.Equal(t, int(nCopy), n) | |
assert.Equal(t, []byte(toWrite), bb.Bytes()) | |
} | |
func BenchmarkAlloc(b *testing.B) { | |
bb := NewFixedBuffer(10, binary.BigEndian) | |
for i := 0; i < b.N; i++ { | |
bb.WriteData(uint8(1)) | |
bb.WriteData(uint16(1)) | |
bb.WriteData(uint32(1)) | |
bb.WriteData(uint64(1)) | |
bb.Write([]byte("123456789")) | |
bb.Write([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9}) | |
b.ReportAllocs() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment