You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
urfd/reflector/DMRScanner.cpp

319 lines
9.7 KiB

/*
* Copyright (c) 2024 by Thomas A. Early N7TAE
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include "DMRScanner.h"
#include <iostream>
#include <algorithm>
CDMRScanner::CDMRScanner() :
m_SingleMode(false),
m_DefaultTimeout(600),
m_HoldTime(5)
{
m_CurrentScanTG[0] = 0;
m_CurrentScanTG[1] = 0;
}
CDMRScanner::~CDMRScanner()
{
}
void CDMRScanner::Configure(bool singleMode, unsigned int defaultTimeout, unsigned int holdTime)
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
m_SingleMode = singleMode;
m_DefaultTimeout = defaultTimeout;
m_HoldTime = holdTime;
}
void CDMRScanner::UpdateSubscriptions(const std::string& options)
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
parseOptions(options);
}
void CDMRScanner::parseOptions(const std::string& options)
{
// Basic parsing: Options: TS1=4001,4002;TS2=9;AUTO=600
// Split by ';'
if (options.empty()) return;
std::stringstream ss(options);
std::string segment;
unsigned int timeout = m_DefaultTimeout;
// First pass to find AUTO/Timeout if present (to apply to TGs)
// Actually, typically AUTO applies to all in the string.
// Let's parse into a temporary structure first.
std::vector<unsigned int> ts1_tgs;
std::vector<unsigned int> ts2_tgs;
while(std::getline(ss, segment, ';'))
{
size_t eq = segment.find('=');
if (eq != std::string::npos)
{
std::string key = segment.substr(0, eq);
std::string val = segment.substr(eq + 1);
// trim key/val
key.erase(0, key.find_first_not_of(" \t\r\n"));
key.erase(key.find_last_not_of(" \t\r\n") + 1);
if (key == "Options") {
// Recursive parse or just assume val contains the options?
// Example: Options=TS1=4001,4002
// Wait, typically MMDVMHost sends: Options=TS1=4001,4002;TS2=9
// If the entire string is "Options=...", we need to parse 'val'.
// If 'val' contains semicolons, std::getline logic above might have split it already?
// No, std::getline splits on ';' first.
// Case 1: "Options=TS1=4001,4002;TS2=9"
// Segment 1: "Options=TS1=4001,4002". Key="Options", Val="TS1=4001,4002".
// We should parse 'Val'.
// But wait, 'Val' is "TS1=4001,4002". It'looks like a K=V itself?
// Let's recursively call parseOptions(val) or just process val.
// But verify val format.
// Simplest: Check if val starts with TS1/TS2/AUTO ?
parseOptions(val);
}
else if (key == "AUTO") {
try {
timeout = std::stoul(val);
} catch(...) {}
} else if (key == "TS1") {
std::stringstream vs(val);
std::string v;
while(std::getline(vs, v, ',')) {
try { ts1_tgs.push_back(std::stoul(v)); } catch(...) {}
}
} else if (key == "TS2") {
std::stringstream vs(val);
std::string v;
while(std::getline(vs, v, ',')) {
try { ts2_tgs.push_back(std::stoul(v)); } catch(...) {}
}
}
}
}
// Apply (Replace existing usually? Or append? The prompt said "Options string... to configure subscriptions".
// Usually RPTC is a full state update. Let's assume replace for provided timeslots).
// Actually user said "clients can send options... similar to freedmr".
// Freedmr options usually add/set.
// Let's implement ADD logic, but if SingleMode is on, it naturally replaces.
// Wait, typical "Options=" in password means "Set these". So we should probably existing ones if they are re-specified?
// Let's assume for now we ADD/UPDATE.
// Actually, simpler implementation for now: Just Add.
// parseOptions: call AddSubscription with isStatic=true
for (auto tg : ts1_tgs) AddSubscription(tg, 1, timeout, true);
for (auto tg : ts2_tgs) AddSubscription(tg, 2, timeout, true);
}
void CDMRScanner::AddSubscription(unsigned int tgid, int timeslot, unsigned int timeout, bool isStatic)
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
if (tgid == 4000) {
m_Subscriptions[timeslot].clear();
return;
}
if (m_SingleMode) {
m_Subscriptions[timeslot].clear();
isStatic = true; // Single Mode always static
}
// Remove if exists to update
RemoveSubscription(tgid, timeslot);
SSubscription sub;
sub.tgid = tgid;
sub.timeout = timeout;
sub.expiry = (timeout == 0) ? 0 : std::time(nullptr) + timeout;
sub.isStatic = isStatic;
m_Subscriptions[timeslot].push_back(sub);
}
void CDMRScanner::RenewSubscription(unsigned int tgid, int timeslot, unsigned int timeout)
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
if (m_Subscriptions.count(timeslot)) {
for (auto& s : m_Subscriptions.at(timeslot)) {
if (s.tgid == tgid && !s.isStatic) {
s.expiry = (timeout == 0) ? 0 : std::time(nullptr) + timeout;
return;
}
}
}
}
void CDMRScanner::RemoveSubscription(unsigned int tgid, int timeslot)
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
auto& subs = m_Subscriptions[timeslot];
subs.erase(std::remove_if(subs.begin(), subs.end(),
[tgid](const SSubscription& s) { return s.tgid == tgid; }), subs.end());
}
void CDMRScanner::ClearSubscriptions()
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
m_Subscriptions.clear();
m_CurrentScanTG[0] = 0;
m_CurrentScanTG[1] = 0;
}
bool CDMRScanner::IsSubscribed(unsigned int tgid) const
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
std::time_t now = std::time(nullptr);
for (const auto& pair : m_Subscriptions) {
for (const auto& sub : pair.second) {
if (sub.tgid == tgid) {
if (!sub.isStatic && sub.timeout > 0 && now > sub.expiry) continue;
return true;
}
}
}
return false;
}
bool CDMRScanner::IsSubscribed(unsigned int tgid, int timeslot) const
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
std::time_t now = std::time(nullptr);
if (m_Subscriptions.count(timeslot)) {
for (const auto& sub : m_Subscriptions.at(timeslot)) {
if (sub.tgid == tgid) {
if (!sub.isStatic && sub.timeout > 0 && now > sub.expiry) continue;
return true;
}
}
}
return false;
}
bool CDMRScanner::CheckAccess(unsigned int tgid, int slot)
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
if (slot == 0) {
// Check both slots
return CheckAccess(tgid, 1) || CheckAccess(tgid, 2);
}
if (slot < 1 || slot > 2) return false;
int idx = slot - 1;
cleanupExpired();
if (!IsSubscribed(tgid, slot)) return false;
// Scanner Logic for Slot
if (m_CurrentScanTG[idx] != 0) {
if (m_CurrentScanTG[idx] == tgid) {
m_HoldTimer[idx].start();
return true;
}
if (m_HoldTimer[idx].time() < m_HoldTime) {
return false;
}
}
m_CurrentScanTG[idx] = tgid;
m_HoldTimer[idx].start();
return true;
}
void CDMRScanner::cleanupExpired()
{
std::time_t now = std::time(nullptr);
for (auto& pair : m_Subscriptions) {
auto& subs = pair.second;
subs.erase(std::remove_if(subs.begin(), subs.end(),
[now](const SSubscription& s) { return !s.isStatic && s.timeout > 0 && now > s.expiry; }), subs.end());
}
if (m_CurrentScanTG[0] != 0 && !IsSubscribed(m_CurrentScanTG[0], 1)) m_CurrentScanTG[0] = 0;
if (m_CurrentScanTG[1] != 0 && !IsSubscribed(m_CurrentScanTG[1], 2)) m_CurrentScanTG[1] = 0;
}
unsigned int CDMRScanner::GetFirstSubscription() const
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
// Check TS2 first (Standard DMRReflector usually)
if (m_Subscriptions.count(2) && !m_Subscriptions.at(2).empty()) return m_Subscriptions.at(2).front().tgid;
if (m_Subscriptions.count(1) && !m_Subscriptions.at(1).empty()) return m_Subscriptions.at(1).front().tgid;
// Check any
for(const auto& p : m_Subscriptions) {
if (!p.second.empty()) return p.second.front().tgid;
}
return 0;
}
unsigned int CDMRScanner::GetSubscriptionSlot(unsigned int tgid) const
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
// Check TS1
if (m_Subscriptions.count(1)) {
for(const auto& s : m_Subscriptions.at(1)) {
if (s.tgid == tgid) return 1;
}
}
// Check TS2
if (m_Subscriptions.count(2)) {
for(const auto& s : m_Subscriptions.at(2)) {
if (s.tgid == tgid) return 2;
}
}
return 0;
}
std::vector<SSubscription> CDMRScanner::GetSubscriptions(int slot) const
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
if (m_Subscriptions.count(slot)) {
return m_Subscriptions.at(slot);
}
return {};
}
void CDMRScanner::GetActiveTalkgroups(std::vector<unsigned int>& tgs) const
{
std::lock_guard<std::recursive_mutex> lock(m_Mutex);
tgs.clear();
std::time_t now = std::time(nullptr);
// Check TS1
if (m_Subscriptions.count(1)) {
for(const auto& s : m_Subscriptions.at(1)) {
if (!s.isStatic && s.timeout > 0 && now > s.expiry) continue;
tgs.push_back(s.tgid);
}
}
// Check TS2
if (m_Subscriptions.count(2)) {
for(const auto& s : m_Subscriptions.at(2)) {
if (!s.isStatic && s.timeout > 0 && now > s.expiry) continue;
tgs.push_back(s.tgid);
}
}
}

Powered by TurnKey Linux.