add Tier III UDT and registration authentication;

pull/133/head
Bryan Biedenkapp 1 month ago
parent dbc30e97b4
commit 536997ac10

@ -194,6 +194,9 @@ protocols:
slot: 1
# Flag indicating whether or not the source ID validation before granting disabled.
disableGrantSourceIdCheck: false
# Require ETSI DMR authentication before accepting unit registration. The 16-byte
# authentication key is requested from the upstream FNE through the existing LLA key path.
requireAuthForReg: false
# Flag indicating whether or not network calls will generate a channel grant.
# (This applies only in conventional operations where channel granting is utilized and RF-only talkgroup

@ -471,6 +471,7 @@ namespace dmr
AHOY = 0x1CU, //!< AHOY - Enquiry from TSCC
RAND = 0x1FU, //!< (ETSI) RAND - Random Access / (DMRA) CALL ALRT - Call Alert
ACK_RSP = 0x20U, //!< ACK RSP - Acknowledge Response
ACK_RSP_U = 0x21U, //!< ACK RSP U - Acknowledge Response from Unit
EXT_FNCT = 0x24U, //!< (DMRA) EXT FNCT - Extended Function
NACK_RSP = 0x26U, //!< NACK RSP - Negative Acknowledgement Response
BROADCAST = 0x28U, //!< BCAST - Announcement PDU

@ -61,11 +61,11 @@ DataHeader::DataHeader() :
m_rspStatus(0U),
m_srcPort(0U),
m_dstPort(0U),
m_UDTO(0U),
m_bptc(),
m_data(nullptr),
m_SF(false),
m_PF(false),
m_UDTO(0U)
m_PF(false)
{
m_data = new uint8_t[DMR_LC_HEADER_LENGTH_BYTES];
::memset(m_data, 0x00U, DMR_LC_HEADER_LENGTH_BYTES);

@ -192,13 +192,17 @@ namespace dmr
*/
DECLARE_PROPERTY(uint8_t, dstPort, DstPort);
/**
* @brief Unified Data Transport opcode (UDTHD/UDTHU).
*/
DECLARE_PROPERTY(uint8_t, UDTO, UDTO);
private:
edac::BPTC19696 m_bptc;
uint8_t* m_data;
bool m_SF;
bool m_PF;
uint8_t m_UDTO;
/**
* @brief Internal helper to copy the class.

@ -400,7 +400,7 @@ void CSBK::copy(const CSBK& data)
m_proxy = data.m_proxy;
m_response = data.m_response;
m_response = data.m_reason;
m_reason = data.m_reason;
m_siteOffsetTiming = data.m_siteOffsetTiming;

@ -119,7 +119,10 @@ std::unique_ptr<CSBK> CSBKFactory::createCSBK(const uint8_t* data, DataType::E d
return decode(new CSBK_NACK_RSP(), data, dataType);
/** Tier 3 */
case CSBKO::AHOY:
return decode(new CSBK_AHOY(), data, dataType);
case CSBKO::ACK_RSP:
case CSBKO::ACK_RSP_U:
return decode(new CSBK_ACK_RSP(), data, dataType);
case CSBKO::BROADCAST:
return decode(new CSBK_BROADCAST(), data, dataType);

@ -25,6 +25,7 @@
#include "common/dmr/DMRDefines.h"
#include "common/dmr/lc/CSBK.h"
#include "common/dmr/lc/csbk/CSBK_ACK_RSP.h"
#include "common/dmr/lc/csbk/CSBK_AHOY.h"
#include "common/dmr/lc/csbk/CSBK_ALOHA.h"
#include "common/dmr/lc/csbk/CSBK_BROADCAST.h"
#include "common/dmr/lc/csbk/CSBK_BSDWNACT.h"

@ -43,8 +43,9 @@ bool CSBK_ACK_RSP::decode(const uint8_t* data)
ulong64_t csbkValue = CSBK::toValue(csbk);
m_GI = (((csbkValue >> 56) & 0xFFU) & 0x40U) == 0x40U; // Group/Individual Flag
m_reason = (uint8_t)((csbkValue >> 33) & 0xFFU); // Reason Code
m_response = (uint8_t)((csbkValue >> 57U) & 0x7FU); // Response Information
m_GI = (m_response & 0x40U) == 0x40U; // Group/Individual Flag
m_reason = (uint8_t)((csbkValue >> 49U) & 0xFFU); // Reason Code
m_dstId = (uint32_t)((csbkValue >> 24) & 0xFFFFFFU); // Target Radio Address
m_srcId = (uint32_t)(csbkValue & 0xFFFFFFU); // Source Radio Address
@ -59,15 +60,14 @@ void CSBK_ACK_RSP::encode(uint8_t* data)
ulong64_t csbkValue = 0U;
if (m_reason == ReasonCode::TS_ACK_RSN_REG) {
csbkValue = 0U;
} else {
csbkValue = (m_GI ? 0x40U : 0x00U) + // Source Type
(s_siteData.siteId() & 0x3FU); // Net + Site LSB
}
csbkValue = (csbkValue << 7) + (m_response & 0x7FU); // Response Information
uint8_t response = m_response & 0x7FU;
if (m_reason != ReasonCode::TS_ACK_RSN_REG && m_GI)
response |= 0x40U;
csbkValue = response; // Response Information
csbkValue = (csbkValue << 8) + (m_reason & 0xFFU); // Reason Code
csbkValue = (csbkValue << 25) + m_dstId; // Target Radio Address
csbkValue = (csbkValue << 1); // Reserved
csbkValue = (csbkValue << 24) + (m_dstId & 0xFFFFFFU); // Target Radio Address
csbkValue = (csbkValue << 24) + m_srcId; // Source Radio Address
std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue);

