Add SCUM log sessions and trajectory projections
This commit is contained in:
@@ -3,10 +3,13 @@ package companion
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var scumLoginLogLine = regexp.MustCompile(`^\d{4}\.\d{2}\.\d{2}-\d{2}\.\d{2}\.\d{2}: '([0-9.]+) (\d{1,50}):([^']{1,80})\(\d+\)' logged (in|out)(?: .*)?$`)
|
||||
|
||||
// ConsoleRecord is supplied by Run's stdout/stderr stream, not by the server
|
||||
// execution log. The channel never accepts a file path or a raw log archive.
|
||||
type ConsoleRecord struct {
|
||||
@@ -21,6 +24,7 @@ type SemanticEvent struct {
|
||||
Sequence uint64
|
||||
Type string
|
||||
PlayerID string
|
||||
DisplayName string
|
||||
OccurredAt time.Time
|
||||
NetworkCorrelation string
|
||||
}
|
||||
@@ -62,6 +66,9 @@ func ParseConsoleRecords(serverID string, records []ConsoleRecord, correlationSe
|
||||
return batch
|
||||
}
|
||||
func parseConsoleRecord(record ConsoleRecord, secret string) (SemanticEvent, bool) {
|
||||
if event, ok := parseLoginLogRecord(record, secret); ok {
|
||||
return event, true
|
||||
}
|
||||
fields := strings.Fields(record.Text)
|
||||
if len(fields) < 3 || fields[0] != "SCUM" || (fields[1] != "LOGIN" && fields[1] != "LOGOUT") || !steamID64(fields[2]) {
|
||||
return SemanticEvent{}, false
|
||||
@@ -76,9 +83,26 @@ func parseConsoleRecord(record ConsoleRecord, secret string) (SemanticEvent, boo
|
||||
}
|
||||
return event, true
|
||||
}
|
||||
|
||||
func parseLoginLogRecord(record ConsoleRecord, secret string) (SemanticEvent, bool) {
|
||||
match := scumLoginLogLine.FindStringSubmatch(record.Text)
|
||||
if match == nil || !steamID64(match[2]) {
|
||||
return SemanticEvent{}, false
|
||||
}
|
||||
eventType := "scum.login"
|
||||
if match[4] == "out" {
|
||||
eventType = "scum.logout"
|
||||
}
|
||||
event := SemanticEvent{ServerID: record.ServerID, Sequence: record.Sequence, Type: eventType, PlayerID: match[2], DisplayName: match[3], OccurredAt: record.OccurredAt}
|
||||
if secret != "" {
|
||||
event.NetworkCorrelation = networkCorrelation(record.ServerID, match[1], secret)
|
||||
}
|
||||
return event, true
|
||||
}
|
||||
|
||||
func networkCorrelation(serverID, value, secret string) string {
|
||||
digest := sha256.Sum256([]byte(serverID + "\x00" + secret + "\x00" + value))
|
||||
return hex.EncodeToString(digest[:16])
|
||||
return hex.EncodeToString(digest[:])
|
||||
}
|
||||
func appendDiagnostic(existing []EventDiagnostic, diagnostic EventDiagnostic) []EventDiagnostic {
|
||||
if len(existing) >= 32 {
|
||||
|
||||
@@ -17,4 +17,24 @@ func TestConsoleSemanticEventProducerParsesOnlyBoundedKnownOutput(t *testing.T)
|
||||
if batch.Events[0].NetworkCorrelation == "10.0.0.1" {
|
||||
t.Fatal("raw network value leaked")
|
||||
}
|
||||
if len(batch.Events[0].NetworkCorrelation) != 64 {
|
||||
t.Fatalf("network correlation must be full sha256 hex, got %q", batch.Events[0].NetworkCorrelation)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsoleSemanticEventProducerParsesScumLoginLog(t *testing.T) {
|
||||
observedAt := time.Date(2026, 8, 27, 12, 0, 0, 0, time.UTC)
|
||||
batch := ParseConsoleRecords("server-1", []ConsoleRecord{
|
||||
{ServerID: "server-1", Stream: "stdout", Sequence: 1, OccurredAt: observedAt, Text: "2026.08.27-12.00.00: '10.0.0.2 76561198000000002:Ada(42)' logged in at: X=1 Y=2 Z=3"},
|
||||
{ServerID: "server-1", Stream: "stdout", Sequence: 2, OccurredAt: observedAt.Add(time.Second), Text: "2026.08.27-12.00.01: '10.0.0.2 76561198000000002:Ada(42)' logged out"},
|
||||
}, "fixture-secret")
|
||||
if len(batch.Events) != 2 || batch.Events[0].Type != "scum.login" || batch.Events[1].Type != "scum.logout" {
|
||||
t.Fatalf("login log events not parsed: %+v", batch)
|
||||
}
|
||||
if batch.Events[0].PlayerID != "76561198000000002" || batch.Events[0].DisplayName != "Ada" || len(batch.Events[0].NetworkCorrelation) != 64 {
|
||||
t.Fatalf("login event fields are incomplete: %+v", batch.Events[0])
|
||||
}
|
||||
if batch.Events[0].NetworkCorrelation == "10.0.0.2" {
|
||||
t.Fatal("raw login log IP leaked")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user