86 lines
3.1 KiB
Go
86 lines
3.1 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package tentacle
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sync"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/tentaclewire"
|
|
)
|
|
|
|
// RemoteKeyDelivery is accepted work, not evidence of native injection. A
|
|
// reader input owner must recheck Context, RemoteContext(keyboard-inject),
|
|
// focus/security policy and its own key ledger immediately before delivery.
|
|
// Revocation must independently release native-owned keys, without relying on
|
|
// this queue or a final key-up from the network child.
|
|
type RemoteKeyDelivery struct {
|
|
Context context.Context
|
|
Lease tentaclewire.RemoteLease
|
|
Sequence uint64
|
|
Key tentaclewire.RemoteKey
|
|
// Release returns retained capacity after delivery/discard. Dequeuing does
|
|
// not free capacity. It is idempotent and scoped to the original lease.
|
|
Release func()
|
|
}
|
|
|
|
// EnableRemoteKeys attaches one reader-owned mailbox. It performs no native
|
|
// setup and grants no authority. No production owner is attached yet, so
|
|
// remote.key remains unavailable in ordinary reader sessions.
|
|
func (session *Session) EnableRemoteKeys() <-chan RemoteKeyDelivery {
|
|
session.mutex.Lock()
|
|
defer session.mutex.Unlock()
|
|
if session.remoteKeys == nil {
|
|
session.remoteKeys = make(chan RemoteKeyDelivery, tentaclewire.RemoteKeyQueueSize)
|
|
}
|
|
return session.remoteKeys
|
|
}
|
|
|
|
func (session *Session) remote_key_locked(params json.RawMessage) (any, *RPCError) {
|
|
var request tentaclewire.RemoteKeyRequest
|
|
if decode_strict(params, &request) != nil {
|
|
return nil, &RPCError{Code: -32602, Message: "Invalid session parameters"}
|
|
}
|
|
lease := tentaclewire.RemoteLease{Lease: request.Lease, Epoch: request.Epoch}
|
|
access := session.remote_access_locked(lease, "keyboard-inject")
|
|
if access == nil || access.role != "controlled" || request.Sequence <= access.sequence {
|
|
return nil, &RPCError{Code: -32001, Message: "Session permission denied"}
|
|
}
|
|
if session.remoteKeys == nil {
|
|
return nil, &RPCError{Code: -32601, Message: "Remote keyboard owner unavailable"}
|
|
}
|
|
// Account canonical full request bytes, including the lease/sequence. Both
|
|
// admission counts include queued and in-flight work until Release.
|
|
data, _ := json.Marshal(request)
|
|
bytes := len(data)
|
|
if access.keyItems == tentaclewire.RemoteKeyQueueSize || access.keyBytes+bytes > tentaclewire.RemoteKeyQueueBytes {
|
|
session.revoke_remote_locked("local", true)
|
|
return nil, &RPCError{Code: -32002, Message: "Remote keyboard queue full"}
|
|
}
|
|
access.keyItems++
|
|
access.keyBytes += bytes
|
|
access.sequence = request.Sequence
|
|
var released sync.Once
|
|
delivery := RemoteKeyDelivery{Context: access.ctx, Lease: lease, Sequence: request.Sequence, Key: request.Key, Release: func() {
|
|
released.Do(func() {
|
|
session.mutex.Lock()
|
|
defer session.mutex.Unlock()
|
|
if session.remote == access {
|
|
access.keyItems--
|
|
access.keyBytes -= bytes
|
|
}
|
|
})
|
|
}}
|
|
select {
|
|
case session.remoteKeys <- delivery:
|
|
return struct{}{}, nil
|
|
default:
|
|
// Losing a release must never leave the input lease active.
|
|
session.revoke_remote_locked("local", true)
|
|
return nil, &RPCError{Code: -32002, Message: "Remote keyboard queue full"}
|
|
}
|
|
}
|