From fbc67f8a419319995b8a90aef7fabf34546efb81 Mon Sep 17 00:00:00 2001 From: Dharma Bellamkonda Date: Sun, 7 Jun 2026 16:40:10 -0600 Subject: [PATCH] Fix crash loop when ACMI stream sends zero ReferenceTime DCS2ACMI sends ReferenceTime=0001-01-01T00:00:00Z as a base epoch, encoding actual mission time as a large offset. This date is also Go's zero time.Time, so the IsZero() guard that detects "reference time not yet received" fired on every time frame, causing an infinite reconnect loop against Lima Kilo. Replace the IsZero() check with a dedicated referenceTimeSet bool. Co-Authored-By: Claude Sonnet 4.6 --- pkg/telemetry/streamer.go | 8 +++++++- pkg/telemetry/streamer_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/pkg/telemetry/streamer.go b/pkg/telemetry/streamer.go index 32bd0e91..b2c1341b 100644 --- a/pkg/telemetry/streamer.go +++ b/pkg/telemetry/streamer.go @@ -34,6 +34,10 @@ type streamingClient struct { // referenceTime is the reference point provided in the ACMI data. referenceTime time.Time + // referenceTimeSet tracks whether a ReferenceTime property has been received. It must not be + // replaced by referenceTime.IsZero() because 0001-01-01T00:00:00Z is a valid reference time + // used by some ACMI recorders (e.g. DCS2ACMI) and is also Go's zero time.Time value. + referenceTimeSet bool // referencePoint is the reference point provided in the ACMI data. referencePoint orb.Point // cursorTime is the current frame time, computed by adding the current time frame to the reference time. @@ -333,7 +337,7 @@ func (c *streamingClient) handleTimeFrame(line string) error { if err != nil { return fmt.Errorf("error parsing time frame: %w", err) } - if c.referenceTime.IsZero() { + if !c.referenceTimeSet { return errors.New("time frame received before reference time") } c.cursorTime = c.referenceTime.Add(offset) @@ -353,6 +357,7 @@ func (c *streamingClient) updateGlobalObject(update *objects.Update) error { return fmt.Errorf("error parsing reference time: %w", err) } c.referenceTime = referenceTime + c.referenceTimeSet = true if c.cursorTime.IsZero() { c.cursorTime = c.referenceTime } @@ -450,6 +455,7 @@ func (c *streamingClient) reset() { defer c.lock.Unlock() c.referenceTime = time.Time{} + c.referenceTimeSet = false c.referencePoint = orb.Point{} c.cursorTime = time.Time{} c.state = map[uint64]*objects.Object{} diff --git a/pkg/telemetry/streamer_test.go b/pkg/telemetry/streamer_test.go index 736bfaf4..934b2827 100644 --- a/pkg/telemetry/streamer_test.go +++ b/pkg/telemetry/streamer_test.go @@ -6,10 +6,41 @@ import ( "testing" "time" + "github.com/dharmab/goacmi/v2/parsing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// TestHandleLineAcceptsZeroReferenceTime verifies that a zero-valued +// ReferenceTime (0001-01-01T00:00:00Z) is accepted and does not cause +// subsequent time frames to be rejected. This is regression test for +// a case that sometimes happens on Lima Kilo. +func TestHandleLineAcceptsZeroReferenceTime(t *testing.T) { + t.Parallel() + + const timeFrame = "#62754967308.46" // Observed value from eu.limakilo.net sometimes + lines := []string{ + "FileType=text/acmi/tacview", + "FileVersion=2.2", + "0,ReferenceTime=0001-01-01T00:00:00Z", + "0,ReferenceLongitude=30", + "0,ReferenceLatitude=30", + timeFrame, + } + + client := newStreamingClient(time.Second) + reader := bufio.NewReader(strings.NewReader(strings.Join(lines, "\n") + "\n")) + for range lines { + line, err := readACMILine(reader) + require.NoError(t, err) + require.NoError(t, client.handleLine(line)) + } + + offset, err := parsing.ParseTimeFrame(timeFrame) + require.NoError(t, err) + assert.Equal(t, time.Time{}.Add(offset), client.Time()) +} + func TestReadACMILineContinuationPreservesNextRecord(t *testing.T) { t.Parallel()