149 lines
4 KiB
Go
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()
|
|
}
|