diff --git a/cotlib.go b/cotlib.go index 2897b98..e900a6a 100644 --- a/cotlib.go +++ b/cotlib.go @@ -2201,6 +2201,14 @@ func (e *Event) ToXML() ([]byte, error) { buf.WriteString(escapeAttr(c.Callsign)) buf.WriteByte('"') } + // endpoint is what the TAK server uses to register the + // callsign->connection mapping; dropping it broke inbound DM + // routing to a self-SA presence (INFR-207 / INFR-206). + if c.Endpoint != "" { + buf.WriteString(` endpoint="`) + buf.WriteString(escapeAttr(c.Endpoint)) + buf.WriteByte('"') + } buf.WriteString("/>\n") } if g := e.Detail.Group; g != nil { diff --git a/event_schema_error_test.go b/event_schema_error_test.go index 879f3f4..e29b56a 100644 --- a/event_schema_error_test.go +++ b/event_schema_error_test.go @@ -1,3 +1,9 @@ +//go:build !novalidator + +// This test exercises the validator schema-init error path (initErr / +// eventPointSchema), which only exists in the cgo validator build +// (event_schema.go is !novalidator). Without this tag it fails to compile +// under -tags novalidator. package cotlib import ( diff --git a/roundtrip_test.go b/roundtrip_test.go index faa0177..c5fcb24 100644 --- a/roundtrip_test.go +++ b/roundtrip_test.go @@ -2,9 +2,50 @@ package cotlib import ( "context" + "strings" "testing" ) +// TestContactEndpointRoundTrip guards INFR-207: ToXML() was a hand-written +// serializer that emitted but dropped the endpoint attribute, +// even though Contact.Endpoint is set. The TAK server keys the callsign-> +// connection mapping off that endpoint, so dropping it silently broke inbound +// direct-message routing to a self-SA presence (INFR-206). The endpoint must +// both appear in the serialized XML and survive a parse round-trip. +func TestContactEndpointRoundTrip(t *testing.T) { + orig, err := NewEvent("EP1", "a-f-G-U-C", 1.0, 2.0, 0) + if err != nil { + t.Fatalf("new event: %v", err) + } + orig.Detail = &Detail{ + Contact: &Contact{Callsign: "NERVA", Endpoint: "*:-1:stcp"}, + } + xmlData, err := orig.ToXML() + if err != nil { + t.Fatalf("marshal: %v", err) + } + ReleaseEvent(orig) + + if !strings.Contains(string(xmlData), `endpoint="*:-1:stcp"`) { + t.Fatalf("ToXML dropped the contact endpoint attribute: %s", xmlData) + } + + evt, err := UnmarshalXMLEvent(context.Background(), xmlData) + if err != nil { + t.Fatalf("unmarshal: %v", err) + } + if evt.Detail == nil || evt.Detail.Contact == nil { + t.Fatalf("no contact after round-trip: %#v", evt) + } + if got := evt.Detail.Contact.Endpoint; got != "*:-1:stcp" { + t.Errorf("endpoint not preserved through round-trip: got %q want %q", got, "*:-1:stcp") + } + if got := evt.Detail.Contact.Callsign; got != "NERVA" { + t.Errorf("callsign not preserved through round-trip: got %q want %q", got, "NERVA") + } + ReleaseEvent(evt) +} + func TestUnmarshalXMLEventRoundTrip(t *testing.T) { orig, err := NewEvent("RT1", "a-f-G", 10.0, 20.0, 0) if err != nil {