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.
1139 lines
41 KiB
1139 lines
41 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 "fne/FNETestHooks.h"
|
|
#include "fne/HostFNE.h"
|
|
#include "fne/network/P25OTARService.h"
|
|
#include "fne/network/callhandler/TagP25Data.h"
|
|
#include "fne/network/callhandler/TagDMRData.h"
|
|
#include "fne/network/callhandler/TagP25P2Data.h"
|
|
#include "fne/network/callhandler/TagNXDNData.h"
|
|
#include "fne/network/callhandler/TagAnalogData.h"
|
|
#include "fne/lookups/AffiliationLookup.h"
|
|
#include "common/p25/data/Assembler.h"
|
|
#include "common/Utils.h"
|
|
|
|
#include <cstring>
|
|
#include <chrono>
|
|
#include <stdexcept>
|
|
|
|
using namespace network;
|
|
using namespace p25::defines;
|
|
using namespace p25::data;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Global Variables
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool g_promiscuousHub = false;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public Class Members
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/* Tests whether a P25 frame is telephone interconnect traffic. */
|
|
|
|
bool FNETestHooks::isP25TelIntTraffic(TrafficNetwork& network, p25::lc::LC& control,
|
|
DUID::E duid, const p25::lc::TSBK* tsbk)
|
|
{
|
|
return network.m_tagP25->isTelIntTraffic(control, duid, tsbk);
|
|
}
|
|
|
|
/* Tests whether telephone interconnect traffic may be routed between two peers. */
|
|
|
|
bool FNETestHooks::isP25TelIntPeerPermitted(TrafficNetwork& network, uint32_t srcPeerId,
|
|
uint32_t dstPeerId, uint32_t dstId)
|
|
{
|
|
return network.m_tagP25->isTelIntPeerPermitted(srcPeerId, dstPeerId,
|
|
network.findPeer(dstPeerId), dstId);
|
|
}
|
|
|
|
/* Adds a unit registration owned by a site peer. */
|
|
|
|
void FNETestHooks::registerP25Unit(TrafficNetwork& network, uint32_t sitePeerId, uint32_t unitId)
|
|
{
|
|
std::shared_ptr<fne_lookups::AffiliationLookup> affiliations =
|
|
std::make_shared<fne_lookups::AffiliationLookup>("test-p25-registrations", nullptr, false);
|
|
affiliations->unitReg(unitId, sitePeerId);
|
|
network.m_peerAffiliations[sitePeerId] = affiliations;
|
|
network.updatePeerUnitRegistration(sitePeerId, unitId, sitePeerId);
|
|
}
|
|
|
|
/* Adds a talkgroup affiliation owned by a site peer. */
|
|
|
|
void FNETestHooks::addGroupAffiliation(TrafficNetwork& network, uint32_t sitePeerId,
|
|
uint32_t unitId, uint32_t talkgroupId)
|
|
{
|
|
std::shared_ptr<fne_lookups::AffiliationLookup> affiliations =
|
|
network.getPeerAffiliations(sitePeerId);
|
|
if (affiliations == nullptr) {
|
|
affiliations = std::make_shared<fne_lookups::AffiliationLookup>(
|
|
"test-parrot-affiliations", nullptr, false);
|
|
network.m_peerAffiliations[sitePeerId] = affiliations;
|
|
}
|
|
|
|
affiliations->groupAff(unitId, talkgroupId);
|
|
}
|
|
|
|
/* Captures peer IDs selected by subsequent test writes. */
|
|
|
|
void FNETestHooks::capturePeerWrites(TrafficNetwork& network, std::vector<uint32_t>& peerIds)
|
|
{
|
|
network.m_testPeerWriteHandler = [&peerIds](uint32_t peerId, FrameQueue::OpcodePair) {
|
|
peerIds.push_back(peerId);
|
|
};
|
|
}
|
|
|
|
/* Clears the test peer-write capture callback. */
|
|
|
|
void FNETestHooks::clearPeerWriteCapture(TrafficNetwork& network)
|
|
{
|
|
network.m_testPeerWriteHandler = nullptr;
|
|
}
|
|
|
|
/* Queues and plays one synthetic parrot frame through a protocol handler. */
|
|
|
|
void FNETestHooks::playbackParrotFrame(TrafficNetwork& network, ParrotProtocol protocol,
|
|
uint32_t sourcePeerId, uint32_t sourceId, uint32_t talkgroupId)
|
|
{
|
|
constexpr uint32_t FRAME_LENGTH = 16U;
|
|
constexpr uint16_t PACKET_SEQUENCE = 1U;
|
|
constexpr uint32_t STREAM_ID = 100U;
|
|
|
|
switch (protocol) {
|
|
case ParrotProtocol::DMR:
|
|
{
|
|
callhandler::TagDMRData::ParrotFrame frame{};
|
|
frame.buffer = new uint8_t[FRAME_LENGTH]();
|
|
frame.bufferLen = FRAME_LENGTH;
|
|
frame.slotNo = 1U;
|
|
frame.pktSeq = PACKET_SEQUENCE;
|
|
frame.streamId = STREAM_ID;
|
|
frame.peerId = sourcePeerId;
|
|
frame.srcId = sourceId;
|
|
frame.dstId = talkgroupId;
|
|
network.m_tagDMR->m_parrotFrames.push_back(frame);
|
|
network.m_tagDMR->playbackParrot();
|
|
break;
|
|
}
|
|
case ParrotProtocol::P25:
|
|
{
|
|
callhandler::TagP25Data::ParrotFrame frame{};
|
|
frame.buffer = new uint8_t[FRAME_LENGTH]();
|
|
frame.bufferLen = FRAME_LENGTH;
|
|
frame.pktSeq = PACKET_SEQUENCE;
|
|
frame.streamId = STREAM_ID;
|
|
frame.peerId = sourcePeerId;
|
|
frame.srcId = sourceId;
|
|
frame.dstId = talkgroupId;
|
|
network.m_tagP25->m_parrotFrames.push_back(frame);
|
|
network.m_tagP25->playbackParrot();
|
|
break;
|
|
}
|
|
case ParrotProtocol::P25P2:
|
|
{
|
|
callhandler::TagP25P2Data::ParrotFrame frame{};
|
|
frame.buffer = new uint8_t[FRAME_LENGTH]();
|
|
frame.bufferLen = FRAME_LENGTH;
|
|
frame.pktSeq = PACKET_SEQUENCE;
|
|
frame.streamId = STREAM_ID;
|
|
frame.peerId = sourcePeerId;
|
|
frame.srcId = sourceId;
|
|
frame.dstId = talkgroupId;
|
|
network.m_tagP25P2->m_parrotFrames.push_back(frame);
|
|
network.m_tagP25P2->playbackParrot();
|
|
break;
|
|
}
|
|
case ParrotProtocol::NXDN:
|
|
{
|
|
callhandler::TagNXDNData::ParrotFrame frame{};
|
|
frame.buffer = new uint8_t[FRAME_LENGTH]();
|
|
frame.bufferLen = FRAME_LENGTH;
|
|
frame.pktSeq = PACKET_SEQUENCE;
|
|
frame.streamId = STREAM_ID;
|
|
frame.peerId = sourcePeerId;
|
|
frame.srcId = sourceId;
|
|
frame.dstId = talkgroupId;
|
|
network.m_tagNXDN->m_parrotFrames.push_back(frame);
|
|
network.m_tagNXDN->playbackParrot();
|
|
break;
|
|
}
|
|
case ParrotProtocol::ANALOG:
|
|
{
|
|
callhandler::TagAnalogData::ParrotFrame frame{};
|
|
frame.buffer = new uint8_t[FRAME_LENGTH]();
|
|
frame.bufferLen = FRAME_LENGTH;
|
|
frame.pktSeq = PACKET_SEQUENCE;
|
|
frame.streamId = STREAM_ID;
|
|
frame.peerId = sourcePeerId;
|
|
frame.srcId = sourceId;
|
|
frame.dstId = talkgroupId;
|
|
network.m_tagAnalog->m_parrotFrames.push_back(frame);
|
|
network.m_tagAnalog->playbackParrot();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Evaluates a call handler's peer-routing policy. */
|
|
|
|
bool FNETestHooks::isPeerPermitted(TrafficNetwork& network, ParrotProtocol protocol,
|
|
uint32_t peerId, uint32_t destinationId, bool group, bool fromUpstream, uint8_t slotNo)
|
|
{
|
|
constexpr uint32_t STREAM_ID = 101U;
|
|
|
|
switch (protocol) {
|
|
case ParrotProtocol::DMR:
|
|
{
|
|
dmr::data::NetData data;
|
|
data.setDstId(destinationId);
|
|
data.setSlotNo(slotNo);
|
|
data.setFLCO(group ? dmr::defines::FLCO::GROUP : dmr::defines::FLCO::PRIVATE);
|
|
return network.m_tagDMR->isPeerPermitted(peerId, data, STREAM_ID, fromUpstream);
|
|
}
|
|
case ParrotProtocol::P25:
|
|
{
|
|
p25::lc::LC control;
|
|
control.setDstId(destinationId);
|
|
control.setLCO(group ? p25::defines::LCO::GROUP : p25::defines::LCO::PRIVATE);
|
|
control.setGroup(group);
|
|
return network.m_tagP25->isPeerPermitted(
|
|
peerId, control, p25::defines::DUID::LDU1, STREAM_ID, fromUpstream);
|
|
}
|
|
case ParrotProtocol::P25P2:
|
|
return network.m_tagP25P2->isPeerPermitted(
|
|
peerId, destinationId, slotNo, !group, STREAM_ID, fromUpstream);
|
|
case ParrotProtocol::NXDN:
|
|
{
|
|
nxdn::lc::RTCH control;
|
|
control.setDstId((uint16_t)destinationId);
|
|
control.setGroup(group);
|
|
return network.m_tagNXDN->isPeerPermitted(
|
|
peerId, control, nxdn::defines::MessageType::RTCH_VCALL, STREAM_ID, fromUpstream);
|
|
}
|
|
case ParrotProtocol::ANALOG:
|
|
{
|
|
analog::data::NetData data;
|
|
data.setDstId(destinationId);
|
|
data.setGroup(group);
|
|
return network.m_tagAnalog->isPeerPermitted(peerId, data, STREAM_ID, fromUpstream);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Evaluates a call handler's group-call validation policy. */
|
|
|
|
bool FNETestHooks::validateGroupCall(TrafficNetwork& network, ParrotProtocol protocol,
|
|
uint32_t peerId, uint32_t sourceId, uint32_t talkgroupId, uint8_t slotNo)
|
|
{
|
|
constexpr uint32_t STREAM_ID = 102U;
|
|
|
|
switch (protocol) {
|
|
case ParrotProtocol::DMR:
|
|
{
|
|
dmr::data::NetData data;
|
|
data.setSrcId(sourceId);
|
|
data.setDstId(talkgroupId);
|
|
data.setSlotNo(slotNo);
|
|
data.setFLCO(dmr::defines::FLCO::GROUP);
|
|
data.setDataType(dmr::defines::DataType::VOICE_LC_HEADER);
|
|
return network.m_tagDMR->validate(peerId, data, nullptr, STREAM_ID);
|
|
}
|
|
case ParrotProtocol::P25:
|
|
{
|
|
p25::lc::LC control;
|
|
control.setSrcId(sourceId);
|
|
control.setDstId(talkgroupId);
|
|
control.setLCO(p25::defines::LCO::GROUP);
|
|
return network.m_tagP25->validate(
|
|
peerId, control, p25::defines::DUID::LDU1, nullptr, 0U, STREAM_ID);
|
|
}
|
|
case ParrotProtocol::P25P2:
|
|
return network.m_tagP25P2->validate(
|
|
peerId, sourceId, talkgroupId, slotNo, false, STREAM_ID);
|
|
case ParrotProtocol::NXDN:
|
|
{
|
|
nxdn::lc::RTCH control;
|
|
control.setSrcId((uint16_t)sourceId);
|
|
control.setDstId((uint16_t)talkgroupId);
|
|
control.setGroup(true);
|
|
return network.m_tagNXDN->validate(
|
|
peerId, control, nxdn::defines::MessageType::RTCH_VCALL, STREAM_ID);
|
|
}
|
|
case ParrotProtocol::ANALOG:
|
|
{
|
|
analog::data::NetData data;
|
|
data.setSrcId(sourceId);
|
|
data.setDstId(talkgroupId);
|
|
data.setGroup(true);
|
|
data.setFrameType(analog::defines::AudioFrameType::VOICE);
|
|
return network.m_tagAnalog->validate(peerId, data, STREAM_ID);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Validates P25 individual traffic whose current LCO is not private. */
|
|
|
|
bool FNETestHooks::validateP25IndividualCall(TrafficNetwork& network, uint32_t peerId,
|
|
uint32_t sourceId, uint32_t destinationId)
|
|
{
|
|
constexpr uint32_t STREAM_ID = 103U;
|
|
|
|
p25::lc::LC control;
|
|
control.setSrcId(sourceId);
|
|
control.setDstId(destinationId);
|
|
control.setLCO(p25::defines::LCO::GROUP);
|
|
control.setGroup(false);
|
|
return network.m_tagP25->validate(
|
|
peerId, control, p25::defines::DUID::LDU2, nullptr, 0U, STREAM_ID) &&
|
|
network.m_tagP25->isPeerPermitted(
|
|
peerId, control, p25::defines::DUID::LDU2, STREAM_ID, false);
|
|
}
|
|
|
|
/* Processes a synthetic DMR FNE grant request. */
|
|
|
|
bool FNETestHooks::processDMRGrantRequest(TrafficNetwork& network, uint32_t peerId,
|
|
uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions)
|
|
{
|
|
return network.m_tagDMR->processGrantReq(
|
|
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, uint8_t serviceOptions)
|
|
{
|
|
return network.m_tagP25->processGrantReq(
|
|
sourceId, destinationId, unitToUnit, serviceOptions, peerId, 1U, 107U);
|
|
}
|
|
|
|
/* Processes a synthetic P25 Phase 2 private voice frame. */
|
|
|
|
bool FNETestHooks::processP25P2PrivateFrame(TrafficNetwork& network, uint32_t peerId,
|
|
uint32_t sourceId, uint32_t destinationId, uint32_t streamId)
|
|
{
|
|
std::vector<uint8_t> frame(P25_P2_PACKET_LENGTH, 0U);
|
|
SET_UINT24(sourceId, frame.data(), 5U);
|
|
SET_UINT24(destinationId, frame.data(), 8U);
|
|
frame[14U] = NET_CTRL_U2U;
|
|
frame[19U] = P2_DUID::VTCH_4V;
|
|
frame[23U] = 64U;
|
|
return network.m_tagP25P2->processFrame(
|
|
frame.data(), static_cast<uint32_t>(frame.size()), peerId, peerId, 1U, streamId);
|
|
}
|
|
|
|
/* Processes a synthetic NXDN FNE grant request. */
|
|
|
|
bool FNETestHooks::processNXDNGrantRequest(TrafficNetwork& network, uint32_t peerId,
|
|
uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions)
|
|
{
|
|
return network.m_tagNXDN->processGrantReq(
|
|
sourceId, destinationId, unitToUnit, serviceOptions, peerId, 1U, 106U);
|
|
}
|
|
|
|
/* Sets whether unit-to-unit traffic is globally disabled. */
|
|
|
|
void FNETestHooks::disallowU2U(TrafficNetwork& network, bool disabled)
|
|
{
|
|
network.m_privateCallRoutingEnabled = !disabled;
|
|
}
|
|
|
|
/* Configures private-call route policy for a test. */
|
|
|
|
void FNETestHooks::configurePrivateCallRouting(TrafficNetwork& network, bool enabled,
|
|
const std::string& unknownDestination, bool learnFromTraffic, uint64_t routeLifetimeMs)
|
|
{
|
|
network.m_privateCallRoutingEnabled = enabled;
|
|
network.m_privateCallUnknownDestination = unknownDestination;
|
|
network.m_privateCallLearnFromTraffic = learnFromTraffic;
|
|
network.m_unitRouteRegistry.setLifetime(routeLifetimeMs);
|
|
}
|
|
|
|
/* Learns a subscriber route through the production traffic-learning path. */
|
|
|
|
void FNETestHooks::learnUnitRoute(TrafficNetwork& network, uint32_t peerId,
|
|
uint32_t unitId, uint32_t ssrc)
|
|
{
|
|
network.learnUnitRouteFromTraffic(peerId, unitId, ssrc);
|
|
}
|
|
|
|
/* Resolves the current next-hop peer for a subscriber. */
|
|
|
|
uint32_t FNETestHooks::findUnitRoute(TrafficNetwork& network, uint32_t unitId)
|
|
{
|
|
return network.findPeerUnitReg(unitId);
|
|
}
|
|
|
|
/* Tests destination availability under the current private-call policy. */
|
|
|
|
bool FNETestHooks::isUnitRouteAvailable(TrafficNetwork& network, uint32_t unitId)
|
|
{
|
|
return network.isUnitToUnitRouteAvailable(unitId);
|
|
}
|
|
|
|
/* Replaces one peer's explicit unit-registration snapshot. */
|
|
|
|
bool FNETestHooks::replaceUnitRegistrations(TrafficNetwork& network, uint32_t peerId,
|
|
const std::vector<uint32_t>& unitIds, uint32_t ssrc)
|
|
{
|
|
return network.replacePeerUnitRegistrations(peerId, unitIds, ssrc);
|
|
}
|
|
|
|
/* Sets whether conventional sites bypass affiliation requirements. */
|
|
|
|
void FNETestHooks::allowConventionalAffiliationOverride(TrafficNetwork& network, bool enabled)
|
|
{
|
|
network.m_allowConvSiteAffOverride = enabled;
|
|
}
|
|
|
|
/* Sets whether P25 terminators are filtered by destination policy. */
|
|
|
|
void FNETestHooks::filterTerminators(TrafficNetwork& network, bool enabled)
|
|
{
|
|
network.m_filterTerminators = enabled;
|
|
}
|
|
|
|
/* Evaluates P25 TDU peer-routing policy. */
|
|
|
|
bool FNETestHooks::isP25TerminatorPeerPermitted(TrafficNetwork& network,
|
|
uint32_t peerId, uint32_t talkgroupId)
|
|
{
|
|
p25::lc::LC control;
|
|
control.setDstId(talkgroupId);
|
|
control.setLCO(p25::defines::LCO::GROUP);
|
|
return network.m_tagP25->isPeerPermitted(
|
|
peerId, control, p25::defines::DUID::TDU, 103U, false);
|
|
}
|
|
|
|
/* Passes a deliberately truncated frame to a protocol handler. */
|
|
|
|
bool FNETestHooks::processTruncatedFrame(TrafficNetwork& network,
|
|
ParrotProtocol protocol, uint32_t peerId)
|
|
{
|
|
constexpr uint32_t STREAM_ID = 104U;
|
|
std::vector<uint8_t> frame(1U, 0U);
|
|
|
|
switch (protocol) {
|
|
case ParrotProtocol::DMR:
|
|
return network.m_tagDMR->processFrame(
|
|
frame.data(), (uint32_t)frame.size(), peerId, peerId, 1U, STREAM_ID);
|
|
case ParrotProtocol::P25:
|
|
frame.resize(24U, 0U);
|
|
frame[22U] = p25::defines::DUID::LDU1;
|
|
return network.m_tagP25->processFrame(
|
|
frame.data(), (uint32_t)frame.size(), peerId, peerId, 1U, STREAM_ID);
|
|
case ParrotProtocol::P25P2:
|
|
return network.m_tagP25P2->processFrame(
|
|
frame.data(), (uint32_t)frame.size(), peerId, peerId, 1U, STREAM_ID);
|
|
case ParrotProtocol::NXDN:
|
|
return network.m_tagNXDN->processFrame(
|
|
frame.data(), (uint32_t)frame.size(), peerId, peerId, 1U, STREAM_ID);
|
|
case ParrotProtocol::ANALOG:
|
|
return network.m_tagAnalog->processFrame(
|
|
frame.data(), (uint32_t)frame.size(), peerId, peerId, 1U, STREAM_ID);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Initializes an instance of the HostFNE class.
|
|
*/
|
|
HostFNE::HostFNE(const std::string& confFile) :
|
|
m_confFile(confFile), m_conf(), m_network(nullptr), m_mdNetwork(nullptr),
|
|
m_vtunEnabled(false), m_packetDataMode(PacketDataMode::PROJECT25),
|
|
#if !defined(_WIN32)
|
|
m_tun(nullptr),
|
|
#endif
|
|
m_dmrEnabled(false), m_p25Enabled(false), m_p25P2Enabled(false),
|
|
m_nxdnEnabled(false), m_analogEnabled(false), m_ridLookup(nullptr),
|
|
m_tidLookup(nullptr), m_peerListLookup(nullptr), m_adjSiteMapLookup(nullptr),
|
|
m_cryptoLookup(nullptr), m_peerNetworks(), m_pingTime(5U), m_maxMissedPings(5U),
|
|
m_updateLookupTime(10U), m_peerReplicaSavesACL(false),
|
|
m_allowActivityTransfer(false), m_allowDiagnosticTransfer(false), m_RESTAPI(nullptr)
|
|
{
|
|
/* stub */
|
|
}
|
|
|
|
/**
|
|
* @brief Finalizes an instance of the HostFNE class.
|
|
*/
|
|
HostFNE::~HostFNE() = default;
|
|
|
|
/**
|
|
* @brief Adds a peer to the specified TrafficNetwork.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param peerId The ID of the peer to add.
|
|
* @param state The connection state of the peer.
|
|
* @param connected Whether the peer is connected.
|
|
* @return Reference to the added FNEPeerConnection.
|
|
*/
|
|
FNEPeerConnection& FNETestHooks::addPeer(TrafficNetwork& network, uint32_t peerId,
|
|
NET_CONN_STATUS state, bool connected)
|
|
{
|
|
TrafficNetwork::PeerConnectionPtr connection = std::make_shared<FNEPeerConnection>();
|
|
connection->m_id = peerId;
|
|
connection->m_connectionState = state;
|
|
connection->m_connected = connected;
|
|
network.storePeer(peerId, connection);
|
|
return *connection;
|
|
}
|
|
|
|
/**
|
|
* @brief Passes an encoded KMM through the FNE OTAR message dispatcher.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param packet The KMM packet data.
|
|
* @param llId The logical link ID.
|
|
* @param payloadSize The size of the processed payload.
|
|
* @return A unique pointer to the processed KMM frame, or nullptr if processing failed.
|
|
*/
|
|
std::unique_ptr<uint8_t[]> FNETestHooks::processOTARKMM(TrafficNetwork& network,
|
|
const std::vector<uint8_t>& packet, uint32_t llId, uint32_t& payloadSize,
|
|
uint8_t outerAlgoId, uint16_t outerKId, bool dataLinkIndependent)
|
|
{
|
|
payloadSize = 0U;
|
|
if (packet.empty() || network.m_p25OTARService == nullptr)
|
|
return nullptr;
|
|
|
|
return network.m_p25OTARService->processKMM(packet.data(), (uint32_t)packet.size(), llId,
|
|
false, &payloadSize, outerAlgoId, outerKId, nullptr, dataLinkIndependent);
|
|
}
|
|
|
|
/**
|
|
* @brief Builds an OTAR rekey command for the specified logical link ID and KMM RSI.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param llId The logical link ID.
|
|
* @param kmmRSI The KMM RSI value.
|
|
* @return A vector of vectors containing the OTAR rekey frames.
|
|
*/
|
|
std::vector<std::vector<uint8_t>> FNETestHooks::buildOTARRekey(TrafficNetwork& network,
|
|
uint32_t llId, uint32_t kmmRSI)
|
|
{
|
|
std::vector<std::vector<uint8_t>> frames;
|
|
if (network.m_p25OTARService == nullptr)
|
|
return frames;
|
|
|
|
P25OTARService::KMMAuthContext auth;
|
|
uint32_t firstLength = 0U;
|
|
std::vector<std::vector<uint8_t>> additional;
|
|
const bool previousAllowClear = network.m_p25OTARService->m_allowNoUKEKRekey;
|
|
network.m_p25OTARService->m_allowNoUKEKRekey = true;
|
|
UInt8Array first = network.m_p25OTARService->write_KMM_Rekey_Command(llId, kmmRSI,
|
|
KMM_HelloFlag::REKEY_REQUEST_NO_UKEK, &firstLength, auth, &additional);
|
|
network.m_p25OTARService->m_allowNoUKEKRekey = previousAllowClear;
|
|
if (first == nullptr || firstLength == 0U)
|
|
return frames;
|
|
|
|
frames.emplace_back(first.get(), first.get() + firstLength);
|
|
for (std::vector<uint8_t>& frame : additional)
|
|
frames.push_back(std::move(frame));
|
|
return frames;
|
|
}
|
|
|
|
/**
|
|
* @brief Builds an OTAR Changeover Command without dispatching it.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param llId The logical link ID.
|
|
* @param kmmRSI The KMM RSI value.
|
|
* @param supersededKeysetId The ID of the superseded keyset.
|
|
* @param activeKeysetId The ID of the active keyset.
|
|
* @return A vector containing the encoded OTAR Changeover Command.
|
|
*/
|
|
std::vector<uint8_t> FNETestHooks::buildOTARChangeover(TrafficNetwork& network,
|
|
uint32_t llId, uint32_t kmmRSI, uint8_t supersededKeysetId, uint8_t activeKeysetId)
|
|
{
|
|
if (network.m_p25OTARService == nullptr)
|
|
return {};
|
|
|
|
uint32_t length = 0U;
|
|
UInt8Array encoded = network.m_p25OTARService->write_KMM_Changeover_Command(llId,
|
|
kmmRSI, supersededKeysetId, activeKeysetId, &length);
|
|
if (encoded == nullptr || length == 0U)
|
|
return {};
|
|
|
|
return std::vector<uint8_t>(encoded.get(), encoded.get() + length);
|
|
}
|
|
|
|
/**
|
|
* @brief Builds an OTAR Warm Start Command without dispatching it.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param llId The logical link ID.
|
|
* @param kmmRSI The KMM RSI value.
|
|
* @return A vector containing the encoded OTAR Warm Start Command.
|
|
*/
|
|
std::vector<uint8_t> FNETestHooks::buildOTARWarmStart(TrafficNetwork& network,
|
|
uint32_t llId, uint32_t kmmRSI)
|
|
{
|
|
if (network.m_p25OTARService == nullptr)
|
|
return {};
|
|
uint32_t length = 0U;
|
|
UInt8Array encoded = network.m_p25OTARService->write_KMM_WarmStart_Command(llId, kmmRSI, &length);
|
|
if (encoded == nullptr || length == 0U)
|
|
return {};
|
|
return std::vector<uint8_t>(encoded.get(), encoded.get() + length);
|
|
}
|
|
|
|
/**
|
|
* @brief Retrieves the temporary TEK for an OTAR Warm Start transaction.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param rsi The RSI associated with the Warm Start transaction.
|
|
* @param kid Output parameter for the key ID.
|
|
* @param key Output parameter for the temporary TEK.
|
|
* @return True if the temporary TEK was successfully retrieved, false otherwise.
|
|
*/
|
|
bool FNETestHooks::getOTARWarmStartTEK(TrafficNetwork& network, uint32_t rsi,
|
|
uint16_t& kid, std::vector<uint8_t>& key)
|
|
{
|
|
if (network.m_p25OTARService == nullptr)
|
|
return false;
|
|
std::lock_guard<std::mutex> lock(network.m_p25OTARService->m_warmStartMutex);
|
|
auto it = network.m_p25OTARService->m_warmStartTransactions.find(rsi);
|
|
if (it == network.m_p25OTARService->m_warmStartTransactions.end())
|
|
return false;
|
|
kid = it->second.temporaryKId;
|
|
key.assign(it->second.temporaryTEK.begin(), it->second.temporaryTEK.end());
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if there is an active OTAR Warm Start transaction for the given RSI.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param rsi The RSI to check.
|
|
* @return True if there is an active Warm Start transaction, false otherwise.
|
|
*/
|
|
bool FNETestHooks::hasOTARWarmStart(TrafficNetwork& network, uint32_t rsi)
|
|
{
|
|
if (network.m_p25OTARService == nullptr)
|
|
return false;
|
|
std::lock_guard<std::mutex> lock(network.m_p25OTARService->m_warmStartMutex);
|
|
return network.m_p25OTARService->m_warmStartTransactions.find(rsi) !=
|
|
network.m_p25OTARService->m_warmStartTransactions.end();
|
|
}
|
|
|
|
/**
|
|
* @brief Injects and resolves an upstream TEK response through the OTAR cache.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param key The KMM key item representing the TEK.
|
|
* @param algId The algorithm ID used for the TEK.
|
|
* @param keyLength The length of the TEK.
|
|
* @return True if the TEK was successfully cached and resolved, false otherwise.
|
|
*/
|
|
bool FNETestHooks::cacheOTARTEK(TrafficNetwork& network,
|
|
const p25::kmm::KeyItem& key, uint8_t algId, uint8_t keyLength)
|
|
{
|
|
if (network.m_p25OTARService == nullptr)
|
|
return false;
|
|
network.m_p25OTARService->cacheUpstreamTEK(key, algId, keyLength);
|
|
const uint32_t id = ((uint32_t)algId << 16U) | key.kId();
|
|
auto it = network.m_p25OTARService->m_upstreamTEKs.find(id);
|
|
return it != network.m_p25OTARService->m_upstreamTEKs.end() &&
|
|
it->second.algorithmId == algId && it->second.keyLength == keyLength;
|
|
}
|
|
|
|
/**
|
|
* @brief Injects and resolves an upstream UKEK response through the OTAR cache.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param rsi The RSI associated with the UKEK.
|
|
* @param key The KMM key item representing the UKEK.
|
|
* @param algId The algorithm ID used for the UKEK.
|
|
* @param keyLength The length of the UKEK.
|
|
* @return True if the UKEK was successfully cached and resolved, false otherwise.
|
|
*/
|
|
bool FNETestHooks::cacheOTARUKEK(TrafficNetwork& network, uint32_t rsi,
|
|
const p25::kmm::KeyItem& key, uint8_t algId, uint8_t keyLength)
|
|
{
|
|
if (network.m_p25OTARService == nullptr)
|
|
return false;
|
|
network.m_p25OTARService->cacheUpstreamUKEK(rsi, key, algId, keyLength);
|
|
auto it = network.m_p25OTARService->m_upstreamUKEKs.find(rsi);
|
|
return it != network.m_p25OTARService->m_upstreamUKEKs.end() &&
|
|
it->second.algorithmId == algId && it->second.keyLength == keyLength;
|
|
}
|
|
|
|
/**
|
|
* @brief Resolves the security parameters of an OTAR response packet.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param packet The OTAR response packet data.
|
|
* @param encrypted Output flag indicating if the packet is encrypted.
|
|
* @param algoId Output parameter for the algorithm ID used for encryption.
|
|
* @param kid Output parameter for the key ID used for encryption.
|
|
* @return True if the security parameters were successfully resolved, false otherwise.
|
|
*/
|
|
bool FNETestHooks::resolveOTARResponseSecurity(TrafficNetwork& network,
|
|
const std::vector<uint8_t>& packet, bool& encrypted, uint8_t& algoId, uint16_t& kid)
|
|
{
|
|
if (packet.empty() || network.m_p25OTARService == nullptr)
|
|
return false;
|
|
|
|
return network.m_p25OTARService->resolveResponseSecurity(packet.data(),
|
|
(uint32_t)packet.size(), encrypted, algoId, kid);
|
|
}
|
|
|
|
/**
|
|
* @brief Passes a DLD KMM through the public P25 OTAR bearer entry point.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param packet The DLD packet data.
|
|
* @param llId The logical link ID.
|
|
* @param n The sequence number.
|
|
* @param encrypted Whether the packet is encrypted.
|
|
* @param algoId The algorithm ID used for encryption.
|
|
* @param kid The key ID used for encryption.
|
|
* @param mi The message integrity value.
|
|
* @return True if the packet was successfully processed, false otherwise.
|
|
*/
|
|
bool FNETestHooks::processOTARDLD(TrafficNetwork& network, const std::vector<uint8_t>& packet,
|
|
uint32_t llId, uint8_t n, bool encrypted, uint8_t algoId, uint16_t kid, const uint8_t* mi)
|
|
{
|
|
if (packet.empty() || network.m_p25OTARService == nullptr)
|
|
return false;
|
|
|
|
return network.m_p25OTARService->processDLD(packet.data(), (uint32_t)packet.size(), llId, n,
|
|
encrypted, algoId, kid, mi);
|
|
}
|
|
|
|
/**
|
|
* @brief Encodes and injects a complete DLD KMM through the P25 PDU assembler path.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param packet The DLD packet data.
|
|
* @param llId The logical link ID.
|
|
* @param encrypted Whether the packet is encrypted.
|
|
* @param algoId The algorithm ID used for encryption.
|
|
* @param kid The key ID used for encryption.
|
|
* @param mi The message integrity value.
|
|
* @return True if the packet was successfully processed, false otherwise.
|
|
*/
|
|
bool FNETestHooks::processOTARDLDPDU(TrafficNetwork& network, const std::vector<uint8_t>& packet,
|
|
uint32_t llId, bool encrypted, uint8_t algoId, uint16_t kid, const uint8_t* mi)
|
|
{
|
|
if (packet.empty() || network.m_tagP25 == nullptr)
|
|
return false;
|
|
|
|
p25::data::DataHeader header;
|
|
header.setFormat(PDUFormatType::CONFIRMED);
|
|
header.setMFId(MFG_STANDARD);
|
|
header.setAckNeeded(true);
|
|
header.setOutbound(false);
|
|
header.setSAP(encrypted ? PDUSAP::ENC_USER_DATA : PDUSAP::UNENC_KMM);
|
|
header.setLLId(llId);
|
|
header.setFullMessage(true);
|
|
header.setBlocksToFollow(1U);
|
|
if (encrypted) {
|
|
if (mi == nullptr)
|
|
return false;
|
|
header.setEXSAP(PDUSAP::UNENC_KMM);
|
|
header.setAlgId(algoId);
|
|
header.setKId(kid);
|
|
header.setMI(mi);
|
|
}
|
|
header.calculateLength((uint32_t)packet.size());
|
|
|
|
p25::data::Assembler assembler;
|
|
uint32_t bitLength = 0U;
|
|
UInt8Array assembled = assembler.assemble(header, false, encrypted, packet.data(), &bitLength);
|
|
if (assembled == nullptr || bitLength <= P25_PREAMBLE_LENGTH_BITS)
|
|
return false;
|
|
|
|
const uint32_t blockCount = (bitLength - P25_PREAMBLE_LENGTH_BITS) / P25_PDU_FEC_LENGTH_BITS;
|
|
auto* packetData = network.m_tagP25->packetData();
|
|
for (uint32_t block = 0U; block < blockCount; ++block) {
|
|
uint8_t envelope[24U + P25_PDU_FEC_LENGTH_BYTES] = { 0U };
|
|
SET_UINT24(P25_PDU_FEC_LENGTH_BYTES, envelope, 8U);
|
|
envelope[20U] = (uint8_t)(blockCount - 1U);
|
|
envelope[21U] = (uint8_t)block;
|
|
Utils::getBitRange(assembled.get(), envelope + 24U,
|
|
P25_PREAMBLE_LENGTH_BITS + block * P25_PDU_FEC_LENGTH_BITS, P25_PDU_FEC_LENGTH_BITS);
|
|
if (!packetData->processFrame(envelope, sizeof(envelope), 1U, (uint16_t)block, 1U))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** Injects a one-block PDU response through the FNE packet-data receiver. */
|
|
|
|
bool FNETestHooks::processP25PDUResponse(TrafficNetwork& network, uint32_t llId,
|
|
uint8_t responseStatus)
|
|
{
|
|
if (network.m_tagP25 == nullptr)
|
|
return false;
|
|
|
|
p25::data::DataHeader header;
|
|
header.setFormat(PDUFormatType::RSP);
|
|
header.setMFId(MFG_STANDARD);
|
|
header.setOutbound(false);
|
|
header.setResponseClass(PDUAckClass::ACK);
|
|
header.setResponseType(PDUAckType::ACK);
|
|
header.setResponseStatus(responseStatus);
|
|
header.setLLId(llId);
|
|
header.setFullMessage(true);
|
|
header.setBlocksToFollow(0U);
|
|
|
|
uint8_t envelope[24U + P25_PDU_FEC_LENGTH_BYTES] = { 0U };
|
|
SET_UINT24(P25_PDU_FEC_LENGTH_BYTES, envelope, 8U);
|
|
envelope[20U] = 0U;
|
|
envelope[21U] = 0U;
|
|
header.encode(envelope + 24U);
|
|
|
|
return network.m_tagP25->packetData()->processFrame(envelope, sizeof(envelope),
|
|
1U, 0U, 1U);
|
|
}
|
|
|
|
/**
|
|
* @brief Passes a complete Version-0 DLI datagram through the network receive task.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param datagram The DLI packet data.
|
|
* @return True if the packet was successfully processed, false otherwise.
|
|
*/
|
|
void FNETestHooks::processOTARDLI(TrafficNetwork& network, const std::vector<uint8_t>& datagram)
|
|
{
|
|
if (datagram.empty() || network.m_p25OTARService == nullptr)
|
|
return;
|
|
|
|
OTARPacketRequest* req = new OTARPacketRequest();
|
|
req->obj = network.m_p25OTARService;
|
|
req->address = {};
|
|
req->addrLen = 0U;
|
|
req->length = (int)datagram.size();
|
|
req->buffer = new uint8_t[datagram.size()];
|
|
::memcpy(req->buffer, datagram.data(), datagram.size());
|
|
|
|
P25OTARService::taskNetworkRx(req);
|
|
}
|
|
|
|
/**
|
|
* @brief Opens the real DLI UDP endpoint for an integration test.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param address The IP address of the DLI UDP endpoint.
|
|
* @param port The port number of the DLI UDP endpoint.
|
|
* @return True if the endpoint was successfully opened, false otherwise.
|
|
*/
|
|
bool FNETestHooks::openOTARDLI(TrafficNetwork& network, const std::string& address, uint16_t port)
|
|
{
|
|
return network.m_p25OTARService != nullptr && network.m_p25OTARService->open(address, port);
|
|
}
|
|
|
|
/**
|
|
* @brief Polls the real DLI UDP endpoint.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param ms The number of milliseconds to wait.
|
|
*/
|
|
void FNETestHooks::clockOTARDLI(TrafficNetwork& network, uint32_t ms)
|
|
{
|
|
if (network.m_p25OTARService != nullptr)
|
|
network.m_p25OTARService->clock(ms);
|
|
}
|
|
|
|
/**
|
|
* @brief Closes the real DLI UDP endpoint.
|
|
* @param network The TrafficNetwork instance.
|
|
*/
|
|
void FNETestHooks::closeOTARDLI(TrafficNetwork& network)
|
|
{
|
|
if (network.m_p25OTARService != nullptr)
|
|
network.m_p25OTARService->close();
|
|
}
|
|
|
|
/**
|
|
* @brief Applies the OTAR outer encryption/decryption implementation.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param algoId The algorithm ID used for encryption/decryption.
|
|
* @param kid The key ID used for encryption/decryption.
|
|
* @param mi The message integrity value.
|
|
* @param packet The packet data to be encrypted/decrypted.
|
|
* @param encrypt True to encrypt, false to decrypt.
|
|
* @return The resulting encrypted/decrypted packet.
|
|
*/
|
|
std::vector<uint8_t> FNETestHooks::cryptOTARKMM(TrafficNetwork& network, uint8_t algoId,
|
|
uint16_t kid, uint8_t* mi, const std::vector<uint8_t>& packet, bool encrypt)
|
|
{
|
|
if (packet.empty() || network.m_p25OTARService == nullptr)
|
|
return {};
|
|
|
|
UInt8Array result = network.m_p25OTARService->cryptKMM(algoId, kid, mi,
|
|
packet.data(), (uint32_t)packet.size(), encrypt);
|
|
if (result == nullptr)
|
|
return {};
|
|
return std::vector<uint8_t>(result.get(), result.get() + packet.size());
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if the specified inbound message number exists for the given RSI.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param rsi The RSI to check.
|
|
* @param mn The message number to check.
|
|
* @return True if the inbound message number exists and matches, false otherwise.
|
|
*/
|
|
bool FNETestHooks::hasOTARInboundMessageNumber(TrafficNetwork& network, uint32_t rsi, uint16_t mn)
|
|
{
|
|
if (network.m_p25OTARService == nullptr)
|
|
return false;
|
|
|
|
auto it = network.m_p25OTARService->m_rsiInboundMessageNumber.find(rsi);
|
|
return it != network.m_p25OTARService->m_rsiInboundMessageNumber.end() && it->second == mn;
|
|
}
|
|
|
|
/**
|
|
* @brief Enables or disables KMF services for the specified TrafficNetwork.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param enabled True to enable KMF services, false to disable.
|
|
*/
|
|
void FNETestHooks::setKMFServicesEnabled(TrafficNetwork& network, bool enabled)
|
|
{
|
|
network.m_kmfServicesEnabled = enabled;
|
|
}
|
|
|
|
/**
|
|
* @brief Adds a cryptographic key to the network's crypto lookup.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param key The cryptographic key to add.
|
|
*/
|
|
void FNETestHooks::addCryptoKey(TrafficNetwork& network, const EKCKeyItem& key)
|
|
{
|
|
network.m_cryptoLookup->addEntry(key);
|
|
}
|
|
|
|
/**
|
|
* @brief Adds a UKEK to the test crypto container.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param key The UKEK key item to add.
|
|
*/
|
|
void FNETestHooks::addCryptoUKEK(TrafficNetwork& network, const EKCKeyItem& key)
|
|
{
|
|
network.m_cryptoLookup->addUKEK(key);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Static Class Members
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @brief Checks if the specified peer exists in the TrafficNetwork.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param peerId The ID of the peer to check.
|
|
* @return True if the peer exists, false otherwise.
|
|
*/
|
|
bool FNETestHooks::hasPeer(const TrafficNetwork& network, uint32_t peerId)
|
|
{
|
|
return network.findPeer(peerId) != nullptr;
|
|
}
|
|
|
|
/**
|
|
* @brief Retrieves the number of peers in the TrafficNetwork.
|
|
* @param network The TrafficNetwork instance.
|
|
* @return The number of peers.
|
|
*/
|
|
size_t FNETestHooks::peerCount(const TrafficNetwork& network)
|
|
{
|
|
return network.peerCount();
|
|
}
|
|
|
|
/**
|
|
* @brief Retrieves the connection state of the specified peer.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param peerId The ID of the peer.
|
|
* @return The connection state of the peer.
|
|
*/
|
|
NET_CONN_STATUS FNETestHooks::peerState(const TrafficNetwork& network, uint32_t peerId)
|
|
{
|
|
TrafficNetwork::PeerConnectionPtr peer = network.findPeer(peerId);
|
|
if (peer == nullptr)
|
|
throw std::out_of_range("peer does not exist");
|
|
return peer->connectionState();
|
|
}
|
|
|
|
/**
|
|
* @brief Retains a shared reference to the specified peer connection.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param peerId The ID of the peer to retain.
|
|
* @return Shared peer connection, or nullptr if the peer does not exist.
|
|
*/
|
|
std::shared_ptr<FNEPeerConnection> FNETestHooks::retainPeer(
|
|
const TrafficNetwork& network, uint32_t peerId)
|
|
{
|
|
return network.findPeer(peerId);
|
|
}
|
|
|
|
/**
|
|
* @brief Removes a peer from the specified TrafficNetwork.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param peerId The ID of the peer to remove.
|
|
*/
|
|
void FNETestHooks::removePeer(TrafficNetwork& network, uint32_t peerId)
|
|
{
|
|
network.removePeer(peerId);
|
|
}
|
|
|
|
/* Runs production peer teardown, including route and affiliation cleanup. */
|
|
|
|
void FNETestHooks::teardownPeer(TrafficNetwork& network, uint32_t peerId)
|
|
{
|
|
network.erasePeer(peerId);
|
|
}
|
|
|
|
/**
|
|
* @brief Clears all peers from the specified TrafficNetwork.
|
|
* @param network The TrafficNetwork instance.
|
|
*/
|
|
void FNETestHooks::clearPeers(TrafficNetwork& network)
|
|
{
|
|
for (const auto& peer : network.peersSnapshot())
|
|
network.removePeer(peer.first);
|
|
}
|
|
|
|
/**
|
|
* @brief Replaces a replicated active peer list for testing.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param peerId The replica peer ID.
|
|
* @param peers The active peer list to store.
|
|
*/
|
|
void FNETestHooks::updateReplicatedPeers(TrafficNetwork& network, uint32_t peerId, const json::array& peers)
|
|
{
|
|
network.updateReplicatedPeers(peerId, peers);
|
|
}
|
|
|
|
/**
|
|
* @brief Gets a snapshot of the replicated active peer lists.
|
|
* @param network The TrafficNetwork instance.
|
|
* @return Map of replica peer IDs to active peer lists.
|
|
*/
|
|
std::unordered_map<uint32_t, json::array> FNETestHooks::replicatedPeersSnapshot(const TrafficNetwork& network)
|
|
{
|
|
return network.replicatedPeersSnapshot();
|
|
}
|
|
|
|
/**
|
|
* @brief Removes a replicated active peer list for testing.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param peerId The replica peer ID.
|
|
*/
|
|
void FNETestHooks::eraseReplicatedPeers(TrafficNetwork& network, uint32_t peerId)
|
|
{
|
|
network.eraseReplicatedPeers(peerId);
|
|
}
|
|
|
|
/**
|
|
* @brief Creates a NetPacketRequest for the specified network, metadata, function, subFunction, peerId, and packet.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param metadata The MetadataNetwork instance (can be nullptr).
|
|
* @param function The network function.
|
|
* @param subFunction The network sub-function.
|
|
* @param peerId The ID of the peer.
|
|
* @param packet The packet data.
|
|
* @return Pointer to the created NetPacketRequest.
|
|
*/
|
|
static NetPacketRequest* makeRequest(TrafficNetwork& network, MetadataNetwork* metadata,
|
|
NET_FUNC::ENUM function, NET_SUBFUNC::ENUM subFunction, uint32_t peerId,
|
|
const std::vector<uint8_t>& packet)
|
|
{
|
|
NetPacketRequest* request = new NetPacketRequest();
|
|
request->obj = &network;
|
|
request->metadataObj = metadata;
|
|
request->peerId = peerId;
|
|
request->fneHeader.setFunction(function);
|
|
request->fneHeader.setSubFunction(subFunction);
|
|
request->fneHeader.setPeerId(peerId);
|
|
request->fneHeader.setStreamId(1U);
|
|
request->rtpHeader.setSSRC(peerId);
|
|
request->pktRxTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
request->length = static_cast<int>(packet.size());
|
|
if (!packet.empty()) {
|
|
request->buffer = new uint8_t[packet.size()];
|
|
::memcpy(request->buffer, packet.data(), packet.size());
|
|
}
|
|
return request;
|
|
}
|
|
|
|
/**
|
|
* @brief Dispatches a traffic packet to the specified TrafficNetwork.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param function The network function.
|
|
* @param subFunction The network sub-function.
|
|
* @param peerId The ID of the peer.
|
|
* @param packet The packet data.
|
|
*/
|
|
void FNETestHooks::dispatchTraffic(TrafficNetwork& network, NET_FUNC::ENUM function,
|
|
NET_SUBFUNC::ENUM subFunction, uint32_t peerId, const std::vector<uint8_t>& packet)
|
|
{
|
|
TrafficNetwork::taskNetworkRx(makeRequest(network, nullptr, function, subFunction, peerId, packet));
|
|
}
|
|
|
|
/**
|
|
* @brief Dispatches a metadata packet to the specified TrafficNetwork.
|
|
* @param network The TrafficNetwork instance.
|
|
* @param metadata The MetadataNetwork instance.
|
|
* @param function The network function.
|
|
* @param subFunction The network sub-function.
|
|
* @param peerId The ID of the peer.
|
|
* @param packet The packet data.
|
|
*/
|
|
void FNETestHooks::dispatchMetadata(TrafficNetwork& network, MetadataNetwork& metadata,
|
|
NET_FUNC::ENUM function, NET_SUBFUNC::ENUM subFunction, uint32_t peerId,
|
|
const std::vector<uint8_t>& packet)
|
|
{
|
|
MetadataNetwork::taskNetworkRx(makeRequest(network, &metadata, function, subFunction, peerId, packet));
|
|
}
|
|
|
|
/* Starts the shared traffic worker pool for ordered-task tests. */
|
|
|
|
void FNETestHooks::startTrafficWorkerPool(TrafficNetwork& network)
|
|
{
|
|
network.m_threadPool.start();
|
|
}
|
|
|
|
/* Stops and joins the shared traffic worker pool after an ordered-task test. */
|
|
|
|
void FNETestHooks::stopTrafficWorkerPool(TrafficNetwork& network)
|
|
{
|
|
network.m_threadPool.stop();
|
|
network.m_threadPool.wait();
|
|
}
|
|
|
|
/* Installs a callback invoked for ordered protocol frames instead of decoding them. */
|
|
|
|
void FNETestHooks::setOrderedFrameHandler(TrafficNetwork& network, ParrotProtocol protocol,
|
|
std::function<void(const uint8_t*, uint32_t, uint32_t, uint32_t, uint16_t, uint32_t, bool)> handler)
|
|
{
|
|
switch (protocol) {
|
|
case ParrotProtocol::DMR: network.m_tagDMR->m_orderedFrames.setTestFrameHandler(std::move(handler)); break;
|
|
case ParrotProtocol::P25: network.m_tagP25->m_orderedFrames.setTestFrameHandler(std::move(handler)); break;
|
|
case ParrotProtocol::P25P2: network.m_tagP25P2->m_orderedFrames.setTestFrameHandler(std::move(handler)); break;
|
|
case ParrotProtocol::NXDN: network.m_tagNXDN->m_orderedFrames.setTestFrameHandler(std::move(handler)); break;
|
|
case ParrotProtocol::ANALOG: break;
|
|
}
|
|
}
|
|
|
|
/* Clears an ordered protocol frame callback. */
|
|
|
|
void FNETestHooks::clearOrderedFrameHandler(TrafficNetwork& network, ParrotProtocol protocol)
|
|
{
|
|
setOrderedFrameHandler(network, protocol, nullptr);
|
|
}
|
|
|
|
/* Enqueues a synthetic ordered protocol frame. */
|
|
|
|
bool FNETestHooks::enqueueOrderedFrame(TrafficNetwork& network, ParrotProtocol protocol,
|
|
uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint16_t pktSeq, bool fromUpstream)
|
|
{
|
|
const uint8_t frame[1U] = { 0U };
|
|
switch (protocol) {
|
|
case ParrotProtocol::DMR: return network.m_tagDMR->enqueueFrame(frame, sizeof(frame), peerId, ssrc, pktSeq, streamId, fromUpstream);
|
|
case ParrotProtocol::P25: return network.m_tagP25->enqueueFrame(frame, sizeof(frame), peerId, ssrc, pktSeq, streamId, fromUpstream);
|
|
case ParrotProtocol::P25P2: return network.m_tagP25P2->enqueueFrame(frame, sizeof(frame), peerId, ssrc, pktSeq, streamId, fromUpstream);
|
|
case ParrotProtocol::NXDN: return network.m_tagNXDN->enqueueFrame(frame, sizeof(frame), peerId, ssrc, pktSeq, streamId, fromUpstream);
|
|
case ParrotProtocol::ANALOG: return false;
|
|
}
|
|
|
|
return false;
|
|
}
|