package runtime import ( "fmt" "sort" "strings" "browser.local/run/protocol" ) const ( RuntimeModeLocalProcess = "local-process" RuntimeModeHostedFTPRCON = "hosted-ftp-rcon" RuntimeModeFTPOnly = "ftp-only" ) type RuntimeProfiles struct { Discovery []RuntimeDiscoveryProbe `json:"discovery,omitempty"` LifecycleProfiles []RuntimeLifecycleProfile `json:"lifecycleProfiles,omitempty"` DependencyProbes []RuntimeDependencyProbe `json:"dependencyProbes,omitempty"` InstallPlans []RuntimeInstallPlan `json:"installPlans,omitempty"` LogSources []RuntimeLogSource `json:"logSources,omitempty"` TransportProfiles []RuntimeTransportProfile `json:"transportProfiles,omitempty"` } type RuntimeDiscoveryProbe struct { Key string `json:"key"` Kind string `json:"kind"` TargetKey string `json:"targetKey"` Required bool `json:"required,omitempty"` Platforms []string `json:"platforms,omitempty"` } type RuntimeLifecycleProfile struct { Key string `json:"key"` Mode string `json:"mode"` Capabilities []string `json:"capabilities"` ActionRefs map[string]string `json:"actionRefs,omitempty"` TransportKeys []string `json:"transportKeys,omitempty"` Platforms []string `json:"platforms,omitempty"` } type RuntimeDependencyProbe struct { Key string `json:"key"` Kind string `json:"kind"` TargetKey string `json:"targetKey"` Required bool `json:"required,omitempty"` Platforms []string `json:"platforms,omitempty"` } type RuntimeInstallPlan struct { Key string `json:"key"` Title string `json:"title"` Platforms []string `json:"platforms,omitempty"` Steps []RuntimeInstallStep `json:"steps"` } type RuntimeInstallStep struct { Type string `json:"type"` TargetKey string `json:"targetKey"` PackageManager string `json:"packageManager,omitempty"` PackageName string `json:"packageName,omitempty"` Version string `json:"version,omitempty"` DownloadRef string `json:"downloadRef,omitempty"` Checksum string `json:"checksum,omitempty"` } type RuntimeLogSource struct { Key string `json:"key"` Kind string `json:"kind"` TargetKey string `json:"targetKey,omitempty"` StreamKey string `json:"streamKey"` CursorKind string `json:"cursorKind,omitempty"` RetentionDays int `json:"retentionDays,omitempty"` } type RuntimeTransportProfile struct { Key string `json:"key"` Kind string `json:"kind"` TargetKey string `json:"targetKey,omitempty"` Capabilities []string `json:"capabilities"` } type RuntimeBindingSet struct { ProfileKey string `json:"profileKey"` Mode string `json:"mode"` Bindings map[string]string `json:"bindings,omitempty"` MissingKeys []string `json:"missingKeys,omitempty"` } type RuntimeResolution struct { ProfileKey string `json:"profileKey"` Mode string `json:"mode"` Capabilities []string `json:"capabilities"` ActionRefs map[string]string `json:"actionRefs,omitempty"` TransportKeys []string `json:"transportKeys,omitempty"` Transports []RuntimeTransportProfile `json:"transports,omitempty"` LogSources []RuntimeLogSource `json:"logSources,omitempty"` Discovery []RuntimeDiscoveryProbe `json:"discovery,omitempty"` MissingKeys []string `json:"missingKeys,omitempty"` Available bool `json:"available"` } func ResolveRuntimeProfile(profiles RuntimeProfiles, profileKey string, targetOS string, binding RuntimeBindingSet) (RuntimeResolution, error) { profile, ok := findLifecycleProfile(profiles.LifecycleProfiles, profileKey) if !ok { return RuntimeResolution{}, fmt.Errorf("runtime profile is not declared") } if !supportedRuntimeMode(profile.Mode) { return RuntimeResolution{}, fmt.Errorf("runtime mode is unsupported") } if targetOS != "" && !supportsPlatform(profile.Platforms, targetOS) { return RuntimeResolution{}, fmt.Errorf("runtime profile does not support target platform") } if binding.ProfileKey != "" && binding.ProfileKey != profile.Key { return RuntimeResolution{}, fmt.Errorf("runtime binding profile does not match") } if binding.Mode != "" && binding.Mode != profile.Mode { return RuntimeResolution{}, fmt.Errorf("runtime binding mode does not match") } if err := validateRuntimeProfile(profile); err != nil { return RuntimeResolution{}, err } transports, err := resolveTransports(profile.TransportKeys, profiles.TransportProfiles) if err != nil { return RuntimeResolution{}, err } missing := missingRuntimeBindingKeys(profile, transports, profiles.Discovery, profiles.LogSources, binding) return RuntimeResolution{ ProfileKey: profile.Key, Mode: profile.Mode, Capabilities: append([]string(nil), profile.Capabilities...), ActionRefs: copyStringMap(profile.ActionRefs), TransportKeys: append([]string(nil), profile.TransportKeys...), Transports: transports, LogSources: safeLogSources(profiles.LogSources, targetOS), Discovery: safeDiscovery(profiles.Discovery, targetOS), MissingKeys: missing, Available: len(missing) == 0, }, nil } func findLifecycleProfile(profiles []RuntimeLifecycleProfile, key string) (RuntimeLifecycleProfile, bool) { for _, profile := range profiles { if profile.Key == key { return profile, true } } return RuntimeLifecycleProfile{}, false } func validateRuntimeProfile(profile RuntimeLifecycleProfile) error { if !protocol.ValidLogicalFileKey(profile.Key) { return fmt.Errorf("runtime profile key is unsafe") } for _, capability := range profile.Capabilities { if strings.TrimSpace(capability) == "" || containsUnsafeRuntimeText(capability) { return fmt.Errorf("runtime capability is unsafe") } } for action, ref := range profile.ActionRefs { if !protocol.ValidLogicalFileKey(action) || !protocol.ValidLogicalFileKey(ref) { return fmt.Errorf("runtime action ref is unsafe") } } return nil } func resolveTransports(keys []string, profiles []RuntimeTransportProfile) ([]RuntimeTransportProfile, error) { out := make([]RuntimeTransportProfile, 0, len(keys)) for _, key := range keys { if !protocol.ValidLogicalFileKey(key) { return nil, fmt.Errorf("transport key is unsafe") } found := false for _, profile := range profiles { if profile.Key != key { continue } if !protocol.ValidLogicalFileKey(profile.Key) || (profile.TargetKey != "" && !protocol.ValidLogicalFileKey(profile.TargetKey)) { return nil, fmt.Errorf("transport profile is unsafe") } out = append(out, profile) found = true break } if !found { return nil, fmt.Errorf("transport profile %q is not declared", key) } } return out, nil } func missingRuntimeBindingKeys(profile RuntimeLifecycleProfile, transports []RuntimeTransportProfile, discovery []RuntimeDiscoveryProbe, logs []RuntimeLogSource, binding RuntimeBindingSet) []string { required := map[string]struct{}{} for _, transport := range transports { if transport.TargetKey != "" { required[transport.TargetKey] = struct{}{} } } for _, probe := range discovery { if probe.Required && probe.TargetKey != "" { required[probe.TargetKey] = struct{}{} } } logTargets := map[string]struct{}{} for _, source := range logs { if source.TargetKey != "" { logTargets[source.TargetKey] = struct{}{} } } for _, key := range binding.MissingKeys { if _, logTarget := logTargets[key]; logTarget { continue } if protocol.ValidLogicalFileKey(key) { required[key] = struct{}{} } } missing := make([]string, 0, len(required)) for key := range required { if _, ok := binding.Bindings[key]; !ok { missing = append(missing, key) } } sort.Strings(missing) return missing } func safeDiscovery(probes []RuntimeDiscoveryProbe, targetOS string) []RuntimeDiscoveryProbe { out := []RuntimeDiscoveryProbe{} for _, probe := range probes { if supportsPlatform(probe.Platforms, targetOS) && protocol.ValidLogicalFileKey(probe.Key) && protocol.ValidLogicalFileKey(probe.TargetKey) { out = append(out, probe) } } return out } func safeLogSources(sources []RuntimeLogSource, targetOS string) []RuntimeLogSource { _ = targetOS out := []RuntimeLogSource{} for _, source := range sources { if protocol.ValidLogicalFileKey(source.Key) && protocol.ValidLogicalFileKey(source.StreamKey) && (source.TargetKey == "" || protocol.ValidLogicalFileKey(source.TargetKey)) { out = append(out, source) } } return out } func supportedRuntimeMode(mode string) bool { switch mode { case RuntimeModeLocalProcess, RuntimeModeHostedFTPRCON, RuntimeModeFTPOnly: return true default: return false } } func supportsPlatform(platforms []string, targetOS string) bool { if targetOS == "" || len(platforms) == 0 { return true } for _, platform := range platforms { if platform == targetOS { return true } } return false } func copyStringMap(values map[string]string) map[string]string { if len(values) == 0 { return nil } out := make(map[string]string, len(values)) for key, value := range values { out[key] = value } return out }