package protocol import ( "encoding/json" "fmt" "strings" "time" ) const CurrentProtocolVersion = 1 // MessageType identifies a versioned agent/control-plane message. type MessageType string const ( // Agent → Control Plane TypeAgentHello MessageType = "agent.hello" TypeAgentHeartbeat MessageType = "agent.heartbeat" TypeAgentInventory MessageType = "agent.inventory" TypeServerState MessageType = "server.state" TypeServerMetrics MessageType = "server.metrics" TypeServerLog MessageType = "server.log" TypeServerOperationProgress MessageType = "server.operation.progress" TypeServerOperationComplete MessageType = "server.operation.completed" TypeServerOperationFailed MessageType = "server.operation.failed" // Control Plane → Agent TypeServerProvision MessageType = "server.provision" TypeServerInstall MessageType = "server.install" TypeServerStart MessageType = "server.start" TypeServerStop MessageType = "server.stop" TypeServerKill MessageType = "server.kill" TypeServerDelete MessageType = "server.delete" TypeServerInspect MessageType = "server.inspect" TypeServerCommand MessageType = "server.command" TypeServerBackupPrepare MessageType = "server.backup.prepare" TypeServerBackupRelease MessageType = "server.backup.release" TypeServerFilesList MessageType = "server.files.list" TypeServerFilesRead MessageType = "server.files.read" TypeServerFilesWrite MessageType = "server.files.write" TypeServerFilesUploadDone MessageType = "server.files.upload.complete" TypeServerWorldValidate MessageType = "server.world.validate" TypeServerWorldArchive MessageType = "server.world.archive" TypeServerWorldReplace MessageType = "server.world.replace" TypeServerStorageUpload MessageType = "server.storage.upload" TypeServerStorageDownload MessageType = "server.storage.download" TypeServerAddonInstall MessageType = "server.addon.install" TypeServerAddonRemove MessageType = "server.addon.remove" TypeServerMetricsSub MessageType = "server.metrics.subscribe" TypeServerLogsSubscribe MessageType = "server.logs.subscribe" ) var agentToControlTypes = map[MessageType]struct{}{ TypeAgentHello: {}, TypeAgentHeartbeat: {}, TypeAgentInventory: {}, TypeServerState: {}, TypeServerMetrics: {}, TypeServerLog: {}, TypeServerOperationProgress: {}, TypeServerOperationComplete: {}, TypeServerOperationFailed: {}, } var controlToAgentTypes = map[MessageType]struct{}{ TypeServerProvision: {}, TypeServerInstall: {}, TypeServerStart: {}, TypeServerStop: {}, TypeServerKill: {}, TypeServerDelete: {}, TypeServerInspect: {}, TypeServerCommand: {}, TypeServerBackupPrepare: {}, TypeServerBackupRelease: {}, TypeServerFilesList: {}, TypeServerFilesRead: {}, TypeServerFilesWrite: {}, TypeServerFilesUploadDone: {}, TypeServerWorldValidate: {}, TypeServerWorldArchive: {}, TypeServerWorldReplace: {}, TypeServerStorageUpload: {}, TypeServerStorageDownload: {}, TypeServerAddonInstall: {}, TypeServerAddonRemove: {}, TypeServerMetricsSub: {}, TypeServerLogsSubscribe: {}, } // Envelope is the common wrapper for all protocol messages. type Envelope struct { ProtocolVersion int `json:"protocolVersion"` MessageID string `json:"messageId"` CorrelationID string `json:"correlationId,omitempty"` Type MessageType `json:"type"` Timestamp time.Time `json:"timestamp"` Payload json.RawMessage `json:"payload"` } // OperationMeta is shared metadata for lifecycle operations. type OperationMeta struct { ServerID string `json:"serverId"` Generation int64 `json:"generation"` Deadline *time.Time `json:"deadline,omitempty"` IdempotencyKey string `json:"idempotencyKey,omitempty"` } // AgentHelloPayload is sent when the agent connects to the control plane. type AgentHelloPayload struct { NodeID string `json:"nodeId"` AgentVersion string `json:"agentVersion"` ProtocolVersion int `json:"protocolVersion"` Hostname string `json:"hostname,omitempty"` Capabilities []string `json:"capabilities,omitempty"` } // AgentHeartbeatPayload reports node health and resource usage. type AgentHeartbeatPayload struct { NodeID string `json:"nodeId"` CPUUsagePercent float64 `json:"cpuUsagePercent"` MemoryUsedBytes int64 `json:"memoryUsedBytes"` MemoryTotalBytes int64 `json:"memoryTotalBytes"` DiskUsedBytes int64 `json:"diskUsedBytes"` DiskTotalBytes int64 `json:"diskTotalBytes"` RunningServers int `json:"runningServers"` } // ServerStartPayload instructs the agent to start a game server. type ServerStartPayload struct { OperationMeta DesiredGeneration int64 `json:"desiredGeneration"` } // ServerProvisionPayload instructs the agent to create a Minecraft server container. type ServerProvisionPayload struct { OperationMeta Edition string `json:"edition"` SoftwareFamily string `json:"softwareFamily"` MinecraftVersion string `json:"minecraftVersion"` HostPort int `json:"hostPort"` RamMb int `json:"ramMb"` DataPath string `json:"dataPath"` EulaAccepted bool `json:"eulaAccepted"` } // ServerStopPayload instructs the agent to stop a running game server. type ServerStopPayload struct { OperationMeta } // ServerCommandPayload instructs the agent to run a console command via RCON. type ServerCommandPayload struct { OperationMeta Command string `json:"command"` } // ServerLogsSubscribePayload instructs the agent to stream container logs. type ServerLogsSubscribePayload struct { OperationMeta TailLines int `json:"tailLines"` Follow bool `json:"follow"` } // ServerLogPayload carries a single log line from a game server. type ServerLogPayload struct { OperationMeta Line string `json:"line"` Stream string `json:"stream"` } // ServerFilesListPayload lists files in a server data directory. type ServerFilesListPayload struct { OperationMeta Path string `json:"path"` } // ServerFilesReadPayload reads a file from the server data directory. type ServerFilesReadPayload struct { OperationMeta Path string `json:"path"` MaxBytes int `json:"maxBytes"` } // ServerFilesWritePayload writes content to a file in the server data directory. type ServerFilesWritePayload struct { OperationMeta Path string `json:"path"` Content string `json:"content"` Create bool `json:"create"` } // ServerWorldValidatePayload validates a Minecraft world directory. type ServerWorldValidatePayload struct { OperationMeta WorldName string `json:"worldName"` } // ServerWorldArchivePayload archives a world directory. type ServerWorldArchivePayload struct { OperationMeta WorldName string `json:"worldName"` ArchiveID string `json:"archiveId"` } // ServerStorageUploadPayload uploads a local file to a presigned URL. type ServerStorageUploadPayload struct { OperationMeta LocalPath string `json:"localPath"` UploadURL string `json:"uploadUrl"` } // ServerStorageDownloadPayload downloads a remote file to a local path. type ServerStorageDownloadPayload struct { OperationMeta DownloadURL string `json:"downloadUrl"` LocalPath string `json:"localPath"` } // ServerWorldReplacePayload replaces a world from a local archive. type ServerWorldReplacePayload struct { OperationMeta WorldName string `json:"worldName"` ArchivePath string `json:"archivePath"` } // ServerAddonInstallPayload downloads and installs an addon file. type ServerAddonInstallPayload struct { OperationMeta DownloadURL string `json:"downloadUrl"` RelativePath string `json:"relativePath"` Sha512 string `json:"sha512,omitempty"` } // ServerAddonRemovePayload removes an installed addon file. type ServerAddonRemovePayload struct { OperationMeta RelativePath string `json:"relativePath"` } // FileEntry describes a file or directory in a list response. type FileEntry struct { Name string `json:"name"` Path string `json:"path"` IsDir bool `json:"isDir"` Size int64 `json:"size"` ModifiedAt string `json:"modifiedAt"` } // ServerFilesListResultPayload is the result of a files.list operation. type ServerFilesListResultPayload struct { Entries []FileEntry `json:"entries"` } // ServerFilesReadResultPayload is the result of a files.read operation. type ServerFilesReadResultPayload struct { Path string `json:"path"` Content string `json:"content"` Truncated bool `json:"truncated"` } // ServerOperationResultPayload reports the outcome of a lifecycle operation. type ServerOperationResultPayload struct { OperationMeta ResultCode string `json:"resultCode"` Operation string `json:"operation,omitempty"` Result json.RawMessage `json:"result,omitempty"` ErrorCode string `json:"errorCode,omitempty"` ErrorMessage string `json:"errorMessage,omitempty"` } // ServerStatePayload reports the observed server state. type ServerStatePayload struct { OperationMeta State string `json:"state"` ErrorCode string `json:"errorCode,omitempty"` ErrorMessage string `json:"errorMessage,omitempty"` } // Validate checks envelope fields and known message types. func (e *Envelope) Validate() error { if e == nil { return fmt.Errorf("envelope is nil") } if e.ProtocolVersion < 1 { return fmt.Errorf("protocolVersion must be >= 1") } if e.ProtocolVersion > CurrentProtocolVersion { return fmt.Errorf("unsupported protocolVersion %d", e.ProtocolVersion) } if strings.TrimSpace(e.MessageID) == "" { return fmt.Errorf("messageId is required") } if strings.TrimSpace(string(e.Type)) == "" { return fmt.Errorf("type is required") } if e.Timestamp.IsZero() { return fmt.Errorf("timestamp is required") } if !e.Timestamp.UTC().Equal(e.Timestamp) { return fmt.Errorf("timestamp must be UTC") } if _, ok := agentToControlTypes[e.Type]; !ok { if _, ok := controlToAgentTypes[e.Type]; !ok { return fmt.Errorf("unknown message type: %s", e.Type) } } if len(e.Payload) == 0 { return fmt.Errorf("payload is required") } if !json.Valid(e.Payload) { return fmt.Errorf("payload must be valid JSON") } return nil } // IsAgentToControl returns true when the message flows from agent to control plane. func (t MessageType) IsAgentToControl() bool { _, ok := agentToControlTypes[t] return ok } // IsControlToAgent returns true when the message flows from control plane to agent. func (t MessageType) IsControlToAgent() bool { _, ok := controlToAgentTypes[t] return ok } // DecodeEnvelope parses and validates a JSON envelope. func DecodeEnvelope(data []byte) (*Envelope, error) { var env Envelope if err := json.Unmarshal(data, &env); err != nil { return nil, fmt.Errorf("decode envelope: %w", err) } if err := env.Validate(); err != nil { return nil, err } return &env, nil } // NewEnvelope creates a validated outbound envelope. func NewEnvelope(msgType MessageType, messageID string, payload any) (*Envelope, error) { raw, err := json.Marshal(payload) if err != nil { return nil, fmt.Errorf("marshal payload: %w", err) } env := &Envelope{ ProtocolVersion: CurrentProtocolVersion, MessageID: messageID, Type: msgType, Timestamp: time.Now().UTC(), Payload: raw, } if err := env.Validate(); err != nil { return nil, err } return env, nil }