mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 17:50:12 +00:00
230 lines
9.5 KiB
Go
230 lines
9.5 KiB
Go
//go:build agentcompat
|
|
|
|
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"io"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func capabilityOwner(patID, userID uint64) AgentCompatCapabilityOwner {
|
|
return AgentCompatCapabilityOwner{PATID: patID, UserID: userID, IsAdmin: false}
|
|
}
|
|
|
|
func capabilityRegistration(owner AgentCompatCapabilityOwner, purpose AgentCompatCapabilityPurpose, serverID, resourceID uint64) AgentCompatCapabilityRegistration {
|
|
return AgentCompatCapabilityRegistration{
|
|
Owner: owner, Purpose: purpose, TargetServerID: serverID, ResourceID: resourceID, ServerAccessAllowed: true,
|
|
}
|
|
}
|
|
|
|
func capabilityAccess(capability AgentCompatIOStreamCapability, registration AgentCompatCapabilityRegistration) AgentCompatCapabilityAccess {
|
|
return AgentCompatCapabilityAccess{
|
|
Capability: capability, Owner: registration.Owner, Purpose: registration.Purpose,
|
|
TargetServerID: registration.TargetServerID, ResourceID: registration.ResourceID, ServerAccessAllowed: true,
|
|
}
|
|
}
|
|
|
|
func TestAgentCompatCapabilityMintUsesURLSafe256BitTokens(t *testing.T) {
|
|
handler := NewNezhaHandler()
|
|
registration := capabilityRegistration(capabilityOwner(1, 2), AgentCompatCapabilityTerminal, 3, 0)
|
|
|
|
capability, err := handler.RegisterAgentCompatIOStreamCapability(agentCompatCapabilityTestContext(t), registration)
|
|
|
|
require.NoError(t, err)
|
|
raw, err := base64.RawURLEncoding.DecodeString(capability.String())
|
|
require.NoError(t, err)
|
|
require.Len(t, raw, 32)
|
|
parsed, err := ParseAgentCompatIOStreamCapability(capability.String())
|
|
require.NoError(t, err)
|
|
require.Equal(t, capability, parsed)
|
|
_, err = ParseAgentCompatIOStreamCapability("not-a-capability")
|
|
require.ErrorIs(t, err, ErrAgentCompatCapabilityHidden)
|
|
}
|
|
|
|
func TestAgentCompatCapabilityMintRetriesActiveAndUsedCollisions(t *testing.T) {
|
|
handler := NewNezhaHandler()
|
|
first := make([]byte, 32)
|
|
second := make([]byte, 32)
|
|
third := make([]byte, 32)
|
|
first[0], second[0], third[0] = 1, 2, 3
|
|
var calls atomic.Int32
|
|
handler.setAgentCompatCapabilityTokenSourceForTest(func(destination []byte) error {
|
|
switch calls.Add(1) {
|
|
case 1, 2, 4:
|
|
copy(destination, first)
|
|
return nil
|
|
case 3:
|
|
copy(destination, second)
|
|
return nil
|
|
default:
|
|
copy(destination, third)
|
|
return nil
|
|
}
|
|
})
|
|
registration := capabilityRegistration(capabilityOwner(1, 2), AgentCompatCapabilityTerminal, 3, 0)
|
|
firstCapability, err := handler.RegisterAgentCompatIOStreamCapability(agentCompatCapabilityTestContext(t), registration)
|
|
require.NoError(t, err)
|
|
activeCollisionCapability, err := handler.RegisterAgentCompatIOStreamCapability(agentCompatCapabilityTestContext(t), registration)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, firstCapability, activeCollisionCapability)
|
|
require.NoError(t, handler.UnregisterAgentCompatIOStreamCapability(capabilityAccess(firstCapability, registration)))
|
|
|
|
tombstoneCollisionCapability, err := handler.RegisterAgentCompatIOStreamCapability(agentCompatCapabilityTestContext(t), registration)
|
|
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, firstCapability, tombstoneCollisionCapability)
|
|
require.Equal(t, int32(5), calls.Load())
|
|
}
|
|
|
|
func TestAgentCompatCapabilityRegistrationRequiresServerAccessProof(t *testing.T) {
|
|
handler := NewNezhaHandler()
|
|
registration := capabilityRegistration(capabilityOwner(1, 2), AgentCompatCapabilityTerminal, 3, 0)
|
|
registration.ServerAccessAllowed = false
|
|
|
|
_, err := handler.RegisterAgentCompatIOStreamCapability(agentCompatCapabilityTestContext(t), registration)
|
|
|
|
require.ErrorIs(t, err, ErrAgentCompatCapabilityHidden)
|
|
}
|
|
|
|
func TestAgentCompatCapabilityWaitRequiresExactOwnerAndRetainsBinding(t *testing.T) {
|
|
handler := NewNezhaHandler()
|
|
registration := capabilityRegistration(capabilityOwner(10, 20), AgentCompatCapabilityTerminal, 30, 0)
|
|
capability, err := handler.RegisterAgentCompatIOStreamCapability(agentCompatCapabilityTestContext(t), registration)
|
|
require.NoError(t, err)
|
|
require.NoError(t, handler.CreateStreamWithPurpose("terminal-bound", 20, 30, PurposeTerminal))
|
|
require.NoError(t, handler.BindAgentCompatIOStreamCapability(AgentCompatCapabilityBinding{
|
|
AgentCompatCapabilityAccess: capabilityAccess(capability, registration), StreamID: "terminal-bound",
|
|
}))
|
|
|
|
streamID, err := handler.WaitAgentCompatIOStreamCapability(agentCompatCapabilityTestContext(t), capabilityAccess(capability, registration))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "terminal-bound", streamID)
|
|
foreign := capabilityAccess(capability, registration)
|
|
foreign.Owner.PATID++
|
|
_, err = handler.WaitAgentCompatIOStreamCapability(agentCompatCapabilityTestContext(t), foreign)
|
|
require.ErrorIs(t, err, ErrAgentCompatCapabilityHidden)
|
|
}
|
|
|
|
type capabilityCloseEndpoint struct {
|
|
handler *NezhaHandler
|
|
streamID string
|
|
err error
|
|
closed atomic.Int32
|
|
}
|
|
|
|
func (endpoint *capabilityCloseEndpoint) Read([]byte) (int, error) { return 0, io.EOF }
|
|
func (endpoint *capabilityCloseEndpoint) Write(data []byte) (int, error) { return len(data), nil }
|
|
func (endpoint *capabilityCloseEndpoint) Close() error {
|
|
endpoint.closed.Add(1)
|
|
endpoint.handler.StreamOwnership(endpoint.streamID)
|
|
return endpoint.err
|
|
}
|
|
|
|
func TestAgentCompatCapabilityCancelClosesOutsideLockAndJoinsErrors(t *testing.T) {
|
|
handler, registration, capability := boundCapabilityFixture(t, AgentCompatCapabilityFileManager, "fm-close", 41)
|
|
firstErr := errors.New("user close")
|
|
secondErr := errors.New("agent close")
|
|
first := &capabilityCloseEndpoint{handler: handler, streamID: "fm-close", err: firstErr}
|
|
second := &capabilityCloseEndpoint{handler: handler, streamID: "fm-close", err: secondErr}
|
|
require.NoError(t, handler.UserConnected("fm-close", first))
|
|
require.NoError(t, handler.AgentConnected("fm-close", second))
|
|
|
|
err := handler.CancelAgentCompatIOStreamCapability(capabilityAccess(capability, registration))
|
|
|
|
require.ErrorIs(t, err, firstErr)
|
|
require.ErrorIs(t, err, secondErr)
|
|
require.Equal(t, int32(1), first.closed.Load())
|
|
require.Equal(t, int32(1), second.closed.Load())
|
|
}
|
|
|
|
func boundCapabilityFixture(t *testing.T, purpose AgentCompatCapabilityPurpose, streamID string, serverID uint64) (*NezhaHandler, AgentCompatCapabilityRegistration, AgentCompatIOStreamCapability) {
|
|
t.Helper()
|
|
handler := NewNezhaHandler()
|
|
registration := capabilityRegistration(capabilityOwner(11, 21), purpose, serverID, 0)
|
|
capability, err := handler.RegisterAgentCompatIOStreamCapability(agentCompatCapabilityTestContext(t), registration)
|
|
require.NoError(t, err)
|
|
require.NoError(t, handler.CreateStreamWithPurpose(streamID, 21, serverID, purpose.streamPurpose()))
|
|
require.NoError(t, handler.BindAgentCompatIOStreamCapability(AgentCompatCapabilityBinding{
|
|
AgentCompatCapabilityAccess: capabilityAccess(capability, registration), StreamID: streamID,
|
|
}))
|
|
return handler, registration, capability
|
|
}
|
|
|
|
func TestAgentCompatCapabilityCancelRacingCloseChangesGenerationOnce(t *testing.T) {
|
|
handler, registration, capability := boundCapabilityFixture(t, AgentCompatCapabilityTerminal, "race-close", 51)
|
|
endpoint := &capabilityCloseEndpoint{handler: handler, streamID: "race-close"}
|
|
require.NoError(t, handler.AgentConnected("race-close", endpoint))
|
|
start := handler.SnapshotIOStreamState()
|
|
ready := make(chan struct{})
|
|
raceCtx := agentCompatCapabilityTestContext(t)
|
|
var waitGroup sync.WaitGroup
|
|
waitGroup.Add(2)
|
|
go func() {
|
|
defer waitGroup.Done()
|
|
select {
|
|
case <-ready:
|
|
_ = handler.CloseStream("race-close")
|
|
case <-raceCtx.Done():
|
|
}
|
|
}()
|
|
go func() {
|
|
defer waitGroup.Done()
|
|
select {
|
|
case <-ready:
|
|
_ = handler.CancelAgentCompatIOStreamCapability(capabilityAccess(capability, registration))
|
|
case <-raceCtx.Done():
|
|
}
|
|
}()
|
|
close(ready)
|
|
raceDone := make(chan struct{})
|
|
go func() {
|
|
waitGroup.Wait()
|
|
close(raceDone)
|
|
}()
|
|
awaitAgentCompatCapabilitySignal(t, raceDone, "cancel/close race did not complete")
|
|
require.NoError(t, raceCtx.Err())
|
|
|
|
state := handler.SnapshotIOStreamState()
|
|
require.Equal(t, start.Generation+1, state.Generation)
|
|
require.Equal(t, int32(1), endpoint.closed.Load())
|
|
}
|
|
|
|
func TestAgentCompatCapabilityWaitTimeoutKeepsRegistration(t *testing.T) {
|
|
handler := NewNezhaHandler()
|
|
registration := capabilityRegistration(capabilityOwner(1, 2), AgentCompatCapabilityTerminal, 3, 0)
|
|
capability, err := handler.RegisterAgentCompatIOStreamCapability(agentCompatCapabilityTestContext(t), registration)
|
|
require.NoError(t, err)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
|
|
_, err = handler.WaitAgentCompatIOStreamCapability(ctx, capabilityAccess(capability, registration))
|
|
require.ErrorIs(t, err, context.Canceled)
|
|
require.NoError(t, handler.UnregisterAgentCompatIOStreamCapability(capabilityAccess(capability, registration)))
|
|
}
|
|
|
|
func TestAgentCompatCapabilityWaitWakesAfterUnregister(t *testing.T) {
|
|
handler := NewNezhaHandler()
|
|
registration := capabilityRegistration(capabilityOwner(1, 2), AgentCompatCapabilityTerminal, 3, 0)
|
|
capability, err := handler.RegisterAgentCompatIOStreamCapability(agentCompatCapabilityTestContext(t), registration)
|
|
require.NoError(t, err)
|
|
result := make(chan error, 1)
|
|
started := make(chan struct{})
|
|
handler.setAgentCompatCapabilityWaitObserverForTest(func() { close(started) })
|
|
waitCtx := agentCompatCapabilityTestContext(t)
|
|
go func() {
|
|
_, waitErr := handler.WaitAgentCompatIOStreamCapability(waitCtx, capabilityAccess(capability, registration))
|
|
result <- waitErr
|
|
}()
|
|
awaitAgentCompatCapabilitySignal(t, started, "wait observer did not start")
|
|
require.NoError(t, handler.UnregisterAgentCompatIOStreamCapability(capabilityAccess(capability, registration)))
|
|
|
|
require.ErrorIs(t, receiveAgentCompatCapabilityError(t, result, "unregister did not wake waiter"), ErrAgentCompatCapabilityHidden)
|
|
}
|