test(scum): cover supported companion adapters
This commit is contained in:
@@ -2,12 +2,15 @@ package companion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var errAdapterUnsupported = errors.New("versioned adapter is unsupported")
|
||||
|
||||
// AuthorizedConfigPort is supplied by a version-bound Companion integration.
|
||||
// It exposes logical configuration values only: never a host path, connection
|
||||
// string, credential, arbitrary command, or direct database handle.
|
||||
@@ -75,9 +78,11 @@ type VersionedAdapter struct {
|
||||
DiagnosticsState map[string]string
|
||||
}
|
||||
|
||||
func (adapter VersionedAdapter) ServerBinding() string { return adapter.BoundServerID }
|
||||
|
||||
func (adapter VersionedAdapter) ReadConfiguration(ctx context.Context) (map[string]any, error) {
|
||||
if !supportedAdapterVersion(adapter.ServerVersion) || adapter.Config == nil {
|
||||
return nil, fmt.Errorf("configuration adapter is unsupported")
|
||||
return nil, errAdapterUnsupported
|
||||
}
|
||||
fields, err := adapter.Config.ReadConfig(ctx)
|
||||
if err != nil {
|
||||
@@ -87,7 +92,7 @@ func (adapter VersionedAdapter) ReadConfiguration(ctx context.Context) (map[stri
|
||||
}
|
||||
func (adapter VersionedAdapter) PatchConfiguration(ctx context.Context, payload map[string]any) (map[string]any, error) {
|
||||
if !supportedAdapterVersion(adapter.ServerVersion) || adapter.Config == nil {
|
||||
return nil, fmt.Errorf("configuration adapter is unsupported")
|
||||
return nil, errAdapterUnsupported
|
||||
}
|
||||
revision, _ := payload["revision"].(string)
|
||||
raw, _ := payload["fields"].([]any)
|
||||
@@ -120,14 +125,14 @@ func (adapter VersionedAdapter) Diagnostics(context.Context) (map[string]any, er
|
||||
return state, nil
|
||||
}
|
||||
func (VersionedAdapter) PatchGameState(context.Context, map[string]any) (map[string]any, error) {
|
||||
return nil, fmt.Errorf("state adapter is unsupported")
|
||||
return nil, errAdapterUnsupported
|
||||
}
|
||||
func (VersionedAdapter) DeliverReward(context.Context, map[string]any) (map[string]any, error) {
|
||||
return nil, fmt.Errorf("reward adapter is unsupported")
|
||||
return nil, errAdapterUnsupported
|
||||
}
|
||||
func (adapter VersionedAdapter) NotifyPlayer(ctx context.Context, payload map[string]any) (map[string]any, error) {
|
||||
if !adapter.supportsPinnedUE4SS() || adapter.Notification == nil {
|
||||
return nil, fmt.Errorf("notification adapter is unsupported")
|
||||
return nil, errAdapterUnsupported
|
||||
}
|
||||
playerID, playerOK := payload["playerId"].(string)
|
||||
message, messageOK := payload["message"].(string)
|
||||
@@ -146,7 +151,7 @@ func (adapter VersionedAdapter) NotifyPlayer(ctx context.Context, payload map[st
|
||||
}
|
||||
func (adapter VersionedAdapter) SpawnVehicle(ctx context.Context, payload map[string]any) (map[string]any, error) {
|
||||
if !adapter.supportsPinnedUE4SS() || adapter.VehicleSpawn == nil {
|
||||
return nil, fmt.Errorf("vehicle spawn adapter is unsupported")
|
||||
return nil, errAdapterUnsupported
|
||||
}
|
||||
vehicleCode, ok := payload["vehicleCode"].(string)
|
||||
spawn, err := newUE4SSVehicleSpawn(adapter.BoundServerID, vehicleCode)
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
package companion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// isolatedAdapterPort is a non-production typed-port fixture. It records only
|
||||
// the adapter's typed inputs and never opens a connection or accepts a raw
|
||||
// game command.
|
||||
type isolatedAdapterPort struct {
|
||||
configFields map[string]string
|
||||
configErr error
|
||||
patchErr error
|
||||
patches []ConfigFieldPatch
|
||||
notifications []ue4SSPlayerNotification
|
||||
notifyErr error
|
||||
notifyAccept bool
|
||||
spawns []ue4SSVehicleSpawn
|
||||
spawnReceipts []UE4SSVehicleSpawnReceipt
|
||||
spawnErrors []error
|
||||
}
|
||||
|
||||
func (port *isolatedAdapterPort) ReadConfig(context.Context) (map[string]string, error) {
|
||||
return port.configFields, port.configErr
|
||||
}
|
||||
|
||||
func (port *isolatedAdapterPort) ApplyConfigPatch(_ context.Context, _ string, fields []ConfigFieldPatch) (map[string]string, error) {
|
||||
port.patches = append(port.patches, fields...)
|
||||
if port.patchErr != nil {
|
||||
return nil, port.patchErr
|
||||
}
|
||||
return map[string]string{"ServerName": "Moonlight", "hostPath": "C:/private/server.ini"}, nil
|
||||
}
|
||||
|
||||
func (port *isolatedAdapterPort) SendPlayerNotification(_ context.Context, notification ue4SSPlayerNotification) (UE4SSNotificationReceipt, error) {
|
||||
port.notifications = append(port.notifications, notification)
|
||||
return UE4SSNotificationReceipt{Accepted: port.notifyAccept}, port.notifyErr
|
||||
}
|
||||
|
||||
func (port *isolatedAdapterPort) SpawnVehicle(_ context.Context, spawn ue4SSVehicleSpawn) (UE4SSVehicleSpawnReceipt, error) {
|
||||
port.spawns = append(port.spawns, spawn)
|
||||
index := len(port.spawns) - 1
|
||||
if index >= len(port.spawnReceipts) {
|
||||
return UE4SSVehicleSpawnReceipt{Outcome: UE4SSVehicleSpawnUnknown}, nil
|
||||
}
|
||||
return port.spawnReceipts[index], port.spawnErrors[index]
|
||||
}
|
||||
|
||||
type isolatedDispatchGateway struct {
|
||||
commands []ClaimedCommand
|
||||
acks []string
|
||||
completed map[string][]CommandResult
|
||||
}
|
||||
|
||||
func (gateway *isolatedDispatchGateway) ClaimCommands(context.Context, int) ([]ClaimedCommand, error) {
|
||||
return append([]ClaimedCommand(nil), gateway.commands...), nil
|
||||
}
|
||||
|
||||
func (gateway *isolatedDispatchGateway) AckCommand(_ context.Context, id string, _ uint64) (CommandAck, error) {
|
||||
gateway.acks = append(gateway.acks, id)
|
||||
return CommandAck{CommandID: id, State: "claimed", FencingToken: 1}, nil
|
||||
}
|
||||
|
||||
func (gateway *isolatedDispatchGateway) CompleteCommand(_ context.Context, id string, _ uint64, result CommandResult) (CompletedCommand, error) {
|
||||
if gateway.completed == nil {
|
||||
gateway.completed = map[string][]CommandResult{}
|
||||
}
|
||||
gateway.completed[id] = append(gateway.completed[id], result)
|
||||
return CompletedCommand{State: "completed"}, nil
|
||||
}
|
||||
|
||||
func e2eClaim(id, commandType string, payload map[string]any, stamp time.Time) ClaimedCommand {
|
||||
return ClaimedCommand{ID: id, ProfileKey: ProfileKey, CommandType: commandType, Payload: payload, FencingToken: 1, LeaseExpiresAt: stamp.Add(time.Minute), ExpiresAt: stamp.Add(time.Minute)}
|
||||
}
|
||||
|
||||
func TestSupportedAdaptersDispatchThroughIsolatedTypedPorts(t *testing.T) {
|
||||
stamp := time.Date(2026, time.July, 29, 12, 0, 0, 0, time.UTC)
|
||||
port := &isolatedAdapterPort{
|
||||
configFields: map[string]string{"ServerName": "Moonlight", "Password": "never-return", "hostPath": "C:/private/server.ini"},
|
||||
notifyAccept: true,
|
||||
spawnReceipts: []UE4SSVehicleSpawnReceipt{{Outcome: UE4SSVehicleSpawnAccepted}, {Outcome: UE4SSVehicleSpawnRejected}, {Outcome: UE4SSVehicleSpawnUnknown}},
|
||||
spawnErrors: []error{nil, nil, errors.New("receipt unavailable")},
|
||||
}
|
||||
adapter := VersionedAdapter{BoundServerID: "server-1", ServerVersion: "0.9.700.90357", UE4SSBuild: UE4SSReferenceBuild, UE4SSReferenceRevision: UE4SSReferenceRevision, Config: port, Notification: port, VehicleSpawn: port}
|
||||
registry := NewHandlerRegistry(HandlerAvailability{BoundServerID: "server-1", ServerVersion: "0.9.700.90357", Approved: true, Capabilities: map[string]bool{"config.read": true, "config.patch": true, "player.notify": true, "vehicle.spawn": true}}, adapter)
|
||||
gateway := &isolatedDispatchGateway{commands: []ClaimedCommand{
|
||||
e2eClaim("config-read", "config.read", map[string]any{}, stamp),
|
||||
e2eClaim("config-patch", "config.patch", map[string]any{"revision": "r1", "fields": []any{map[string]any{"key": "ServerName", "value": "Moonlight"}}}, stamp),
|
||||
e2eClaim("notify", "player.notify", map[string]any{"playerId": "76561198000000001", "message": "Moonlight ready"}, stamp),
|
||||
e2eClaim("notify", "player.notify", map[string]any{"playerId": "76561198000000001", "message": "Moonlight ready"}, stamp),
|
||||
e2eClaim("spawn-success", "vehicle.spawn", map[string]any{"vehicleCode": "BPC_Laika_C"}, stamp),
|
||||
e2eClaim("spawn-failed", "vehicle.spawn", map[string]any{"vehicleCode": "BPC_WolfsWagen_C"}, stamp),
|
||||
e2eClaim("spawn-unknown", "vehicle.spawn", map[string]any{"vehicleCode": "BPC_Laika_C"}, stamp),
|
||||
e2eClaim("spawn-unknown", "vehicle.spawn", map[string]any{"vehicleCode": "BPC_Laika_C"}, stamp),
|
||||
}}
|
||||
dispatcher := Dispatcher{Client: gateway, Registry: registry, Now: func() time.Time { return stamp }}
|
||||
if err := dispatcher.DispatchOnce(context.Background()); err != nil {
|
||||
t.Fatalf("dispatch supported adapters: %v", err)
|
||||
}
|
||||
if len(port.notifications) != 1 || port.notifications[0].ServerID != "server-1" || port.notifications[0].protectedAuditCommand == "" {
|
||||
t.Fatalf("notification did not remain server-bound and idempotent: %+v", port.notifications)
|
||||
}
|
||||
if len(port.patches) != 1 || gateway.completed["config-read"][0].Payload["fields"].(map[string]string)["ServerName"] != "Moonlight" || gateway.completed["config-patch"][0].Payload["appliedFields"].(map[string]string)["ServerName"] != "Moonlight" || gateway.completed["notify"][0].Payload["accepted"] != true {
|
||||
t.Fatalf("supported adapters did not return their bounded successful results: patches=%+v completed=%+v", port.patches, gateway.completed)
|
||||
}
|
||||
if len(port.spawns) != 3 || port.spawns[0].protectedAuditCommand != "#spawnvehicle BPC_Laika_C" || port.spawns[1].protectedAuditCommand != "#spawnvehicle BPC_WolfsWagen_C" || port.spawns[2].protectedAuditCommand != "#spawnvehicle BPC_Laika_C" {
|
||||
t.Fatalf("vehicle adapter did not use only fixed private templates: %+v", port.spawns)
|
||||
}
|
||||
if gateway.completed["spawn-success"][0].Payload["outcome"] != "succeeded" || gateway.completed["spawn-failed"][0].Payload["outcome"] != "failed" || gateway.completed["spawn-unknown"][0].Payload["outcome"] != "unknown" {
|
||||
t.Fatalf("unexpected bounded vehicle outcomes: %+v", gateway.completed)
|
||||
}
|
||||
for id, results := range gateway.completed {
|
||||
for _, result := range results {
|
||||
serialized := result.Summary + " " + stringifyPayload(result.Payload)
|
||||
for _, secret := range []string{"C:/private/server.ini", "never-return", "#spawnvehicle", "SendChat"} {
|
||||
if strings.Contains(serialized, secret) {
|
||||
t.Fatalf("%s exposed protected adapter output %q: %+v", id, secret, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportedAdaptersFailClosedForBindingApprovalVersionAndCapability(t *testing.T) {
|
||||
stamp := time.Date(2026, time.July, 29, 12, 0, 0, 0, time.UTC)
|
||||
for name, testCase := range map[string]struct {
|
||||
availability HandlerAvailability
|
||||
adapter VersionedAdapter
|
||||
}{
|
||||
"binding": {availability: HandlerAvailability{BoundServerID: "server-1", ServerVersion: "0.9.700.90357", Approved: true, Capabilities: map[string]bool{"vehicle.spawn": true}}, adapter: VersionedAdapter{BoundServerID: "server-2", ServerVersion: "0.9.700.90357", UE4SSBuild: UE4SSReferenceBuild, UE4SSReferenceRevision: UE4SSReferenceRevision}},
|
||||
"approval": {availability: HandlerAvailability{BoundServerID: "server-1", ServerVersion: "0.9.700.90357", Approved: false, Capabilities: map[string]bool{"vehicle.spawn": true}}, adapter: VersionedAdapter{BoundServerID: "server-1", ServerVersion: "0.9.700.90357", UE4SSBuild: UE4SSReferenceBuild, UE4SSReferenceRevision: UE4SSReferenceRevision}},
|
||||
"capability": {availability: HandlerAvailability{BoundServerID: "server-1", ServerVersion: "0.9.700.90357", Approved: true, Capabilities: map[string]bool{}}, adapter: VersionedAdapter{BoundServerID: "server-1", ServerVersion: "0.9.700.90357", UE4SSBuild: UE4SSReferenceBuild, UE4SSReferenceRevision: UE4SSReferenceRevision}},
|
||||
"version": {availability: HandlerAvailability{BoundServerID: "server-1", ServerVersion: "0.9.700.90357", Approved: true, Capabilities: map[string]bool{"vehicle.spawn": true}}, adapter: VersionedAdapter{BoundServerID: "server-1", ServerVersion: "0.9.700.90357", UE4SSBuild: "3.0.2", UE4SSReferenceRevision: UE4SSReferenceRevision}},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
port := &isolatedAdapterPort{}
|
||||
testCase.adapter.VehicleSpawn = port
|
||||
gateway := &isolatedDispatchGateway{commands: []ClaimedCommand{e2eClaim(name, "vehicle.spawn", map[string]any{"vehicleCode": "BPC_Laika_C"}, stamp)}}
|
||||
dispatcher := Dispatcher{Client: gateway, Registry: NewHandlerRegistry(testCase.availability, testCase.adapter), Now: func() time.Time { return stamp }}
|
||||
if err := dispatcher.DispatchOnce(context.Background()); err != nil {
|
||||
t.Fatalf("dispatch fail-closed case: %v", err)
|
||||
}
|
||||
result := gateway.completed[name][0]
|
||||
if result.Payload["result"] != "unsupported" || len(port.spawns) != 0 {
|
||||
t.Fatalf("unsafe adapter reached transport: result=%+v spawns=%+v", result, port.spawns)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportedAdapterTransportFailuresCompleteWithoutProtectedOutput(t *testing.T) {
|
||||
stamp := time.Date(2026, time.July, 29, 12, 0, 0, 0, time.UTC)
|
||||
port := &isolatedAdapterPort{patchErr: errors.New("private port failed"), notifyErr: errors.New("private notification failed")}
|
||||
adapter := VersionedAdapter{BoundServerID: "server-1", ServerVersion: "0.9.700.90357", UE4SSBuild: UE4SSReferenceBuild, UE4SSReferenceRevision: UE4SSReferenceRevision, Config: port, Notification: port}
|
||||
gateway := &isolatedDispatchGateway{commands: []ClaimedCommand{
|
||||
e2eClaim("patch-failure", "config.patch", map[string]any{"revision": "r1", "fields": []any{map[string]any{"key": "ServerName", "value": "Moonlight"}}}, stamp),
|
||||
e2eClaim("notification-failure", "player.notify", map[string]any{"playerId": "76561198000000001", "message": "Moonlight ready"}, stamp),
|
||||
}}
|
||||
dispatcher := Dispatcher{Client: gateway, Registry: NewHandlerRegistry(HandlerAvailability{BoundServerID: "server-1", ServerVersion: "0.9.700.90357", Approved: true, Capabilities: map[string]bool{"config.patch": true, "player.notify": true}}, adapter), Now: func() time.Time { return stamp }}
|
||||
if err := dispatcher.DispatchOnce(context.Background()); err != nil {
|
||||
t.Fatalf("dispatch adapter failures: %v", err)
|
||||
}
|
||||
for id, results := range gateway.completed {
|
||||
result := results[0]
|
||||
if result.Payload["result"] != "failed" || strings.Contains(stringifyPayload(result.Payload), "private") || strings.Contains(result.Summary, "private") {
|
||||
t.Fatalf("%s did not redact failed typed-port output: %+v", id, result)
|
||||
}
|
||||
}
|
||||
if len(port.notifications) != 1 || port.notifications[0].protectedAuditCommand == "" {
|
||||
t.Fatalf("notification fixture did not receive one protected typed request: %+v", port.notifications)
|
||||
}
|
||||
}
|
||||
|
||||
func stringifyPayload(payload map[string]any) string {
|
||||
parts := make([]string, 0, len(payload))
|
||||
for key, value := range payload {
|
||||
parts = append(parts, key+"="+strings.TrimSpace(strings.ReplaceAll(strings.TrimSpace(toText(value)), "\n", " ")))
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func toText(value any) string {
|
||||
if text, ok := value.(string); ok {
|
||||
return text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestCompanionProductionSourceHasNoForbiddenAdapterPaths(t *testing.T) {
|
||||
_, currentFile, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
t.Fatal("resolve companion source directory")
|
||||
}
|
||||
forbidden := map[string]*regexp.Regexp{
|
||||
"raw SQL or direct database access": regexp.MustCompile(`(?im)"(?:database/sql|github\.com/(?:mattn/go-sqlite3|go-sql-driver/mysql)|gorm\.io/gorm)"|\b(?:sql|db|database)\.(?:Open|Exec(?:Context)?|Query(?:Context)?|Prepare(?:Context)?)\s*\(`),
|
||||
"unrestricted RCON or command execution": regexp.MustCompile(`(?i)\b(?:send|execute|run|dispatch)[a-z0-9_]*(?:rcon|rawcommand|command)\s*\(`),
|
||||
"desktop automation or screen capture": regexp.MustCompile(`(?i)\b(?:tesseract|gosseract|screenshot|robotgo|autogui|keybd_event|mouse_event|sendinput)\b`),
|
||||
"direct socket transport": regexp.MustCompile(`\bnet\.(?:Dial|DialTimeout)\s*\(`),
|
||||
}
|
||||
root := filepath.Dir(currentFile)
|
||||
err := filepath.WalkDir(root, func(path string, entry os.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
if entry.IsDir() || filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") {
|
||||
return nil
|
||||
}
|
||||
source, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for name, pattern := range forbidden {
|
||||
if match := pattern.FindString(string(source)); match != "" {
|
||||
t.Fatalf("%s contains forbidden %s: %q", filepath.Base(path), name, match)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("scan production companion source: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package companion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -20,6 +21,11 @@ type SafeAdapter interface {
|
||||
SpawnVehicle(context.Context, map[string]any) (map[string]any, error)
|
||||
}
|
||||
|
||||
// ServerBoundAdapter lets a versioned adapter prove that it is configured for
|
||||
// the same server as the registration which declared handler availability.
|
||||
// Generic test adapters do not need this optional assertion.
|
||||
type ServerBoundAdapter interface{ ServerBinding() string }
|
||||
|
||||
type HandlerAvailability struct {
|
||||
BoundServerID string
|
||||
ServerVersion string
|
||||
@@ -28,10 +34,11 @@ type HandlerAvailability struct {
|
||||
}
|
||||
type CommandHandler func(context.Context, map[string]any) (map[string]any, error)
|
||||
type HandlerRegistry struct {
|
||||
availability HandlerAvailability
|
||||
handlers map[string]CommandHandler
|
||||
mu sync.Mutex
|
||||
completed map[string]CommandResult
|
||||
availability HandlerAvailability
|
||||
handlers map[string]CommandHandler
|
||||
adapterServer string
|
||||
mu sync.Mutex
|
||||
completed map[string]CommandResult
|
||||
}
|
||||
|
||||
func NewHandlerRegistry(availability HandlerAvailability, adapter SafeAdapter) *HandlerRegistry {
|
||||
@@ -39,6 +46,9 @@ func NewHandlerRegistry(availability HandlerAvailability, adapter SafeAdapter) *
|
||||
if adapter == nil {
|
||||
return registry
|
||||
}
|
||||
if bound, ok := adapter.(ServerBoundAdapter); ok {
|
||||
registry.adapterServer = bound.ServerBinding()
|
||||
}
|
||||
registry.handlers["config.read"] = func(ctx context.Context, _ map[string]any) (map[string]any, error) {
|
||||
return adapter.ReadConfiguration(ctx)
|
||||
}
|
||||
@@ -71,7 +81,7 @@ func (registry *HandlerRegistry) Execute(ctx context.Context, command ClaimedCom
|
||||
if err := validateDeclaredCommandAt(command, time.Now); err != nil {
|
||||
return unsupportedResult("validation-failed"), nil
|
||||
}
|
||||
if strings.TrimSpace(registry.availability.BoundServerID) == "" || !registry.availability.Approved || !registry.availability.Capabilities[command.CommandType] {
|
||||
if strings.TrimSpace(registry.availability.BoundServerID) == "" || !registry.adapterBindingMatches() || !registry.availability.Approved || !registry.availability.Capabilities[command.CommandType] {
|
||||
return unsupportedResult("unsupported"), nil
|
||||
}
|
||||
handler, exists := registry.handlers[command.CommandType]
|
||||
@@ -80,6 +90,9 @@ func (registry *HandlerRegistry) Execute(ctx context.Context, command ClaimedCom
|
||||
}
|
||||
payload, err := handler(ctx, command.Payload)
|
||||
if err != nil {
|
||||
if errors.Is(err, errAdapterUnsupported) {
|
||||
return unsupportedResult("unsupported"), nil
|
||||
}
|
||||
return CommandResult{Status: "failed", Summary: "typed adapter failed", Payload: map[string]any{"result": "failed"}}, nil
|
||||
}
|
||||
result := CommandResult{Status: "succeeded", Summary: "typed adapter completed", Payload: redactTypedPayload(payload)}
|
||||
@@ -89,6 +102,10 @@ func (registry *HandlerRegistry) Execute(ctx context.Context, command ClaimedCom
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (registry *HandlerRegistry) adapterBindingMatches() bool {
|
||||
return registry.adapterServer == "" || registry.adapterServer == registry.availability.BoundServerID
|
||||
}
|
||||
|
||||
func validateDeclaredCommandAt(command ClaimedCommand, now func() time.Time) error {
|
||||
if command.ID == "" || command.ProfileKey != ProfileKey || command.FencingToken == 0 || command.Payload == nil || command.LeaseExpiresAt.IsZero() || command.ExpiresAt.IsZero() || !now().Before(command.LeaseExpiresAt) || !now().Before(command.ExpiresAt) {
|
||||
return fmt.Errorf("invalid command")
|
||||
|
||||
Reference in New Issue
Block a user