feat(multiplayer): deliver allocated server rosters

This commit is contained in:
Josh Creek
2026-09-01 15:54:07 +01:00
parent 863cf61f1a
commit 68e76b5feb
10 changed files with 267 additions and 22 deletions
+45
View File
@@ -69,6 +69,11 @@ const AssignmentSelectSQL = `SELECT match_id, player_id, allocation_id, server_i
FROM assignments
WHERE match_id = $1 AND player_id = $2 AND expires_at > $3`
const AssignmentRosterSelectSQL = `SELECT allocation_id, server_id, join_authorisation
FROM assignments
WHERE match_id = $1 AND server_id = $2 AND expires_at > $3
ORDER BY slot, player_id`
func validateDurableAssignment(assignment DurableAssignment) error {
if assignment.MatchID == "" || assignment.PlayerID == "" || assignment.AllocationID == "" || assignment.ServerID == "" || assignment.Slot < 0 || assignment.Slot > 5 || (assignment.Region != "EU" && assignment.Region != "NA") || assignment.ClientBuild == "" || assignment.ProtocolVersion < 1 || (assignment.Transport != "enet" && assignment.Transport != "steam_sdr") || assignment.Endpoint == "" || assignment.JoinAuthorisation == "" || len(assignment.ManifestDigest) == 0 || assignment.ExpiresAt.IsZero() || assignment.Revision < 0 {
return fmt.Errorf("invalid durable assignment")
@@ -187,3 +192,43 @@ func GetAssignment(ctx context.Context, db *sql.DB, playerID, matchID string, no
}
return assignment, nil
}
// GetAssignmentRoster returns the complete signed roster for an allocated
// server. It is intentionally server-scoped rather than player-scoped and is
// called only after workload authentication at the API boundary. All rows
// must belong to one allocation; a partial or mixed allocation is unsafe to
// hand to the game process.
func GetAssignmentRoster(ctx context.Context, db *sql.DB, matchID, serverID string, now time.Time) ([][]byte, error) {
if db == nil || matchID == "" || serverID == "" || now.IsZero() {
return nil, fmt.Errorf("invalid assignment roster arguments")
}
rows, err := db.QueryContext(ctx, AssignmentRosterSelectSQL, matchID, serverID, now)
if err != nil {
return nil, err
}
defer rows.Close()
var allocationID string
var roster [][]byte
for rows.Next() {
var rowAllocation, rowServer, encoded string
if err := rows.Scan(&rowAllocation, &rowServer, &encoded); err != nil {
return nil, err
}
if rowServer != serverID || rowAllocation == "" || (allocationID != "" && allocationID != rowAllocation) {
return nil, fmt.Errorf("assignment roster contains mixed allocation")
}
decoded, err := base64.RawURLEncoding.DecodeString(encoded)
if err != nil || len(decoded) == 0 {
return nil, fmt.Errorf("assignment roster contains invalid envelope")
}
allocationID = rowAllocation
roster = append(roster, decoded)
}
if err := rows.Err(); err != nil {
return nil, err
}
if len(roster) == 0 {
return nil, sql.ErrNoRows
}
return roster, nil
}