@ -0,0 +1,105 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2026 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
#include "dmr/lc/csbk/CSBK_AHOY.h"
using namespace dmr::defines;
using namespace dmr::lc;
using namespace dmr::lc::csbk;
#include <cassert>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a new instance of the CSBK_AHOY class. */
CSBK_AHOY::CSBK_AHOY() : CSBK(),
m_serviceOptionsMirror(0U),
m_serviceKindFlag(false),
m_ambientListening(false),
m_appendedBlocks(0U),
m_serviceKind(0U)
{
m_CSBKO = CSBKO::AHOY;
}
/* Decode a control signalling block. */
bool CSBK_AHOY::decode(const uint8_t* data)
{
assert(data != nullptr);
uint8_t csbk[DMR_CSBK_LENGTH_BYTES];
::memset(csbk, 0x00U, DMR_CSBK_LENGTH_BYTES);
bool ret = CSBK::decode(data, csbk);
if (!ret)
return false;
ulong64_t csbkValue = CSBK::toValue(csbk);
m_serviceOptionsMirror = (uint8_t)((csbkValue >> 57U) & 0x7FU); // 7-bit Service Options Mirror
m_serviceKindFlag = ((csbkValue >> 56U) & 0x01U) != 0U; // Service Kind Flag
m_ambientListening = ((csbkValue >> 55U) & 0x01U) != 0U; // Ambient Listening Flag
m_GI = ((csbkValue >> 54U) & 0x01U) != 0U; // GI Flag
m_appendedBlocks = (uint8_t)((csbkValue >> 52U) & 0x03U); // Appended Blocks
m_serviceKind = (uint8_t)((csbkValue >> 48U) & 0x0FU); // Service Kind
m_dstId = (uint32_t)((csbkValue >> 24U) & 0xFFFFFFU); // Destination ID
m_srcId = (uint32_t)(csbkValue & 0xFFFFFFU); // Source ID
return true;
}
/* Encode a control signalling block. */
void CSBK_AHOY::encode(uint8_t* data)
{
assert(data != nullptr);
ulong64_t csbkValue = 0U;
csbkValue = m_serviceOptionsMirror & 0x7FU; // 7-bit Service Options Mirror
csbkValue = (csbkValue << 1U) | (m_serviceKindFlag ? 1U : 0U); // Service Kind Flag
csbkValue = (csbkValue << 1U) | (m_ambientListening ? 1U : 0U); // Ambient Listening Flag
csbkValue = (csbkValue << 1U) | (m_GI ? 1U : 0U); // GI Flag
csbkValue = (csbkValue << 2U) | (m_appendedBlocks & 0x03U); // Appended Blocks
csbkValue = (csbkValue << 4U) | (m_serviceKind & 0x0FU); // Service Kind
csbkValue = (csbkValue << 24U) | (m_dstId & 0xFFFFFFU); // Destination ID
csbkValue = (csbkValue << 24U) | (m_srcId & 0xFFFFFFU); // Source ID
std::unique_ptr<uint8_t[]> csbk = CSBK::fromValue(csbkValue);
CSBK::encode(data, csbk.get());
}
/* Returns a string that represents the current CSBK. */
std::string CSBK_AHOY::toString()
{
return std::string("CSBKO, AHOY (Enquiry from TSCC)");
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/* Internal helper to copy the the class. */
void CSBK_AHOY::copy(const CSBK_AHOY& data)
{
CSBK::copy(data);
m_serviceOptionsMirror = data.m_serviceOptionsMirror;
m_serviceKindFlag = data.m_serviceKindFlag;
m_ambientListening = data.m_ambientListening;
m_appendedBlocks = data.m_appendedBlocks;
m_serviceKind = data.m_serviceKind;
}

@ -0,0 +1,91 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2026 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file CSBK_AHOY.h
* @ingroup dmr_csbk
* @file CSBK_AHOY.cpp
* @ingroup dmr_csbk
*/
#if !defined(__DMR_LC_CSBK__CSBK_AHOY_H__)
#define __DMR_LC_CSBK__CSBK_AHOY_H__
#include "common/Defines.h"
#include "common/dmr/lc/CSBK.h"
namespace dmr
{
namespace lc
{
namespace csbk
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements the C_AHOY enquiry from a TSCC.
* @ingroup dmr_csbk
*/
class DVM_COMMON_API CSBK_AHOY : public CSBK {
public:
/**
* @brief Initializes a new instance of the CSBK_AHOY class.
*/
CSBK_AHOY();
/**
* @brief Decodes a control signalling block.
* @param[in] data Buffer containing a CSBK to decode.
*/
bool decode(const uint8_t* data) override;
/**
* @brief Encodes a control signalling block.
* @param[out] data Buffer to encode a CSBK.
*/
void encode(uint8_t* data) override;
/**
* @brief Returns a string that represents the current CSBK.
* @returns std::string String representation of the CSBK.
*/
std::string toString() override;
public:
/**
* @brief Service Options.
*/
DECLARE_PROPERTY(uint8_t, serviceOptionsMirror, ServiceOptionsMirror);
/**
* @brief Service Kind Flag.
*/
DECLARE_PROPERTY(bool, serviceKindFlag, ServiceKindFlag);
/**
* @brief Ambient Listening.
*/
DECLARE_PROPERTY(bool, ambientListening, AmbientListening);
/**
* @brief Appended Blocks.
*/
DECLARE_PROPERTY(uint8_t, appendedBlocks, AppendedBlocks);
/**
* @brief Service Kind.
*/
DECLARE_PROPERTY(uint8_t, serviceKind, ServiceKind);
DECLARE_COPY(CSBK_AHOY);
};
} // namespace csbk
} // namespace lc
} // namespace dmr
#endif // __DMR_LC_CSBK__CSBK_AHOY_H__

