Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions CALL-ALERT-PATCH.md
Original file line number Diff line number Diff line change
@@ -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: <src>\tTarget Unit: <dst>`

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`
92 changes: 92 additions & 0 deletions Dockerfile.call-alert
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions patches/simplestream-dangling-pointer.patch
Original file line number Diff line number Diff line change
@@ -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<boost::asio::const_buffer> 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 " <<json_length <<" 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
@@ -180,6 +181,7 @@ class Simple_Stream : public Plugin_Api {
json json_object;
std::string json_string;
std::vector<boost::asio::const_buffer> 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<boost::asio::const_buffer> 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
}
3 changes: 3 additions & 0 deletions trunk-recorder/monitor_systems.cc
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,9 @@ void handle_message(std::vector<TrunkMessage> 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.
Expand Down
11 changes: 9 additions & 2 deletions trunk-recorder/systems/p25_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -702,8 +702,15 @@ std::vector<TrunkMessage> 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);
Expand Down
1 change: 1 addition & 0 deletions trunk-recorder/systems/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enum MessageType {
UU_V_UPDATE = 15,
INVALID_CC_MESSAGE = 16,
TDULC = 17,
CALL_ALERT = 18,
UNKNOWN = 99
};

Expand Down
Loading