test(scum): verify companion dispatch boundaries

This commit is contained in:
npc0-hue
2026-07-29 11:03:06 +08:00
parent acf4e4a8f0
commit 32015a4d9b
6 changed files with 109 additions and 29 deletions
@@ -1,32 +1,17 @@
package companion
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"regexp"
"strings"
"time"
)
// SemanticEventProducerAvailability is intentionally fail-closed. The pinned
// UE4SS reference exposes command dispatch and online chat only; it does not
// expose a versioned server-side login, logout, position, vehicle, or network
// identity producer. Do not add a parser until such a source is versioned.
type SemanticEventProducerAvailability struct {
Available bool
Reason string
}
const semanticEventsSnapshotType = "semantic.events"
const semanticEventsSchemaVersion = "1"
const semanticEventsRetentionSeconds = 7 * 24 * 60 * 60
const semanticEventsMaxRecords = 1000
type SemanticEvent struct { Type string `json:"type"`; OccurredAt time.Time `json:"occurredAt"`; PlayerID string `json:"playerId,omitempty"`; DisplayName string `json:"displayName,omitempty"`; NetworkCorrelation string `json:"networkCorrelation,omitempty"` }
var supportedLoginLine = regexp.MustCompile(`^LOGIN player=([A-Za-z0-9._:-]{1,96}) name=([^\n]{1,80}) at=([0-9TZ:+.-]{20,40})(?: network=([^\s]{1,128}))?$`)
var supportedLogoutLine = regexp.MustCompile(`^LOGOUT player=([A-Za-z0-9._:-]{1,96}) at=([0-9TZ:+.-]{20,40})$`)
// ParseSemanticEvent supports only versioned, allow-listed extension output.
// Unknown formats deliberately yield no event and may be reported as a bounded
// diagnostic by the caller.
func ParseSemanticEvent(line string, serverCorrelationKey []byte) (SemanticEvent, bool) {
if match := supportedLoginLine.FindStringSubmatch(strings.TrimSpace(line)); len(match) != 0 { occurred, err := time.Parse(time.RFC3339, match[3]); if err != nil { return SemanticEvent{}, false }; event := SemanticEvent{Type: "scum.login", PlayerID: match[1], DisplayName: match[2], OccurredAt: occurred}; if match[4] != "" { event.NetworkCorrelation = irreversibleServerCorrelation(serverCorrelationKey, match[4]) }; return event, true }
if match := supportedLogoutLine.FindStringSubmatch(strings.TrimSpace(line)); len(match) != 0 { occurred, err := time.Parse(time.RFC3339, match[2]); if err != nil { return SemanticEvent{}, false }; return SemanticEvent{Type: "scum.logout", PlayerID: match[1], OccurredAt: occurred}, true }
return SemanticEvent{}, false
func VerifiedSemanticEventProducer() SemanticEventProducerAvailability {
return SemanticEventProducerAvailability{
Available: false,
Reason: "no versioned SCUM server-side semantic event producer is installed",
}
}
func irreversibleServerCorrelation(key []byte, source string) string { if len(key) == 0 || source == "" { return "" }; mac := hmac.New(sha256.New, key); _, _ = mac.Write([]byte(source)); return hex.EncodeToString(mac.Sum(nil)) }
func (client *Client) UploadSemanticEvents(ctx context.Context, streamKey string, sequence uint64, events []SemanticEvent) (AcceptedSnapshot, error) { if len(events) == 0 || len(events) > 100 { return AcceptedSnapshot{}, fmt.Errorf("semantic event batch is invalid") }; payload := map[string]any{"events": events}; return client.UploadSnapshot(ctx, Snapshot{Type: semanticEventsSnapshotType, SchemaVersion: semanticEventsSchemaVersion, StreamKey: streamKey, Sequence: sequence, ObservedAt: client.now().UTC(), Payload: payload, KeepForSeconds: semanticEventsRetentionSeconds, MaxRecords: semanticEventsMaxRecords}) }