feat(levin): header encode/decode (33-byte Levin packet framing)

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

95
node/levin/header.go Normal file
View file

@ -0,0 +1,95 @@
// Copyright (c) 2024-2026 Lethean Contributors
// SPDX-License-Identifier: EUPL-1.2
// Package levin implements the CryptoNote Levin binary protocol.
// It is a standalone package with no imports from the parent node package.
package levin
import (
"encoding/binary"
"errors"
)
// HeaderSize is the exact byte length of a serialised Levin header.
const HeaderSize = 33
// Signature is the magic value that opens every Levin packet.
const Signature uint64 = 0x0101010101012101
// MaxPayloadSize is the upper bound we accept for a single payload (100 MB).
const MaxPayloadSize uint64 = 100 * 1024 * 1024
// Return-code constants carried in every Levin response.
const (
ReturnOK int32 = 0
ReturnErrConnection int32 = -1
ReturnErrFormat int32 = -7
ReturnErrSignature int32 = -13
)
// Command IDs for the CryptoNote P2P layer.
const (
CommandHandshake uint32 = 1001
CommandTimedSync uint32 = 1002
CommandPing uint32 = 1003
CommandNewBlock uint32 = 2001
CommandNewTransactions uint32 = 2002
CommandRequestObjects uint32 = 2003
CommandResponseObjects uint32 = 2004
CommandRequestChain uint32 = 2006
CommandResponseChain uint32 = 2007
)
// Sentinel errors returned by DecodeHeader.
var (
ErrBadSignature = errors.New("levin: bad signature")
ErrPayloadTooBig = errors.New("levin: payload exceeds maximum size")
)
// Header is the 33-byte packed header that prefixes every Levin message.
type Header struct {
Signature uint64
PayloadSize uint64
ExpectResponse bool
Command uint32
ReturnCode int32
Flags uint32
ProtocolVersion uint32
}
// EncodeHeader serialises h into a fixed-size 33-byte array (little-endian).
func EncodeHeader(h *Header) [HeaderSize]byte {
var buf [HeaderSize]byte
binary.LittleEndian.PutUint64(buf[0:8], h.Signature)
binary.LittleEndian.PutUint64(buf[8:16], h.PayloadSize)
if h.ExpectResponse {
buf[16] = 0x01
} else {
buf[16] = 0x00
}
binary.LittleEndian.PutUint32(buf[17:21], h.Command)
binary.LittleEndian.PutUint32(buf[21:25], uint32(h.ReturnCode))
binary.LittleEndian.PutUint32(buf[25:29], h.Flags)
binary.LittleEndian.PutUint32(buf[29:33], h.ProtocolVersion)
return buf
}
// DecodeHeader deserialises a 33-byte array into a Header, validating
// the magic signature.
func DecodeHeader(buf [HeaderSize]byte) (Header, error) {
var h Header
h.Signature = binary.LittleEndian.Uint64(buf[0:8])
if h.Signature != Signature {
return Header{}, ErrBadSignature
}
h.PayloadSize = binary.LittleEndian.Uint64(buf[8:16])
if h.PayloadSize > MaxPayloadSize {
return Header{}, ErrPayloadTooBig
}
h.ExpectResponse = buf[16] == 0x01
h.Command = binary.LittleEndian.Uint32(buf[17:21])
h.ReturnCode = int32(binary.LittleEndian.Uint32(buf[21:25]))
h.Flags = binary.LittleEndian.Uint32(buf[25:29])
h.ProtocolVersion = binary.LittleEndian.Uint32(buf[29:33])
return h, nil
}

168
node/levin/header_test.go Normal file
View file

