package main import ( "encoding/json" "errors" "fmt" "os" "strings" "time" "data-terminal-cloud/internal/firmwareconformance" ) func recordSimulatedFirmwareEvidence(command simulatedDeviceCommand, manifest simulatedFirmwareManifest, phase string, state simulatedFirmwareBootState, resultCode string) error { path := strings.TrimSpace(os.Getenv("SIM_FIRMWARE_EVIDENCE_FILE")) if path == "" { return nil } now := time.Now().UTC() var evidence firmwareconformance.DeviceEvidence content, err := os.ReadFile(path) if errors.Is(err, os.ErrNotExist) { partitionDigest := strings.ToLower(env("SIM_PARTITION_TABLE_SHA256", firmwareconformance.SHA256([]byte(env("SIM_DEVICE_MODEL", "simulated-terminal")+":"+env("SIM_DEVICE_HARDWARE_REVISION", "sim-rev-a")+":A/B")))) secureBoot, parseErr := simulatedEvidenceBoolean("SIM_SECURE_BOOT_ENABLED", false) if parseErr != nil { return parseErr } evidence = firmwareconformance.DeviceEvidence{ SchemaVersion: firmwareconformance.DeviceEvidenceSchema, EvidenceSource: "SIMULATOR", CandidateVersion: env("SIM_CANDIDATE_VERSION", "local-development"), BuildDigest: strings.ToLower(strings.TrimSpace(os.Getenv("SIM_BUILD_DIGEST"))), DeviceID: env("SIM_DEVICE_ID", "10000000-0000-0000-0000-000000000511"), DeviceModel: env("SIM_DEVICE_MODEL", "simulated-terminal"), HardwareRevision: env("SIM_DEVICE_HARDWARE_REVISION", "sim-rev-a"), ReleaseID: manifest.ReleaseID, CommandID: command.ID, Scenario: strings.ToUpper(strings.TrimSpace(os.Getenv("SIM_FIRMWARE_EVIDENCE_SCENARIO"))), BootloaderVersion: env("SIM_BOOTLOADER_VERSION", "sim-bootloader-1"), PartitionTableSHA256: partitionDigest, SecureBootEnabled: secureBoot, ArtifactSHA256: manifest.Digest, SigningKeyID: manifest.SigningKeyID, InitialSlot: state.PreviousSlot, TargetSlot: state.TargetSlot, FinalSlot: state.PreviousSlot, InitialVersion: state.PreviousVersion, TargetVersion: state.TargetVersion, FinalVersion: state.PreviousVersion, BootAttemptID: state.BootAttemptID, CollectedAt: now, Events: []firmwareconformance.DeviceEvent{}, } if evidence.BuildDigest == "" || evidence.Scenario == "" { return errors.New("SIM_BUILD_DIGEST and SIM_FIRMWARE_EVIDENCE_SCENARIO are required when evidence capture is enabled") } } else if err != nil { return fmt.Errorf("read simulated firmware evidence: %w", err) } else { evidence, err = firmwareconformance.ParseDeviceEvidence(content) if err != nil { return err } if evidence.CommandID != command.ID || evidence.ReleaseID != manifest.ReleaseID { return errors.New("simulated firmware evidence belongs to a different command or release") } } if manifest.SigningKeyID != "" && evidence.SigningKeyID == "" { evidence.SigningKeyID = manifest.SigningKeyID } if state.BootAttemptID != "" { evidence.BootAttemptID = state.BootAttemptID } event := firmwareconformance.DeviceEvent{ Phase: phase, OccurredAt: now, ActiveSlot: state.ActiveSlot, TargetSlot: state.TargetSlot, ResultCode: resultCode, BootAttemptID: state.BootAttemptID, } evidence.Events = append(evidence.Events, event) evidence.CollectedAt = now switch phase { case "confirmed": evidence.FinalSlot = state.ActiveSlot evidence.FinalVersion = manifest.Version case "rolled_back": evidence.FinalSlot = state.ActiveSlot evidence.FinalVersion = state.TargetVersion } encoded, err := json.MarshalIndent(evidence, "", " ") if err != nil { return err } file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600) if err != nil { return fmt.Errorf("open simulated firmware evidence: %w", err) } if _, err = file.Write(encoded); err == nil { err = file.Sync() } closeErr := file.Close() if err != nil { return fmt.Errorf("write simulated firmware evidence: %w", err) } if closeErr != nil { return fmt.Errorf("close simulated firmware evidence: %w", closeErr) } return nil } func simulatedEvidenceBoolean(key string, fallback bool) (bool, error) { value := strings.ToLower(strings.TrimSpace(os.Getenv(key))) if value == "" { return fallback, nil } if value == "true" { return true, nil } if value == "false" { return false, nil } return false, fmt.Errorf("%s must be true or false", key) }