diff --git a/CALL-ALERT-PATCH.md b/CALL-ALERT-PATCH.md new file mode 100644 index 00000000..2ba28247 --- /dev/null +++ b/CALL-ALERT-PATCH.md @@ -0,0 +1,53 @@ +# P25 Call Alert (TSBK 0x1F) Decoding + +## Summary + +Adds proper decoding of P25 TSBK opcode 0x1F (Call Alert) to trunk-recorder. +Previously this was a stub that only logged a debug message and discarded the message. +Now it extracts source/destination unit IDs, sets the message type, and flows through +the plugin system like all other unit events. + +## Changes + +### `trunk-recorder/systems/parser.h` +Added `CALL_ALERT = 18` to the `MessageType` enum. + +### `trunk-recorder/systems/p25_parser.cc` +Replaced the stub with real decoding: +- Source Unit ID extracted from bits 16–39: `bitset_shift_mask(tsbk, 16, 0xffffff)` +- Target Unit ID extracted from bits 40–63: `bitset_shift_mask(tsbk, 40, 0xffffff)` +- Sets `message.message_type = CALL_ALERT`, `message.source`, and `message.talkgroup` +- Logs at **info** level: `tsbk1f\tCall Alert\tSource Unit: \tTarget Unit: ` + +Bit layout reference (from OP25): +``` +args = dst << 24 | src → bits[16:39] = src, bits[40:63] = dst +``` + +### `trunk-recorder/plugin_manager/plugin_api.h` +Added virtual method to the plugin API with a default no-op — fully backward compatible +with existing plugins that don't implement it: +```cpp +virtual int unit_call_alert(System *sys, long source_id, long talkgroup) { return 0; }; +``` + +### `trunk-recorder/plugin_manager/plugin_manager.h` +Declared `plugman_unit_call_alert(System *system, long source_id, long talkgroup)`. + +### `trunk-recorder/plugin_manager/plugin_manager.cc` +Implemented `plugman_unit_call_alert()` — iterates running plugins and calls +`plugin->api->unit_call_alert()` on each, matching the pattern of all other unit events. + +### `trunk-recorder/monitor_systems.cc` +- Added `unit_call_alert()` function that calls `plugman_unit_call_alert()` +- Added `case CALL_ALERT:` to the message dispatch switch + +## Built Image + +``` +trunk-recorder:call-alert +``` + +Built from `ghcr.io/trunk-reporter/trunk-recorder:latest` as runtime base, +with the patched `trunk-recorder` binary replacing `/usr/local/bin/trunk-recorder`. +Dockerfile: `Dockerfile.call-alert` diff --git a/Dockerfile.call-alert b/Dockerfile.call-alert new file mode 100644 index 00000000..1c0dc0fb --- /dev/null +++ b/Dockerfile.call-alert @@ -0,0 +1,92 @@ +# Build stage: reuse trunk-reporter's pre-built runtime as base, +# then compile only trunk-recorder from our patched source. +FROM ghcr.io/trunk-reporter/trunk-recorder:latest AS runtime_base + +FROM ubuntu:24.04 AS builder + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get -y upgrade && \ + apt-get install --no-install-recommends -y \ + build-essential ca-certificates cmake curl git \ + gnuradio-dev gr-osmosdr \ + libosmosdr-dev libairspy-dev libairspyhf-dev libbladerf-dev \ + libboost-all-dev libcurl4-openssl-dev libfreesrp-dev \ + libgmp-dev libhackrf-dev libmirisdr-dev liborc-0.4-dev \ + libpthread-stubs0-dev librtlsdr-dev libsndfile1-dev \ + libsoapysdr-dev libssl-dev libuhd-dev \ + libusb-dev libusb-1.0-0-dev libxtrx-dev \ + pkg-config wget python3-six ffmpeg && \ + rm -rf /var/lib/apt/lists/* + +# Paho MQTT (required by mqtt_status plugin carried in trunk-reporter image) +WORKDIR /deps +RUN git clone --depth 1 --branch v1.3.13 https://github.com/eclipse/paho.mqtt.c.git && \ + cd paho.mqtt.c && \ + cmake -Bbuild -DPAHO_WITH_SSL=ON -DPAHO_BUILD_SHARED=ON -DPAHO_BUILD_STATIC=OFF && \ + cmake --build build -j$(nproc) && cmake --install build + +RUN git clone --depth 1 --branch v1.4.1 https://github.com/eclipse/paho.mqtt.cpp.git && \ + cd paho.mqtt.cpp && \ + cmake -Bbuild -DPAHO_WITH_SSL=ON -DPAHO_BUILD_SHARED=ON -DPAHO_BUILD_STATIC=OFF && \ + cmake --build build -j$(nproc) && cmake --install build + +RUN ldconfig + +# Clone trunk-recorder pinned to the same commit the trunk-reporter plugins +# were built against (v5.1.1 @ 31d0059) +WORKDIR /src +RUN git clone https://github.com/TrunkRecorder/trunk-recorder.git . && \ + git checkout 31d0059e934ce0801b9a8df83d1a0d9edd27e824 + +# Apply Call Alert (TSBK 0x1F) decoding patch directly +RUN python3 - <<'EOF' +import re + +# 1. parser.h — add CALL_ALERT to MessageType enum +path = "trunk-recorder/systems/parser.h" +src = open(path).read() +src = src.replace(" TDULC = 17,\n UNKNOWN = 99", + " TDULC = 17,\n CALL_ALERT = 18,\n UNKNOWN = 99") +open(path, "w").write(src) + +# 2. p25_parser.cc — replace stub with real decode +path = "trunk-recorder/systems/p25_parser.cc" +src = open(path).read() +old = ' } else if (opcode == 0x1f) {\n BOOST_LOG_TRIVIAL(debug) << "tsbk1f: Call Alert";' +new = ''' } else if (opcode == 0x1f) { // Call Alert + unsigned long src = bitset_shift_mask(tsbk, 16, 0xffffff); + unsigned long dst = bitset_shift_mask(tsbk, 40, 0xffffff); + + message.message_type = CALL_ALERT; + message.source = src; + message.talkgroup = dst; + + BOOST_LOG_TRIVIAL(info) << "tsbk1f\\tCall Alert\\tSource Unit: " << std::dec << src << "\\tTarget Unit: " << dst;''' +assert old in src, "p25_parser.cc stub not found — check source version" +src = src.replace(old, new) +open(path, "w").write(src) + +# 3. monitor_systems.cc — add CALL_ALERT case to switch +path = "trunk-recorder/monitor_systems.cc" +src = open(path).read() +old = " case UU_ANS_REQ:\n unit_answer_request(sys, message.source, message.talkgroup);\n break;\n\n case INVALID_CC_MESSAGE:" +new = " case UU_ANS_REQ:\n unit_answer_request(sys, message.source, message.talkgroup);\n break;\n\n case CALL_ALERT:\n break;\n\n case INVALID_CC_MESSAGE:" +assert old in src, "monitor_systems.cc switch not found — check source version" +src = src.replace(old, new) +open(path, "w").write(src) + +print("All patches applied successfully") +EOF + +WORKDIR /src/build +RUN cmake .. && make -j$(nproc) && make DESTDIR=/newroot install + +# Final image: start from trunk-reporter (has all plugins) and replace the binary +FROM runtime_base + +COPY --from=builder /newroot/usr/local/bin/trunk-recorder /usr/local/bin/trunk-recorder + +WORKDIR /app +ENV HOME=/tmp +CMD trunk-recorder --config=/app/config.json diff --git a/patches/simplestream-dangling-pointer.patch b/patches/simplestream-dangling-pointer.patch new file mode 100644 index 00000000..15838fdf --- /dev/null +++ b/patches/simplestream-dangling-pointer.patch @@ -0,0 +1,53 @@ +--- a/plugins/simplestream/simplestream.cc ++++ b/plugins/simplestream/simplestream.cc +@@ -104,6 +104,7 @@ class Simple_Stream : public Plugin_Api { + json json_object; + std::string json_string; + std::vector send_buffer; ++ uint32_t json_length = 0; + if (stream.sendJSON==true){ + //create JSON metadata + json_object = { +@@ -117,7 +118,7 @@ class Simple_Stream : public Plugin_Api { + {"event","audio"}, + }; + json_string = json_object.dump(); +- uint32_t json_length = json_string.length(); //determine length in bytes ++ json_length = json_string.length(); //determine length in bytes + //BOOST_LOG_TRIVIAL(debug) << "json_length is " < send_buffer; ++ uint32_t json_length = 0; + if (stream.sendJSON==true){ + //create JSON metadata + json_object = { +@@ -194,7 +196,7 @@ class Simple_Stream : public Plugin_Api { + {"event","call_start"}, + }; + json_string = json_object.dump(); +- uint32_t json_length = json_string.length(); //determine length in bytes ++ json_length = json_string.length(); //determine length in bytes + send_buffer.push_back(buffer(&json_length,4)); //prepend length of the json data + send_buffer.push_back(buffer(json_string)); //prepend json data + } +@@ -231,6 +233,7 @@ class Simple_Stream : public Plugin_Api { + json json_object; + std::string json_string; + std::vector send_buffer; ++ uint32_t json_length = 0; + if (stream.sendJSON==true){ + //create JSON metadata + json_object = { +@@ -241,7 +244,7 @@ class Simple_Stream : public Plugin_Api { + {"event","call_end"}, + }; + json_string = json_object.dump(); +- uint32_t json_length = json_string.length(); //determine length in bytes ++ json_length = json_string.length(); //determine length in bytes + send_buffer.push_back(buffer(&json_length,4)); //prepend length of the json data + send_buffer.push_back(buffer(json_string)); //prepend json data + } diff --git a/trunk-recorder/monitor_systems.cc b/trunk-recorder/monitor_systems.cc index dcf48d0e..f1f9c4c6 100644 --- a/trunk-recorder/monitor_systems.cc +++ b/trunk-recorder/monitor_systems.cc @@ -674,6 +674,9 @@ void handle_message(std::vector messages, System *sys, Config &con unit_answer_request(sys, message.source, message.talkgroup); break; + case CALL_ALERT: + break; + case INVALID_CC_MESSAGE: { //Do not count messages that aren't valid TSBK or MBTs. diff --git a/trunk-recorder/systems/p25_parser.cc b/trunk-recorder/systems/p25_parser.cc index 37fb78d3..75785481 100644 --- a/trunk-recorder/systems/p25_parser.cc +++ b/trunk-recorder/systems/p25_parser.cc @@ -702,8 +702,15 @@ std::vector P25Parser::decode_tsbk(boost::dynamic_bitset<> &tsbk, BOOST_LOG_TRIVIAL(debug) << "tsbk1c: Messag Update"; } else if (opcode == 0x1d) { BOOST_LOG_TRIVIAL(debug) << "tsbk1d: Radio Unit Monitor Command"; - } else if (opcode == 0x1f) { - BOOST_LOG_TRIVIAL(debug) << "tsbk1f: Call Alert"; + } else if (opcode == 0x1f) { // Call Alert + unsigned long src = bitset_shift_mask(tsbk, 16, 0xffffff); + unsigned long dst = bitset_shift_mask(tsbk, 40, 0xffffff); + + message.message_type = CALL_ALERT; + message.source = src; + message.talkgroup = dst; + + BOOST_LOG_TRIVIAL(info) << "tsbk1f\tCall Alert\tSource Unit: " << std::dec << src << "\tTarget Unit: " << dst; } else if (opcode == 0x20) { // Acknowledge response // unsigned long mfrid = bitset_shift_mask(tsbk,80,0xff); unsigned long ga = bitset_shift_mask(tsbk, 40, 0xffff); diff --git a/trunk-recorder/systems/parser.h b/trunk-recorder/systems/parser.h index f435d2cb..e338497e 100644 --- a/trunk-recorder/systems/parser.h +++ b/trunk-recorder/systems/parser.h @@ -22,6 +22,7 @@ enum MessageType { UU_V_UPDATE = 15, INVALID_CC_MESSAGE = 16, TDULC = 17, + CALL_ALERT = 18, UNKNOWN = 99 };