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)) } // AssignmentParticipant is the durable roster row the allocator turns into one // signed join authorisation. It lives here rather than in the store so the // allocator can consume it through an interface without depending on the // persistence package. type AssignmentParticipant struct { PlayerID string SteamID string Slot int Team int }