* fix: accept NUL-padded RPTC callsigns in login check
Fixed-width RPTC fields may be padded with NUL (ipsc2hbp) or spaces
(MMDVM). str.rstrip() left trailing NULs so matching DB callsigns were
rejected with MSTNAK after a successful passphrase exchange.
* fix: strip NUL padding from RPTO OPTIONS in logs and storage
ipsc2hbp pads the fixed-width RPTO body with NULs; logs showed ^@ noise
and peer CALLSIGN as raw bytes on the options line. Normalize on ingest
and in redact_pass_in_options.
* fix: strip NUL padding from monitor export and shared HBP fields
Centralize fixed-width NUL/space trimming in domain helpers and use them
for dashboard_state peer fields, OPTIONS parsing, proxy self-service, and
remaining CALLSIGN log lines.
Skip ensure_obp_source_for_tg_store on OBP voice bursts when indexed
store lookup shows an ACTIVE TS1 source for the TG table, avoiding two
full subscription snapshots on every packet under concurrent OBP load.
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.
Restrict stream trimmer END,RX/END,TX to ingress rows only so BCSQ-quenched
forward legs no longer cascade END to still-active peers; drop stale forward
STATUS before START,TX on stream_id reuse.
* feat: poll peer_dynamic_tgs.need_reload for dynamic TG purge
Proxy send_opts and fallback loop apply TG-4000-equivalent reset when the
monitor sets need_reload, with migration 006 and restore filter updates.
* chore: fix import order in voice subscription plan test
* 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.
* fix: raise UDP SO_RCVBUF on voice listeners (C-LOCAL)
Apply a 4 MB receive buffer on system and proxy UDP sockets to reduce
kernel RcvbufErrors under OBP load; size is configurable via GLOBAL.UDP_RCVBUF.
* fix: exclude byte-identical duplicates from HBP rate counter (A.2)
Check lastData before incrementing the ingress packet counter so compressed
duplicate bursts do not trigger legitimate RATE DROP on call start.
* fix: duplicate-safe TA embed phase and REPEAT VHEAD DMRA (B)
Ignore byte-identical B-E embed bursts so duplicate uplinks do not desync the
TA phase machine, and re-emit DMRA on every VHEAD on the REPEAT path.
* chore: document echo point-to-point and logged_in reconciliation (EN/ES)
Document multi-hotspot echo/service delivery via RX_PEER, the lst_seen
logged_in reconcile loop, and cross-links between user and dev guides.
Echo (TG 9990-9999) must be point-to-point: only the originating hotspot
receives the playback. Two bugs in the inject-only proxy broke this:
Data plane (udp_hbp.py): _peer_should_receive_dmrd had a fuzzy-match
fallback (peer_id // 100 == rf_src) for special TGs that leaked the
first VHEAD to every hotspot sharing the user's DMR-id prefix. Removed
the fallback so echo delivers only to the exact RX_PEER.
Report plane (monitor_topology.py): _echo_tx_target_peer resolved the
monitor chip peer via fuzzy rf_src matching, which picked the wrong
hotspot when a user has several radios sharing a base id (e.g. rf_src
7140023 matched peer 714002301 instead of the real originator
714000103). Now resolves from STATUS[slot].RX_PEER when available.
After a server restart, Clients.logged_in stayed 1 for peers no longer
connected, letting the monitor authenticate disconnected hotspots via
login-by-ip. Replace the hourly clean_tbl (24h idle sweep) with a
reconcile in the existing 120s lst_seen loop: connected peers get
logged_in=1, the rest 0. The lst_seen loop now starts with now=True so
its first tick at boot clears all stale flags immediately.
* fix: add shared PASS= redaction for OPTIONS log lines
Mask PASS= secrets in RPTO/OPTIONS logging to avoid leaking hotspot
passwords in log files.
* fix: rework self-service RPTO to distinguish PASS, empty, and OPTIONS
Three RPTO cases now handled explicitly:
- PASS=password: store hash, fetch OPTIONS from DB (unchanged)
- Empty payload: DB is the authority, fetch OPTIONS from DB
- OPTIONS with content: hotspot is the authority, pass through to master
When the hotspot does not send PASS, the stored password is cleared
(NULL) so only IP auto-login works — password login is impossible
with a NULL hash, matching the legacy dashboard proxy behaviour.
Also removes the noisy DEBUG log on RPTC ("not in mysql_option_peers")
that fired on every normal login, and adds clear_psswd action to the
MySQL repository for explicit NULL writes.
* fix: elevate proxy attach rejection to WARNING for RPTC/RPTO
Control commands (RPTC/RPTO) rejected by attach should be visible at
INFO level, not hidden behind a debug flag, since they indicate a peer
sending configuration before the login exchange completed.
* fix: remove noisy CALL RX log on OBP DMRE voice header
The log fired on every DMRE VHEAD packet from each OBP peer, producing
duplicate entries for the same stream (multiple OBP peers relay the
same call). The existing CALL RX logs for DMRD and OBP v0/v1 paths
already cover stream start diagnostics.
* fix: fetch DB options when hotspot sends no RPTO after RPTC
Some hotspots complete the RPTC (repeater configuration) step but never
send an RPTO packet, leaving the peer without any OPTIONS applied. The
legacy proxy used a 10s timer after RPTC to fetch OPTIONS from MySQL in
that case; restore that behaviour here.
When RPTC arrives and the peer is not already in _mysql_option_peers,
schedule a 10s fallback timer. If no RPTO arrives before it fires, treat
the peer the same as an empty RPTO (BD is the authority) and fetch
OPTIONS from MySQL. If an RPTO does arrive, the existing cancel paths
abort the timer.
Add a dedicated public page explaining routing, contention, timers,
SINGLE mode, static/dynamic TGs, silent activation, slot mapping and
OBP parity. Cross-link from behaviour-and-timers, bridges-and-talkgroups,
introduction and special-numbers, and register it in both mkdocs navs.