package httpapi import ( "net/http" "regexp" "strings" "data-terminal-cloud/internal/deviceauth" "data-terminal-cloud/internal/saas" ) var sha256Pattern = regexp.MustCompile(`^[0-9a-f]{64}$`) func RegisterFirmwareSigningKeyHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } var in struct { KeyID string `json:"key_id"` PublicKey string `json:"public_key_base64"` } if !decodeWebJSON(w, r, &in, "固件签名密钥参数不正确") { return } in.KeyID = strings.TrimSpace(in.KeyID) in.PublicKey = strings.TrimSpace(in.PublicKey) if in.KeyID == "" || in.PublicKey == "" { writeValidation(w, r, "固件签名密钥参数不正确", FieldError{Field: "key", Message: "密钥编号和Ed25519公钥均为必填"}) return } if err := service.RegisterFirmwareSigningKey(r.Context(), p, r.PathValue("id"), RequestID(r.Context()), in.KeyID, in.PublicKey); err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusCreated, map[string]any{"key_id": in.KeyID, "status": "ACTIVE"}) }) } func ListFirmwareSigningKeysHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } items, err := service.ListFirmwareSigningKeys(r.Context(), p, r.PathValue("id")) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusOK, map[string]any{"items": items}) }) } func RevokeFirmwareSigningKeyHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } var input struct { KeyID string `json:"key_id"` } if !decodeWebJSON(w, r, &input, "固件签名密钥吊销参数不正确") { return } input.KeyID = strings.TrimSpace(input.KeyID) if input.KeyID == "" || len([]rune(input.KeyID)) > 120 { writeValidation(w, r, "固件签名密钥吊销参数不正确", FieldError{Field: "key_id", Message: "密钥编号必须为1至120个字符"}) return } item, err := service.RevokeFirmwareSigningKey(r.Context(), p, r.PathValue("id"), RequestID(r.Context()), input.KeyID) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusOK, item) }) } func PrepareFirmwareReleaseHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } var in struct { Model string `json:"model"` HardwareRevision string `json:"hardware_revision"` Version string `json:"version"` ExpectedSize int64 `json:"expected_size"` ExpectedDigest string `json:"expected_digest"` Signature string `json:"signature"` SigningKeyID string `json:"signing_key_id"` MinimumSecurityVersion string `json:"minimum_security_version"` ReleaseNotes string `json:"release_notes"` } if !decodeWebJSON(w, r, &in, "固件版本参数不正确") { return } in.Model = strings.TrimSpace(in.Model) in.HardwareRevision = strings.TrimSpace(in.HardwareRevision) in.Version = strings.TrimSpace(in.Version) in.ExpectedDigest = strings.ToLower(strings.TrimSpace(in.ExpectedDigest)) in.Signature = strings.TrimSpace(in.Signature) in.SigningKeyID = strings.TrimSpace(in.SigningKeyID) if in.Model == "" || in.Version == "" || in.ExpectedSize < 1 || in.ExpectedSize > 268435456 || !sha256Pattern.MatchString(in.ExpectedDigest) || in.Signature == "" || in.SigningKeyID == "" { writeValidation(w, r, "固件版本参数不正确", FieldError{Field: "firmware", Message: "型号、版本、1至256MB大小、SHA-256、Ed25519签名和密钥编号均为必填"}) return } item, err := service.PrepareFirmwareRelease(r.Context(), p, r.PathValue("id"), RequestID(r.Context()), saas.PrepareFirmwareInput{Model: in.Model, HardwareRevision: in.HardwareRevision, Version: in.Version, ExpectedSize: in.ExpectedSize, ExpectedDigest: in.ExpectedDigest, Signature: in.Signature, SigningKeyID: in.SigningKeyID, MinimumSecurityVersion: strings.TrimSpace(in.MinimumSecurityVersion), ReleaseNotes: strings.TrimSpace(in.ReleaseNotes)}) if err != nil { writeSaaSError(w, r, err) return } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusCreated, item) }) } func ListFirmwareReleasesHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } items, err := service.ListFirmwareReleases(r.Context(), p, r.PathValue("id")) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusOK, map[string]any{"items": items}) }) } func GetFirmwareReleaseImpactHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } item, err := service.GetFirmwareReleaseImpact(r.Context(), p, r.PathValue("id")) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusOK, item) }) } func FinalizeFirmwareReleaseHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } item, err := service.FinalizeFirmwareRelease(r.Context(), p, r.PathValue("id"), RequestID(r.Context())) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusOK, item) }) } func RenewFirmwareUploadHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } item, err := service.RenewFirmwareUpload(r.Context(), p, r.PathValue("id"), RequestID(r.Context())) if err != nil { writeSaaSError(w, r, err) return } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, item) }) } func PauseFirmwareReleaseHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } item, err := service.PauseFirmwareRelease(r.Context(), p, r.PathValue("id"), RequestID(r.Context())) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusOK, item) }) } func ResumeFirmwareReleaseHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } item, err := service.ResumeFirmwareRelease(r.Context(), p, r.PathValue("id"), RequestID(r.Context())) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusOK, item) }) } func RetireFirmwareReleaseHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } item, err := service.RetireFirmwareRelease(r.Context(), p, r.PathValue("id"), RequestID(r.Context())) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusOK, item) }) } func StartFirmwareRolloutHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } var in struct { ReleaseID string `json:"release_id"` Name string `json:"name"` TargetPercentage int `json:"target_percentage"` DeviceIDs []string `json:"device_ids"` } if !decodeWebJSON(w, r, &in, "固件灰度批次参数不正确") { return } in.ReleaseID = strings.TrimSpace(in.ReleaseID) in.Name = strings.TrimSpace(in.Name) if in.ReleaseID == "" || in.Name == "" || in.TargetPercentage < 1 || in.TargetPercentage > 100 || len(in.DeviceIDs) == 0 { writeValidation(w, r, "固件灰度批次参数不正确", FieldError{Field: "rollout", Message: "版本、批次名称、1至100灰度比例和目标设备均为必填"}) return } item, err := service.StartFirmwareRollout(r.Context(), p, r.PathValue("id"), in.ReleaseID, RequestID(r.Context()), in.Name, in.TargetPercentage, in.DeviceIDs) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusAccepted, item) }) } func ListFirmwareRolloutsHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } items, err := service.ListFirmwareRollouts(r.Context(), p, r.PathValue("id")) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusOK, map[string]any{"items": items}) }) } func DeviceFirmwareDownloadHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { id, ok := deviceauth.FromContext(r.Context()) if !ok { WriteError(w, r, http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED", "设备身份验证失败") return } url, until, err := service.FirmwareDownloadForDevice(r.Context(), id, r.PathValue("id")) if err != nil { WriteError(w, r, http.StatusForbidden, "FIRMWARE_DOWNLOAD_DENIED", "设备不在该固件批次中或版本不可下载") return } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, map[string]any{"download_url": url, "download_until": until}) }) } func GetFirmwareRolloutHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } item, err := service.GetFirmwareRollout(r.Context(), p, r.PathValue("id")) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusOK, item) }) } func PauseFirmwareRolloutHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } item, err := service.PauseFirmwareRollout(r.Context(), p, r.PathValue("id"), RequestID(r.Context())) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusOK, item) }) } func RollbackFirmwareRolloutHandler(service *saas.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } var in struct { ReleaseID string `json:"rollback_release_id"` } if !decodeWebJSON(w, r, &in, "固件回滚参数不正确") { return } in.ReleaseID = strings.TrimSpace(in.ReleaseID) if in.ReleaseID == "" { writeValidation(w, r, "固件回滚参数不正确", FieldError{Field: "rollback_release_id", Message: "必须选择已验证的回滚版本"}) return } item, err := service.StartFirmwareRollback(r.Context(), p, r.PathValue("id"), in.ReleaseID, RequestID(r.Context())) if err != nil { writeSaaSError(w, r, err) return } WriteJSON(w, r, http.StatusAccepted, item) }) }