ce6332cc39
Signed-off-by: Ciro S. Costa <utxobr@protonmail.com>
308 lines
7.8 KiB
Go
308 lines
7.8 KiB
Go
//
|
|
// see https://github.com/monero-project/monero/blob/e45619e61e4831eea70a43fe6985f4d57ea02e9e/contrib/epee/include/net/levin_base.h
|
|
// see https://github.com/monero-project/monero/blob/e45619e61e4831eea70a43fe6985f4d57ea02e9e/docs/LEVIN_PROTOCOL.md
|
|
|
|
package levin
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
)
|
|
|
|
const (
|
|
LevinSignature uint64 = 0x0101010101012101 // Dander's Nightmare
|
|
|
|
LevinProtocolVersion uint32 = 1
|
|
|
|
LevinPacketRequest uint32 = 0x00000001 // Q flag
|
|
LevinPacketReponse uint32 = 0x00000002 // S flag
|
|
LevinPacketMaxDefaultSize uint64 = 100000000 // 100MB _after_ handshake
|
|
LevinPacketMaxInitialSize uint64 = 256 * 1024 // 256KiB _before_ handshake
|
|
|
|
LevinHeaderSizeBytes = 33
|
|
)
|
|
|
|
const (
|
|
// Return Codes.
|
|
LevinOk int32 = 0
|
|
LevinErrorConnection int32 = -1
|
|
LevinErrorConnectionNotFound int32 = -2
|
|
LevinErrorConnectionDestroyed int32 = -3
|
|
LevinErrorConnectionTimedout int32 = -4
|
|
LevinErrorConnectionNoDuplexProtocol int32 = -5
|
|
LevinErrorConnectionHandlerNotDefined int32 = -6
|
|
LevinErrorFormat int32 = -7
|
|
)
|
|
|
|
func IsValidReturnCode(c int32) bool {
|
|
// anything >= 0 is good (there are some `1`s in the code :shrug:)
|
|
return c >= LevinErrorFormat
|
|
}
|
|
|
|
const (
|
|
// p2p admin commands.
|
|
CommandHandshake uint32 = 1001
|
|
CommandTimedSync uint32 = 1002
|
|
CommandPing uint32 = 1003
|
|
CommandStat uint32 = 1004
|
|
CommandNetworkState uint32 = 1005
|
|
CommandPeerID uint32 = 1006
|
|
CommandSupportFlags uint32 = 1007
|
|
)
|
|
|
|
var (
|
|
MainnetNetworkId = []byte{
|
|
0x12, 0x30, 0xf1, 0x71,
|
|
0x61, 0x04, 0x41, 0x61,
|
|
0x17, 0x31, 0x00, 0x82,
|
|
0x16, 0xa1, 0xa1, 0x10,
|
|
}
|
|
|
|
MainnetGenesisTx = "418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3"
|
|
)
|
|
|
|
func IsValidCommand(c uint32) bool {
|
|
return (c >= CommandHandshake && c <= CommandSupportFlags)
|
|
}
|
|
|
|
//
|
|
// Header
|
|
//
|
|
//
|
|
// 0 1 2 3
|
|
// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | 0x01 | 0x21 | 0x01 | 0x01 |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | 0x01 | 0x01 | 0x01 | 0x01 |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | Length |
|
|
// | |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | E. Response | _ Command _
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | _ Return Code _
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// |Q|S|B|E| _ Reserved_
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | 0x01 | 0x00 | 0x00 |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | 0x00 |
|
|
// +-+-+-+-+-+-+-+-+
|
|
//
|
|
//
|
|
// i.e.,
|
|
//
|
|
// BYTE(0X01) BYTE(0X21) BYTE(0X01) BYTE(0X01) ---.
|
|
// +--> protocol identification
|
|
// BYTE(0X01) BYTE(0X01) BYTE(0X01) BYTE(0X01) ---'
|
|
//
|
|
//
|
|
// UINT64(LENGTH) -----------------------------------> unsigned little-endian 64bit integer
|
|
// length of the payload _not including_
|
|
// the header. messages >100MB are rejected.
|
|
//
|
|
//
|
|
// BYTE(E.RESPONSE) 4BYTE(COMMAND) 4BYTE(RET CODE)
|
|
// | | |
|
|
// | | |
|
|
// | | '-> signed 32-bit little endian integer representing the response
|
|
// | | from the peer from the last command invoked. `0` for request msgs.
|
|
// | |
|
|
// | '-> unsigned 32-bit little endian integer
|
|
// | representing the monero specific cmd
|
|
// |
|
|
// '-> zero-byte if no response is expected from the peer, non-zero if response is expected.
|
|
// peers must respond to requests w/ this flag in the same order as received.
|
|
//
|
|
//
|
|
// BIT(Q) BIT(S) BIT(B) BIT(E) 3BYTE+4BIT(RESERVED)
|
|
// | | | |
|
|
// | | | |
|
|
// | | | '-> set if this is the end of a frag msg
|
|
// | | |
|
|
// | | '-> set if this is the beginning of a frag msg
|
|
// | |
|
|
// | '-> set if the message is a response
|
|
// |
|
|
// '-> set if the message is a request
|
|
//
|
|
//
|
|
//
|
|
// BYTE(0X01) BYTE(0X00) BYTE(0X00) BYTE(0X00)
|
|
// |
|
|
// '--> version
|
|
//
|
|
type Header struct {
|
|
Signature uint64
|
|
Length uint64
|
|
ExpectsResponse bool
|
|
Command uint32
|
|
ReturnCode int32
|
|
Flags uint32 // only 4 most significant bits matter (Q|S|B|E)
|
|
Version uint32
|
|
}
|
|
|
|
func NewRequestHeader(command uint32, length uint64) *Header {
|
|
return &Header{
|
|
Signature: LevinSignature,
|
|
Length: length,
|
|
ExpectsResponse: true,
|
|
Command: command,
|
|
ReturnCode: 0,
|
|
Flags: LevinPacketRequest,
|
|
Version: LevinProtocolVersion,
|
|
}
|
|
}
|
|
|
|
func NewHeaderFromBytesBytes(bytes []byte) (*Header, error) {
|
|
if len(bytes) != LevinHeaderSizeBytes {
|
|
return nil, fmt.Errorf("invalid header size: expected %d, has %d",
|
|
LevinHeaderSizeBytes, len(bytes),
|
|
)
|
|
}
|
|
|
|
var (
|
|
size = 0
|
|
idx = 0
|
|
)
|
|
|
|
header := &Header{}
|
|
|
|
{ // signature
|
|
size = 8
|
|
header.Signature = binary.LittleEndian.Uint64(bytes[idx : idx+size])
|
|
idx += size
|
|
|
|
if header.Signature != LevinSignature {
|
|
return nil, fmt.Errorf("signature mismatch: expected %x, got %x",
|
|
LevinSignature, header.Signature,
|
|
)
|
|
}
|
|
}
|
|
|
|
{ // length
|
|
size = 8
|
|
header.Length = binary.LittleEndian.Uint64(bytes[idx : idx+size])
|
|
idx += size
|
|
}
|
|
|
|
{ // expects response
|
|
size = 1
|
|
header.ExpectsResponse = (bytes[idx] != 0)
|
|
idx += size
|
|
}
|
|
|
|
{ // command
|
|
size = 4
|
|
header.Command = binary.LittleEndian.Uint32(bytes[idx : idx+size])
|
|
idx += size
|
|
|
|
if !IsValidCommand(header.Command) {
|
|
return nil, fmt.Errorf("invalid command %d", header.Command)
|
|
}
|
|
}
|
|
|
|
{ // return code
|
|
size = 4
|
|
header.ReturnCode = int32(binary.LittleEndian.Uint32(bytes[idx : idx+size]))
|
|
idx += size
|
|
|
|
if !IsValidReturnCode(header.ReturnCode) {
|
|
return nil, fmt.Errorf("invalid return code %d", header.ReturnCode)
|
|
}
|
|
}
|
|
|
|
{ // flags
|
|
size = 4
|
|
header.Flags = binary.LittleEndian.Uint32(bytes[idx : idx+size])
|
|
idx += size
|
|
}
|
|
|
|
{ // version
|
|
size = 4
|
|
header.Version = binary.LittleEndian.Uint32(bytes[idx : idx+size])
|
|
idx += size
|
|
|
|
if header.Version != LevinProtocolVersion {
|
|
return nil, fmt.Errorf("invalid version %x",
|
|
header.Version)
|
|
}
|
|
}
|
|
|
|
return header, nil
|
|
}
|
|
|
|
func (h *Header) Bytes() []byte {
|
|
var (
|
|
header = make([]byte, LevinHeaderSizeBytes) // full header
|
|
b = make([]byte, 8) // biggest type
|
|
|
|
idx = 0
|
|
size = 0
|
|
)
|
|
|
|
{ // signature
|
|
size = 8
|
|
|
|
binary.LittleEndian.PutUint64(b, h.Signature)
|
|
copy(header[idx:], b[:size])
|
|
idx += size
|
|
}
|
|
|
|
{ // length
|
|
size = 8
|
|
|
|
binary.LittleEndian.PutUint64(b, h.Length)
|
|
copy(header[idx:], b[:size])
|
|
idx += size
|
|
}
|
|
|
|
{ // expects response
|
|
size = 1
|
|
|
|
if h.ExpectsResponse {
|
|
b[0] = 0x01
|
|
} else {
|
|
b[0] = 0x00
|
|
}
|
|
|
|
copy(header[idx:], b[:size])
|
|
idx += size
|
|
}
|
|
|
|
{ // command
|
|
size = 4
|
|
|
|
binary.LittleEndian.PutUint32(b, h.Command)
|
|
copy(header[idx:], b[:size])
|
|
idx += size
|
|
}
|
|
|
|
{ // return code
|
|
size = 4
|
|
|
|
binary.LittleEndian.PutUint32(b, uint32(h.ReturnCode))
|
|
copy(header[idx:], b[:size])
|
|
idx += size
|
|
}
|
|
|
|
{ // flags
|
|
size = 4
|
|
|
|
binary.LittleEndian.PutUint32(b, h.Flags)
|
|
copy(header[idx:], b[:size])
|
|
idx += size
|
|
}
|
|
|
|
{ // version
|
|
size = 4
|
|
|
|
binary.LittleEndian.PutUint32(b, h.Version)
|
|
copy(header[idx:], b[:size])
|
|
idx += size
|
|
}
|
|
|
|
return header
|
|
}
|