go-p2p/node/levin/connection.go
Virgil 723f71143e refactor(node): align AX naming across transport and protocol helpers
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-31 13:56:56 +00:00

149 lines
4 KiB
Go

// Copyright (c) 2024-2026 Lethean Contributors
// SPDX-License-Identifier: EUPL-1.2
package levin
import (
"io"
"net"
"sync"
"time"
)
// flags := FlagRequest | FlagResponse
const (
FlagRequest uint32 = 0x00000001
FlagResponse uint32 = 0x00000002
)
// header.ProtocolVersion = LevinProtocolVersion
const LevinProtocolVersion uint32 = 1
// connection.ReadTimeout = DefaultReadTimeout
const (
DefaultReadTimeout = 120 * time.Second
DefaultWriteTimeout = 30 * time.Second
)
// connection := NewConnection(networkConnection)
type Connection struct {
// MaxPayloadSize is the upper bound accepted for incoming payloads.
// Defaults to the package-level MaxPayloadSize (100 MB).
MaxPayloadSize uint64
// ReadTimeout is the deadline applied before each ReadPacket call.
ReadTimeout time.Duration
// WriteTimeout is the deadline applied before each write call.
WriteTimeout time.Duration
networkConnection net.Conn
writeMutex sync.Mutex
}
// connection := NewConnection(networkConnection)
func NewConnection(connection net.Conn) *Connection {
return &Connection{
MaxPayloadSize: MaxPayloadSize,
ReadTimeout: DefaultReadTimeout,
WriteTimeout: DefaultWriteTimeout,
networkConnection: connection,
}
}
// err := connection.WritePacket(CommandPing, payload, true)
func (connection *Connection) WritePacket(commandID uint32, payload []byte, expectResponse bool) error {
header := Header{
Signature: Signature,
PayloadSize: uint64(len(payload)),
ExpectResponse: expectResponse,
Command: commandID,
ReturnCode: ReturnOK,
Flags: FlagRequest,
ProtocolVersion: LevinProtocolVersion,
}
return connection.writeFrame(&header, payload)
}
// err := connection.WriteResponse(CommandPing, payload, ReturnOK)
func (connection *Connection) WriteResponse(commandID uint32, payload []byte, returnCode int32) error {
header := Header{
Signature: Signature,
PayloadSize: uint64(len(payload)),
ExpectResponse: false,
Command: commandID,
ReturnCode: returnCode,
Flags: FlagResponse,
ProtocolVersion: LevinProtocolVersion,
}
return connection.writeFrame(&header, payload)
}
// writeFrame serialises header + payload and writes them atomically.
func (connection *Connection) writeFrame(header *Header, payload []byte) error {
headerBytes := EncodeHeader(header)
connection.writeMutex.Lock()
defer connection.writeMutex.Unlock()
if err := connection.networkConnection.SetWriteDeadline(time.Now().Add(connection.WriteTimeout)); err != nil {
return err
}
if _, err := connection.networkConnection.Write(headerBytes[:]); err != nil {
return err
}
if len(payload) > 0 {
if _, err := connection.networkConnection.Write(payload); err != nil {
return err
}
}
return nil
}
// header, payload, err := connection.ReadPacket()
func (connection *Connection) ReadPacket() (Header, []byte, error) {
if err := connection.networkConnection.SetReadDeadline(time.Now().Add(connection.ReadTimeout)); err != nil {
return Header{}, nil, err
}
// Read header.
var headerBytes [HeaderSize]byte
if _, err := io.ReadFull(connection.networkConnection, headerBytes[:]); err != nil {
return Header{}, nil, err
}
header, err := DecodeHeader(headerBytes)
if err != nil {
return Header{}, nil, err
}
// Check against the connection-specific payload limit.
if header.PayloadSize > connection.MaxPayloadSize {
return Header{}, nil, ErrorPayloadTooBig
}
// Empty payload is valid — return nil data without allocation.
if header.PayloadSize == 0 {
return header, nil, nil
}
payload := make([]byte, header.PayloadSize)
if _, err := io.ReadFull(connection.networkConnection, payload); err != nil {
return Header{}, nil, err
}
return header, payload, nil
}
// err := connection.Close()
func (connection *Connection) Close() error {
return connection.networkConnection.Close()
}
// addr := connection.RemoteAddr()
func (connection *Connection) RemoteAddr() string {
return connection.networkConnection.RemoteAddr().String()
}