@ -12,6 +12,7 @@
#include "common/dmr/acl/AccessControl.h"
#include "common/dmr/lc/CSBK.h"
#include "common/dmr/lc/csbk/CSBKFactory.h"
#include "common/RC4Crypto.h"
#include "common/Log.h"
#include "dmr/Control.h"
#include "Host.h"
@ -21,6 +22,32 @@ using namespace dmr::defines;
#include <cassert>
#include <algorithm>
#include <memory>
#include <random>
// ---------------------------------------------------------------------------
// Global Functions
// ---------------------------------------------------------------------------
/**
* @brief Calculates the registration authentication response.
* @param challenge Registration challenge.
* @param key Authentication key.
* @return Calculated registration authentication response.
*/
uint32_t calculateRegistrationAuthResponse(uint32_t challenge, const uint8_t key[16U])
{
uint8_t rc4Key[19U];
rc4Key[0U] = (uint8_t)((challenge >> 16U) & 0xFFU);
rc4Key[1U] = (uint8_t)((challenge >> 8U) & 0xFFU);
rc4Key[2U] = (uint8_t)(challenge & 0xFFU);
::memcpy(rc4Key + 3U, key, 16U);
crypto::RC4 rc4;
std::unique_ptr<uint8_t[]> stream(rc4.keystream(259U, rc4Key, sizeof(rc4Key)));
return ((uint32_t)stream[256U] << 16U) |
((uint32_t)stream[257U] << 8U) | stream[258U];
}
// ---------------------------------------------------------------------------
// Public Class Members
@ -43,6 +70,7 @@ Control::Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint
m_ridLookup(ridLookup),
m_tidLookup(tidLookup),
m_enableTSCC(false),
m_requireAuthForReg(false),
m_tsccCnt(0U),
m_tsccCntInterval(1000U, 0U, DMR_SLOT_TIME / 2U),
m_tsccSlotNo(0U),
@ -104,6 +132,17 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa
backOff = 15U;
yaml::Node control = dmrProtocol["control"];
m_requireAuthForReg = control["requireAuthForReg"].as<bool>(false);
if (m_requireAuthForReg && m_network != nullptr) {
m_network->setKEKKeyResponseCallback([this](uint32_t srcId, p25::kmm::KeyItem key,
uint8_t algId, uint8_t keyLength) {
processRegistrationAuthKey(srcId, key, algId, keyLength);
});
} else if (m_requireAuthForReg) {
LogWarning(LOG_DMR, "DMR registration authentication disabled; no FNE network is configured");
m_requireAuthForReg = false;
}
bool enableTSCC = control["enable"].as<bool>(false);
bool dedicatedTSCC = false;
if (enableTSCC) {
@ -798,6 +837,39 @@ void Control::processNetwork()
}
}
/* Helper to process a registration authentication key. */
void Control::processRegistrationAuthKey(uint32_t srcId, const p25::kmm::KeyItem& key, uint8_t algId, uint8_t keyLength)
{
(void)algId; // The FNE labels this shared 16-byte key as the P25 AES-128 KEK type.
Slot* tscc = getTSCCSlot();
if (tscc == nullptr)
return;
auto it = tscc->m_authTransactions.find(srcId);
if (it == tscc->m_authTransactions.end())
return;
if (keyLength != 16U) {
tscc->m_authTransactions.erase(it);
tscc->m_control->writeRF_CSBK_NACK_RSP(srcId, ReasonCode::TS_DENY_RSN_REG_DENIED, ServiceKind::REG_SVC);
return;
}
uint8_t keyData[16U];
::memset(keyData, 0U, 16U);
key.getKey(keyData);
static std::random_device random;
std::uniform_int_distribution<uint32_t> distribution(0U, 0xFFFCDFU);
it->second.challenge = distribution(random);
it->second.expectedResponse = calculateRegistrationAuthResponse(it->second.challenge, keyData);
it->second.challengeSent = true;
it->second.expires = std::chrono::steady_clock::now() + std::chrono::seconds(30);
tscc->m_control->writeRF_CSBK_AHOY(srcId, ServiceKind::REG_SVC, it->second.challenge, false, 0U);
}
/* Helper to process an In-Call Control message. */
void Control::processInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo)

