feat: execute durable client commands through supervisor

This commit is contained in:
2026-09-06 12:18:15 +00:00
parent 3f84d3b2f1
commit 56b15c7f4f
41 changed files with 4272 additions and 66 deletions
+126
View File
@@ -0,0 +1,126 @@
// Package windowstray contains the small, versioned protocol between the
// per-session notification-area process and the machine-wide service. The
// tray never receives command payloads or opens the client spool.
package windowstray
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"unicode/utf8"
)
const (
protocolVersion uint16 = 1
maxFrameBytes = 64 << 10
maxPayloadBytes = 4 << 10
)
var (
ErrInvalidFrame = errors.New("invalid tray protocol frame")
ErrFrameTooLarge = errors.New("tray protocol frame is too large")
ErrUnauthorized = errors.New("tray peer is not authorized for this action")
ErrInvalidPeer = errors.New("tray peer identity is not verified")
)
type Action uint16
const (
ActionStatus Action = iota + 1
ActionOpenConfig
ActionOpenLog
ActionStartService
ActionStopService
ActionRestartService
ActionExitTray
)
func (action Action) valid() bool { return action >= ActionStatus && action <= ActionExitTray }
// Frame is deliberately not an RPC envelope. Payloads are bounded display
// text only (status/detail); service mutations use an enum and are rechecked
// by the service under the caller's token.
type Frame struct {
Action Action
Payload []byte
}
func Encode(frame Frame) ([]byte, error) {
if !frame.Action.valid() || len(frame.Payload) > maxPayloadBytes || !utf8.Valid(frame.Payload) {
return nil, ErrInvalidFrame
}
if frame.Action != ActionStatus && len(frame.Payload) != 0 {
return nil, ErrInvalidFrame
}
total := 4 + 2 + 2 + 4 + len(frame.Payload)
if total > maxFrameBytes {
return nil, ErrFrameTooLarge
}
encoded := make([]byte, total)
copy(encoded[:4], []byte("RVTY"))
binary.BigEndian.PutUint16(encoded[4:6], protocolVersion)
binary.BigEndian.PutUint16(encoded[6:8], uint16(frame.Action))
binary.BigEndian.PutUint32(encoded[8:12], uint32(len(frame.Payload)))
copy(encoded[12:], frame.Payload)
return encoded, nil
}
func Decode(encoded []byte) (Frame, error) {
if len(encoded) > maxFrameBytes {
return Frame{}, ErrFrameTooLarge
}
if len(encoded) < 12 || !bytes.Equal(encoded[:4], []byte("RVTY")) || binary.BigEndian.Uint16(encoded[4:6]) != protocolVersion {
return Frame{}, ErrInvalidFrame
}
action := Action(binary.BigEndian.Uint16(encoded[6:8]))
length := binary.BigEndian.Uint32(encoded[8:12])
if !action.valid() || length > maxPayloadBytes || uint64(length)+12 != uint64(len(encoded)) {
return Frame{}, ErrInvalidFrame
}
payload := bytes.Clone(encoded[12:])
if !utf8.Valid(payload) || action != ActionStatus && len(payload) != 0 {
return Frame{}, ErrInvalidFrame
}
return Frame{Action: action, Payload: payload}, nil
}
type Peer struct {
PID uint32
SessionID uint32
SID string
TokenVerified bool
Interactive bool
Administrator bool
System bool
}
func (peer Peer) Validate() error {
if peer.PID == 0 || peer.SessionID == ^uint32(0) || peer.SID == "" || !peer.TokenVerified {
return ErrInvalidPeer
}
return nil
}
func Authorize(peer Peer, action Action) error {
if !action.valid() {
return ErrInvalidFrame
}
if err := peer.Validate(); err != nil {
return err
}
if !peer.Interactive {
return fmt.Errorf("%w: tray peer is not interactive", ErrUnauthorized)
}
switch action {
case ActionStatus, ActionOpenConfig, ActionOpenLog, ActionExitTray:
return nil
case ActionStartService, ActionStopService, ActionRestartService:
if peer.Administrator || peer.System {
return nil
}
return fmt.Errorf("%w: service mutation requires administrator authorization", ErrUnauthorized)
default:
return ErrInvalidFrame
}
}
@@ -0,0 +1,56 @@
package windowstray
import (
"bytes"
"errors"
"testing"
)
func TestTrayFrameRoundTripAndPayloadBounds_HP_WINTRAY_01(t *testing.T) {
t.Parallel()
frame := Frame{Action: ActionStatus, Payload: []byte("connected=true dirty=false")}
encoded, err := Encode(frame)
if err != nil {
t.Fatal(err)
}
decoded, err := Decode(encoded)
if err != nil || decoded.Action != frame.Action || !bytes.Equal(decoded.Payload, frame.Payload) {
t.Fatalf("tray frame round trip = %#v, %v", decoded, err)
}
for _, invalid := range []Frame{{Action: 0}, {Action: ActionOpenLog, Payload: []byte("unexpected")}, {Action: ActionStatus, Payload: bytes.Repeat([]byte("x"), maxPayloadBytes+1)}} {
if !errors.Is(mustEncode(invalid), ErrInvalidFrame) && !errors.Is(mustEncode(invalid), ErrFrameTooLarge) {
t.Fatalf("invalid tray frame accepted: %#v", invalid)
}
}
corrupt := append([]byte(nil), encoded...)
corrupt[0] = 'X'
if _, err := Decode(corrupt); !errors.Is(err, ErrInvalidFrame) {
t.Fatalf("corrupt tray frame error = %v", err)
}
}
func mustEncode(frame Frame) error {
_, err := Encode(frame)
return err
}
func TestTrayPeerAuthorizationIsActionScoped_BH_WINTRAY_01(t *testing.T) {
t.Parallel()
user := Peer{PID: 10, SessionID: 1, SID: "S-1-5-21-user", TokenVerified: true, Interactive: true}
if err := Authorize(user, ActionStatus); err != nil {
t.Fatal(err)
}
if err := Authorize(user, ActionRestartService); !errors.Is(err, ErrUnauthorized) {
t.Fatalf("unprivileged service mutation error = %v", err)
}
admin := user
admin.Administrator = true
if err := Authorize(admin, ActionRestartService); err != nil {
t.Fatal(err)
}
for _, peer := range []Peer{{PID: 10, SessionID: 1, SID: "S-1-5-21-user", Interactive: true}, {PID: 10, SessionID: 1, SID: "S-1-5-21-user", TokenVerified: true}} {
if err := Authorize(peer, ActionStatus); !errors.Is(err, ErrInvalidPeer) && !errors.Is(err, ErrUnauthorized) {
t.Fatalf("invalid peer accepted: %#v err=%v", peer, err)
}
}
}