WHACKERS REJOICE! implement and route emergency alarm and call signaling for P25, DMR and NXDN, including P25 ISP emergency alarm, cancellation and acknowledgement procedures;

pull/134/head
Bryan Biedenkapp 2 days ago
parent 12fac00e43
commit 830a356757

@ -102,7 +102,8 @@ BaseNetwork::~BaseNetwork()
/* Writes grant request to the network. */
bool BaseNetwork::writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, const uint8_t slot, const bool unitToUnit)
bool BaseNetwork::writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId,
const uint8_t slot, const bool unitToUnit, const uint8_t serviceOptions)
{
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return false;
@ -118,6 +119,7 @@ bool BaseNetwork::writeGrantReq(const uint8_t mode, const uint32_t srcId, const
buffer[19U] |= 0x80U;
buffer[20U] = mode; // DVM Mode State
buffer[21U] = serviceOptions; // Service Options
return writeMaster({ NET_FUNC::GRANT_REQ, NET_SUBFUNC::NOP }, buffer, MSG_HDR_SIZE, RTP_END_OF_CALL_SEQ, 0U);
}
@ -903,7 +905,7 @@ bool BaseNetwork::writeP25TDU(const p25::lc::LC& control, const p25::data::LowSp
/* Writes P25 TSDU frame data to the network. */
bool BaseNetwork::writeP25TSDU(const p25::lc::LC& control, const uint8_t* data)
bool BaseNetwork::writeP25TSDU(const p25::lc::LC& control, const uint8_t* data, const uint8_t controlByte)
{
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return false;
@ -913,7 +915,7 @@ bool BaseNetwork::writeP25TSDU(const p25::lc::LC& control, const uint8_t* data)
}
uint32_t messageLength = 0U;
UInt8Array message = createP25_TSDUMessage(messageLength, control, data);
UInt8Array message = createP25_TSDUMessage(messageLength, control, data, controlByte);
if (message == nullptr) {
return false;
}
@ -1606,7 +1608,7 @@ UInt8Array BaseNetwork::createP25_TDUMessage(uint32_t& length, const p25::lc::LC
/* Creates an P25 TSDU frame message. */
UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data)
UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data, const uint8_t controlByte)
{
using namespace p25::defines;
assert(data != nullptr);
@ -1617,6 +1619,7 @@ UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::L
// construct P25 message header
p25::data::LowSpeedData lsd = p25::data::LowSpeedData();
createP25_MessageHdr(buffer, DUID::TSDU, control, lsd, FrameType::TERMINATOR);
buffer[14U] = controlByte;
// pack raw P25 TSDU bytes
::memcpy(buffer + 24U, data, P25_TSDU_FRAME_LENGTH_BYTES);

@ -154,6 +154,7 @@ namespace network
NET_CTRL_GRANT_DEMAND = 0x80U, //!< Grant Demand
NET_CTRL_GRANT_DENIAL = 0x40U, //!< Grant Denial
NET_CTRL_SWITCH_OVER = 0x20U, //!< Call Source RID Switch Over
NET_CTRL_ISP = 0x10U, //!< P25 inbound service packet direction
NET_CTRL_GRANT_ENCRYPT = 0x08U, //!< Grant Encrypt
NET_CTRL_U2U = 0x01U, //!< Unit-to-Unit
};
@ -449,7 +450,7 @@ namespace network
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Destination ID | Reserved |S| Reserverd |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Mode | Reserved |
* | Mode | Service Options| Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* \endcode
* @param mode Digital Mode.
@ -457,9 +458,11 @@ namespace network
* @param dstId Destination Radio ID.
* @param slot DMR slot number (if DMR).
* @param unitToUnit Flag indicating if this grant is unit-to-unit.
* @param serviceOptions Protocol service-options octet for the requested grant.
* @returns bool True, if grant request was sent, otherwise false.
*/
bool writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, const uint8_t slot, const bool unitToUnit);
bool writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId,
const uint8_t slot, const bool unitToUnit, const uint8_t serviceOptions);
/**
* @brief Writes a enc. key request to the network.
@ -913,9 +916,10 @@ namespace network
* @brief Writes P25 TSDU frame data to the network.
* @param[in] control Instance of p25::lc::LC containing link control data.
* @param[in] data Buffer containing P25 TSDU data to send.
* @param[in] controlByte DVM control byte, including the TSBK direction marker.
* @returns bool True, if message was sent, otherwise false.
*/
virtual bool writeP25TSDU(const p25::lc::LC& control, const uint8_t* data);
virtual bool writeP25TSDU(const p25::lc::LC& control, const uint8_t* data, const uint8_t controlByte = 0U);
/**
* @brief Writes P25 TDULC frame data to the network.
* @param[in] control Instance of p25::lc::LC containing link control data.
@ -1239,9 +1243,10 @@ namespace network
* @param[out] length Length of network message buffer.
* @param[in] control Instance of p25::lc::LC containing link control data.
* @param[in] data Buffer containing P25 TSDU data to send.
* @param[in] controlByte DVM control byte, including the TSBK direction marker.
* @returns UInt8Array Buffer containing the built network message.
*/
UInt8Array createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data);
UInt8Array createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data, const uint8_t controlByte);
/**
* @brief Creates an P25 TDULC frame message.

@ -118,12 +118,12 @@ Network::~Network()
/* Writes a grant request unless unit-to-unit network traffic is disabled. */
bool Network::writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId,
const uint8_t slot, const bool unitToUnit)
const uint8_t slot, const bool unitToUnit, const uint8_t serviceOptions)
{
if (m_disallowUnitToUnit && unitToUnit)
return false;
return BaseNetwork::writeGrantReq(mode, srcId, dstId, slot, unitToUnit);
return BaseNetwork::writeGrantReq(mode, srcId, dstId, slot, unitToUnit, serviceOptions);
}
/* Resets the DMR ring buffer for the given slot. */

