diff --git a/configs/config.example.yml b/configs/config.example.yml index 000b2ddf..b3f027b7 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -69,6 +69,8 @@ network: allowActivityTransfer: true # Flag indicating whether or not the host diagnostic log will be sent to the network. allowDiagnosticTransfer: true + # Flag indicating whether or not the host status will be sent to the network. + allowStatusTransfer: true # Flag indicating whether or not verbose debug logging is enabled. debug: false diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index 6000d2f8..f3f14da3 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -129,7 +129,6 @@ bool BaseNetwork::writeGrantReq(const uint8_t mode, const uint32_t srcId, const /// Writes the local activity log to the network. /// /// -/// /// bool BaseNetwork::writeActLog(const char* message) { @@ -154,7 +153,6 @@ bool BaseNetwork::writeActLog(const char* message) /// Writes the local diagnostics log to the network. /// /// -/// /// bool BaseNetwork::writeDiagLog(const char* message) { @@ -175,6 +173,34 @@ bool BaseNetwork::writeDiagLog(const char* message) 0U, 0U, false, m_useAlternatePortForDiagnostics); } +/// +/// Writes the local status to the network. +/// +/// +/// +bool BaseNetwork::writePeerStatus(json::object obj) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + if (!m_allowActivityTransfer) + return false; + if (!m_useAlternatePortForDiagnostics) + return false; // this is intentional -- peer status is a noisy message and it shouldn't be done + // when the FNE is configured for main port transfers + + json::value v = json::value(obj); + std::string json = std::string(v.serialize()); + + char buffer[DATA_PACKET_LENGTH]; + uint32_t len = ::strlen(json.c_str()); + + ::strcpy(buffer + 11U, json.c_str()); + + return writeMaster({ NET_FUNC_TRANSFER, NET_TRANSFER_SUBFUNC_STATUS }, (uint8_t*)buffer, (uint32_t)len + 12U, + 0U, 0U, false, m_useAlternatePortForDiagnostics); +} + /// /// Writes a group affiliation to the network. /// diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index 15bf1219..43e2213d 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -23,6 +23,7 @@ #include "common/p25/Audio.h" #include "common/nxdn/lc/RTCH.h" #include "common/network/FrameQueue.h" +#include "common/network/json/json.h" #include "common/network/udp/Socket.h" #include "common/RingBuffer.h" #include "common/Utils.h" @@ -53,6 +54,7 @@ #define TAG_TRANSFER "TRNS" #define TAG_TRANSFER_ACT_LOG "TRNSLOG" #define TAG_TRANSFER_DIAG_LOG "TRNSDIAG" +#define TAG_TRANSFER_STATUS "TRNSSTS" #define TAG_ANNOUNCE "ANNC" @@ -103,6 +105,7 @@ namespace network const uint8_t NET_FUNC_TRANSFER = 0x90U; // Network Transfer Function const uint8_t NET_TRANSFER_SUBFUNC_ACTIVITY = 0x01U; // Activity Log Transfer const uint8_t NET_TRANSFER_SUBFUNC_DIAG = 0x02U; // Diagnostic Log Transfer + const uint8_t NET_TRANSFER_SUBFUNC_STATUS = 0x03U; // Status Transfer const uint8_t NET_FUNC_ANNOUNCE = 0x91U; // Network Announce Function const uint8_t NET_ANNC_SUBFUNC_GRP_AFFIL = 0x00U; // Announce Group Affiliation @@ -178,6 +181,9 @@ namespace network /// Writes the local diagnostic logs to the network. virtual bool writeDiagLog(const char* message); + /// Writes the local status to the network. + virtual bool writePeerStatus(json::object obj); + /// Writes a group affiliation to the network. virtual bool announceGroupAffiliation(uint32_t srcId, uint32_t dstId); /// Writes a unit registration to the network. diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index 91b20a47..048d07d6 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -280,6 +280,36 @@ void* DiagNetwork::threadedNetworkRx(void* arg) } } } + else if (req->fneHeader.getSubFunction() == NET_TRANSFER_SUBFUNC_STATUS) { // Peer Status Transfer + // report peer status to InfluxDB + if (network->m_enableInfluxDB) { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + uint8_t rawPayload[req->length - 11U]; + ::memset(rawPayload, 0x00U, req->length - 11U); + ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); + std::string payload(rawPayload, rawPayload + (req->length - 11U)); + + influxdb::QueryBuilder() + .meas("peer_status") + .tag("peerId", std::to_string(peerId)) + .field("identity", connection->identity()) + .field("status", payload) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .request(network->m_influxServer); + } + else { + network->writePeerNAK(peerId, TAG_TRANSFER_STATUS, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + } else { network->writePeerNAK(peerId, TAG_TRANSFER, NET_CONN_NAK_ILLEGAL_PACKET); Utils::dump("unknown transfer opcode from the peer", req->buffer, req->length); diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index cd8570e2..40de4709 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -1036,6 +1036,9 @@ void* FNENetwork::threadedNetworkRx(void* arg) } } } + else if (req->fneHeader.getSubFunction() == NET_TRANSFER_SUBFUNC_STATUS) { // Peer Status Transfer + // main traffic port status transfers aren't supported for performance reasons + } else { network->writePeerNAK(peerId, TAG_TRANSFER, NET_CONN_NAK_ILLEGAL_PACKET); Utils::dump("unknown transfer opcode from the peer", req->buffer, req->length); diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index aaae172f..544eb490 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -693,10 +693,13 @@ bool Host::createNetwork() bool slot2 = networkConf["slot2"].as(true); bool allowActivityTransfer = networkConf["allowActivityTransfer"].as(false); bool allowDiagnosticTransfer = networkConf["allowDiagnosticTransfer"].as(false); + bool allowStatusTransfer = networkConf["allowStatusTransfer"].as(true); bool updateLookup = networkConf["updateLookups"].as(false); bool saveLookup = networkConf["saveLookups"].as(false); bool debug = networkConf["debug"].as(false); + m_allowStatusTransfer = allowStatusTransfer; + bool encrypted = networkConf["encrypted"].as(false); std::string key = networkConf["presharedKey"].as(); uint8_t presharedKey[AES_WRAPPED_PCKT_KEY_LEN]; @@ -778,6 +781,7 @@ bool Host::createNetwork() LogInfo(" Slot 2: %s", slot2 ? "enabled" : "disabled"); LogInfo(" Allow Activity Log Transfer: %s", allowActivityTransfer ? "yes" : "no"); LogInfo(" Allow Diagnostic Log Transfer: %s", allowDiagnosticTransfer ? "yes" : "no"); + LogInfo(" Allow Status Transfer: %s", m_allowStatusTransfer ? "yes" : "no"); LogInfo(" Update Lookups: %s", updateLookup ? "yes" : "no"); LogInfo(" Save Network Lookups: %s", saveLookup ? "yes" : "no"); diff --git a/src/host/Host.cpp b/src/host/Host.cpp index fb6f8c22..cbcc02c6 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -75,6 +75,7 @@ Host::Host(const std::string& confFile) : m_netModeHang(3U), m_lastDstId(0U), m_lastSrcId(0U), + m_allowStatusTransfer(true), m_identity(), m_cwCallsign(), m_cwIdTime(0U), @@ -1035,13 +1036,14 @@ int Host::run() if (g_killed) return; + Timer networkPeerStatusNotify(1000U, 5U); + networkPeerStatusNotify.start(); + StopWatch stopWatch; stopWatch.start(); LogDebug(LOG_HOST, "started adj. site and affiliation processor"); while (!g_killed) { - m_nxdnTxWatchdogTimer.start(); - uint32_t ms = stopWatch.elapsed(); stopWatch.start(); m_adjSiteLoopMS = ms; @@ -1053,6 +1055,15 @@ int Host::run() if (nxdn != nullptr) nxdn->clockSiteData(ms); + if (m_allowStatusTransfer) { + networkPeerStatusNotify.clock(ms); + if (networkPeerStatusNotify.isRunning() && networkPeerStatusNotify.hasExpired()) { + networkPeerStatusNotify.start(); + json::object statusObj = getStatus(); + m_network->writePeerStatus(statusObj); + } + } + if (m_state != STATE_IDLE) Thread::sleep(m_activeTickDelay); if (m_state == STATE_IDLE) @@ -1683,6 +1694,141 @@ int Host::run() // Private Class Members // --------------------------------------------------------------------------- +/// +/// Helper to generate the status of the host in JSON format. +/// +json::object Host::getStatus() +{ + json::object response = json::object(); + + yaml::Node systemConf = m_conf["system"]; + yaml::Node networkConf = m_conf["network"]; + { + response["state"].set(m_state); + + response["isTxCW"].set(m_isTxCW); + + response["fixedMode"].set(m_fixedMode); + + response["dmrTSCCEnable"].set(m_dmrTSCCData); + response["dmrCC"].set(m_dmrCtrlChannel); + response["p25CtrlEnable"].set(m_p25CCData); + response["p25CC"].set(m_p25CtrlChannel); + response["nxdnCtrlEnable"].set(m_nxdnCCData); + response["nxdnCC"].set(m_nxdnCtrlChannel); + + yaml::Node p25Protocol = m_conf["protocols"]["p25"]; + yaml::Node nxdnProtocol = m_conf["protocols"]["nxdn"]; + + response["tx"].set(m_modem->m_tx); + + response["channelId"].set(m_channelId); + response["channelNo"].set(m_channelNo); + + response["lastDstId"].set(m_lastDstId); + response["lastSrcId"].set(m_lastSrcId); + + uint32_t peerId = networkConf["id"].as(); + response["peerId"].set(peerId); + } + + yaml::Node modemConfig = m_conf["system"]["modem"]; + { + json::object modemInfo = json::object(); + std::string portType = modemConfig["protocol"]["type"].as(); + modemInfo["portType"].set(portType); + + yaml::Node uartConfig = modemConfig["protocol"]["uart"]; + std::string modemPort = uartConfig["port"].as(); + modemInfo["modemPort"].set(modemPort); + uint32_t portSpeed = uartConfig["speed"].as(115200U); + modemInfo["portSpeed"].set(portSpeed); + + if (!m_modem->isHotspot()) { + modemInfo["pttInvert"].set(m_modem->m_pttInvert); + modemInfo["rxInvert"].set(m_modem->m_rxInvert); + modemInfo["txInvert"].set(m_modem->m_txInvert); + modemInfo["dcBlocker"].set(m_modem->m_dcBlocker); + } + + modemInfo["rxLevel"].set(m_modem->m_rxLevel); + modemInfo["cwTxLevel"].set(m_modem->m_cwIdTXLevel); + modemInfo["dmrTxLevel"].set(m_modem->m_dmrTXLevel); + modemInfo["p25TxLevel"].set(m_modem->m_p25TXLevel); + modemInfo["nxdnTxLevel"].set(m_modem->m_nxdnTXLevel); + + modemInfo["rxDCOffset"].set(m_modem->m_rxDCOffset); + modemInfo["txDCOffset"].set(m_modem->m_txDCOffset); + + if (!m_modem->isHotspot()) { + modemInfo["dmrSymLevel3Adj"].set(m_modem->m_dmrSymLevel3Adj); + modemInfo["dmrSymLevel1Adj"].set(m_modem->m_dmrSymLevel1Adj); + modemInfo["p25SymLevel3Adj"].set(m_modem->m_p25SymLevel3Adj); + modemInfo["p25SymLevel1Adj"].set(m_modem->m_p25SymLevel1Adj); + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + modemInfo["nxdnSymLevel3Adj"].set(m_modem->m_nxdnSymLevel3Adj); + modemInfo["nxdnSymLevel1Adj"].set(m_modem->m_nxdnSymLevel1Adj); + } + } + + if (m_modem->isHotspot()) { + modemInfo["dmrDiscBW"].set(m_modem->m_dmrDiscBWAdj); + modemInfo["dmrPostBW"].set(m_modem->m_dmrPostBWAdj); + modemInfo["p25DiscBW"].set(m_modem->m_p25DiscBWAdj); + modemInfo["p25PostBW"].set(m_modem->m_p25PostBWAdj); + + // are we on a protocol version 3 firmware? + if (m_modem->getVersion() >= 3U) { + modemInfo["nxdnDiscBW"].set(m_modem->m_nxdnDiscBWAdj); + modemInfo["nxdnPostBW"].set(m_modem->m_nxdnPostBWAdj); + + modemInfo["afcEnabled"].set(m_modem->m_afcEnable); + modemInfo["afcKI"].set(m_modem->m_afcKI); + modemInfo["afcKP"].set(m_modem->m_afcKP); + modemInfo["afcRange"].set(m_modem->m_afcRange); + } + + switch (m_modem->m_adfGainMode) { + case ADF_GAIN_AUTO_LIN: + modemInfo["gainMode"].set("ADF7021 Gain Mode: Auto High Linearity"); + break; + case ADF_GAIN_LOW: + modemInfo["gainMode"].set("ADF7021 Gain Mode: Low"); + break; + case ADF_GAIN_HIGH: + modemInfo["gainMode"].set("ADF7021 Gain Mode: High"); + break; + case ADF_GAIN_AUTO: + default: + modemInfo["gainMode"].set("ADF7021 Gain Mode: Auto"); + break; + } + } + + modemInfo["fdmaPreambles"].set(m_modem->m_fdmaPreamble); + modemInfo["dmrRxDelay"].set(m_modem->m_dmrRxDelay); + modemInfo["p25CorrCount"].set(m_modem->m_p25CorrCount); + + modemInfo["rxFrequency"].set(m_modem->m_rxFrequency); + modemInfo["txFrequency"].set(m_modem->m_txFrequency); + modemInfo["rxTuning"].set(m_modem->m_rxTuning); + modemInfo["txTuning"].set(m_modem->m_txTuning); + uint32_t rxFreqEffective = m_modem->m_rxFrequency + m_modem->m_rxTuning; + modemInfo["rxFrequencyEffective"].set(rxFreqEffective); + uint32_t txFreqEffective = m_modem->m_txFrequency + m_modem->m_txTuning; + modemInfo["txFrequencyEffective"].set(txFreqEffective); + + uint8_t protoVer = m_modem->getVersion(); + modemInfo["protoVer"].set(protoVer); + + response["modem"].set(modemInfo); + } + + return response; +} + /// /// /// diff --git a/src/host/Host.h b/src/host/Host.h index fabe0321..6892dde0 100644 --- a/src/host/Host.h +++ b/src/host/Host.h @@ -22,6 +22,7 @@ #include "common/lookups/IdenTableLookup.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" +#include "common/network/json/json.h" #include "common/yaml/Yaml.h" #include "dmr/Control.h" #include "p25/Control.h" @@ -91,6 +92,8 @@ private: uint32_t m_lastDstId; uint32_t m_lastSrcId; + bool m_allowStatusTransfer; + std::string m_identity; std::string m_cwCallsign; uint32_t m_cwIdTime; @@ -171,6 +174,9 @@ private: uint16_t m_restPort; RESTAPI *m_RESTAPI; + /// Helper to generate the status of the host in JSON format. + json::object getStatus(); + /// Modem port open callback. bool rmtPortModemOpen(modem::Modem* modem); /// Modem port close callback. diff --git a/src/host/modem/Modem.h b/src/host/modem/Modem.h index 4f0d55c9..7c9075d0 100644 --- a/src/host/modem/Modem.h +++ b/src/host/modem/Modem.h @@ -54,6 +54,7 @@ // Class Prototypes // --------------------------------------------------------------------------- +class HOST_SW_API Host; class HOST_SW_API HostCal; class HOST_SW_API RESTAPI; @@ -365,6 +366,7 @@ namespace modem uint8_t getVersion() const; private: + friend class ::Host; friend class ::HostCal; friend class ::RESTAPI; diff --git a/src/host/network/RESTAPI.cpp b/src/host/network/RESTAPI.cpp index 2ad78b39..d3be0ae6 100644 --- a/src/host/network/RESTAPI.cpp +++ b/src/host/network/RESTAPI.cpp @@ -509,138 +509,16 @@ void RESTAPI::restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply, return; } - json::object response = json::object(); + json::object response = m_host->getStatus(); setResponseDefaultStatus(response); - yaml::Node systemConf = m_host->m_conf["system"]; - yaml::Node networkConf = m_host->m_conf["network"]; { - response["state"].set(m_host->m_state); bool dmrEnabled = m_dmr != nullptr; response["dmrEnabled"].set(dmrEnabled); bool p25Enabled = m_p25 != nullptr; response["p25Enabled"].set(p25Enabled); bool nxdnEnabled = m_nxdn != nullptr; response["nxdnEnabled"].set(nxdnEnabled); - - response["isTxCW"].set(m_host->m_isTxCW); - - response["fixedMode"].set(m_host->m_fixedMode); - - response["dmrTSCCEnable"].set(m_host->m_dmrTSCCData); - response["dmrCC"].set(m_host->m_dmrCtrlChannel); - response["p25CtrlEnable"].set(m_host->m_p25CCData); - response["p25CC"].set(m_host->m_p25CtrlChannel); - response["nxdnCtrlEnable"].set(m_host->m_nxdnCCData); - response["nxdnCC"].set(m_host->m_nxdnCtrlChannel); - - yaml::Node p25Protocol = m_host->m_conf["protocols"]["p25"]; - yaml::Node nxdnProtocol = m_host->m_conf["protocols"]["nxdn"]; - - response["tx"].set(m_host->m_modem->m_tx); - - response["channelId"].set(m_host->m_channelId); - response["channelNo"].set(m_host->m_channelNo); - - response["lastDstId"].set(m_host->m_lastDstId); - response["lastSrcId"].set(m_host->m_lastSrcId); - - uint32_t peerId = networkConf["id"].as(); - response["peerId"].set(peerId); - } - - yaml::Node modemConfig = m_host->m_conf["system"]["modem"]; - { - json::object modemInfo = json::object(); - std::string portType = modemConfig["protocol"]["type"].as(); - modemInfo["portType"].set(portType); - - yaml::Node uartConfig = modemConfig["protocol"]["uart"]; - std::string modemPort = uartConfig["port"].as(); - modemInfo["modemPort"].set(modemPort); - uint32_t portSpeed = uartConfig["speed"].as(115200U); - modemInfo["portSpeed"].set(portSpeed); - - if (!m_host->m_modem->isHotspot()) { - modemInfo["pttInvert"].set(m_host->m_modem->m_pttInvert); - modemInfo["rxInvert"].set(m_host->m_modem->m_rxInvert); - modemInfo["txInvert"].set(m_host->m_modem->m_txInvert); - modemInfo["dcBlocker"].set(m_host->m_modem->m_dcBlocker); - } - - modemInfo["rxLevel"].set(m_host->m_modem->m_rxLevel); - modemInfo["cwTxLevel"].set(m_host->m_modem->m_cwIdTXLevel); - modemInfo["dmrTxLevel"].set(m_host->m_modem->m_dmrTXLevel); - modemInfo["p25TxLevel"].set(m_host->m_modem->m_p25TXLevel); - modemInfo["nxdnTxLevel"].set(m_host->m_modem->m_nxdnTXLevel); - - modemInfo["rxDCOffset"].set(m_host->m_modem->m_rxDCOffset); - modemInfo["txDCOffset"].set(m_host->m_modem->m_txDCOffset); - - if (!m_host->m_modem->isHotspot()) { - modemInfo["dmrSymLevel3Adj"].set(m_host->m_modem->m_dmrSymLevel3Adj); - modemInfo["dmrSymLevel1Adj"].set(m_host->m_modem->m_dmrSymLevel1Adj); - modemInfo["p25SymLevel3Adj"].set(m_host->m_modem->m_p25SymLevel3Adj); - modemInfo["p25SymLevel1Adj"].set(m_host->m_modem->m_p25SymLevel1Adj); - - // are we on a protocol version 3 firmware? - if (m_host->m_modem->getVersion() >= 3U) { - modemInfo["nxdnSymLevel3Adj"].set(m_host->m_modem->m_nxdnSymLevel3Adj); - modemInfo["nxdnSymLevel1Adj"].set(m_host->m_modem->m_nxdnSymLevel1Adj); - } - } - - if (m_host->m_modem->isHotspot()) { - modemInfo["dmrDiscBW"].set(m_host->m_modem->m_dmrDiscBWAdj); - modemInfo["dmrPostBW"].set(m_host->m_modem->m_dmrPostBWAdj); - modemInfo["p25DiscBW"].set(m_host->m_modem->m_p25DiscBWAdj); - modemInfo["p25PostBW"].set(m_host->m_modem->m_p25PostBWAdj); - - // are we on a protocol version 3 firmware? - if (m_host->m_modem->getVersion() >= 3U) { - modemInfo["nxdnDiscBW"].set(m_host->m_modem->m_nxdnDiscBWAdj); - modemInfo["nxdnPostBW"].set(m_host->m_modem->m_nxdnPostBWAdj); - - modemInfo["afcEnabled"].set(m_host->m_modem->m_afcEnable); - modemInfo["afcKI"].set(m_host->m_modem->m_afcKI); - modemInfo["afcKP"].set(m_host->m_modem->m_afcKP); - modemInfo["afcRange"].set(m_host->m_modem->m_afcRange); - } - - switch (m_host->m_modem->m_adfGainMode) { - case ADF_GAIN_AUTO_LIN: - modemInfo["gainMode"].set("ADF7021 Gain Mode: Auto High Linearity"); - break; - case ADF_GAIN_LOW: - modemInfo["gainMode"].set("ADF7021 Gain Mode: Low"); - break; - case ADF_GAIN_HIGH: - modemInfo["gainMode"].set("ADF7021 Gain Mode: High"); - break; - case ADF_GAIN_AUTO: - default: - modemInfo["gainMode"].set("ADF7021 Gain Mode: Auto"); - break; - } - } - - modemInfo["fdmaPreambles"].set(m_host->m_modem->m_fdmaPreamble); - modemInfo["dmrRxDelay"].set(m_host->m_modem->m_dmrRxDelay); - modemInfo["p25CorrCount"].set(m_host->m_modem->m_p25CorrCount); - - modemInfo["rxFrequency"].set(m_host->m_modem->m_rxFrequency); - modemInfo["txFrequency"].set(m_host->m_modem->m_txFrequency); - modemInfo["rxTuning"].set(m_host->m_modem->m_rxTuning); - modemInfo["txTuning"].set(m_host->m_modem->m_txTuning); - uint32_t rxFreqEffective = m_host->m_modem->m_rxFrequency + m_host->m_modem->m_rxTuning; - modemInfo["rxFrequencyEffective"].set(rxFreqEffective); - uint32_t txFreqEffective = m_host->m_modem->m_txFrequency + m_host->m_modem->m_txTuning; - modemInfo["txFrequencyEffective"].set(txFreqEffective); - - uint8_t protoVer = m_host->m_modem->getVersion(); - modemInfo["protoVer"].set(protoVer); - - response["modem"].set(modemInfo); } reply.payload(response);