refactor
This commit is contained in:
81
varn/buffer.go
Normal file
81
varn/buffer.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package varn
|
||||
|
||||
import "io"
|
||||
|
||||
// Buffer is a simple buffer reader that allows reading bytes and
|
||||
// variable-length integers. It is not thread-safe and should not be used
|
||||
// concurrently.
|
||||
type Buffer struct {
|
||||
pos *int
|
||||
buf []byte
|
||||
}
|
||||
|
||||
// NewBuffer creates a new Buffer instance.
|
||||
func NewBuffer(buf []byte) Buffer {
|
||||
zero := 0
|
||||
return Buffer{&zero, buf}
|
||||
}
|
||||
|
||||
// ReadBytes reads n bytes from the buffer.
|
||||
func (buf Buffer) ReadBytes(n int) ([]byte, error) {
|
||||
if *buf.pos >= len(buf.buf) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
if *buf.pos+n > len(buf.buf) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
res := buf.buf[*buf.pos : *buf.pos+n]
|
||||
*buf.pos = *buf.pos + n
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (buf Buffer) ReadByte() (byte, error) {
|
||||
b, err := buf.ReadBytes(1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return b[0], nil
|
||||
}
|
||||
|
||||
// ReadVarUint reads a variable-length unsigned integer from the buffer.
|
||||
// Returns io.EOF if the end of the buffer is reached before a complete integer
|
||||
// is read.
|
||||
func (buf Buffer) ReadVarUint() (uint64, error) {
|
||||
var value uint64 = 0
|
||||
var shift uint64 = 0
|
||||
|
||||
for {
|
||||
b, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
value |= (uint64(b) & 0b01111111) << shift
|
||||
shift += 7
|
||||
if b&0b10000000 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ReadVarBytes reads a variable-length byte array from the buffer.
|
||||
// It first reads a variable-length unsigned integer to determine the length
|
||||
// of the byte array, and then reads that many bytes from the buffer.
|
||||
// Returns the byte array and an error if any occurs during reading.
|
||||
// The function will return io.EOF if the end of the buffer is reached before
|
||||
// the specified number of bytes is read.
|
||||
// If the length of the byte array is 0, it will return an empty byte slice.
|
||||
// If the length is greater than the remaining bytes in the buffer, it will
|
||||
// return io.EOF.
|
||||
func (buf Buffer) ReadVarBytes() ([]byte, error) {
|
||||
v, err := buf.ReadVarUint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := buf.ReadBytes(int(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
155
varn/buffer_test.go
Normal file
155
varn/buffer_test.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package varn_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"git.intruders.space/public/opentimestamps/varn"
|
||||
)
|
||||
|
||||
func TestNewBuffer(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03}
|
||||
buf := varn.NewBuffer(data)
|
||||
|
||||
// Test the first byte to verify initialization
|
||||
b, err := buf.ReadByte()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, byte(0x01), b)
|
||||
|
||||
// Test reading the second byte
|
||||
b, err = buf.ReadByte()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, byte(0x02), b)
|
||||
|
||||
// Test reading the third byte
|
||||
b, err = buf.ReadByte()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, byte(0x03), b)
|
||||
|
||||
// Test reading beyond the buffer
|
||||
_, err = buf.ReadByte()
|
||||
assert.ErrorIs(t, err, io.EOF)
|
||||
|
||||
// Test reading from an empty buffer
|
||||
emptyBuf := varn.NewBuffer([]byte{})
|
||||
_, err = emptyBuf.ReadByte()
|
||||
assert.ErrorIs(t, err, io.EOF)
|
||||
|
||||
// Test reading from a nil buffer
|
||||
nilBuf := varn.NewBuffer(nil)
|
||||
_, err = nilBuf.ReadByte()
|
||||
assert.ErrorIs(t, err, io.EOF)
|
||||
|
||||
// Test reading from a buffer with a single byte
|
||||
singleByteBuf := varn.NewBuffer([]byte{0xFF})
|
||||
b, err = singleByteBuf.ReadByte()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, byte(0xFF), b)
|
||||
}
|
||||
|
||||
func TestReadBytes(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
|
||||
buf := varn.NewBuffer(data)
|
||||
|
||||
// Read 3 bytes
|
||||
bytes, err := buf.ReadBytes(3)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte{0x01, 0x02, 0x03}, bytes)
|
||||
|
||||
// Read 2 more bytes
|
||||
bytes, err = buf.ReadBytes(2)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte{0x04, 0x05}, bytes)
|
||||
|
||||
// Try to read beyond the buffer
|
||||
_, err = buf.ReadBytes(1)
|
||||
assert.ErrorIs(t, err, io.EOF)
|
||||
}
|
||||
|
||||
func TestReadByte(t *testing.T) {
|
||||
data := []byte{0x0A, 0x0B}
|
||||
buf := varn.NewBuffer(data)
|
||||
|
||||
// Read first byte
|
||||
b, err := buf.ReadByte()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, byte(0x0A), b)
|
||||
|
||||
// Read second byte
|
||||
b, err = buf.ReadByte()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, byte(0x0B), b)
|
||||
|
||||
// Try to read beyond the buffer
|
||||
_, err = buf.ReadByte()
|
||||
assert.ErrorIs(t, err, io.EOF)
|
||||
}
|
||||
|
||||
func TestReadVarUint(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expected uint64
|
||||
}{
|
||||
{
|
||||
name: "Single byte",
|
||||
input: []byte{0x01},
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "Two bytes",
|
||||
input: []byte{0x81, 0x01}, // 129 in varint encoding
|
||||
expected: 129,
|
||||
},
|
||||
{
|
||||
name: "Large number",
|
||||
input: []byte{0xFF, 0xFF, 0xFF, 0xFF, 0x01}, // 536870911 in varint encoding
|
||||
expected: 536870911,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
buf := varn.NewBuffer(tc.input)
|
||||
val, err := buf.ReadVarUint()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expected, val)
|
||||
})
|
||||
}
|
||||
|
||||
// Test EOF
|
||||
buf := varn.NewBuffer([]byte{0x81}) // Incomplete varint
|
||||
_, err := buf.ReadVarUint()
|
||||
assert.ErrorIs(t, err, io.EOF)
|
||||
}
|
||||
|
||||
func TestReadVarBytes(t *testing.T) {
|
||||
// Test normal case: length followed by data
|
||||
data := []byte{0x03, 0x0A, 0x0B, 0x0C, 0x0D}
|
||||
buf := varn.NewBuffer(data)
|
||||
|
||||
bytes, err := buf.ReadVarBytes()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte{0x0A, 0x0B, 0x0C}, bytes)
|
||||
|
||||
// Test empty array
|
||||
data = []byte{0x00, 0x01}
|
||||
buf = varn.NewBuffer(data)
|
||||
|
||||
bytes, err = buf.ReadVarBytes()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte{}, bytes)
|
||||
|
||||
// Test EOF during length read
|
||||
buf = varn.NewBuffer([]byte{})
|
||||
_, err = buf.ReadVarBytes()
|
||||
assert.ErrorIs(t, err, io.EOF)
|
||||
|
||||
// Test error during data read (insufficient bytes)
|
||||
data = []byte{0x03, 0x01}
|
||||
buf = varn.NewBuffer(data)
|
||||
_, err = buf.ReadVarBytes()
|
||||
assert.ErrorIs(t, err, io.ErrUnexpectedEOF)
|
||||
}
|
||||
38
varn/varn.go
Normal file
38
varn/varn.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Package varn implements variable-length encoding for unsigned integers and
|
||||
// byte slices. It is used in the OpenTimestamps protocol to encode
|
||||
// instructions and attestations. The encoding is similar to the one used in
|
||||
// Protocol Buffers, but with a different format for the variable-length
|
||||
// integers. The encoding is designed to be compact and efficient, while still
|
||||
// being easy to decode. The package provides functions to read and write
|
||||
// variable-length integers and byte slices, as well as a Buffer type for
|
||||
// reading and writing data in a more convenient way. The package is not
|
||||
// thread-safe and should not be used concurrently. It is intended for use in
|
||||
// the OpenTimestamps protocol and is not a general-purpose encoding library.
|
||||
package varn
|
||||
|
||||
// AppendVarUint appends a variable-length unsigned integer to the buffer
|
||||
func AppendVarUint(buf []byte, value uint64) []byte {
|
||||
if value == 0 {
|
||||
buf = append(buf, 0)
|
||||
} else {
|
||||
for value != 0 {
|
||||
b := byte(value & 0b01111111)
|
||||
if value > 0b01111111 {
|
||||
b |= 0b10000000
|
||||
}
|
||||
buf = append(buf, b)
|
||||
if value <= 0b01111111 {
|
||||
break
|
||||
}
|
||||
value >>= 7
|
||||
}
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func AppendVarBytes(buf []byte, value []byte) []byte {
|
||||
buf = AppendVarUint(buf, uint64(len(value)))
|
||||
buf = append(buf, value...)
|
||||
return buf
|
||||
}
|
||||
118
varn/varn_test.go
Normal file
118
varn/varn_test.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package varn_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"git.intruders.space/public/opentimestamps/varn"
|
||||
)
|
||||
|
||||
// Helper functions for decoding (to verify encoding)
|
||||
func readVarUint(t *testing.T, data []byte) (uint64, int) {
|
||||
t.Helper()
|
||||
require.NotZero(t, data, "empty buffer")
|
||||
|
||||
if data[0] == 0 {
|
||||
return 0, 1
|
||||
}
|
||||
|
||||
var value uint64
|
||||
var bytesRead int
|
||||
|
||||
for i, b := range data {
|
||||
bytesRead = i + 1
|
||||
value |= uint64(b&0x7F) << uint(7*i)
|
||||
if b&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
require.Less(t, i, 9, "varint too long") // 9 is max bytes needed for uint64
|
||||
}
|
||||
|
||||
return value, bytesRead
|
||||
}
|
||||
|
||||
func readVarBytes(t *testing.T, data []byte) ([]byte, int) {
|
||||
t.Helper()
|
||||
length, bytesRead := readVarUint(t, data)
|
||||
|
||||
require.GreaterOrEqual(t, uint64(len(data)-bytesRead), length, "buffer too short")
|
||||
|
||||
end := bytesRead + int(length)
|
||||
return data[bytesRead:end], end
|
||||
}
|
||||
|
||||
func TestAppendVarUint(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value uint64
|
||||
expected string // hex representation of expected bytes
|
||||
}{
|
||||
{"zero", 0, "00"},
|
||||
{"small value", 42, "2a"},
|
||||
{"value below 128", 127, "7f"},
|
||||
{"value requiring 2 bytes", 128, "8001"},
|
||||
{"medium value", 300, "ac02"},
|
||||
{"large value", 1234567890, "d285d8cc04"}, // Updated from "d2a6e58e07" to correct encoding
|
||||
{"max uint64", 18446744073709551615, "ffffffffffffffffff01"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var buf []byte
|
||||
result := varn.AppendVarUint(buf, tt.value)
|
||||
|
||||
// Check encoding
|
||||
expected, err := hex.DecodeString(tt.expected)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// Verify by decoding
|
||||
decoded, _ := readVarUint(t, result)
|
||||
assert.Equal(t, tt.value, decoded)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendVarBytes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value []byte
|
||||
}{
|
||||
{"empty", []byte{}},
|
||||
{"small", []byte{0x01, 0x02, 0x03}},
|
||||
{"medium", bytes.Repeat([]byte{0xAA}, 127)},
|
||||
{"large", bytes.Repeat([]byte{0xBB}, 256)},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var buf []byte
|
||||
result := varn.AppendVarBytes(buf, tt.value)
|
||||
|
||||
// Verify by decoding
|
||||
decoded, _ := readVarBytes(t, result)
|
||||
assert.Equal(t, tt.value, decoded)
|
||||
|
||||
// Check that length is properly encoded
|
||||
lenEncoded, _ := readVarUint(t, result)
|
||||
assert.Equal(t, uint64(len(tt.value)), lenEncoded)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendingToExistingBuffer(t *testing.T) {
|
||||
existing := []byte{0xFF, 0xFF}
|
||||
|
||||
// Test AppendVarUint
|
||||
result := varn.AppendVarUint(existing, 42)
|
||||
assert.Equal(t, []byte{0xFF, 0xFF, 0x2A}, result)
|
||||
|
||||
// Test AppendVarBytes
|
||||
data := []byte{0x01, 0x02, 0x03}
|
||||
result = varn.AppendVarBytes(existing, data)
|
||||
assert.Equal(t, []byte{0xFF, 0xFF, 0x03, 0x01, 0x02, 0x03}, result)
|
||||
}
|
||||
Reference in New Issue
Block a user