Add independent Remote input helper with EOF and watchdog cleanup
This commit is contained in:
1 parent
cd9ae05bbe
commit
21d8904ee3
13 files changed
+1501
-20
No files matched your search
@@ -0,0 +1,138 @@
|
||||
// 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
|
||||
}
|
||||
Reference in new issue
Block a user