509 lines
17 KiB
Go
509 lines
17 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 (
|
|
SCUMSchemaProbeStatusSucceeded SCUMCapabilityEvidenceStatus = "succeeded"
|
|
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"
|
|
SCUMSafeErrorInvalidRequest SCUMSafeErrorCode = "invalid_request"
|
|
SCUMSafeErrorTargetUnavailable SCUMSafeErrorCode = "target_unavailable"
|
|
SCUMSafeErrorSourceUnavailable SCUMSafeErrorCode = "source_unavailable"
|
|
SCUMSafeErrorSQLiteOpenFailed SCUMSafeErrorCode = "sqlite_open_failed"
|
|
SCUMSafeErrorSQLiteReadFailed SCUMSafeErrorCode = "sqlite_read_failed"
|
|
SCUMSafeErrorDatabaseBusy SCUMSafeErrorCode = "database_busy"
|
|
SCUMSafeErrorTimeout SCUMSafeErrorCode = "timeout"
|
|
SCUMSafeErrorCancelled SCUMSafeErrorCode = "cancelled"
|
|
SCUMSafeErrorSourceChanged SCUMSafeErrorCode = "source_changed"
|
|
SCUMSafeErrorResultLimitExceeded SCUMSafeErrorCode = "result_limit_exceeded"
|
|
SCUMSafeErrorTemplateMissing SCUMSafeErrorCode = "template_missing"
|
|
SCUMSafeErrorTemplateMismatch SCUMSafeErrorCode = "template_digest_mismatch"
|
|
SCUMSafeErrorParameterInvalid SCUMSafeErrorCode = "parameter_schema_invalid"
|
|
SCUMSafeErrorRowLimitExceeded SCUMSafeErrorCode = "row_limit_exceeded"
|
|
SCUMSafeErrorResultSchemaInvalid SCUMSafeErrorCode = "result_schema_invalid"
|
|
)
|
|
|
|
type SCUMTerminalResultStatus string
|
|
|
|
const (
|
|
SCUMTerminalResultSucceeded SCUMTerminalResultStatus = "succeeded"
|
|
SCUMTerminalResultFailed SCUMTerminalResultStatus = "failed"
|
|
SCUMTerminalResultCancelled SCUMTerminalResultStatus = "cancelled"
|
|
)
|
|
|
|
type SCUMRCONConfirmationStatus string
|
|
|
|
const (
|
|
SCUMRCONConfirmationConfirmed SCUMRCONConfirmationStatus = "confirmed"
|
|
SCUMRCONConfirmationFailed SCUMRCONConfirmationStatus = "failed"
|
|
SCUMRCONConfirmationUnknown SCUMRCONConfirmationStatus = "unknown"
|
|
)
|
|
|
|
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 SCUMSQLiteTemplateBounds struct {
|
|
MaxParameters int
|
|
MaxRows int
|
|
TimeoutMS int
|
|
BusyTimeoutMS int
|
|
MaxResultBytes int
|
|
}
|
|
|
|
func DefaultSCUMSQLiteTemplateBounds() SCUMSQLiteTemplateBounds {
|
|
return SCUMSQLiteTemplateBounds{MaxParameters: 64, MaxRows: 500, TimeoutMS: 5000, BusyTimeoutMS: 250, MaxResultBytes: 1024 * 1024}
|
|
}
|
|
|
|
type SCUMTypedRCONTemplateBounds struct {
|
|
MaxPayloadBytes int
|
|
TimeoutMS int
|
|
MaxResponseBytes int
|
|
MaxConfirmRecords int
|
|
}
|
|
|
|
func DefaultSCUMTypedRCONTemplateBounds() SCUMTypedRCONTemplateBounds {
|
|
return SCUMTypedRCONTemplateBounds{MaxPayloadBytes: 2048, TimeoutMS: 5000, MaxResponseBytes: 16 * 1024, MaxConfirmRecords: 16}
|
|
}
|
|
|
|
type SCUMSchemaProbeRequest struct {
|
|
RequestID string
|
|
JobID string
|
|
Binding SCUMBindingIdentity
|
|
Bounds SCUMSchemaProbeBounds
|
|
RequestedAt time.Time
|
|
}
|
|
|
|
type SCUMSQLiteTemplateRequest struct {
|
|
RequestID string
|
|
JobID string
|
|
Binding SCUMBindingIdentity
|
|
Capability SCUMDataCapability
|
|
TargetKey string
|
|
TemplateKey string
|
|
AdapterVersion string
|
|
RequiredSchemaFingerprint string
|
|
AssetDigest string
|
|
ParameterDigest string
|
|
Parameters map[string]any
|
|
Bounds SCUMSQLiteTemplateBounds
|
|
RequestedAt time.Time
|
|
}
|
|
|
|
type SCUMTypedRCONTemplateRequest struct {
|
|
RequestID string
|
|
JobID string
|
|
Binding SCUMBindingIdentity
|
|
Capability SCUMDataCapability
|
|
TransportKey string
|
|
TargetKey string
|
|
TemplateKey string
|
|
AdapterVersion string
|
|
RequiredSchemaFingerprint string
|
|
AssetDigest string
|
|
PayloadDigest string
|
|
ConfirmationDigest string
|
|
TargetIdentityDigest string
|
|
IdempotencyKey string
|
|
Payload map[string]any
|
|
ReviewReason string
|
|
Bounds SCUMTypedRCONTemplateBounds
|
|
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
|
|
SourceFingerprint string
|
|
SchemaFingerprint string
|
|
ObservedAt time.Time
|
|
ResultDigest string
|
|
Objects []SCUMSchemaObjectEvidence
|
|
SafeError SCUMSafeError
|
|
Limits SCUMSchemaProbeBounds
|
|
}
|
|
|
|
type SCUMSQLiteTemplateResult struct {
|
|
RequestID string
|
|
JobID string
|
|
Binding SCUMBindingIdentity
|
|
Status SCUMTerminalResultStatus
|
|
Capability SCUMDataCapability
|
|
TargetKey string
|
|
TemplateKey string
|
|
AdapterVersion string
|
|
SchemaFingerprint string
|
|
AssetDigest string
|
|
ParameterDigest string
|
|
SourceFingerprint string
|
|
ObservedAt time.Time
|
|
ResultDigest string
|
|
RowCount int
|
|
Rows []map[string]any
|
|
Truncated bool
|
|
SafeError SCUMSafeError
|
|
Limits SCUMSQLiteTemplateBounds
|
|
}
|
|
|
|
type SCUMTypedRCONTemplateResult struct {
|
|
RequestID string
|
|
JobID string
|
|
Binding SCUMBindingIdentity
|
|
Status SCUMTerminalResultStatus
|
|
Capability SCUMDataCapability
|
|
TransportKey string
|
|
TargetKey string
|
|
TemplateKey string
|
|
AdapterVersion string
|
|
SchemaFingerprint string
|
|
AssetDigest string
|
|
PayloadDigest string
|
|
ConfirmationDigest string
|
|
TargetIdentityDigest string
|
|
ObservedAt time.Time
|
|
ResultDigest string
|
|
ResponseDigest string
|
|
ConfirmationStatus SCUMRCONConfirmationStatus
|
|
ConfirmationDigestID string
|
|
SafeSummary string
|
|
SafeError SCUMSafeError
|
|
Limits SCUMTypedRCONTemplateBounds
|
|
}
|
|
|
|
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 CopySCUMSQLiteTemplateRequestPtr(value *SCUMSQLiteTemplateRequest) *SCUMSQLiteTemplateRequest {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
copy := *value
|
|
copy.Parameters = CopySCUMValueMap(value.Parameters)
|
|
return ©
|
|
}
|
|
|
|
func CopySCUMTypedRCONTemplateRequestPtr(value *SCUMTypedRCONTemplateRequest) *SCUMTypedRCONTemplateRequest {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
copy := *value
|
|
copy.Payload = CopySCUMValueMap(value.Payload)
|
|
return ©
|
|
}
|
|
|
|
func CopySCUMSQLiteTemplateResultPtr(value *SCUMSQLiteTemplateResult) *SCUMSQLiteTemplateResult {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
copy := *value
|
|
copy.Rows = CopySCUMRows(value.Rows)
|
|
return ©
|
|
}
|
|
|
|
func CopySCUMTypedRCONTemplateResultPtr(value *SCUMTypedRCONTemplateResult) *SCUMTypedRCONTemplateResult {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
copy := *value
|
|
return ©
|
|
}
|
|
|
|
func CopySCUMValueMap(value map[string]any) map[string]any {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
copy := make(map[string]any, len(value))
|
|
for key, item := range value {
|
|
copy[key] = item
|
|
}
|
|
return copy
|
|
}
|
|
|
|
func CopySCUMRows(rows []map[string]any) []map[string]any {
|
|
if rows == nil {
|
|
return nil
|
|
}
|
|
copy := make([]map[string]any, len(rows))
|
|
for index, row := range rows {
|
|
copy[index] = CopySCUMValueMap(row)
|
|
}
|
|
return copy
|
|
}
|
|
|
|
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 CopySCUMSchemaProbeRequestPtr(value *SCUMSchemaProbeRequest) *SCUMSchemaProbeRequest {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
copy := *value
|
|
return ©
|
|
}
|
|
|
|
func CopySCUMSchemaProbeResultPtr(value *SCUMSchemaProbeResult) *SCUMSchemaProbeResult {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
copy := CopySCUMSchemaProbeResult(*value)
|
|
return ©
|
|
}
|
|
|
|
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
|
|
}
|