diff --git a/CMakeLists.txt b/CMakeLists.txt index bf856c0..5aa0ab3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,7 +107,7 @@ add_executable(owprov src/RESTAPI/RESTAPI_db_helpers.h src/JobController.cpp src/JobController.h src/JobRegistrations.cpp - src/storage/storage_jobs.cpp src/storage/storage_jobs.h) + src/storage/storage_jobs.cpp src/storage/storage_jobs.h src/WebSocketClientServer.cpp src/WebSocketClientServer.h) target_link_libraries(owprov PUBLIC ${Poco_LIBRARIES} ${MySQL_LIBRARIES} diff --git a/build b/build index b393560..978b4e8 100644 --- a/build +++ b/build @@ -1 +1 @@ -23 \ No newline at end of file +26 \ No newline at end of file diff --git a/src/Daemon.cpp b/src/Daemon.cpp index babcee3..d3ae96c 100644 --- a/src/Daemon.cpp +++ b/src/Daemon.cpp @@ -18,6 +18,7 @@ #include "framework/ConfigurationValidator.h" #include "SerialNumberCache.h" #include "JobController.h" +#include "WebSocketClientServer.h" namespace OpenWifi { class Daemon *Daemon::instance_ = nullptr; @@ -35,7 +36,8 @@ namespace OpenWifi { SerialNumberCache(), SecurityDBProxy(), AutoDiscovery(), - JobController() + JobController(), + WebSocketClientServer() }); } return instance_; diff --git a/src/RESTAPI/RESTAPI_webSocketServer.cpp b/src/RESTAPI/RESTAPI_webSocketServer.cpp index 6848c49..833556a 100644 --- a/src/RESTAPI/RESTAPI_webSocketServer.cpp +++ b/src/RESTAPI/RESTAPI_webSocketServer.cpp @@ -15,9 +15,31 @@ #include "Poco/Net/HTTPSClientSession.h" #include "SerialNumberCache.h" +#include "WebSocketClientServer.h" namespace OpenWifi { + void RESTAPI_webSocketServer::DoGet() { + try + { + if(Request->find("Upgrade") != Request->end() && Poco::icompare((*Request)["Upgrade"], "websocket") == 0) { + try + { + Poco::Net::WebSocket WS(*Request, *Response); + Logger_.information("WebSocket connection established."); + auto Id = MicroService::instance().CreateUUID(); + new WebSocketClient(WS,Id,Logger_); + } + catch (...) { + std::cout << "Cannot create websocket client..." << std::endl; + } + } + } catch(...) { + std::cout << "Cannot upgrade connection..." << std::endl; + } + } + +/* void RESTAPI_webSocketServer::DoGet() { // try and upgrade this session to websocket... @@ -112,7 +134,7 @@ namespace OpenWifi { } } } - +*/ void RESTAPI_webSocketServer::Process(const Poco::JSON::Object::Ptr &O, std::string &Answer, bool &Done ) { try { if (O->has("command")) { @@ -136,7 +158,7 @@ namespace OpenWifi { Poco::JSON::Stringifier::stringify(AO, SS); Answer = SS.str(); } - } else if(GeoCodeEnabled_ && Command == "address_completion" && O->has("address")) { + } else if (GeoCodeEnabled_ && Command == "address_completion" && O->has("address")) { auto Address = O->get("address").toString(); Answer = GoogleGeoCodeCall(Address); } else if (Command=="exit") { diff --git a/src/WebSocketClientServer.cpp b/src/WebSocketClientServer.cpp new file mode 100644 index 0000000..042e30a --- /dev/null +++ b/src/WebSocketClientServer.cpp @@ -0,0 +1,170 @@ +// +// Created by stephane bourque on 2021-11-01. +// + +#include "WebSocketClientServer.h" +#include "SerialNumberCache.h" + +namespace OpenWifi { + void WebSocketClientServer::run() { + Running_ = true ; + while(Running_) { + Poco::Thread::trySleep(2000); + + if(!Running_) + break; + } + }; + + int WebSocketClientServer::Start() { + GoogleApiKey_ = MicroService::instance().ConfigGetString("google.apikey",""); + GeoCodeEnabled_ = !GoogleApiKey_.empty(); + ReactorPool_ = std::make_unique(Poco::Environment::processorCount()); + Thr_.start(*this); + return 0; + }; + + void WebSocketClientServer::Stop() { + if(Running_) { + Running_ = false; + Thr_.wakeUp(); + Thr_.join(); + } + }; + + void WebSocketClient::OnSocketError(const Poco::AutoPtr &pNf) { + delete this; + } + + void WebSocketClient::OnSocketReadable(const Poco::AutoPtr &pNf) { + int flags; + int n; + bool Done=false; + Poco::Buffer IncomingFrame(0); + n = WS_->receiveFrame(IncomingFrame, flags); + auto Op = flags & Poco::Net::WebSocket::FRAME_OP_BITMASK; + switch(Op) { + case Poco::Net::WebSocket::FRAME_OP_PING: { + WS_->sendFrame("", 0, + (int)Poco::Net::WebSocket::FRAME_OP_PONG | + (int)Poco::Net::WebSocket::FRAME_FLAG_FIN); + } + break; + case Poco::Net::WebSocket::FRAME_OP_PONG: { + } + break; + case Poco::Net::WebSocket::FRAME_OP_TEXT: { + IncomingFrame.append(0); + if(!Authenticated_) { + std::string Frame{IncomingFrame.begin()}; + auto Tokens = Utils::Split(Frame,':'); + if(Tokens.size()==2 && AuthClient()->IsTokenAuthorized(Tokens[1], UserInfo_)) { + Authenticated_=true; + std::string S{"Welcome! Bienvenue! Bienvenidos!"}; + WS_->sendFrame(S.c_str(),S.size()); + } else { + std::string S{"Invalid token. Closing connection."}; + WS_->sendFrame(S.c_str(),S.size()); + Done=true; + } + + } else { + try { + Poco::JSON::Parser P; + auto Obj = P.parse(IncomingFrame.begin()) + .extract(); + std::string Answer; + Process(Obj, Answer, Done ); + if (!Answer.empty()) + WS_->sendFrame(Answer.c_str(), Answer.size()); + else { + WS_->sendFrame("{}", 2); + } + } catch (const Poco::JSON::JSONException & E) { + Logger_.log(E); + } + } + } + break; + default: + { + + } + } + + if(Done) + delete this; + } + + void WebSocketClient::OnSocketShutdown(const Poco::AutoPtr &pNf) { + delete this; + } + + void WebSocketClient::Process(const Poco::JSON::Object::Ptr &O, std::string &Answer, bool &Done ) { + try { + if (O->has("command")) { + auto Command = O->get("command").toString(); + if (Command == "serial_number_search" && O->has("serial_prefix")) { + auto Prefix = O->get("serial_prefix").toString(); + uint64_t HowMany = 32; + if (O->has("howMany")) + HowMany = O->get("howMany"); + Logger_.information(Poco::format("serial_number_search: %s", Prefix)); + if (!Prefix.empty() && Prefix.length() < 13) { + std::vector Numbers; + SerialNumberCache()->FindNumbers(Prefix, 50, Numbers); + Poco::JSON::Array A; + for (const auto &i : Numbers) + A.add(Utils::int_to_hex(i)); + Poco::JSON::Object AO; + AO.set("serialNumbers", A); + AO.set("command","serial_number_search"); + std::ostringstream SS; + Poco::JSON::Stringifier::stringify(AO, SS); + Answer = SS.str(); + } + } else if (WebSocketClientServer()->GeoCodeEnabled() && Command == "address_completion" && O->has("address")) { + auto Address = O->get("address").toString(); + Answer = GoogleGeoCodeCall(Address); + } else if (Command=="exit") { + Answer = R"lit({ "closing" : "Goodbye! Aurevoir! Hasta la vista!" })lit"; + Done = true; + } else { + Answer = std::string{R"lit({ "error" : "invalid command" })lit"}; + } + } + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + } + + std::string WebSocketClient::GoogleGeoCodeCall(const std::string &A) { + try { + std::string URI = { "https://maps.googleapis.com/maps/api/geocode/json"}; + Poco::URI uri(URI); + + uri.addQueryParameter("address",A); + uri.addQueryParameter("key", WebSocketClientServer()->GoogleApiKey()); + + Poco::Net::HTTPSClientSession session(uri.getHost(), uri.getPort()); + Poco::Net::HTTPRequest req(Poco::Net::HTTPRequest::HTTP_GET, uri.getPathAndQuery(), Poco::Net::HTTPMessage::HTTP_1_1); + session.sendRequest(req); + Poco::Net::HTTPResponse res; + std::istream& rs = session.receiveResponse(res); + + if(res.getStatus()==Poco::Net::HTTPResponse::HTTP_OK) { + std::ostringstream os; + Poco::StreamCopier::copyStream(rs,os); + return os.str(); + } else { + std::ostringstream os; + Poco::StreamCopier::copyStream(rs,os); + return R"lit({ "error: )lit" + os.str() + R"lit( })lit"; + } + } catch(...) { + + } + return "{ \"error\" : \"No call made\" }"; + } + +} \ No newline at end of file diff --git a/src/WebSocketClientServer.h b/src/WebSocketClientServer.h new file mode 100644 index 0000000..6e7b362 --- /dev/null +++ b/src/WebSocketClientServer.h @@ -0,0 +1,142 @@ +// +// Created by stephane bourque on 2021-11-01. +// + +#ifndef OWPROV_WEBSOCKETCLIENTSERVER_H +#define OWPROV_WEBSOCKETCLIENTSERVER_H + +#include "framework/MicroService.h" +#include "Poco/Net/SocketReactor.h" +#include "Poco/Net/WebSocket.h" +#include "Poco/Environment.h" +#include "Poco/NObserver.h" +#include "Poco/Net/SocketNotification.h" + +#include "RESTObjects/RESTAPI_SecurityObjects.h" + +namespace OpenWifi { + + class MyParallelSocketReactor { + public: + explicit MyParallelSocketReactor(unsigned NumReactors = Poco::Environment::processorCount()) : + NumReactors_(NumReactors) { + Reactors_.reserve(NumReactors_); + for(int i=0;i(); + Reactors_[i]->run(); + } + } + + ~MyParallelSocketReactor() { + for(const auto &i:Reactors_) + i->stop(); + } + + Poco::Net::SocketReactor & Reactor() { + return *Reactors_[ rand() % NumReactors_ ]; + } + + private: + unsigned NumReactors_; + std::vector> Reactors_; + }; + + class WebSocketClient; + + class WebSocketClientServer : public SubSystemServer, Poco::Runnable { + public: + static WebSocketClientServer *instance() { + if(instance_== nullptr) + instance_ = new WebSocketClientServer; + return instance_; + } + + int Start() override; + void Stop() override; + void run() override; + inline MyParallelSocketReactor & ReactorPool() { return *ReactorPool_; } + inline bool Register( WebSocketClient * Client, const std::string &Id) { + std::lock_guard G(Mutex_); + Clients_[Id] = Client; + return true; + } + + inline void UnRegister(const std::string &Id) { + std::lock_guard G(Mutex_); + Clients_.erase(Id); + + } + inline bool GeoCodeEnabled() const { return GeoCodeEnabled_; } + [[nodiscard]] inline const std::string GoogleApiKey() { return GoogleApiKey_; } + + private: + static WebSocketClientServer * instance_; + std::atomic_bool Running_=false; + Poco::Thread Thr_; + std::unique_ptr ReactorPool_; + bool GeoCodeEnabled_=false; + std::string GoogleApiKey_; + std::map Clients_; + + WebSocketClientServer() noexcept: + SubSystemServer("WebSocketClientServer", "WSCLNT-SVR", "websocketclients") + { + } + }; + + inline WebSocketClientServer * WebSocketClientServer() { return WebSocketClientServer::instance(); } + inline class WebSocketClientServer *WebSocketClientServer::instance_ = nullptr; + + class WebSocketClient { + public: + explicit WebSocketClient( Poco::Net::WebSocket & WS , const std::string Id, Poco::Logger & L) : + Reactor_(WebSocketClientServer()->ReactorPool().Reactor()), + Id_(std::move(Id)), + Logger_(L) { + WS_ = std::make_unique(WS); + Reactor_.addEventHandler(*WS_, + Poco::NObserver( + *this, &WebSocketClient::OnSocketReadable)); + Reactor_.addEventHandler(*WS_, + Poco::NObserver( + *this, &WebSocketClient::OnSocketShutdown)); + Reactor_.addEventHandler(*WS_, + Poco::NObserver( + *this, &WebSocketClient::OnSocketError)); + } + + ~WebSocketClient() { + Reactor_.removeEventHandler(*WS_, + Poco::NObserver(*this,&WebSocketClient::OnSocketReadable)); + Reactor_.removeEventHandler(*WS_, + Poco::NObserver(*this,&WebSocketClient::OnSocketShutdown)); + Reactor_.removeEventHandler(*WS_, + Poco::NObserver(*this,&WebSocketClient::OnSocketError)); + (*WS_).shutdown(); + (*WS_).close(); + } + + [[nodiscard]] inline const std::string & Id() { return Id_; }; + + std::string GoogleGeoCodeCall(const std::string &A); + + private: + std::unique_ptr WS_; + Poco::Net::SocketReactor & Reactor_; + std::string Id_; + Poco::Logger & Logger_; + bool Authenticated_=false; + SecurityObjects::UserInfoAndPolicy UserInfo_; + + void Process(const Poco::JSON::Object::Ptr &O, std::string &Answer, bool &Done ); + void OnSocketReadable(const Poco::AutoPtr& pNf); + void OnSocketShutdown(const Poco::AutoPtr& pNf); + void OnSocketError(const Poco::AutoPtr& pNf); + }; + +} + +#endif //OWPROV_WEBSOCKETCLIENTSERVER_H