Fix SCUM companion trajectory storage

This commit is contained in:
npc0-hue
2026-08-31 15:42:16 +08:00
parent bc927a5144
commit ea4e780562
26 changed files with 1028 additions and 112 deletions
@@ -11,10 +11,15 @@ import (
)
const (
ConfigSchemaVersion = 1
PluginID = "game.scum"
ProfileKey = "scum-client-manager"
ProofEnvironment = "SCUM_COMPONENT_PROOF"
ConfigSchemaVersion = 1
PluginID = "game.scum"
ProfileKey = "scum-client-manager"
ProofEnvironment = "SCUM_COMPONENT_PROOF"
SCUMDatabaseFileEnvironment = "SCUM_DB_FILE"
TrajectorySourceSCUMSQLite = "scum-sqlite"
TrajectoryStoreSharedPlatformMySQL = "shared-platform-mysql"
DefaultTrajectoryCollectionIntervalSecs = 3
DefaultTrajectoryCollectionMaxRows = 500
)
var requiredCapabilities = []string{
@@ -41,6 +46,7 @@ type Config struct {
Capabilities []string `json:"capabilities" yaml:"capabilities"`
Timing TimingConfig `json:"timing" yaml:"timing"`
TLS TransportTLSConfig `json:"tls" yaml:"tls"`
Trajectory TrajectoryConfig `json:"trajectory" yaml:"trajectory"`
}
type PlatformConfig struct {
@@ -80,6 +86,15 @@ type TransportTLSConfig struct {
Policy string `json:"policy" yaml:"policy"`
}
type TrajectoryConfig struct {
Enabled bool `json:"enabled" yaml:"enabled"`
Source string `json:"source" yaml:"source"`
Store string `json:"store" yaml:"store"`
FileEnv string `json:"fileEnv" yaml:"fileEnv"`
IntervalSeconds int `json:"intervalSeconds" yaml:"intervalSeconds"`
MaxRows int `json:"maxRows" yaml:"maxRows"`
}
func LoadConfig(reader io.Reader) (Config, error) {
decoder := yaml.NewDecoder(reader)
decoder.KnownFields(true)
@@ -94,6 +109,7 @@ func LoadConfig(reader io.Reader) (Config, error) {
}
return Config{}, fmt.Errorf("decode companion config: %w", err)
}
config.applyDefaults()
if err := config.Validate(); err != nil {
return Config{}, err
}
@@ -102,6 +118,24 @@ func LoadConfig(reader io.Reader) (Config, error) {
return config, nil
}
func (config *Config) applyDefaults() {
if config.Trajectory.Source == "" {
config.Trajectory.Source = TrajectorySourceSCUMSQLite
}
if config.Trajectory.Store == "" {
config.Trajectory.Store = TrajectoryStoreSharedPlatformMySQL
}
if config.Trajectory.FileEnv == "" {
config.Trajectory.FileEnv = SCUMDatabaseFileEnvironment
}
if config.Trajectory.IntervalSeconds == 0 {
config.Trajectory.IntervalSeconds = DefaultTrajectoryCollectionIntervalSecs
}
if config.Trajectory.MaxRows == 0 {
config.Trajectory.MaxRows = DefaultTrajectoryCollectionMaxRows
}
}
func (config Config) Validate() error {
if config.SchemaVersion != ConfigSchemaVersion {
return fmt.Errorf("companion config schema version is unsupported")
@@ -134,9 +168,43 @@ func (config Config) Validate() error {
if config.Timing.HeartbeatIntervalSeconds < 5 || config.Timing.HeartbeatIntervalSeconds > 300 || config.Timing.CommandPollIntervalSeconds < 1 || config.Timing.CommandPollIntervalSeconds > 60 || config.Timing.RequestTimeoutSeconds < 1 || config.Timing.RequestTimeoutSeconds > 60 {
return fmt.Errorf("companion timing policy is invalid")
}
if err := config.Trajectory.Validate(); err != nil {
return err
}
return nil
}
func (config TrajectoryConfig) Validate() error {
if config.Source != TrajectorySourceSCUMSQLite || config.Store != TrajectoryStoreSharedPlatformMySQL {
return fmt.Errorf("SCUM trajectory collection mode is unsupported")
}
if !validCompanionEnvironmentName(config.FileEnv) {
return fmt.Errorf("SCUM database file environment name is invalid")
}
if config.IntervalSeconds < 1 || config.IntervalSeconds > 3600 || config.MaxRows < 1 || config.MaxRows > 5000 {
return fmt.Errorf("SCUM trajectory collection bounds are invalid")
}
return nil
}
func validCompanionEnvironmentName(value string) bool {
if len(value) < 3 || len(value) > 64 || value[0] < 'A' || value[0] > 'Z' {
return false
}
for _, char := range value[1:] {
if char >= 'A' && char <= 'Z' || char >= '0' && char <= '9' || char == '_' {
continue
}
return false
}
switch value {
case "PATH", "LD_PRELOAD", "DYLD_INSERT_LIBRARIES":
return false
default:
return true
}
}
func canonicalPlatformOrigin(value string) (string, error) {
parsed, err := url.Parse(strings.TrimSpace(value))
if err != nil || parsed.Scheme != "https" || parsed.Host == "" || parsed.Hostname() == "" || parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" || parsed.Path != "" && parsed.Path != "/" {