mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 08:23:45 +00:00
feat: validate queue compatibility metadata
This commit is contained in:
+27
-5
@@ -19,12 +19,14 @@ import (
|
||||
const maxBodyBytes = 8 << 10
|
||||
|
||||
type CandidateProvider func(playerID, ticketID string) (domain.Candidate, error)
|
||||
type CandidateProviderV2 func(playerID, ticketID string, spec domain.QueueSpec) (domain.Candidate, error)
|
||||
type ProbeProvider func(playerID, region string, opaqueLocation, nonce []byte, receivedAt time.Time) (domain.ProbeEvidence, []byte, error)
|
||||
|
||||
type Service struct {
|
||||
Sessions *domain.SessionStore
|
||||
Queue *domain.Queue
|
||||
Candidate CandidateProvider
|
||||
CandidateV2 CandidateProviderV2
|
||||
Probe ProbeProvider
|
||||
Now func() time.Time
|
||||
Proposals map[string]*domain.Proposal
|
||||
@@ -49,7 +51,10 @@ func (s *Service) health(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
|
||||
type queueCreateRequest struct {
|
||||
TicketID string `json:"ticket_id"`
|
||||
TicketID string `json:"ticket_id"`
|
||||
Playlist string `json:"playlist"`
|
||||
ClientBuild string `json:"client_build"`
|
||||
ProtocolVersion int `json:"protocol_version"`
|
||||
}
|
||||
type queueResponse struct {
|
||||
TicketID string `json:"ticket_id"`
|
||||
@@ -58,6 +63,7 @@ type queueResponse struct {
|
||||
Revision uint64 `json:"revision"`
|
||||
EnqueuedAt time.Time `json:"enqueued_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
Playlist string `json:"playlist"`
|
||||
}
|
||||
|
||||
func (s *Service) queueCreate(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -69,7 +75,7 @@ func (s *Service) queueCreate(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if s.Queue == nil || s.Candidate == nil {
|
||||
if s.Queue == nil || (s.Candidate == nil && s.CandidateV2 == nil) {
|
||||
writeError(w, http.StatusServiceUnavailable, "queue_unavailable")
|
||||
return
|
||||
}
|
||||
@@ -77,7 +83,7 @@ func (s *Service) queueCreate(w http.ResponseWriter, r *http.Request) {
|
||||
if !decodeBody(w, r, &input) {
|
||||
return
|
||||
}
|
||||
if input.TicketID == "" {
|
||||
if input.TicketID == "" || (input.Playlist != string(domain.Casual) && input.Playlist != string(domain.Ranked)) || input.ClientBuild == "" || len(input.ClientBuild) > 128 || input.ProtocolVersion < 1 {
|
||||
writeError(w, http.StatusBadRequest, "invalid_request")
|
||||
return
|
||||
}
|
||||
@@ -87,11 +93,27 @@ func (s *Service) queueCreate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
now := s.now()
|
||||
candidate, err := s.Candidate(playerID, input.TicketID)
|
||||
spec := domain.QueueSpec{Playlist: domain.Playlist(input.Playlist), ClientBuild: input.ClientBuild, ProtocolVersion: input.ProtocolVersion}
|
||||
var candidate domain.Candidate
|
||||
var err error
|
||||
if s.CandidateV2 != nil {
|
||||
candidate, err = s.CandidateV2(playerID, input.TicketID, spec)
|
||||
} else {
|
||||
candidate, err = s.Candidate(playerID, input.TicketID)
|
||||
// Legacy providers predate queue compatibility metadata. The API has
|
||||
// validated the request; keep the resulting projection self-describing.
|
||||
candidate.Playlist = spec.Playlist
|
||||
candidate.ClientBuild = spec.ClientBuild
|
||||
candidate.ProtocolVersion = spec.ProtocolVersion
|
||||
}
|
||||
if err != nil {
|
||||
writeError(w, http.StatusUnprocessableEntity, "candidate_unavailable")
|
||||
return
|
||||
}
|
||||
if candidate.PlayerID != playerID || candidate.TicketID != input.TicketID || candidate.Playlist != spec.Playlist || candidate.ClientBuild != spec.ClientBuild || candidate.ProtocolVersion != spec.ProtocolVersion {
|
||||
writeError(w, http.StatusUnprocessableEntity, "candidate_mismatch")
|
||||
return
|
||||
}
|
||||
ticket, err := s.Queue.Create(playerID, input.TicketID, key, candidate, now)
|
||||
if err != nil {
|
||||
writeDomainError(w, err)
|
||||
@@ -322,7 +344,7 @@ func decodeBody(w http.ResponseWriter, r *http.Request, target any) bool {
|
||||
}
|
||||
|
||||
func toQueueResponse(ticket domain.QueueTicket) queueResponse {
|
||||
return queueResponse{TicketID: ticket.TicketID, PlayerID: ticket.PlayerID, State: string(ticket.State), Revision: ticket.Revision, EnqueuedAt: ticket.EnqueuedAt, ExpiresAt: ticket.ExpiresAt}
|
||||
return queueResponse{TicketID: ticket.TicketID, PlayerID: ticket.PlayerID, Playlist: string(ticket.Playlist), State: string(ticket.State), Revision: ticket.Revision, EnqueuedAt: ticket.EnqueuedAt, ExpiresAt: ticket.ExpiresAt}
|
||||
}
|
||||
|
||||
func toProposalResponse(proposal domain.Proposal) proposalResponse {
|
||||
|
||||
Reference in New Issue
Block a user