296 lines
10 KiB
Go
296 lines
10 KiB
Go
package domain
|
|
|
|
import "time"
|
|
|
|
type SCUMDataCapability string
|
|
|
|
const (
|
|
SCUMDataCapabilitySchemaProbe SCUMDataCapability = "schema-probe"
|
|
SCUMDataCapabilityPlayerRead SCUMDataCapability = "players.read"
|
|
SCUMDataCapabilityPlayerDetailRead SCUMDataCapability = "player-details.read"
|
|
SCUMDataCapabilitySquadRead SCUMDataCapability = "squads.read"
|
|
SCUMDataCapabilitySquadMemberRead SCUMDataCapability = "squad-members.read"
|
|
SCUMDataCapabilityVehicleRead SCUMDataCapability = "vehicles.read"
|
|
SCUMDataCapabilityFlagRead SCUMDataCapability = "flags.read"
|
|
SCUMDataCapabilityPositionRead SCUMDataCapability = "positions.read"
|
|
SCUMDataCapabilityProfileXMLWrite SCUMDataCapability = "profile-xml.write"
|
|
SCUMDataCapabilityEconomyCommand SCUMDataCapability = "economy-command.write"
|
|
SCUMDataCapabilityGiftCommand SCUMDataCapability = "gift-command.write"
|
|
)
|
|
|
|
type SCUMCapabilityEvidenceStatus string
|
|
|
|
const (
|
|
SCUMCapabilityEvidenceMissing SCUMCapabilityEvidenceStatus = "missing"
|
|
SCUMCapabilityEvidenceCompatible SCUMCapabilityEvidenceStatus = "compatible"
|
|
SCUMCapabilityEvidenceIncompatible SCUMCapabilityEvidenceStatus = "incompatible"
|
|
SCUMCapabilityEvidenceFailed SCUMCapabilityEvidenceStatus = "failed"
|
|
)
|
|
|
|
type SCUMCapabilityGateState string
|
|
|
|
const (
|
|
SCUMCapabilityGateEnabled SCUMCapabilityGateState = "enabled"
|
|
SCUMCapabilityGateDisabled SCUMCapabilityGateState = "disabled"
|
|
)
|
|
|
|
type SCUMSafeErrorCode string
|
|
|
|
const (
|
|
SCUMSafeErrorNone SCUMSafeErrorCode = "none"
|
|
SCUMSafeErrorProbeExecutorAbsent SCUMSafeErrorCode = "probe_executor_absent"
|
|
SCUMSafeErrorProbeMissing SCUMSafeErrorCode = "probe_missing"
|
|
SCUMSafeErrorProbeFailed SCUMSafeErrorCode = "probe_failed"
|
|
SCUMSafeErrorSchemaIncompatible SCUMSafeErrorCode = "schema_incompatible"
|
|
SCUMSafeErrorBindingMismatch SCUMSafeErrorCode = "binding_mismatch"
|
|
SCUMSafeErrorAdapterMismatch SCUMSafeErrorCode = "adapter_mismatch"
|
|
SCUMSafeErrorFingerprintMismatch SCUMSafeErrorCode = "fingerprint_mismatch"
|
|
SCUMSafeErrorDigestMismatch SCUMSafeErrorCode = "digest_mismatch"
|
|
SCUMSafeErrorEvidenceExpired SCUMSafeErrorCode = "evidence_expired"
|
|
SCUMSafeErrorInvalidProbePayload SCUMSafeErrorCode = "invalid_probe_payload"
|
|
)
|
|
|
|
type SCUMSafeError struct {
|
|
Code SCUMSafeErrorCode
|
|
Message string
|
|
Retryable bool
|
|
}
|
|
|
|
type SCUMBindingIdentity struct {
|
|
ServerInstanceID string
|
|
RunBindingID string
|
|
RunEndpointID string
|
|
PluginID string
|
|
PluginVersion string
|
|
AdapterVersion string
|
|
GameVersion string
|
|
DatabaseIdentity string
|
|
}
|
|
|
|
type SCUMSchemaProbeBounds struct {
|
|
MaxObjects int
|
|
MaxColumnsPerObject int
|
|
MaxIndexesPerObject int
|
|
MaxForeignKeys int
|
|
MaxCardinalityReads int
|
|
MaxSampleRows int
|
|
TimeoutMS int
|
|
MaxResultBytes int
|
|
}
|
|
|
|
func DefaultSCUMSchemaProbeBounds() SCUMSchemaProbeBounds {
|
|
return SCUMSchemaProbeBounds{MaxObjects: 256, MaxColumnsPerObject: 128, MaxIndexesPerObject: 64, MaxForeignKeys: 64, MaxCardinalityReads: 64, MaxSampleRows: 3, TimeoutMS: 5000, MaxResultBytes: 512 * 1024}
|
|
}
|
|
|
|
type SCUMSchemaProbeRequest struct {
|
|
RequestID string
|
|
JobID string
|
|
Binding SCUMBindingIdentity
|
|
Bounds SCUMSchemaProbeBounds
|
|
RequestedAt time.Time
|
|
}
|
|
|
|
type SCUMSchemaProbeDeclaration struct {
|
|
Capability string
|
|
TargetKey string
|
|
Bounds SCUMSchemaProbeBounds
|
|
}
|
|
|
|
type SCUMLiveDataCapabilityGateDeclaration struct {
|
|
Capability SCUMDataCapability
|
|
Gate SCUMCapabilityGateState
|
|
AdapterVersion string
|
|
RequiredSchemaFingerprint string
|
|
RequiredAssetDigests []string
|
|
EvidenceStatus SCUMCapabilityEvidenceStatus
|
|
SafeReason string
|
|
}
|
|
|
|
type SCUMLiveDataManifest struct {
|
|
SchemaVersion string
|
|
Probe SCUMSchemaProbeDeclaration
|
|
CapabilityGates []SCUMLiveDataCapabilityGateDeclaration
|
|
}
|
|
|
|
type SCUMSchemaColumnEvidence struct {
|
|
NameFingerprint string
|
|
DeclaredType string
|
|
Nullable *bool
|
|
PrimaryKey bool
|
|
Ordinal int
|
|
}
|
|
|
|
type SCUMSchemaIndexEvidence struct {
|
|
NameFingerprint string
|
|
Unique bool
|
|
ColumnHashes []string
|
|
}
|
|
|
|
type SCUMSchemaForeignKeyEvidence struct {
|
|
FromColumnHash string
|
|
ToObjectHash string
|
|
ToColumnHash string
|
|
}
|
|
|
|
type SCUMSchemaObjectEvidence struct {
|
|
ObjectHash string
|
|
Kind string
|
|
NameFingerprint string
|
|
DeclaredColumns []SCUMSchemaColumnEvidence
|
|
Indexes []SCUMSchemaIndexEvidence
|
|
ForeignKeys []SCUMSchemaForeignKeyEvidence
|
|
ApproximateRows *int64
|
|
SampleFingerprints []string
|
|
}
|
|
|
|
type SCUMSchemaProbeResult struct {
|
|
RequestID string
|
|
JobID string
|
|
Binding SCUMBindingIdentity
|
|
Status SCUMCapabilityEvidenceStatus
|
|
SchemaFingerprint string
|
|
ObservedAt time.Time
|
|
ResultDigest string
|
|
Objects []SCUMSchemaObjectEvidence
|
|
SafeError SCUMSafeError
|
|
Limits SCUMSchemaProbeBounds
|
|
}
|
|
|
|
type SCUMCapabilityRequirement struct {
|
|
Capability SCUMDataCapability
|
|
AdapterVersion string
|
|
SchemaFingerprint string
|
|
AssetDigests []string
|
|
}
|
|
|
|
type SCUMCapabilityEvidence struct {
|
|
Capability SCUMDataCapability
|
|
Status SCUMCapabilityEvidenceStatus
|
|
Binding SCUMBindingIdentity
|
|
AdapterVersion string
|
|
SchemaFingerprint string
|
|
ProbeResultDigest string
|
|
AssetDigests []string
|
|
ObservedAt time.Time
|
|
ExpiresAt time.Time
|
|
SafeError SCUMSafeError
|
|
}
|
|
|
|
type SCUMCapabilityGate struct {
|
|
Capability SCUMDataCapability
|
|
State SCUMCapabilityGateState
|
|
Enabled bool
|
|
ReasonCode SCUMSafeErrorCode
|
|
Reason string
|
|
Evidence SCUMCapabilityEvidence
|
|
}
|
|
|
|
func EvaluateSCUMCapabilityGate(requirement SCUMCapabilityRequirement, evidence SCUMCapabilityEvidence, active SCUMBindingIdentity, probeExecutorAvailable bool, now time.Time) SCUMCapabilityGate {
|
|
gate := SCUMCapabilityGate{Capability: requirement.Capability, State: SCUMCapabilityGateDisabled, ReasonCode: SCUMSafeErrorProbeMissing, Reason: "current-service evidence is required before this SCUM capability can run"}
|
|
if !probeExecutorAvailable {
|
|
gate.ReasonCode = SCUMSafeErrorProbeExecutorAbsent
|
|
gate.Reason = "bound Run does not expose the generic SQLite schema-probe executor"
|
|
return gate
|
|
}
|
|
if evidence.Status == SCUMCapabilityEvidenceMissing || evidence.Capability == "" {
|
|
return gate
|
|
}
|
|
gate.Evidence = CopySCUMCapabilityEvidence(evidence)
|
|
if evidence.Status == SCUMCapabilityEvidenceFailed {
|
|
gate.ReasonCode = SCUMSafeErrorProbeFailed
|
|
gate.Reason = safeReason(evidence.SafeError.Message, "last schema probe failed")
|
|
return gate
|
|
}
|
|
if evidence.Status == SCUMCapabilityEvidenceIncompatible {
|
|
gate.ReasonCode = SCUMSafeErrorSchemaIncompatible
|
|
gate.Reason = safeReason(evidence.SafeError.Message, "current schema is incompatible with the plugin adapter")
|
|
return gate
|
|
}
|
|
if evidence.Capability != requirement.Capability {
|
|
gate.ReasonCode = SCUMSafeErrorSchemaIncompatible
|
|
gate.Reason = "capability evidence does not match the requested SCUM capability"
|
|
return gate
|
|
}
|
|
if !sameSCUMBinding(evidence.Binding, active) {
|
|
gate.ReasonCode = SCUMSafeErrorBindingMismatch
|
|
gate.Reason = "evidence belongs to a different server, Run binding, plugin, adapter, game, or database identity"
|
|
return gate
|
|
}
|
|
if evidence.AdapterVersion != requirement.AdapterVersion {
|
|
gate.ReasonCode = SCUMSafeErrorAdapterMismatch
|
|
gate.Reason = "evidence adapter version does not match the plugin requirement"
|
|
return gate
|
|
}
|
|
if evidence.SchemaFingerprint == "" || evidence.SchemaFingerprint != requirement.SchemaFingerprint {
|
|
gate.ReasonCode = SCUMSafeErrorFingerprintMismatch
|
|
gate.Reason = "schema fingerprint does not match the plugin requirement"
|
|
return gate
|
|
}
|
|
if !containsAllStrings(evidence.AssetDigests, requirement.AssetDigests) {
|
|
gate.ReasonCode = SCUMSafeErrorDigestMismatch
|
|
gate.Reason = "packaged asset digest does not match the compatible evidence"
|
|
return gate
|
|
}
|
|
if !evidence.ExpiresAt.IsZero() && !now.IsZero() && !now.Before(evidence.ExpiresAt) {
|
|
gate.ReasonCode = SCUMSafeErrorEvidenceExpired
|
|
gate.Reason = "current-service evidence has expired and must be probed again"
|
|
return gate
|
|
}
|
|
gate.State = SCUMCapabilityGateEnabled
|
|
gate.Enabled = true
|
|
gate.ReasonCode = SCUMSafeErrorNone
|
|
gate.Reason = "current-service evidence matches the versioned plugin adapter"
|
|
return gate
|
|
}
|
|
|
|
func CopySCUMCapabilityEvidence(value SCUMCapabilityEvidence) SCUMCapabilityEvidence {
|
|
value.AssetDigests = append([]string(nil), value.AssetDigests...)
|
|
return value
|
|
}
|
|
|
|
func CopySCUMSchemaProbeResult(value SCUMSchemaProbeResult) SCUMSchemaProbeResult {
|
|
value.Objects = append([]SCUMSchemaObjectEvidence(nil), value.Objects...)
|
|
for index := range value.Objects {
|
|
value.Objects[index].DeclaredColumns = append([]SCUMSchemaColumnEvidence(nil), value.Objects[index].DeclaredColumns...)
|
|
value.Objects[index].Indexes = append([]SCUMSchemaIndexEvidence(nil), value.Objects[index].Indexes...)
|
|
value.Objects[index].ForeignKeys = append([]SCUMSchemaForeignKeyEvidence(nil), value.Objects[index].ForeignKeys...)
|
|
value.Objects[index].SampleFingerprints = append([]string(nil), value.Objects[index].SampleFingerprints...)
|
|
for idx := range value.Objects[index].Indexes {
|
|
value.Objects[index].Indexes[idx].ColumnHashes = append([]string(nil), value.Objects[index].Indexes[idx].ColumnHashes...)
|
|
}
|
|
}
|
|
return value
|
|
}
|
|
|
|
func CopySCUMLiveDataManifest(value SCUMLiveDataManifest) SCUMLiveDataManifest {
|
|
value.CapabilityGates = append([]SCUMLiveDataCapabilityGateDeclaration(nil), value.CapabilityGates...)
|
|
for index := range value.CapabilityGates {
|
|
value.CapabilityGates[index].RequiredAssetDigests = append([]string(nil), value.CapabilityGates[index].RequiredAssetDigests...)
|
|
}
|
|
return value
|
|
}
|
|
|
|
func sameSCUMBinding(a SCUMBindingIdentity, b SCUMBindingIdentity) bool {
|
|
return a.ServerInstanceID == b.ServerInstanceID && a.RunBindingID == b.RunBindingID && a.RunEndpointID == b.RunEndpointID && a.PluginID == b.PluginID && a.PluginVersion == b.PluginVersion && a.AdapterVersion == b.AdapterVersion && a.GameVersion == b.GameVersion && a.DatabaseIdentity == b.DatabaseIdentity
|
|
}
|
|
|
|
func containsAllStrings(values []string, required []string) bool {
|
|
set := map[string]struct{}{}
|
|
for _, value := range values {
|
|
set[value] = struct{}{}
|
|
}
|
|
for _, value := range required {
|
|
if _, ok := set[value]; !ok {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func safeReason(value string, fallback string) string {
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
return value
|
|
}
|