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.
953 lines
31 KiB
953 lines
31 KiB
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Digital Voice Modem - Test Suite
|
|
* 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 "host/HostTestHooks.h"
|
|
#include "common/dmr/SlotType.h"
|
|
#include "common/dmr/Sync.h"
|
|
#include "common/dmr/lc/FullLC.h"
|
|
#include "common/dmr/lc/csbk/CSBK_PV_GRANT.h"
|
|
#include "common/dmr/lc/csbk/CSBK_TV_GRANT.h"
|
|
#include "common/nxdn/NXDNDefines.h"
|
|
#include "common/nxdn/NXDNUtils.h"
|
|
#include "common/nxdn/Sync.h"
|
|
#include "common/nxdn/channel/FACCH1.h"
|
|
#include "common/nxdn/channel/LICH.h"
|
|
#include "common/nxdn/channel/SACCH.h"
|
|
#include "common/nxdn/lc/RTCH.h"
|
|
#include "common/edac/Trellis.h"
|
|
#include "common/p25/lc/tsbk/TSBKFactory.h"
|
|
#include "common/p25/P25Utils.h"
|
|
#include "host/p25/packet/ControlSignaling.h"
|
|
#include "host/p25/packet/Voice.h"
|
|
|
|
#include <cstring>
|
|
#include <memory>
|
|
|
|
#if defined(CATCH2_TEST_COMPILATION)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Constants
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static const uint8_t DMR_CAL_VH_1K_PAYLOAD[dmr::defines::DMR_FRAME_LENGTH_BYTES] = {
|
|
0x00U, 0x20U, 0x08U, 0x08U, 0x02U, 0x38U, 0x15U, 0x00U, 0x2CU, 0xA0U, 0x14U,
|
|
0x60U, 0x84U, 0x6DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xDEU, 0x30U, 0x30U,
|
|
0x01U, 0x10U, 0x01U, 0x40U, 0x03U, 0xC0U, 0x13U, 0xC1U, 0x1EU, 0x80U, 0x6FU
|
|
};
|
|
|
|
static const uint8_t NXDN_CAL_1K_PAYLOAD[nxdn::defines::NXDN_FRAME_LENGTH_BYTES] = {
|
|
0xCDU, 0xF5U, 0x9DU, 0x5DU, 0x7CU, 0xFAU, 0x0AU, 0x6EU, 0x8AU, 0x23U, 0x56U, 0xE8U,
|
|
0x4CU, 0xAAU, 0xDEU, 0x8BU, 0x26U, 0xE4U, 0xF2U, 0x82U, 0x88U,
|
|
0xC6U, 0x8AU, 0x74U, 0x29U, 0xA4U, 0xECU, 0xD0U, 0x08U, 0x22U,
|
|
0xCEU, 0xA2U, 0xFCU, 0x01U, 0x8CU, 0xECU, 0xDAU, 0x0AU, 0xA0U,
|
|
0xEEU, 0x8AU, 0x7EU, 0x2BU, 0x26U, 0xCCU, 0xF8U, 0x8AU, 0x08U
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Static Class Members
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/* Gets DMR slot 1 instance. */
|
|
|
|
dmr::Slot* HostTestHooks::dmrSlot1(const dmr::Control& control) { return control.m_slot1; }
|
|
|
|
/* Gets DMR slot 2 instance. */
|
|
|
|
dmr::Slot* HostTestHooks::dmrSlot2(const dmr::Control& control) { return control.m_slot2; }
|
|
|
|
/* Gets DMR network state. */
|
|
|
|
RPT_NET_STATE HostTestHooks::dmrNetState(const dmr::Slot& slot) { return slot.m_netState; }
|
|
|
|
/* Gets DMR RF state. */
|
|
|
|
RPT_RF_STATE HostTestHooks::dmrRFState(const dmr::Slot& slot) { return slot.m_rfState; }
|
|
|
|
/* Gets DMR permitted destination ID. */
|
|
|
|
uint32_t HostTestHooks::dmrPermittedDstId(const dmr::Slot& slot) { return slot.m_permittedDstId; }
|
|
|
|
/* Gets DMR network watchdog timer. */
|
|
|
|
Timer& HostTestHooks::dmrNetworkWatchdog(dmr::Slot& slot) { return slot.m_networkWatchdog; }
|
|
|
|
/* Gets DMR network talkgroup hang timer. */
|
|
|
|
Timer& HostTestHooks::dmrNetTGHang(dmr::Slot& slot) { return slot.m_netTGHang; }
|
|
|
|
/* Gets DMR RF talkgroup hang timer. */
|
|
|
|
Timer& HostTestHooks::dmrRFTGHang(dmr::Slot& slot) { return slot.m_rfTGHang; }
|
|
|
|
/* Gets DMR RF loss watchdog timer. */
|
|
|
|
Timer& HostTestHooks::dmrRFLossWatchdog(dmr::Slot& slot) { return slot.m_rfLossWatchdog; }
|
|
|
|
/* Forces DMR slot into active RF call state. */
|
|
|
|
void HostTestHooks::dmrSetRFCall(dmr::Slot& slot, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
slot.m_rfState = RS_RF_AUDIO;
|
|
slot.m_rfLastSrcId = srcId;
|
|
slot.m_rfLastDstId = dstId;
|
|
slot.m_rfTGHang.start();
|
|
}
|
|
|
|
/* Forces DMR slot into residual RF talkgroup hang state. */
|
|
|
|
void HostTestHooks::dmrSetRFHang(dmr::Slot& slot, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
slot.m_rfState = RS_RF_LISTENING;
|
|
slot.m_rfLastSrcId = srcId;
|
|
slot.m_rfLastDstId = dstId;
|
|
slot.m_rfTGHang.start();
|
|
}
|
|
|
|
/* Returns DMR slot to RF listening state. */
|
|
|
|
void HostTestHooks::dmrClearRFCall(dmr::Slot& slot)
|
|
{
|
|
slot.m_rfState = RS_RF_LISTENING;
|
|
slot.m_rfLastSrcId = 0U;
|
|
slot.m_rfLastDstId = 0U;
|
|
slot.m_rfTGHang.stop();
|
|
}
|
|
|
|
/* Forces DMR slot into RF rejected state. */
|
|
|
|
void HostTestHooks::dmrSetRFRejected(dmr::Slot& slot)
|
|
{
|
|
slot.m_rfState = RS_RF_REJECTED;
|
|
}
|
|
|
|
/* Injects synthetic DMR network voice call. */
|
|
|
|
void HostTestHooks::dmrStartNetVoiceCall(dmr::Slot& slot, uint32_t srcId, uint32_t dstId, bool group)
|
|
{
|
|
dmr::data::NetData dmrData;
|
|
dmrData.setSlotNo(slot.m_slotNo);
|
|
dmrData.setSrcId(srcId);
|
|
dmrData.setDstId(dstId);
|
|
dmrData.setFLCO(group ? dmr::defines::FLCO::GROUP : dmr::defines::FLCO::PRIVATE);
|
|
dmrData.setDataType(dmr::defines::DataType::VOICE_LC_HEADER);
|
|
|
|
uint8_t data[dmr::defines::DMR_FRAME_LENGTH_BYTES];
|
|
::memset(data, 0x00U, sizeof(data));
|
|
|
|
dmr::lc::LC lc(dmrData.getFLCO(), srcId, dstId);
|
|
dmr::lc::FullLC fullLC;
|
|
fullLC.encode(lc, data, dmr::defines::DataType::VOICE_LC_HEADER);
|
|
|
|
dmr::SlotType slotType;
|
|
slotType.setColorCode(dmr::Slot::s_colorCode);
|
|
slotType.setDataType(dmr::defines::DataType::VOICE_LC_HEADER);
|
|
slotType.encode(data);
|
|
|
|
dmr::Sync::addDMRDataSync(data, dmr::Slot::s_duplex);
|
|
|
|
dmrData.setData(data);
|
|
slot.processNetwork(dmrData);
|
|
}
|
|
|
|
/* Injects an FNE-generated DMR voice grant response. */
|
|
|
|
void HostTestHooks::dmrProcessNetworkGrant(dmr::Slot& slot, uint32_t srcId, uint32_t dstId,
|
|
bool group, bool emergency)
|
|
{
|
|
std::unique_ptr<dmr::lc::CSBK> grant;
|
|
if (group)
|
|
grant = std::make_unique<dmr::lc::csbk::CSBK_TV_GRANT>();
|
|
else
|
|
grant = std::make_unique<dmr::lc::csbk::CSBK_PV_GRANT>();
|
|
|
|
grant->setSrcId(srcId);
|
|
grant->setDstId(dstId);
|
|
grant->setLogicalCh1(0U);
|
|
grant->setSlotNo(0U);
|
|
grant->setEmergency(emergency);
|
|
|
|
uint8_t data[dmr::defines::DMR_FRAME_LENGTH_BYTES];
|
|
::memset(data, 0x00U, sizeof(data));
|
|
grant->encode(data);
|
|
|
|
dmr::SlotType slotType;
|
|
slotType.setColorCode(0U);
|
|
slotType.setDataType(dmr::defines::DataType::CSBK);
|
|
slotType.encode(data);
|
|
dmr::Sync::addDMRDataSync(data, true);
|
|
|
|
dmr::data::NetData dmrData;
|
|
dmrData.setSlotNo(slot.m_slotNo);
|
|
dmrData.setDataType(dmr::defines::DataType::CSBK);
|
|
dmrData.setSrcId(srcId);
|
|
dmrData.setDstId(dstId);
|
|
dmrData.setFLCO(group ? dmr::defines::FLCO::GROUP : dmr::defines::FLCO::PRIVATE);
|
|
dmrData.setData(data);
|
|
slot.processNetwork(dmrData);
|
|
}
|
|
|
|
/* Injects synthetic DMR RF voice call frame. */
|
|
|
|
bool HostTestHooks::dmrStartRFVoiceCall(dmr::Slot& slot, uint32_t srcId, uint32_t dstId, bool group)
|
|
{
|
|
uint8_t frame[dmr::defines::DMR_FRAME_LENGTH_BYTES + 2U];
|
|
::memset(frame, 0x00U, sizeof(frame));
|
|
|
|
::memcpy(frame + 2U, DMR_CAL_VH_1K_PAYLOAD, dmr::defines::DMR_FRAME_LENGTH_BYTES);
|
|
|
|
frame[0U] = modem::TAG_DATA;
|
|
frame[1U] = dmr::defines::SYNC_DATA | dmr::defines::DataType::VOICE_LC_HEADER;
|
|
|
|
dmr::lc::LC lc(group ? dmr::defines::FLCO::GROUP : dmr::defines::FLCO::PRIVATE, srcId, dstId);
|
|
dmr::lc::FullLC fullLC;
|
|
fullLC.encode(lc, frame + 2U, dmr::defines::DataType::VOICE_LC_HEADER);
|
|
|
|
dmr::SlotType slotType;
|
|
slotType.setColorCode(dmr::Slot::s_colorCode);
|
|
slotType.setDataType(dmr::defines::DataType::VOICE_LC_HEADER);
|
|
slotType.encode(frame + 2U);
|
|
|
|
dmr::Sync::addDMRDataSync(frame + 2U, dmr::Slot::s_duplex);
|
|
|
|
return slot.processFrame(frame, sizeof(frame));
|
|
}
|
|
|
|
/* Detaches the shared DMR network pointer during test-fixture teardown. */
|
|
|
|
void HostTestHooks::dmrDetachNetwork()
|
|
{
|
|
dmr::Slot::s_network = nullptr;
|
|
}
|
|
|
|
/* Configures a co-located DMR Tier III control channel. */
|
|
|
|
void HostTestHooks::dmrEnableTier3(dmr::Control& control, uint32_t channelNo, uint8_t tsccSlot,
|
|
bool dedicated)
|
|
{
|
|
control.m_enableTSCC = true;
|
|
control.m_tsccSlotNo = tsccSlot;
|
|
dmr::Slot::s_channelNo = channelNo;
|
|
|
|
dmr::Slot* slot = tsccSlot == 1U ? control.m_slot1 : control.m_slot2;
|
|
slot->setTSCC(true, dedicated);
|
|
}
|
|
|
|
/* Gets the active DMR Tier III payload destination. */
|
|
|
|
uint32_t HostTestHooks::dmrPayloadDstId(const dmr::Slot& slot) { return slot.m_tsccPayloadDstId; }
|
|
|
|
/* Tests whether a DMR Tier III grant exists. */
|
|
|
|
bool HostTestHooks::dmrIsGranted(const dmr::Slot& slot, uint32_t dstId)
|
|
{
|
|
return slot.s_affiliations->isGranted(dstId);
|
|
}
|
|
|
|
/* Tests whether a DMR grant is classified as group traffic. */
|
|
|
|
bool HostTestHooks::dmrIsGroupGrant(const dmr::Slot& slot, uint32_t dstId)
|
|
{
|
|
return slot.s_affiliations->isGroup(dstId);
|
|
}
|
|
|
|
/* Registers a DMR subscriber on the local Tier III site. */
|
|
|
|
void HostTestHooks::dmrRegisterUnit(dmr::Slot& slot, uint32_t unitId)
|
|
{
|
|
slot.s_affiliations->unitReg(unitId);
|
|
}
|
|
|
|
/* Starts a synthetic DMR Tier III OACSU transaction. */
|
|
|
|
bool HostTestHooks::dmrBeginU2UOACSU(dmr::Slot& slot, uint32_t callerId,
|
|
uint32_t calledId, uint8_t serviceOptions)
|
|
{
|
|
return slot.beginU2UOACSU(callerId, calledId, serviceOptions);
|
|
}
|
|
|
|
/* Returns the number of pending DMR OACSU transactions. */
|
|
|
|
size_t HostTestHooks::dmrPendingU2UOACSU(const dmr::Slot& slot)
|
|
{
|
|
return slot.m_u2uOACSUTransactions.size();
|
|
}
|
|
|
|
/* Delivers a synthetic called-unit OACSU response. */
|
|
|
|
bool HostTestHooks::dmrRespondU2UOACSU(dmr::Slot& slot, uint32_t calledId,
|
|
uint32_t callerId, bool accepted)
|
|
{
|
|
return slot.processU2UOACSUResponse(calledId, callerId, accepted);
|
|
}
|
|
|
|
/* Cancels a synthetic pending DMR OACSU transaction. */
|
|
|
|
bool HostTestHooks::dmrCancelU2UOACSU(dmr::Slot& slot, uint32_t callerId)
|
|
{
|
|
return slot.cancelU2UOACSU(callerId);
|
|
}
|
|
|
|
/* Gets a P25 Phase 2 slot for tests. */
|
|
|
|
p25::phase2::Slot& HostTestHooks::p25P2Slot(p25::phase2::Control& control, uint32_t slotNo)
|
|
{
|
|
assert(slotNo < p25::phase2::Control::SLOT_COUNT);
|
|
return slotNo == 0U ? *control.m_slot1 : *control.m_slot2;
|
|
}
|
|
|
|
/* Gets P25 Phase 2 RF state. */
|
|
|
|
RPT_RF_STATE HostTestHooks::p25P2RFState(const p25::phase2::Slot& slot) { return slot.m_rfState; }
|
|
|
|
void HostTestHooks::p25P2SetRFState(p25::phase2::Slot& slot, RPT_RF_STATE state) { slot.m_rfState = state; }
|
|
|
|
/* Gets P25 Phase 2 last source ID. */
|
|
|
|
uint32_t HostTestHooks::p25P2LastSrcId(const p25::phase2::Slot& slot)
|
|
{
|
|
return slot.m_rfLastSrcId != 0U ? slot.m_rfLastSrcId : slot.m_netLastSrcId;
|
|
}
|
|
|
|
/* Gets P25 Phase 2 last destination ID. */
|
|
|
|
uint32_t HostTestHooks::p25P2LastDstId(const p25::phase2::Slot& slot)
|
|
{
|
|
return slot.m_rfLastDstId != 0U ? slot.m_rfLastDstId : slot.m_netLastDstId;
|
|
}
|
|
|
|
/* Gets P25 Phase 2 RF scramble offset. */
|
|
|
|
uint16_t HostTestHooks::p25P2RFScrambleOffset(const p25::phase2::Slot& slot) { return slot.m_rfScrambleOffset; }
|
|
|
|
/* Gets P25 Phase 2 network scramble offset. */
|
|
|
|
uint16_t HostTestHooks::p25P2NetScrambleOffset(const p25::phase2::Slot& slot) { return slot.m_netScrambleOffset; }
|
|
|
|
/* Gets whether P25 Phase 2 ESS is complete. */
|
|
|
|
bool HostTestHooks::p25P2ESSComplete(const p25::phase2::Slot& slot)
|
|
{
|
|
return slot.m_voice.m_rfESSComplete || slot.m_voice.m_netESSComplete;
|
|
}
|
|
|
|
/* Gets P25 Phase 2 ESS algorithm ID. */
|
|
|
|
uint8_t HostTestHooks::p25P2ESSAlgId(const p25::phase2::Slot& slot)
|
|
{
|
|
const uint8_t* ess = slot.m_voice.m_netESSComplete ? slot.m_voice.m_netESS : slot.m_voice.m_rfESS;
|
|
return ess[0U];
|
|
}
|
|
|
|
/* Gets P25 Phase 2 ESS key ID. */
|
|
|
|
uint16_t HostTestHooks::p25P2ESSKeyId(const p25::phase2::Slot& slot)
|
|
{
|
|
const uint8_t* ess = slot.m_voice.m_netESSComplete ? slot.m_voice.m_netESS : slot.m_voice.m_rfESS;
|
|
return static_cast<uint16_t>((ess[1U] << 8U) | ess[2U]);
|
|
}
|
|
|
|
/* Copies P25 Phase 2 ESS MI. */
|
|
|
|
void HostTestHooks::p25P2ESSMI(const p25::phase2::Slot& slot, uint8_t* mi)
|
|
{
|
|
if (mi != nullptr)
|
|
::memcpy(mi, slot.m_voice.m_netESSComplete ? slot.m_voice.m_netESS + 3U :
|
|
slot.m_voice.m_rfESS + 3U, 9U);
|
|
}
|
|
|
|
/* Gets P25 Phase 2 RF VCH state. */
|
|
|
|
p25::phase2::Slot::VCH_STATE HostTestHooks::p25P2RFVCHState(const p25::phase2::Slot& slot) { return slot.m_rfVCHState; }
|
|
|
|
/* Gets P25 Phase 2 network VCH state. */
|
|
|
|
p25::phase2::Slot::VCH_STATE HostTestHooks::p25P2NetVCHState(const p25::phase2::Slot& slot) { return slot.m_netVCHState; }
|
|
|
|
/* Gets P25 Phase 2 DUID. */
|
|
|
|
p25::defines::P2_DUID::E HostTestHooks::p25P2DUID(const p25::phase2::Slot& slot) { return slot.m_duid; }
|
|
|
|
/* Gets P25 Phase 2 RF PTT count. */
|
|
|
|
uint8_t HostTestHooks::p25P2RFPTTCount(const p25::phase2::Slot& slot) { return slot.m_rfPTTCount; }
|
|
|
|
/* Gets P25 Phase 2 RF frames count. */
|
|
|
|
uint32_t HostTestHooks::p25P2RFFrames(const p25::phase2::Slot& slot) { return slot.m_rfFrames; }
|
|
|
|
/* Gets P25 Phase 2 network frames count. */
|
|
|
|
uint32_t HostTestHooks::p25P2NetFrames(const p25::phase2::Slot& slot) { return slot.m_netFrames; }
|
|
|
|
/* Gets P25 Phase 2 network lost frames count. */
|
|
uint32_t HostTestHooks::p25P2NetLost(const p25::phase2::Slot& slot) { return slot.m_netLost; }
|
|
|
|
/* Gets P25 Phase 2 network missed frames count. */
|
|
uint32_t HostTestHooks::p25P2NetMissed(const p25::phase2::Slot& slot) { return slot.m_netMissed; }
|
|
|
|
/* Gets P25 Phase 2 RF bits, network bits, RF errors, and network errors. */
|
|
|
|
uint32_t HostTestHooks::p25P2RFBits(const p25::phase2::Slot& slot) { return slot.m_rfBits; }
|
|
|
|
/* Gets P25 Phase 2 network bits. */
|
|
|
|
uint32_t HostTestHooks::p25P2NetBits(const p25::phase2::Slot& slot) { return slot.m_netBits; }
|
|
|
|
/* Gets P25 Phase 2 RF errors. */
|
|
|
|
uint32_t HostTestHooks::p25P2RFErrs(const p25::phase2::Slot& slot) { return slot.m_rfErrs; }
|
|
|
|
/* Gets P25 Phase 2 network errors. */
|
|
|
|
uint32_t HostTestHooks::p25P2NetErrs(const p25::phase2::Slot& slot) { return slot.m_netErrs; }
|
|
|
|
/* Gets P25 network state. */
|
|
|
|
RPT_NET_STATE HostTestHooks::p25NetState(const p25::Control& control) { return control.m_netState; }
|
|
|
|
/* Gets P25 RF state. */
|
|
|
|
RPT_RF_STATE HostTestHooks::p25RFState(const p25::Control& control) { return control.m_rfState; }
|
|
|
|
/* Gets P25 last network destination ID. */
|
|
|
|
uint32_t HostTestHooks::p25NetLastDstId(const p25::Control& control) { return control.m_netLastDstId; }
|
|
|
|
/* Gets P25 last network source ID. */
|
|
|
|
uint32_t HostTestHooks::p25NetLastSrcId(const p25::Control& control) { return control.m_netLastSrcId; }
|
|
|
|
/* Gets P25 network encryption parameters. */
|
|
|
|
uint8_t HostTestHooks::p25NetAlgId(const p25::Control& control) { return control.m_voice->m_netLC.getAlgId(); }
|
|
|
|
uint32_t HostTestHooks::p25NetKId(const p25::Control& control) { return control.m_voice->m_netLC.getKId(); }
|
|
|
|
void HostTestHooks::p25NetMI(const p25::Control& control, uint8_t* mi)
|
|
{
|
|
control.m_voice->m_netLC.getMI(mi);
|
|
}
|
|
|
|
/* Gets P25 permitted destination ID. */
|
|
|
|
uint32_t HostTestHooks::p25PermittedDstId(const p25::Control& control) { return control.m_permittedDstId; }
|
|
|
|
/* Gets P25 tail-on-idle flag. */
|
|
|
|
bool HostTestHooks::p25TailOnIdle(const p25::Control& control) { return control.m_tailOnIdle; }
|
|
|
|
/* Gets P25 network watchdog timer. */
|
|
|
|
Timer& HostTestHooks::p25NetworkWatchdog(p25::Control& control) { return control.m_networkWatchdog; }
|
|
|
|
/* Gets P25 network talkgroup hang timer. */
|
|
|
|
Timer& HostTestHooks::p25NetTGHang(p25::Control& control) { return control.m_netTGHang; }
|
|
|
|
/* Gets P25 RF talkgroup hang timer. */
|
|
|
|
Timer& HostTestHooks::p25RFTGHang(p25::Control& control) { return control.m_rfTGHang; }
|
|
|
|
/* Gets P25 RF loss watchdog timer. */
|
|
|
|
Timer& HostTestHooks::p25RFLossWatchdog(p25::Control& control) { return control.m_rfLossWatchdog; }
|
|
|
|
/* Configures P25 RF grant talkgroup steering for a test. */
|
|
|
|
void HostTestHooks::p25ConfigureRFGrantSteering(p25::Control& control, bool enabled, bool dedicated)
|
|
{
|
|
control.m_enableControl = true;
|
|
control.m_dedicatedControl = dedicated;
|
|
control.m_allowRFGrantTGSteering = enabled;
|
|
}
|
|
|
|
/* Configures whether a dedicated P25 control channel carries voice. */
|
|
|
|
void HostTestHooks::p25SetVoiceOnControl(p25::Control& control, bool enabled)
|
|
{
|
|
control.m_voiceOnControl = enabled;
|
|
}
|
|
|
|
/* Runs P25 network-voice admission for a test call. */
|
|
|
|
bool HostTestHooks::p25CheckNetworkVoiceAdmission(p25::Control& control, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
return control.m_voice->checkNetTrafficCollision(srcId, dstId, true, p25::defines::DUID::LDU1);
|
|
}
|
|
|
|
/* Tests whether the P25 affiliation table contains a grant. */
|
|
|
|
bool HostTestHooks::p25IsGranted(const p25::Control& control, uint32_t dstId)
|
|
{
|
|
return control.m_affiliations->isGranted(dstId);
|
|
}
|
|
|
|
/* Seeds a P25 grant for a steering-policy test. */
|
|
|
|
bool HostTestHooks::p25SeedGrant(p25::Control& control, uint32_t srcId, uint32_t dstId, bool netGranted)
|
|
{
|
|
return control.m_affiliations->grantCh(dstId, srcId, 15U, true, netGranted);
|
|
}
|
|
|
|
/* Seeds a P25 unit-to-unit grant for a call-procedure test. */
|
|
|
|
bool HostTestHooks::p25SeedU2UGrant(p25::Control& control, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
return control.m_affiliations->grantCh(dstId, srcId, 15U, false, false);
|
|
}
|
|
|
|
/* Configures P25 unit-to-unit call-procedure policy for a test. */
|
|
|
|
void HostTestHooks::p25ConfigureU2U(p25::Control& control, bool availabilityCheck)
|
|
{
|
|
control.m_enableControl = true;
|
|
control.m_ignoreAffiliationCheck = false;
|
|
control.m_control->m_unitToUnitAvailCheck = availabilityCheck;
|
|
}
|
|
|
|
/* Registers a P25 unit for a call-procedure test. */
|
|
|
|
void HostTestHooks::p25RegisterUnit(p25::Control& control, uint32_t srcId)
|
|
{
|
|
control.m_affiliations->unitReg(srcId);
|
|
}
|
|
|
|
/* Injects an independently encoded raw TSBK into P25 control signaling. */
|
|
|
|
bool HostTestHooks::p25ProcessRawTSBK(p25::Control& control, const uint8_t* raw)
|
|
{
|
|
std::unique_ptr<p25::lc::TSBK> tsbk = p25::lc::tsbk::TSBKFactory::createTSBK(raw, true);
|
|
if (tsbk == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
uint8_t frame[2U] = { 0U };
|
|
return control.m_control->process(frame, sizeof(frame), std::move(tsbk));
|
|
}
|
|
|
|
/* Injects an independently encoded raw TSBK through P25 network processing. */
|
|
|
|
bool HostTestHooks::p25ProcessNetworkRawTSBK(p25::Control& control, const uint8_t* raw)
|
|
{
|
|
uint8_t fec[p25::defines::P25_TSBK_FEC_LENGTH_BYTES] = { 0U };
|
|
uint8_t encoded[p25::defines::P25_TSDU_FRAME_LENGTH_BYTES] = { 0U };
|
|
edac::Trellis trellis;
|
|
trellis.encode12(raw, fec);
|
|
p25::P25Utils::encode(fec, encoded, 114U, 318U);
|
|
|
|
p25::lc::LC lc;
|
|
p25::data::LowSpeedData lsd;
|
|
p25::defines::DUID::E duid = p25::defines::DUID::TSDU;
|
|
return control.m_control->processNetwork(encoded, sizeof(encoded), lc, lsd, duid);
|
|
}
|
|
|
|
/* Emits a P25 deny response through the RF control-signaling path. */
|
|
|
|
void HostTestHooks::p25WriteDenyResponse(p25::Control& control, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
control.m_enableControl = true;
|
|
control.m_control->writeRF_TSDU_Deny(srcId, dstId,
|
|
p25::defines::ReasonCode::DENY_TGT_GROUP_NOT_VALID,
|
|
p25::defines::TSBKO::IOSP_GRP_VCH, true, true);
|
|
}
|
|
|
|
/* Tests whether one P25 private grant owns the specified RID pair. */
|
|
|
|
bool HostTestHooks::p25IsU2UGranted(const p25::Control& control, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
return control.m_affiliations->isU2UGranted(srcId, dstId);
|
|
}
|
|
|
|
/* Gets the current source RID advertised for a P25 private grant. */
|
|
|
|
uint32_t HostTestHooks::p25U2UGrantSource(const p25::Control& control, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
const uint32_t owner = control.m_affiliations->getU2UGrantOwner(srcId, dstId);
|
|
return control.m_affiliations->getU2UGrantSource(owner);
|
|
}
|
|
|
|
/* Gets the current target RID advertised for a P25 private grant. */
|
|
|
|
uint32_t HostTestHooks::p25U2UGrantTarget(const p25::Control& control, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
const uint32_t owner = control.m_affiliations->getU2UGrantOwner(srcId, dstId);
|
|
return control.m_affiliations->getU2UGrantTarget(owner);
|
|
}
|
|
|
|
/* Gets the number of pending P25 U2U availability transactions. */
|
|
|
|
size_t HostTestHooks::p25PendingU2UCount(const p25::Control& control)
|
|
{
|
|
return control.m_control->m_u2uAvailabilityTransactions.size();
|
|
}
|
|
|
|
/* Advances P25 U2U availability transaction timers. */
|
|
|
|
void HostTestHooks::p25ClockU2U(p25::Control& control, uint32_t ms)
|
|
{
|
|
control.m_control->clock(ms);
|
|
}
|
|
|
|
/* Stores an MFID transaction for a control-signaling test. */
|
|
|
|
void HostTestHooks::p25StoreMFId(p25::Control& control, uint32_t srcId, uint32_t dstId,
|
|
uint8_t lco, uint8_t mfId, uint32_t timeoutSeconds)
|
|
{
|
|
control.m_control->storeMFId(srcId, dstId, lco, mfId, timeoutSeconds);
|
|
}
|
|
|
|
/* Resolves an MFID transaction with a compatibility fallback. */
|
|
|
|
uint8_t HostTestHooks::p25TransactionMFId(const p25::Control& control, uint32_t srcId,
|
|
uint32_t dstId, uint8_t lco, uint8_t fallback)
|
|
{
|
|
return control.m_control->transactionMFId(srcId, dstId, lco, fallback);
|
|
}
|
|
|
|
/* Tests whether an exact MFID transaction exists. */
|
|
|
|
bool HostTestHooks::p25HasMFIdTransaction(const p25::Control& control, uint32_t srcId,
|
|
uint32_t dstId, uint8_t lco)
|
|
{
|
|
return control.m_control->hasMFIdTransaction(srcId, dstId, lco);
|
|
}
|
|
|
|
/* Clears one exact MFID transaction. */
|
|
|
|
void HostTestHooks::p25ClearMFId(p25::Control& control, uint32_t srcId, uint32_t dstId, uint8_t lco)
|
|
{
|
|
control.m_control->clearMFId(srcId, dstId, lco);
|
|
}
|
|
|
|
/* Clears MFID transactions by LCO and optional subscriber. */
|
|
|
|
void HostTestHooks::p25ClearMFIds(p25::Control& control, uint8_t lco, uint32_t subscriberId)
|
|
{
|
|
control.m_control->clearMFIds(lco, subscriberId);
|
|
}
|
|
|
|
/* Gets the legacy ambient MFID compatibility value. */
|
|
|
|
uint8_t HostTestHooks::p25LastMFId(const p25::Control& control)
|
|
{
|
|
return control.m_control->m_lastMFID;
|
|
}
|
|
|
|
/* Sets the legacy ambient MFID compatibility value. */
|
|
|
|
void HostTestHooks::p25SetLastMFId(p25::Control& control, uint8_t mfId)
|
|
{
|
|
control.m_control->setLastMFId(mfId);
|
|
}
|
|
|
|
/* Releases a P25 grant for a steering-policy test. */
|
|
|
|
bool HostTestHooks::p25ReleaseGrant(p25::Control& control, uint32_t dstId)
|
|
{
|
|
return control.m_affiliations->releaseGrant(dstId, false);
|
|
}
|
|
|
|
/* Releases the active P25 private grant involving a radio ID. */
|
|
|
|
bool HostTestHooks::p25ReleaseU2UGrant(p25::Control& control, uint32_t unitId)
|
|
{
|
|
return control.m_affiliations->releaseU2UGrant(unitId);
|
|
}
|
|
|
|
/* Applies the P25 voice-channel grant-release policy. */
|
|
|
|
void HostTestHooks::p25VoiceChannelReleaseGrant(p25::Control& control, uint32_t dstId)
|
|
{
|
|
control.releaseGrantFromVoiceChannel(dstId);
|
|
}
|
|
|
|
/* Gets the last P25 RF destination ID. */
|
|
|
|
uint32_t HostTestHooks::p25RFLastDstId(const p25::Control& control) { return control.m_rfLastDstId; }
|
|
|
|
/* Forces P25 control network state and last IDs. */
|
|
|
|
void HostTestHooks::p25SetNetState(p25::Control& control, RPT_NET_STATE netState, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
control.m_netState = netState;
|
|
control.m_netLastSrcId = srcId;
|
|
control.m_netLastDstId = dstId;
|
|
}
|
|
|
|
/* Forces P25 control into active RF call state. */
|
|
|
|
void HostTestHooks::p25SetRFCall(p25::Control& control, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
control.m_rfState = RS_RF_AUDIO;
|
|
control.m_rfLastSrcId = srcId;
|
|
control.m_rfLastDstId = dstId;
|
|
control.m_rfTGHang.start();
|
|
}
|
|
|
|
/* Returns P25 control to RF listening state. */
|
|
|
|
void HostTestHooks::p25ClearRFCall(p25::Control& control)
|
|
{
|
|
control.m_rfState = RS_RF_LISTENING;
|
|
control.m_rfLastSrcId = 0U;
|
|
control.m_rfLastDstId = 0U;
|
|
control.m_rfTGHang.stop();
|
|
}
|
|
|
|
/* Forces P25 control into RF rejected state. */
|
|
|
|
void HostTestHooks::p25SetRFRejected(p25::Control& control)
|
|
{
|
|
control.m_rfState = RS_RF_REJECTED;
|
|
}
|
|
|
|
/* Encodes and stamps a valid NID for a DUID into a P25 RF frame. */
|
|
|
|
void HostTestHooks::p25StampRFFrameNID(p25::Control& control, uint8_t* frame, p25::defines::DUID::E duid)
|
|
{
|
|
control.m_nid.encode(frame + 2U, duid);
|
|
}
|
|
|
|
/* Injects synthetic P25 network call start. */
|
|
|
|
void HostTestHooks::p25StartNetCall(p25::Control& control, const p25::lc::LC& lc, const p25::data::LowSpeedData& lsd)
|
|
{
|
|
p25::packet::Voice* voice = control.m_voice;
|
|
voice->m_dfsiLC = p25::dfsi::LC(lc, lsd);
|
|
voice->m_netLastLDU1 = lc;
|
|
voice->writeNet_LDU1();
|
|
}
|
|
|
|
/* Injects synthetic P25 network encryption-sync data. */
|
|
|
|
void HostTestHooks::p25UpdateNetEncryption(p25::Control& control, const p25::lc::LC& lc, const p25::data::LowSpeedData& lsd)
|
|
{
|
|
p25::packet::Voice* voice = control.m_voice;
|
|
voice->m_dfsiLC = p25::dfsi::LC(lc, lsd);
|
|
voice->writeNet_LDU2();
|
|
}
|
|
|
|
/* Injects synthetic P25 network call termination. */
|
|
|
|
bool HostTestHooks::p25TerminateNetCall(p25::Control& control, const p25::lc::LC& lc, p25::defines::DUID::E duid)
|
|
{
|
|
p25::packet::Voice* voice = control.m_voice;
|
|
uint8_t buffer[1U] = { 0x00U };
|
|
p25::data::LowSpeedData lsd;
|
|
p25::defines::FrameType::E frameType = p25::defines::FrameType::DATA_UNIT;
|
|
p25::lc::LC localControl = lc;
|
|
p25::defines::DUID::E localDuid = duid;
|
|
return voice->processNetwork(buffer, 1U, localControl, lsd, localDuid, frameType);
|
|
}
|
|
|
|
/* Attempts to write a synthetic P25 RF voice frame to the network. */
|
|
|
|
void HostTestHooks::p25WriteRFVoiceToNetwork(p25::Control& control, uint32_t srcId, uint32_t dstId, bool group)
|
|
{
|
|
control.m_voice->m_rfLC.setLCO(group ? p25::defines::LCO::GROUP : p25::defines::LCO::PRIVATE);
|
|
control.m_voice->m_rfLC.setSrcId(srcId);
|
|
control.m_voice->m_rfLC.setDstId(dstId);
|
|
|
|
uint8_t data[p25::defines::P25_LDU_FRAME_LENGTH_BYTES] = { 0U };
|
|
control.m_voice->writeNetwork(data, p25::defines::DUID::LDU1);
|
|
}
|
|
|
|
/* Processes one queued P25 network frame. */
|
|
|
|
void HostTestHooks::p25ProcessNetworkQueue(p25::Control& control)
|
|
{
|
|
control.processNetwork();
|
|
}
|
|
|
|
/* Selects the next secondary active talkgroup for a P25 voice channel. */
|
|
|
|
bool HostTestHooks::p25NextActiveTalkgroups(p25::Control& control, const std::vector<uint32_t>& activeTG,
|
|
uint8_t& groupUpdtIndex, const uint32_t& dstId, uint32_t& dstIdB, bool& hasDstIdB)
|
|
{
|
|
return control.m_voice->nextActiveTalkgroups(activeTG, groupUpdtIndex, dstId, dstIdB, hasDstIdB);
|
|
}
|
|
|
|
/* Gets NXDN network state. */
|
|
|
|
RPT_NET_STATE HostTestHooks::nxdnNetState(const nxdn::Control& control) { return control.m_netState; }
|
|
|
|
/* Gets NXDN RF state. */
|
|
|
|
RPT_RF_STATE HostTestHooks::nxdnRFState(const nxdn::Control& control) { return control.m_rfState; }
|
|
|
|
/* Gets NXDN last network destination ID. */
|
|
|
|
uint32_t HostTestHooks::nxdnNetLastDstId(const nxdn::Control& control) { return control.m_netLastDstId; }
|
|
|
|
/* Gets NXDN last network source ID. */
|
|
|
|
uint32_t HostTestHooks::nxdnNetLastSrcId(const nxdn::Control& control) { return control.m_netLastSrcId; }
|
|
|
|
/* Gets NXDN network encryption parameters. */
|
|
|
|
uint8_t HostTestHooks::nxdnNetAlgId(const nxdn::Control& control) { return control.m_netLC.getAlgId(); }
|
|
|
|
uint8_t HostTestHooks::nxdnNetKId(const nxdn::Control& control) { return control.m_netLC.getKId(); }
|
|
|
|
void HostTestHooks::nxdnNetMI(const nxdn::Control& control, uint8_t* mi)
|
|
{
|
|
control.m_netLC.getMI(mi);
|
|
}
|
|
|
|
/* Gets NXDN permitted destination ID. */
|
|
|
|
uint32_t HostTestHooks::nxdnPermittedDstId(const nxdn::Control& control) { return control.m_permittedDstId; }
|
|
|
|
/* Gets NXDN network watchdog timer. */
|
|
|
|
Timer& HostTestHooks::nxdnNetworkWatchdog(nxdn::Control& control) { return control.m_networkWatchdog; }
|
|
|
|
/* Gets NXDN network talkgroup hang timer. */
|
|
|
|
Timer& HostTestHooks::nxdnNetTGHang(nxdn::Control& control) { return control.m_netTGHang; }
|
|
|
|
/* Gets NXDN RF talkgroup hang timer. */
|
|
|
|
Timer& HostTestHooks::nxdnRFTGHang(nxdn::Control& control) { return control.m_rfTGHang; }
|
|
|
|
/* Gets NXDN RF loss watchdog timer. */
|
|
|
|
Timer& HostTestHooks::nxdnRFLossWatchdog(nxdn::Control& control) { return control.m_rfLossWatchdog; }
|
|
|
|
/* Forces NXDN control into active RF call state. */
|
|
|
|
void HostTestHooks::nxdnSetRFCall(nxdn::Control& control, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
control.m_rfState = RS_RF_AUDIO;
|
|
control.m_rfLastSrcId = srcId;
|
|
control.m_rfLastDstId = dstId;
|
|
control.m_rfTGHang.start();
|
|
}
|
|
|
|
/* Forces NXDN control into residual RF talkgroup hang state. */
|
|
|
|
void HostTestHooks::nxdnSetRFHang(nxdn::Control& control, uint32_t srcId, uint32_t dstId)
|
|
{
|
|
control.m_rfState = RS_RF_LISTENING;
|
|
control.m_rfLastSrcId = srcId;
|
|
control.m_rfLastDstId = dstId;
|
|
control.m_rfTGHang.start();
|
|
}
|
|
|
|
/* Returns NXDN control to RF listening state. */
|
|
|
|
void HostTestHooks::nxdnClearRFCall(nxdn::Control& control)
|
|
{
|
|
control.m_rfState = RS_RF_LISTENING;
|
|
control.m_rfLastSrcId = 0U;
|
|
control.m_rfLastDstId = 0U;
|
|
control.m_rfTGHang.stop();
|
|
}
|
|
|
|
/* Forces NXDN control into RF rejected state. */
|
|
|
|
void HostTestHooks::nxdnSetRFRejected(nxdn::Control& control)
|
|
{
|
|
control.m_rfState = RS_RF_REJECTED;
|
|
}
|
|
|
|
/* Injects synthetic NXDN RF call start frame. */
|
|
|
|
bool HostTestHooks::nxdnStartRFCall(nxdn::Control& control, uint32_t srcId, uint32_t dstId, bool group)
|
|
{
|
|
uint8_t start[nxdn::defines::NXDN_FRAME_LENGTH_BYTES + 2U];
|
|
::memset(start, 0x00U, sizeof(start));
|
|
|
|
::memcpy(start + 2U, NXDN_CAL_1K_PAYLOAD, nxdn::defines::NXDN_FRAME_LENGTH_BYTES);
|
|
|
|
start[0U] = modem::TAG_DATA;
|
|
start[1U] = 0x01U;
|
|
|
|
nxdn::Sync::addNXDNSync(start + 2U);
|
|
|
|
nxdn::channel::LICH lich;
|
|
lich.setRFCT(nxdn::defines::RFChannelType::RTCH);
|
|
lich.setFCT(nxdn::defines::FuncChannelType::USC_SACCH_NS);
|
|
lich.setOption(nxdn::defines::ChOption::STEAL_FACCH);
|
|
lich.setOutbound(false);
|
|
lich.encode(start + 2U);
|
|
|
|
nxdn::channel::SACCH sacch;
|
|
sacch.setData(nxdn::defines::SACCH_IDLE);
|
|
sacch.setRAN(control.m_ran);
|
|
sacch.setStructure(nxdn::defines::ChStructure::SR_SINGLE);
|
|
sacch.encode(start + 2U);
|
|
|
|
uint8_t buffer[nxdn::defines::NXDN_RTCH_LC_LENGTH_BYTES];
|
|
nxdn::lc::RTCH lc;
|
|
lc.setMessageType(nxdn::defines::MessageType::RTCH_VCALL);
|
|
lc.setCallType(group ? nxdn::defines::CallType::BROADCAST : nxdn::defines::CallType::INDIVIDUAL);
|
|
lc.setSrcId(srcId);
|
|
lc.setDstId(dstId);
|
|
lc.setGroup(group);
|
|
lc.setTransmissionMode(nxdn::defines::TransmissionMode::MODE_4800);
|
|
lc.encode(buffer, nxdn::defines::NXDN_RTCH_LC_LENGTH_BITS);
|
|
|
|
nxdn::channel::FACCH1 facch;
|
|
facch.setData(buffer);
|
|
facch.encode(start + 2U, nxdn::defines::NXDN_FSW_LENGTH_BITS + nxdn::defines::NXDN_LICH_LENGTH_BITS + nxdn::defines::NXDN_SACCH_FEC_LENGTH_BITS);
|
|
|
|
facch.encode(start + 2U, nxdn::defines::NXDN_FSW_LENGTH_BITS + nxdn::defines::NXDN_LICH_LENGTH_BITS + nxdn::defines::NXDN_SACCH_FEC_LENGTH_BITS + nxdn::defines::NXDN_FACCH1_FEC_LENGTH_BITS);
|
|
|
|
nxdn::NXDNUtils::scrambler(start + 2U);
|
|
|
|
return control.processFrame(start, sizeof(start));
|
|
}
|
|
|
|
/* Injects synthetic NXDN network call start frame. */
|
|
|
|
bool HostTestHooks::nxdnStartNetCall(nxdn::Control& control, const nxdn::lc::RTCH& lc)
|
|
{
|
|
uint8_t start[nxdn::defines::NXDN_FRAME_LENGTH_BYTES + 2U];
|
|
::memset(start, 0x00U, sizeof(start));
|
|
|
|
nxdn::Sync::addNXDNSync(start + 2U);
|
|
|
|
nxdn::channel::LICH lich;
|
|
lich.setRFCT(nxdn::defines::RFChannelType::RCCH);
|
|
lich.setFCT(nxdn::defines::FuncChannelType::USC_SACCH_NS);
|
|
lich.setOption(nxdn::defines::ChOption::STEAL_FACCH);
|
|
lich.setOutbound(true);
|
|
lich.encode(start + 2U);
|
|
|
|
nxdn::channel::SACCH sacch;
|
|
sacch.setData(nxdn::defines::SACCH_IDLE);
|
|
sacch.setRAN(control.m_ran);
|
|
sacch.setStructure(nxdn::defines::ChStructure::SR_SINGLE);
|
|
sacch.encode(start + 2U);
|
|
|
|
uint8_t buffer[nxdn::defines::NXDN_RTCH_LC_LENGTH_BYTES];
|
|
nxdn::lc::RTCH localControl = lc;
|
|
localControl.encode(buffer, nxdn::defines::NXDN_RTCH_LC_LENGTH_BITS);
|
|
|
|
nxdn::channel::FACCH1 facch;
|
|
facch.setData(buffer);
|
|
facch.encode(start + 2U, nxdn::defines::NXDN_FSW_LENGTH_BITS + nxdn::defines::NXDN_LICH_LENGTH_BITS + nxdn::defines::NXDN_SACCH_FEC_LENGTH_BITS);
|
|
if (localControl.getAlgId() == nxdn::defines::CIPHER_TYPE_DES ||
|
|
localControl.getAlgId() == nxdn::defines::CIPHER_TYPE_AES) {
|
|
nxdn::lc::RTCH ivLC;
|
|
ivLC.setMessageType(nxdn::defines::MessageType::RTCH_VCALL_IV);
|
|
uint8_t mi[nxdn::defines::MI_LENGTH_BYTES];
|
|
localControl.getMI(mi);
|
|
ivLC.setMI(mi);
|
|
::memset(buffer, 0x00U, sizeof(buffer));
|
|
ivLC.encode(buffer, nxdn::defines::NXDN_RTCH_LC_LENGTH_BITS);
|
|
facch.setData(buffer);
|
|
}
|
|
facch.encode(start + 2U, nxdn::defines::NXDN_FSW_LENGTH_BITS + nxdn::defines::NXDN_LICH_LENGTH_BITS + nxdn::defines::NXDN_SACCH_FEC_LENGTH_BITS + nxdn::defines::NXDN_FACCH1_FEC_LENGTH_BITS);
|
|
|
|
return control.m_voice->processNetwork(nxdn::defines::FuncChannelType::USC_SACCH_NS, nxdn::defines::ChOption::STEAL_FACCH, localControl, start, sizeof(start));
|
|
}
|
|
#endif
|