@ -55,6 +55,7 @@ namespace dmr
class HOST_SW_API Slot;
namespace packet { class HOST_SW_API ControlSignaling; }
namespace packet { class HOST_SW_API Data; }
// ---------------------------------------------------------------------------
// Class Declaration
@ -307,6 +308,8 @@ namespace dmr
private:
friend class Slot;
friend class packet::ControlSignaling;
friend class packet::Data;
#if defined(CATCH2_TEST_COMPILATION)
friend class ::HostTestHooks;
#endif
@ -327,6 +330,7 @@ namespace dmr
::lookups::TalkgroupRulesLookup* m_tidLookup;
bool m_enableTSCC;
bool m_requireAuthForReg;
uint16_t m_tsccCnt;
Timer m_tsccCntInterval;
@ -344,6 +348,14 @@ namespace dmr
* @brief Process a data frames from the network.
*/
void processNetwork();
/**
* @brief Helper to process a registration authentication key.
* @param srcId Source Radio ID.
* @param key Key item containing the authentication key.
* @param algId Algorithm ID.
* @param keyLength Length of the key.
*/
void processRegistrationAuthKey(uint32_t srcId, const p25::kmm::KeyItem& key, uint8_t algId, uint8_t keyLength);
/**
* @brief Helper to process an In-Call Control message.
* @param command In-Call Control Command.

@ -592,6 +592,24 @@ void Slot::clock()
uint32_t ms = m_interval.elapsed();
m_interval.start();
const auto now = std::chrono::steady_clock::now();
// clean up expired authentication transactions
for (auto it = m_authTransactions.begin(); it != m_authTransactions.end(); ) {
if (it->second.expires <= now)
it = m_authTransactions.erase(it);
else
++it;
}
// clean up expired UDT transactions
for (auto it = m_udtTransactions.begin(); it != m_udtTransactions.end(); ) {
if (it->second.expires <= now)
it = m_udtTransactions.erase(it);
else
++it;
}
if (s_network != nullptr) {
if (s_network->getStatus() == network::NET_STAT_RUNNING) {
s_siteData.setNetActive(true);
@ -1796,6 +1814,108 @@ void Slot::confirmTSCCPayloadActive()
m_tsccPayloadActRetryCount = 0U;
}
/* Starts registration authentication using a key requested by the root controller. */
void Slot::beginRegistrationAuth(uint32_t srcId, uint8_t serviceOptions)
{
AuthTransaction& transaction = m_authTransactions[srcId];
transaction.serviceOptions = serviceOptions;
transaction.challengeSent = false;
transaction.expires = std::chrono::steady_clock::now() + std::chrono::seconds(30);
m_control->writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_WAIT_RSN, 0U);
if (s_network == nullptr || !s_network->writeLLAKeyReq(srcId)) {
m_authTransactions.erase(srcId);
m_control->writeRF_CSBK_NACK_RSP(srcId, ReasonCode::TS_DENY_RSN_REG_DENIED, ServiceKind::REG_SVC);
}
}
/* Validates an authentication response and completes the pending registration. */
bool Slot::processRegistrationAuthResponse(uint32_t srcId, uint32_t response)
{
auto it = m_authTransactions.find(srcId);
if (it == m_authTransactions.end() || !it->second.challengeSent)
return false;
uint32_t difference = (it->second.expectedResponse ^ response) & 0xFFFFFFU;
uint8_t serviceOptions = it->second.serviceOptions;
m_authTransactions.erase(it);
if (difference == 0U)
m_control->writeRF_CSBK_U_Reg_Rsp(srcId, serviceOptions);
else
m_control->writeRF_CSBK_NACK_RSP(srcId, ReasonCode::TS_DENY_RSN_REG_DENIED, ServiceKind::REG_SVC);
return true;
}
/* Starts a TSCC-local Unified Data Transport transaction. */
void Slot::beginUDT(uint32_t srcId, uint32_t dstId, uint8_t serviceKind)
{
bool group = serviceKind == ServiceKind::GRP_UDT_DATA_CALL;
m_udtTransactions[srcId] = { dstId, group, serviceKind, std::chrono::steady_clock::now() + std::chrono::seconds(30) };
m_control->writeRF_CSBK_AHOY(srcId, serviceKind, WUID_SDMI, group, 0U);
}
/* Validates an inbound UDT upload and converts its header for delivery. */
bool Slot::prepareUDTHeader(data::DataHeader& header)
{
// look for the UDT transaction corresponding to the source ID in the header
auto it = m_udtTransactions.find(header.getSrcId());
if (it == m_udtTransactions.end() || header.getDstId() != WUID_SDMI || header.getUDTO() != 0x1BU)
return false;
header.setDstId(it->second.target);
header.setSrcId(WUID_SDMI);
header.setGI(it->second.group);
header.setA(!it->second.group);
header.setUDTO(0x1AU);
it->second.expires = std::chrono::steady_clock::now() + std::chrono::seconds(30);
return true;
}
/* Completes an upload or waits for the individual recipient acknowledgement. */
void Slot::completeUDT(uint32_t srcId)
{
// look for the UDT transaction corresponding to the given source ID
auto it = m_udtTransactions.find(srcId);
if (it == m_udtTransactions.end())
return;
// if the transaction is a group transaction, complete it and send an acknowledgement
if (it->second.group) {
m_udtTransactions.erase(it);
m_control->writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_ACK_RSN_MSG, 1U);
} else {
it->second.expires = std::chrono::steady_clock::now() + std::chrono::seconds(30);
}
}
/* Mirrors an individual recipient acknowledgement to the originating unit. */
bool Slot::processUDTAcknowledgement(uint32_t targetId)
{
// look for a non-group UDT transaction matching the target ID
for (auto it = m_udtTransactions.begin(); it != m_udtTransactions.end(); ++it) {
if (!it->second.group && it->second.target == targetId) {
uint32_t caller = it->first;
m_udtTransactions.erase(it);
m_control->writeRF_CSBK_ACK_RSP(caller, ReasonCode::TS_ACK_RSN_MSG, 0U);
return true;
}
}
return false;
}
/* Helper to set the DMR short LC. */
void Slot::setShortLC(uint32_t slotNo, uint32_t id, FLCO::E flco, SLCO_ACT_TYPE actType)

