Files

271 lines
10 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package tentacle
import (
"encoding/json"
"errors"
"strings"
"sync"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/tentaclewire"
)
func keyRequest(lease tentaclewire.RemoteLease, sequence uint64, pressed bool) tentaclewire.RemoteKeyRequest {
return tentaclewire.RemoteKeyRequest{RemoteOperation: tentaclewire.RemoteOperation{
Lease: lease.Lease, Epoch: lease.Epoch, Sequence: sequence},
Key: tentaclewire.RemoteKey{Name: "Control_R", Pressed: pressed}}
}
func TestRemoteKeySDKMailboxAndSequence(t *testing.T) {
for _, language := range []string{"go", "python"} {
t.Run(language, func(t *testing.T) {
manager := test_manager(t, DefaultLimits(), nil)
session := startSessionSDK(t, manager, language, "keys", "keyboard-inject")
lease := speech_fixture_lease(t, session, "controlled", []string{"keyboard-inject"})
request := keyRequest(lease, 1, true)
if _, code := remoteCall(t, session, "remote.key", request); code != -32601 {
t.Fatal("accepted keys without reader input owner", code)
}
deliveries := session.EnableRemoteKeys()
if session.EnableRemoteKeys() != deliveries {
t.Fatal("attachment created another owner mailbox")
}
if _, code := remoteCall(t, session, "remote.key", request); code != 0 {
t.Fatal("valid key was not admitted", code)
}
first := <-deliveries
if first.Key != request.Key || first.Sequence != 1 || first.Lease != lease || first.Context.Err() != nil {
t.Fatal("delivery changed key identity, sequence or lifetime")
}
if _, err := session.RemoteContext(first.Lease, "keyboard-inject"); err != nil {
t.Fatal("delivery lost original consent", err)
}
if _, code := remoteCall(t, session, "remote.key", request); code != -32001 {
t.Fatal("replayed key was accepted", code)
}
request.Sequence++
if _, code := remoteCall(t, session, "remote.heartbeat", request.RemoteOperation); code != 0 {
t.Fatal("heartbeat did not share the operation sequence", code)
}
if _, code := remoteCall(t, session, "remote.key", request); code != -32001 {
t.Fatal("key reused heartbeat sequence", code)
}
request.Sequence++
request.Key.Pressed = false
if _, code := remoteCall(t, session, "remote.key", request); code != 0 {
t.Fatal("key release was not admitted", code)
}
second := <-deliveries
if second.Key != request.Key || second.Sequence != request.Sequence {
t.Fatal("lost release ordering")
}
firstBytes, _ := json.Marshal(keyRequest(lease, 1, true))
secondBytes, _ := json.Marshal(request)
session.mutex.Lock()
items, bytes := session.remote.keyItems, session.remote.keyBytes
session.mutex.Unlock()
if items != 2 || bytes != len(firstBytes)+len(secondBytes) {
t.Fatal("in-flight accounting omitted request metadata", items, bytes)
}
var releases sync.WaitGroup
for range 8 {
releases.Add(1)
go func() { defer releases.Done(); first.Release(); second.Release() }()
}
releases.Wait()
session.mutex.Lock()
items, bytes = session.remote.keyItems, session.remote.keyBytes
session.mutex.Unlock()
if items != 0 || bytes != 0 {
t.Fatal("idempotent concurrent release retained capacity", items, bytes)
}
})
}
}
func TestRemoteKeySDKPermissionAndParameters(t *testing.T) {
for _, language := range []string{"go", "python"} {
t.Run(language, func(t *testing.T) {
manager := test_manager(t, DefaultLimits(), nil)
session := startSessionSDK(t, manager, language, "keys", "keyboard-inject")
deliveries := session.EnableRemoteKeys()
lease := speech_fixture_lease(t, session, "controller", []string{"remote-present"})
if _, code := remoteCall(t, session, "remote.key", keyRequest(lease, 1, true)); code != -32001 {
t.Fatal("controller role injected input", code)
}
lease = speech_fixture_lease(t, session, "controlled", []string{"output-observe"})
if _, code := remoteCall(t, session, "remote.key", keyRequest(lease, 1, true)); code != -32001 {
t.Fatal("output grant allowed input", code)
}
lease = speech_fixture_lease(t, session, "controlled", []string{"keyboard-inject"})
request := keyRequest(lease, 1, true)
for _, foreign := range []tentaclewire.RemoteKeyRequest{
keyRequest(tentaclewire.RemoteLease{Lease: strings.Repeat("f", 32), Epoch: lease.Epoch}, 1, true),
keyRequest(tentaclewire.RemoteLease{Lease: lease.Lease, Epoch: lease.Epoch + 1}, 1, true),
} {
if _, code := remoteCall(t, session, "remote.key", foreign); code != -32001 {
t.Fatal("foreign lease/epoch admitted input", code)
}
}
other := startSessionSDK(t, manager, language, "other-keys", "keyboard-inject")
other.EnableRemoteKeys()
if _, code := remoteCall(t, other, "remote.key", request); code != -32001 {
t.Fatal("foreign process used another session's lease", code)
}
data, _ := json.Marshal(request)
for _, raw := range []string{
strings.Replace(string(data), `"pressed":true`, `"pressed":null`, 1),
strings.Replace(string(data), `"name":"Control_R"`, `"name":"unknown private value"`, 1),
strings.Replace(string(data), `"sequence":1`, `"sequence":1,"origin":"local"`, 1),
strings.Replace(string(data), `"sequence":1`, `"sequence":0`, 1),
} {
if _, code := remoteCall(t, session, "remote.key", json.RawMessage(raw)); code != -32602 {
t.Fatal("invalid key parameters were admitted", code)
}
}
if len(deliveries) != 0 {
t.Fatal("denied or invalid input reached the owner")
}
if _, code := remoteCall(t, session, "remote.key", request); code != 0 {
t.Fatal("denied requests consumed the live operation sequence", code)
}
(<-deliveries).Release()
})
}
}
func TestRemoteKeySDKOverflowRetiresInFlightWork(t *testing.T) {
for _, language := range []string{"go", "python"} {
t.Run(language, func(t *testing.T) {
session := startSessionSDK(t, test_manager(t, DefaultLimits(), nil), language, "keys", "keyboard-inject")
lease := speech_fixture_lease(t, session, "controlled", []string{"keyboard-inject"})
deliveries := session.EnableRemoteKeys()
request := keyRequest(lease, 1, true)
if _, code := remoteCall(t, session, "remote.key", request); code != 0 {
t.Fatal(code)
}
inFlight := <-deliveries // Dequeue must not free input capacity.
session.mutex.Lock()
old := session.remote
session.mutex.Unlock()
for index := 2; index <= tentaclewire.RemoteKeyQueueSize; index++ {
request.Sequence++
if index%32 == 0 {
if _, code := remoteCall(t, session, "remote.heartbeat", request.RemoteOperation); code != 0 {
t.Fatal(code)
}
request.Sequence++
}
if _, code := remoteCall(t, session, "remote.key", request); code != 0 {
t.Fatal("input capacity ended early", index, code)
}
}
request.Sequence++
request.Key.Pressed = false
if _, code := remoteCall(t, session, "remote.key", request); code != -32002 {
t.Fatal("full queue silently lost a release", code)
}
if inFlight.Context.Err() == nil {
t.Fatal("overflow retained in-flight authority")
}
if _, err := session.RemoteContext(lease, "keyboard-inject"); !errors.Is(err, ErrPermission) {
t.Fatal("overflow retained input consent", err)
}
inFlight.Release()
inFlight.Release()
session.mutex.Lock()
defer session.mutex.Unlock()
if session.remote != nil || old.keyItems != 0 || old.keyBytes != 0 || len(deliveries) != 0 {
t.Fatal("overflow retained queued input or accounting")
}
})
}
}
func TestRemoteKeySDKRetirementAndReplacement(t *testing.T) {
for _, language := range []string{"go", "python"} {
for _, reason := range []string{"local", "close", "replace", "expire", "disable", "failure", "policy"} {
t.Run(language+"/"+reason, func(t *testing.T) {
manager := test_manager(t, DefaultLimits(), nil)
policy := NewRemotePolicy()
if err := manager.UseRemotePolicy(policy); err != nil {
t.Fatal(err)
}
session := startSessionSDK(t, manager, language, "keys", "keyboard-inject")
lease := speech_fixture_lease(t, session, "controlled", []string{"keyboard-inject"})
deliveries := session.EnableRemoteKeys()
for sequence := uint64(1); sequence <= 2; sequence++ {
if _, code := remoteCall(t, session, "remote.key", keyRequest(lease, sequence, true)); code != 0 {
t.Fatal(code)
}
}
inFlight := <-deliveries
session.mutex.Lock()
old := session.remote
session.mutex.Unlock()
switch reason {
case "local":
session.RevokeRemote()
case "close":
if _, code := remoteCall(t, session, "remote.close", keyRequest(lease, 3, false).RemoteOperation); code != 0 {
t.Fatal(code)
}
case "replace":
fresh := speech_fixture_lease(t, session, "controlled", []string{"keyboard-inject"})
if _, code := remoteCall(t, session, "remote.key", keyRequest(fresh, 1, true)); code != 0 {
t.Fatal(code)
}
replacement := <-deliveries
inFlight.Release() // Must not release the replacement budget.
session.mutex.Lock()
count := session.remote.keyItems
session.mutex.Unlock()
if count != 1 || replacement.Context.Err() != nil {
t.Fatal("stale release affected replacement work")
}
replacement.Release()
case "expire", "policy":
session.mutex.Lock()
if reason == "expire" {
session.refresh_remote_locked(time.Millisecond)
} else {
policy.SetAvailable(false)
if inFlight.Context.Err() == nil {
session.mutex.Unlock()
t.Fatal("policy cancellation waited for a session worker")
}
policy.SetAvailable(true)
}
session.mutex.Unlock()
var event tentaclewire.SessionEvent
if json.Unmarshal(invokeSessionSDK(t, session, "event", nil), &event) != nil || event.Kind != "remote.revoked" || event.Epoch != lease.Epoch {
t.Fatal("missing original-epoch retirement notification")
}
case "disable":
session.Disable()
case "failure":
session.fail(ErrProtocol)
}
if inFlight.Context.Err() == nil {
t.Fatal("retired input survived")
}
if _, err := session.RemoteContext(lease, "keyboard-inject"); !errors.Is(err, ErrPermission) {
t.Fatal("retired input passed delivery-time consent check")
}
inFlight.Release()
inFlight.Release()
session.mutex.Lock()
defer session.mutex.Unlock()
if old.keyItems != 0 || old.keyBytes != 0 || len(deliveries) != 0 {
t.Fatal("retirement retained pending input/accounting")
}
})
}
}
}