go-p2p/node/levin/varint.go
Virgil c678d20608 refactor(repo): prefer AX error names
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-30 19:44:05 +00:00

104 lines
2.8 KiB
Go

// Copyright (c) 2024-2026 Lethean Contributors
// SPDX-License-Identifier: EUPL-1.2
package levin
import (
"encoding/binary"
core "dappco.re/go/core"
)
// 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
)
// ErrorVarintTruncated is returned when the buffer is too short.
var ErrorVarintTruncated = core.E("levin", "truncated varint", nil)
// Deprecated: use ErrorVarintTruncated.
var ErrVarintTruncated = ErrorVarintTruncated
// ErrorVarintOverflow is returned when the value is too large to encode.
var ErrorVarintOverflow = core.E("levin", "varint overflow", nil)
// Deprecated: use ErrorVarintOverflow.
var ErrVarintOverflow = ErrorVarintOverflow
// 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.
//
// encoded := PackVarint(42)
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.
//
// value, err := UnpackVarint(data)
func UnpackVarint(buf []byte) (value uint64, bytesConsumed int, err error) {
if len(buf) == 0 {
return 0, 0, ErrorVarintTruncated
}
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, ErrorVarintTruncated
}
raw := binary.LittleEndian.Uint16(buf[:2])
value = uint64(raw) >> 2
return value, 2, nil
case varintMark4:
if len(buf) < 4 {
return 0, 0, ErrorVarintTruncated
}
raw := binary.LittleEndian.Uint32(buf[:4])
value = uint64(raw) >> 2
return value, 4, nil
case varintMark8:
if len(buf) < 8 {
return 0, 0, ErrorVarintTruncated
}
raw := binary.LittleEndian.Uint64(buf[:8])
value = raw >> 2
return value, 8, nil
default:
// Unreachable — mark is masked to 2 bits.
return 0, 0, ErrorVarintTruncated
}
}