@ -19,6 +19,7 @@
#include "Defines.h"
#include "common/dmr/SiteData.h"
#include "common/dmr/data/DataHeader.h"
#include "common/lookups/RSSIInterpolator.h"
#include "common/lookups/IdenTableLookup.h"
#include "common/lookups/RadioIdLookup.h"
@ -36,6 +37,8 @@
#include <vector>
#include <mutex>
#include <chrono>
#include <unordered_map>
// ---------------------------------------------------------------------------
// Class Prototypes
@ -366,6 +369,56 @@ namespace dmr
friend class packet::ControlSignaling;
packet::ControlSignaling* m_control;
/**
* @brief Represents an authentication transaction.
*/
struct AuthTransaction {
/**
* @brief Service options for the authentication transaction.
*/
uint8_t serviceOptions;
/**
* @brief Challenge value for the authentication transaction.
*/
uint32_t challenge;
/**
* @brief Expected response for the authentication transaction.
*/
uint32_t expectedResponse;
/**
* @brief Expiration time of the authentication transaction.
*/
std::chrono::steady_clock::time_point expires;
/**
* @brief Flag indicating whether the challenge has been sent.
*/
bool challengeSent;
};
std::unordered_map<uint32_t, AuthTransaction> m_authTransactions;
/**
* @brief Represents a Unified Data Transport (UDT) transaction.
*/
struct UDTTransaction {
/**
* @brief Target ID of the UDT transaction.
*/
uint32_t target;
/**
* @brief Indicates whether the UDT transaction is for a group.
*/
bool group;
/**
* @brief Service kind of the UDT transaction.
*/
uint8_t serviceKind;
/**
* @brief Expiration time of the UDT transaction.
*/
std::chrono::steady_clock::time_point expires;
};
std::unordered_map<uint32_t, UDTTransaction> m_udtTransactions;
uint32_t m_slotNo;
RingBuffer<uint8_t> m_txImmQueue;
@ -602,6 +655,45 @@ namespace dmr
*/
void confirmTSCCPayloadActive();
/**
* @brief Starts registration authentication using a key requested by the root controller.
* @param srcId Source Radio ID.
* @param serviceOptions Service options for the registration request.
*/
void beginRegistrationAuth(uint32_t srcId, uint8_t serviceOptions);
/**
* @brief Validates an authentication response and completes the pending registration.
* @param srcId Source Radio ID.
* @param response Authentication response received from the radio.
* @return True if the response was valid and the registration was completed, false otherwise.
*/
bool processRegistrationAuthResponse(uint32_t srcId, uint32_t response);
/**
* @brief Starts a TSCC-local Unified Data Transport transaction.
* @param srcId Source Radio ID.
* @param dstId Destination Radio ID.
* @param serviceKind Service kind for the UDT transaction.
*/
void beginUDT(uint32_t srcId, uint32_t dstId, uint8_t serviceKind);
/**
* @brief Validates an inbound UDT upload and converts its header for delivery.
* @param header Reference to the DataHeader object representing the UDT header.
* @return True if the header was successfully prepared for delivery, false otherwise.
*/
bool prepareUDTHeader(data::DataHeader& header);
/**
* @brief Completes an upload or waits for the individual recipient acknowledgement.
* @param srcId Source Radio ID.
*/
void completeUDT(uint32_t srcId);
/**
* @brief Mirrors an individual recipient acknowledgement to the originating unit.
* @param targetId Target Radio ID.
* @return True if the acknowledgement was successfully processed, false otherwise.
*/
bool processUDTAcknowledgement(uint32_t targetId);
/**
* @brief Helper to set the DMR short LC.
* @param slotNo DMR slot number.

@ -237,7 +237,6 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len)
}
break;
case ServiceKind::IND_DATA_CALL:
case ServiceKind::IND_UDT_DATA_CALL:
// make sure control data is supported
IS_SUPPORT_CONTROL_CHECK(csbk->toString(), ServiceKind::IND_VOICE_CALL, srcId);
@ -254,8 +253,24 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len)
writeRF_CSBK_Data_Grant(srcId, dstId, isp->getServiceOptions(), false);
break;
case ServiceKind::IND_UDT_DATA_CALL:
// make sure control data is supported
IS_SUPPORT_CONTROL_CHECK(csbk->toString(), ServiceKind::IND_UDT_DATA_CALL, srcId);
// validate the source RID
VALID_SRCID(csbk->toString(), ServiceKind::IND_UDT_DATA_CALL, srcId);
// validate the target RID
VALID_DSTID(csbk->toString(), ServiceKind::IND_UDT_DATA_CALL, srcId, dstId);
// verify the source RID is registered
VERIFY_SRCID_REG(csbk->toString(), ServiceKind::IND_UDT_DATA_CALL, srcId);
writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_WAIT_RSN, 0U);
m_slot->beginUDT(srcId, dstId, isp->getServiceKind());
break;
case ServiceKind::GRP_DATA_CALL:
case ServiceKind::GRP_UDT_DATA_CALL:
// make sure control data is supported
IS_SUPPORT_CONTROL_CHECK(csbk->toString(), ServiceKind::GRP_VOICE_CALL, srcId);
@ -269,11 +284,28 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len)
writeRF_CSBK_Data_Grant(srcId, dstId, isp->getServiceOptions(), true);
break;
case ServiceKind::GRP_UDT_DATA_CALL:
// make sure control data is supported
IS_SUPPORT_CONTROL_CHECK(csbk->toString(), ServiceKind::GRP_UDT_DATA_CALL, srcId);
// validate the source RID
VALID_SRCID(csbk->toString(), ServiceKind::GRP_UDT_DATA_CALL, srcId);
// validate the talkgroup ID
VALID_TGID(csbk->toString(), ServiceKind::GRP_UDT_DATA_CALL, srcId, dstId);
writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_WAIT_RSN, 1U);
m_slot->beginUDT(srcId, dstId, isp->getServiceKind());
break;
case ServiceKind::REG_SVC:
// make sure control data is supported
IS_SUPPORT_CONTROL_CHECK(csbk->toString(), ServiceKind::REG_SVC, srcId);
writeRF_CSBK_U_Reg_Rsp(srcId, isp->getServiceOptions());
if (m_slot->s_dmr->m_requireAuthForReg && (isp->getServiceOptions() & 0x01U) != 0U)
m_slot->beginRegistrationAuth(srcId, isp->getServiceOptions());
else
writeRF_CSBK_U_Reg_Rsp(srcId, isp->getServiceOptions());
break;
default:
LogWarning(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), unhandled service, serviceKind = %02X", m_slot->m_slotNo, isp->getServiceKind());
@ -284,9 +316,15 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len)
}
break;
case CSBKO::ACK_RSP:
case CSBKO::ACK_RSP_U:
{
VERBOSE_LOG_CSBK(csbk->toString(), srcId, dstId);
::ActivityLog("DMR", true, "Slot %u ack response from %u to %u", m_slot->m_slotNo, srcId, dstId);
CSBK_ACK_RSP* ack = static_cast<CSBK_ACK_RSP*>(csbk.get());
if (csbk->getCSBKO() == CSBKO::ACK_RSP_U && ack->getReason() == ReasonCode::MS_ACK_RSN_AUTH_RESP)
m_slot->processRegistrationAuthResponse(srcId, dstId);
else if (csbk->getCSBKO() == CSBKO::ACK_RSP_U && ack->getReason() == ReasonCode::MS_ACK_RSN_MSG)
m_slot->processUDTAcknowledgement(srcId);
}
break;
case CSBKO::EXT_FNCT:
@ -811,6 +849,30 @@ void ControlSignaling::writeRF_CSBK_ACK_RSP(uint32_t dstId, uint8_t reason, uint
writeRF_CSBK_Imm(csbk.get());
}
/* Helper to write an AHOY packet. */
void ControlSignaling::writeRF_CSBK_AHOY(uint32_t dstId, uint8_t serviceKind,
uint32_t sourceOrChallenge, bool gi, uint8_t appendedBlocks)
{
std::unique_ptr<CSBK_AHOY> csbk = std::make_unique<CSBK_AHOY>();
csbk->setServiceKind(serviceKind);
csbk->setServiceKindFlag(false);
csbk->setServiceOptionsMirror(0U);
csbk->setAmbientListening(false);
csbk->setAppendedBlocks(appendedBlocks);
csbk->setGI(gi);
csbk->setDstId(dstId);
csbk->setSrcId(sourceOrChallenge);
if (m_verbose) {
LogInfoEx(LOG_DMR, "DMR Slot %u, CSBK, %s, serviceKind = $%02X, srcId = %u, dstId = %u",
m_slot->m_slotNo, csbk->toString().c_str(), serviceKind,
csbk->getSrcId(), csbk->getDstId());
}
writeRF_CSBK_Imm(csbk.get());
}
/* Helper to write a NACK RSP packet. */
void ControlSignaling::writeRF_CSBK_NACK_RSP(uint32_t dstId, uint8_t reason, uint8_t service)