@ -180,10 +180,11 @@ namespace network
* @param dstId Destination subscriber or talkgroup ID.
* @param slot DMR slot number, or zero for protocols without slots.
* @param unitToUnit Flag indicating an individual-call grant request.
* @param serviceOptions Protocol service-options octet for the requested grant.
* @return True if the request was written; otherwise false.
*/
bool writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId,
const uint8_t slot, const bool unitToUnit);
const uint8_t slot, const bool unitToUnit, const uint8_t serviceOptions);
/**
* @brief Resets the DMR ring buffer for the given slot.

@ -246,6 +246,8 @@ namespace p25
/** @brief FNE Working Unit ID */
const uint32_t WUID_FNE = 0xFFFFFCU;
/** @brief System-default Working Unit ID used by FNE call-processing responses. */
const uint32_t WUID_SYSTEM = 0xFFFFFDU;
/** @brief Registration Working Unit ID */
const uint32_t WUID_REG = 0xFFFFFEU;
/** @brief All-call Working Unit ID */

@ -58,7 +58,13 @@ void ISP_CAN_SRV_REQ::encode(uint8_t* data, bool rawTSBK, bool noTrellis)
{
assert(data != nullptr);
/* stub */
ulong64_t tsbkValue = (m_aivFlag ? 0x80U : 0x00U) + (m_service & 0x3FU); // AIV and Service Type
tsbkValue = (tsbkValue << 8) + m_response; // Reason
tsbkValue = (tsbkValue << 24) + (m_dstId & 0xFFFFFFU); // Additional Information
tsbkValue = (tsbkValue << 24) + (m_srcId & 0xFFFFFFU); // Source Radio Address
std::unique_ptr<uint8_t[]> tsbk = TSBK::fromValue(tsbkValue);
TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis);
}
/* Returns a string that represents the current TSBK. */

@ -23,7 +23,9 @@ using namespace p25::lc::tsbk;
/* Initializes a new instance of the ISP_EMERG_ALRM_REQ class. */
ISP_EMERG_ALRM_REQ::ISP_EMERG_ALRM_REQ() : TSBK()
ISP_EMERG_ALRM_REQ::ISP_EMERG_ALRM_REQ() : TSBK(),
m_specialInfo1(0U),
m_specialInfo2(0U)
{
m_lco = TSBKO::ISP_EMERG_ALRM_REQ;
}
@ -43,34 +45,17 @@ bool ISP_EMERG_ALRM_REQ::decode(const uint8_t* data, bool rawTSBK)
ulong64_t tsbkValue = TSBK::toValue(tsbk);
/*
** bryanb: this is a bit of a hack -- because the EMERG ALRM and DENY have the same
** opcode; the following are used by TSBK_OSP_DENY_RSP; best way to check is for m_response > 0
*/
uint8_t si1 = (uint8_t)((tsbkValue >> 56) & 0xFFU); // Emerg. Special Info 1
bool manDown = si1 & 0x01U; // Man Down Flag
uint8_t si2 = (uint8_t)((tsbkValue >> 48) & 0xFFU); // Emerg. Special Info 2
// if we have no special info, this is a defacto emergency button press
if (si1 == 0U && si2 == 0U) {
m_emergency = true;
}
// if we have a man down flag set and no special info 2, this is a man-down emergency
if (manDown && si2 == 0U) {
m_emergency = true;
}
// all other emergency alarms aren't supported and are ignored (and infact most code will treat that as
// OSP_DENY_RSP)
m_aivFlag = (((tsbkValue >> 56) & 0xFFU) & 0x80U) == 0x80U; // Additional Info. Flag
m_service = (uint8_t)((tsbkValue >> 56) & 0x3FU); // Service Type
m_response = (uint8_t)((tsbkValue >> 48) & 0xFFU); // Reason
m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFU); // Target Radio Address
m_specialInfo1 = (uint8_t)((tsbkValue >> 56) & 0xFFU); // Emergency Special Information 1
m_specialInfo2 = (uint8_t)((tsbkValue >> 48) & 0xFFU); // Emergency Special Information 2
m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFU); // Group Address
m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address
// Emergency Alarm Request and Denial Response share an opcode. Retain the
// direction discriminator used by network-originated TSBKs; the RF receive
// path independently identifies this opcode as an inbound alarm request.
m_emergency = (m_specialInfo2 == 0U) &&
(m_specialInfo1 == 0U || (m_specialInfo1 & 0x01U) == 0x01U);
return true;
}
@ -80,7 +65,14 @@ void ISP_EMERG_ALRM_REQ::encode(uint8_t* data, bool rawTSBK, bool noTrellis)
{
assert(data != nullptr);
/* stub */
ulong64_t tsbkValue = m_specialInfo1; // Emergency Special Information 1
tsbkValue = (tsbkValue << 8) + m_specialInfo2; // Emergency Special Information 2
tsbkValue = (tsbkValue << 8) + 0U; // Reserved
tsbkValue = (tsbkValue << 16) + (m_dstId & 0xFFFFU); // Group Address
tsbkValue = (tsbkValue << 24) + (m_srcId & 0xFFFFFFU); // Source Radio Address
std::unique_ptr<uint8_t[]> tsbk = TSBK::fromValue(tsbkValue);
TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis);
}
/* Returns a string that represents the current TSBK. */

@ -60,6 +60,16 @@ namespace p25
* @returns std::string String representation of the TSBK.
*/
std::string toString(bool isp = false) override;
public:
/**
* @brief Emergency special-information octet 1.
*/
DECLARE_PROPERTY(uint8_t, specialInfo1, SpecialInfo1);
/**
* @brief Emergency special-information octet 2.
*/
DECLARE_PROPERTY(uint8_t, specialInfo2, SpecialInfo2);
};
} // namespace tsbk
} // namespace lc

