fix: normalize marker.match event tag to int to match the REST schema

The WS event carried the tag as str (verbatim from the MARK signal) when
present and as int when falling back to the registry, so one tag reached
consumers as two different values. Parse the signal's decimal (the exact
value we installed via 'mark <bpf> tag <id>') and keep the registered int
on malformed input; None only when neither side carries one.
This commit is contained in:
YueGuobin 2026-09-01 01:34:19 +08:00
parent b72b8b44b4
commit 9262dcfaef
No known key found for this signature in database
2 changed files with 36 additions and 3 deletions

View File

@ -114,13 +114,24 @@ class MarkerListener(asyncio.DatagramProtocol):
# signals that carry no `link=`.
signal_link = link if link and link != "-" else None
# Normalize the tag to int so the event matches the REST schema
# (MarkerCreate.tag is Optional[int]): the signal merely echoes the
# decimal we installed via `mark <bpf> tag <id>`, so parsing cannot
# fail for well-formed signals; a malformed value keeps the registered
# int, and the tag is None only when neither side carries one.
event_tag = registered_tag
if tag and tag != "-":
try:
event_tag = int(tag)
except ValueError:
pass # malformed signal tag: keep the registered value
event = {
"project_id": project_id,
"node_id": node_id,
"link_id": signal_link or link_id,
"filter": filter_name,
# Prefer the value carried in the signal; fall back to the one we registered.
"tag": tag if tag and tag != "-" else registered_tag,
"tag": event_tag,
"ts": ts,
"len": int(length) if length and length.isdigit() else 0,
# Travel direction relative to the capture node (node_id above);

View File

@ -121,7 +121,11 @@ class TestMarkerListener:
assert ev["node_id"] == "n1"
assert ev["link_id"] == "l1"
assert ev["filter"] == "f1"
assert ev["tag"] == "7"
# The event tag is normalized to int to match the REST schema — the
# signal echoes the decimal we installed, so str and int variants of
# the same tag must never reach consumers as different keys.
assert ev["tag"] == 7
assert isinstance(ev["tag"], int)
assert ev["ts"] == pytest.approx(1700000000.123456)
assert ev["len"] == 98
# No dir= in the signal (legacy uBridge) → undirected.
@ -163,6 +167,24 @@ class TestMarkerListener:
lis.datagram_received(b"MARK 2.0 node=n filter=f tag=- len=20\n", None)
assert fmgr.events[0][1]["tag"] == 42
def test_non_numeric_signal_tag_falls_back_to_registered(self):
# A corrupt/unknown signal value must not leak a str tag into the
# event stream — the registry's int wins.
fmgr = FakeMarkerManager()
fmgr.register("p", "n", "f", "l", tag=42)
lis = MarkerListener(fmgr)
lis.connection_made(None)
lis.datagram_received(b"MARK 2.0 node=n filter=f tag=oops len=20\n", None)
assert fmgr.events[0][1]["tag"] == 42
def test_no_tag_anywhere_is_none(self):
fmgr = FakeMarkerManager()
fmgr.register("p", "n", "f", "l", tag=None)
lis = MarkerListener(fmgr)
lis.connection_made(None)
lis.datagram_received(b"MARK 2.0 node=n filter=f tag=- len=20\n", None)
assert fmgr.events[0][1]["tag"] is None
def test_link_in_signal_overrides_registry_link(self):
# Per-link attribution (contract §3.2/§3.3): the signal's `link=` is
# authoritative and must disambiguate links sharing a node+filter —