Files
wlan-cloud-ucentralgw/src/AP_WS_Connection.cpp
2024-03-13 14:07:10 -07:00

923 lines
32 KiB
C++

//
// Created by stephane bourque on 2022-02-03.
//
#include <Poco/Base64Decoder.h>
#include <Poco/Net/Context.h>
#include <Poco/Net/HTTPServerRequestImpl.h>
#include <Poco/Net/HTTPServerResponseImpl.h>
#include <Poco/Net/NetException.h>
#include <Poco/Net/SSLException.h>
#include <Poco/Net/SecureStreamSocketImpl.h>
#include <Poco/Net/WebSocketImpl.h>
#include <framework/KafkaManager.h>
#include <framework/MicroServiceFuncs.h>
#include <framework/utils.h>
#include <framework/ow_constants.h>
#include <fmt/format.h>
#include <AP_WS_Connection.h>
#include <AP_WS_Server.h>
#include <CentralConfig.h>
#include <CommandManager.h>
#include <StorageService.h>
#include <RADIUSSessionTracker.h>
#include <RADIUS_proxy_server.h>
#include <GWKafkaEvents.h>
#include <UI_GW_WebSocketNotifications.h>
namespace OpenWifi {
void AP_WS_Connection::LogException(const Poco::Exception &E) {
poco_information(Logger_, fmt::format("EXCEPTION({}): {}", CId_, E.displayText()));
}
AP_WS_Connection::AP_WS_Connection(Poco::Net::HTTPServerRequest &request,
Poco::Net::HTTPServerResponse &response,
uint64_t session_id, Poco::Logger &L,
std::pair<std::shared_ptr<Poco::Net::SocketReactor>, std::shared_ptr<LockedDbSession>> R)
: Logger_(L) {
Reactor_ = R.first;
DbSession_ = R.second;
State_.sessionId = session_id;
WS_ = std::make_unique<Poco::Net::WebSocket>(request, response);
auto TS = Poco::Timespan(360, 0);
WS_->setMaxPayloadSize(BufSize);
WS_->setReceiveTimeout(TS);
WS_->setNoDelay(false);
WS_->setKeepAlive(true);
WS_->setBlocking(false);
uuid_ = MicroServiceRandom(std::numeric_limits<std::uint64_t>::max()-1);
AP_WS_Server()->IncrementConnectionCount();
}
void AP_WS_Connection::Start() {
Registered_ = true;
LastContact_ = Utils::Now();
Reactor_->addEventHandler(*WS_,
Poco::NObserver<AP_WS_Connection, Poco::Net::ReadableNotification>(
*this, &AP_WS_Connection::OnSocketReadable));
Reactor_->addEventHandler(*WS_,
Poco::NObserver<AP_WS_Connection, Poco::Net::ShutdownNotification>(
*this, &AP_WS_Connection::OnSocketShutdown));
Reactor_->addEventHandler(*WS_,
Poco::NObserver<AP_WS_Connection, Poco::Net::ErrorNotification>(
*this, &AP_WS_Connection::OnSocketError));
}
AP_WS_Connection::~AP_WS_Connection() {
std::lock_guard G(ConnectionMutex_);
AP_WS_Server()->DecrementConnectionCount();
EndConnection();
poco_debug(Logger_, fmt::format("TERMINATION({}): Session={}, Connection removed.", SerialNumber_,
State_.sessionId));
}
static void NotifyKafkaDisconnect(const std::string &SerialNumber, std::uint64_t uuid) {
try {
Poco::JSON::Object Disconnect;
Poco::JSON::Object Details;
Details.set(uCentralProtocol::SERIALNUMBER, SerialNumber);
Details.set(uCentralProtocol::TIMESTAMP, Utils::Now());
Details.set(uCentralProtocol::UUID,uuid);
Disconnect.set(uCentralProtocol::DISCONNECTION, Details);
KafkaManager()->PostMessage(KafkaTopics::CONNECTION, SerialNumber, Disconnect);
} catch (...) {
}
}
void AP_WS_Connection::EndConnection() {
bool expectedValue=false;
if (Dead_.compare_exchange_strong(expectedValue,true,std::memory_order_release,std::memory_order_relaxed)) {
if(!SerialNumber_.empty() && State_.LastContact!=0) {
StorageService()->SetDeviceLastRecordedContact(SerialNumber_, State_.LastContact);
}
if (Registered_) {
Registered_ = false;
Reactor_->removeEventHandler(
*WS_, Poco::NObserver<AP_WS_Connection, Poco::Net::ReadableNotification>(
*this, &AP_WS_Connection::OnSocketReadable));
Reactor_->removeEventHandler(
*WS_, Poco::NObserver<AP_WS_Connection, Poco::Net::ShutdownNotification>(
*this, &AP_WS_Connection::OnSocketShutdown));
Reactor_->removeEventHandler(
*WS_, Poco::NObserver<AP_WS_Connection, Poco::Net::ErrorNotification>(
*this, &AP_WS_Connection::OnSocketError));
Registered_=false;
}
WS_->close();
if(!SerialNumber_.empty()) {
DeviceDisconnectionCleanup(SerialNumber_, uuid_);
}
AP_WS_Server()->AddCleanupSession(State_.sessionId, SerialNumberInt_);
}
}
bool AP_WS_Connection::ValidatedDevice() {
if(Dead_)
return false;
if (DeviceValidated_)
return true;
try {
auto SockImpl = dynamic_cast<Poco::Net::WebSocketImpl *>(WS_->impl());
auto SS =
dynamic_cast<Poco::Net::SecureStreamSocketImpl *>(SockImpl->streamSocketImpl());
PeerAddress_ = SS->peerAddress().host();
CId_ = Utils::FormatIPv6(SS->peerAddress().toString());
State_.started = Utils::Now();
if (!SS->secure()) {
poco_warning(Logger_, fmt::format("TLS-CONNECTION({}): Session={} Connection is "
"NOT secure. Device is not allowed.",
CId_, State_.sessionId));
return false;
}
poco_trace(Logger_, fmt::format("TLS-CONNECTION({}): Session={} Connection is secure.",
CId_, State_.sessionId));
if (!SS->havePeerCertificate()) {
State_.VerifiedCertificate = GWObjects::NO_CERTIFICATE;
poco_warning(
Logger_,
fmt::format("TLS-CONNECTION({}): Session={} No certificates available..", CId_,
State_.sessionId));
return false;
}
Poco::Crypto::X509Certificate PeerCert(SS->peerCertificate());
if (!AP_WS_Server()->ValidateCertificate(CId_, PeerCert)) {
State_.VerifiedCertificate = GWObjects::NO_CERTIFICATE;
poco_warning(Logger_,
fmt::format("TLS-CONNECTION({}): Session={} Device certificate is not "
"valid. Device is not allowed.",
CId_, State_.sessionId));
return false;
}
CN_ = Poco::trim(Poco::toLower(PeerCert.commonName()));
if(!Utils::ValidSerialNumber(CN_)) {
poco_trace(Logger_,
fmt::format("TLS-CONNECTION({}): Session={} Invalid serial number: CN={}", CId_,
State_.sessionId, CN_));
return false;
}
SerialNumber_ = CN_;
SerialNumberInt_ = Utils::SerialNumberToInt(SerialNumber_);
State_.VerifiedCertificate = GWObjects::VALID_CERTIFICATE;
poco_trace(Logger_,
fmt::format("TLS-CONNECTION({}): Session={} Valid certificate: CN={}", CId_,
State_.sessionId, CN_));
if (AP_WS_Server::IsSim(CN_) && !AP_WS_Server()->IsSimEnabled()) {
poco_warning(Logger_, fmt::format("TLS-CONNECTION({}): Session={} Sim Device {} is "
"not allowed. Disconnecting.",
CId_, State_.sessionId, CN_));
return false;
}
if(AP_WS_Server::IsSim(SerialNumber_)) {
State_.VerifiedCertificate = GWObjects::SIMULATED;
Simulated_ = true;
}
std::string reason, author;
std::uint64_t created;
if (!CN_.empty() && StorageService()->IsBlackListed(SerialNumberInt_, reason, author, created)) {
DeviceBlacklistedKafkaEvent KE(Utils::SerialNumberToInt(CN_), Utils::Now(), reason, author, created, CId_);
poco_warning(
Logger_,
fmt::format(
"TLS-CONNECTION({}): Session={} Device {} is black listed. Disconnecting.",
CId_, State_.sessionId, CN_));
return false;
}
State_.certificateExpiryDate = PeerCert.expiresOn().timestamp().epochTime();
poco_trace(Logger_,
fmt::format("TLS-CONNECTION({}): Session={} CN={} Completed. (t={})", CId_,
State_.sessionId, CN_, ConcurrentStartingDevices_));
DeviceValidated_ = true;
return true;
} catch (const Poco::Net::CertificateValidationException &E) {
poco_error(
Logger_,
fmt::format(
"CONNECTION({}): Session:{} Poco::CertificateValidationException Certificate "
"Validation failed during connection. Device will have to retry.",
CId_, State_.sessionId));
Logger_.log(E);
} catch (const Poco::Net::WebSocketException &E) {
poco_error(Logger_,
fmt::format("CONNECTION({}): Session:{} Poco::WebSocketException WebSocket "
"error during connection. Device will have to retry.",
CId_, State_.sessionId));
Logger_.log(E);
} catch (const Poco::Net::ConnectionAbortedException &E) {
poco_error(
Logger_,
fmt::format("CONNECTION({}):Session:{} Poco::ConnectionAbortedException "
"Connection was aborted during connection. Device will have to retry.",
CId_, State_.sessionId));
Logger_.log(E);
} catch (const Poco::Net::ConnectionResetException &E) {
poco_error(
Logger_,
fmt::format("CONNECTION({}): Session:{} Poco::ConnectionResetException Connection "
"was reset during connection. Device will have to retry.",
CId_, State_.sessionId));
Logger_.log(E);
} catch (const Poco::Net::InvalidCertificateException &E) {
poco_error(Logger_,
fmt::format("CONNECTION({}): Session:{} Poco::InvalidCertificateException "
"Invalid certificate. Device will have to retry.",
CId_, State_.sessionId));
Logger_.log(E);
} catch (const Poco::Net::SSLException &E) {
poco_error(Logger_,
fmt::format("CONNECTION({}): Session:{} Poco::SSLException SSL Exception "
"during connection. Device will have to retry.",
CId_, State_.sessionId));
Logger_.log(E);
} catch (const Poco::Exception &E) {
poco_error(Logger_, fmt::format("CONNECTION({}): Session:{} Poco::Exception caught "
"during device connection. Device will have to retry.",
CId_, State_.sessionId));
Logger_.log(E);
} catch (...) {
poco_error(
Logger_,
fmt::format("CONNECTION({}): Session:{} Exception caught during device connection. "
"Device will have to retry. Unsecure connect denied.",
CId_, State_.sessionId));
}
EndConnection();
return false;
}
void AP_WS_Connection::DeviceDisconnectionCleanup(const std::string &SerialNumber, std::uint64_t uuid) {
if (KafkaManager()->Enabled()) {
NotifyKafkaDisconnect(SerialNumber, uuid);
}
RADIUSSessionTracker()->DeviceDisconnect(SerialNumber);
GWWebSocketNotifications::SingleDevice_t N;
N.content.serialNumber = SerialNumber;
GWWebSocketNotifications::DeviceDisconnected(N);
}
void AP_WS_Connection::ProcessJSONRPCResult(Poco::JSON::Object::Ptr Doc) {
poco_trace(Logger_, fmt::format("RECEIVED-RPC({}): {}.", CId_,
Doc->get(uCentralProtocol::ID).toString()));
CommandManager()->PostCommandResult(SerialNumber_, Doc);
}
void AP_WS_Connection::ProcessJSONRPCEvent(Poco::JSON::Object::Ptr &Doc) {
auto Method = Doc->get(uCentralProtocol::METHOD).toString();
auto EventType = uCentralProtocol::Events::EventFromString(Method);
if (EventType == uCentralProtocol::Events::ET_UNKNOWN) {
poco_warning(Logger_, fmt::format("ILLEGAL-PROTOCOL({}): Unknown message type '{}'",
CId_, Method));
Errors_++;
return;
}
if (!Doc->isObject(uCentralProtocol::PARAMS)) {
poco_warning(Logger_,
fmt::format("MISSING-PARAMS({}): params must be an object.", CId_));
Errors_++;
return;
}
// expand params if necessary
auto ParamsObj = Doc->get(uCentralProtocol::PARAMS).extract<Poco::JSON::Object::Ptr>();
if (ParamsObj->has(uCentralProtocol::COMPRESS_64)) {
std::string UncompressedData;
try {
auto CompressedData = ParamsObj->get(uCentralProtocol::COMPRESS_64).toString();
uint64_t compress_sz = 0;
if (ParamsObj->has("compress_sz")) {
compress_sz = ParamsObj->get("compress_sz");
}
if (Utils::ExtractBase64CompressedData(CompressedData, UncompressedData,
compress_sz)) {
poco_trace(Logger_,
fmt::format("EVENT({}): Found compressed payload expanded to '{}'.",
CId_, UncompressedData));
Poco::JSON::Parser Parser;
ParamsObj = Parser.parse(UncompressedData).extract<Poco::JSON::Object::Ptr>();
} else {
poco_warning(Logger_,
fmt::format("INVALID-COMPRESSED-DATA({}): Compressed cannot be "
"uncompressed - content must be corrupt..: size={}",
CId_, CompressedData.size()));
Errors_++;
return;
}
} catch (const Poco::Exception &E) {
poco_warning(Logger_, fmt::format("INVALID-COMPRESSED-JSON-DATA({}): Compressed "
"cannot be parsed - JSON must be corrupt..",
CId_));
Logger_.log(E);
return;
}
}
if (!ParamsObj->has(uCentralProtocol::SERIAL)) {
poco_warning(
Logger_,
fmt::format("MISSING-PARAMS({}): Serial number is missing in message.", CId_));
return;
}
auto Serial =
Poco::trim(Poco::toLower(ParamsObj->get(uCentralProtocol::SERIAL).toString()));
if (!Utils::ValidSerialNumber(Serial)) {
Poco::Exception E(
fmt::format(
"ILLEGAL-DEVICE-NAME({}): device name is illegal and not allowed to connect.",
Serial),
EACCES);
E.rethrow();
}
std::string reason, author;
std::uint64_t created;
if (StorageService()->IsBlackListed(SerialNumberInt_, reason, author, created)) {
DeviceBlacklistedKafkaEvent KE(Utils::SerialNumberToInt(CN_), Utils::Now(), reason, author, created, CId_);
Poco::Exception E(
fmt::format("BLACKLIST({}): device is blacklisted and not allowed to connect.",
Serial),
EACCES);
E.rethrow();
}
switch (EventType) {
case uCentralProtocol::Events::ET_CONNECT: {
Process_connect(ParamsObj, Serial);
} break;
case uCentralProtocol::Events::ET_STATE: {
Process_state(ParamsObj);
} break;
case uCentralProtocol::Events::ET_HEALTHCHECK: {
Process_healthcheck(ParamsObj);
} break;
case uCentralProtocol::Events::ET_LOG: {
Process_log(ParamsObj);
} break;
case uCentralProtocol::Events::ET_CRASHLOG: {
Process_crashlog(ParamsObj);
} break;
case uCentralProtocol::Events::ET_PING: {
Process_ping(ParamsObj);
} break;
case uCentralProtocol::Events::ET_CFGPENDING: {
Process_cfgpending(ParamsObj);
} break;
case uCentralProtocol::Events::ET_RECOVERY: {
Process_recovery(ParamsObj);
} break;
case uCentralProtocol::Events::ET_DEVICEUPDATE: {
Process_deviceupdate(ParamsObj, Serial);
} break;
case uCentralProtocol::Events::ET_TELEMETRY: {
Process_telemetry(ParamsObj);
} break;
case uCentralProtocol::Events::ET_VENUEBROADCAST: {
Process_venuebroadcast(ParamsObj);
} break;
case uCentralProtocol::Events::ET_EVENT: {
Process_event(ParamsObj);
} break;
case uCentralProtocol::Events::ET_ALARM: {
Process_alarm(ParamsObj);
} break;
case uCentralProtocol::Events::ET_WIFISCAN: {
Process_wifiscan(ParamsObj);
} break;
case uCentralProtocol::Events::ET_REBOOTLOG: {
Process_rebootLog(ParamsObj);
} break;
// this will never be called but some compilers will complain if we do not have a case for
// every single values of an enum
case uCentralProtocol::Events::ET_UNKNOWN: {
poco_warning(Logger_, fmt::format("ILLEGAL-EVENT({}): Event '{}' unknown. CN={}", CId_,
Method, CN_));
Errors_++;
}
}
}
bool AP_WS_Connection::StartTelemetry(uint64_t RPCID,
const std::vector<std::string> &TelemetryTypes) {
poco_information(Logger_, fmt::format("TELEMETRY({}): Starting.", CId_));
Poco::JSON::Object StartMessage;
StartMessage.set("jsonrpc", "2.0");
StartMessage.set("method", "telemetry");
Poco::JSON::Object Params;
Params.set("serial", SerialNumber_);
Params.set("interval", (uint64_t)TelemetryInterval_);
Poco::JSON::Array Types;
if (TelemetryTypes.empty()) {
Types.add("wifi-frames");
Types.add("dhcp-snooping");
Types.add("state");
} else {
for (const auto &type : TelemetryTypes)
Types.add(type);
}
Params.set(RESTAPI::Protocol::TYPES, Types);
StartMessage.set("id", RPCID);
StartMessage.set("params", Params);
Poco::JSON::Stringifier Stringify;
std::ostringstream OS;
Stringify.condense(StartMessage, OS);
return Send(OS.str());
}
bool AP_WS_Connection::StopTelemetry(uint64_t RPCID) {
poco_information(Logger_, fmt::format("TELEMETRY({}): Stopping.", CId_));
Poco::JSON::Object StopMessage;
StopMessage.set("jsonrpc", "2.0");
StopMessage.set("method", "telemetry");
Poco::JSON::Object Params;
Params.set("serial", SerialNumber_);
Params.set("interval", 0);
StopMessage.set("id", RPCID);
StopMessage.set("params", Params);
Poco::JSON::Stringifier Stringify;
std::ostringstream OS;
Stringify.condense(StopMessage, OS);
TelemetryKafkaPackets_ = TelemetryWebSocketPackets_ = TelemetryInterval_ =
TelemetryKafkaTimer_ = TelemetryWebSocketTimer_ = 0;
return Send(OS.str());
}
void AP_WS_Connection::UpdateCounts() {
State_.kafkaClients = TelemetryKafkaRefCount_;
State_.webSocketClients = TelemetryWebSocketRefCount_;
}
bool AP_WS_Connection::SetWebSocketTelemetryReporting(
std::uint64_t RPCID, std::uint64_t Interval, std::uint64_t LifeTime,
const std::vector<std::string> &TelemetryTypes) {
std::unique_lock Lock(TelemetryMutex_);
TelemetryWebSocketRefCount_++;
TelemetryInterval_ = TelemetryInterval_
? (Interval < (std::uint64_t)TelemetryInterval_ ? Interval : (std::uint64_t )TelemetryInterval_)
: Interval;
auto TelemetryWebSocketTimer = LifeTime + Utils::Now();
TelemetryWebSocketTimer_ = TelemetryWebSocketTimer > (std::uint64_t)TelemetryWebSocketTimer_
? (std::uint64_t)TelemetryWebSocketTimer
: (std::uint64_t)TelemetryWebSocketTimer_;
UpdateCounts();
if (!TelemetryReporting_) {
TelemetryReporting_ = true;
return StartTelemetry(RPCID, TelemetryTypes);
}
return true;
}
bool
AP_WS_Connection::SetKafkaTelemetryReporting(uint64_t RPCID, uint64_t Interval,
uint64_t LifeTime,
const std::vector<std::string> &TelemetryTypes) {
std::unique_lock Lock(TelemetryMutex_);
TelemetryKafkaRefCount_++;
TelemetryInterval_ = TelemetryInterval_
? (Interval < (std::uint64_t)TelemetryInterval_ ? (std::uint64_t)Interval : (std::uint64_t)TelemetryInterval_)
: Interval;
auto TelemetryKafkaTimer = LifeTime + Utils::Now();
TelemetryKafkaTimer_ =
TelemetryKafkaTimer > (std::uint64_t)TelemetryKafkaTimer_ ? (std::uint64_t)TelemetryKafkaTimer : (std::uint64_t)TelemetryKafkaTimer_;
UpdateCounts();
if (!TelemetryReporting_) {
TelemetryReporting_ = true;
return StartTelemetry(RPCID, TelemetryTypes);
}
return true;
}
bool AP_WS_Connection::StopWebSocketTelemetry(uint64_t RPCID) {
std::unique_lock Lock(TelemetryMutex_);
if (TelemetryWebSocketRefCount_)
TelemetryWebSocketRefCount_--;
UpdateCounts();
if (TelemetryWebSocketRefCount_ == 0 && TelemetryKafkaRefCount_ == 0) {
TelemetryReporting_ = false;
StopTelemetry(RPCID);
}
return true;
}
bool AP_WS_Connection::StopKafkaTelemetry(uint64_t RPCID) {
std::unique_lock Lock(TelemetryMutex_);
if (TelemetryKafkaRefCount_)
TelemetryKafkaRefCount_--;
UpdateCounts();
if (TelemetryWebSocketRefCount_ == 0 && TelemetryKafkaRefCount_ == 0) {
TelemetryReporting_ = false;
StopTelemetry(RPCID);
}
return true;
}
void AP_WS_Connection::OnSocketShutdown(
[[maybe_unused]] const Poco::AutoPtr<Poco::Net::ShutdownNotification> &pNf) {
poco_trace(Logger_, fmt::format("SOCKET-SHUTDOWN({}): Closing.", CId_));
// std::lock_guard G(ConnectionMutex_);
return EndConnection();
}
void AP_WS_Connection::OnSocketError(
[[maybe_unused]] const Poco::AutoPtr<Poco::Net::ErrorNotification> &pNf) {
poco_trace(Logger_, fmt::format("SOCKET-ERROR({}): Closing.", CId_));
// std::lock_guard G(ConnectionMutex_);
return EndConnection();
}
void AP_WS_Connection::OnSocketReadable(
[[maybe_unused]] const Poco::AutoPtr<Poco::Net::ReadableNotification> &pNf) {
if (Dead_) // we are dead, so we do not process anything.
return;
std::lock_guard G(ConnectionMutex_);
State_.LastContact = LastContact_ = Utils::Now();
if (AP_WS_Server()->Running() && (DeviceValidated_ || ValidatedDevice())) {
try {
return ProcessIncomingFrame();
} catch (const Poco::Exception &E) {
Logger_.log(E);
} catch (const std::exception &E) {
std::string W = E.what();
poco_information(
Logger_, fmt::format("std::exception caught: {}. Connection terminated with {}",
W, CId_));
} catch (...) {
poco_information(
Logger_, fmt::format("Unknown exception for {}. Connection terminated.", CId_));
}
}
EndConnection();
}
void AP_WS_Connection::ProcessIncomingFrame() {
Poco::Buffer<char> IncomingFrame(0);
bool KillConnection=false;
try {
int Op, flags;
auto IncomingSize = WS_->receiveFrame(IncomingFrame, flags);
Op = flags & Poco::Net::WebSocket::FRAME_OP_BITMASK;
if (IncomingSize == 0 && flags == 0 && Op == 0) {
poco_information(Logger_,
fmt::format("DISCONNECT({}): device has disconnected. Session={}",
CId_, State_.sessionId));
return EndConnection();
}
IncomingFrame.append(0);
State_.RX += IncomingSize;
AP_WS_Server()->AddRX(IncomingSize);
State_.MessageCount++;
State_.LastContact = Utils::Now();
switch (Op) {
case Poco::Net::WebSocket::FRAME_OP_PING: {
poco_trace(Logger_, fmt::format("WS-PING({}): received. PONG sent back.", CId_));
WS_->sendFrame("", 0,
(int)Poco::Net::WebSocket::FRAME_OP_PONG |
(int)Poco::Net::WebSocket::FRAME_FLAG_FIN);
if (KafkaManager()->Enabled()) {
Poco::JSON::Object PingObject;
Poco::JSON::Object PingDetails;
PingDetails.set(uCentralProtocol::FIRMWARE, State_.Firmware);
PingDetails.set(uCentralProtocol::SERIALNUMBER, SerialNumber_);
PingDetails.set(uCentralProtocol::COMPATIBLE, Compatible_);
PingDetails.set(uCentralProtocol::CONNECTIONIP, CId_);
PingDetails.set(uCentralProtocol::TIMESTAMP, Utils::Now());
PingDetails.set(uCentralProtocol::UUID, uuid_);
PingDetails.set("locale", State_.locale);
PingObject.set(uCentralProtocol::PING, PingDetails);
poco_trace(Logger_,fmt::format("Sending PING for {}", SerialNumber_));
KafkaManager()->PostMessage(KafkaTopics::CONNECTION, SerialNumber_,PingObject);
}
} break;
case Poco::Net::WebSocket::FRAME_OP_PONG: {
poco_trace(Logger_, fmt::format("PONG({}): received and ignored.", CId_));
} break;
case Poco::Net::WebSocket::FRAME_OP_TEXT: {
poco_trace(Logger_,
fmt::format("FRAME({}): Frame received (length={}, flags={}). Msg={}",
CId_, IncomingSize, flags, IncomingFrame.begin()));
Poco::JSON::Parser parser;
auto ParsedMessage = parser.parse(IncomingFrame.begin());
auto IncomingJSON = ParsedMessage.extract<Poco::JSON::Object::Ptr>();
if (IncomingJSON->has(uCentralProtocol::JSONRPC)) {
if (IncomingJSON->has(uCentralProtocol::METHOD) &&
IncomingJSON->has(uCentralProtocol::PARAMS)) {
ProcessJSONRPCEvent(IncomingJSON);
} else if (IncomingJSON->has(uCentralProtocol::RESULT) &&
IncomingJSON->has(uCentralProtocol::ID)) {
poco_trace(Logger_, fmt::format("RPC-RESULT({}): payload: {}", CId_,
IncomingFrame.begin()));
ProcessJSONRPCResult(IncomingJSON);
} else {
poco_warning(
Logger_,
fmt::format("INVALID-PAYLOAD({}): Payload is not JSON-RPC 2.0: {}",
CId_, IncomingFrame.begin()));
}
} else if (IncomingJSON->has(uCentralProtocol::RADIUS)) {
ProcessIncomingRadiusData(IncomingJSON);
} else {
std::ostringstream iS;
IncomingJSON->stringify(iS);
poco_warning(
Logger_,
fmt::format("FRAME({}): illegal transaction header, missing 'jsonrpc': {}",
CId_, iS.str()));
Errors_++;
}
} break;
case Poco::Net::WebSocket::FRAME_OP_CLOSE: {
poco_information(Logger_,
fmt::format("CLOSE({}): Device is closing its connection.", CId_));
KillConnection=true;
} break;
default: {
poco_warning(Logger_, fmt::format("UNKNOWN({}): unknown WS Frame operation: {}",
CId_, std::to_string(Op)));
Errors_++;
return;
}
}
} catch (const Poco::Net::ConnectionResetException &E) {
poco_warning(Logger_,
fmt::format("ConnectionResetException({}): Text:{} Payload:{} Session:{}",
CId_, E.displayText(),
IncomingFrame.begin() == nullptr ? "" : IncomingFrame.begin(),
State_.sessionId));
KillConnection=true;
} catch (const Poco::JSON::JSONException &E) {
poco_warning(Logger_,
fmt::format("JSONException({}): Text:{} Payload:{} Session:{}", CId_,
E.displayText(),
IncomingFrame.begin() == nullptr ? "" : IncomingFrame.begin(),
State_.sessionId));
KillConnection=true;
} catch (const Poco::Net::WebSocketException &E) {
poco_warning(Logger_,
fmt::format("WebSocketException({}): Text:{} Payload:{} Session:{}", CId_,
E.displayText(),
IncomingFrame.begin() == nullptr ? "" : IncomingFrame.begin(),
State_.sessionId));
KillConnection=true;
} catch (const Poco::Net::SSLConnectionUnexpectedlyClosedException &E) {
poco_warning(
Logger_,
fmt::format(
"SSLConnectionUnexpectedlyClosedException({}): Text:{} Payload:{} Session:{}",
CId_, E.displayText(),
IncomingFrame.begin() == nullptr ? "" : IncomingFrame.begin(),
State_.sessionId));
KillConnection=true;
} catch (const Poco::Net::SSLException &E) {
poco_warning(Logger_,
fmt::format("SSLException({}): Text:{} Payload:{} Session:{}", CId_,
E.displayText(),
IncomingFrame.begin() == nullptr ? "" : IncomingFrame.begin(),
State_.sessionId));
KillConnection=true;
} catch (const Poco::Net::NetException &E) {
poco_warning(Logger_,
fmt::format("NetException({}): Text:{} Payload:{} Session:{}", CId_,
E.displayText(),
IncomingFrame.begin() == nullptr ? "" : IncomingFrame.begin(),
State_.sessionId));
KillConnection=true;
} catch (const Poco::IOException &E) {
poco_warning(Logger_,
fmt::format("IOException({}): Text:{} Payload:{} Session:{}", CId_,
E.displayText(),
IncomingFrame.begin() == nullptr ? "" : IncomingFrame.begin(),
State_.sessionId));
KillConnection=true;
} catch (const Poco::Exception &E) {
poco_warning(Logger_,
fmt::format("Exception({}): Text:{} Payload:{} Session:{}", CId_,
E.displayText(),
IncomingFrame.begin() == nullptr ? "" : IncomingFrame.begin(),
State_.sessionId));
KillConnection=true;
} catch (const std::exception &E) {
poco_warning(Logger_,
fmt::format("std::exception({}): Text:{} Payload:{} Session:{}", CId_,
E.what(),
IncomingFrame.begin() == nullptr ? "" : IncomingFrame.begin(),
State_.sessionId));
KillConnection=true;
} catch (...) {
poco_error(Logger_, fmt::format("UnknownException({}): Device must be disconnected. "
"Unknown exception. Session:{}",
CId_, State_.sessionId));
KillConnection=true;
}
if (!KillConnection && Errors_ < 10)
return;
poco_warning(Logger_, fmt::format("DISCONNECTING({}): ConnectionException: {} Errors: {}", CId_, KillConnection, Errors_ ));
EndConnection();
}
bool AP_WS_Connection::Send(const std::string &Payload) {
try {
size_t BytesSent = WS_->sendFrame(Payload.c_str(), (int)Payload.size());
/*
* There is a possibility to actually try and send data but the device is no longer
* listening. This code attempts to wait 5 seconds to see if the device is actually
* still listening. if the data is not acked under 5 seconds, then we consider that the
* data never made it or the device is disconnected somehow.
*/
#if defined(__APPLE__)
tcp_connection_info info;
int timeout = 4000;
auto expireAt = std::chrono::system_clock::now() + std::chrono::milliseconds(timeout);
do {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
socklen_t opt_len = sizeof(info);
getsockopt(WS_->impl()->sockfd(), SOL_SOCKET, TCP_CONNECTION_INFO, (void *)&info,
&opt_len);
} while (!info.tcpi_tfo_syn_data_acked && expireAt > std::chrono::system_clock::now());
if (!info.tcpi_tfo_syn_data_acked)
return false;
#else
tcp_info info;
int timeout = 4000;
auto expireAt = std::chrono::system_clock::now() + std::chrono::milliseconds(timeout);
do {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
socklen_t opt_len = sizeof(info);
getsockopt(WS_->impl()->sockfd(), SOL_TCP, TCP_INFO, (void *)&info, &opt_len);
} while (info.tcpi_unacked > 0 && expireAt > std::chrono::system_clock::now());
if (info.tcpi_unacked > 0) {
return false;
}
#endif
State_.TX += BytesSent;
AP_WS_Server()->AddTX(BytesSent);
return BytesSent == Payload.size();
} catch (const Poco::Exception &E) {
Logger_.log(E);
}
return false;
}
std::string Base64Encode(const unsigned char *buffer, std::size_t size) {
return Utils::base64encode(buffer, size);
}
std::string Base64Decode(const std::string &F) {
std::istringstream ifs(F);
Poco::Base64Decoder b64in(ifs);
std::ostringstream ofs;
Poco::StreamCopier::copyStream(b64in, ofs);
return ofs.str();
}
bool AP_WS_Connection::SendRadiusAuthenticationData(const unsigned char *buffer,
std::size_t size) {
Poco::JSON::Object Answer;
Answer.set(uCentralProtocol::RADIUS, uCentralProtocol::RADIUSAUTH);
Answer.set(uCentralProtocol::RADIUSDATA, Base64Encode(buffer, size));
std::ostringstream Payload;
Answer.stringify(Payload);
return Send(Payload.str());
}
bool AP_WS_Connection::SendRadiusAccountingData(const unsigned char *buffer, std::size_t size) {
Poco::JSON::Object Answer;
Answer.set(uCentralProtocol::RADIUS, uCentralProtocol::RADIUSACCT);
Answer.set(uCentralProtocol::RADIUSDATA, Base64Encode(buffer, size));
std::ostringstream Payload;
Answer.stringify(Payload);
return Send(Payload.str());
}
bool AP_WS_Connection::SendRadiusCoAData(const unsigned char *buffer, std::size_t size) {
Poco::JSON::Object Answer;
Answer.set(uCentralProtocol::RADIUS, uCentralProtocol::RADIUSCOA);
Answer.set(uCentralProtocol::RADIUSDATA, Base64Encode(buffer, size));
std::ostringstream Payload;
Answer.stringify(Payload);
return Send(Payload.str());
}
void AP_WS_Connection::ProcessIncomingRadiusData(const Poco::JSON::Object::Ptr &Doc) {
if (Doc->has(uCentralProtocol::RADIUSDATA)) {
auto Type = Doc->get(uCentralProtocol::RADIUS).toString();
if (Type == uCentralProtocol::RADIUSACCT) {
auto Data = Doc->get(uCentralProtocol::RADIUSDATA).toString();
auto DecodedData = Base64Decode(Data);
RADIUS_proxy_server()->SendAccountingData(SerialNumber_, DecodedData.c_str(),
DecodedData.size());
} else if (Type == uCentralProtocol::RADIUSAUTH) {
auto Data = Doc->get(uCentralProtocol::RADIUSDATA).toString();
auto DecodedData = Base64Decode(Data);
RADIUS_proxy_server()->SendAuthenticationData(SerialNumber_, DecodedData.c_str(),
DecodedData.size());
} else if (Type == uCentralProtocol::RADIUSCOA) {
auto Data = Doc->get(uCentralProtocol::RADIUSDATA).toString();
auto DecodedData = Base64Decode(Data);
RADIUS_proxy_server()->SendCoAData(SerialNumber_, DecodedData.c_str(),
DecodedData.size());
}
}
}
void AP_WS_Connection::SetLastStats(const std::string &LastStats) {
RawLastStats_ = LastStats;
try {
Poco::JSON::Parser P;
auto Stats = P.parse(LastStats).extract<Poco::JSON::Object::Ptr>();
State_.hasGPS = Stats->isObject("gps");
auto Unit = Stats->getObject("unit");
auto Memory = Unit->getObject("memory");
std::uint64_t TotalMemory = Memory->get("total");
std::uint64_t FreeMemory = Memory->get("free");
if (TotalMemory > 0) {
State_.memoryUsed =
(100.0 * ((double)TotalMemory - (double)FreeMemory)) / (double)TotalMemory;
}
if (Unit->isArray("load")) {
Poco::JSON::Array::Ptr Load = Unit->getArray("load");
if (Load->size() > 1) {
State_.load = Load->get(1);
}
}
if (Unit->isArray("temperature")) {
Poco::JSON::Array::Ptr Temperature = Unit->getArray("temperature");
if (Temperature->size() > 1) {
State_.temperature = Temperature->get(0);
}
}
} catch (const Poco::Exception &E) {
poco_error(Logger_, "Failed to parse last stats: " + E.displayText());
}
}
} // namespace OpenWifi