package checker import ( "context" "fmt" "strings" sdk "git.happydns.org/checker-sdk-go/checker" ) // Rule returns the single aggregated STUN/TURN check rule. func Rule() sdk.CheckRule { return &stunTurnRule{} } type stunTurnRule struct{} func (r *stunTurnRule) Name() string { return "stun_turn" } func (r *stunTurnRule) Description() string { return "Validates STUN binding and TURN allocation against the configured server(s)." } func (r *stunTurnRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState { var data StunTurnData if err := obs.Get(ctx, ObservationKeyStunTurn, &data); err != nil { return []sdk.CheckState{{ Status: sdk.StatusError, Message: fmt.Sprintf("failed to get STUN/TURN observation: %v", err), Code: "stun_turn_obs_error", }} } if data.GlobalError != "" { return []sdk.CheckState{{ Status: sdk.StatusError, Message: data.GlobalError, Code: "stun_turn_discovery_error", }} } if len(data.Endpoints) == 0 { return []sdk.CheckState{{ Status: sdk.StatusError, Message: "no endpoints to probe", Code: "stun_turn_no_endpoints", }} } worst := SubTestOK var firstFailLine string for _, ep := range data.Endpoints { w := ep.Worst() if statusRank(w) > statusRank(worst) { worst = w } if firstFailLine == "" { if f := ep.FirstFailure(); f != nil { parts := []string{ fmt.Sprintf("[%s] %s", ep.Endpoint.URI, f.Name), } if f.Detail != "" { parts = append(parts, f.Detail) } if f.Error != "" { parts = append(parts, f.Error) } firstFailLine = strings.Join(parts, ": ") if f.Fix != "" { firstFailLine += ". Fix: " + f.Fix } } } } state := sdk.CheckState{ Status: toSDKStatus(worst), Code: fmt.Sprintf("stun_turn_%s", worst), Meta: map[string]any{ "endpoint_count": len(data.Endpoints), }, } if firstFailLine != "" { state.Message = firstFailLine } else { state.Message = fmt.Sprintf("All %d endpoint(s) healthy", len(data.Endpoints)) } return []sdk.CheckState{state} }