Files
Cort BuffingtonandClaude Opus 5 046c47b7d0 Group calls only, per-slot lanes, and N instances per process
Three changes, all driven by the same design conversation.

GROUP CALLS ONLY. Private calls to the talkback's radio ID are now
ignored. Unit routing draws on a globally administered namespace and is
unbounded -- a private call to an unknown ID floods until located -- so
reachability-by-ID would require a unique registered radio ID for every
instance anyone ever deploys, for something that is neither a radio nor
a repeater. BrandMeister can do it because it is the sole authority over
its own namespace; these tools assume an internet of independent
routers. Talkgroups are operator-scoped and bounded by the operator's
own rules, so they cost nobody else anything.

Consequence: the radio ID never needs to be reachable, so one ID an
operator already owns serves every instance they run.

PER-SLOT LANES. The previous version had a single global capture buffer,
so two simultaneous callers -- one per timeslot, which the wire delivers
routinely -- reset it on every interleaved packet and captured nothing.
That is the same defect the 2019 Python playback.py had. Capture and
replay are now per timeslot and fully independent. Within a lane, a
second stream no longer supersedes unconditionally: first-come-wins
unless the current capture has gone quiet, so interleaving can never
thrash the buffer.

One talkgroup per slot, deliberately: a slot carries one call at a time,
so a list would advertise capacity that does not exist.

N INSTANCES. One process now runs several independent connections, each
with its own radio ID, server, and talkgroups. The purpose is one
instance per HBlink3 *system*, so each system gets a private talkback
without building a bridge to multiplex everyone into one engine.
Bridging into a single instance remains supported. Two instances sharing
a radio ID on the same server is rejected at startup.

Adds toml_sections() to the vendored reader to discover [instance.<name>]
sections, since it has no array-of-tables.

Tests: rewrite conformance 566 checks; new test_lanes covers lane
isolation, same-lane non-thrash, the ingress gate, and the capture
ceiling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 15:55:00 -05:00

105 lines
3.5 KiB
Makefile

# dmr-talkback — DMR voice test / talkback endpoint
CC ?= cc
CFLAGS ?= -std=c11 -O2 -Wall -Wextra -D_DEFAULT_SOURCE
LDFLAGS ?= -lm
SRC_DIR := src
DMR_DIR := src/dmr
SOURCES := \
$(SRC_DIR)/main.c \
$(SRC_DIR)/config.c \
$(SRC_DIR)/toml.c \
$(SRC_DIR)/log.c \
$(SRC_DIR)/eventloop.c \
$(SRC_DIR)/net.c \
$(SRC_DIR)/crypto.c \
$(SRC_DIR)/hbp.c \
$(SRC_DIR)/capture.c \
$(SRC_DIR)/replay.c \
$(DMR_DIR)/dmr_bits.c \
$(DMR_DIR)/dmr_const.c \
$(DMR_DIR)/hamming.c \
$(DMR_DIR)/crc.c \
$(DMR_DIR)/rs129.c \
$(DMR_DIR)/golay.c \
$(DMR_DIR)/bptc.c \
$(DMR_DIR)/ambe.c
OBJECTS := $(SOURCES:.c=.o)
BIN := talkback
DMR_SOURCES := $(filter $(DMR_DIR)/%,$(SOURCES))
.PHONY: all clean test install uninstall
all: $(BIN)
$(BIN): $(OBJECTS)
$(CC) $(CFLAGS) -o $@ $(OBJECTS) $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
# FHS install (run as root: sudo make install)
# binary -> $(PREFIX)/bin/talkback (default /usr/local/bin)
# config -> $(SYSCONFDIR)/talkback/ (default /etc/talkback)
# unit -> $(UNITDIR)/talkback.service (default /lib/systemd/system)
# The live config is NEVER overwritten; the sample is always refreshed.
# install does NOT enable or start the service.
PREFIX ?= /usr/local
SYSCONFDIR ?= /etc
UNITDIR ?= /lib/systemd/system
CONFDIR := $(SYSCONFDIR)/talkback
UNIT := $(UNITDIR)/talkback.service
install: $(BIN)
install -d $(DESTDIR)$(PREFIX)/bin
install -m 0755 $(BIN) $(DESTDIR)$(PREFIX)/bin/talkback
install -d $(DESTDIR)$(CONFDIR)
install -m 0644 talkback.toml.sample $(DESTDIR)$(CONFDIR)/talkback.toml.sample
@if [ -f $(DESTDIR)$(CONFDIR)/talkback.toml ]; then \
echo "Preserving existing $(DESTDIR)$(CONFDIR)/talkback.toml (not overwritten)"; \
else \
install -m 0600 talkback.toml.sample $(DESTDIR)$(CONFDIR)/talkback.toml; \
echo "Installed fresh $(DESTDIR)$(CONFDIR)/talkback.toml — edit it before starting"; \
fi
install -d $(DESTDIR)$(UNITDIR)
install -m 0644 talkback.service $(DESTDIR)$(UNIT)
-systemctl daemon-reload
@echo
@echo "Installed. NOT enabled/started. Edit $(CONFDIR)/talkback.toml then:"
@echo " sudo systemctl enable --now talkback"
uninstall:
-systemctl disable --now talkback
-rm -f $(DESTDIR)$(PREFIX)/bin/talkback
-rm -f $(DESTDIR)$(UNIT)
-systemctl daemon-reload
@echo "Removed binary and unit. Left $(CONFDIR) intact (delete manually if desired)."
# Sources the tests link against (no main, no net). The HBP client is stubbed
# inside each test.
TEST_BASE := $(SRC_DIR)/log.c $(SRC_DIR)/eventloop.c $(DMR_SOURCES)
# test_rewrite stubs the instance accessors, so it links replay.c without capture.c.
TEST_REWRITE := $(SRC_DIR)/replay.c $(TEST_BASE)
# test_lanes drives the real capture path, so it needs both.
TEST_LANES := $(SRC_DIR)/capture.c $(SRC_DIR)/replay.c $(TEST_BASE)
# test_rewrite: the loopback-identity conformance vector — replayed AMBE is
# bit-identical to captured AMBE, headers are rewritten, and the LC in both
# carriers decodes back to the new addressing.
# test_lanes: the concurrency model — TS1 and TS2 capture independently and
# simultaneously, one lane never thrashes, and the ingress gate holds.
test: tests/test_rewrite.c tests/test_lanes.c $(TEST_LANES)
$(CC) $(CFLAGS) -I$(SRC_DIR) -o /tmp/talkback_test_rewrite \
tests/test_rewrite.c $(TEST_REWRITE) $(LDFLAGS)
/tmp/talkback_test_rewrite
@echo
$(CC) $(CFLAGS) -I$(SRC_DIR) -o /tmp/talkback_test_lanes \
tests/test_lanes.c $(TEST_LANES) $(LDFLAGS)
/tmp/talkback_test_lanes
clean:
rm -f $(OBJECTS) $(BIN)