@ -0,0 +1,168 @@
// Copyright (c) 2024-2026 Lethean Contributors
// SPDX-License-Identifier: EUPL-1.2
package levin
import (
"encoding/binary"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHeaderSizeIs33(t *testing.T) {
assert.Equal(t, 33, HeaderSize)
}
func TestEncodeHeader_KnownValues(t *testing.T) {
h := &Header{
Signature: Signature,
PayloadSize: 256,
ExpectResponse: true,
Command: CommandHandshake,
ReturnCode: ReturnOK,
Flags: 0,
ProtocolVersion: 0,
}
buf := EncodeHeader(h)
// Verify signature at offset 0.
sig := binary.LittleEndian.Uint64(buf[0:8])
assert.Equal(t, Signature, sig)
// Verify payload size at offset 8.
ps := binary.LittleEndian.Uint64(buf[8:16])
assert.Equal(t, uint64(256), ps)
// Verify expect-response at offset 16.
assert.Equal(t, byte(0x01), buf[16])
// Verify command at offset 17.
cmd := binary.LittleEndian.Uint32(buf[17:21])
assert.Equal(t, CommandHandshake, cmd)
// Verify return code at offset 21.
rc := int32(binary.LittleEndian.Uint32(buf[21:25]))
assert.Equal(t, ReturnOK, rc)
// Verify flags at offset 25.
flags := binary.LittleEndian.Uint32(buf[25:29])
assert.Equal(t, uint32(0), flags)
// Verify protocol version at offset 29.
pv := binary.LittleEndian.Uint32(buf[29:33])
assert.Equal(t, uint32(0), pv)
}
func TestEncodeHeader_ExpectResponseFalse(t *testing.T) {
h := &Header{
Signature: Signature,
PayloadSize: 42,
ExpectResponse: false,
Command: CommandPing,
ReturnCode: ReturnOK,
}
buf := EncodeHeader(h)
assert.Equal(t, byte(0x00), buf[16])
}
func TestEncodeHeader_NegativeReturnCode(t *testing.T) {
h := &Header{
Signature: Signature,
PayloadSize: 0,
ExpectResponse: false,
Command: CommandHandshake,
ReturnCode: ReturnErrFormat,
}
buf := EncodeHeader(h)
rc := int32(binary.LittleEndian.Uint32(buf[21:25]))
assert.Equal(t, ReturnErrFormat, rc)
}
func TestDecodeHeader_RoundTrip(t *testing.T) {
original := &Header{
Signature: Signature,
PayloadSize: 1024,
ExpectResponse: true,
Command: CommandTimedSync,
ReturnCode: ReturnErrConnection,
Flags: 0,
ProtocolVersion: 0,
}
buf := EncodeHeader(original)
decoded, err := DecodeHeader(buf)
require.NoError(t, err)
assert.Equal(t, original.Signature, decoded.Signature)
assert.Equal(t, original.PayloadSize, decoded.PayloadSize)
assert.Equal(t, original.ExpectResponse, decoded.ExpectResponse)
assert.Equal(t, original.Command, decoded.Command)
assert.Equal(t, original.ReturnCode, decoded.ReturnCode)
assert.Equal(t, original.Flags, decoded.Flags)
assert.Equal(t, original.ProtocolVersion, decoded.ProtocolVersion)
}
func TestDecodeHeader_AllCommands(t *testing.T) {
commands := []uint32{
CommandHandshake,
CommandTimedSync,
CommandPing,
CommandNewBlock,
CommandNewTransactions,
CommandRequestObjects,
CommandResponseObjects,
CommandRequestChain,
CommandResponseChain,
}
for _, cmd := range commands {
h := &Header{
Signature: Signature,
Command: cmd,
ReturnCode: ReturnOK,
}
buf := EncodeHeader(h)
decoded, err := DecodeHeader(buf)
require.NoError(t, err)
assert.Equal(t, cmd, decoded.Command)
}
}
func TestDecodeHeader_BadSignature(t *testing.T) {
h := &Header{
Signature: 0xDEADBEEF,
PayloadSize: 0,
Command: CommandPing,
}
buf := EncodeHeader(h)
_, err := DecodeHeader(buf)
require.Error(t, err)
assert.ErrorIs(t, err, ErrBadSignature)
}
func TestDecodeHeader_PayloadTooBig(t *testing.T) {
h := &Header{
Signature: Signature,
PayloadSize: MaxPayloadSize + 1,
Command: CommandHandshake,
}
buf := EncodeHeader(h)
_, err := DecodeHeader(buf)
require.Error(t, err)
assert.ErrorIs(t, err, ErrPayloadTooBig)
}
func TestDecodeHeader_MaxPayloadExact(t *testing.T) {
h := &Header{
Signature: Signature,
PayloadSize: MaxPayloadSize,
Command: CommandHandshake,
}
buf := EncodeHeader(h)
decoded, err := DecodeHeader(buf)
require.NoError(t, err)
assert.Equal(t, MaxPayloadSize, decoded.PayloadSize)
}