@ -734,7 +734,8 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
/* Process a grant request frame from the network. */
bool TagDMRData::processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId)
bool TagDMRData::processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit,
uint8_t serviceOptions, uint32_t peerId, uint16_t pktSeq, uint32_t streamId)
{
if (unitToUnit && (!m_network->isUnitToUnitPeerAllowed(peerId) || !m_network->isUnitToUnitRouteAvailable(dstId))) {
return false;
@ -789,11 +790,13 @@ bool TagDMRData::processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, b
continue;
if (unitToUnit && !m_network->isUnitToUnitPeerAllowed(peer.first))
continue;
write_CSBK_Grant(peer.first, srcId, dstId, 4U, !unitToUnit);
write_CSBK_Grant(peer.first, srcId, dstId, serviceOptions, !unitToUnit);
}
}
}
// check if the destination has been granted globally
if (!m_network->m_globalAff->isGranted(dstId)) {
if (m_network->m_globalAff->grantCh(dstId, srcId, peerId, fne_lookups::GRANT_TIMER_TIMEOUT, !unitToUnit)) {
return true;

@ -92,12 +92,14 @@ namespace network
* @param dstId Destination ID.
* @param slot DMR slot number.
* @param unitToUnit Flag indicating whether or not this grant is for unit-to-unit traffic.
* @param serviceOptions DMR service-options octet.
* @param peerId Peer ID.
* @param pktSeq RTP packet sequence.
* @param streamId Stream ID.
* @returns bool True, if the grant was processed, otherwise false.
*/
bool processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId);
bool processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit,
uint8_t serviceOptions, uint32_t peerId, uint16_t pktSeq, uint32_t streamId);
/**
* @brief Helper to trigger a call takeover from a In-Call control event.

@ -744,7 +744,8 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI
/* Process a grant request frame from the network. */
bool TagNXDNData::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId)
bool TagNXDNData::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint8_t serviceOptions,
uint32_t peerId, uint16_t pktSeq, uint32_t streamId)
{
if (unitToUnit && (!m_network->isUnitToUnitPeerAllowed(peerId) || !m_network->isUnitToUnitRouteAvailable(dstId))) {
return false;
@ -799,11 +800,13 @@ bool TagNXDNData::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUni
continue;
if (unitToUnit && !m_network->isUnitToUnitPeerAllowed(peer.first))
continue;
write_Message_Grant(peer.first, srcId, dstId, 4U, !unitToUnit);
write_Message_Grant(peer.first, srcId, dstId, serviceOptions, !unitToUnit);
}
}
}
// check if the destination has been granted globally
if (!m_network->m_globalAff->isGranted(dstId)) {
if (m_network->m_globalAff->grantCh(dstId, srcId, peerId, fne_lookups::GRANT_TIMER_TIMEOUT, !unitToUnit)) {
return true;

@ -91,12 +91,14 @@ namespace network
* @param srcId Source Radio ID.
* @param dstId Destination ID.
* @param unitToUnit Flag indicating whether or not this grant is for unit-to-unit traffic.
* @param serviceOptions NXDN service-options octet.
* @param peerId Peer ID.
* @param pktSeq RTP packet sequence.
* @param streamId Stream ID.
* @returns bool True, if the grant was processed, otherwise false.
*/
bool processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId);
bool processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint8_t serviceOptions,
uint32_t peerId, uint16_t pktSeq, uint32_t streamId);
/**
* @brief Helper to trigger a call takeover from a In-Call control event.

@ -870,7 +870,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId
/* Process a grant request frame from the network. */
bool TagP25Data::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId)
bool TagP25Data::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint8_t serviceOptions,
uint32_t peerId, uint16_t pktSeq, uint32_t streamId)
{
if (unitToUnit && (!m_network->isUnitToUnitPeerAllowed(peerId) || !m_network->isUnitToUnitRouteAvailable(dstId))) {
return false;
@ -925,11 +926,13 @@ bool TagP25Data::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit
continue;
if (unitToUnit && !m_network->isUnitToUnitPeerAllowed(peer.first))
continue;
write_TSDU_Grant(peer.first, srcId, dstId, 4U, !unitToUnit);
write_TSDU_Grant(peer.first, srcId, dstId, serviceOptions, !unitToUnit);
}
}
}
// check if the destination has been granted globally
if (!m_network->m_globalAff->isGranted(dstId)) {
if (m_network->m_globalAff->grantCh(dstId, srcId, peerId, fne_lookups::GRANT_TIMER_TIMEOUT, !unitToUnit)) {
return true;
@ -2277,7 +2280,7 @@ void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk)
lc.setDstId(tsbk->getDstId());
uint32_t messageLength = 0U;
UInt8Array message = m_network->createP25_TSDUMessage(messageLength, lc, data);
UInt8Array message = m_network->createP25_TSDUMessage(messageLength, lc, data, 0U);
if (message == nullptr) {
return;
}

@ -97,12 +97,14 @@ namespace network
* @param srcId Source Radio ID.
* @param dstId Destination ID.
* @param unitToUnit Flag indicating whether or not this grant is for unit-to-unit traffic.
* @param serviceOptions P25 service-options octet.
* @param peerId Peer ID.
* @param pktSeq RTP packet sequence.
* @param streamId Stream ID.
* @returns bool True, if the grant was processed, otherwise false.
*/
bool processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId);
bool processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint8_t serviceOptions,
uint32_t peerId, uint16_t pktSeq, uint32_t streamId);
/**
* @brief Helper to trigger a call takeover from a In-Call control event.

@ -41,11 +41,13 @@ void TrafficNetwork::PacketHandler::grantRequest(TrafficNetwork* network, NetPac
bool unitToUnit = (req->buffer[19U] & 0x80U) == 0x80U;
DVM_STATE state = (DVM_STATE)req->buffer[20U]; // DVM Mode State
uint8_t serviceOptions = req->buffer[21U]; // Service Options
switch (state) {
case STATE_DMR:
if (network->m_dmrEnabled) {
if (network->m_tagDMR != nullptr) {
network->m_tagDMR->processGrantReq(srcId, dstId, slot, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId);
network->m_tagDMR->processGrantReq(srcId, dstId, slot, unitToUnit, serviceOptions,
peerId, req->rtpHeader.getSequence(), streamId);
} else {
network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED);
}
@ -54,7 +56,8 @@ void TrafficNetwork::PacketHandler::grantRequest(TrafficNetwork* network, NetPac
case STATE_P25:
if (network->m_p25Enabled) {
if (network->m_tagP25 != nullptr) {
network->m_tagP25->processGrantReq(srcId, dstId, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId);
network->m_tagP25->processGrantReq(srcId, dstId, unitToUnit, serviceOptions,
peerId, req->rtpHeader.getSequence(), streamId);
} else {
network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED);
}
@ -63,7 +66,8 @@ void TrafficNetwork::PacketHandler::grantRequest(TrafficNetwork* network, NetPac
case STATE_NXDN:
if (network->m_nxdnEnabled) {
if (network->m_tagNXDN != nullptr) {
network->m_tagNXDN->processGrantReq(srcId, dstId, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId);
network->m_tagNXDN->processGrantReq(srcId, dstId, unitToUnit, serviceOptions,
peerId, req->rtpHeader.getSequence(), streamId);
} else {
network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED);
}

@ -213,7 +213,8 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len)
} else {
writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_WAIT_RSN, 0U);
if (m_slot->s_network != nullptr)
m_slot->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, srcId, dstId, m_slot->m_slotNo, true);
m_slot->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR,
srcId, dstId, m_slot->m_slotNo, true, isp->getServiceOptions());
}
break;
case ServiceKind::CANCEL_CALL:
@ -238,7 +239,8 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len)
writeRF_CSBK_Grant(srcId, dstId, isp->getServiceOptions(), true);
} else {
if (m_slot->s_network != nullptr)
m_slot->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, srcId, dstId, m_slot->m_slotNo, false);
m_slot->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR,
srcId, dstId, m_slot->m_slotNo, false, isp->getServiceOptions());
}
break;
case ServiceKind::IND_DATA_CALL:
@ -1261,8 +1263,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
// sites; ask the FNE to demand a site-local grant for the called
// subscriber rather than forwarding this site's channel number
if (!net && tscc->s_network != nullptr) {
tscc->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR,
srcId, dstId, slot, true);
tscc->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, srcId, dstId, slot, true, serviceOptions);
}
// if the channel granted isn't the same as the TSCC; remote activate the payload channel

@ -192,7 +192,7 @@ bool ControlSignaling::process(FuncChannelType::E fct, ChOption::E option, uint8
writeRF_Message_Grant(srcId, dstId, serviceOptions, group);
} else {
if (m_nxdn->m_network != nullptr)
m_nxdn->m_network->writeGrantReq(modem::DVM_STATE::STATE_NXDN, srcId, dstId, 0U, !group);
m_nxdn->m_network->writeGrantReq(modem::DVM_STATE::STATE_NXDN, srcId, dstId, 0U, !group, serviceOptions);
}
}
break;

@ -1503,6 +1503,7 @@ void Control::processNetwork()
bool grantDenial = (buffer[14U] & network::NET_CTRL_GRANT_DENIAL) == network::NET_CTRL_GRANT_DENIAL;
bool grantEncrypt = (buffer[14U] & network::NET_CTRL_GRANT_ENCRYPT) == network::NET_CTRL_GRANT_ENCRYPT;
bool unitToUnit = (buffer[14U] & network::NET_CTRL_U2U) == network::NET_CTRL_U2U;
bool inbound = (buffer[14U] & network::NET_CTRL_ISP) == network::NET_CTRL_ISP;
// process network message header
DUID::E duid = (DUID::E)buffer[22U];
@ -1811,7 +1812,7 @@ void Control::processNetwork()
break;
case DUID::TSDU:
m_control->processNetwork(data.get(), frameLength, control, lsd, duid);
m_control->processNetwork(data.get(), frameLength, control, lsd, duid, inbound);
break;
default:

@ -166,9 +166,6 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
{
assert(data != nullptr);
if (!m_p25->m_enableControl)
return false;
DUID::E duid = DUID::HDU;
if (preDecodedTSBK == nullptr) {
// Decode the NID
@ -222,6 +219,17 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
uint32_t srcId = tsbk->getSrcId();
uint32_t dstId = tsbk->getDstId();
// check if control signaling is enabled and if the TSBK is not one of the allowed exceptions
// if control signaling is disabled and the TSBK is not an allowed exception, ignore it
if (!m_p25->m_enableControl &&
tsbk->getLCO() != TSBKO::IOSP_CALL_ALRT &&
tsbk->getLCO() != TSBKO::IOSP_ACK_RSP &&
tsbk->getLCO() != TSBKO::ISP_EMERG_ALRM_REQ &&
tsbk->getLCO() != TSBKO::ISP_CAN_SRV_REQ) {
m_p25->m_rfState = prevRfState;
return false;
}
m_p25->m_affiliations->touchUnitReg(srcId);
m_lastMFID = tsbk->getMFId();
@ -242,15 +250,16 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
VERIFY_SRCID_AFF(tsbk->toString(true), TSBKO::IOSP_GRP_VCH, srcId, dstId);
VERBOSE_LOG_TSBK(tsbk->toString(true), srcId, dstId);
if (m_p25->m_authoritative) {
uint8_t serviceOptions = (tsbk->getEmergency() ? 0x80U : 0x00U) + // Emergency Flag
(tsbk->getEncrypted() ? 0x40U : 0x00U) + // Encrypted Flag
(tsbk->getPriority() & 0x07U); // Priority
uint8_t serviceOptions = (tsbk->getEmergency() ? 0x80U : 0x00U) + // Emergency Flag
(tsbk->getEncrypted() ? 0x40U : 0x00U) + // Encrypted Flag
(tsbk->getPriority() & 0x07U); // Priority
if (m_p25->m_authoritative) {
writeRF_TSDU_Grant(srcId, dstId, serviceOptions, true);
} else {
if (m_p25->m_network != nullptr)
m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, srcId, dstId, 0U, false);
m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25,
srcId, dstId, 0U, false, serviceOptions);
}
}
break;
@ -287,7 +296,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
}
else if (m_p25->m_network != nullptr) {
m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25,
srcId, dstId, 0U, true);
srcId, dstId, 0U, true, serviceOptions);
}
break;
}
@ -305,7 +314,8 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
writeRF_TSDU_Grant(srcId, dstId, serviceOptions, false);
} else {
if (m_p25->m_network != nullptr)
m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, srcId, dstId, 0U, true);
m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25,
srcId, dstId, 0U, true, serviceOptions);
}
}
}
@ -356,8 +366,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
writeRF_TSDU_Grant(srcId, dstId, transaction.serviceOptions, false, false, false, 0U, false, srcId);
} else {
if (m_p25->m_network != nullptr)
m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25,
srcId, dstId, 0U, true);
m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, srcId, dstId, 0U, true, transaction.serviceOptions);
}
m_u2uAvailabilityTransactions.erase(transactionIt);
@ -591,6 +600,42 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
// the telephone interconnect gateway must release the call
uint8_t service = isp->getService();
if (service == TSBKO::ISP_EMERG_ALRM_REQ) {
VALID_SRCID(tsbk->toString(true), TSBKO::ISP_CAN_SRV_REQ, srcId);
if (m_p25->m_enableControl && !m_p25->m_affiliations->isUnitReg(srcId)) {
LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, emergency alarm cancellation from unregistered RID, srcId = %u",
tsbk->toString(true).c_str(), srcId);
writeRF_TSDU_Deny(srcId, WUID_FNE, ReasonCode::DENY_REQ_UNIT_NOT_AUTH,
TSBKO::ISP_EMERG_ALRM_REQ);
writeRF_TSDU_U_Reg_Cmd(srcId);
m_p25->m_rfState = RS_RF_REJECTED;
return false;
}
// validate the emergency alarm cancellation request
const uint32_t additionalInfo = isp->getDstId();
const bool validAlarmCancel = isp->getAIV() && isp->getResponse() == 0U && (additionalInfo & 0x800000U) == 0x800000U;
// extract the group ID from the additional info field
const uint32_t groupId = additionalInfo & 0xFFFFU;
if (!validAlarmCancel || !acl::AccessControl::validateTGId(groupId)) {
LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, invalid emergency alarm cancellation, srcId = %u, dstId = %u",
tsbk->toString(true).c_str(), srcId, groupId);
writeRF_TSDU_Deny(srcId, groupId, ReasonCode::DENY_TGT_GROUP_NOT_VALID, TSBKO::ISP_EMERG_ALRM_REQ, true, true);
m_p25->m_rfState = RS_RF_REJECTED;
return false;
}
RF_TO_WRITE_NET(isp);
writeRF_TSDU_ACK_FNE(srcId, TSBKO::ISP_EMERG_ALRM_REQ, false, true);
::ActivityLog("P25", true, "emergency alarm cancellation from %u for TG %u", srcId, groupId);
break;
}
// handle telephone interconnect services
if (m_telephoneInterconnect && (service == TSBKO::IOSP_TELE_INT_DIAL || service == TSBKO::ISP_TELE_INT_PSTN_REQ ||
service == TSBKO::IOSP_TELE_INT_ANS)) {
RF_TO_WRITE_NET(isp);
@ -659,15 +704,40 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
case TSBKO::ISP_EMERG_ALRM_REQ:
{
ISP_EMERG_ALRM_REQ* isp = static_cast<ISP_EMERG_ALRM_REQ*>(tsbk.get());
if (isp->getEmergency()) {
VERBOSE_LOG_TSBK(tsbk->toString(true), srcId, dstId);
isp->setEmergency(true);
::ActivityLog("P25", true, "emergency alarm request request from %u", srcId);
// validate the source RID
VALID_SRCID(tsbk->toString(true), TSBKO::ISP_EMERG_ALRM_REQ, srcId);
// emergency functions are expressly not supported by DVM -- DVM will *ACKNOWLEDGE* the request but will not do any
// further processing with it
writeRF_TSDU_ACK_FNE(srcId, TSBKO::ISP_EMERG_ALRM_REQ, false, true);
// validate the target RID
VALID_TGID(tsbk->toString(true), TSBKO::ISP_EMERG_ALRM_REQ, srcId, dstId);
// check if control is enabled and the source unit is registered
if (m_p25->m_enableControl) {
if (!m_p25->m_affiliations->isUnitReg(srcId)) {
LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, emergency alarm from unregistered RID, srcId = %u, dstId = %u",
tsbk->toString(true).c_str(), srcId, dstId);
writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_REQ_UNIT_NOT_AUTH, TSBKO::ISP_EMERG_ALRM_REQ, true, true);
writeRF_TSDU_U_Reg_Cmd(srcId);
m_p25->m_rfState = RS_RF_REJECTED;
return false;
}
VERIFY_SRCID_AFF(tsbk->toString(true), TSBKO::ISP_EMERG_ALRM_REQ, srcId, dstId);
}
if (m_verbose) {
LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, specialInfo1 = $%02X, specialInfo2 = $%02X, srcId = %u, dstId = %u",
tsbk->toString(true).c_str(), isp->getSpecialInfo1(), isp->getSpecialInfo2(), srcId, dstId);
}
RF_TO_WRITE_NET(isp);
writeRF_TSDU_ACK_FNE(srcId, TSBKO::ISP_EMERG_ALRM_REQ, false, true);
::ActivityLog("P25", true, "emergency alarm request from %u for TG %u, specialInfo1 = $%02X, specialInfo2 = $%02X",
srcId, dstId, isp->getSpecialInfo1(), isp->getSpecialInfo2());
}
break;
case TSBKO::IOSP_GRP_AFF:
@ -865,9 +935,10 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
/* Process a data frame from the network. */
bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, DUID::E& duid)
bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd,
DUID::E& duid, bool inbound)
{
if (!m_p25->m_enableControl)
if (!m_p25->m_enableControl && duid != DUID::TSDU)
return false;
if (m_p25->m_rfState != RS_RF_LISTENING && m_p25->m_netState == RS_NET_IDLE)
return false;
@ -882,6 +953,23 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr
return false;
}
// handle emergency alarm requests and cancellations
if (inbound && tsbk->getLCO() == TSBKO::ISP_EMERG_ALRM_REQ) {
tsbk->setEmergency(true);
}
// if control is not enabled, only allow emergency alarm and cancel requests
if (!m_p25->m_enableControl) {
const bool ack = inbound && tsbk->getLCO() == TSBKO::IOSP_ACK_RSP;
const bool callAlert = inbound && tsbk->getLCO() == TSBKO::IOSP_CALL_ALRT;
const bool emergencyAlarm = inbound && tsbk->getLCO() == TSBKO::ISP_EMERG_ALRM_REQ;
const bool emergencyCancel = inbound && tsbk->getLCO() == TSBKO::ISP_CAN_SRV_REQ && tsbk->getService() == TSBKO::ISP_EMERG_ALRM_REQ;
if (!ack && !callAlert && !emergencyAlarm && !emergencyCancel) {
return false;
}
}
// reject network unit-to-unit signaling if disallowed by the network
if (m_p25->m_network != nullptr && m_p25->m_network->disallowUnitToUnit()) {
switch (tsbk->getLCO()) {
case TSBKO::IOSP_UU_VCH:
@ -1044,8 +1132,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr
writeRF_TSDU_Grant(srcId, dstId, transaction.serviceOptions, false, false, false, 0U, false, srcId);
}
else if (m_p25->m_network != nullptr) {
m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25,
srcId, dstId, 0U, true);
m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, srcId, dstId, 0U, true, transaction.serviceOptions);
}
m_u2uAvailabilityTransactions.erase(transactionIt);
@ -1223,12 +1310,11 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr
break;
case TSBKO::ISP_EMERG_ALRM_REQ:
{
// non-emergency mode is a TSBKO::OSP_DENY_RSP
if (!tsbk->getEmergency()) {
break; // the FNE may explicitly send these
} else {
if (inbound) {
VERBOSE_LOG_TSBK_NET(tsbk->toString(true), srcId, dstId);
return true; // don't allow this to write to the air
if (m_p25->m_enableControl) {
return true; // do not repeat an inbound request on a trunking control channel
}
}
}
break;
@ -1633,7 +1719,7 @@ void ControlSignaling::writeNetworkRF(lc::TSBK* tsbk, const uint8_t* data, bool
lc.setSrcId(tsbk->getSrcId());
lc.setDstId(tsbk->getDstId());
m_p25->m_network->writeP25TSDU(lc, data);
m_p25->m_network->writeP25TSDU(lc, data, network::NET_CTRL_ISP);
if (autoReset)
m_p25->m_network->resetP25();
}
@ -1756,11 +1842,12 @@ void ControlSignaling::writeNet_TDULC(lc::TDULC* lc)
void ControlSignaling::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool forceSingle, bool imm)
{
if (!m_p25->m_enableControl)
return;
assert(tsbk != nullptr);
if (!m_p25->m_enableControl && tsbk->getLCO() != TSBKO::IOSP_ACK_RSP &&
tsbk->getLCO() != TSBKO::ISP_EMERG_ALRM_REQ)
return;
uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES + 2U];
::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES);
@ -3196,10 +3283,13 @@ void ControlSignaling::writeRF_TSDU_UU_Ans_Req(uint32_t srcId, uint32_t dstId)
/* Helper to write a acknowledge packet. */
void ControlSignaling::writeRF_TSDU_ACK_FNE(uint32_t srcId, uint32_t service, bool extended, bool noNetwork)
void ControlSignaling::writeRF_TSDU_ACK_FNE(uint32_t dstId, uint32_t service, bool extended, bool noNetwork)
{
std::unique_ptr<IOSP_ACK_RSP> iosp = std::make_unique<IOSP_ACK_RSP>();
iosp->setSrcId(srcId);
iosp->setAIV(true);
iosp->setEX(false);
iosp->setSrcId(dstId);
iosp->setDstId(WUID_SYSTEM);
iosp->setService(service);
if (extended) {
@ -3208,8 +3298,8 @@ void ControlSignaling::writeRF_TSDU_ACK_FNE(uint32_t srcId, uint32_t service, bo
}
if (m_verbose) {
LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, EX = %u, serviceType = $%02X, srcId = %u",
iosp->toString().c_str(), iosp->getAIV(), iosp->getEX(), iosp->getService(), srcId);
LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, EX = %u, serviceType = $%02X, srcId = %u, dstId = %u",
iosp->toString().c_str(), iosp->getAIV(), iosp->getEX(), iosp->getService(), WUID_SYSTEM, dstId);
}
writeRF_TSDU_SBF_Imm(iosp.get(), noNetwork);

