145 lines
4.4 KiB
Go
145 lines
4.4 KiB
Go
// Package protocol defines RemLink wire-level constants and codecs.
|
|
package protocol
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
const (
|
|
// SessionHeaderSize is the fixed v1 Session header length in bytes.
|
|
SessionHeaderSize = 20
|
|
// SessionVersion is the only Session protocol version supported by v1.
|
|
SessionVersion uint8 = 1
|
|
// SessionTypeIPv4 marks a payload as one complete raw IPv4 packet.
|
|
SessionTypeIPv4 uint8 = 0x01
|
|
)
|
|
|
|
var (
|
|
sessionMagic = [4]byte{'R', 'M', 'L', 'K'}
|
|
|
|
ErrSessionHeaderTooShort = errors.New("session packet is shorter than the 20-byte header")
|
|
ErrSessionMagic = errors.New("invalid session magic")
|
|
ErrSessionVersion = errors.New("unsupported session version")
|
|
ErrSessionType = errors.New("unsupported session payload type")
|
|
ErrSessionFlags = errors.New("session flags must be zero in v1")
|
|
ErrSessionReserved = errors.New("session reserved field must be zero in v1")
|
|
ErrSessionPayloadLength = errors.New("session payload length mismatch")
|
|
ErrSessionPayloadTooLong = errors.New("session payload exceeds uint16 length")
|
|
)
|
|
|
|
// SessionHeader is the fixed 20-byte prefix of every Remote Subnet datagram.
|
|
// Multi-byte fields are encoded in network byte order (big-endian).
|
|
type SessionHeader struct {
|
|
Version uint8
|
|
Type uint8
|
|
Flags uint16
|
|
SessionID uint64
|
|
PayloadLen uint16
|
|
Reserved uint16
|
|
}
|
|
|
|
// NewIPv4SessionHeader constructs the canonical v1 header for a raw IPv4 payload.
|
|
func NewIPv4SessionHeader(sessionID uint64, payloadLen uint16) SessionHeader {
|
|
return SessionHeader{
|
|
Version: SessionVersion,
|
|
Type: SessionTypeIPv4,
|
|
SessionID: sessionID,
|
|
PayloadLen: payloadLen,
|
|
}
|
|
}
|
|
|
|
// Validate checks all v1 constants and reserved fields.
|
|
func (h SessionHeader) Validate() error {
|
|
switch {
|
|
case h.Version != SessionVersion:
|
|
return fmt.Errorf("%w: got %d", ErrSessionVersion, h.Version)
|
|
case h.Type != SessionTypeIPv4:
|
|
return fmt.Errorf("%w: got 0x%02x", ErrSessionType, h.Type)
|
|
case h.Flags != 0:
|
|
return fmt.Errorf("%w: got 0x%04x", ErrSessionFlags, h.Flags)
|
|
case h.Reserved != 0:
|
|
return fmt.Errorf("%w: got 0x%04x", ErrSessionReserved, h.Reserved)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// MarshalBinary serializes only the fixed header.
|
|
func (h SessionHeader) MarshalBinary() ([]byte, error) {
|
|
if err := h.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out := make([]byte, SessionHeaderSize)
|
|
copy(out[0:4], sessionMagic[:])
|
|
out[4] = h.Version
|
|
out[5] = h.Type
|
|
binary.BigEndian.PutUint16(out[6:8], h.Flags)
|
|
binary.BigEndian.PutUint64(out[8:16], h.SessionID)
|
|
binary.BigEndian.PutUint16(out[16:18], h.PayloadLen)
|
|
binary.BigEndian.PutUint16(out[18:20], h.Reserved)
|
|
return out, nil
|
|
}
|
|
|
|
// ParseSessionHeader parses and validates a fixed header from the start of data.
|
|
func ParseSessionHeader(data []byte) (SessionHeader, error) {
|
|
if len(data) < SessionHeaderSize {
|
|
return SessionHeader{}, ErrSessionHeaderTooShort
|
|
}
|
|
if !bytes.Equal(data[0:4], sessionMagic[:]) {
|
|
return SessionHeader{}, ErrSessionMagic
|
|
}
|
|
|
|
header := SessionHeader{
|
|
Version: data[4],
|
|
Type: data[5],
|
|
Flags: binary.BigEndian.Uint16(data[6:8]),
|
|
SessionID: binary.BigEndian.Uint64(data[8:16]),
|
|
PayloadLen: binary.BigEndian.Uint16(data[16:18]),
|
|
Reserved: binary.BigEndian.Uint16(data[18:20]),
|
|
}
|
|
if err := header.Validate(); err != nil {
|
|
return SessionHeader{}, err
|
|
}
|
|
return header, nil
|
|
}
|
|
|
|
// EncodeIPv4Session serializes a header and one complete raw IPv4 packet.
|
|
func EncodeIPv4Session(sessionID uint64, packet []byte) ([]byte, error) {
|
|
if len(packet) > int(^uint16(0)) {
|
|
return nil, ErrSessionPayloadTooLong
|
|
}
|
|
header := NewIPv4SessionHeader(sessionID, uint16(len(packet)))
|
|
headerBytes, err := header.MarshalBinary()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out := make([]byte, SessionHeaderSize+len(packet))
|
|
copy(out, headerBytes)
|
|
copy(out[SessionHeaderSize:], packet)
|
|
return out, nil
|
|
}
|
|
|
|
// DecodeIPv4Session validates framing and returns the header and raw IPv4 payload.
|
|
// The returned payload aliases data and must be copied before data is reused.
|
|
func DecodeIPv4Session(data []byte) (SessionHeader, []byte, error) {
|
|
header, err := ParseSessionHeader(data)
|
|
if err != nil {
|
|
return SessionHeader{}, nil, err
|
|
}
|
|
payload := data[SessionHeaderSize:]
|
|
if len(payload) != int(header.PayloadLen) {
|
|
return SessionHeader{}, nil, fmt.Errorf(
|
|
"%w: header=%d actual=%d",
|
|
ErrSessionPayloadLength,
|
|
header.PayloadLen,
|
|
len(payload),
|
|
)
|
|
}
|
|
return header, payload, nil
|
|
}
|