Files
CosmicClash/server/domain/probes.go
T
2026-08-31 20:29:11 +01:00

111 lines
3.0 KiB
Go

package domain
import (
"crypto/subtle"
"errors"
"fmt"
"time"
)
const (
ProbeFreshness = 30 * time.Second
ProbeFutureSkew = 5 * time.Second
MaxOpaqueLocationBytes = 512
DiscrepancyWindow = 24 * time.Hour
DiscrepancyLimit = 3
CleanSamplesToRelease = 5
)
var (
ErrInvalidProbe = errors.New("invalid latency probe evidence")
ErrProbeQuarantined = errors.New("latency samples are quarantined")
)
// ProbeEvidence deliberately treats Steam's location as opaque. The backend
// validates freshness/nonce and computes RTT from its own receive timestamps;
// no client-provided RTT is used for placement.
type ProbeEvidence struct {
OpaqueLocation []byte
Nonce []byte
IssuedAt time.Time
Region string
ServerRTT time.Duration
}
func ValidateProbe(evidence ProbeEvidence, expectedNonce []byte, now time.Time) error {
if len(evidence.OpaqueLocation) == 0 || len(evidence.OpaqueLocation) > MaxOpaqueLocationBytes || len(expectedNonce) == 0 {
return ErrInvalidProbe
}
if len(evidence.Nonce) != len(expectedNonce) || subtle.ConstantTimeCompare(evidence.Nonce, expectedNonce) != 1 {
return fmt.Errorf("%w: nonce mismatch", ErrInvalidProbe)
}
if evidence.IssuedAt.After(now.Add(ProbeFutureSkew)) || now.Sub(evidence.IssuedAt) > ProbeFreshness {
return fmt.Errorf("%w: stale or future timestamp", ErrInvalidProbe)
}
if evidence.Region != "EU" && evidence.Region != "NA" {
return fmt.Errorf("%w: unsupported region", ErrInvalidProbe)
}
if evidence.ServerRTT < 0 {
return fmt.Errorf("%w: negative RTT", ErrInvalidProbe)
}
return nil
}
type DiscrepancyTracker struct {
BadSamples []time.Time
CleanSamples int
Quarantined bool
}
// RecordComparison compares backend-computed predicted and observed RTT. A
// discrepancy is over 25 ms or 30% (whichever is larger). Three bad samples
// in 24 hours quarantine placement evidence; five clean samples release it.
func (tracker *DiscrepancyTracker) RecordComparison(predicted, observed time.Duration, now time.Time) {
tracker.prune(now)
maxAllowed := 25 * time.Millisecond
if predicted > 0 {
percent := time.Duration(float64(predicted) * 0.30)
if percent > maxAllowed {
maxAllowed = percent
}
}
delta := predicted - observed
if delta < 0 {
delta = -delta
}
if delta > maxAllowed {
tracker.BadSamples = append(tracker.BadSamples, now)
tracker.CleanSamples = 0
if len(tracker.BadSamples) >= DiscrepancyLimit {
tracker.Quarantined = true
}
return
}
if tracker.Quarantined {
tracker.CleanSamples++
if tracker.CleanSamples >= CleanSamplesToRelease {
tracker.Quarantined = false
tracker.BadSamples = nil
tracker.CleanSamples = 0
}
}
}
func (tracker *DiscrepancyTracker) prune(now time.Time) {
cutoff := now.Add(-DiscrepancyWindow)
kept := tracker.BadSamples[:0]
for _, sample := range tracker.BadSamples {
if !sample.Before(cutoff) {
kept = append(kept, sample)
}
}
tracker.BadSamples = kept
}
func (tracker DiscrepancyTracker) PlacementAllowed() error {
if tracker.Quarantined {
return ErrProbeQuarantined
}
return nil
}