@ -72,9 +72,11 @@ namespace p25
* @param control Link Control Data.
* @param lsd Low Speed Data.
* @param duid Data Unit ID.
* @param inbound Flag indicating an inbound service packet received from RF.
* @returns bool True, if data frame is processed, otherwise false.
*/
bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, defines::DUID::E& duid);
bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd,
defines::DUID::E& duid, bool inbound = false);
/** @} */
/**
@ -414,9 +416,9 @@ namespace p25
* @param srcId Source Radio ID.
* @param service Service being acknowledged.
* @param extended Flag indicating this is an extended acknowledgement.
* @param noActivityLog
* @param noNetwork Flag indicating the acknowledgement must not be forwarded to the network.
*/
void writeRF_TSDU_ACK_FNE(uint32_t srcId, uint32_t service, bool extended, bool noActivityLog);
void writeRF_TSDU_ACK_FNE(uint32_t dstId, uint32_t service, bool extended, bool noNetwork);
/**
* @brief Helper to write a deny packet.
* @param srcId Source Radio ID.

@ -303,19 +303,19 @@ bool FNETestHooks::validateP25IndividualCall(TrafficNetwork& network, uint32_t p
/* Processes a synthetic DMR FNE grant request. */
bool FNETestHooks::processDMRGrantRequest(TrafficNetwork& network, uint32_t peerId,
uint32_t sourceId, uint32_t destinationId, bool unitToUnit)
uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions)
{
return network.m_tagDMR->processGrantReq(
sourceId, destinationId, 1U, unitToUnit, peerId, 1U, 105U);
sourceId, destinationId, 1U, unitToUnit, serviceOptions, peerId, 1U, 105U);
}
/* Processes a synthetic P25 FNE grant request. */
bool FNETestHooks::processP25GrantRequest(TrafficNetwork& network, uint32_t peerId,
uint32_t sourceId, uint32_t destinationId, bool unitToUnit)
uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions)
{
return network.m_tagP25->processGrantReq(
sourceId, destinationId, unitToUnit, peerId, 1U, 107U);
sourceId, destinationId, unitToUnit, serviceOptions, peerId, 1U, 107U);
}
/* Processes a synthetic P25 Phase 2 private voice frame. */
@ -336,10 +336,10 @@ bool FNETestHooks::processP25P2PrivateFrame(TrafficNetwork& network, uint32_t pe
/* Processes a synthetic NXDN FNE grant request. */
bool FNETestHooks::processNXDNGrantRequest(TrafficNetwork& network, uint32_t peerId,
uint32_t sourceId, uint32_t destinationId, bool unitToUnit)
uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions)
{
return network.m_tagNXDN->processGrantReq(
sourceId, destinationId, unitToUnit, peerId, 1U, 106U);
sourceId, destinationId, unitToUnit, serviceOptions, peerId, 1U, 106U);
}
/* Sets whether unit-to-unit traffic is globally disabled. */

