diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 4fc2f286..b2614b6f 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -88,6 +88,8 @@ master: influxOrg: "dvm" # Data Bucket Name on InfluxDB instance API. influxBucket: "dvm" + # Flag indicating whether TSBK/CSBK/RCCH messages will be logged to InfluxDB. + influxLogRawData: false # # Talkgroup Rules Configuration diff --git a/src/common/dmr/lc/CSBK.cpp b/src/common/dmr/lc/CSBK.cpp index 50f68948..67655709 100644 --- a/src/common/dmr/lc/CSBK.cpp +++ b/src/common/dmr/lc/CSBK.cpp @@ -72,7 +72,8 @@ CSBK::CSBK() : m_logicalCh1(DMR_CHNULL), m_logicalCh2(DMR_CHNULL), m_slotNo(0U), - m_siteIdenEntry(::lookups::IdenTable()) + m_siteIdenEntry(::lookups::IdenTable()), + m_raw(nullptr) { /* stub */ } @@ -80,7 +81,11 @@ CSBK::CSBK() : /// /// Finalizes a instance of the CSBK class. /// -CSBK::~CSBK() = default; +CSBK::~CSBK() +{ + if (m_raw != nullptr) + delete[] m_raw; +} /// /// Returns a string that represents the current CSBK. @@ -91,6 +96,16 @@ std::string CSBK::toString() return std::string("CSBKO_UNKWN (Unknown CSBK)"); } +/// +/// Returns a copy of the raw decoded CSBK bytes. +/// +/// This will only return data for a *decoded* CSBK, not a created or copied CSBK. +/// +uint8_t* CSBK::getDecodedRaw() const +{ + return m_raw; +} + /// /// Regenerate a DMR CSBK without decoding. /// @@ -268,6 +283,9 @@ bool CSBK::decode(const uint8_t* data, uint8_t* payload) Utils::dump(2U, "Decoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); } + m_raw = new uint8_t[DMR_CSBK_LENGTH_BYTES]; + ::memcpy(m_raw, csbk, DMR_CSBK_LENGTH_BYTES); + m_CSBKO = csbk[0U] & 0x3FU; // CSBKO m_lastBlock = (csbk[0U] & 0x80U) == 0x80U; // Last Block Marker m_FID = csbk[1U]; // Feature ID diff --git a/src/common/dmr/lc/CSBK.h b/src/common/dmr/lc/CSBK.h index 494864cd..6169b177 100644 --- a/src/common/dmr/lc/CSBK.h +++ b/src/common/dmr/lc/CSBK.h @@ -46,6 +46,10 @@ namespace dmr /// Returns a string that represents the current CSBK. virtual std::string toString(); + /// Returns a copy of the raw decoded CSBK bytes. + /// This will only return data for a *decoded* CSBK, not a created or copied CSBK. + uint8_t* getDecodedRaw() const; + /// Regenerate a DMR CSBK without decoding. /// This is because the DMR architecture allows fall-thru of unsupported CSBKs. static bool regenerate(uint8_t* data, uint8_t dataType); @@ -144,6 +148,9 @@ namespace dmr void encode(uint8_t* data, const uint8_t* payload); __PROTECTED_COPY(CSBK); + + private: + uint8_t* m_raw; }; } // namespace lc } // namespace dmr diff --git a/src/common/p25/lc/TSBK.cpp b/src/common/p25/lc/TSBK.cpp index 5a1e3a80..bf89b3cd 100644 --- a/src/common/p25/lc/TSBK.cpp +++ b/src/common/p25/lc/TSBK.cpp @@ -95,7 +95,8 @@ TSBK::TSBK() : m_group(true), m_siteIdenEntry(lookups::IdenTable()), m_rs(), - m_trellis() + m_trellis(), + m_raw(nullptr) { if (m_siteCallsign == nullptr) { m_siteCallsign = new uint8_t[P25_MOT_CALLSIGN_LENGTH_BYTES]; @@ -112,7 +113,8 @@ TSBK::TSBK() : /// TSBK::~TSBK() { - /* stub */ + if (m_raw != nullptr) + delete[] m_raw; } /// @@ -125,6 +127,16 @@ std::string TSBK::toString(bool isp) return std::string("TSBK_IOSP_UNKWN (Unknown TSBK)"); } +/// +/// Returns a copy of the raw decoded TSBK bytes. +/// +/// This will only return data for a *decoded* TSBK, not a created or copied TSBK. +/// +uint8_t* TSBK::getDecodedRaw() const +{ + return m_raw; +} + /// /// Sets the callsign. /// @@ -270,6 +282,9 @@ bool TSBK::decode(const uint8_t* data, uint8_t* payload, bool rawTSBK) Utils::dump(2U, "TSBK::decode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); } + m_raw = new uint8_t[P25_TSBK_LENGTH_BYTES]; + ::memcpy(m_raw, tsbk, P25_TSBK_LENGTH_BYTES); + m_lco = tsbk[0U] & 0x3F; // LCO m_lastBlock = (tsbk[0U] & 0x80U) == 0x80U; // Last Block Marker m_mfId = tsbk[1U]; // Mfg Id. diff --git a/src/common/p25/lc/TSBK.h b/src/common/p25/lc/TSBK.h index 04704bb7..f5bf3c47 100644 --- a/src/common/p25/lc/TSBK.h +++ b/src/common/p25/lc/TSBK.h @@ -68,6 +68,10 @@ namespace p25 /// Returns a string that represents the current TSBK. virtual std::string toString(bool isp = false); + /// Returns a copy of the raw decoded TSBK bytes. + /// This will only return data for a *decoded* TSBK, not a created or copied TSBK. + uint8_t* getDecodedRaw() const; + /// Gets the flag indicating verbose log output. static bool getVerbose() { return m_verbose; } /// Sets the flag indicating verbose log output. @@ -159,6 +163,9 @@ namespace p25 void encode(uint8_t* data, const uint8_t* payload, bool rawTSBK = false, bool noTrellis = false); __PROTECTED_COPY(TSBK); + + private: + uint8_t* m_raw; }; } // namespace lc } // namespace p25 diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 6290d9cf..4987cca7 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -101,6 +101,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_influxServerToken(), m_influxOrg("dvm"), m_influxBucket("dvm"), + m_influxLogRawData(false), m_reportPeerPing(reportPeerPing), m_verbose(verbose) { @@ -151,6 +152,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_influxServerToken = conf["influxServerToken"].as(); m_influxOrg = conf["influxOrg"].as("dvm"); m_influxBucket = conf["influxBucket"].as("dvm"); + m_influxLogRawData = conf["influxLogRawData"].as(false); if (m_enableInfluxDB) { m_influxServer = influxdb::ServerInfo(m_influxServerAddress, m_influxServerPort, m_influxOrg, m_influxServerToken, m_influxBucket); } @@ -168,6 +170,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" InfluxDB Port: %u", m_influxServerPort); LogInfo(" InfluxDB Organization: %s", m_influxOrg.c_str()); LogInfo(" InfluxDB Bucket: %s", m_influxBucket.c_str()); + LogInfo(" InfluxDB Log Raw TSBK/CSBK/RCCH: %s", m_influxLogRawData ? "yes" : "no"); } } } diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 4d59d438..a2b90211 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -300,6 +300,7 @@ namespace network std::string m_influxServerToken; std::string m_influxOrg; std::string m_influxBucket; + bool m_influxLogRawData; influxdb::ServerInfo m_influxServer; bool m_reportPeerPing; diff --git a/src/fne/network/fne/TagDMRData.cpp b/src/fne/network/fne/TagDMRData.cpp index 7b77d99e..211ea939 100644 --- a/src/fne/network/fne/TagDMRData.cpp +++ b/src/fne/network/fne/TagDMRData.cpp @@ -231,6 +231,11 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId m_parrotFrames.push_back(std::make_tuple(copy, len, pktSeq, streamId)); } + // process CSBK from peer + if (!processCSBK(buffer, peerId, dmrData)) { + return false; + } + // repeat traffic to the connected peers if (m_network->m_peers.size() > 0U) { uint32_t i = 0U; @@ -504,6 +509,49 @@ bool TagDMRData::peerRewrite(uint32_t peerId, uint32_t& dstId, uint32_t& slotNo, return rewrote; } +/// +/// Helper to process CSBKs being passed from a peer. +/// +/// +/// Peer ID +/// +bool TagDMRData::processCSBK(uint8_t* buffer, uint32_t peerId, dmr::data::Data& dmrData) +{ + // are we receiving a CSBK? + if (dmrData.getDataType() == DT_CSBK) { + uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; + dmrData.getData(data + 2U); + + std::unique_ptr csbk = lc::csbk::CSBKFactory::createCSBK(data + 2U, DT_CSBK); + if (csbk != nullptr) { + // report csbk event to InfluxDB + if (m_network->m_enableInfluxDB && m_network->m_influxLogRawData) { + const uint8_t* raw = csbk->getDecodedRaw(); + if (raw != nullptr) { + std::stringstream ss; + ss << std::hex << + (int)raw[0] << (int)raw[1] << (int)raw[2] << (int)raw[4] << + (int)raw[5] << (int)raw[6] << (int)raw[7] << (int)raw[8] << + (int)raw[9] << (int)raw[10] << (int)raw[11]; + + influxdb::QueryBuilder() + .meas("csbk_event") + .tag("peerId", std::to_string(peerId)) + .tag("lco", __INT_HEX_STR(csbk->getCSBKO())) + .tag("csbk", csbk->toString()) + .field("raw", ss.str()) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .request(m_network->m_influxServer); + } + } + } else { + LogWarning(LOG_NET, "PEER %u, passing CSBK that failed to decode? csbk == nullptr", peerId); + } + } + + return true; +} + /// /// Helper to determine if the peer is permitted for traffic. /// diff --git a/src/fne/network/fne/TagDMRData.h b/src/fne/network/fne/TagDMRData.h index d5faab71..30baaba8 100644 --- a/src/fne/network/fne/TagDMRData.h +++ b/src/fne/network/fne/TagDMRData.h @@ -77,6 +77,9 @@ namespace network /// Helper to route rewrite destination ID and slot. bool peerRewrite(uint32_t peerId, uint32_t& dstId, uint32_t& slotNo, bool outbound = true); + /// Helper to process CSBKs being passed from a peer. + bool processCSBK(uint8_t* buffer, uint32_t peerId, dmr::data::Data& dmrData); + /// Helper to determine if the peer is permitted for traffic. bool isPeerPermitted(uint32_t peerId, dmr::data::Data& data, uint32_t streamId); /// Helper to validate the DMR call stream. diff --git a/src/fne/network/fne/TagP25Data.cpp b/src/fne/network/fne/TagP25Data.cpp index 52af13e2..a57294aa 100644 --- a/src/fne/network/fne/TagP25Data.cpp +++ b/src/fne/network/fne/TagP25Data.cpp @@ -631,6 +631,27 @@ bool TagP25Data::processTSDU(uint8_t* buffer, uint32_t peerId, uint8_t duid) std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); if (tsbk != nullptr) { + // report tsbk event to InfluxDB + if (m_network->m_enableInfluxDB && m_network->m_influxLogRawData) { + const uint8_t* raw = tsbk->getDecodedRaw(); + if (raw != nullptr) { + std::stringstream ss; + ss << std::hex << + (int)raw[0] << (int)raw[1] << (int)raw[2] << (int)raw[4] << + (int)raw[5] << (int)raw[6] << (int)raw[7] << (int)raw[8] << + (int)raw[9] << (int)raw[10] << (int)raw[11]; + + influxdb::QueryBuilder() + .meas("tsbk_event") + .tag("peerId", std::to_string(peerId)) + .tag("lco", __INT_HEX_STR(tsbk->getLCO())) + .tag("tsbk", tsbk->toString()) + .field("raw", ss.str()) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .request(m_network->m_influxServer); + } + } + // handle standard P25 reference opcodes switch (tsbk->getLCO()) { case TSBK_OSP_ADJ_STS_BCAST: