# FreeDMR 1.x Changelog ## Test Harnesses - Added an in-process deterministic packet harness for `bridge_master.py` routing, state, expiry and packet rewrite checks without UDP. - Added a black-box UDP harness that starts FreeDMR with generated test configs, emulates HBP clients and FBP/OpenBridge peers, captures outbound UDP, supports venv bootstrap, and can model packet loss, duplicates and reordering. - Added synthetic and recorded packet fixture coverage for routing, slot rewrite, byte preservation, malformed packets, cadence and link impairment. ## Configuration and Options - Hardened config parsing for booleans, alias stale time, missing session options and invalid numeric fields. - Added `DIAL_A_TG` to disable private-call dial-a-TG control. - Added `DYNAMIC_TG_ROUTING` to disable automatic creation of unknown conventional TG bridges. - Deprecated `DEFAULT_REFLECTOR` as the system default dial-a-TG setting, while keeping it as a TS2 compatibility alias. - Added canonical per-slot defaults: `DEFAULT_DIAL_TS1` and `DEFAULT_DIAL_TS2`. - Kept legacy OPTIONS aliases `DIAL`, `StartRef` and `DEFAULT_REFLECTOR` mapped to TS2; explicit `DEFAULT_DIAL_TS2` takes precedence. - Added validation/logging so invalid default dial values do not create bridge state and normalize to no default for the active runtime session. - Added compact HBP `OPTIONS` aliases for newer slot/default/timer settings: `DIAL1`, `DIAL2`, `TGTO1`, `TGTO2`, `DIALTO1`, `DIALTO2` and `NDD_SLOT`. - Bounded received HBP `OPTIONS` to 255 bytes. Overlength options are acknowledged but ignored so clients do not reconnect-loop and oversized strings do not enter session state. - Added startup validation for `NETWORK_DIRECT_DIAL_SLOT`; invalid values fall back to the default TS1 setting (`1`) and are logged. ## Dial-a-TG - Made dial-a-TG private-call control slot-local: TS1 controls TS1, TS2 controls TS2. TS1 no longer retunes or disconnects TS2. - Changed dial-a-TG voice prompts to follow the controlling slot: TS1 control emits TG9 on TS1, and TS2 control emits TG9 on TS2. - Preserved TG9 as the RF-visible dial-a-TG talkgroup for both slots. - Added independent timeout controls for conventional TG and dial-a-TG routes, including per-slot overrides. Timer precedence is exact slot/route timer, then slot timer, then route timer, then legacy `TIMER`. - Rejected reserved/control targets consistently for live dial-a-TG and default startup/session configuration. - Preserved the current FreeDMR dial-a-TG policy cap of `999999`. - Ensured FBP route targets created for dial-a-TG remain active across local retunes/disconnects, in line with the mesh "everything everywhere" model. - Added network direct dial: a network-originated group call to a connected repeater/hotspot's own ID can be presented locally as TG9 on the configured slot. This is configured with `NETWORK_DIRECT_DIAL_SLOT` or `NDD_SLOT` (`0` disabled, `1` TG9 TS1, `2` TG9 TS2). - Changed network direct dial to default to enabled on TG9 TS1. Sysops can set `NETWORK_DIRECT_DIAL_SLOT: 0`, or clients can send `NDD_SLOT=0`, to disable it for a session. - Network direct dial is a deliberate, narrow exception where network traffic may activate the local RF TG9 presentation path. It does not override an active or hangtime user-selected dial-a-TG route and does not apply to RF-originated traffic. - Existing HBP `SLOTS` data remains repeater-advertised metadata. FreeDMR1 does not use it as a master-side routing policy for network direct dial; the new explicit setting is `NETWORK_DIRECT_DIAL_SLOT`. ## Data Path - Added optional, reporting-only DMR data/control burst observability behind global `DATA_PACKET_LOGGING`, disabled by default. Records include HBP/FBP source metadata, wrapper and air Slot Type, Color Code, sync, raw payload and generic BPTC logical bytes where applicable. - Added conservative Data Header DPF and CSBK CSBKO/FID labels without reassembly, vendor-profile guesses, delivery responses or packet mutation. CRC/FEC status is explicitly logged as `NOT_CHECKED`. - Moved raw per-burst metadata to DEBUG. INFO now surfaces bounded reconstructed ASCII/UTF-16 text, ETSI/Anytone or Motorola SMS port hints, and complete NMEA RMC coordinates. The collector is capped at 32 streams and 256 bytes per stream. - Kept decoded in-call Talker Alias and embedded-LC GPS at INFO while moving their raw LC bytes and partial TA block detail to DEBUG. - Kept the observer out of voice bursts and bounded to one RX burst with no retained reassembly state. Raw/logical payload logging may expose SMS/GPS content and must be enabled explicitly. - Preserved DMR data forwarding support. - Kept DATA-GATEWAY behavior for protocol-v1 SMS/GPS style handling. - Reported group-addressed data as data/control, not as voice lifecycle. - Suppressed false `GROUP VOICE` timeout reports for data/control packets. - Preserved data-sync/control payload bytes across HBP and FBP forwarding. - Kept `SUB_MAP` last-known-location behavior for unit data routed toward HBP. - Preserved FBP metadata such as source server, source repeater, BER, RSSI and hops according to protocol version. ## Voice Path - Preserved real inbound LC where available and used explicit synthetic LC only as fallback. - Switched normal synthetic group voice LC service options to `0x00`; retained HBLink `0x20` only as an explicit legacy compatibility constant. - Reworked embedded LC handling so same-TG forwarding preserves embedded LC payloads where possible, while TG-mapped forwarding regenerates routing LC. - Added in-call Talker Alias and GPS embedded-LC logging without changing routing or packet mutation behavior. - Added generated prompt lifecycle handling so real RF voice can interrupt a prompt instead of being blocked as busy. - Fixed private dial-a-TG/AMI timeout reporting so private control calls do not emit unmatched group voice lifecycle events. - Made HBP and FBP voice sequence handling modulo-256 with explicit duplicate, loss and stale/out-of-order treatment. - Ensured voice terminators mark streams finished even when reporting is disabled, preventing late same-stream packets from reopening ended streams. ## Mesh and FBP/OpenBridge - Added malformed/truncated `DMRD` and `DMRE` guards before fixed-offset parsing. - Corrected source-quench matching so BCSQ uses the TG namespace visible to the peer being quenched, including dial-a-TG reflector TGs. - Made STUN/BCST handling consistent as a broad FBP traffic gate. - Preserved protocol-version-sensitive FBP/OBP metadata layout. - Fixed outbound FBP `DMRE` version bytes to use the negotiated OpenBridge session version rather than the module default version constant. BCVE remains an advertisement of the local maximum supported version. - Made FBP/OpenBridge per-stream rate-drop protection more tolerant of short trunk bursts, using a sustained over-rate threshold and logging peer/source metadata. The OBP path no longer calls HBP proxy blacklist handling. - Removed obsolete top-level sample configs with invalid `PROTO_VER: 2` examples, the stale HBLink-era sample config, and old non-compose install helpers. Docker Compose is the recommended Docker install path. - Added a concise FreeDMR bridge-instance `PEER` / `XLXPEER` example document so the useful bridge-mode notes from the old HBLink sample remain available in FreeDMR terminology, including the common XLX/D-Star and AMBE transcoder use case. - Renamed the minimal top-level config to `FreeDMR.cfg`, aligned its default timer with the Docker Compose install config, and added current OBP/FBP and ECHO examples. - Aligned the runtime-default `freedmr.cfg` files with `FreeDMR.cfg`, removing stale debug-oriented defaults such as `LOG_LEVEL: TRACE`, `DEBUG_BRIDGES: True` and `DEFAULT_UA_TIMER: 60` from the shipped default config. - Changed the Docker Compose installer to install the maintained `FreeDMR.cfg` from the repository instead of carrying a duplicated embedded config block. - Tidied the Docker Compose installer so `/etc/freedmr` creation is idempotent, downloads fail clearly, and the optional `lastheard` cron chmod is only run when that file exists. - Removed obsolete unreferenced legacy Dockerfile/entrypoint pairs for `noproxy`, `hdstack` and `hbmonv2`; the active Docker path uses the proxy image, supervisor and `BRIDGE_SERVER=1` for bridge instances. - Pinned active Python 3.11 Dockerfiles to the Alpine 3.24 based image and removed unnecessary `apk update` / debug listing steps from the release build Dockerfile. - Documented the `extrastats2` CI job as a frozen reporting/export variant used for global lastheard importer compatibility; this should be folded back into the main reporting/export model rather than removed as stale. - Added tests for FBP keepalive gating, wrong network ID, bad hashes, stale timestamps, max-hop handling, v4 characterization and v1 refusal on v5 links. ## Reporting and Dashboard Compatibility - Kept the legacy report socket opcode model unchanged. - Kept bridge event CSV field order unchanged. - Kept `DEFAULT_REFLECTOR` in runtime config as the effective TS2 default for compatibility with existing config/API/report consumers. - Dial-a-TG generated audio is now visible as TG9 on the controlling RF slot. Other generated audio such as idents and disconnected announcements remains on TG9 TS2. - The latest per-slot default-dial changes do not introduce new report event names or new report event fields. - Expected dashboard impact is low if the dashboard reads event fields and bridge entries by their existing keys. - Downgraded the expected stale static-bridge reset message from an error traceback to a debug log when options reload finds that a bridge key has already been removed. - Compatibility risk: `BRIDGE_SND` pickled bridge state may now include active TS1 `#reflector` entries. A dashboard that assumes every `#reflector` entry is TS2-only may need an update; a dashboard that already respects `TS`, `TGID` and `ACTIVE` should continue to parse it. - TS1 dial-a-TG activity may now appear as RF-visible TG9 on slot 1, which is intentional new behavior. ## Codec and Utility Cleanup - Added `freedmr_dmr_codec.py` for locally tested DMR LC, embedded LC, slot type, BPTC, Hamming, Golay and RS parity helper behavior. - Moved runtime LC generation and byte/int helper usage away from older `dmrutils3` functions where covered. - Added standalone codec tests using fixed fixtures and MMDVMHost-style behavior. - Added focused utility tests for ID/byte helpers and alias lookup. ## API and Support Tools - Replaced the Spyne-based API path with a small bounded HTTP/JSON control API. - Kept API operations as small in-memory control-plane actions. - Added API tests for request size limits, key validation, JSON responses, option storage and reset/kill behavior. - Tidied auxiliary tests for report receiver flags, SQL report insertion, AMI factory state and proxy environment booleans. ## Bridge.py Backports - Backported only directly relevant, already-supported fixes from `bridge_master.py` to `bridge.py`. - Kept `bridge.py` focused on its existing conference-bridge role; did not add FreeDMR master-only features such as dial-a-TG. ## Documentation - Added and updated harness, testing, API and architecture documentation. - Added a concise user-facing OPTIONS cheat sheet suitable for wiki use. - Removed FreeDMR 2 design/ADR artifacts from the FreeDMR1 repository. FreeDMR2 design work now belongs in the separate FreeDMR2 repository only. ## Validation - Current non-UDP test discovery passes. - Focused UDP black-box tests for TS1 dial-a-TG same-slot prompts, generated prompt interruption and slot/route-specific timer config pass when local UDP sockets are permitted. - Live RF validation is still required before treating protocol-visible behavior changes as release-ready.