// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later // Package remoteinput implements private, bounded input-helper IPC. Possession // of the inherited control pipe is not reader consent; only a future reader // owner may launch it for an already authorized lease. package remoteinput import ( "encoding/binary" "errors" "git.stormux.org/storm/cthulhu/internal/input" ) const ( Version = 1 OperationSize = 52 AckSize = 20 Heartbeat byte = 1 Down byte = 2 Up byte = 3 Close byte = 4 Ready byte = 1 Accepted byte = 2 Closed byte = 3 ReasonClose byte = 1 ReasonEOF byte = 2 ReasonExpired byte = 3 ReasonProtocol byte = 4 ReasonTarget byte = 5 ReasonOutput byte = 6 ReasonCancelled byte = 7 ) var ErrProtocol = errors.New("invalid private remote input protocol") type Operation struct { Kind byte Epoch, Sequence uint64 Name string } func EncodeOperation(operation Operation) ([]byte, error) { if operation.Epoch == 0 || operation.Sequence == 0 || len(operation.Name) > 32 { return nil, ErrProtocol } switch operation.Kind { case Down, Up: if !input.ValidRemoteKeyName(operation.Name) { return nil, ErrProtocol } case Heartbeat, Close: if operation.Name != "" { return nil, ErrProtocol } default: return nil, ErrProtocol } frame := make([]byte, OperationSize) frame[0], frame[1], frame[2] = Version, operation.Kind, byte(len(operation.Name)) binary.BigEndian.PutUint64(frame[4:12], operation.Epoch) binary.BigEndian.PutUint64(frame[12:20], operation.Sequence) copy(frame[20:], operation.Name) return frame, nil } func DecodeOperation(frame []byte) (Operation, error) { if len(frame) != OperationSize || frame[0] != Version || frame[3] != 0 || frame[2] > 32 { return Operation{}, ErrProtocol } length := int(frame[2]) for _, padding := range frame[20+length:] { if padding != 0 { return Operation{}, ErrProtocol } } operation := Operation{Kind: frame[1], Epoch: binary.BigEndian.Uint64(frame[4:12]), Sequence: binary.BigEndian.Uint64(frame[12:20]), Name: string(frame[20 : 20+length])} if _, err := EncodeOperation(operation); err != nil { return Operation{}, err } return operation, nil } // Accepted acknowledges a completed target Apply, not application delivery. // Only Closed with CleanupFailed=false confirms successful target cleanup. type Ack struct { Kind, Reason byte CleanupFailed bool Epoch, Sequence uint64 } func EncodeAck(ack Ack) ([]byte, error) { if ack.Epoch == 0 { return nil, ErrProtocol } switch ack.Kind { case Ready: if ack.Sequence != 0 || ack.Reason != 0 || ack.CleanupFailed { return nil, ErrProtocol } case Accepted: if ack.Sequence == 0 || ack.Reason != 0 || ack.CleanupFailed { return nil, ErrProtocol } case Closed: if ack.Reason < ReasonClose || ack.Reason > ReasonCancelled { return nil, ErrProtocol } default: return nil, ErrProtocol } frame := make([]byte, AckSize) frame[0], frame[1], frame[2] = Version, ack.Kind, ack.Reason if ack.CleanupFailed { frame[3] = 1 } binary.BigEndian.PutUint64(frame[4:12], ack.Epoch) binary.BigEndian.PutUint64(frame[12:20], ack.Sequence) return frame, nil } func DecodeAck(frame []byte) (Ack, error) { if len(frame) != AckSize || frame[0] != Version || frame[3] > 1 { return Ack{}, ErrProtocol } ack := Ack{Kind: frame[1], Reason: frame[2], CleanupFailed: frame[3] != 0, Epoch: binary.BigEndian.Uint64(frame[4:12]), Sequence: binary.BigEndian.Uint64(frame[12:20])} if _, err := EncodeAck(ack); err != nil { return Ack{}, err } return ack, nil }