extend subscriber operation tracking for inbound DMR/P25 and P25 KMM requests, and expose recent operation history over REST and metadata;

r05a06_dev
Bryan Biedenkapp 1 day ago
parent 9d774c0d9b
commit 3577544578

@ -13,6 +13,7 @@
#include "common/Thread.h"
#include "common/Utils.h"
#include "network/TrafficNetwork.h"
#include "network/SubscriberOperationStore.h"
#include "network/P25OTARService.h"
#include "HostFNE.h"
@ -1043,6 +1044,8 @@ UInt8Array P25OTARService::processKMM(const uint8_t* data, uint32_t len, uint32_
switch (frame->getMessageId()) {
case KMM_MessageType::CHANGEOVER_RSP:
{
m_network->subscriberOperations().complete(SubscriberOperationStore::Protocol::P25, SubscriberOperationStore::Kind::KMM_CHANGEOVER, llId, 0U);
KMMChangeover* kmm = static_cast<KMMChangeover*>(frame.get());
LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, supersededKeysetId = $%02X, activeKeysetId = $%02X", kmm->toString().c_str(),
llId, kmm->getSupersededKeysetId(), kmm->getActiveKeysetId());
@ -1113,6 +1116,7 @@ UInt8Array P25OTARService::processKMM(const uint8_t* data, uint32_t len, uint32_
LogInfoEx(LOG_P25, P25_KMM_STR ", %s, no UKEK found for rekey request, llId = %u", kmm->toString().c_str(), llId);
return write_KMM_NoService(llId, kmm->getSrcLLId(), payloadSize, auth);
} else {
m_network->subscriberOperations().start(SubscriberOperationStore::Protocol::P25, SubscriberOperationStore::Kind::KMM_REKEY, llId, 0U);
if (m_network->m_kmfWarmStart) {
// an authenticated Hello proves a common TEK already exists -- otherwise establish
// a temporary security context before sending the Rekey Command
@ -1202,6 +1206,7 @@ UInt8Array P25OTARService::processKMM(const uint8_t* data, uint32_t len, uint32_
m_rsiInboundMessageNumber[kmm->getSrcLLId()] = kmm->KMMFrame::getMessageNumber();
LogInfoEx(LOG_P25, P25_KMM_STR ", Change-RSI MN resynchronization completed, RSI = %u, initialMN = %u", kmm->getSrcLLId(), initialMN);
m_network->subscriberOperations().complete(SubscriberOperationStore::Protocol::P25, SubscriberOperationStore::Kind::KMM_CHANGERSI, llId, 0U);
return write_KMM_Rekey_Command(llId, kmm->getSrcLLId(),
KMM_HelloFlag::REKEY_REQUEST_UKEK, payloadSize, auth,
@ -1312,6 +1317,8 @@ UInt8Array P25OTARService::processKMM(const uint8_t* data, uint32_t len, uint32_
// if the rekey process is complete, issue a changeover command
if (rekeyComplete && m_network->m_kmfChangeoverAfterRekey) {
m_network->subscriberOperations().complete(SubscriberOperationStore::Protocol::P25, SubscriberOperationStore::Kind::KMM_REKEY, llId, 0U);
// bryanb: should these be hardcoded? as this causes whatever SU on a DVM system to swap to keyset 1
static constexpr uint8_t SUPERSEDED_KEYSET_ID = 1U;
static constexpr uint8_t ACTIVE_KEYSET_ID = 1U;
@ -2164,6 +2171,8 @@ UInt8Array P25OTARService::write_KMM_ChangeRSI_Command(uint32_t llId, uint32_t k
it->second.deadline = monotonicMilliseconds() + WARM_START_TIMEOUT_MS;
}
m_network->subscriberOperations().start(SubscriberOperationStore::Protocol::P25, SubscriberOperationStore::Kind::KMM_CHANGERSI, llId, 0U);
if (payloadSize != nullptr)
*payloadSize = frameLength;
@ -2216,6 +2225,8 @@ UInt8Array P25OTARService::write_KMM_Changeover_Command(uint32_t llId, uint32_t
return nullptr;
}
m_network->subscriberOperations().start(SubscriberOperationStore::Protocol::P25, SubscriberOperationStore::Kind::KMM_CHANGEOVER, llId, 0U);
const uint16_t mn = reserveOutboundMessageNumbers(llId);
// build the KMM Changeover command using the determined MAC TEK and message number

@ -9,6 +9,8 @@
*/
#include "fne/network/SubscriberOperationStore.h"
#include <algorithm>
using namespace network;
// ---------------------------------------------------------------------------
@ -129,6 +131,32 @@ bool SubscriberOperationStore::get(uint64_t id, Operation& operation)
return true;
}
/* Retrieves the most recently started operations. */
std::vector<SubscriberOperationStore::Operation> SubscriberOperationStore::getLast(uint32_t count)
{
const auto now = std::chrono::steady_clock::now();
std::lock_guard<std::mutex> lock(m_mutex);
// expire records before taking the history snapshot
expireLocked(now);
std::vector<Operation> operations;
operations.reserve(std::min<size_t>(count, m_operations.size()));
for (const auto& entry : m_operations) {
operations.push_back(entry.second.value);
}
std::sort(operations.begin(), operations.end(), [](const Operation& lhs, const Operation& rhs) {
return lhs.id > rhs.id;
});
if (operations.size() > count) {
operations.resize(count);
}
return operations;
}
/* Retrieves the latest P25 status snapshot. */
bool SubscriberOperationStore::getP25Status(uint32_t subscriberId, Status& status)
@ -182,6 +210,12 @@ const char* SubscriberOperationStore::kindName(Kind kind)
return "inhibit";
case Kind::UNINHIBIT:
return "uninhibit";
case Kind::KMM_REKEY:
return "kmm-rekey";
case Kind::KMM_CHANGEOVER:
return "kmm-changeover";
case Kind::KMM_CHANGERSI:
return "kmm-changersi";
}
return "unknown";

@ -22,6 +22,7 @@
#include <cstdint>
#include <mutex>
#include <unordered_map>
#include <vector>
namespace network
{
@ -40,8 +41,8 @@ namespace network
* @ingroup fne_network
*/
enum class Protocol : uint8_t {
DMR = 0U, //!< Digital Mobile Radio.
P25 = 1U //!< Project 25.
DMR = 0U, //!< Digital Mobile Radio.
P25 = 1U //!< Project 25.
};
/**
@ -49,11 +50,15 @@ namespace network
* @ingroup fne_network
*/
enum class Kind : uint8_t {
RADIO_CHECK = 0U, //!< Subscriber presence check.
STATUS_QUERY = 1U, //!< Subscriber status query.
MESSAGE_UPDATE = 2U,//!< Subscriber coded-message update.
INHIBIT = 3U, //!< Subscriber inhibit request.
UNINHIBIT = 4U //!< Subscriber uninhibit request.
RADIO_CHECK = 0U, //!< Subscriber presence check.
STATUS_QUERY = 1U, //!< Subscriber status query.
MESSAGE_UPDATE = 2U, //!< Subscriber coded-message update.
INHIBIT = 3U, //!< Subscriber inhibit request.
UNINHIBIT = 4U, //!< Subscriber uninhibit request.
KMM_REKEY = 5U, //!< Key management rekey request.
KMM_CHANGEOVER = 6U, //!< Key management changeover request.
KMM_CHANGERSI = 7U //!< Key management change RSI request.
};
/**
@ -61,10 +66,10 @@ namespace network
* @ingroup fne_network
*/
enum class State : uint8_t {
PENDING = 0U, //!< Request has been transmitted and awaits a response.
SUCCEEDED = 1U, //!< A matching successful response was received.
REJECTED = 2U, //!< A matching negative response was received.
TIMED_OUT = 3U //!< No matching response arrived before the deadline.
PENDING = 0U, //!< Request has been transmitted and awaits a response.
SUCCEEDED = 1U, //!< A matching successful response was received.
REJECTED = 2U, //!< A matching negative response was received.
TIMED_OUT = 3U //!< No matching response arrived before the deadline.
};
/**
@ -72,16 +77,16 @@ namespace network
* @ingroup fne_network
*/
struct Operation {
uint64_t id; //!< FNE-local operation identifier.
Protocol protocol; //!< Air-interface protocol.
Kind kind; //!< Requested operation.
State state; //!< Current lifecycle state.
uint32_t targetId; //!< Target subscriber ID.
uint32_t peerId; //!< Requested or responding peer ID.
uint16_t requestValue; //!< Optional requested status/message value.
uint16_t resultValue; //!< Optional returned status value.
uint64_t createdAt; //!< Creation time in Unix milliseconds.
uint64_t completedAt; //!< Completion time in Unix milliseconds, or zero.
uint64_t id; //!< FNE-local operation identifier.
Protocol protocol; //!< Air-interface protocol.
Kind kind; //!< Requested operation.
State state; //!< Current lifecycle state.
uint32_t targetId; //!< Target subscriber ID.
uint32_t peerId; //!< Requested or responding peer ID.
uint16_t requestValue; //!< Optional requested status/message value.
uint16_t resultValue; //!< Optional returned status value.
uint64_t createdAt; //!< Creation time in Unix milliseconds.
uint64_t completedAt; //!< Completion time in Unix milliseconds, or zero.
};
/**
@ -89,11 +94,11 @@ namespace network
* @ingroup fne_network
*/
struct Status {
uint32_t subscriberId; //!< Reporting subscriber ID.
uint32_t peerId; //!< Peer that delivered the report.
uint16_t value; //!< Status value.
bool solicited; //!< True when matched to an active query.
uint64_t observedAt; //!< Observation time in Unix milliseconds.
uint32_t subscriberId; //!< Reporting subscriber ID.
uint32_t peerId; //!< Peer that delivered the report.
uint16_t value; //!< Status value.
bool solicited; //!< True when matched to an active query.
uint64_t observedAt; //!< Observation time in Unix milliseconds.
};
/**
@ -141,6 +146,12 @@ namespace network
* @returns bool True if the operation exists, otherwise false.
*/
bool get(uint64_t id, Operation& operation);
/**
* @brief Retrieves the most recently started operations.
* @param[in] count Maximum number of operations to retrieve.
* @returns std::vector<Operation> Operation snapshots ordered newest first.
*/
std::vector<Operation> getLast(uint32_t count);
/**
* @brief Retrieves the latest P25 status snapshot.
* @param[in] subscriberId Subscriber ID.

@ -1129,6 +1129,18 @@ bool TagDMRData::processCSBK(uint8_t* buffer, uint32_t peerId, dmr::data::NetDat
{
lc::csbk::CSBK_EXT_FNCT* iosp = static_cast<lc::csbk::CSBK_EXT_FNCT*>(csbk.get());
switch (iosp->getExtendedFunction()) {
case ExtendedFunctions::CHECK:
m_network->subscriberOperations().start(SubscriberOperationStore::Protocol::DMR,
SubscriberOperationStore::Kind::RADIO_CHECK, iosp->getDstId(), peerId);
return false;
case ExtendedFunctions::INHIBIT:
m_network->subscriberOperations().start(SubscriberOperationStore::Protocol::DMR,
SubscriberOperationStore::Kind::INHIBIT, iosp->getDstId(), peerId);
return false;
case ExtendedFunctions::UNINHIBIT:
m_network->subscriberOperations().start(SubscriberOperationStore::Protocol::DMR,
SubscriberOperationStore::Kind::UNINHIBIT, iosp->getDstId(), peerId);
return false;
case ExtendedFunctions::CHECK_ACK:
m_network->subscriberOperations().complete(SubscriberOperationStore::Protocol::DMR,
SubscriberOperationStore::Kind::RADIO_CHECK, iosp->getDstId(), peerId);

@ -1453,6 +1453,18 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid)
lc::tsbk::IOSP_EXT_FNCT* iosp = static_cast<lc::tsbk::IOSP_EXT_FNCT*>(tsbk.get());
switch (iosp->getExtendedFunction()) {
// Standard
case ExtendedFunctions::CHECK:
m_network->subscriberOperations().start(SubscriberOperationStore::Protocol::P25,
SubscriberOperationStore::Kind::RADIO_CHECK, iosp->getDstId(), peerId);
return false;
case ExtendedFunctions::INHIBIT:
m_network->subscriberOperations().start(SubscriberOperationStore::Protocol::P25,
SubscriberOperationStore::Kind::INHIBIT, iosp->getDstId(), peerId);
return false;
case ExtendedFunctions::UNINHIBIT:
m_network->subscriberOperations().start(SubscriberOperationStore::Protocol::P25,
SubscriberOperationStore::Kind::UNINHIBIT, iosp->getDstId(), peerId);
return false;
case ExtendedFunctions::CHECK_ACK:
m_network->subscriberOperations().complete(SubscriberOperationStore::Protocol::P25,
SubscriberOperationStore::Kind::RADIO_CHECK, iosp->getDstId(), peerId);

@ -23,6 +23,10 @@ using namespace network;
// Constants
// ---------------------------------------------------------------------------
#define SOP_DMR "dmr"
#define SOP_P25 "p25"
#define SOP_NXDN "nxdn"
const uint32_t SUBSCRIBER_OPERATION_HEADER_LENGTH = 11U;
// ---------------------------------------------------------------------------
@ -52,6 +56,8 @@ void MetadataNetwork::PacketHandler::subscriberOperation(TrafficNetwork* network
const bool notFound = false;
const bool found = true;
const NET_SUBFUNC::ENUM subFunction = req->fneHeader.getSubFunction();
// helper function to write the response back to the peer
const auto writeResponse = [network, mdNetwork, connection, subFunction, streamId](json::object responseObj) {
json::value responseValue(responseObj);
const std::string responsePayload = responseValue.serialize();
@ -119,7 +125,7 @@ void MetadataNetwork::PacketHandler::subscriberOperation(TrafficNetwork* network
SubscriberOperationStore::Protocol protocol;
SubscriberOperationStore::Kind kind;
uint16_t requestValue = 0U;
if (protocolName == "p25") {
if (protocolName == SOP_P25) {
protocol = SubscriberOperationStore::Protocol::P25;
// determine the kind of P25 operation requested
@ -144,7 +150,7 @@ void MetadataNetwork::PacketHandler::subscriberOperation(TrafficNetwork* network
response["error"].set<std::string>("unsupported P25 operation");
break;
}
} else if (protocolName == "dmr") {
} else if (protocolName == SOP_DMR) {
protocol = SubscriberOperationStore::Protocol::DMR;
if (!request["slot"].is<uint8_t>()) {
response["found"].set<bool>(notFound);
@ -238,28 +244,61 @@ void MetadataNetwork::PacketHandler::subscriberOperation(TrafficNetwork* network
case NET_SUBFUNC::SUBSCRIBER_OP_OPERATION:
{
response["type"].set<std::string>("operation");
if (!request["operationId"].is<uint64_t>()) {
// check if the request contains an operation ID and validate it
const bool hasOperationId = request.find("operationId") != request.end();
if (hasOperationId && !request["operationId"].is<uint64_t>()) {
response["found"].set<bool>(notFound);
response["error"].set<std::string>("invalid operation ID");
break;
}
const uint64_t operationId = request["operationId"].get<uint64_t>();
SubscriberOperationStore::Operation operation;
const bool found = operationId != 0U && network->subscriberOperations().get(operationId, operation);
response["found"].set<bool>(found);
response["operationId"].set<uint64_t>(operationId);
if (found) {
response["protocol"].set<std::string>(SubscriberOperationStore::protocolName(operation.protocol));
response["operation"].set<std::string>(SubscriberOperationStore::kindName(operation.kind));
response["state"].set<std::string>(SubscriberOperationStore::stateName(operation.state));
response["targetId"].set<uint32_t>(operation.targetId);
response["peerId"].set<uint32_t>(operation.peerId);
response["requestValue"].set<uint16_t>(operation.requestValue);
response["resultValue"].set<uint16_t>(operation.resultValue);
response["createdAt"].set<uint64_t>(operation.createdAt);
response["completedAt"].set<uint64_t>(operation.completedAt);
// if an operation ID was provided, attempt to retrieve the corresponding operation from the store
if (hasOperationId) {
const uint64_t operationId = request["operationId"].get<uint64_t>();
SubscriberOperationStore::Operation operation;
const bool found = operationId != 0U && network->subscriberOperations().get(operationId, operation);
response["found"].set<bool>(found);
response["operationId"].set<uint64_t>(operationId);
if (found) {
response["protocol"].set<std::string>(SubscriberOperationStore::protocolName(operation.protocol));
response["operation"].set<std::string>(SubscriberOperationStore::kindName(operation.kind));
response["state"].set<std::string>(SubscriberOperationStore::stateName(operation.state));
response["targetId"].set<uint32_t>(operation.targetId);
response["peerId"].set<uint32_t>(operation.peerId);
response["requestValue"].set<uint16_t>(operation.requestValue);
response["resultValue"].set<uint16_t>(operation.resultValue);
response["createdAt"].set<uint64_t>(operation.createdAt);
response["completedAt"].set<uint64_t>(operation.completedAt);
}
}
// retrieve the last 5 subscriber operations from the store and construct the JSON array
json::array operationHistory = json::array();
const std::vector<SubscriberOperationStore::Operation> operations = network->subscriberOperations().getLast(5U);
for (const SubscriberOperationStore::Operation& historyOperation : operations) {
json::object operationObj = json::object();
operationObj["operationId"].set<uint64_t>(historyOperation.id);
operationObj["protocol"].set<std::string>(SubscriberOperationStore::protocolName(historyOperation.protocol));
operationObj["operation"].set<std::string>(SubscriberOperationStore::kindName(historyOperation.kind));
operationObj["state"].set<std::string>(SubscriberOperationStore::stateName(historyOperation.state));
operationObj["targetId"].set<uint32_t>(historyOperation.targetId);
operationObj["peerId"].set<uint32_t>(historyOperation.peerId);
operationObj["requestValue"].set<uint16_t>(historyOperation.requestValue);
operationObj["resultValue"].set<uint16_t>(historyOperation.resultValue);
operationObj["createdAt"].set<uint64_t>(historyOperation.createdAt);
operationObj["completedAt"].set<uint64_t>(historyOperation.completedAt);
operationHistory.push_back(json::value(operationObj));
}
// if no specific operation ID was requested, indicate whether any history was found
if (!hasOperationId) {
const bool historyFound = !operations.empty();
response["found"].set<bool>(historyFound);
}
response["operations"].set<json::array>(operationHistory);
}
break;
@ -272,12 +311,13 @@ void MetadataNetwork::PacketHandler::subscriberOperation(TrafficNetwork* network
break;
}
// retrieve the subscriber ID from the request and fetch the corresponding P25 status from the store
const uint32_t subscriberId = request["subscriberId"].get<uint32_t>();
SubscriberOperationStore::Status status;
const bool found = subscriberId != 0U &&
network->subscriberOperations().getP25Status(subscriberId, status);
const bool found = subscriberId != 0U && network->subscriberOperations().getP25Status(subscriberId, status);
response["found"].set<bool>(found);
response["protocol"].set<std::string>("p25");
response["protocol"].set<std::string>(SOP_P25);
response["subscriberId"].set<uint32_t>(subscriberId);
if (found) {
response["peerId"].set<uint32_t>(status.peerId);

@ -724,6 +724,7 @@ void RESTAPI::initializeEndpoints()
m_dispatcher.match(FNE_GET_GRANT_LIST).get(REST_API_BIND(RESTAPI::restAPI_GetGrantList, this));
m_dispatcher.match(FNE_GET_SPANNING_TREE).get(REST_API_BIND(RESTAPI::restAPI_GetSpanningTree, this));
m_dispatcher.match(FNE_GET_RID_OPERATION_HISTORY, true).get(REST_API_BIND(RESTAPI::restAPI_GetRIDOperationHistory, this));
m_dispatcher.match(FNE_GET_RID_OPERATION, true).get(REST_API_BIND(RESTAPI::restAPI_GetRIDOperation, this));
/*
@ -3091,6 +3092,44 @@ void RESTAPI::restAPI_GetRIDOperation(const HTTPPayload& request, HTTPPayload& r
response["resultValue"].set<uint16_t>(operation.resultValue);
response["createdAt"].set<uint64_t>(operation.createdAt);
response["completedAt"].set<uint64_t>(operation.completedAt);
reply.payload(response);
}
/* REST API endpoint; retrieves recent subscriber operations. */
void RESTAPI::restAPI_GetRIDOperationHistory(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{
if (!validateAuth(request, reply)) {
return;
}
// retrieve the requested count of recent operations from the URL and limit it to 64 if necessary
const uint64_t requestedCount = ::strtoull(match[1U].str().c_str(), nullptr, 10);
const uint32_t count = requestedCount > 64U ? 64U : static_cast<uint32_t>(requestedCount);
const std::vector<SubscriberOperationStore::Operation> operations = m_network->subscriberOperations().getLast(count);
// construct the JSON array of recent operations
json::array operationHistory = json::array();
for (const SubscriberOperationStore::Operation& operation : operations) {
json::object operationObj = json::object();
operationObj["operationId"].set<uint64_t>(operation.id);
operationObj["protocol"].set<std::string>(SubscriberOperationStore::protocolName(operation.protocol));
operationObj["operation"].set<std::string>(SubscriberOperationStore::kindName(operation.kind));
operationObj["state"].set<std::string>(SubscriberOperationStore::stateName(operation.state));
operationObj["targetId"].set<uint32_t>(operation.targetId);
operationObj["peerId"].set<uint32_t>(operation.peerId);
operationObj["requestValue"].set<uint16_t>(operation.requestValue);
operationObj["resultValue"].set<uint16_t>(operation.resultValue);
operationObj["createdAt"].set<uint64_t>(operation.createdAt);
operationObj["completedAt"].set<uint64_t>(operation.completedAt);
operationHistory.push_back(json::value(operationObj));
}
// construct the JSON response with the operation history array
json::object response = json::object();
setResponseDefaultStatus(response);
response["operations"].set<json::array>(operationHistory);
reply.payload(response);
}

@ -511,6 +511,14 @@ namespace fne_restapi
*/
void restAPI_GetRIDOperation(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match);
/**
* @brief REST API endpoint; retrieves recent subscriber operations.
* @param[in] request HTTP request.
* @param[out] reply HTTP reply.
* @param[in] match HTTP request matcher.
*/
void restAPI_GetRIDOperationHistory(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match);
/*
** Digital Mobile Radio
*/

@ -40,6 +40,7 @@
#define FNE_GET_RID_ALIAS_COMMIT "/rid/alias/commit"
#define FNE_GET_RID_OPERATION_BASE "/rid/operation/"
#define FNE_GET_RID_OPERATION "^/rid/operation/([0-9]+)$"
#define FNE_GET_RID_OPERATION_HISTORY "^/rid/operation/last/([0-9]+)$"
#define FNE_GET_P25_RID_STATUS_BASE "/p25/rid/status/"
#define FNE_GET_P25_RID_STATUS "^/p25/rid/status/([0-9]+)$"

@ -120,6 +120,33 @@ TEST_CASE("FNE metadata returns subscriber operation and P25 status results",
CHECK(operationResponse["operationId"].get<uint64_t>() == operationId);
CHECK(operationResponse["state"].get<std::string>() == "succeeded");
CHECK(operationResponse["resultValue"].get<uint16_t>() == 0x1234U);
REQUIRE(operationResponse["operations"].is<json::array>());
json::array& operations = operationResponse["operations"].get<json::array>();
REQUIRE(operations.size() == 1U);
REQUIRE(operations[0U].is<json::object>());
CHECK(operations[0U].get<json::object>()["operationId"].get<uint64_t>() == operationId);
json::object historyRequest = json::object();
const std::string historyJson = json::value(historyRequest).serialize();
requestPayload.assign(11U + historyJson.length(), 0U);
::memcpy(requestPayload.data() + 11U, historyJson.data(), historyJson.length());
responsePayload.clear();
FNETestHooks::dispatchMetadata(harness.traffic, harness.metadata,
network::NET_FUNC::SUBSCRIBER_OP, network::NET_SUBFUNC::SUBSCRIBER_OP_OPERATION,
CONSOLE_PEER_ID, requestPayload);
REQUIRE(responsePayload.size() > 11U);
json::value historyResponseValue;
REQUIRE(json::parse(historyResponseValue,
std::string(reinterpret_cast<const char*>(responsePayload.data() + 11U),
responsePayload.size() - 11U))
.empty());
REQUIRE(historyResponseValue.is<json::object>());
json::object& historyResponse = historyResponseValue.get<json::object>();
CHECK(historyResponse["found"].get<bool>());
REQUIRE(historyResponse["operations"].is<json::array>());
REQUIRE(historyResponse["operations"].get<json::array>().size() == 1U);
json::object statusRequest = json::object();
statusRequest["subscriberId"].set<uint32_t>(SUBSCRIBER_ID);

@ -11,6 +11,8 @@
#include <catch2/catch_test_macros.hpp>
#include <vector>
using namespace network;
TEST_CASE("FNE subscriber operations correlate protocol results", "[fne][subscriber-operations]")
@ -59,3 +61,24 @@ TEST_CASE("FNE subscriber operations isolate protocols and kinds", "[fne][subscr
CHECK(dmrOperation.peerId == 12U);
CHECK(p25Operation.state == SubscriberOperationStore::State::PENDING);
}
TEST_CASE("FNE subscriber operations return recent history newest first", "[fne][subscriber-operations]")
{
SubscriberOperationStore store;
const uint64_t firstId = store.start(SubscriberOperationStore::Protocol::DMR,
SubscriberOperationStore::Kind::RADIO_CHECK, 1001U, 0U);
const uint64_t secondId = store.start(SubscriberOperationStore::Protocol::P25,
SubscriberOperationStore::Kind::INHIBIT, 1002U, 0U);
const uint64_t thirdId = store.start(SubscriberOperationStore::Protocol::P25,
SubscriberOperationStore::Kind::UNINHIBIT, 1003U, 0U);
REQUIRE(firstId != 0U);
REQUIRE(secondId != 0U);
REQUIRE(thirdId != 0U);
const std::vector<SubscriberOperationStore::Operation> operations = store.getLast(2U);
REQUIRE(operations.size() == 2U);
CHECK(operations[0U].id == thirdId);
CHECK(operations[1U].id == secondId);
CHECK(store.getLast(0U).empty());
}

Loading…
Cancel
Save

Powered by TurnKey Linux.