feat(levin): portable storage varint encode/decode

Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
Claude 2026-02-20 19:23:28 +00:00
parent 7089e0990c
commit abc88f5c7a
No known key found for this signature in database
GPG key ID: AF404715446AEB41
2 changed files with 233 additions and 0 deletions

93
node/levin/varint.go Normal file
View file

@ -0,0 +1,93 @@
// Copyright (c) 2024-2026 Lethean Contributors
// SPDX-License-Identifier: EUPL-1.2
package levin
import (
"encoding/binary"
"errors"
)
// Size-mark bits occupying the two lowest bits of the first byte.
const (
varintMask = 0x03
varintMark1 = 0x00 // 1 byte, max 63
varintMark2 = 0x01 // 2 bytes, max 16,383
varintMark4 = 0x02 // 4 bytes, max 1,073,741,823
varintMark8 = 0x03 // 8 bytes, max 4,611,686,018,427,387,903
varintMax1 = 63
varintMax2 = 16_383
varintMax4 = 1_073_741_823
varintMax8 = 4_611_686_018_427_387_903
)
// ErrVarintTruncated is returned when the buffer is too short.
var ErrVarintTruncated = errors.New("levin: truncated varint")
// ErrVarintOverflow is returned when the value is too large to encode.
var ErrVarintOverflow = errors.New("levin: varint overflow")
// PackVarint encodes v using the epee portable-storage varint scheme.
// The low two bits of the first byte indicate the total encoded width;
// the remaining bits carry the value in little-endian order.
func PackVarint(v uint64) []byte {
switch {
case v <= varintMax1:
return []byte{byte((v << 2) | varintMark1)}
case v <= varintMax2:
raw := uint16((v << 2) | varintMark2)
buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, raw)
return buf
case v <= varintMax4:
raw := uint32((v << 2) | varintMark4)
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, raw)
return buf
default:
raw := (v << 2) | varintMark8
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, raw)
return buf
}
}
// UnpackVarint decodes one epee portable-storage varint from buf.
// It returns the decoded value, the number of bytes consumed, and any error.
func UnpackVarint(buf []byte) (value uint64, bytesConsumed int, err error) {
if len(buf) == 0 {
return 0, 0, ErrVarintTruncated
}
mark := buf[0] & varintMask
switch mark {
case varintMark1:
value = uint64(buf[0]) >> 2
return value, 1, nil
case varintMark2:
if len(buf) < 2 {
return 0, 0, ErrVarintTruncated
}
raw := binary.LittleEndian.Uint16(buf[:2])
value = uint64(raw) >> 2
return value, 2, nil
case varintMark4:
if len(buf) < 4 {
return 0, 0, ErrVarintTruncated
}
raw := binary.LittleEndian.Uint32(buf[:4])
value = uint64(raw) >> 2
return value, 4, nil
case varintMark8:
if len(buf) < 8 {
return 0, 0, ErrVarintTruncated
}
raw := binary.LittleEndian.Uint64(buf[:8])
value = raw >> 2
return value, 8, nil
default:
// Unreachable — mark is masked to 2 bits.
return 0, 0, ErrVarintTruncated
}
}

140
node/levin/varint_test.go Normal file
View file

@ -0,0 +1,140 @@
// Copyright (c) 2024-2026 Lethean Contributors
// SPDX-License-Identifier: EUPL-1.2
package levin
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPackVarint_Value5(t *testing.T) {
// 5 << 2 | 0x00 = 20 = 0x14
got := PackVarint(5)
assert.Equal(t, []byte{0x14}, got)
}
func TestPackVarint_Value100(t *testing.T) {
// 100 << 2 | 0x01 = 401 = 0x0191 → LE [0x91, 0x01]
got := PackVarint(100)
assert.Equal(t, []byte{0x91, 0x01}, got)
}
func TestPackVarint_Value65536(t *testing.T) {
// 65536 << 2 | 0x02 = 262146 = 0x00040002 → LE [0x02, 0x00, 0x04, 0x00]
got := PackVarint(65536)
assert.Equal(t, []byte{0x02, 0x00, 0x04, 0x00}, got)
}
func TestPackVarint_Value2Billion(t *testing.T) {
got := PackVarint(2_000_000_000)
require.Len(t, got, 8)
// Low 2 bits must be 0x03 (8-byte mark).
assert.Equal(t, byte(0x03), got[0]&0x03)
}
func TestPackVarint_Zero(t *testing.T) {
got := PackVarint(0)
assert.Equal(t, []byte{0x00}, got)
}
func TestPackVarint_Boundaries(t *testing.T) {
tests := []struct {
name string
value uint64
wantLen int
}{
{"1-byte max (63)", 63, 1},
{"2-byte min (64)", 64, 2},
{"2-byte max (16383)", 16_383, 2},
{"4-byte min (16384)", 16_384, 4},
{"4-byte max (1073741823)", 1_073_741_823, 4},
{"8-byte min (1073741824)", 1_073_741_824, 8},
{"8-byte max", 4_611_686_018_427_387_903, 8},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := PackVarint(tc.value)
assert.Len(t, got, tc.wantLen, "wrong length for value %d", tc.value)
})
}
}
func TestVarint_RoundTrip(t *testing.T) {
values := []uint64{
0, 1, 63, 64, 100, 16_383, 16_384,
1_073_741_823, 1_073_741_824,
4_611_686_018_427_387_903,
}
for _, v := range values {
buf := PackVarint(v)
decoded, consumed, err := UnpackVarint(buf)
require.NoError(t, err, "value %d", v)
assert.Equal(t, v, decoded, "mismatch for value %d", v)
assert.Equal(t, len(buf), consumed, "wrong bytes consumed for value %d", v)
}
}
func TestUnpackVarint_EmptyInput(t *testing.T) {
_, _, err := UnpackVarint([]byte{})
require.Error(t, err)
assert.ErrorIs(t, err, ErrVarintTruncated)
}
func TestUnpackVarint_Truncated2Byte(t *testing.T) {
// Encode 64 (needs 2 bytes), then only pass 1 byte.
buf := PackVarint(64)
require.Len(t, buf, 2)
_, _, err := UnpackVarint(buf[:1])
require.Error(t, err)
assert.ErrorIs(t, err, ErrVarintTruncated)
}
func TestUnpackVarint_Truncated4Byte(t *testing.T) {
buf := PackVarint(16_384)
require.Len(t, buf, 4)
_, _, err := UnpackVarint(buf[:2])
require.Error(t, err)
assert.ErrorIs(t, err, ErrVarintTruncated)
}
func TestUnpackVarint_Truncated8Byte(t *testing.T) {
buf := PackVarint(1_073_741_824)
require.Len(t, buf, 8)
_, _, err := UnpackVarint(buf[:4])
require.Error(t, err)
assert.ErrorIs(t, err, ErrVarintTruncated)
}
func TestUnpackVarint_ExtraBytes(t *testing.T) {
// Ensure that extra trailing bytes are not consumed.
buf := append(PackVarint(42), 0xFF, 0xFF)
decoded, consumed, err := UnpackVarint(buf)
require.NoError(t, err)
assert.Equal(t, uint64(42), decoded)
assert.Equal(t, 1, consumed)
}
func TestPackVarint_SizeMarkBits(t *testing.T) {
tests := []struct {
name string
value uint64
wantMark byte
}{
{"1-byte", 0, 0x00},
{"2-byte", 64, 0x01},
{"4-byte", 16_384, 0x02},
{"8-byte", 1_073_741_824, 0x03},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := PackVarint(tc.value)
assert.Equal(t, tc.wantMark, got[0]&0x03)
})
}
}