mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 20:13:43 +00:00
feat: add assignment readiness gate
This commit is contained in:
@@ -37,6 +37,10 @@ type Allocation struct {
|
||||
AllocationID string
|
||||
MatchID string
|
||||
ServerID string
|
||||
Region string
|
||||
Build string
|
||||
Protocol int
|
||||
Transport string
|
||||
State ServerLifecycle
|
||||
AllocatedAt time.Time
|
||||
}
|
||||
@@ -96,7 +100,7 @@ func (a *Allocator) Allocate(request AllocationRequest, now time.Time) (Allocati
|
||||
server := a.servers[ids[0]]
|
||||
server.State = ServerAllocated
|
||||
a.servers[server.ServerID] = server
|
||||
allocation := Allocation{AllocationID: request.AllocationID, MatchID: request.MatchID, ServerID: server.ServerID, State: ServerAllocated, AllocatedAt: now}
|
||||
allocation := Allocation{AllocationID: request.AllocationID, MatchID: request.MatchID, ServerID: server.ServerID, Region: server.Region, Build: server.Build, Protocol: server.Protocol, Transport: server.Transport, State: ServerAllocated, AllocatedAt: now}
|
||||
a.allocations[request.AllocationID] = allocation
|
||||
a.requestHashes[request.AllocationID] = digest
|
||||
return allocation, nil
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type AllocationManifest struct {
|
||||
AllocationID string
|
||||
MatchID string
|
||||
ServerID string
|
||||
Region string
|
||||
Build string
|
||||
Protocol int
|
||||
Transport string
|
||||
RosterDigest string
|
||||
}
|
||||
|
||||
type Assignment struct {
|
||||
Allocation Allocation
|
||||
Manifest AllocationManifest
|
||||
Endpoint string
|
||||
}
|
||||
|
||||
var ErrManifestRejected = fmt.Errorf("allocation manifest rejected")
|
||||
|
||||
// VerifyAssignment is the assignment-ready gate. A Ready/Allocated process
|
||||
// has no client-facing endpoint until its signed manifest, allocator binding,
|
||||
// and hosted endpoint all pass this check.
|
||||
func VerifyAssignment(allocation Allocation, manifest AllocationManifest, endpoint string, signature []byte, verify func([]byte, []byte) bool) (Assignment, error) {
|
||||
if allocation.State != ServerAllocated || allocation.AllocationID == "" || allocation.MatchID == "" || allocation.ServerID == "" || endpoint == "" || len(signature) == 0 || verify == nil {
|
||||
return Assignment{}, ErrManifestRejected
|
||||
}
|
||||
if manifest.AllocationID != allocation.AllocationID || manifest.MatchID != allocation.MatchID || manifest.ServerID != allocation.ServerID || manifest.Region != allocation.Region || manifest.Build != allocation.Build || manifest.Protocol != allocation.Protocol || manifest.Transport != allocation.Transport || manifest.RosterDigest == "" {
|
||||
return Assignment{}, ErrManifestRejected
|
||||
}
|
||||
if !verify(manifestBytes(manifest), signature) {
|
||||
return Assignment{}, ErrManifestRejected
|
||||
}
|
||||
return Assignment{Allocation: allocation, Manifest: manifest, Endpoint: endpoint}, nil
|
||||
}
|
||||
|
||||
func manifestBytes(manifest AllocationManifest) []byte {
|
||||
canonical := fmt.Sprintf("%s\x00%s\x00%s\x00%s\x00%s\x00%d\x00%s\x00%s", manifest.AllocationID, manifest.MatchID, manifest.ServerID, manifest.Region, manifest.Build, manifest.Protocol, manifest.Transport, manifest.RosterDigest)
|
||||
return []byte(canonical)
|
||||
}
|
||||
|
||||
func ManifestDigest(manifest AllocationManifest) [32]byte {
|
||||
return sha256.Sum256(manifestBytes(manifest))
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func testAllocation() Allocation {
|
||||
return Allocation{AllocationID: "allocation-1", MatchID: "match-1", ServerID: "server-1", Region: "EU", Build: "build-1", Protocol: 1, Transport: "enet", State: ServerAllocated, AllocatedAt: time.Unix(1000, 0)}
|
||||
}
|
||||
|
||||
func testManifest() AllocationManifest {
|
||||
return AllocationManifest{AllocationID: "allocation-1", MatchID: "match-1", ServerID: "server-1", Region: "EU", Build: "build-1", Protocol: 1, Transport: "enet", RosterDigest: "roster-digest"}
|
||||
}
|
||||
|
||||
func TestAssignmentReadyRequiresBoundSignedManifestAndEndpoint(t *testing.T) {
|
||||
manifest := testManifest()
|
||||
digest := ManifestDigest(manifest)
|
||||
sign := func(payload, signature []byte) bool {
|
||||
return string(payload) == string(manifestBytes(manifest)) && string(signature) == string(digest[:])
|
||||
}
|
||||
assignment, err := VerifyAssignment(testAllocation(), manifest, "203.0.113.9:31001", digest[:], sign)
|
||||
if err != nil || assignment.Endpoint == "" {
|
||||
t.Fatalf("assignment = %+v err=%v", assignment, err)
|
||||
}
|
||||
if _, err := VerifyAssignment(testAllocation(), manifest, "", digest[:], sign); !errors.Is(err, ErrManifestRejected) {
|
||||
t.Fatalf("empty endpoint accepted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignmentReadyRejectsTamperedOrPrematureManifest(t *testing.T) {
|
||||
manifest := testManifest()
|
||||
digest := ManifestDigest(manifest)
|
||||
verify := func(payload, signature []byte) bool {
|
||||
return string(payload) == string(manifestBytes(manifest)) && string(signature) == string(digest[:])
|
||||
}
|
||||
tampered := manifest
|
||||
tampered.ServerID = "server-2"
|
||||
if _, err := VerifyAssignment(testAllocation(), tampered, "127.0.0.1:1", digest[:], verify); !errors.Is(err, ErrManifestRejected) {
|
||||
t.Fatalf("tampered manifest accepted: %v", err)
|
||||
}
|
||||
ready := testAllocation()
|
||||
ready.State = ServerReady
|
||||
if _, err := VerifyAssignment(ready, manifest, "127.0.0.1:1", digest[:], verify); !errors.Is(err, ErrManifestRejected) {
|
||||
t.Fatalf("Ready process exposed assignment: %v", err)
|
||||
}
|
||||
wrongBuild := manifest
|
||||
wrongBuild.Build = "build-2"
|
||||
if _, err := VerifyAssignment(testAllocation(), wrongBuild, "127.0.0.1:1", digest[:], verify); !errors.Is(err, ErrManifestRejected) {
|
||||
t.Fatalf("incompatible build accepted: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user