@ -181,10 +181,11 @@ public:
* @param sourceId Calling subscriber ID.
* @param destinationId Called subscriber or talkgroup ID.
* @param unitToUnit Whether the request is an individual call.
* @param serviceOptions DMR service-options octet.
* @return True when the request is accepted.
*/
static bool processDMRGrantRequest(network::TrafficNetwork& network, uint32_t peerId,
uint32_t sourceId, uint32_t destinationId, bool unitToUnit);
uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions = 4U);
/**
* @brief Processes a synthetic P25 FNE grant request.
* @param network The TrafficNetwork instance.
@ -192,10 +193,11 @@ public:
* @param sourceId Calling subscriber ID.
* @param destinationId Called subscriber or talkgroup ID.
* @param unitToUnit Whether the request is an individual call.
* @param serviceOptions P25 service-options octet.
* @return True when the request is accepted.
*/
static bool processP25GrantRequest(network::TrafficNetwork& network, uint32_t peerId,
uint32_t sourceId, uint32_t destinationId, bool unitToUnit);
uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions = 4U);
/**
* @brief Processes a synthetic P25 Phase 2 private voice frame.
* @param network The TrafficNetwork instance.
@ -214,10 +216,11 @@ public:
* @param sourceId Calling subscriber ID.
* @param destinationId Called subscriber or talkgroup ID.
* @param unitToUnit Whether the request is an individual call.
* @param serviceOptions NXDN service-options octet.
* @return True when the request is accepted.
*/
static bool processNXDNGrantRequest(network::TrafficNetwork& network, uint32_t peerId,
uint32_t sourceId, uint32_t destinationId, bool unitToUnit);
uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions = 4U);
/**
* @brief Sets whether unit-to-unit traffic is globally disabled.
* @param network The TrafficNetwork instance.

@ -195,6 +195,46 @@ void buildCancelServiceRequest(uint32_t srcId, uint8_t serviceType, uint8_t* raw
edac::CRC::addCCITT162(raw, p25::defines::P25_TSBK_LENGTH_BYTES);
}
/**
* @brief Builds an independently encoded emergency alarm request ISP TSBK.
* @param srcId RID declaring the emergency alarm.
* @param dstId Group associated with the emergency alarm.
* @param specialInfo1 Emergency Special Information octet 1.
* @param specialInfo2 Emergency Special Information octet 2.
* @param raw Buffer to store the constructed TSBK.
*/
void buildEmergencyAlarmRequest(uint32_t srcId, uint16_t dstId, uint8_t specialInfo1,
uint8_t specialInfo2, uint8_t* raw)
{
::memset(raw, 0x00U, p25::defines::P25_TSBK_LENGTH_BYTES);
raw[0U] = 0x80U | p25::defines::TSBKO::ISP_EMERG_ALRM_REQ;
raw[1U] = p25::defines::MFG_STANDARD;
raw[2U] = specialInfo1;
raw[3U] = specialInfo2;
SET_UINT16(dstId, raw, 5U);
SET_UINT24(srcId, raw, 7U);
edac::CRC::addCCITT162(raw, p25::defines::P25_TSBK_LENGTH_BYTES);
}
/**
* @brief Builds an independently encoded emergency alarm cancellation ISP TSBK.
* @param srcId RID cancelling its emergency alarm.
* @param dstId Group associated with the emergency alarm.
* @param raw Buffer to store the constructed TSBK.
*/
void buildEmergencyAlarmCancel(uint32_t srcId, uint16_t dstId, uint8_t* raw)
{
::memset(raw, 0x00U, p25::defines::P25_TSBK_LENGTH_BYTES);
raw[0U] = 0x80U | p25::defines::TSBKO::ISP_CAN_SRV_REQ;
raw[1U] = p25::defines::MFG_STANDARD;
raw[2U] = 0x80U | p25::defines::TSBKO::ISP_EMERG_ALRM_REQ;
raw[3U] = 0x00U;
raw[4U] = 0x80U;
SET_UINT16(dstId, raw, 5U);
SET_UINT24(srcId, raw, 7U);
edac::CRC::addCCITT162(raw, p25::defines::P25_TSBK_LENGTH_BYTES);
}
/** Builds an independently encoded UU ANS request OSP TSBK. */
void buildUUAnswerRequest(uint32_t callerId, uint32_t calledId, uint8_t* raw)
{
@ -805,6 +845,62 @@ TEST_CASE("P25 active unit-to-unit call continues with a grant instead of anothe
CHECK(HostTestHooks::p25U2UGrantTarget(*harness.m_control, caller, called) == called);
}
TEST_CASE("P25 conventional host accepts and acknowledges an emergency alarm TSBK",
"[p25][host][control][emergency][conventional]")
{
P25HostHarness harness;
const uint32_t srcId = 1001U;
const uint16_t dstId = 2001U;
uint8_t raw[p25::defines::P25_TSBK_LENGTH_BYTES];
uint8_t response[p25::defines::P25_TSDU_FRAME_LENGTH_BYTES + 2U];
buildEmergencyAlarmRequest(srcId, dstId, 0x00U, 0x01U, raw);
REQUIRE(HostTestHooks::p25ProcessRawTSBK(*harness.m_control, raw));
CHECK(harness.m_control->getFrame(response) > 0U);
}
TEST_CASE("P25 trunking host rejects an emergency alarm from an unregistered unit",
"[p25][host][control][emergency][trunking]")
{
P25HostHarness harness;
const uint32_t srcId = 1001U;
const uint16_t dstId = 2001U;
uint8_t raw[p25::defines::P25_TSBK_LENGTH_BYTES];
HostTestHooks::p25ConfigureU2U(*harness.m_control, false);
buildEmergencyAlarmRequest(srcId, dstId, 0x00U, 0x00U, raw);
CHECK_FALSE(HostTestHooks::p25ProcessRawTSBK(*harness.m_control, raw));
}
TEST_CASE("P25 conventional host accepts and acknowledges emergency alarm cancellation",
"[p25][host][control][emergency][conventional]")
{
P25HostHarness harness;
const uint32_t srcId = 1001U;
const uint16_t dstId = 2001U;
uint8_t raw[p25::defines::P25_TSBK_LENGTH_BYTES];
uint8_t response[p25::defines::P25_TSDU_FRAME_LENGTH_BYTES + 2U];
buildEmergencyAlarmCancel(srcId, dstId, raw);
REQUIRE(HostTestHooks::p25ProcessRawTSBK(*harness.m_control, raw));
CHECK(harness.m_control->getFrame(response) > 0U);
}
TEST_CASE("P25 trunking host accepts an emergency alarm from a registered unit",
"[p25][host][control][emergency][trunking]")
{
P25HostHarness harness;
const uint32_t srcId = 1001U;
const uint16_t dstId = 2001U;
uint8_t raw[p25::defines::P25_TSBK_LENGTH_BYTES];
HostTestHooks::p25ConfigureU2U(*harness.m_control, false);
HostTestHooks::p25RegisterUnit(*harness.m_control, srcId);
buildEmergencyAlarmRequest(srcId, dstId, 0x01U, 0x00U, raw);
CHECK(HostTestHooks::p25ProcessRawTSBK(*harness.m_control, raw));
}
TEST_CASE("P25 unit-to-unit cancellation cleanup releases only private call state",
"[p25][host][control][u2u][procedure][cancel]")
{

@ -33,6 +33,79 @@ TEST_CASE("TSBK", "[p25][tsbk]") {
REQUIRE(P25_TSBK_FEC_LENGTH_BITS == (P25_TSBK_FEC_LENGTH_BYTES * 8 - 4)); // 196 bits (Trellis)
}
SECTION("Emergency_Alarm_Request_Roundtrip") {
ISP_EMERG_ALRM_REQ source;
source.setSpecialInfo1(0x01U);
source.setSpecialInfo2(0x00U);
source.setDstId(0x2345U);
source.setSrcId(0x654321U);
uint8_t raw[P25_TSBK_LENGTH_BYTES];
::memset(raw, 0x00U, sizeof(raw));
source.encode(raw, true, true);
const uint16_t groupId = GET_UINT16(raw, 5U);
const uint32_t srcId = GET_UINT24(raw, 7U);
REQUIRE((raw[0U] & 0x3FU) == TSBKO::ISP_EMERG_ALRM_REQ);
REQUIRE(raw[2U] == 0x01U);
REQUIRE(raw[3U] == 0x00U);
REQUIRE(raw[4U] == 0x00U);
REQUIRE(groupId == 0x2345U);
REQUIRE(srcId == 0x654321U);
std::unique_ptr<TSBK> decoded = TSBKFactory::createTSBK(raw, true);
REQUIRE(decoded != nullptr);
ISP_EMERG_ALRM_REQ* alarm = dynamic_cast<ISP_EMERG_ALRM_REQ*>(decoded.get());
REQUIRE(alarm != nullptr);
CHECK(alarm->getEmergency());
CHECK(alarm->getSpecialInfo1() == 0x01U);
CHECK(alarm->getSpecialInfo2() == 0x00U);
CHECK(alarm->getDstId() == 0x2345U);
CHECK(alarm->getSrcId() == 0x654321U);
}
SECTION("Emergency_Alarm_Cancel_Roundtrip") {
ISP_CAN_SRV_REQ source;
source.setAIV(true);
source.setService(TSBKO::ISP_EMERG_ALRM_REQ);
source.setResponse(0U);
source.setDstId(0x800000U | 0x2345U);
source.setSrcId(0x654321U);
uint8_t raw[P25_TSBK_LENGTH_BYTES];
::memset(raw, 0x00U, sizeof(raw));
source.encode(raw, true, true);
std::unique_ptr<TSBK> decoded = TSBKFactory::createTSBK(raw, true);
REQUIRE(decoded != nullptr);
ISP_CAN_SRV_REQ* cancel = dynamic_cast<ISP_CAN_SRV_REQ*>(decoded.get());
REQUIRE(cancel != nullptr);
CHECK(cancel->getAIV());
CHECK(cancel->getService() == TSBKO::ISP_EMERG_ALRM_REQ);
CHECK(cancel->getResponse() == 0U);
CHECK(cancel->getDstId() == (0x800000U | 0x2345U));
CHECK(cancel->getSrcId() == 0x654321U);
}
SECTION("Emergency_Alarm_ACK_Uses_System_Default_Source") {
IOSP_ACK_RSP ack;
ack.setAIV(true);
ack.setEX(false);
ack.setService(TSBKO::ISP_EMERG_ALRM_REQ);
ack.setDstId(WUID_SYSTEM);
ack.setSrcId(0x654321U);
uint8_t raw[P25_TSBK_LENGTH_BYTES];
::memset(raw, 0x00U, sizeof(raw));
ack.encode(raw, true, true);
const uint32_t sourceId = GET_UINT24(raw, 4U);
const uint32_t targetId = GET_UINT24(raw, 7U);
CHECK((raw[2U] & 0xC0U) == 0x80U);
CHECK(targetId == 0x654321U);
CHECK(sourceId == WUID_SYSTEM);
}
SECTION("Standard_Unconfirmed_MBT_TELE_INT_DIAL_REQ") {
const uint8_t counts[] = { 1U, 10U, 11U, 34U };
for (uint8_t count : counts) {

Loading…
Cancel
Save

Powered by TurnKey Linux.