STATUS is keyed by stream_id alone and the trimmer only drops a row after
180s idle, so when a peer reuses an id for another destination the second
call lands on the first one's row, fails the TGID check in loop control and
is discarded frame by frame. to_target already evicts a stale row on a
forward leg; ingress did not.
Ingress now evicts it too, once the old row has been idle for 2s so two
genuinely interleaved streams keep their own rows.
Also drops the bogus TS from the loop-control warning (it printed a slot
index from an unrelated loop), gives that branch the once-per-stream guard
its siblings have, and says what the condition is.
Seen on a production bridge: one talkgroup took 116 frames and completed no
calls at all.
Unit data to an individual ID (>= 1000000) was copied to every OpenBridge
(VER > 1) even when the destination was just heard on a local system and the
SUB_MAP lookup already delivers it there. A D-APRS gateway's ARS/LRRP answers
(one per minute per radio) went out on every configured bridge, multiplying
traffic and causing remote masters that mis-classify those frames to create a
dynamic talkgroup named after the radio id.
Skip the OBP fan-out when the SUB_MAP entry for the destination is on a
non-OPENBRIDGE system and younger than UNIT_DATA_LOCAL_SUB_MAX_AGE (900s).
Subscribers learned via an OpenBridge, or stale entries, keep the current
behavior, so a radio that moved to another master stays reachable.
Rescued from #72 (opened and self-closed by pyopower without a merge),
adapted to the current file layout.
Use SUB_MAP's stored peer id for delivery (repeat, unit-data, and
pvt_call_received), add Talker Alias support for private calls, and
report the receiving hotspot to the monitor.
Announcement/TTS injection reuses a real peer's DMR id as rf_src, which made
resolve_voice_peer_id's rf_src fallback (and the inject-only proxy's fuzzy
peer match) misattribute the call to that peer: monitor showed it TX/red and
learned bogus dynamic TGs. Guard both resolvers with a new
synthetic_announcement flag, and add a trailing is_announcement field to
every GROUP VOICE report (CSV and JSON voice_event) for monitor-side use.
A BRIDGES scan can legitimately list the same MASTER on both TS1 and
TS2 for one TG (e.g. an inject-only proxy whose connected hotspots
collectively use both slots). SubscriptionRouter.resolve() kept both
as distinct legs, so dmrd_received called send_to_system twice for
that MASTER per source frame.
Legacy's dumb send_peers() broadcast made that harmless: each
hotspot's own radio silently dropped the slot it didn't want. This
server's send_peers() instead resolves every peer's real listen slot
from its own OPTIONS regardless of the wire slot
(iter_downlink_voice_slots), so the second leg delivered the same
audio a second time to every subscribed peer - doubling the downlink
rate and making unpaced bridges (e.g. ysf2dmr) sound slow. Only
OBP-sourced calls hit this: a MASTER never forwards to itself, so
locally-originated (HBP) calls never produced a redundant leg.
Collapse forward legs to a MASTER/PEER target down to one per
(target_system, target_tgid), keeping distinct legs for OpenBridge
targets and for legitimate TG-translation legs to the same target on
a different TGID.
Route announcements/TTS through synthetic PTT on the proxy MASTER (SERVER_ID
peer, normal dmrd_received forwarding). Emit START/END TX report events for
inject so monitor fans out to SYSTEM-N; configurable server voice DMR_ID.
* fix: audit wave 1 server hygiene refactors
Inject call_later into PlaybackUseCases, move voice config mtime watch to
bootstrap, complete SubscriptionStore port methods, and relocate echo
routing seed to the application layer.
* fix: document dashboard_state in report-v2 schema
Add dashboard_state to report-v2.json with a two-master example fixture,
update public protocol docs for HELLO → STATE_SND connect flow, and drop
the unused TOPOLOGY_JSON HELLO feature token.
* fix: add ReportWire contract tests against report-v2 schema
Assert state_frames and bridge_event_frames output validates against
committed example fixtures and the report-v2 JSON schema.
Fix test imports for echo_seed module relocation.
* fix: align OPTIONS static validity checks across routing and report
Delegate subscription_table validation to peer_options_static_valid so empty
OPTIONS is valid and PASS-mixed strings are rejected consistently.
* fix: OBP DMRE source-server validation without ALLOW_UNREG_ID bypass
Port OPENBRIDGE.validate_id lookup for 6-7 digit source servers so OBP
ingress matches legacy production config (VALIDATE_SERVER_IDS=True).
* fix: audit items 11-13 coverage, infra tests, and warning logs
* fix: add tests/fakes shim for application test decoupling
* fix: per-stream OBP bridge TX legs for concurrent MASTER downlink
When two OBP voice streams share the same MASTER timeslot, stop
flip-flopping the flat TX row so per-peer downlink gates stay stable.
* fix: ruff lint in OBP concurrent streams downlink test
Remove dead code after return and unused start_tx_events variable.
Drop duplicate bridge/parrot modules and develop-only slot contention tests
that do not match the subscription routing harness on trial/downlink-clean.
Normalize all Python sources to the standard ADN copyright block with
complete GPLv3 notice. Add legacy attribution on routing and dmr_utils
ports; drop SemVer wording from changelog and fix an unused test import.
Narrow send_peers and REPEAT fan-out on inject-only proxies using a
precomputed OPTIONS index, cache connected peer count, and debounce
CONFIG_SND pushes to the monitor.
Match legacy routerHBP so ARS/LRRP replies to private subscribers
use pvt_call when SUB_MAP idle check fails, without marking unit data
ingress as voice-busy on the source hotspot.
Replace internal bridge terminology with routing (RoutingUseCases, AclRouter,
routing_table export). SubscriptionStore remains runtime authority with O(1)
indexes for router and downlink filters. Fix STATIC TG parity on OPTIONS/RPTO,
parrot in-band edge cases, and remove per-packet routing_table export from the
hot path that caused high CPU under multi-hotspot OBP load.