package api import ( "bufio" "bytes" "context" "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "io" "log" "net/http" "net/url" "reflect" "strconv" "strings" "sync/atomic" "time" "browser.local/run/protocol" ) type PlatformClient struct { baseURL string httpClient *http.Client serverClockOffsetNanos *atomic.Int64 serverClockOffsetActive *atomic.Bool } const ( runSessionTokenHeader = "X-Run-Session-Token" artifactTransferIDHeader = "X-Artifact-Transfer-Id" artifactIDHeader = "X-Artifact-Id" artifactChunkIndexHeader = "X-Artifact-Chunk-Index" artifactChunkOffsetHeader = "X-Artifact-Offset" artifactChunkSizeHeader = "X-Artifact-Size" artifactChunkHashHeader = "X-Artifact-Checksum" ) type PlatformRequestError struct { Status int Path string Code string Details []string } func (err PlatformRequestError) Error() string { parts := []string{fmt.Sprintf("status=%d", err.Status)} if strings.TrimSpace(err.Path) != "" { parts = append(parts, "path="+strings.TrimSpace(err.Path)) } if strings.TrimSpace(err.Code) != "" { parts = append(parts, "code="+strings.TrimSpace(err.Code)) } if len(err.Details) > 0 { parts = append(parts, "details="+strings.Join(err.Details, "; ")) } return "platform request failed: " + strings.Join(parts, " ") } func (err PlatformRequestError) HTTPStatus() int { return err.Status } // SessionInvalid reports the one legacy validation response that means a Run // session must be renewed. Component-authenticated sessions return 401 for the // same condition; older endpoint sessions return this safe 400 response. func (err PlatformRequestError) SessionInvalid() bool { if err.Status == http.StatusUnauthorized { return true } if err.Status != http.StatusBadRequest || err.Code != "validation_failed" { return false } for _, detail := range err.Details { if strings.TrimSpace(detail) == "sessionToken is invalid" { return true } } return false } func (err PlatformRequestError) LogBatchSequenceGap() bool { if err.Status != http.StatusBadRequest || err.Code != "validation_failed" { return false } for _, detail := range err.Details { if strings.TrimSpace(detail) == "log batch firstSeq must follow latest acknowledged sequence" { return true } } return false } func (err PlatformRequestError) LogBatchAcknowledgedRangeConflict() bool { if err.Status != http.StatusBadRequest || err.Code != "validation_failed" { return false } for _, detail := range err.Details { if strings.TrimSpace(detail) == "log batch conflicts with acknowledged range" { return true } } return false } func (err PlatformRequestError) LogBatchLegacySessionMetadata() bool { if err.Status != http.StatusBadRequest || err.Code != "validation_failed" { return false } for _, detail := range err.Details { if strings.TrimSpace(detail) == "logSessionId and sessionStartedAt must be provided together" { return true } } return false } func (err PlatformRequestError) LogBatchSessionMetadataMismatch() bool { if err.Status != http.StatusBadRequest || err.Code != "validation_failed" { return false } for _, detail := range err.Details { if strings.TrimSpace(detail) == "log session metadata must match stream" { return true } } return false } func NewPlatformClient(rawURL string) (PlatformClient, error) { return NewPlatformClientWithHTTPClient(rawURL, http.DefaultClient) } func NewPlatformClientWithHTTPClient(rawURL string, httpClient *http.Client) (PlatformClient, error) { parsed, err := url.Parse(rawURL) if err != nil { return PlatformClient{}, err } if parsed.Scheme == "" || parsed.Host == "" { return PlatformClient{}, fmt.Errorf("platform URL must include scheme and host") } if httpClient == nil { httpClient = http.DefaultClient } return PlatformClient{baseURL: strings.TrimRight(parsed.String(), "/"), httpClient: httpClient, serverClockOffsetNanos: &atomic.Int64{}, serverClockOffsetActive: &atomic.Bool{}}, nil } func (c PlatformClient) BaseURL() string { return c.baseURL } func (c PlatformClient) Hello(ctx context.Context, request protocol.RunHelloRequest) (protocol.RunHelloResponse, error) { return postPlatformJSON[protocol.RunHelloRequest, protocol.RunHelloResponse](ctx, c, "/api/v1/run/control/hello", request) } func (c PlatformClient) Heartbeat(ctx context.Context, request protocol.RunHeartbeatRequest) (protocol.RunHeartbeatResponse, error) { return postPlatformJSON[protocol.RunHeartbeatRequest, protocol.RunHeartbeatResponse](ctx, c, "/api/v1/run/control/heartbeat", request) } func (c PlatformClient) StreamControlEvents(ctx context.Context, request protocol.RunControlStreamRequest, handle func(protocol.RunControlEvent) error) error { if handle == nil { return fmt.Errorf("control event handler is required") } startedAt := time.Now() path := "/api/v1/run/control/events" log.Printf("RUN platform stream status=starting method=POST base=%s path=%s", diagnosticLogValue(c.baseURL), path) var body bytes.Buffer if err := json.NewEncoder(&body).Encode(request); err != nil { return fmt.Errorf("encode control stream request: %w", err) } httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, &body) if err != nil { return fmt.Errorf("build control stream request: %w", err) } httpRequest.Header.Set("Content-Type", "application/json") httpRequest.Header.Set("Accept", "text/event-stream") signatureSummary, err := signRunRequest(httpRequest, body.Bytes(), c.signatureTime()) if err != nil { return err } log.Printf("RUN platform stream status=signed method=POST base=%s path=%s endpoint=%s timestamp=%s nonce=%s bodyHash=%s signature=%s", diagnosticLogValue(c.baseURL), path, diagnosticLogValue(signatureSummary.RunEndpointID), signatureSummary.Timestamp, shortDiagnosticValue(signatureSummary.Nonce), shortDiagnosticValue(signatureSummary.BodyHash), shortDiagnosticValue(signatureSummary.Signature)) httpResponse, err := c.httpClient.Do(httpRequest) if err != nil { log.Printf("RUN platform stream status=send_error method=POST base=%s path=%s durationMs=%d error=%s", diagnosticLogValue(c.baseURL), path, time.Since(startedAt).Milliseconds(), err) return fmt.Errorf("send control stream request: %w", err) } defer httpResponse.Body.Close() log.Printf("RUN platform stream status=response method=POST base=%s path=%s httpStatus=%d durationMs=%d", diagnosticLogValue(c.baseURL), path, httpResponse.StatusCode, time.Since(startedAt).Milliseconds()) if httpResponse.StatusCode < http.StatusOK || httpResponse.StatusCode >= http.StatusMultipleChoices { var failure struct { Code string `json:"code"` Details []string `json:"details"` } _ = json.NewDecoder(io.LimitReader(httpResponse.Body, 64<<10)).Decode(&failure) return PlatformRequestError{Status: httpResponse.StatusCode, Path: path, Code: failure.Code, Details: failure.Details} } reader := bufio.NewReader(httpResponse.Body) var eventName string var dataLines []string for { line, err := reader.ReadString('\n') if err != nil && len(line) == 0 { if errors.Is(err, io.EOF) || ctx.Err() != nil { return ctx.Err() } return fmt.Errorf("read control stream: %w", err) } line = strings.TrimRight(line, "\r\n") if line == "" { if len(dataLines) > 0 { var event protocol.RunControlEvent if err := json.Unmarshal([]byte(strings.Join(dataLines, "\n")), &event); err != nil { return fmt.Errorf("decode control event: %w", err) } if event.Type == "" { event.Type = eventName } if err := handle(event); err != nil { return err } } eventName = "" dataLines = nil } else if strings.HasPrefix(line, "event:") { eventName = strings.TrimSpace(strings.TrimPrefix(line, "event:")) } else if strings.HasPrefix(line, "data:") { dataLines = append(dataLines, strings.TrimSpace(strings.TrimPrefix(line, "data:"))) } if err != nil { if errors.Is(err, io.EOF) || ctx.Err() != nil { return ctx.Err() } return fmt.Errorf("read control stream: %w", err) } } } func (c PlatformClient) ReportLifecycle(ctx context.Context, request protocol.RunLifecycleReportRequest) (protocol.RunLifecycleReportResponse, error) { return postPlatformJSON[protocol.RunLifecycleReportRequest, protocol.RunLifecycleReportResponse](ctx, c, "/api/v1/run/lifecycle/report", request) } func (c PlatformClient) ClaimJob(ctx context.Context, request protocol.RunJobClaimRequest) (protocol.RunJobClaimResponse, error) { response, err := postPlatformJSON[protocol.RunJobClaimRequest, protocol.RunJobClaimResponse](ctx, c, "/api/v1/run/jobs/claim", request) if request.WaitSeconds > 0 && claimWaitUnsupported(err) { request.WaitSeconds = 0 return postPlatformJSON[protocol.RunJobClaimRequest, protocol.RunJobClaimResponse](ctx, c, "/api/v1/run/jobs/claim", request) } return response, err } func claimWaitUnsupported(err error) bool { var requestErr PlatformRequestError if !errors.As(err, &requestErr) || requestErr.Status != http.StatusBadRequest || requestErr.Code != "bad_request" { return false } for _, detail := range requestErr.Details { if strings.Contains(detail, "waitSeconds") { return true } } return false } func (c PlatformClient) AckJob(ctx context.Context, request protocol.RunJobAckRequest) (protocol.RunJobAckResponse, error) { return postPlatformJSON[protocol.RunJobAckRequest, protocol.RunJobAckResponse](ctx, c, "/api/v1/run/jobs/ack", request) } func (c PlatformClient) UpdateJobProgress(ctx context.Context, request protocol.RunJobProgressRequest) (protocol.RunJobProgressResponse, error) { return postPlatformJSON[protocol.RunJobProgressRequest, protocol.RunJobProgressResponse](ctx, c, "/api/v1/run/jobs/progress", request) } func (c PlatformClient) CompleteJob(ctx context.Context, request protocol.RunJobResultRequest) (protocol.RunJobResultResponse, error) { return postPlatformJSON[protocol.RunJobResultRequest, protocol.RunJobResultResponse](ctx, c, "/api/v1/run/jobs/result", request) } func (c PlatformClient) GetDistributionBuildInput(ctx context.Context, request protocol.DistributionBuildInputRequest) (protocol.DistributionBuildInputResponse, error) { return postPlatformJSON[protocol.DistributionBuildInputRequest, protocol.DistributionBuildInputResponse](ctx, c, "/api/v1/run/jobs/build-input", request) } func (c PlatformClient) GetDependencyExecutionInput(ctx context.Context, request protocol.DependencyExecutionInputRequest) (protocol.DependencyExecutionInputResponse, error) { return postPlatformJSON[protocol.DependencyExecutionInputRequest, protocol.DependencyExecutionInputResponse](ctx, c, "/api/v1/run/jobs/dependency-input", request) } func (c PlatformClient) GetSourceRCONExecutionInput(ctx context.Context, request protocol.SourceRCONExecutionInputRequest) (protocol.SourceRCONExecutionInputResponse, error) { return postPlatformJSON[protocol.SourceRCONExecutionInputRequest, protocol.SourceRCONExecutionInputResponse](ctx, c, "/api/v1/run/jobs/source-rcon-input", request) } func (c PlatformClient) GetProtectedRequestExecutionInput(ctx context.Context, request protocol.ProtectedRequestExecutionInputRequest) (protocol.ProtectedRequestExecutionInputResponse, error) { return postPlatformJSON[protocol.ProtectedRequestExecutionInputRequest, protocol.ProtectedRequestExecutionInputResponse](ctx, c, "/api/v1/run/jobs/protected-request-input", request) } func (c PlatformClient) GetRunUpdateInput(ctx context.Context, request protocol.RunUpdateInputRequest) (protocol.RunUpdateInputResponse, error) { return postPlatformJSON[protocol.RunUpdateInputRequest, protocol.RunUpdateInputResponse](ctx, c, "/api/v1/run/jobs/update-input", request) } func (c PlatformClient) ReadRunUpdateChunk(ctx context.Context, request protocol.RunUpdateChunkRequest) (protocol.RunUpdateChunkResponse, error) { return postPlatformJSON[protocol.RunUpdateChunkRequest, protocol.RunUpdateChunkResponse](ctx, c, "/api/v1/run/jobs/update-chunk", request) } func (c PlatformClient) ReportRunUpdateHealth(ctx context.Context, request protocol.RunUpdateHealthRequest) (protocol.RunUpdateHealthResponse, error) { return postPlatformJSON[protocol.RunUpdateHealthRequest, protocol.RunUpdateHealthResponse](ctx, c, "/api/v1/run/jobs/update-health", request) } func (c PlatformClient) PollJobCancel(ctx context.Context, request protocol.RunJobCancelPollRequest) (protocol.RunJobCancelPollResponse, error) { return postPlatformJSON[protocol.RunJobCancelPollRequest, protocol.RunJobCancelPollResponse](ctx, c, "/api/v1/run/jobs/cancel", request) } func (c PlatformClient) ReconcileJobs(ctx context.Context, request protocol.RunJobReconcileRequest) (protocol.RunJobReconcileResponse, error) { return postPlatformJSON[protocol.RunJobReconcileRequest, protocol.RunJobReconcileResponse](ctx, c, "/api/v1/run/jobs/reconcile", request) } func (c PlatformClient) IngestLogBatch(ctx context.Context, request protocol.LogBatchIngestRequest) (protocol.LogBatchIngestResponse, error) { return postPlatformJSON[protocol.LogBatchIngestRequest, protocol.LogBatchIngestResponse](ctx, c, "/api/v1/run/logs/batches", request) } func (c PlatformClient) RelayLiveLogBatch(ctx context.Context, request protocol.LogBatchIngestRequest) (protocol.LogBatchIngestResponse, error) { return postPlatformJSON[protocol.LogBatchIngestRequest, protocol.LogBatchIngestResponse](ctx, c, "/api/v1/run/logs/relay", request) } func (c PlatformClient) GetRunLogStreamProgress(ctx context.Context, request protocol.RunLogStreamProgressRequest) (protocol.RunLogStreamProgressResponse, error) { return postPlatformJSON[protocol.RunLogStreamProgressRequest, protocol.RunLogStreamProgressResponse](ctx, c, "/api/v1/run/logs/progress", request) } func (c PlatformClient) IngestMetricBatch(ctx context.Context, request protocol.MetricBatchIngestRequest) (protocol.MetricBatchIngestResponse, error) { return postPlatformJSON[protocol.MetricBatchIngestRequest, protocol.MetricBatchIngestResponse](ctx, c, "/api/v1/run/metrics/batches", request) } func (c PlatformClient) OpenArtifactTransfer(ctx context.Context, request protocol.ArtifactTransferOpenRequest) (protocol.ArtifactTransferOpenResponse, error) { return postPlatformJSON[protocol.ArtifactTransferOpenRequest, protocol.ArtifactTransferOpenResponse](ctx, c, "/api/v1/run/artifacts/open", request) } func (c PlatformClient) UploadArtifactChunk(ctx context.Context, request protocol.ArtifactChunkUploadRequest) (protocol.ArtifactChunkUploadResponse, error) { return postPlatformArtifactChunk(ctx, c, "/api/v1/run/artifacts/chunks", request) } func (c PlatformClient) QueryArtifactTransferStatus(ctx context.Context, request protocol.ArtifactTransferStatusRequest) (protocol.ArtifactTransferStatusResponse, error) { return postPlatformJSON[protocol.ArtifactTransferStatusRequest, protocol.ArtifactTransferStatusResponse](ctx, c, "/api/v1/run/artifacts/status", request) } func (c PlatformClient) CompleteArtifactTransfer(ctx context.Context, request protocol.ArtifactTransferCompleteRequest) (protocol.ArtifactTransferCompleteResponse, error) { return postPlatformJSON[protocol.ArtifactTransferCompleteRequest, protocol.ArtifactTransferCompleteResponse](ctx, c, "/api/v1/run/artifacts/complete", request) } func postPlatformArtifactChunk(ctx context.Context, client PlatformClient, path string, request protocol.ArtifactChunkUploadRequest) (protocol.ArtifactChunkUploadResponse, error) { var response protocol.ArtifactChunkUploadResponse startedAt := time.Now() log.Printf("RUN platform request status=starting method=POST base=%s path=%s", diagnosticLogValue(client.baseURL), path) body := request.Payload httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, client.baseURL+path, bytes.NewReader(body)) if err != nil { log.Printf("RUN platform request status=build_error method=POST base=%s path=%s durationMs=%d error=%s", diagnosticLogValue(client.baseURL), path, time.Since(startedAt).Milliseconds(), err) return response, fmt.Errorf("build platform request: %w", err) } httpRequest.Header.Set("Content-Type", "application/octet-stream") httpRequest.Header.Set("Accept", "application/json") httpRequest.Header.Set(runSessionTokenHeader, request.SessionToken) httpRequest.Header.Set(artifactTransferIDHeader, request.TransferID) httpRequest.Header.Set(artifactIDHeader, request.ArtifactID) httpRequest.Header.Set(artifactChunkIndexHeader, strconv.Itoa(request.ChunkIndex)) httpRequest.Header.Set(artifactChunkOffsetHeader, strconv.FormatInt(request.Offset, 10)) httpRequest.Header.Set(artifactChunkSizeHeader, strconv.Itoa(request.SizeBytes)) httpRequest.Header.Set(artifactChunkHashHeader, request.Checksum) signatureSummary, err := signRunRequestWithEnvelope(httpRequest, body, runRequestEnvelope{RunEndpointID: request.RunEndpointID, SessionToken: request.SessionToken}, client.signatureTime()) if err != nil { log.Printf("RUN platform request status=sign_error method=POST base=%s path=%s durationMs=%d error=%s", diagnosticLogValue(client.baseURL), path, time.Since(startedAt).Milliseconds(), err) return response, err } log.Printf("RUN platform request status=signed method=POST base=%s path=%s endpoint=%s timestamp=%s nonce=%s bodyHash=%s signature=%s", diagnosticLogValue(client.baseURL), path, diagnosticLogValue(signatureSummary.RunEndpointID), signatureSummary.Timestamp, shortDiagnosticValue(signatureSummary.Nonce), shortDiagnosticValue(signatureSummary.BodyHash), shortDiagnosticValue(signatureSummary.Signature)) httpResponse, err := client.httpClient.Do(httpRequest) if err != nil { log.Printf("RUN platform request status=send_error method=POST base=%s path=%s durationMs=%d error=%s", diagnosticLogValue(client.baseURL), path, time.Since(startedAt).Milliseconds(), err) return response, fmt.Errorf("send platform request: %w", err) } defer httpResponse.Body.Close() log.Printf("RUN platform request status=response method=POST base=%s path=%s httpStatus=%d durationMs=%d", diagnosticLogValue(client.baseURL), path, httpResponse.StatusCode, time.Since(startedAt).Milliseconds()) if httpResponse.StatusCode < http.StatusOK || httpResponse.StatusCode >= http.StatusMultipleChoices { var failure struct { Code string `json:"code"` Details []string `json:"details"` } _ = json.NewDecoder(io.LimitReader(httpResponse.Body, 64<<10)).Decode(&failure) return response, PlatformRequestError{Status: httpResponse.StatusCode, Path: path, Code: failure.Code, Details: failure.Details} } if err := json.NewDecoder(httpResponse.Body).Decode(&response); err != nil { return response, fmt.Errorf("decode platform response: %w", err) } client.observeResponseServerTime(response) return response, nil } func postPlatformJSON[Request any, Response any](ctx context.Context, client PlatformClient, path string, request Request) (Response, error) { var response Response startedAt := time.Now() log.Printf("RUN platform request status=starting method=POST base=%s path=%s", diagnosticLogValue(client.baseURL), path) var body bytes.Buffer if err := json.NewEncoder(&body).Encode(request); err != nil { log.Printf("RUN platform request status=encode_error method=POST base=%s path=%s durationMs=%d error=%s", diagnosticLogValue(client.baseURL), path, time.Since(startedAt).Milliseconds(), err) return response, fmt.Errorf("encode platform request: %w", err) } httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, client.baseURL+path, &body) if err != nil { log.Printf("RUN platform request status=build_error method=POST base=%s path=%s durationMs=%d error=%s", diagnosticLogValue(client.baseURL), path, time.Since(startedAt).Milliseconds(), err) return response, fmt.Errorf("build platform request: %w", err) } httpRequest.Header.Set("Content-Type", "application/json") httpRequest.Header.Set("Accept", "application/json") if path != "/api/v1/run/control/hello" { signatureSummary, err := signRunRequest(httpRequest, body.Bytes(), client.signatureTime()) if err != nil { log.Printf("RUN platform request status=sign_error method=POST base=%s path=%s durationMs=%d error=%s", diagnosticLogValue(client.baseURL), path, time.Since(startedAt).Milliseconds(), err) return response, err } log.Printf("RUN platform request status=signed method=POST base=%s path=%s endpoint=%s timestamp=%s nonce=%s bodyHash=%s signature=%s", diagnosticLogValue(client.baseURL), path, diagnosticLogValue(signatureSummary.RunEndpointID), signatureSummary.Timestamp, shortDiagnosticValue(signatureSummary.Nonce), shortDiagnosticValue(signatureSummary.BodyHash), shortDiagnosticValue(signatureSummary.Signature)) } httpResponse, err := client.httpClient.Do(httpRequest) if err != nil { log.Printf("RUN platform request status=send_error method=POST base=%s path=%s durationMs=%d error=%s", diagnosticLogValue(client.baseURL), path, time.Since(startedAt).Milliseconds(), err) return response, fmt.Errorf("send platform request: %w", err) } defer httpResponse.Body.Close() log.Printf("RUN platform request status=response method=POST base=%s path=%s httpStatus=%d durationMs=%d", diagnosticLogValue(client.baseURL), path, httpResponse.StatusCode, time.Since(startedAt).Milliseconds()) if httpResponse.StatusCode < http.StatusOK || httpResponse.StatusCode >= http.StatusMultipleChoices { var failure struct { Code string `json:"code"` Details []string `json:"details"` } _ = json.NewDecoder(io.LimitReader(httpResponse.Body, 64<<10)).Decode(&failure) return response, PlatformRequestError{Status: httpResponse.StatusCode, Path: path, Code: failure.Code, Details: failure.Details} } if err := json.NewDecoder(httpResponse.Body).Decode(&response); err != nil { return response, fmt.Errorf("decode platform response: %w", err) } client.observeResponseServerTime(response) return response, nil } type runRequestEnvelope struct { RunEndpointID string `json:"runEndpointId"` SessionToken string `json:"sessionToken"` } type runRequestSignatureSummary struct { RunEndpointID string Timestamp string Nonce string BodyHash string Signature string } func signRunRequest(request *http.Request, body []byte, stamp time.Time) (runRequestSignatureSummary, error) { var envelope runRequestEnvelope if err := json.Unmarshal(body, &envelope); err != nil { return runRequestSignatureSummary{}, fmt.Errorf("decode Run signing envelope: %w", err) } return signRunRequestWithEnvelope(request, body, envelope, stamp) } func signRunRequestWithEnvelope(request *http.Request, body []byte, envelope runRequestEnvelope, stamp time.Time) (runRequestSignatureSummary, error) { if strings.TrimSpace(envelope.RunEndpointID) == "" || strings.TrimSpace(envelope.SessionToken) == "" { return runRequestSignatureSummary{}, fmt.Errorf("Run signing envelope requires endpoint and session token") } nonceBytes := make([]byte, 16) if _, err := rand.Read(nonceBytes); err != nil { return runRequestSignatureSummary{}, fmt.Errorf("create Run request nonce: %w", err) } timestamp := strconv.FormatInt(stamp.UTC().Unix(), 10) nonce := hex.EncodeToString(nonceBytes) bodyHash := sha256.Sum256(body) bodyHashHex := hex.EncodeToString(bodyHash[:]) canonical := strings.Join([]string{request.Method, request.URL.Path, timestamp, nonce, hex.EncodeToString(bodyHash[:])}, "\n") mac := hmac.New(sha256.New, []byte(envelope.SessionToken)) _, _ = mac.Write([]byte(canonical)) signature := hex.EncodeToString(mac.Sum(nil)) request.Header.Set("X-Run-Endpoint", envelope.RunEndpointID) request.Header.Set("X-Run-Timestamp", timestamp) request.Header.Set("X-Run-Nonce", nonce) request.Header.Set("X-Run-Signature", signature) return runRequestSignatureSummary{RunEndpointID: envelope.RunEndpointID, Timestamp: timestamp, Nonce: nonce, BodyHash: bodyHashHex, Signature: signature}, nil } func (c PlatformClient) signatureTime() time.Time { now := time.Now().UTC() if c.serverClockOffsetActive != nil && c.serverClockOffsetActive.Load() && c.serverClockOffsetNanos != nil { return now.Add(time.Duration(c.serverClockOffsetNanos.Load())) } return now } func (c PlatformClient) observeResponseServerTime(response any) { serverTime, ok := responseServerTime(response) if !ok || serverTime.IsZero() || c.serverClockOffsetNanos == nil || c.serverClockOffsetActive == nil { return } observedAt := time.Now().UTC() offset := serverTime.UTC().Sub(observedAt) previousActive := c.serverClockOffsetActive.Load() previous := time.Duration(c.serverClockOffsetNanos.Load()) c.serverClockOffsetNanos.Store(int64(offset)) c.serverClockOffsetActive.Store(true) if !previousActive || absDuration(offset-previous) > time.Second { log.Printf("RUN platform clock status=calibrated offsetMs=%d serverTime=%s", offset.Milliseconds(), serverTime.UTC().Format(time.RFC3339)) } } func responseServerTime(response any) (time.Time, bool) { value := reflect.ValueOf(response) for value.Kind() == reflect.Pointer || value.Kind() == reflect.Interface { if value.IsNil() { return time.Time{}, false } value = value.Elem() } if value.Kind() != reflect.Struct { return time.Time{}, false } field := value.FieldByName("ServerTime") if !field.IsValid() || !field.CanInterface() { return time.Time{}, false } serverTime, ok := field.Interface().(time.Time) return serverTime, ok } func absDuration(value time.Duration) time.Duration { if value < 0 { return -value } return value } func shortDiagnosticValue(value string) string { value = diagnosticLogValue(value) if value == "" { return "-" } if len(value) <= 16 { return value } return value[:12] + "..." + value[len(value)-4:] } func diagnosticLogValue(value string) string { value = strings.TrimSpace(value) return strings.NewReplacer("\n", " ", "\r", " ", "\t", " ").Replace(value) }