@ -139,6 +139,15 @@ namespace dmr
* @param responseInfo Response code.
*/
void writeRF_CSBK_ACK_RSP(uint32_t dstId, uint8_t reason, uint8_t responseInfo);
/**
* @brief Helper to write an AHOY packet.
* @param dstId Destination ID.
* @param serviceKind Service Kind.
* @param sourceOrChallenge Source or Challenge ID.
* @param gi Flag indicating whether the GI bit is set.
* @param appendedBlocks Number of appended blocks.
*/
void writeRF_CSBK_AHOY(uint32_t dstId, uint8_t serviceKind, uint32_t sourceOrChallenge, bool gi, uint8_t appendedBlocks);
/**
* @brief Helper to write a NACK RSP packet.
* @param dstId Destination ID.

@ -126,6 +126,8 @@ bool Data::process(uint8_t* data, uint32_t len)
uint32_t srcId = m_rfDataHeader.getSrcId();
uint32_t dstId = m_rfDataHeader.getDstId();
m_rfUDT = false;
if (!m_slot->s_authoritative && m_slot->m_permittedDstId != dstId) {
if (!g_disableNonAuthoritativeLogging)
LogWarning(LOG_RF, "[NON-AUTHORITATIVE] Ignoring RF traffic, destination not permitted!");
@ -168,6 +170,18 @@ bool Data::process(uint8_t* data, uint32_t len)
}
}
if (m_slot->m_enableTSCC && m_rfDataHeader.getDPF() == DPF::UDT) {
m_rfUDTSource = srcId;
m_rfUDT = m_slot->prepareUDTHeader(m_rfDataHeader);
if (!m_rfUDT) {
LogWarning(LOG_RF, "DMR Slot %u, unsolicited or invalid UDT upload from %u", m_slot->m_slotNo, srcId);
return false;
}
gi = m_rfDataHeader.getGI();
dstId = m_rfDataHeader.getDstId();
srcId = m_rfDataHeader.getSrcId();
}
m_rfDataBlockCnt = 0U;
m_slot->m_rfFrames = m_rfDataHeader.getBlocksToFollow();
@ -229,7 +243,7 @@ bool Data::process(uint8_t* data, uint32_t len)
data[0U] = m_slot->m_rfFrames == 0U ? modem::TAG_EOT : modem::TAG_DATA;
data[1U] = 0x00U;
if (m_slot->s_duplex && m_repeatDataPacket)
if (m_slot->s_duplex && (m_repeatDataPacket || m_rfUDT))
m_slot->addFrame(data);
uint8_t controlByte = 0U;
@ -253,6 +267,10 @@ bool Data::process(uint8_t* data, uint32_t len)
m_pduDataOffset = 0U;
if (m_slot->m_rfFrames == 0U) {
if (m_rfUDT) {
m_slot->completeUDT(m_rfUDTSource);
}
::ActivityLog("DMR", true, "Slot %u ended RF data transmission", m_slot->m_slotNo);
m_slot->writeEndRF();
}
@ -290,7 +308,7 @@ bool Data::process(uint8_t* data, uint32_t len)
m_rfDataBlockCnt++;
}
if (m_rfDataHeader.getBlocksToFollow() > 0U && m_slot->m_rfFrames == 0U) {
if (!m_rfUDT && m_rfDataHeader.getBlocksToFollow() > 0U && m_slot->m_rfFrames == 0U) {
// ooookay -- lets do the insane, and ridiculously stupid, ETSI Big-Endian reversed byte ordering bullshit for the CRC-32
uint8_t crcBytes[MAX_PDU_COUNT * DMR_PDU_UNCODED_LENGTH_BYTES + 2U];
::memset(crcBytes, 0x00U, MAX_PDU_COUNT * DMR_PDU_UNCODED_LENGTH_BYTES + 2U);
@ -325,11 +343,15 @@ bool Data::process(uint8_t* data, uint32_t len)
m_slot->writeNetwork(data, dataType, 0U);
if (m_slot->s_duplex && m_repeatDataPacket) {
if (m_slot->s_duplex && (m_repeatDataPacket || m_rfUDT)) {
m_slot->addFrame(data);
}
if (m_slot->m_rfFrames == 0U) {
if (m_rfUDT) {
m_slot->completeUDT(m_rfUDTSource);
}
LogInfoEx(LOG_RF, "DMR Slot %u, RATE_12/34_DATA, ended data transmission", m_slot->m_slotNo);
m_slot->writeEndRF();
}
@ -621,6 +643,8 @@ Data::Data(Slot* slot, network::BaseNetwork* network, bool dumpDataPacket, bool
m_lastRejectId(0U),
m_dumpDataPacket(dumpDataPacket),
m_repeatDataPacket(repeatDataPacket),
m_rfUDT(false),
m_rfUDTSource(0U),
m_verbose(verbose),
m_debug(debug)
{

@ -88,6 +88,8 @@ namespace dmr
bool m_dumpDataPacket;
bool m_repeatDataPacket;
bool m_rfUDT;
uint32_t m_rfUDTSource;
bool m_verbose;
bool m_debug;

@ -11,6 +11,7 @@
#include "host/Defines.h"
#include "common/dmr/DMRDefines.h"
#include "common/dmr/lc/csbk/CSBK_ACK_RSP.h"
#include "common/dmr/lc/csbk/CSBK_AHOY.h"
#include "common/dmr/lc/csbk/CSBK_MAINT.h"
#include "common/dmr/lc/csbk/CSBK_P_CLEAR.h"
#include "common/lookups/ChannelLookup.h"
@ -38,6 +39,34 @@ TEST_CASE("DMR Tier III acknowledgement preserves 24-bit target addresses", "[dm
REQUIRE(decoded.decode(burst));
CHECK(decoded.getDstId() == 0xABCDEFU);
CHECK(decoded.getSrcId() == WUID_TSI);
CHECK(decoded.getReason() == ReasonCode::TS_ACK_RSN_MSG);
CHECK(decoded.getResponse() == 0x55U);
}
TEST_CASE("C_AHOY preserves all ETSI information elements", "[dmr][tier3][ahoy]") {
CSBK_AHOY encoded;
encoded.setServiceOptionsMirror(0x55U);
encoded.setServiceKindFlag(true);
encoded.setAmbientListening(true);
encoded.setGI(true);
encoded.setAppendedBlocks(3U);
encoded.setServiceKind(ServiceKind::REG_SVC);
encoded.setDstId(0xFEDCBAU);
encoded.setSrcId(0xABCDEFU);
uint8_t frame[DMR_FRAME_LENGTH_BYTES] = { 0U };
encoded.encode(frame);
CSBK_AHOY decoded;
decoded.setDataType(DataType::CSBK);
REQUIRE(decoded.decode(frame));
REQUIRE(decoded.getServiceOptionsMirror() == 0x55U);
REQUIRE(decoded.getServiceKindFlag());
REQUIRE(decoded.getAmbientListening());
REQUIRE(decoded.getGI());
REQUIRE(decoded.getAppendedBlocks() == 3U);
REQUIRE(decoded.getServiceKind() == ServiceKind::REG_SVC);
REQUIRE(decoded.getDstId() == 0xFEDCBAU);
REQUIRE(decoded.getSrcId() == 0xABCDEFU);
}
TEST_CASE("DMR Tier III maintenance preserves 24-bit addresses", "[dmr][tier3][csbk]")

Loading…
Cancel
Save

Powered by TurnKey Linux.