// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package tentaclewire import "slices" const ( SessionQueueSize = 32 SessionQueueBytes = 256 * 1024 ) // RemoteGrants are independent install-time permissions, never session consent. func RemoteGrants() []string { return []string{"output-observe", "remote-present", "keyboard-capture", "keyboard-inject", "braille-stream", "clipboard-exchange"} } func ValidRemoteGrants(grants []string) bool { seen := map[string]bool{} for _, grant := range grants { if seen[grant] || !slices.Contains(RemoteGrants(), grant) { return false } seen[grant] = true } return true } // ValidNegotiation keeps the 1.0 prefix contract. In 1.1 APIs are sets and // capabilities are permissions: commands and transport APIs confer no access. func ValidNegotiation(minor int, apis, capabilities []string) bool { if minor == 0 { return ValidAPIs(apis, capabilities) } if minor != 1 { return false } a, c := map[string]bool{}, map[string]bool{} for _, api := range apis { if a[api] || !slices.Contains([]string{"commands", "presentation", "interaction", "reader-settings", "session-events", "remote-access"}, api) { return false } a[api] = true } for _, capability := range capabilities { if c[capability] || !(slices.Contains(RemoteGrants(), capability) || slices.Contains([]string{"presentation", "interaction", "reader-settings"}, capability)) { return false } c[capability] = true } if !a["commands"] || !a["session-events"] || a["interaction"] && !a["presentation"] || a["reader-settings"] && !a["interaction"] { return false } for _, api := range []string{"presentation", "interaction", "reader-settings"} { if a[api] != c[api] { return false } } hasRemote := false for _, grant := range RemoteGrants() { hasRemote = hasRemote || c[grant] } return a["remote-access"] == hasRemote } // SessionEvent is deliberately limited to lifecycle metadata. Output/input // payloads will require their own typed validators before they can be enabled. type SessionEvent struct { Sequence uint64 `json:"sequence"` Kind string `json:"kind"` Action string `json:"action,omitempty"` Lease string `json:"lease,omitempty"` Epoch uint64 `json:"epoch"` Role string `json:"role,omitempty"` Grants []string `json:"grants,omitempty"` Reason string `json:"reason,omitempty"` } func ValidRole(role string) bool { return role == "controller" || role == "controlled" } func ValidRoleGrants(role string, grants []string) bool { if !ValidRole(role) || !ValidRemoteGrants(grants) || len(grants) == 0 { return false } for _, grant := range grants { if role == "controller" && (grant == "output-observe" || grant == "keyboard-inject") || role == "controlled" && (grant == "remote-present" || grant == "keyboard-capture") { return false } } return true } func ValidSessionEvent(event SessionEvent) bool { if event.Sequence == 0 || event.Epoch == 0 { return false } switch event.Kind { case "remote.action": return len(event.Action) == 32 && event.Lease == "" && event.Reason == "" && ValidRoleGrants(event.Role, event.Grants) case "remote.revoked": return event.Action == "" && len(event.Lease) == 32 && event.Role == "" && len(event.Grants) == 0 && slices.Contains([]string{"closed", "expired", "local", "replaced"}, event.Reason) } return false } type RemoteOpen struct { Action string `json:"action"` Epoch uint64 `json:"epoch"` Role string `json:"role"` Grants []string `json:"grants"` } type RemoteLease struct { Lease string `json:"lease"` Epoch uint64 `json:"epoch"` } type RemoteOperation struct { Lease string `json:"lease"` Epoch uint64 `json:"epoch"` Sequence uint64 `json:"sequence"` }