mirror of
https://github.com/Telecominfraproject/wlan-cloud-analytics.git
synced 2026-01-27 10:22:33 +00:00
Signed-off-by: stephb9959 <stephane.bourque@gmail.com>
This commit is contained in:
76
src/framework/ALBserver.cpp
Normal file
76
src/framework/ALBserver.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include "ALBserver.h"
|
||||
|
||||
#include "framework/utils.h"
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
ALBRequestHandler::ALBRequestHandler(Poco::Logger & L, uint64_t id)
|
||||
: Logger_(L), id_(id)
|
||||
{
|
||||
}
|
||||
|
||||
void ALBRequestHandler::handleRequest([[maybe_unused]] Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response) {
|
||||
Utils::SetThreadName("alb-request");
|
||||
try {
|
||||
if((id_ % 100) == 0) {
|
||||
poco_debug(Logger_,fmt::format("ALB-REQUEST({}): ALB Request {}.",
|
||||
Request.clientAddress().toString(), id_));
|
||||
}
|
||||
Response.setChunkedTransferEncoding(true);
|
||||
Response.setContentType("text/html");
|
||||
Response.setDate(Poco::Timestamp());
|
||||
Response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
|
||||
Response.setKeepAlive(true);
|
||||
Response.set("Connection", "keep-alive");
|
||||
Response.setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
|
||||
std::ostream &Answer = Response.send();
|
||||
Answer << "process Alive and kicking!";
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ALBRequestHandlerFactory::ALBRequestHandlerFactory(Poco::Logger & L):
|
||||
Logger_(L) {
|
||||
}
|
||||
|
||||
ALBRequestHandler* ALBRequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest& request) {
|
||||
if (request.getURI() == "/")
|
||||
return new ALBRequestHandler(Logger_, req_id_++);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ALBHealthCheckServer::ALBHealthCheckServer() :
|
||||
SubSystemServer("ALBHealthCheckServer", "ALB-SVR", "alb")
|
||||
{
|
||||
}
|
||||
|
||||
int ALBHealthCheckServer::Start() {
|
||||
if(MicroServiceConfigGetBool("alb.enable",false)) {
|
||||
Running_=true;
|
||||
Port_ = (int)MicroServiceConfigGetInt("alb.port",15015);
|
||||
Socket_ = std::make_unique<Poco::Net::ServerSocket>(Port_);
|
||||
auto Params = new Poco::Net::HTTPServerParams;
|
||||
Params->setName("ws:alb");
|
||||
Server_ = std::make_unique<Poco::Net::HTTPServer>(new ALBRequestHandlerFactory(Logger()), *Socket_, Params);
|
||||
Server_->start();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ALBHealthCheckServer::Stop() {
|
||||
poco_information(Logger(),"Stopping...");
|
||||
if(Running_)
|
||||
Server_->stopAll(true);
|
||||
poco_information(Logger(),"Stopped...");
|
||||
}
|
||||
|
||||
} // namespace OpenWifi
|
||||
63
src/framework/ALBserver.h
Normal file
63
src/framework/ALBserver.h
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "framework/SubSystemServer.h"
|
||||
|
||||
#include "Poco/Net/HTTPRequestHandler.h"
|
||||
#include "Poco/Net/HTTPServerRequest.h"
|
||||
#include "Poco/Net/HTTPServerResponse.h"
|
||||
#include "Poco/Net/HTTPRequestHandlerFactory.h"
|
||||
#include "Poco/Net/HTTPServer.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
class ALBRequestHandler: public Poco::Net::HTTPRequestHandler
|
||||
/// Return a HTML document with the current date and time.
|
||||
{
|
||||
public:
|
||||
explicit ALBRequestHandler(Poco::Logger & L, uint64_t id);
|
||||
void handleRequest([[maybe_unused]] Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response) override;
|
||||
|
||||
private:
|
||||
Poco::Logger & Logger_;
|
||||
uint64_t id_;
|
||||
};
|
||||
|
||||
class ALBRequestHandlerFactory: public Poco::Net::HTTPRequestHandlerFactory
|
||||
{
|
||||
public:
|
||||
explicit ALBRequestHandlerFactory(Poco::Logger & L);
|
||||
ALBRequestHandler* createRequestHandler(const Poco::Net::HTTPServerRequest& request) override;
|
||||
|
||||
private:
|
||||
Poco::Logger &Logger_;
|
||||
inline static std::atomic_uint64_t req_id_=1;
|
||||
};
|
||||
|
||||
class ALBHealthCheckServer : public SubSystemServer {
|
||||
public:
|
||||
ALBHealthCheckServer();
|
||||
|
||||
static auto instance() {
|
||||
static auto instance = new ALBHealthCheckServer;
|
||||
return instance;
|
||||
}
|
||||
|
||||
int Start() override;
|
||||
|
||||
void Stop() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Poco::Net::HTTPServer> Server_;
|
||||
std::unique_ptr<Poco::Net::ServerSocket> Socket_;
|
||||
int Port_ = 0;
|
||||
mutable std::atomic_bool Running_=false;
|
||||
};
|
||||
|
||||
inline auto ALBHealthCheckServer() { return ALBHealthCheckServer::instance(); }
|
||||
|
||||
} // namespace OpenWifi
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "framework/MicroService.h"
|
||||
#include "Poco/Logger.h"
|
||||
#include "Poco/JSON/Parser.h"
|
||||
#include "Poco/Net/HTTPServerRequest.h"
|
||||
#include "Poco/Net/HTTPServerResponse.h"
|
||||
#include "Poco/Net/HTTPSClientSession.h"
|
||||
#include "Poco/URI.h"
|
||||
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
inline void API_Proxy( Poco::Logger &Logger,
|
||||
@@ -15,7 +21,7 @@ namespace OpenWifi {
|
||||
const char * PathRewrite,
|
||||
uint64_t msTimeout_ = 10000 ) {
|
||||
try {
|
||||
auto Services = MicroService::instance().GetServices(ServiceType);
|
||||
auto Services = MicroServiceGetServices(ServiceType);
|
||||
for(auto const &Svc:Services) {
|
||||
Poco::URI SourceURI(Request->getURI());
|
||||
Poco::URI DestinationURI(Svc.PrivateEndPoint);
|
||||
@@ -35,7 +41,7 @@ namespace OpenWifi {
|
||||
ProxyRequest.add("Authorization", Request->get("Authorization"));
|
||||
} else {
|
||||
ProxyRequest.add("X-API-KEY", Svc.AccessKey);
|
||||
ProxyRequest.add("X-INTERNAL-NAME", MicroService::instance().PublicEndPoint());
|
||||
ProxyRequest.add("X-INTERNAL-NAME", MicroServicePublicEndPoint());
|
||||
}
|
||||
|
||||
if(Request->getMethod() == Poco::Net::HTTPRequest::HTTP_DELETE) {
|
||||
|
||||
102
src/framework/AppServiceRegistry.h
Normal file
102
src/framework/AppServiceRegistry.h
Normal file
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include "Poco/StreamCopier.h"
|
||||
#include "Poco/File.h"
|
||||
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
|
||||
class AppServiceRegistry {
|
||||
public:
|
||||
AppServiceRegistry() {
|
||||
FileName = MicroServiceDataDirectory() + "/registry.json";
|
||||
Poco::File F(FileName);
|
||||
|
||||
try {
|
||||
if(F.exists()) {
|
||||
std::ostringstream OS;
|
||||
std::ifstream IF(FileName);
|
||||
Poco::StreamCopier::copyStream(IF, OS);
|
||||
Registry_ = nlohmann::json::parse(OS.str());
|
||||
}
|
||||
} catch (...) {
|
||||
Registry_ = nlohmann::json::parse("{}");
|
||||
}
|
||||
}
|
||||
|
||||
static AppServiceRegistry & instance() {
|
||||
static auto instance_= new AppServiceRegistry;
|
||||
return *instance_;
|
||||
}
|
||||
|
||||
inline ~AppServiceRegistry() {
|
||||
Save();
|
||||
}
|
||||
|
||||
inline void Save() {
|
||||
std::istringstream IS( to_string(Registry_));
|
||||
std::ofstream OF;
|
||||
OF.open(FileName,std::ios::binary | std::ios::trunc);
|
||||
Poco::StreamCopier::copyStream(IS, OF);
|
||||
}
|
||||
|
||||
inline void Set(const char *Key, uint64_t Value ) {
|
||||
Registry_[Key] = Value;
|
||||
Save();
|
||||
}
|
||||
|
||||
inline void Set(const char *Key, const std::string &Value ) {
|
||||
Registry_[Key] = Value;
|
||||
Save();
|
||||
}
|
||||
|
||||
inline void Set(const char *Key, bool Value ) {
|
||||
Registry_[Key] = Value;
|
||||
Save();
|
||||
}
|
||||
|
||||
inline bool Get(const char *Key, bool & Value ) {
|
||||
if(Registry_[Key].is_boolean()) {
|
||||
Value = Registry_[Key].get<bool>();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool Get(const char *Key, uint64_t & Value ) {
|
||||
if(Registry_[Key].is_number_unsigned()) {
|
||||
Value = Registry_[Key].get<uint64_t>();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool Get(const char *Key, std::string & Value ) {
|
||||
if(Registry_[Key].is_string()) {
|
||||
Value = Registry_[Key].get<std::string>();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string FileName;
|
||||
nlohmann::json Registry_;
|
||||
};
|
||||
|
||||
inline auto AppServiceRegistry() { return AppServiceRegistry::instance(); }
|
||||
|
||||
}
|
||||
71
src/framework/AuthClient.cpp
Normal file
71
src/framework/AuthClient.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include "Poco/Net/HTTPServerResponse.h"
|
||||
|
||||
#include "framework/AuthClient.h"
|
||||
#include "framework/MicroServiceNames.h"
|
||||
#include "framework/OpenAPIRequests.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
bool AuthClient::RetrieveTokenInformation(const std::string & SessionToken,
|
||||
SecurityObjects::UserInfoAndPolicy & UInfo,
|
||||
std::uint64_t TID,
|
||||
bool & Expired, bool & Contacted, bool Sub) {
|
||||
try {
|
||||
Types::StringPairVec QueryData;
|
||||
QueryData.push_back(std::make_pair("token",SessionToken));
|
||||
OpenAPIRequestGet Req( uSERVICE_SECURITY,
|
||||
Sub ? "/api/v1/validateSubToken" : "/api/v1/validateToken",
|
||||
QueryData,
|
||||
10000);
|
||||
Poco::JSON::Object::Ptr Response;
|
||||
|
||||
auto StatusCode = Req.Do(Response);
|
||||
if(StatusCode==Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT) {
|
||||
Contacted = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
Contacted = true;
|
||||
if(StatusCode==Poco::Net::HTTPServerResponse::HTTP_OK) {
|
||||
if(Response->has("tokenInfo") && Response->has("userInfo")) {
|
||||
UInfo.from_json(Response);
|
||||
if(IsTokenExpired(UInfo.webtoken)) {
|
||||
Expired = true;
|
||||
return false;
|
||||
}
|
||||
Expired = false;
|
||||
Cache_.update(SessionToken, UInfo);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
poco_error(Logger(),fmt::format("Failed to retrieve token={} for TID={}", SessionToken, TID));
|
||||
}
|
||||
Expired = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AuthClient::IsAuthorized(const std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo,
|
||||
std::uint64_t TID,
|
||||
bool & Expired, bool & Contacted, bool Sub) {
|
||||
auto User = Cache_.get(SessionToken);
|
||||
if(!User.isNull()) {
|
||||
if(IsTokenExpired(User->webtoken)) {
|
||||
Expired = true;
|
||||
return false;
|
||||
}
|
||||
Expired = false;
|
||||
UInfo = *User;
|
||||
return true;
|
||||
}
|
||||
return RetrieveTokenInformation(SessionToken, UInfo, TID, Expired, Contacted, Sub);
|
||||
}
|
||||
|
||||
} // namespace OpenWifi
|
||||
59
src/framework/AuthClient.h
Normal file
59
src/framework/AuthClient.h
Normal file
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "framework/SubSystemServer.h"
|
||||
#include "RESTObjects/RESTAPI_SecurityObjects.h"
|
||||
#include "Poco/ExpireLRUCache.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
class AuthClient : public SubSystemServer {
|
||||
public:
|
||||
explicit AuthClient() noexcept:
|
||||
SubSystemServer("Authentication", "AUTH-CLNT", "authentication")
|
||||
{
|
||||
}
|
||||
|
||||
static auto instance() {
|
||||
static auto instance_ = new AuthClient;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
inline int Start() override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline void Stop() override {
|
||||
poco_information(Logger(),"Stopping...");
|
||||
std::lock_guard G(Mutex_);
|
||||
Cache_.clear();
|
||||
poco_information(Logger(),"Stopped...");
|
||||
}
|
||||
|
||||
inline void RemovedCachedToken(const std::string &Token) {
|
||||
Cache_.remove(Token);
|
||||
}
|
||||
|
||||
inline static bool IsTokenExpired(const SecurityObjects::WebToken &T) {
|
||||
return ((T.expires_in_+T.created_) < OpenWifi::Now());
|
||||
}
|
||||
|
||||
bool RetrieveTokenInformation(const std::string & SessionToken,
|
||||
SecurityObjects::UserInfoAndPolicy & UInfo,
|
||||
std::uint64_t TID,
|
||||
bool & Expired, bool & Contacted, bool Sub=false);
|
||||
bool IsAuthorized(const std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo,
|
||||
std::uint64_t TID,
|
||||
bool & Expired, bool & Contacted, bool Sub = false);
|
||||
|
||||
private:
|
||||
Poco::ExpireLRUCache<std::string,OpenWifi::SecurityObjects::UserInfoAndPolicy> Cache_{512,1200000 };
|
||||
};
|
||||
|
||||
inline auto AuthClient() { return AuthClient::instance(); }
|
||||
|
||||
} // namespace OpenWifi
|
||||
|
||||
155
src/framework/CIDR.h
Normal file
155
src/framework/CIDR.h
Normal file
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Poco/Net/IPAddress.h"
|
||||
#include "Poco/StringTokenizer.h"
|
||||
|
||||
#include "framework/OpenWifiTypes.h"
|
||||
|
||||
namespace OpenWifi::CIDR {
|
||||
|
||||
static bool cidr_match(const in_addr &addr, const in_addr &net, uint8_t bits) {
|
||||
if (bits == 0) {
|
||||
return true;
|
||||
}
|
||||
return !((addr.s_addr ^ net.s_addr) & htonl(0xFFFFFFFFu << (32 - bits)));
|
||||
}
|
||||
|
||||
static bool cidr6_match(const in6_addr &address, const in6_addr &network, uint8_t bits) {
|
||||
#ifdef __linux__
|
||||
const uint32_t *a = address.s6_addr32;
|
||||
const uint32_t *n = network.s6_addr32;
|
||||
#else
|
||||
const uint32_t *a = address.__u6_addr.__u6_addr32;
|
||||
const uint32_t *n = network.__u6_addr.__u6_addr32;
|
||||
#endif
|
||||
int bits_whole, bits_incomplete;
|
||||
bits_whole = bits >> 5; // number of whole u32
|
||||
bits_incomplete = bits & 0x1F; // number of bits in incomplete u32
|
||||
if (bits_whole) {
|
||||
if (memcmp(a, n, bits_whole << 2) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (bits_incomplete) {
|
||||
uint32_t mask = htonl((0xFFFFFFFFu) << (32 - bits_incomplete));
|
||||
if ((a[bits_whole] ^ n[bits_whole]) & mask) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ConvertStringToLong(const char *S, unsigned long &L) {
|
||||
char *end;
|
||||
L = std::strtol(S, &end, 10);
|
||||
return end != S;
|
||||
}
|
||||
|
||||
static bool CidrIPinRange(const Poco::Net::IPAddress &IP, const std::string &Range) {
|
||||
Poco::StringTokenizer TimeTokens(Range, "/", Poco::StringTokenizer::TOK_TRIM);
|
||||
|
||||
Poco::Net::IPAddress RangeIP;
|
||||
if (Poco::Net::IPAddress::tryParse(TimeTokens[0], RangeIP)) {
|
||||
if (TimeTokens.count() == 2) {
|
||||
if (RangeIP.family() == Poco::Net::IPAddress::IPv4) {
|
||||
unsigned long MaskLength;
|
||||
if (ConvertStringToLong(TimeTokens[1].c_str(), MaskLength)) {
|
||||
return cidr_match(*static_cast<const in_addr *>(RangeIP.addr()),
|
||||
*static_cast<const in_addr *>(IP.addr()), MaskLength);
|
||||
}
|
||||
} else if (RangeIP.family() == Poco::Net::IPAddress::IPv6) {
|
||||
unsigned long MaskLength;
|
||||
if (ConvertStringToLong(TimeTokens[1].c_str(), MaskLength)) {
|
||||
return cidr6_match(*static_cast<const in6_addr *>(RangeIP.addr()),
|
||||
*static_cast<const in6_addr *>(IP.addr()), MaskLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Ranges can be a single IP, of IP1-IP2, of A set of IPs: IP1,IP2,IP3, or a cidr IP/24
|
||||
// These can work for IPv6 too...
|
||||
//
|
||||
static bool ValidateRange(const std::string &R) {
|
||||
|
||||
auto Tokens = Poco::StringTokenizer(R, "-");
|
||||
if (Tokens.count() == 2) {
|
||||
Poco::Net::IPAddress a, b;
|
||||
if (!Poco::Net::IPAddress::tryParse(Tokens[0], a) &&
|
||||
Poco::Net::IPAddress::tryParse(Tokens[1], b))
|
||||
return false;
|
||||
return a.family() == b.family();
|
||||
}
|
||||
|
||||
Tokens = Poco::StringTokenizer(R, ",");
|
||||
if (Tokens.count() > 1) {
|
||||
return std::all_of(Tokens.begin(), Tokens.end(), [](const std::string &A) {
|
||||
Poco::Net::IPAddress a;
|
||||
return Poco::Net::IPAddress::tryParse(A, a);
|
||||
});
|
||||
}
|
||||
|
||||
Tokens = Poco::StringTokenizer(R, "/");
|
||||
if (Tokens.count() == 2) {
|
||||
Poco::Net::IPAddress a;
|
||||
if (!Poco::Net::IPAddress::tryParse(Tokens[0], a))
|
||||
return false;
|
||||
if (std::atoi(Tokens[1].c_str()) == 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Poco::Net::IPAddress a;
|
||||
return Poco::Net::IPAddress::tryParse(R, a);
|
||||
}
|
||||
|
||||
static bool IpInRange(const Poco::Net::IPAddress &target, const std::string &R) {
|
||||
|
||||
auto Tokens = Poco::StringTokenizer(R, "-");
|
||||
if (Tokens.count() == 2) {
|
||||
auto a = Poco::Net::IPAddress::parse(Tokens[0]);
|
||||
auto b = Poco::Net::IPAddress::parse(Tokens[1]);
|
||||
if (target.family() != a.family())
|
||||
return false;
|
||||
return (a <= target && b >= target);
|
||||
}
|
||||
|
||||
Tokens = Poco::StringTokenizer(R, ",");
|
||||
if (Tokens.count() > 1) {
|
||||
return std::any_of(Tokens.begin(), Tokens.end(), [target](const std::string &Element) {
|
||||
return Poco::Net::IPAddress::parse(Element) == target;
|
||||
});
|
||||
}
|
||||
|
||||
Tokens = Poco::StringTokenizer(R, "/");
|
||||
if (Tokens.count() == 2) {
|
||||
return CidrIPinRange(target, R);
|
||||
}
|
||||
|
||||
return Poco::Net::IPAddress::parse(R) == target;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IpInRanges(const std::string &IP, const Types::StringVec &R) {
|
||||
Poco::Net::IPAddress Target;
|
||||
|
||||
if (!Poco::Net::IPAddress::tryParse(IP, Target))
|
||||
return false;
|
||||
|
||||
return std::any_of(cbegin(R), cend(R),
|
||||
[Target](const std::string &i) { return IpInRange(Target, i); });
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool ValidateIpRanges(const Types::StringVec &Ranges) {
|
||||
return std::all_of(cbegin(Ranges), cend(Ranges), ValidateRange);
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,15 @@
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
|
||||
#include "framework/MicroService.h"
|
||||
#include "ConfigurationValidator.h"
|
||||
#include "framework/CountryCodes.h"
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
#include "framework/utils.h"
|
||||
|
||||
#include "Poco/StringTokenizer.h"
|
||||
#include "Poco/URI.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
@@ -2625,7 +2630,7 @@ static json DefaultUCentralSchema = R"(
|
||||
return;
|
||||
|
||||
std::string GitSchema;
|
||||
if(MicroService::instance().ConfigGetBool("ucentral.datamodel.internal",true)) {
|
||||
if(MicroServiceConfigGetBool("ucentral.datamodel.internal",true)) {
|
||||
RootSchema_ = DefaultUCentralSchema;
|
||||
Logger().information("Using uCentral validation from built-in default.");
|
||||
Initialized_ = Working_ = true;
|
||||
@@ -2633,12 +2638,12 @@ static json DefaultUCentralSchema = R"(
|
||||
}
|
||||
|
||||
try {
|
||||
auto GitURI = MicroService::instance().ConfigGetString("ucentral.datamodel.uri",GitUCentralJSONSchemaFile);
|
||||
auto GitURI = MicroServiceConfigGetString("ucentral.datamodel.uri",GitUCentralJSONSchemaFile);
|
||||
if(Utils::wgets(GitURI, GitSchema)) {
|
||||
RootSchema_ = json::parse(GitSchema);
|
||||
Logger().information("Using uCentral validation schema from GIT.");
|
||||
} else {
|
||||
std::string FileName{ MicroService::instance().DataDir() + "/ucentral.schema.json" };
|
||||
std::string FileName{ MicroServiceDataDirectory() + "/ucentral.schema.json" };
|
||||
std::ifstream input(FileName);
|
||||
std::stringstream schema_file;
|
||||
schema_file << input.rdbuf();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <nlohmann/json-schema.hpp>
|
||||
#include "framework/MicroService.h"
|
||||
#include "framework/SubSystemServer.h"
|
||||
|
||||
using nlohmann::json;
|
||||
using nlohmann::json_schema::json_validator;
|
||||
|
||||
48
src/framework/EventBusManager.cpp
Normal file
48
src/framework/EventBusManager.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-26.
|
||||
//
|
||||
|
||||
#include "framework/EventBusManager.h"
|
||||
#include "framework/KafkaManager.h"
|
||||
#include "framework/utils.h"
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
EventBusManager::EventBusManager(Poco::Logger &L) :
|
||||
Logger_(L) {
|
||||
}
|
||||
|
||||
void EventBusManager::run() {
|
||||
Running_ = true;
|
||||
Utils::SetThreadName("fmwk:EventMgr");
|
||||
auto Msg = MicroServiceMakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_JOIN);
|
||||
KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,MicroServicePrivateEndPoint(),Msg, false);
|
||||
while(Running_) {
|
||||
Poco::Thread::trySleep((unsigned long)MicroServiceDaemonBusTimer());
|
||||
if(!Running_)
|
||||
break;
|
||||
Msg = MicroServiceMakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE);
|
||||
KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,MicroServicePrivateEndPoint(),Msg, false);
|
||||
}
|
||||
Msg = MicroServiceMakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_LEAVE);
|
||||
KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,MicroServicePrivateEndPoint(),Msg, false);
|
||||
};
|
||||
|
||||
void EventBusManager::Start() {
|
||||
if(KafkaManager()->Enabled()) {
|
||||
Thread_.start(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void EventBusManager::Stop() {
|
||||
if(KafkaManager()->Enabled()) {
|
||||
poco_information(Logger(),"Stopping...");
|
||||
Running_ = false;
|
||||
Thread_.wakeUp();
|
||||
Thread_.join();
|
||||
poco_information(Logger(),"Stopped...");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace OpenWifi
|
||||
28
src/framework/EventBusManager.h
Normal file
28
src/framework/EventBusManager.h
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-26.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Poco/Runnable.h"
|
||||
#include "Poco/Logger.h"
|
||||
#include "Poco/Thread.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
class EventBusManager : public Poco::Runnable {
|
||||
public:
|
||||
explicit EventBusManager(Poco::Logger &L);
|
||||
void run() final;
|
||||
void Start();
|
||||
void Stop();
|
||||
inline Poco::Logger & Logger() { return Logger_; }
|
||||
|
||||
private:
|
||||
mutable std::atomic_bool Running_ = false;
|
||||
Poco::Thread Thread_;
|
||||
Poco::Logger &Logger_;
|
||||
};
|
||||
|
||||
} // namespace OpenWifi
|
||||
|
||||
356
src/framework/KafkaManager.cpp
Normal file
356
src/framework/KafkaManager.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include "KafkaManager.h"
|
||||
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
void KafkaLoggerFun([[maybe_unused]] cppkafka::KafkaHandleBase & handle, int level, const std::string & facility, const std::string &message) {
|
||||
switch ((cppkafka::LogLevel) level) {
|
||||
case cppkafka::LogLevel::LogNotice: {
|
||||
poco_notice(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
||||
}
|
||||
break;
|
||||
case cppkafka::LogLevel::LogDebug: {
|
||||
poco_debug(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
||||
}
|
||||
break;
|
||||
case cppkafka::LogLevel::LogInfo: {
|
||||
poco_information(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
||||
}
|
||||
break;
|
||||
case cppkafka::LogLevel::LogWarning: {
|
||||
poco_warning(KafkaManager()->Logger(), fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
||||
}
|
||||
break;
|
||||
case cppkafka::LogLevel::LogAlert:
|
||||
case cppkafka::LogLevel::LogCrit: {
|
||||
poco_critical(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
||||
}
|
||||
break;
|
||||
case cppkafka::LogLevel::LogErr:
|
||||
case cppkafka::LogLevel::LogEmerg:
|
||||
default: {
|
||||
poco_error(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void KafkaErrorFun([[maybe_unused]] cppkafka::KafkaHandleBase & handle, int error, const std::string &reason) {
|
||||
poco_error(KafkaManager()->Logger(),fmt::format("kafka-error: {}, reason: {}", error, reason));
|
||||
}
|
||||
|
||||
inline void AddKafkaSecurity(cppkafka::Configuration & Config) {
|
||||
auto CA = MicroServiceConfigGetString("openwifi.kafka.ssl.ca.location","");
|
||||
auto Certificate = MicroServiceConfigGetString("openwifi.kafka.ssl.certificate.location","");
|
||||
auto Key = MicroServiceConfigGetString("openwifi.kafka.ssl.key.location","");
|
||||
auto Password = MicroServiceConfigGetString("openwifi.kafka.ssl.key.password","");
|
||||
|
||||
if(CA.empty() || Certificate.empty() || Key.empty())
|
||||
return;
|
||||
|
||||
Config.set("ssl.ca.location", CA);
|
||||
Config.set("ssl.certificate.location", Certificate);
|
||||
Config.set("ssl.key.location", Key);
|
||||
if(!Password.empty())
|
||||
Config.set("ssl.key.password", Password);
|
||||
}
|
||||
|
||||
|
||||
void KafkaManager::initialize(Poco::Util::Application & self) {
|
||||
SubSystemServer::initialize(self);
|
||||
KafkaEnabled_ = MicroServiceConfigGetBool("openwifi.kafka.enable",false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
inline void KafkaProducer::run() {
|
||||
|
||||
Utils::SetThreadName("Kafka:Prod");
|
||||
cppkafka::Configuration Config({
|
||||
{ "client.id", MicroServiceConfigGetString("openwifi.kafka.client.id", "") },
|
||||
{ "metadata.broker.list", MicroServiceConfigGetString("openwifi.kafka.brokerlist", "") }
|
||||
});
|
||||
|
||||
AddKafkaSecurity(Config);
|
||||
|
||||
Config.set_log_callback(KafkaLoggerFun);
|
||||
Config.set_error_callback(KafkaErrorFun);
|
||||
|
||||
KafkaManager()->SystemInfoWrapper_ = R"lit({ "system" : { "id" : )lit" +
|
||||
std::to_string(MicroServiceID()) +
|
||||
R"lit( , "host" : ")lit" + MicroServicePrivateEndPoint() +
|
||||
R"lit(" } , "payload" : )lit" ;
|
||||
|
||||
cppkafka::Producer Producer(Config);
|
||||
Running_ = true;
|
||||
|
||||
Poco::AutoPtr<Poco::Notification> Note(Queue_.waitDequeueNotification());
|
||||
while(Note && Running_) {
|
||||
try {
|
||||
auto Msg = dynamic_cast<KafkaMessage *>(Note.get());
|
||||
if (Msg != nullptr) {
|
||||
Producer.produce(
|
||||
cppkafka::MessageBuilder(Msg->Topic()).key(Msg->Key()).payload(Msg->Payload()));
|
||||
}
|
||||
} catch (const cppkafka::HandleException &E) {
|
||||
poco_warning(KafkaManager()->Logger(),fmt::format("Caught a Kafka exception (producer): {}", E.what()));
|
||||
} catch( const Poco::Exception &E) {
|
||||
KafkaManager()->Logger().log(E);
|
||||
} catch (...) {
|
||||
poco_error(KafkaManager()->Logger(),"std::exception");
|
||||
}
|
||||
Note = Queue_.waitDequeueNotification();
|
||||
}
|
||||
}
|
||||
|
||||
inline void KafkaConsumer::run() {
|
||||
Utils::SetThreadName("Kafka:Cons");
|
||||
|
||||
cppkafka::Configuration Config({
|
||||
{ "client.id", MicroServiceConfigGetString("openwifi.kafka.client.id","") },
|
||||
{ "metadata.broker.list", MicroServiceConfigGetString("openwifi.kafka.brokerlist","") },
|
||||
{ "group.id", MicroServiceConfigGetString("openwifi.kafka.group.id","") },
|
||||
{ "enable.auto.commit", MicroServiceConfigGetBool("openwifi.kafka.auto.commit",false) },
|
||||
{ "auto.offset.reset", "latest" } ,
|
||||
{ "enable.partition.eof", false }
|
||||
});
|
||||
|
||||
AddKafkaSecurity(Config);
|
||||
|
||||
Config.set_log_callback(KafkaLoggerFun);
|
||||
Config.set_error_callback(KafkaErrorFun);
|
||||
|
||||
cppkafka::TopicConfiguration topic_config = {
|
||||
{ "auto.offset.reset", "smallest" }
|
||||
};
|
||||
|
||||
// Now configure it to be the default topic config
|
||||
Config.set_default_topic_configuration(topic_config);
|
||||
|
||||
cppkafka::Consumer Consumer(Config);
|
||||
Consumer.set_assignment_callback([](cppkafka::TopicPartitionList& partitions) {
|
||||
if(!partitions.empty()) {
|
||||
KafkaManager()->Logger().information(fmt::format("Partition assigned: {}...",
|
||||
partitions.front().get_partition()));
|
||||
}
|
||||
});
|
||||
Consumer.set_revocation_callback([](const cppkafka::TopicPartitionList& partitions) {
|
||||
if(!partitions.empty()) {
|
||||
KafkaManager()->Logger().information(fmt::format("Partition revocation: {}...",
|
||||
partitions.front().get_partition()));
|
||||
}
|
||||
});
|
||||
|
||||
bool AutoCommit = MicroServiceConfigGetBool("openwifi.kafka.auto.commit",false);
|
||||
auto BatchSize = MicroServiceConfigGetInt("openwifi.kafka.consumer.batchsize",20);
|
||||
|
||||
Types::StringVec Topics;
|
||||
KafkaManager()->Topics(Topics);
|
||||
Consumer.subscribe(Topics);
|
||||
|
||||
Running_ = true;
|
||||
while(Running_) {
|
||||
try {
|
||||
std::vector<cppkafka::Message> MsgVec = Consumer.poll_batch(BatchSize, std::chrono::milliseconds(100));
|
||||
for(auto const &Msg:MsgVec) {
|
||||
if (!Msg)
|
||||
continue;
|
||||
if (Msg.get_error()) {
|
||||
if (!Msg.is_eof()) {
|
||||
poco_error(KafkaManager()->Logger(),fmt::format("Error: {}", Msg.get_error().to_string()));
|
||||
}
|
||||
if(!AutoCommit)
|
||||
Consumer.async_commit(Msg);
|
||||
continue;
|
||||
}
|
||||
KafkaManager()->Dispatch(Msg.get_topic(), Msg.get_key(),Msg.get_payload() );
|
||||
if (!AutoCommit)
|
||||
Consumer.async_commit(Msg);
|
||||
}
|
||||
} catch (const cppkafka::HandleException &E) {
|
||||
poco_warning(KafkaManager()->Logger(),fmt::format("Caught a Kafka exception (consumer): {}", E.what()));
|
||||
} catch (const Poco::Exception &E) {
|
||||
KafkaManager()->Logger().log(E);
|
||||
} catch (...) {
|
||||
poco_error(KafkaManager()->Logger(),"std::exception");
|
||||
}
|
||||
}
|
||||
Consumer.unsubscribe();
|
||||
}
|
||||
|
||||
void KafkaProducer::Start() {
|
||||
if(!Running_) {
|
||||
Running_=true;
|
||||
Worker_.start(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaProducer::Stop() {
|
||||
if(Running_) {
|
||||
Running_=false;
|
||||
Queue_.wakeUpAll();
|
||||
Worker_.join();
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaProducer::Produce(const std::string &Topic, const std::string &Key, const std::string &Payload) {
|
||||
std::lock_guard G(Mutex_);
|
||||
Queue_.enqueueNotification( new KafkaMessage(Topic,Key,Payload));
|
||||
}
|
||||
|
||||
void KafkaConsumer::Start() {
|
||||
if(!Running_) {
|
||||
Running_=true;
|
||||
Worker_.start(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaConsumer::Stop() {
|
||||
if(Running_) {
|
||||
Running_=false;
|
||||
Worker_.wakeUp();
|
||||
Worker_.join();
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaDispatcher::Start() {
|
||||
if(!Running_) {
|
||||
Running_=true;
|
||||
Worker_.start(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaDispatcher::Stop() {
|
||||
if(Running_) {
|
||||
Running_=false;
|
||||
Queue_.wakeUpAll();
|
||||
Worker_.join();
|
||||
}
|
||||
}
|
||||
|
||||
auto KafkaDispatcher::RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F) {
|
||||
std::lock_guard G(Mutex_);
|
||||
auto It = Notifiers_.find(Topic);
|
||||
if(It == Notifiers_.end()) {
|
||||
Types::TopicNotifyFunctionList L;
|
||||
L.emplace(L.end(),std::make_pair(F,FunctionId_));
|
||||
Notifiers_[Topic] = std::move(L);
|
||||
} else {
|
||||
It->second.emplace(It->second.end(),std::make_pair(F,FunctionId_));
|
||||
}
|
||||
return FunctionId_++;
|
||||
}
|
||||
|
||||
void KafkaDispatcher::UnregisterTopicWatcher(const std::string &Topic, int Id) {
|
||||
std::lock_guard G(Mutex_);
|
||||
auto It = Notifiers_.find(Topic);
|
||||
if(It != Notifiers_.end()) {
|
||||
Types::TopicNotifyFunctionList & L = It->second;
|
||||
for(auto it=L.begin(); it!=L.end(); it++)
|
||||
if(it->second == Id) {
|
||||
L.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaDispatcher::Dispatch(const std::string &Topic, const std::string &Key, const std::string &Payload) {
|
||||
std::lock_guard G(Mutex_);
|
||||
auto It = Notifiers_.find(Topic);
|
||||
if(It!=Notifiers_.end()) {
|
||||
Queue_.enqueueNotification(new KafkaMessage(Topic, Key, Payload));
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaDispatcher::run() {
|
||||
Poco::AutoPtr<Poco::Notification> Note(Queue_.waitDequeueNotification());
|
||||
Utils::SetThreadName("kafka:dispatch");
|
||||
while(Note && Running_) {
|
||||
auto Msg = dynamic_cast<KafkaMessage*>(Note.get());
|
||||
if(Msg!= nullptr) {
|
||||
auto It = Notifiers_.find(Msg->Topic());
|
||||
if (It != Notifiers_.end()) {
|
||||
const auto & FL = It->second;
|
||||
for(const auto &[CallbackFunc,_]:FL) {
|
||||
CallbackFunc(Msg->Key(), Msg->Payload());
|
||||
}
|
||||
}
|
||||
}
|
||||
Note = Queue_.waitDequeueNotification();
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaDispatcher::Topics(std::vector<std::string> &T) {
|
||||
T.clear();
|
||||
for(const auto &[TopicName,_]:Notifiers_)
|
||||
T.push_back(TopicName);
|
||||
}
|
||||
|
||||
|
||||
int KafkaManager::Start() {
|
||||
if(!KafkaEnabled_)
|
||||
return 0;
|
||||
ConsumerThr_.Start();
|
||||
ProducerThr_.Start();
|
||||
Dispatcher_.Start();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void KafkaManager::Stop() {
|
||||
if(KafkaEnabled_) {
|
||||
poco_information(Logger(),"Stopping...");
|
||||
Dispatcher_.Stop();
|
||||
ProducerThr_.Stop();
|
||||
ConsumerThr_.Stop();
|
||||
poco_information(Logger(),"Stopped...");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaManager::PostMessage(const std::string &topic, const std::string & key, const std::string &PayLoad, bool WrapMessage ) {
|
||||
if(KafkaEnabled_) {
|
||||
ProducerThr_.Produce(topic,key,WrapMessage ? WrapSystemId(PayLoad) : PayLoad);
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaManager::Dispatch(const std::string &Topic, const std::string & Key, const std::string &Payload) {
|
||||
Dispatcher_.Dispatch(Topic, Key, Payload);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string KafkaManager::WrapSystemId(const std::string & PayLoad) {
|
||||
return SystemInfoWrapper_ + PayLoad + "}";
|
||||
}
|
||||
|
||||
uint64_t KafkaManager::RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F) {
|
||||
if(KafkaEnabled_) {
|
||||
return Dispatcher_.RegisterTopicWatcher(Topic,F);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaManager::UnregisterTopicWatcher(const std::string &Topic, uint64_t Id) {
|
||||
if(KafkaEnabled_) {
|
||||
Dispatcher_.UnregisterTopicWatcher(Topic, Id);
|
||||
}
|
||||
}
|
||||
|
||||
void KafkaManager::Topics(std::vector<std::string> &T) {
|
||||
Dispatcher_.Topics(T);
|
||||
}
|
||||
|
||||
void KafkaManager::PartitionAssignment(const cppkafka::TopicPartitionList& partitions) {
|
||||
Logger().information(fmt::format("Partition assigned: {}...", partitions.front().get_partition()));
|
||||
}
|
||||
|
||||
void KafkaManager::PartitionRevocation(const cppkafka::TopicPartitionList& partitions) {
|
||||
Logger().information(fmt::format("Partition revocation: {}...",partitions.front().get_partition()));
|
||||
}
|
||||
|
||||
} // namespace OpenWifi
|
||||
124
src/framework/KafkaManager.h
Normal file
124
src/framework/KafkaManager.h
Normal file
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Poco/Notification.h"
|
||||
#include "Poco/NotificationQueue.h"
|
||||
|
||||
#include "framework/SubSystemServer.h"
|
||||
#include "framework/OpenWifiTypes.h"
|
||||
#include "framework/utils.h"
|
||||
#include "framework/KafkaTopics.h"
|
||||
|
||||
#include "cppkafka/cppkafka.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
class KafkaMessage: public Poco::Notification {
|
||||
public:
|
||||
KafkaMessage( const std::string &Topic, const std::string &Key, const std::string & Payload) :
|
||||
Topic_(Topic), Key_(Key), Payload_(Payload) {
|
||||
}
|
||||
|
||||
inline const std::string & Topic() { return Topic_; }
|
||||
inline const std::string & Key() { return Key_; }
|
||||
inline const std::string & Payload() { return Payload_; }
|
||||
|
||||
private:
|
||||
std::string Topic_;
|
||||
std::string Key_;
|
||||
std::string Payload_;
|
||||
};
|
||||
|
||||
class KafkaProducer : public Poco::Runnable {
|
||||
public:
|
||||
|
||||
void run () override;
|
||||
void Start();
|
||||
void Stop();
|
||||
void Produce(const std::string &Topic, const std::string &Key, const std::string &Payload);
|
||||
|
||||
private:
|
||||
std::recursive_mutex Mutex_;
|
||||
Poco::Thread Worker_;
|
||||
mutable std::atomic_bool Running_=false;
|
||||
Poco::NotificationQueue Queue_;
|
||||
};
|
||||
|
||||
class KafkaConsumer : public Poco::Runnable {
|
||||
public:
|
||||
void run() override;
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
std::recursive_mutex Mutex_;
|
||||
Poco::Thread Worker_;
|
||||
mutable std::atomic_bool Running_=false;
|
||||
};
|
||||
|
||||
class KafkaDispatcher : public Poco::Runnable {
|
||||
public:
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
auto RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F);
|
||||
void UnregisterTopicWatcher(const std::string &Topic, int Id);
|
||||
void Dispatch(const std::string &Topic, const std::string &Key, const std::string &Payload);
|
||||
void run() override;
|
||||
void Topics(std::vector<std::string> &T);
|
||||
|
||||
private:
|
||||
std::recursive_mutex Mutex_;
|
||||
Types::NotifyTable Notifiers_;
|
||||
Poco::Thread Worker_;
|
||||
mutable std::atomic_bool Running_=false;
|
||||
uint64_t FunctionId_=1;
|
||||
Poco::NotificationQueue Queue_;
|
||||
};
|
||||
|
||||
class KafkaManager : public SubSystemServer {
|
||||
public:
|
||||
|
||||
friend class KafkaConsumer;
|
||||
friend class KafkaProducer;
|
||||
|
||||
inline void initialize(Poco::Util::Application & self) override;
|
||||
|
||||
static auto instance() {
|
||||
static auto instance_ = new KafkaManager;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
int Start() override;
|
||||
void Stop() override;
|
||||
|
||||
void PostMessage(const std::string &topic, const std::string & key, const std::string &PayLoad, bool WrapMessage = true );
|
||||
void Dispatch(const std::string &Topic, const std::string & Key, const std::string &Payload);
|
||||
[[nodiscard]] std::string WrapSystemId(const std::string & PayLoad);
|
||||
[[nodiscard]] inline bool Enabled() const { return KafkaEnabled_; }
|
||||
uint64_t RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F);
|
||||
void UnregisterTopicWatcher(const std::string &Topic, uint64_t Id);
|
||||
void Topics(std::vector<std::string> &T);
|
||||
|
||||
private:
|
||||
bool KafkaEnabled_ = false;
|
||||
std::string SystemInfoWrapper_;
|
||||
KafkaProducer ProducerThr_;
|
||||
KafkaConsumer ConsumerThr_;
|
||||
KafkaDispatcher Dispatcher_;
|
||||
|
||||
void PartitionAssignment(const cppkafka::TopicPartitionList& partitions);
|
||||
void PartitionRevocation(const cppkafka::TopicPartitionList& partitions);
|
||||
|
||||
KafkaManager() noexcept:
|
||||
SubSystemServer("KafkaManager", "KAFKA-SVR", "openwifi.kafka") {
|
||||
}
|
||||
};
|
||||
|
||||
inline auto KafkaManager() { return KafkaManager::instance(); }
|
||||
|
||||
} // namespace OpenWifi
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
namespace OpenWifi::KafkaTopics {
|
||||
static const std::string HEALTHCHECK{"healthcheck"};
|
||||
static const std::string STATE{"state"};
|
||||
|
||||
635
src/framework/MicroService.cpp
Normal file
635
src/framework/MicroService.cpp
Normal file
@@ -0,0 +1,635 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-26.
|
||||
//
|
||||
|
||||
#include "Poco/FileChannel.h"
|
||||
#include "Poco/ConsoleChannel.h"
|
||||
#include "Poco/PatternFormatter.h"
|
||||
#include "Poco/FormattingChannel.h"
|
||||
#include "Poco/AsyncChannel.h"
|
||||
#include "Poco/NullChannel.h"
|
||||
#include "Poco/Net/HTTPStreamFactory.h"
|
||||
#include "Poco/Net/HTTPSStreamFactory.h"
|
||||
#include "Poco/Net/FTPSStreamFactory.h"
|
||||
#include "Poco/Net/FTPStreamFactory.h"
|
||||
#include "Poco/Net/SSLManager.h"
|
||||
#include "Poco/JSON/JSONException.h"
|
||||
|
||||
#include "framework/MicroService.h"
|
||||
#include "framework/MicroServiceErrorHandler.h"
|
||||
#include "framework/UI_WebSocketClientServer.h"
|
||||
#include "framework/MicroServiceNames.h"
|
||||
#include "framework/AuthClient.h"
|
||||
#include "framework/ALBserver.h"
|
||||
#include "framework/KafkaManager.h"
|
||||
#include "framework/RESTAPI_RateLimiter.h"
|
||||
#include "framework/WebSocketLogger.h"
|
||||
#include "framework/RESTAPI_GenericServerAccounting.h"
|
||||
#include "framework/RESTAPI_Handler.h"
|
||||
#include "framework/RESTAPI_ExtServer.h"
|
||||
#include "framework/RESTAPI_IntServer.h"
|
||||
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
void MicroService::Exit(int Reason) {
|
||||
std::exit(Reason);
|
||||
}
|
||||
|
||||
void MicroService::BusMessageReceived([[maybe_unused]] const std::string &Key, const std::string & Payload) {
|
||||
std::lock_guard G(InfraMutex_);
|
||||
try {
|
||||
Poco::JSON::Parser P;
|
||||
auto Object = P.parse(Payload).extract<Poco::JSON::Object::Ptr>();
|
||||
|
||||
if (Object->has(KafkaTopics::ServiceEvents::Fields::ID) &&
|
||||
Object->has(KafkaTopics::ServiceEvents::Fields::EVENT)) {
|
||||
uint64_t ID = Object->get(KafkaTopics::ServiceEvents::Fields::ID);
|
||||
auto Event = Object->get(KafkaTopics::ServiceEvents::Fields::EVENT).toString();
|
||||
if (ID != ID_) {
|
||||
if( Event==KafkaTopics::ServiceEvents::EVENT_JOIN ||
|
||||
Event==KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE ||
|
||||
Event==KafkaTopics::ServiceEvents::EVENT_LEAVE ) {
|
||||
if( Object->has(KafkaTopics::ServiceEvents::Fields::TYPE) &&
|
||||
Object->has(KafkaTopics::ServiceEvents::Fields::PUBLIC) &&
|
||||
Object->has(KafkaTopics::ServiceEvents::Fields::PRIVATE) &&
|
||||
Object->has(KafkaTopics::ServiceEvents::Fields::VRSN) &&
|
||||
Object->has(KafkaTopics::ServiceEvents::Fields::KEY)) {
|
||||
auto PrivateEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString();
|
||||
if (Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE && Services_.find(PrivateEndPoint) != Services_.end()) {
|
||||
Services_[PrivateEndPoint].LastUpdate = OpenWifi::Now();
|
||||
} else if (Event == KafkaTopics::ServiceEvents::EVENT_LEAVE) {
|
||||
Services_.erase(PrivateEndPoint);
|
||||
poco_debug(logger(),fmt::format("Service {} ID={} leaving system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID));
|
||||
} else if (Event == KafkaTopics::ServiceEvents::EVENT_JOIN || Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE) {
|
||||
poco_debug(logger(),fmt::format("Service {} ID={} joining system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID));
|
||||
Services_[PrivateEndPoint] = Types::MicroServiceMeta{
|
||||
.Id = ID,
|
||||
.Type = Poco::toLower(Object->get(KafkaTopics::ServiceEvents::Fields::TYPE).toString()),
|
||||
.PrivateEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),
|
||||
.PublicEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PUBLIC).toString(),
|
||||
.AccessKey = Object->get(KafkaTopics::ServiceEvents::Fields::KEY).toString(),
|
||||
.Version = Object->get(KafkaTopics::ServiceEvents::Fields::VRSN).toString(),
|
||||
.LastUpdate = OpenWifi::Now() };
|
||||
|
||||
std::string SvcList;
|
||||
for (const auto &Svc: Services_) {
|
||||
if(SvcList.empty())
|
||||
SvcList = Svc.second.Type;
|
||||
else
|
||||
SvcList += ", " + Svc.second.Type;
|
||||
}
|
||||
logger().information(fmt::format("Current list of microservices: {}", SvcList));
|
||||
}
|
||||
} else {
|
||||
poco_error(logger(),fmt::format("KAFKA-MSG: invalid event '{}', missing a field.",Event));
|
||||
}
|
||||
} else if (Event==KafkaTopics::ServiceEvents::EVENT_REMOVE_TOKEN) {
|
||||
if(Object->has(KafkaTopics::ServiceEvents::Fields::TOKEN)) {
|
||||
#ifndef TIP_SECURITY_SERVICE
|
||||
AuthClient()->RemovedCachedToken(Object->get(KafkaTopics::ServiceEvents::Fields::TOKEN).toString());
|
||||
#endif
|
||||
} else {
|
||||
poco_error(logger(),fmt::format("KAFKA-MSG: invalid event '{}', missing token",Event));
|
||||
}
|
||||
} else {
|
||||
poco_error(logger(),fmt::format("Unknown Event: {} Source: {}", Event, ID));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
poco_error(logger(),"Bad bus message.");
|
||||
}
|
||||
|
||||
auto i=Services_.begin();
|
||||
auto now = OpenWifi::Now();
|
||||
for(;i!=Services_.end();) {
|
||||
if((now - i->second.LastUpdate)>60) {
|
||||
i = Services_.erase(i);
|
||||
} else
|
||||
++i;
|
||||
}
|
||||
|
||||
} catch (const Poco::Exception &E) {
|
||||
logger().log(E);
|
||||
}
|
||||
}
|
||||
|
||||
Types::MicroServiceMetaVec MicroService::GetServices(const std::string & Type) {
|
||||
std::lock_guard G(InfraMutex_);
|
||||
|
||||
auto T = Poco::toLower(Type);
|
||||
Types::MicroServiceMetaVec Res;
|
||||
for(const auto &[_,ServiceRec]:Services_) {
|
||||
if(ServiceRec.Type==T)
|
||||
Res.push_back(ServiceRec);
|
||||
}
|
||||
return Res;
|
||||
}
|
||||
|
||||
Types::MicroServiceMetaVec MicroService::GetServices() {
|
||||
std::lock_guard G(InfraMutex_);
|
||||
|
||||
Types::MicroServiceMetaVec Res;
|
||||
for(const auto &[_,ServiceRec]:Services_) {
|
||||
Res.push_back(ServiceRec);
|
||||
}
|
||||
return Res;
|
||||
}
|
||||
|
||||
void MicroService::LoadConfigurationFile() {
|
||||
std::string Location = Poco::Environment::get(DAEMON_CONFIG_ENV_VAR,".");
|
||||
ConfigFileName_ = ConfigFileName_.empty() ? Location + "/" + DAEMON_PROPERTIES_FILENAME : ConfigFileName_;
|
||||
Poco::Path ConfigFile(ConfigFileName_);
|
||||
|
||||
if(!ConfigFile.isFile())
|
||||
{
|
||||
std::cerr << DAEMON_APP_NAME << ": Configuration "
|
||||
<< ConfigFile.toString() << " does not seem to exist. Please set " + DAEMON_CONFIG_ENV_VAR
|
||||
+ " env variable the path of the " + DAEMON_PROPERTIES_FILENAME + " file." << std::endl;
|
||||
std::exit(Poco::Util::Application::EXIT_CONFIG);
|
||||
}
|
||||
|
||||
// loadConfiguration(ConfigFile.toString());
|
||||
PropConfigurationFile_ = new Poco::Util::PropertyFileConfiguration(ConfigFile.toString());
|
||||
configPtr()->addWriteable(PropConfigurationFile_, PRIO_DEFAULT);
|
||||
}
|
||||
|
||||
void MicroService::Reload() {
|
||||
LoadConfigurationFile();
|
||||
LoadMyConfig();
|
||||
}
|
||||
|
||||
void MicroService::LoadMyConfig() {
|
||||
NoAPISecurity_ = ConfigGetBool("openwifi.security.restapi.disable",false);
|
||||
std::string KeyFile = ConfigPath("openwifi.service.key","");
|
||||
if(!KeyFile.empty()) {
|
||||
std::string KeyFilePassword = ConfigPath("openwifi.service.key.password", "");
|
||||
AppKey_ = Poco::SharedPtr<Poco::Crypto::RSAKey>(new Poco::Crypto::RSAKey("", KeyFile, KeyFilePassword));
|
||||
Cipher_ = CipherFactory_.createCipher(*AppKey_);
|
||||
Signer_.setRSAKey(AppKey_);
|
||||
Signer_.addAllAlgorithms();
|
||||
NoBuiltInCrypto_ = false;
|
||||
} else {
|
||||
NoBuiltInCrypto_ = true;
|
||||
}
|
||||
|
||||
ID_ = Utils::GetSystemId();
|
||||
if(!DebugMode_)
|
||||
DebugMode_ = ConfigGetBool("openwifi.system.debug",false);
|
||||
MyPrivateEndPoint_ = ConfigGetString("openwifi.system.uri.private");
|
||||
MyPublicEndPoint_ = ConfigGetString("openwifi.system.uri.public");
|
||||
UIURI_ = ConfigGetString("openwifi.system.uri.ui");
|
||||
MyHash_ = Utils::ComputeHash(MyPublicEndPoint_);
|
||||
}
|
||||
|
||||
void MicroServicePostInitialization();
|
||||
|
||||
void MicroService::InitializeLoggingSystem() {
|
||||
static auto initialized = false;
|
||||
|
||||
if(!initialized) {
|
||||
initialized = true;
|
||||
LoadConfigurationFile();
|
||||
|
||||
auto LoggingDestination = MicroService::instance().ConfigGetString("logging.type", "file");
|
||||
auto LoggingFormat = MicroService::instance().ConfigGetString("logging.format",
|
||||
"%Y-%m-%d %H:%M:%S.%i %s: [%p][thr:%I] %t");
|
||||
auto UseAsyncLogs_ = MicroService::instance().ConfigGetBool("logging.asynch",false);
|
||||
if (LoggingDestination == "null") {
|
||||
Poco::AutoPtr<Poco::NullChannel> DevNull(new Poco::NullChannel);
|
||||
Poco::Logger::root().setChannel(DevNull);
|
||||
} else if (LoggingDestination == "console") {
|
||||
Poco::AutoPtr<Poco::ConsoleChannel> Console(new Poco::ConsoleChannel);
|
||||
if(UseAsyncLogs_) {
|
||||
Poco::AutoPtr<Poco::AsyncChannel> Async(new Poco::AsyncChannel(Console));
|
||||
Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
|
||||
Formatter->setProperty("pattern", LoggingFormat);
|
||||
Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
|
||||
new Poco::FormattingChannel(Formatter, Async));
|
||||
Poco::Logger::root().setChannel(FormattingChannel);
|
||||
} else {
|
||||
Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
|
||||
Formatter->setProperty("pattern", LoggingFormat);
|
||||
Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
|
||||
new Poco::FormattingChannel(Formatter, Console));
|
||||
Poco::Logger::root().setChannel(FormattingChannel);
|
||||
}
|
||||
} else if (LoggingDestination == "colorconsole") {
|
||||
Poco::AutoPtr<Poco::ColorConsoleChannel> ColorConsole(new Poco::ColorConsoleChannel);
|
||||
if(UseAsyncLogs_) {
|
||||
Poco::AutoPtr<Poco::AsyncChannel> Async(new Poco::AsyncChannel(ColorConsole));
|
||||
Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
|
||||
Formatter->setProperty("pattern", LoggingFormat);
|
||||
Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
|
||||
new Poco::FormattingChannel(Formatter, Async));
|
||||
Poco::Logger::root().setChannel(FormattingChannel);
|
||||
} else {
|
||||
Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
|
||||
Formatter->setProperty("pattern", LoggingFormat);
|
||||
Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
|
||||
new Poco::FormattingChannel(Formatter, ColorConsole));
|
||||
Poco::Logger::root().setChannel(FormattingChannel);
|
||||
}
|
||||
} else if (LoggingDestination == "sql") {
|
||||
//"CREATE TABLE T_POCO_LOG (Source VARCHAR, Name VARCHAR, ProcessId INTEGER, Thread VARCHAR, ThreadId INTEGER, Priority INTEGER, Text VARCHAR, DateTime DATE)"
|
||||
|
||||
} else if (LoggingDestination == "syslog") {
|
||||
|
||||
} else {
|
||||
auto LoggingLocation =
|
||||
MicroService::instance().ConfigPath("logging.path", "$OWCERT_ROOT/logs") + "/log";
|
||||
|
||||
Poco::AutoPtr<Poco::FileChannel> FileChannel(new Poco::FileChannel);
|
||||
FileChannel->setProperty("rotation", "10 M");
|
||||
FileChannel->setProperty("archive", "timestamp");
|
||||
FileChannel->setProperty("path", LoggingLocation);
|
||||
if(UseAsyncLogs_) {
|
||||
Poco::AutoPtr<Poco::AsyncChannel> Async_File(
|
||||
new Poco::AsyncChannel(FileChannel));
|
||||
Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
|
||||
Formatter->setProperty("pattern", LoggingFormat);
|
||||
Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
|
||||
new Poco::FormattingChannel(Formatter, Async_File));
|
||||
Poco::Logger::root().setChannel(FormattingChannel);
|
||||
} else {
|
||||
Poco::AutoPtr<Poco::PatternFormatter> Formatter(new Poco::PatternFormatter);
|
||||
Formatter->setProperty("pattern", LoggingFormat);
|
||||
Poco::AutoPtr<Poco::FormattingChannel> FormattingChannel(
|
||||
new Poco::FormattingChannel(Formatter, FileChannel));
|
||||
Poco::Logger::root().setChannel(FormattingChannel);
|
||||
}
|
||||
}
|
||||
auto Level = Poco::Logger::parseLevel(MicroService::instance().ConfigGetString("logging.level", "debug"));
|
||||
Poco::Logger::root().setLevel(Level);
|
||||
}
|
||||
}
|
||||
|
||||
void DaemonPostInitialization(Poco::Util::Application &self);
|
||||
|
||||
void MicroService::initialize(Poco::Util::Application &self) {
|
||||
// add the default services
|
||||
LoadConfigurationFile();
|
||||
InitializeLoggingSystem();
|
||||
|
||||
SubSystems_.push_back(KafkaManager());
|
||||
SubSystems_.push_back(ALBHealthCheckServer());
|
||||
SubSystems_.push_back(RESTAPI_ExtServer());
|
||||
SubSystems_.push_back(RESTAPI_IntServer());
|
||||
#ifndef TIP_SECURITY_SERVICE
|
||||
SubSystems_.push_back(AuthClient());
|
||||
#endif
|
||||
Poco::Net::initializeSSL();
|
||||
Poco::Net::HTTPStreamFactory::registerFactory();
|
||||
Poco::Net::HTTPSStreamFactory::registerFactory();
|
||||
Poco::Net::FTPStreamFactory::registerFactory();
|
||||
Poco::Net::FTPSStreamFactory::registerFactory();
|
||||
|
||||
Poco::File DataDir(ConfigPath("openwifi.system.data"));
|
||||
DataDir_ = DataDir.path();
|
||||
if(!DataDir.exists()) {
|
||||
try {
|
||||
DataDir.createDirectory();
|
||||
} catch (const Poco::Exception &E) {
|
||||
logger().log(E);
|
||||
}
|
||||
}
|
||||
WWWAssetsDir_ = ConfigPath("openwifi.restapi.wwwassets","");
|
||||
if(WWWAssetsDir_.empty())
|
||||
WWWAssetsDir_ = DataDir_;
|
||||
|
||||
LoadMyConfig();
|
||||
|
||||
InitializeSubSystemServers();
|
||||
ServerApplication::initialize(self);
|
||||
DaemonPostInitialization(self);
|
||||
|
||||
Types::TopicNotifyFunction F = [this](const std::string &Key,const std::string &Payload) { this->BusMessageReceived(Key, Payload); };
|
||||
KafkaManager()->RegisterTopicWatcher(KafkaTopics::SERVICE_EVENTS, F);
|
||||
}
|
||||
|
||||
void MicroService::uninitialize() {
|
||||
// add your own uninitialization code here
|
||||
ServerApplication::uninitialize();
|
||||
}
|
||||
|
||||
void MicroService::reinitialize(Poco::Util::Application &self) {
|
||||
ServerApplication::reinitialize(self);
|
||||
// add your own reinitialization code here
|
||||
}
|
||||
|
||||
void MicroService::defineOptions(Poco::Util::OptionSet &options) {
|
||||
ServerApplication::defineOptions(options);
|
||||
|
||||
options.addOption(
|
||||
Poco::Util::Option("help", "", "display help information on command line arguments")
|
||||
.required(false)
|
||||
.repeatable(false)
|
||||
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleHelp)));
|
||||
|
||||
options.addOption(
|
||||
Poco::Util::Option("file", "", "specify the configuration file")
|
||||
.required(false)
|
||||
.repeatable(false)
|
||||
.argument("file")
|
||||
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleConfig)));
|
||||
|
||||
options.addOption(
|
||||
Poco::Util::Option("debug", "", "to run in debug, set to true")
|
||||
.required(false)
|
||||
.repeatable(false)
|
||||
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleDebug)));
|
||||
|
||||
options.addOption(
|
||||
Poco::Util::Option("logs", "", "specify the log directory and file (i.e. dir/file.log)")
|
||||
.required(false)
|
||||
.repeatable(false)
|
||||
.argument("dir")
|
||||
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleLogs)));
|
||||
|
||||
options.addOption(
|
||||
Poco::Util::Option("version", "", "get the version and quit.")
|
||||
.required(false)
|
||||
.repeatable(false)
|
||||
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleVersion)));
|
||||
|
||||
}
|
||||
|
||||
void MicroService::handleHelp([[maybe_unused]] const std::string &name, [[maybe_unused]] const std::string &value) {
|
||||
HelpRequested_ = true;
|
||||
displayHelp();
|
||||
stopOptionsProcessing();
|
||||
}
|
||||
|
||||
void MicroService::handleVersion([[maybe_unused]] const std::string &name, [[maybe_unused]] const std::string &value) {
|
||||
HelpRequested_ = true;
|
||||
std::cout << Version() << std::endl;
|
||||
stopOptionsProcessing();
|
||||
}
|
||||
|
||||
void MicroService::handleDebug([[maybe_unused]] const std::string &name, const std::string &value) {
|
||||
if(value == "true")
|
||||
DebugMode_ = true ;
|
||||
}
|
||||
|
||||
void MicroService::handleLogs([[maybe_unused]] const std::string &name, const std::string &value) {
|
||||
LogDir_ = value;
|
||||
}
|
||||
|
||||
void MicroService::handleConfig([[maybe_unused]] const std::string &name, const std::string &value) {
|
||||
ConfigFileName_ = value;
|
||||
}
|
||||
|
||||
void MicroService::displayHelp() {
|
||||
Poco::Util::HelpFormatter helpFormatter(options());
|
||||
helpFormatter.setCommand(commandName());
|
||||
helpFormatter.setUsage("OPTIONS");
|
||||
helpFormatter.setHeader("A " + DAEMON_APP_NAME + " implementation for TIP.");
|
||||
helpFormatter.format(std::cout);
|
||||
}
|
||||
|
||||
void MicroService::InitializeSubSystemServers() {
|
||||
for(auto i:SubSystems_) {
|
||||
addSubsystem(i);
|
||||
}
|
||||
}
|
||||
|
||||
void MicroService::StartSubSystemServers() {
|
||||
AddActivity("Starting");
|
||||
for(auto i:SubSystems_) {
|
||||
i->Start();
|
||||
}
|
||||
EventBusManager_ = std::make_unique<EventBusManager>(Poco::Logger::create("EventBusManager",Poco::Logger::root().getChannel(),Poco::Logger::root().getLevel()));
|
||||
EventBusManager_->Start();
|
||||
}
|
||||
|
||||
void MicroService::StopSubSystemServers() {
|
||||
AddActivity("Stopping");
|
||||
EventBusManager_->Stop();
|
||||
for(auto i=SubSystems_.rbegin(); i!=SubSystems_.rend(); ++i) {
|
||||
(*i)->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string MicroService::CreateUUID() {
|
||||
static std::random_device rd;
|
||||
static std::mt19937_64 gen(rd());
|
||||
static std::uniform_int_distribution<> dis(0, 15);
|
||||
static std::uniform_int_distribution<> dis2(8, 11);
|
||||
|
||||
std::stringstream ss;
|
||||
int i;
|
||||
ss << std::hex;
|
||||
for (i = 0; i < 8; i++) {
|
||||
ss << dis(gen);
|
||||
}
|
||||
ss << "-";
|
||||
for (i = 0; i < 4; i++) {
|
||||
ss << dis(gen);
|
||||
}
|
||||
ss << "-4";
|
||||
for (i = 0; i < 3; i++) {
|
||||
ss << dis(gen);
|
||||
}
|
||||
ss << "-";
|
||||
ss << dis2(gen);
|
||||
for (i = 0; i < 3; i++) {
|
||||
ss << dis(gen);
|
||||
}
|
||||
ss << "-";
|
||||
for (i = 0; i < 12; i++) {
|
||||
ss << dis(gen);
|
||||
};
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool MicroService::SetSubsystemLogLevel(const std::string &SubSystem, const std::string &Level) {
|
||||
try {
|
||||
auto P = Poco::Logger::parseLevel(Level);
|
||||
auto Sub = Poco::toLower(SubSystem);
|
||||
|
||||
if (Sub == "all") {
|
||||
for (auto i : SubSystems_) {
|
||||
i->Logger().setLevel(P);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
for (auto i : SubSystems_) {
|
||||
if (Sub == Poco::toLower(i->Name())) {
|
||||
i->Logger().setLevel(P);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const Poco::Exception & E) {
|
||||
std::cerr << "Exception" << std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MicroService::Reload(const std::string &Sub) {
|
||||
for (auto i : SubSystems_) {
|
||||
if (Poco::toLower(Sub) == Poco::toLower(i->Name())) {
|
||||
i->reinitialize(Poco::Util::Application::instance());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Types::StringVec MicroService::GetSubSystems() const {
|
||||
Types::StringVec Result;
|
||||
for(auto i:SubSystems_)
|
||||
Result.push_back(Poco::toLower(i->Name()));
|
||||
return Result;
|
||||
}
|
||||
|
||||
Types::StringPairVec MicroService::GetLogLevels() {
|
||||
Types::StringPairVec Result;
|
||||
|
||||
for(auto &i:SubSystems_) {
|
||||
auto P = std::make_pair( i->Name(), Utils::LogLevelToString(i->GetLoggingLevel()));
|
||||
Result.push_back(P);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
const Types::StringVec & MicroService::GetLogLevelNames() {
|
||||
static Types::StringVec LevelNames{"none", "fatal", "critical", "error", "warning", "notice", "information", "debug", "trace" };
|
||||
return LevelNames;
|
||||
}
|
||||
|
||||
uint64_t MicroService::ConfigGetInt(const std::string &Key,uint64_t Default) {
|
||||
return (uint64_t) config().getInt64(Key,Default);
|
||||
}
|
||||
|
||||
uint64_t MicroService::ConfigGetInt(const std::string &Key) {
|
||||
return config().getInt(Key);
|
||||
}
|
||||
|
||||
uint64_t MicroService::ConfigGetBool(const std::string &Key,bool Default) {
|
||||
return config().getBool(Key,Default);
|
||||
}
|
||||
|
||||
uint64_t MicroService::ConfigGetBool(const std::string &Key) {
|
||||
return config().getBool(Key);
|
||||
}
|
||||
|
||||
std::string MicroService::ConfigGetString(const std::string &Key,const std::string & Default) {
|
||||
return config().getString(Key, Default);
|
||||
}
|
||||
|
||||
std::string MicroService::ConfigGetString(const std::string &Key) {
|
||||
return config().getString(Key);
|
||||
}
|
||||
|
||||
std::string MicroService::ConfigPath(const std::string &Key,const std::string & Default) {
|
||||
std::string R = config().getString(Key, Default);
|
||||
return Poco::Path::expand(R);
|
||||
}
|
||||
|
||||
std::string MicroService::ConfigPath(const std::string &Key) {
|
||||
std::string R = config().getString(Key);
|
||||
return Poco::Path::expand(R);
|
||||
}
|
||||
|
||||
std::string MicroService::Encrypt(const std::string &S) {
|
||||
if(NoBuiltInCrypto_) {
|
||||
return S;
|
||||
}
|
||||
return Cipher_->encryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);;
|
||||
}
|
||||
|
||||
std::string MicroService::Decrypt(const std::string &S) {
|
||||
if(NoBuiltInCrypto_) {
|
||||
return S;
|
||||
}
|
||||
return Cipher_->decryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);;
|
||||
}
|
||||
|
||||
std::string MicroService::MakeSystemEventMessage( const std::string & Type ) const {
|
||||
Poco::JSON::Object Obj;
|
||||
Obj.set(KafkaTopics::ServiceEvents::Fields::EVENT,Type);
|
||||
Obj.set(KafkaTopics::ServiceEvents::Fields::ID,ID_);
|
||||
Obj.set(KafkaTopics::ServiceEvents::Fields::TYPE,Poco::toLower(DAEMON_APP_NAME));
|
||||
Obj.set(KafkaTopics::ServiceEvents::Fields::PUBLIC,MyPublicEndPoint_);
|
||||
Obj.set(KafkaTopics::ServiceEvents::Fields::PRIVATE,MyPrivateEndPoint_);
|
||||
Obj.set(KafkaTopics::ServiceEvents::Fields::KEY,MyHash_);
|
||||
Obj.set(KafkaTopics::ServiceEvents::Fields::VRSN,Version_);
|
||||
std::stringstream ResultText;
|
||||
Poco::JSON::Stringifier::stringify(Obj, ResultText);
|
||||
return ResultText.str();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MicroService::IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request) {
|
||||
try {
|
||||
auto APIKEY = Request.get("X-API-KEY");
|
||||
return APIKEY == MyHash_;
|
||||
} catch (const Poco::Exception &E) {
|
||||
logger().log(E);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MicroService::SavePID() {
|
||||
try {
|
||||
std::ofstream O;
|
||||
O.open(MicroService::instance().DataDir() + "/pidfile",std::ios::binary | std::ios::trunc);
|
||||
O << Poco::Process::id();
|
||||
O.close();
|
||||
} catch (...)
|
||||
{
|
||||
std::cout << "Could not save system ID" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int MicroService::main([[maybe_unused]] const ArgVec &args) {
|
||||
MicroServiceErrorHandler ErrorHandler(*this);
|
||||
Poco::ErrorHandler::set(&ErrorHandler);
|
||||
|
||||
if (!HelpRequested_) {
|
||||
SavePID();
|
||||
|
||||
Poco::Logger &logger = Poco::Logger::get(DAEMON_APP_NAME);
|
||||
logger.notice(fmt::format("Starting {} version {}.",DAEMON_APP_NAME, Version()));
|
||||
|
||||
if(Poco::Net::Socket::supportsIPv6())
|
||||
logger.information("System supports IPv6.");
|
||||
else
|
||||
logger.information("System does NOT support IPv6.");
|
||||
|
||||
if (config().getBool("application.runAsDaemon", false)) {
|
||||
logger.information("Starting as a daemon.");
|
||||
}
|
||||
|
||||
logger.information(fmt::format("System ID set to {}",ID_));
|
||||
StartSubSystemServers();
|
||||
waitForTerminationRequest();
|
||||
StopSubSystemServers();
|
||||
logger.notice(fmt::format("Stopped {}...",DAEMON_APP_NAME));
|
||||
}
|
||||
|
||||
return Application::EXIT_OK;
|
||||
}
|
||||
|
||||
void MicroService::AddActivity(const std::string &Activity) {
|
||||
if(!DataDir_.empty()) {
|
||||
std::string ActivityFile{ DataDir_ + "/activity.log"};
|
||||
try {
|
||||
std::ofstream of(ActivityFile,std::ios_base::app | std::ios_base::out );
|
||||
auto t = std::chrono::system_clock::now();
|
||||
std::time_t now = std::chrono::system_clock::to_time_t(t);
|
||||
of << Activity << " at " << std::ctime(&now) ;
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string MicroService::Sign(Poco::JWT::Token &T, const std::string &Algo) {
|
||||
if(NoBuiltInCrypto_) {
|
||||
return T.toString();
|
||||
} else {
|
||||
return Signer_.sign(T,Algo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
132
src/framework/MicroServiceExtra.h
Normal file
132
src/framework/MicroServiceExtra.h
Normal file
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-26.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "Poco/BasicEvent.h"
|
||||
#include "Poco/ExpireLRUCache.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
class ConfigurationEntry {
|
||||
public:
|
||||
template <typename T> explicit ConfigurationEntry(T def) :
|
||||
Default_(def),
|
||||
Current_(def){
|
||||
}
|
||||
|
||||
template <typename T> explicit ConfigurationEntry(T def, T cur, const std::string &Hint="") :
|
||||
Default_(def),
|
||||
Current_(cur),
|
||||
Hint_(Hint){
|
||||
}
|
||||
|
||||
inline ConfigurationEntry()=default;
|
||||
inline ~ConfigurationEntry()=default;
|
||||
|
||||
template <typename T> explicit operator T () const { return std::get<T>(Current_); }
|
||||
inline ConfigurationEntry & operator=(const char *v) { Current_ = std::string(v); return *this;}
|
||||
template <typename T> ConfigurationEntry & operator=(T v) { Current_ = (T) v; return *this;}
|
||||
|
||||
void reset() {
|
||||
Current_ = Default_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<bool,uint64_t,std::string> Default_, Current_;
|
||||
std::string Hint_;
|
||||
};
|
||||
inline std::string to_string(const ConfigurationEntry &v) { return (std::string) v; }
|
||||
|
||||
typedef std::map<std::string,ConfigurationEntry> ConfigurationMap_t;
|
||||
|
||||
template <typename T> class FIFO {
|
||||
public:
|
||||
explicit FIFO(uint32_t Size) :
|
||||
Size_(Size) {
|
||||
Buffer_ = new T [Size_];
|
||||
}
|
||||
|
||||
~FIFO() {
|
||||
delete [] Buffer_;
|
||||
}
|
||||
|
||||
mutable Poco::BasicEvent<bool> Writable_;
|
||||
mutable Poco::BasicEvent<bool> Readable_;
|
||||
|
||||
inline bool Read(T &t) {
|
||||
{
|
||||
std::lock_guard M(Mutex_);
|
||||
if (Write_ == Read_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
t = Buffer_[Read_++];
|
||||
if (Read_ == Size_) {
|
||||
Read_ = 0;
|
||||
}
|
||||
Used_--;
|
||||
}
|
||||
bool flag = true;
|
||||
Writable_.notify(this, flag);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool Write(const T &t) {
|
||||
{
|
||||
std::lock_guard M(Mutex_);
|
||||
|
||||
Buffer_[Write_++] = t;
|
||||
if (Write_ == Size_) {
|
||||
Write_ = 0;
|
||||
}
|
||||
Used_++;
|
||||
MaxEverUsed_ = std::max(Used_,MaxEverUsed_);
|
||||
}
|
||||
bool flag = true;
|
||||
Readable_.notify(this, flag);
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool isFull() {
|
||||
std::lock_guard M(Mutex_);
|
||||
return Used_==Buffer_->capacity();
|
||||
}
|
||||
|
||||
inline auto MaxEverUser() const { return MaxEverUsed_; }
|
||||
|
||||
private:
|
||||
std::recursive_mutex Mutex_;
|
||||
uint32_t Size_=0;
|
||||
uint32_t Read_=0;
|
||||
uint32_t Write_=0;
|
||||
uint32_t Used_=0;
|
||||
uint32_t MaxEverUsed_=0;
|
||||
T * Buffer_ = nullptr;
|
||||
};
|
||||
|
||||
template <class Record, typename KeyType = std::string, int Size=256, int Expiry=60000> class RecordCache {
|
||||
public:
|
||||
explicit RecordCache( KeyType Record::* Q) :
|
||||
MemberOffset(Q){
|
||||
};
|
||||
inline auto update(const Record &R) {
|
||||
return Cache_.update(R.*MemberOffset, R);
|
||||
}
|
||||
inline auto get(const KeyType &K) {
|
||||
return Cache_.get(K);
|
||||
}
|
||||
inline auto remove(const KeyType &K) {
|
||||
return Cache_.remove(K);
|
||||
}
|
||||
inline auto remove(const Record &R) {
|
||||
return Cache_.remove(R.*MemberOffset);
|
||||
}
|
||||
private:
|
||||
KeyType Record::* MemberOffset;
|
||||
Poco::ExpireLRUCache<KeyType,Record> Cache_{Size,Expiry};
|
||||
};
|
||||
}
|
||||
113
src/framework/MicroServiceFuncs.cpp
Normal file
113
src/framework/MicroServiceFuncs.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include "framework/MicroService.h"
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
const std::string &MicroServiceDataDirectory() { return MicroService::instance().DataDir(); }
|
||||
|
||||
Types::MicroServiceMetaVec MicroServiceGetServices(const std::string &Type) {
|
||||
return MicroService::instance().GetServices(Type);
|
||||
}
|
||||
|
||||
Types::MicroServiceMetaVec MicroServiceGetServices() {
|
||||
return MicroService::instance().GetServices();
|
||||
}
|
||||
|
||||
std::string MicroServicePublicEndPoint() { return MicroService::instance().PublicEndPoint(); }
|
||||
|
||||
std::string MicroServiceConfigGetString(const std::string &Key, const std::string &DefaultValue) {
|
||||
return MicroService::instance().ConfigGetString(Key, DefaultValue);
|
||||
}
|
||||
|
||||
bool MicroServiceConfigGetBool(const std::string &Key, bool DefaultValue) {
|
||||
return MicroService::instance().ConfigGetBool(Key, DefaultValue);
|
||||
}
|
||||
|
||||
std::uint64_t MicroServiceConfigGetInt(const std::string &Key, std::uint64_t DefaultValue) {
|
||||
return MicroService::instance().ConfigGetInt(Key, DefaultValue);
|
||||
}
|
||||
|
||||
std::string MicroServicePrivateEndPoint() { return MicroService::instance().PrivateEndPoint(); }
|
||||
|
||||
std::uint64_t MicroServiceID() { return MicroService::instance().ID(); }
|
||||
|
||||
bool MicroServiceIsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request) {
|
||||
return MicroService::instance().IsValidAPIKEY(Request);
|
||||
}
|
||||
|
||||
bool MicroServiceNoAPISecurity() { return MicroService::instance().NoAPISecurity(); }
|
||||
|
||||
void MicroServiceLoadConfigurationFile() { MicroService::instance().LoadConfigurationFile(); }
|
||||
|
||||
void MicroServiceReload() { MicroService::instance().Reload(); }
|
||||
|
||||
void MicroServiceReload(const std::string &Type) { MicroService::instance().Reload(Type); }
|
||||
|
||||
const Types::StringVec MicroServiceGetLogLevelNames() {
|
||||
return MicroService::instance().GetLogLevelNames();
|
||||
}
|
||||
|
||||
const Types::StringVec MicroServiceGetSubSystems() {
|
||||
return MicroService::instance().GetSubSystems();
|
||||
}
|
||||
|
||||
Types::StringPairVec MicroServiceGetLogLevels() { return MicroService::instance().GetLogLevels(); }
|
||||
|
||||
bool MicroServiceSetSubsystemLogLevel(const std::string &SubSystem, const std::string &Level) {
|
||||
return MicroService::instance().SetSubsystemLogLevel(SubSystem, Level);
|
||||
}
|
||||
|
||||
void MicroServiceGetExtraConfiguration(Poco::JSON::Object &Answer) {
|
||||
MicroService::instance().GetExtraConfiguration(Answer);
|
||||
}
|
||||
|
||||
std::string MicroServiceVersion() { return MicroService::instance().Version(); }
|
||||
|
||||
std::uint64_t MicroServiceUptimeTotalSeconds() {
|
||||
return MicroService::instance().uptime().totalSeconds();
|
||||
}
|
||||
|
||||
std::uint64_t MicroServiceStartTimeEpochTime() {
|
||||
return MicroService::instance().startTime().epochTime();
|
||||
}
|
||||
|
||||
std::string MicroServiceGetUIURI() { return MicroService::instance().GetUIURI(); }
|
||||
|
||||
const SubSystemVec MicroServiceGetFullSubSystems() {
|
||||
return MicroService::instance().GetFullSubSystems();
|
||||
}
|
||||
|
||||
std::string MicroServiceCreateUUID() { return MicroService::CreateUUID(); }
|
||||
|
||||
std::uint64_t MicroServiceDaemonBusTimer() { return MicroService::instance().DaemonBusTimer(); }
|
||||
|
||||
std::string MicroServiceMakeSystemEventMessage(const std::string &Type) {
|
||||
return MicroService::instance().MakeSystemEventMessage(Type);
|
||||
}
|
||||
|
||||
Poco::ThreadPool &MicroServiceTimerPool() { return MicroService::instance().TimerPool(); }
|
||||
|
||||
std::string MicroServiceConfigPath(const std::string &Key,
|
||||
const std::string &DefaultValue) {
|
||||
return MicroService::instance().ConfigPath(Key, DefaultValue);
|
||||
}
|
||||
|
||||
std::string MicroServiceWWWAssetsDir() {
|
||||
return MicroService::instance().WWWAssetsDir();
|
||||
}
|
||||
|
||||
std::uint64_t MicroServiceRandom(std::uint64_t Start,std::uint64_t End) {
|
||||
return MicroService::instance().Random(Start, End);
|
||||
}
|
||||
|
||||
std::string MicroServiceSign(Poco::JWT::Token &T, const std::string &Algo) {
|
||||
return MicroService::instance().Sign(T, Algo);
|
||||
}
|
||||
|
||||
std::string MicroServiceGetPublicAPIEndPoint() {
|
||||
return MicroService::instance().GetPublicAPIEndPoint();
|
||||
}
|
||||
}
|
||||
54
src/framework/MicroServiceFuncs.h
Normal file
54
src/framework/MicroServiceFuncs.h
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "framework/OpenWifiTypes.h"
|
||||
|
||||
#include "Poco/Net/HTTPServerRequest.h"
|
||||
#include "Poco/JSON/Object.h"
|
||||
#include "Poco/ThreadPool.h"
|
||||
#include "Poco/JWT/Token.h"
|
||||
|
||||
|
||||
namespace OpenWifi {
|
||||
class SubSystemServer;
|
||||
using SubSystemVec=std::vector<SubSystemServer *>;
|
||||
const std::string & MicroServiceDataDirectory();
|
||||
Types::MicroServiceMetaVec MicroServiceGetServices(const std::string & Type);
|
||||
Types::MicroServiceMetaVec MicroServiceGetServices();
|
||||
std::string MicroServicePublicEndPoint();
|
||||
std::string MicroServiceConfigGetString(const std::string &Key, const std::string &DefaultValue);
|
||||
bool MicroServiceConfigGetBool(const std::string &Key, bool DefaultValue);
|
||||
std::uint64_t MicroServiceConfigGetInt(const std::string &Key, std::uint64_t DefaultValue);
|
||||
std::string MicroServicePrivateEndPoint();
|
||||
std::uint64_t MicroServiceID();
|
||||
bool MicroServiceIsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request);
|
||||
bool MicroServiceNoAPISecurity();
|
||||
void MicroServiceLoadConfigurationFile();
|
||||
void MicroServiceReload();
|
||||
void MicroServiceReload(const std::string &Type);
|
||||
const Types::StringVec MicroServiceGetLogLevelNames();
|
||||
const Types::StringVec MicroServiceGetSubSystems();
|
||||
Types::StringPairVec MicroServiceGetLogLevels();
|
||||
bool MicroServiceSetSubsystemLogLevel(const std::string &SubSystem, const std::string &Level);
|
||||
void MicroServiceGetExtraConfiguration(Poco::JSON::Object &Answer);
|
||||
std::string MicroServiceVersion();
|
||||
std::uint64_t MicroServiceUptimeTotalSeconds();
|
||||
std::uint64_t MicroServiceStartTimeEpochTime();
|
||||
std::string MicroServiceGetUIURI();
|
||||
const SubSystemVec MicroServiceGetFullSubSystems();
|
||||
std::string MicroServiceCreateUUID();
|
||||
std::uint64_t MicroServiceDaemonBusTimer();
|
||||
std::string MicroServiceMakeSystemEventMessage( const std::string & Type );
|
||||
Poco::ThreadPool & MicroServiceTimerPool();
|
||||
std::string MicroServiceConfigPath(const std::string &Key,
|
||||
const std::string &DefaultValue);
|
||||
std::string MicroServiceWWWAssetsDir();
|
||||
std::uint64_t MicroServiceRandom(std::uint64_t Start,std::uint64_t End);
|
||||
std::string MicroServiceSign(Poco::JWT::Token &T, const std::string &Algo);
|
||||
std::string MicroServiceGetPublicAPIEndPoint();
|
||||
}
|
||||
22
src/framework/MicroServiceNames.h
Normal file
22
src/framework/MicroServiceNames.h
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
static const std::string uSERVICE_SECURITY{"owsec"};
|
||||
static const std::string uSERVICE_GATEWAY{"owgw"};
|
||||
static const std::string uSERVICE_FIRMWARE{ "owfms"};
|
||||
static const std::string uSERVICE_TOPOLOGY{ "owtopo"};
|
||||
static const std::string uSERVICE_PROVISIONING{ "owprov"};
|
||||
static const std::string uSERVICE_OWLS{ "owls"};
|
||||
static const std::string uSERVICE_SUBCRIBER{ "owsub"};
|
||||
static const std::string uSERVICE_INSTALLER{ "owinst"};
|
||||
static const std::string uSERVICE_ANALYTICS{ "owanalytics"};
|
||||
static const std::string uSERVICE_OWRRM{ "owrrm"};
|
||||
|
||||
}
|
||||
289
src/framework/OpenAPIRequests.cpp
Normal file
289
src/framework/OpenAPIRequests.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include "OpenAPIRequests.h"
|
||||
|
||||
#include "Poco/Logger.h"
|
||||
#include "Poco/URI.h"
|
||||
#include "Poco/Net/HTTPRequest.h"
|
||||
#include "Poco/Net/HTTPSClientSession.h"
|
||||
#include "Poco/JSON/Parser.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestGet::Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken) {
|
||||
try {
|
||||
|
||||
auto Services = MicroServiceGetServices(Type_);
|
||||
for(auto const &Svc:Services) {
|
||||
Poco::URI URI(Svc.PrivateEndPoint);
|
||||
|
||||
auto Secure = (URI.getScheme() == "https");
|
||||
|
||||
URI.setPath(EndPoint_);
|
||||
for (const auto &qp : QueryData_)
|
||||
URI.addQueryParameter(qp.first, qp.second);
|
||||
|
||||
std::string Path(URI.getPathAndQuery());
|
||||
Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_GET,
|
||||
Path,
|
||||
Poco::Net::HTTPMessage::HTTP_1_1);
|
||||
|
||||
poco_debug(Poco::Logger::get("REST-CALLER-GET"),fmt::format(" {}", URI.toString()));
|
||||
|
||||
if(BearerToken.empty()) {
|
||||
Request.add("X-API-KEY", Svc.AccessKey);
|
||||
Request.add("X-INTERNAL-NAME", MicroServicePublicEndPoint());
|
||||
} else {
|
||||
// Authorization: Bearer ${token}
|
||||
Request.add("Authorization", "Bearer " + BearerToken);
|
||||
}
|
||||
|
||||
if(Secure) {
|
||||
Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort());
|
||||
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
||||
|
||||
Session.sendRequest(Request);
|
||||
|
||||
Poco::Net::HTTPResponse Response;
|
||||
std::istream &is = Session.receiveResponse(Response);
|
||||
if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) {
|
||||
Poco::JSON::Parser P;
|
||||
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
||||
}
|
||||
return Response.getStatus();
|
||||
} else {
|
||||
Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort());
|
||||
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
||||
|
||||
Session.sendRequest(Request);
|
||||
|
||||
Poco::Net::HTTPResponse Response;
|
||||
std::istream &is = Session.receiveResponse(Response);
|
||||
if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) {
|
||||
Poco::JSON::Parser P;
|
||||
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
||||
}
|
||||
return Response.getStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const Poco::Exception &E)
|
||||
{
|
||||
Poco::Logger::get("REST-CALLER-GET").log(E);
|
||||
}
|
||||
return Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT;
|
||||
}
|
||||
|
||||
Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestPut::Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken) {
|
||||
try {
|
||||
auto Services = MicroServiceGetServices(Type_);
|
||||
for(auto const &Svc:Services) {
|
||||
Poco::URI URI(Svc.PrivateEndPoint);
|
||||
|
||||
auto Secure = (URI.getScheme() == "https");
|
||||
|
||||
URI.setPath(EndPoint_);
|
||||
for (const auto &qp : QueryData_)
|
||||
URI.addQueryParameter(qp.first, qp.second);
|
||||
|
||||
poco_debug(Poco::Logger::get("REST-CALLER-PUT"),fmt::format("{}", URI.toString()));
|
||||
|
||||
std::string Path(URI.getPathAndQuery());
|
||||
|
||||
Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_PUT,
|
||||
Path,
|
||||
Poco::Net::HTTPMessage::HTTP_1_1);
|
||||
std::ostringstream obody;
|
||||
Poco::JSON::Stringifier::stringify(Body_,obody);
|
||||
|
||||
Request.setContentType("application/json");
|
||||
Request.setContentLength(obody.str().size());
|
||||
|
||||
if(BearerToken.empty()) {
|
||||
Request.add("X-API-KEY", Svc.AccessKey);
|
||||
Request.add("X-INTERNAL-NAME", MicroServicePublicEndPoint());
|
||||
} else {
|
||||
// Authorization: Bearer ${token}
|
||||
Request.add("Authorization", "Bearer " + BearerToken);
|
||||
}
|
||||
|
||||
if(Secure) {
|
||||
Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort());
|
||||
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
||||
|
||||
std::ostream &os = Session.sendRequest(Request);
|
||||
os << obody.str();
|
||||
|
||||
Poco::Net::HTTPResponse Response;
|
||||
std::istream &is = Session.receiveResponse(Response);
|
||||
if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) {
|
||||
Poco::JSON::Parser P;
|
||||
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
||||
} else {
|
||||
Poco::JSON::Parser P;
|
||||
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
||||
}
|
||||
return Response.getStatus();
|
||||
} else {
|
||||
Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort());
|
||||
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
||||
|
||||
std::ostream &os = Session.sendRequest(Request);
|
||||
os << obody.str();
|
||||
|
||||
Poco::Net::HTTPResponse Response;
|
||||
std::istream &is = Session.receiveResponse(Response);
|
||||
if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) {
|
||||
Poco::JSON::Parser P;
|
||||
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
||||
} else {
|
||||
Poco::JSON::Parser P;
|
||||
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
||||
}
|
||||
return Response.getStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const Poco::Exception &E)
|
||||
{
|
||||
Poco::Logger::get("REST-CALLER-PUT").log(E);
|
||||
}
|
||||
return Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT;
|
||||
}
|
||||
|
||||
Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestPost::Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken) {
|
||||
try {
|
||||
auto Services = MicroServiceGetServices(Type_);
|
||||
|
||||
for(auto const &Svc:Services) {
|
||||
Poco::URI URI(Svc.PrivateEndPoint);
|
||||
|
||||
|
||||
auto Secure = (URI.getScheme() == "https");
|
||||
|
||||
URI.setPath(EndPoint_);
|
||||
for (const auto &qp : QueryData_)
|
||||
URI.addQueryParameter(qp.first, qp.second);
|
||||
|
||||
poco_debug(Poco::Logger::get("REST-CALLER-POST"),fmt::format(" {}", URI.toString()));
|
||||
|
||||
std::string Path(URI.getPathAndQuery());
|
||||
|
||||
Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_POST,
|
||||
Path,
|
||||
Poco::Net::HTTPMessage::HTTP_1_1);
|
||||
std::ostringstream obody;
|
||||
Poco::JSON::Stringifier::stringify(Body_,obody);
|
||||
|
||||
Request.setContentType("application/json");
|
||||
Request.setContentLength(obody.str().size());
|
||||
|
||||
if(BearerToken.empty()) {
|
||||
Request.add("X-API-KEY", Svc.AccessKey);
|
||||
Request.add("X-INTERNAL-NAME", MicroServicePublicEndPoint());
|
||||
} else {
|
||||
// Authorization: Bearer ${token}
|
||||
Request.add("Authorization", "Bearer " + BearerToken);
|
||||
}
|
||||
|
||||
if(Secure) {
|
||||
Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort());
|
||||
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
||||
std::ostream &os = Session.sendRequest(Request);
|
||||
os << obody.str();
|
||||
|
||||
Poco::Net::HTTPResponse Response;
|
||||
std::istream &is = Session.receiveResponse(Response);
|
||||
if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) {
|
||||
Poco::JSON::Parser P;
|
||||
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
||||
} else {
|
||||
Poco::JSON::Parser P;
|
||||
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
||||
}
|
||||
return Response.getStatus();
|
||||
} else {
|
||||
Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort());
|
||||
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
||||
std::ostream &os = Session.sendRequest(Request);
|
||||
os << obody.str();
|
||||
|
||||
Poco::Net::HTTPResponse Response;
|
||||
std::istream &is = Session.receiveResponse(Response);
|
||||
if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) {
|
||||
Poco::JSON::Parser P;
|
||||
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
||||
} else {
|
||||
Poco::JSON::Parser P;
|
||||
ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
|
||||
}
|
||||
return Response.getStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const Poco::Exception &E)
|
||||
{
|
||||
Poco::Logger::get("REST-CALLER-POST").log(E);
|
||||
}
|
||||
return Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT;
|
||||
}
|
||||
|
||||
Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestDelete::Do(const std::string & BearerToken) {
|
||||
try {
|
||||
auto Services = MicroServiceGetServices(Type_);
|
||||
|
||||
for(auto const &Svc:Services) {
|
||||
Poco::URI URI(Svc.PrivateEndPoint);
|
||||
|
||||
auto Secure = (URI.getScheme() == "https");
|
||||
|
||||
URI.setPath(EndPoint_);
|
||||
for (const auto &qp : QueryData_)
|
||||
URI.addQueryParameter(qp.first, qp.second);
|
||||
|
||||
poco_debug(Poco::Logger::get("REST-CALLER-DELETE"),fmt::format(" {}", URI.toString()));
|
||||
|
||||
std::string Path(URI.getPathAndQuery());
|
||||
|
||||
Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_DELETE,
|
||||
Path,
|
||||
Poco::Net::HTTPMessage::HTTP_1_1);
|
||||
if(BearerToken.empty()) {
|
||||
Request.add("X-API-KEY", Svc.AccessKey);
|
||||
Request.add("X-INTERNAL-NAME", MicroServicePublicEndPoint());
|
||||
} else {
|
||||
// Authorization: Bearer ${token}
|
||||
Request.add("Authorization", "Bearer " + BearerToken);
|
||||
}
|
||||
|
||||
if(Secure) {
|
||||
Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort());
|
||||
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
||||
Session.sendRequest(Request);
|
||||
Poco::Net::HTTPResponse Response;
|
||||
Session.receiveResponse(Response);
|
||||
return Response.getStatus();
|
||||
} else {
|
||||
Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort());
|
||||
Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000));
|
||||
Session.sendRequest(Request);
|
||||
Poco::Net::HTTPResponse Response;
|
||||
Session.receiveResponse(Response);
|
||||
return Response.getStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const Poco::Exception &E)
|
||||
{
|
||||
Poco::Logger::get("REST-CALLER-DELETE").log(E);
|
||||
}
|
||||
return Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT;
|
||||
}
|
||||
|
||||
|
||||
} // namespace OpenWifi
|
||||
98
src/framework/OpenAPIRequests.h
Normal file
98
src/framework/OpenAPIRequests.h
Normal file
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Poco/JSON/Object.h"
|
||||
#include "Poco/Net/HTTPServerResponse.h"
|
||||
|
||||
#include "framework/OpenWifiTypes.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
class OpenAPIRequestGet {
|
||||
public:
|
||||
explicit OpenAPIRequestGet( const std::string & Type,
|
||||
const std::string & EndPoint,
|
||||
const Types::StringPairVec & QueryData,
|
||||
uint64_t msTimeout):
|
||||
Type_(Type),
|
||||
EndPoint_(EndPoint),
|
||||
QueryData_(QueryData),
|
||||
msTimeout_(msTimeout) {};
|
||||
Poco::Net::HTTPServerResponse::HTTPStatus Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken = "");
|
||||
private:
|
||||
std::string Type_;
|
||||
std::string EndPoint_;
|
||||
Types::StringPairVec QueryData_;
|
||||
uint64_t msTimeout_;
|
||||
};
|
||||
|
||||
class OpenAPIRequestPut {
|
||||
public:
|
||||
explicit OpenAPIRequestPut( const std::string & Type,
|
||||
const std::string & EndPoint,
|
||||
const Types::StringPairVec & QueryData,
|
||||
const Poco::JSON::Object & Body,
|
||||
uint64_t msTimeout):
|
||||
Type_(Type),
|
||||
EndPoint_(EndPoint),
|
||||
QueryData_(QueryData),
|
||||
msTimeout_(msTimeout),
|
||||
Body_(Body){};
|
||||
|
||||
Poco::Net::HTTPServerResponse::HTTPStatus Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken = "");
|
||||
|
||||
private:
|
||||
std::string Type_;
|
||||
std::string EndPoint_;
|
||||
Types::StringPairVec QueryData_;
|
||||
uint64_t msTimeout_;
|
||||
Poco::JSON::Object Body_;
|
||||
};
|
||||
|
||||
class OpenAPIRequestPost {
|
||||
public:
|
||||
explicit OpenAPIRequestPost( const std::string & Type,
|
||||
const std::string & EndPoint,
|
||||
const Types::StringPairVec & QueryData,
|
||||
const Poco::JSON::Object & Body,
|
||||
uint64_t msTimeout):
|
||||
Type_(Type),
|
||||
EndPoint_(EndPoint),
|
||||
QueryData_(QueryData),
|
||||
msTimeout_(msTimeout),
|
||||
Body_(Body){};
|
||||
Poco::Net::HTTPServerResponse::HTTPStatus Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken = "");
|
||||
private:
|
||||
std::string Type_;
|
||||
std::string EndPoint_;
|
||||
Types::StringPairVec QueryData_;
|
||||
uint64_t msTimeout_;
|
||||
Poco::JSON::Object Body_;
|
||||
};
|
||||
|
||||
class OpenAPIRequestDelete {
|
||||
public:
|
||||
explicit OpenAPIRequestDelete( const std::string & Type,
|
||||
const std::string & EndPoint,
|
||||
const Types::StringPairVec & QueryData,
|
||||
uint64_t msTimeout):
|
||||
Type_(Type),
|
||||
EndPoint_(EndPoint),
|
||||
QueryData_(QueryData),
|
||||
msTimeout_(msTimeout){};
|
||||
Poco::Net::HTTPServerResponse::HTTPStatus Do(const std::string & BearerToken = "");
|
||||
|
||||
private:
|
||||
std::string Type_;
|
||||
std::string EndPoint_;
|
||||
Types::StringPairVec QueryData_;
|
||||
uint64_t msTimeout_;
|
||||
Poco::JSON::Object Body_;
|
||||
};
|
||||
|
||||
} // namespace OpenWifi
|
||||
@@ -28,6 +28,19 @@ namespace OpenWifi::Types {
|
||||
typedef std::string UUID_t;
|
||||
typedef std::vector<UUID_t> UUIDvec_t;
|
||||
typedef std::map<std::string,std::map<uint32_t,uint64_t>> Counted3DMapSII;
|
||||
|
||||
struct MicroServiceMeta {
|
||||
uint64_t Id=0;
|
||||
std::string Type;
|
||||
std::string PrivateEndPoint;
|
||||
std::string PublicEndPoint;
|
||||
std::string AccessKey;
|
||||
std::string Version;
|
||||
uint64_t LastUpdate=0;
|
||||
};
|
||||
|
||||
typedef std::map<std::string, MicroServiceMeta> MicroServiceMetaMap;
|
||||
typedef std::vector<MicroServiceMeta> MicroServiceMetaVec;
|
||||
}
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
27
src/framework/RESTAPI_ExtServer.cpp
Normal file
27
src/framework/RESTAPI_ExtServer.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include "framework/RESTAPI_ExtServer.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
Poco::Net::HTTPRequestHandler *ExtRequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest &Request) {
|
||||
try {
|
||||
Poco::URI uri(Request.getURI());
|
||||
auto TID = NextTransactionId_++;
|
||||
Utils::SetThreadName(fmt::format("x-rest:{}",TID).c_str());
|
||||
return RESTAPI_ExtServer()->CallServer(uri.getPath(), TID);
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Poco::Net::HTTPRequestHandler *RESTAPI_ExtServer::CallServer(const std::string &Path, uint64_t Id) {
|
||||
RESTAPIHandler::BindingMap Bindings;
|
||||
Utils::SetThreadName(fmt::format("x-rest:{}",Id).c_str());
|
||||
return RESTAPI_ExtRouter(Path, Bindings, Logger(), Server_, Id);
|
||||
}
|
||||
|
||||
}
|
||||
99
src/framework/RESTAPI_ExtServer.h
Normal file
99
src/framework/RESTAPI_ExtServer.h
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Poco/Net/HTTPServer.h"
|
||||
|
||||
#include "framework/SubSystemServer.h"
|
||||
#include "framework/RESTAPI_Handler.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
Poco::Net::HTTPRequestHandler * RESTAPI_ExtRouter(const std::string &Path, RESTAPIHandler::BindingMap &Bindings,
|
||||
Poco::Logger & L, RESTAPI_GenericServerAccounting & S, uint64_t Id);
|
||||
|
||||
class ExtRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory {
|
||||
public:
|
||||
ExtRequestHandlerFactory() = default;
|
||||
Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &Request) override;
|
||||
private:
|
||||
static inline std::atomic_uint64_t NextTransactionId_ = 1;
|
||||
};
|
||||
|
||||
class RESTAPI_ExtServer : public SubSystemServer {
|
||||
public:
|
||||
static auto instance() {
|
||||
static auto instance_ = new RESTAPI_ExtServer;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
inline int Start() override {
|
||||
Logger().information("Starting.");
|
||||
Server_.InitLogging();
|
||||
|
||||
for(const auto & Svr: ConfigServersList_) {
|
||||
|
||||
if(MicroServiceNoAPISecurity()) {
|
||||
Logger().information(fmt::format("Starting: {}:{}. Security has been disabled for APIs.", Svr.Address(), Svr.Port()));
|
||||
} else {
|
||||
Logger().information(fmt::format("Starting: {}:{} Keyfile:{} CertFile: {}", Svr.Address(), Svr.Port(),
|
||||
Svr.KeyFile(),Svr.CertFile()));
|
||||
Svr.LogCert(Logger());
|
||||
if (!Svr.RootCA().empty())
|
||||
Svr.LogCas(Logger());
|
||||
}
|
||||
|
||||
Poco::Net::HTTPServerParams::Ptr Params = new Poco::Net::HTTPServerParams;
|
||||
Params->setKeepAlive(true);
|
||||
Params->setName("ws:xrest");
|
||||
|
||||
std::unique_ptr<Poco::Net::HTTPServer> NewServer;
|
||||
if(MicroServiceNoAPISecurity()) {
|
||||
auto Sock{Svr.CreateSocket(Logger())};
|
||||
NewServer = std::make_unique<Poco::Net::HTTPServer>(new ExtRequestHandlerFactory, Pool_, Sock, Params);
|
||||
} else {
|
||||
auto Sock{Svr.CreateSecureSocket(Logger())};
|
||||
NewServer = std::make_unique<Poco::Net::HTTPServer>(new ExtRequestHandlerFactory, Pool_, Sock, Params);
|
||||
};
|
||||
NewServer->start();
|
||||
RESTServers_.push_back(std::move(NewServer));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline void Stop() override {
|
||||
Logger().information("Stopping...");
|
||||
for( const auto & svr : RESTServers_ )
|
||||
svr->stopAll(true);
|
||||
Pool_.stopAll();
|
||||
Pool_.joinAll();
|
||||
RESTServers_.clear();
|
||||
Logger().information("Stopped...");
|
||||
}
|
||||
|
||||
inline void reinitialize([[maybe_unused]] Poco::Util::Application &self) override {
|
||||
MicroServiceLoadConfigurationFile();
|
||||
Logger().information("Reinitializing.");
|
||||
Stop();
|
||||
Start();
|
||||
}
|
||||
|
||||
Poco::Net::HTTPRequestHandler *CallServer(const std::string &Path, uint64_t Id);
|
||||
const Poco::ThreadPool & Pool() { return Pool_; }
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Poco::Net::HTTPServer>> RESTServers_;
|
||||
Poco::ThreadPool Pool_{"x-rest",8,128};
|
||||
RESTAPI_GenericServerAccounting Server_;
|
||||
|
||||
RESTAPI_ExtServer() noexcept:
|
||||
SubSystemServer("RESTAPI_ExtServer", "REST-XSRV", "openwifi.restapi")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
inline auto RESTAPI_ExtServer() { return RESTAPI_ExtServer::instance(); };
|
||||
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#include "Poco/JSON/Parser.h"
|
||||
#include "Poco/JSON/Stringifier.h"
|
||||
|
||||
#include "Daemon.h"
|
||||
#ifdef TIP_GATEWAY_SERVICE
|
||||
#include "DeviceRegistry.h"
|
||||
#include "CapabilitiesCache.h"
|
||||
#endif
|
||||
|
||||
#include "RESTAPI_GWobjects.h"
|
||||
#include "framework/MicroService.h"
|
||||
|
||||
using OpenWifi::RESTAPI_utils::field_to_json;
|
||||
using OpenWifi::RESTAPI_utils::field_from_json;
|
||||
using OpenWifi::RESTAPI_utils::EmbedDocument;
|
||||
|
||||
namespace OpenWifi::GWObjects {
|
||||
|
||||
void Device::to_json(Poco::JSON::Object &Obj) const {
|
||||
field_to_json(Obj,"serialNumber", SerialNumber);
|
||||
#ifdef TIP_GATEWAY_SERVICE
|
||||
field_to_json(Obj,"deviceType", CapabilitiesCache::instance()->GetPlatform(Compatible));
|
||||
#endif
|
||||
field_to_json(Obj,"macAddress", MACAddress);
|
||||
field_to_json(Obj,"manufacturer", Manufacturer);
|
||||
field_to_json(Obj,"UUID", UUID);
|
||||
EmbedDocument("configuration", Obj, Configuration);
|
||||
field_to_json(Obj,"notes", Notes);
|
||||
field_to_json(Obj,"createdTimestamp", CreationTimestamp);
|
||||
field_to_json(Obj,"lastConfigurationChange", LastConfigurationChange);
|
||||
field_to_json(Obj,"lastConfigurationDownload", LastConfigurationDownload);
|
||||
field_to_json(Obj,"lastFWUpdate", LastFWUpdate);
|
||||
field_to_json(Obj,"owner", Owner);
|
||||
field_to_json(Obj,"location", Location);
|
||||
field_to_json(Obj,"venue", Venue);
|
||||
field_to_json(Obj,"firmware", Firmware);
|
||||
field_to_json(Obj,"compatible", Compatible);
|
||||
field_to_json(Obj,"fwUpdatePolicy", FWUpdatePolicy);
|
||||
field_to_json(Obj,"devicePassword", DevicePassword);
|
||||
field_to_json(Obj,"subscriber", subscriber);
|
||||
field_to_json(Obj,"entity", entity);
|
||||
field_to_json(Obj,"modified", modified);
|
||||
field_to_json(Obj,"locale", locale);
|
||||
}
|
||||
|
||||
void Device::to_json_with_status(Poco::JSON::Object &Obj) const {
|
||||
to_json(Obj);
|
||||
|
||||
#ifdef TIP_GATEWAY_SERVICE
|
||||
ConnectionState ConState;
|
||||
|
||||
if (DeviceRegistry()->GetState(SerialNumber, ConState)) {
|
||||
ConState.to_json(Obj);
|
||||
} else {
|
||||
field_to_json(Obj,"ipAddress", "");
|
||||
field_to_json(Obj,"txBytes", (uint64_t) 0);
|
||||
field_to_json(Obj,"rxBytes", (uint64_t )0);
|
||||
field_to_json(Obj,"messageCount", (uint64_t )0);
|
||||
field_to_json(Obj,"connected", false);
|
||||
field_to_json(Obj,"lastContact", "");
|
||||
field_to_json(Obj,"verifiedCertificate", "NO_CERTIFICATE");
|
||||
field_to_json(Obj,"associations_2G", (uint64_t) 0);
|
||||
field_to_json(Obj,"associations_5G", (uint64_t) 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Device::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
field_from_json(Obj,"serialNumber",SerialNumber);
|
||||
field_from_json(Obj,"deviceType",DeviceType);
|
||||
field_from_json(Obj,"macAddress",MACAddress);
|
||||
field_from_json(Obj,"configuration",Configuration);
|
||||
field_from_json(Obj,"notes",Notes);
|
||||
field_from_json(Obj,"manufacturer",Manufacturer);
|
||||
field_from_json(Obj,"owner",Owner);
|
||||
field_from_json(Obj,"location",Location);
|
||||
field_from_json(Obj,"venue",Venue);
|
||||
field_from_json(Obj,"compatible",Compatible);
|
||||
field_from_json(Obj,"subscriber", subscriber);
|
||||
field_from_json(Obj,"entity", entity);
|
||||
field_from_json(Obj,"locale", locale);
|
||||
return true;
|
||||
} catch (const Poco::Exception &E) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Device::Print() const {
|
||||
std::cout << "Device: " << SerialNumber << " DeviceType:" << DeviceType << " MACAddress:" << MACAddress << " Manufacturer:"
|
||||
<< Manufacturer << " " << Configuration << std::endl;
|
||||
}
|
||||
|
||||
void Statistics::to_json(Poco::JSON::Object &Obj) const {
|
||||
EmbedDocument("data", Obj, Data);
|
||||
field_to_json(Obj,"UUID", UUID);
|
||||
field_to_json(Obj,"recorded", Recorded);
|
||||
}
|
||||
|
||||
void Capabilities::to_json(Poco::JSON::Object &Obj) const {
|
||||
EmbedDocument("capabilities", Obj, Capabilities);
|
||||
field_to_json(Obj,"firstUpdate", FirstUpdate);
|
||||
field_to_json(Obj,"lastUpdate", LastUpdate);
|
||||
}
|
||||
|
||||
void DeviceLog::to_json(Poco::JSON::Object &Obj) const {
|
||||
EmbedDocument("data", Obj, Data);
|
||||
field_to_json(Obj,"log", Log);
|
||||
field_to_json(Obj,"severity", Severity);
|
||||
field_to_json(Obj,"recorded", Recorded);
|
||||
field_to_json(Obj,"logType", LogType);
|
||||
field_to_json(Obj,"UUID", UUID);
|
||||
}
|
||||
|
||||
void HealthCheck::to_json(Poco::JSON::Object &Obj) const {
|
||||
EmbedDocument("values", Obj, Data);
|
||||
field_to_json(Obj,"UUID", UUID);
|
||||
field_to_json(Obj,"sanity", Sanity);
|
||||
field_to_json(Obj,"recorded", Recorded);
|
||||
}
|
||||
|
||||
void DefaultConfiguration::to_json(Poco::JSON::Object &Obj) const {
|
||||
EmbedDocument("configuration", Obj, Configuration);
|
||||
field_to_json(Obj,"name", Name);
|
||||
field_to_json(Obj,"modelIds", Models);
|
||||
field_to_json(Obj,"description", Description);
|
||||
field_to_json(Obj,"created", Created);
|
||||
field_to_json(Obj,"lastModified", LastModified);
|
||||
}
|
||||
|
||||
void CommandDetails::to_json(Poco::JSON::Object &Obj) const {
|
||||
EmbedDocument("details", Obj, Details);
|
||||
EmbedDocument("results", Obj, Results);
|
||||
field_to_json(Obj,"UUID", UUID);
|
||||
field_to_json(Obj,"serialNumber", SerialNumber);
|
||||
field_to_json(Obj,"command", Command);
|
||||
field_to_json(Obj,"errorText", ErrorText);
|
||||
field_to_json(Obj,"submittedBy", SubmittedBy);
|
||||
field_to_json(Obj,"status", Status);
|
||||
field_to_json(Obj,"submitted", Submitted);
|
||||
field_to_json(Obj,"executed", Executed);
|
||||
field_to_json(Obj,"completed", Completed);
|
||||
field_to_json(Obj,"when", RunAt);
|
||||
field_to_json(Obj,"errorCode", ErrorCode);
|
||||
field_to_json(Obj,"custom", Custom);
|
||||
field_to_json(Obj,"waitingForFile", WaitingForFile);
|
||||
field_to_json(Obj,"attachFile", AttachDate);
|
||||
field_to_json(Obj,"executionTime", executionTime);
|
||||
}
|
||||
|
||||
bool DefaultConfiguration::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
field_from_json(Obj,"name",Name);
|
||||
field_from_json(Obj,"configuration",Configuration);
|
||||
field_from_json(Obj,"modelIds",Models);
|
||||
field_from_json(Obj,"description",Description);
|
||||
return true;
|
||||
} catch (const Poco::Exception &E) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BlackListedDevice::to_json(Poco::JSON::Object &Obj) const {
|
||||
field_to_json(Obj,"serialNumber", serialNumber);
|
||||
field_to_json(Obj,"author", author);
|
||||
field_to_json(Obj,"reason", reason);
|
||||
field_to_json(Obj,"created", created);
|
||||
}
|
||||
|
||||
bool BlackListedDevice::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
field_from_json(Obj,"serialNumber",serialNumber);
|
||||
field_from_json(Obj,"author",author);
|
||||
field_from_json(Obj,"reason",reason);
|
||||
field_from_json(Obj,"created",created);
|
||||
return true;
|
||||
} catch (const Poco::Exception &E) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ConnectionState::to_json(Poco::JSON::Object &Obj) const {
|
||||
field_to_json(Obj,"ipAddress", Address);
|
||||
field_to_json(Obj,"txBytes", TX);
|
||||
field_to_json(Obj,"rxBytes", RX);
|
||||
field_to_json(Obj,"messageCount", MessageCount);
|
||||
field_to_json(Obj,"UUID", UUID);
|
||||
field_to_json(Obj,"connected", Connected);
|
||||
field_to_json(Obj,"firmware", Firmware);
|
||||
field_to_json(Obj,"lastContact", LastContact);
|
||||
field_to_json(Obj,"associations_2G", Associations_2G);
|
||||
field_to_json(Obj,"associations_5G", Associations_5G);
|
||||
field_to_json(Obj,"webSocketClients", webSocketClients);
|
||||
field_to_json(Obj,"websocketPackets", websocketPackets);
|
||||
field_to_json(Obj,"kafkaClients", kafkaClients);
|
||||
field_to_json(Obj,"kafkaPackets", kafkaPackets);
|
||||
field_to_json(Obj,"locale", locale);
|
||||
|
||||
switch(VerifiedCertificate) {
|
||||
case NO_CERTIFICATE:
|
||||
field_to_json(Obj,"verifiedCertificate", "NO_CERTIFICATE"); break;
|
||||
case VALID_CERTIFICATE:
|
||||
field_to_json(Obj,"verifiedCertificate", "VALID_CERTIFICATE"); break;
|
||||
case MISMATCH_SERIAL:
|
||||
field_to_json(Obj,"verifiedCertificate", "MISMATCH_SERIAL"); break;
|
||||
case VERIFIED:
|
||||
field_to_json(Obj,"verifiedCertificate", "VERIFIED"); break;
|
||||
default:
|
||||
field_to_json(Obj,"verifiedCertificate", "NO_CERTIFICATE"); break;
|
||||
}
|
||||
}
|
||||
|
||||
void RttySessionDetails::to_json(Poco::JSON::Object &Obj) const {
|
||||
field_to_json(Obj,"serialNumber", SerialNumber);
|
||||
field_to_json(Obj,"server", Server);
|
||||
field_to_json(Obj,"port", Port);
|
||||
field_to_json(Obj,"token",Token);
|
||||
field_to_json(Obj,"timeout", TimeOut);
|
||||
field_to_json(Obj,"connectionId",ConnectionId);
|
||||
field_to_json(Obj,"commandUUID",CommandUUID);
|
||||
field_to_json(Obj,"started", Started);
|
||||
field_to_json(Obj,"viewport",ViewPort);
|
||||
field_to_json(Obj,"password",DevicePassword);
|
||||
}
|
||||
|
||||
void Dashboard::to_json(Poco::JSON::Object &Obj) const {
|
||||
field_to_json(Obj,"commands",commands);
|
||||
field_to_json(Obj,"upTimes",upTimes);
|
||||
field_to_json(Obj,"memoryUsed",memoryUsed);
|
||||
field_to_json(Obj,"load1",load1);
|
||||
field_to_json(Obj,"load5",load5);
|
||||
field_to_json(Obj,"load15",load15);
|
||||
field_to_json(Obj,"vendors",vendors);
|
||||
field_to_json(Obj,"status",status);
|
||||
field_to_json(Obj,"deviceType",deviceType);
|
||||
field_to_json(Obj,"healths",healths);
|
||||
field_to_json(Obj,"certificates",certificates);
|
||||
field_to_json(Obj,"lastContact",lastContact);
|
||||
field_to_json(Obj,"associations",associations);
|
||||
field_to_json(Obj,"snapshot",snapshot);
|
||||
field_to_json(Obj,"numberOfDevices",numberOfDevices);
|
||||
}
|
||||
|
||||
void Dashboard::reset() {
|
||||
commands.clear();
|
||||
upTimes.clear();
|
||||
memoryUsed.clear();
|
||||
load1.clear();
|
||||
load5.clear();
|
||||
load15.clear();
|
||||
vendors.clear();
|
||||
status.clear();
|
||||
deviceType.clear();
|
||||
healths.clear();
|
||||
certificates.clear();
|
||||
lastContact.clear();
|
||||
associations.clear();
|
||||
numberOfDevices = 0 ;
|
||||
snapshot = OpenWifi::Now();
|
||||
}
|
||||
|
||||
void CapabilitiesModel::to_json(Poco::JSON::Object &Obj) const{
|
||||
field_to_json(Obj,"deviceType", deviceType);
|
||||
field_to_json(Obj,"capabilities", capabilities);
|
||||
};
|
||||
|
||||
void ScriptRequest::to_json(Poco::JSON::Object &Obj) const {
|
||||
field_to_json(Obj,"serialNumber",serialNumber);
|
||||
field_to_json(Obj,"timeout",timeout);
|
||||
field_to_json(Obj,"type",type);
|
||||
field_to_json(Obj,"script",script);
|
||||
field_to_json(Obj,"scriptId",scriptId);
|
||||
field_to_json(Obj,"when",when);
|
||||
}
|
||||
|
||||
bool ScriptRequest::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
field_from_json(Obj,"serialNumber",serialNumber);
|
||||
field_from_json(Obj,"timeout",timeout);
|
||||
field_from_json(Obj,"type",type);
|
||||
field_from_json(Obj,"script",script);
|
||||
field_from_json(Obj,"scriptId",scriptId);
|
||||
field_from_json(Obj,"when",when);
|
||||
return true;
|
||||
} catch (const Poco::Exception &E) {
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Poco/JSON/Object.h"
|
||||
#include "RESTAPI_SecurityObjects.h"
|
||||
|
||||
namespace OpenWifi::GWObjects {
|
||||
|
||||
enum CertificateValidation {
|
||||
NO_CERTIFICATE,
|
||||
VALID_CERTIFICATE,
|
||||
MISMATCH_SERIAL,
|
||||
VERIFIED
|
||||
};
|
||||
|
||||
struct ConnectionState {
|
||||
uint64_t MessageCount = 0 ;
|
||||
std::string Address;
|
||||
uint64_t UUID = 0 ;
|
||||
uint64_t PendingUUID = 0 ;
|
||||
uint64_t TX = 0, RX = 0;
|
||||
uint64_t Associations_2G=0;
|
||||
uint64_t Associations_5G=0;
|
||||
bool Connected = false;
|
||||
uint64_t LastContact=0;
|
||||
std::string Firmware;
|
||||
CertificateValidation VerifiedCertificate = NO_CERTIFICATE;
|
||||
std::string Compatible;
|
||||
uint64_t kafkaClients=0;
|
||||
uint64_t webSocketClients=0;
|
||||
uint64_t kafkaPackets=0;
|
||||
uint64_t websocketPackets=0;
|
||||
std::string locale;
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
};
|
||||
|
||||
struct Device {
|
||||
std::string SerialNumber;
|
||||
std::string DeviceType;
|
||||
std::string MACAddress;
|
||||
std::string Manufacturer;
|
||||
std::string Configuration;
|
||||
SecurityObjects::NoteInfoVec Notes;
|
||||
std::string Owner;
|
||||
std::string Location;
|
||||
std::string Firmware;
|
||||
std::string Compatible;
|
||||
std::string FWUpdatePolicy;
|
||||
uint64_t UUID = 0 ;
|
||||
uint64_t CreationTimestamp = 0 ;
|
||||
uint64_t LastConfigurationChange = 0 ;
|
||||
uint64_t LastConfigurationDownload = 0 ;
|
||||
uint64_t LastFWUpdate = 0 ;
|
||||
std::string Venue;
|
||||
std::string DevicePassword;
|
||||
std::string subscriber;
|
||||
std::string entity;
|
||||
uint64_t modified=0;
|
||||
std::string locale;
|
||||
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
void to_json_with_status(Poco::JSON::Object &Obj) const;
|
||||
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||
void Print() const;
|
||||
};
|
||||
|
||||
struct Statistics {
|
||||
std::string SerialNumber;
|
||||
uint64_t UUID = 0 ;
|
||||
std::string Data;
|
||||
uint64_t Recorded = 0;
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
};
|
||||
|
||||
struct HealthCheck {
|
||||
std::string SerialNumber;
|
||||
uint64_t UUID = 0 ;
|
||||
std::string Data;
|
||||
uint64_t Recorded = 0 ;
|
||||
uint64_t Sanity = 0 ;
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
};
|
||||
|
||||
struct Capabilities {
|
||||
std::string Capabilities;
|
||||
uint64_t FirstUpdate = 0 ;
|
||||
uint64_t LastUpdate = 0 ;
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
};
|
||||
|
||||
struct DeviceLog {
|
||||
enum Level {
|
||||
LOG_EMERG = 0, /* system is unusable */
|
||||
LOG_ALERT = 1, /* action must be taken immediately */
|
||||
LOG_CRIT = 2, /* critical conditions */
|
||||
LOG_ERR = 3, /* error conditions */
|
||||
LOG_WARNING = 4, /* warning conditions */
|
||||
LOG_NOTICE = 5, /* normal but significant condition */
|
||||
LOG_INFO = 6, /* informational */
|
||||
LOG_DEBUG = 7 /* debug-level messages */
|
||||
};
|
||||
std::string SerialNumber;
|
||||
std::string Log;
|
||||
std::string Data;
|
||||
uint64_t Severity = 0 ;
|
||||
uint64_t Recorded = 0 ;
|
||||
uint64_t LogType = 0 ;
|
||||
uint64_t UUID = 0 ;
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
};
|
||||
|
||||
struct DefaultConfiguration {
|
||||
std::string Name;
|
||||
std::string Configuration;
|
||||
Types::StringVec Models;
|
||||
std::string Description;
|
||||
uint64_t Created;
|
||||
uint64_t LastModified;
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||
};
|
||||
|
||||
struct CommandDetails {
|
||||
std::string UUID;
|
||||
std::string SerialNumber;
|
||||
std::string Command;
|
||||
std::string Status;
|
||||
std::string SubmittedBy;
|
||||
std::string Results;
|
||||
std::string Details;
|
||||
std::string ErrorText;
|
||||
uint64_t Submitted = time(nullptr);
|
||||
uint64_t Executed = 0;
|
||||
uint64_t Completed = 0 ;
|
||||
uint64_t RunAt = 0 ;
|
||||
uint64_t ErrorCode = 0 ;
|
||||
uint64_t Custom = 0 ;
|
||||
uint64_t WaitingForFile = 0 ;
|
||||
uint64_t AttachDate = 0 ;
|
||||
uint64_t AttachSize = 0 ;
|
||||
std::string AttachType;
|
||||
double executionTime = 0.0;
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
};
|
||||
|
||||
struct BlackListedDevice {
|
||||
std::string serialNumber;
|
||||
std::string reason;
|
||||
std::string author;
|
||||
uint64_t created;
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||
};
|
||||
|
||||
struct RttySessionDetails {
|
||||
std::string SerialNumber;
|
||||
std::string Server;
|
||||
uint64_t Port = 0 ;
|
||||
std::string Token;
|
||||
uint64_t TimeOut = 0 ;
|
||||
std::string ConnectionId;
|
||||
uint64_t Started = 0 ;
|
||||
std::string CommandUUID;
|
||||
uint64_t ViewPort = 0 ;
|
||||
std::string DevicePassword;
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
};
|
||||
|
||||
struct Dashboard {
|
||||
uint64_t snapshot = 0 ;
|
||||
uint64_t numberOfDevices = 0 ;
|
||||
Types::CountedMap commands;
|
||||
Types::CountedMap upTimes;
|
||||
Types::CountedMap memoryUsed;
|
||||
Types::CountedMap load1;
|
||||
Types::CountedMap load5;
|
||||
Types::CountedMap load15;
|
||||
Types::CountedMap vendors;
|
||||
Types::CountedMap status;
|
||||
Types::CountedMap deviceType;
|
||||
Types::CountedMap healths;
|
||||
Types::CountedMap certificates;
|
||||
Types::CountedMap lastContact;
|
||||
Types::CountedMap associations;
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
void reset();
|
||||
};
|
||||
|
||||
struct CapabilitiesModel {
|
||||
std::string deviceType;
|
||||
std::string capabilities;
|
||||
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
};
|
||||
|
||||
struct ScriptRequest {
|
||||
uint64_t timeout=30;
|
||||
std::string serialNumber;
|
||||
std::string type;
|
||||
std::string script;
|
||||
std::string scriptId;
|
||||
uint64_t when=0;
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||
};
|
||||
}
|
||||
75
src/framework/RESTAPI_GenericServerAccounting.h
Normal file
75
src/framework/RESTAPI_GenericServerAccounting.h
Normal file
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
#include "Poco/StringTokenizer.h"
|
||||
#include "Poco/String.h"
|
||||
#include "Poco/Net/HTTPRequest.h"
|
||||
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
class RESTAPI_GenericServerAccounting {
|
||||
public:
|
||||
|
||||
enum {
|
||||
LOG_GET=0,
|
||||
LOG_DELETE,
|
||||
LOG_PUT,
|
||||
LOG_POST
|
||||
};
|
||||
|
||||
void inline SetFlags(bool External, const std::string &Methods) {
|
||||
Poco::StringTokenizer Tokens(Methods,",");
|
||||
auto Offset = (External ? 0 : 4);
|
||||
for(const auto &i:Tokens) {
|
||||
if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_DELETE)==0)
|
||||
LogFlags_[Offset+LOG_DELETE]=true;
|
||||
else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_PUT)==0)
|
||||
LogFlags_[Offset+LOG_PUT]=true;
|
||||
else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_POST)==0)
|
||||
LogFlags_[Offset+LOG_POST]=true;
|
||||
else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_GET)==0)
|
||||
LogFlags_[Offset+LOG_GET]=true;
|
||||
}
|
||||
}
|
||||
|
||||
inline void InitLogging() {
|
||||
std::string Public = MicroServiceConfigGetString("apilogging.public.methods","PUT,POST,DELETE");
|
||||
SetFlags(true, Public);
|
||||
std::string Private = MicroServiceConfigGetString("apilogging.private.methods","PUT,POST,DELETE");
|
||||
SetFlags(false, Private);
|
||||
|
||||
std::string PublicBadTokens = MicroServiceConfigGetString("apilogging.public.badtokens.methods","");
|
||||
LogBadTokens_[0] = (Poco::icompare(PublicBadTokens,"true")==0);
|
||||
std::string PrivateBadTokens = MicroServiceConfigGetString("apilogging.private.badtokens.methods","");
|
||||
LogBadTokens_[1] = (Poco::icompare(PrivateBadTokens,"true")==0);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool LogIt(const std::string &Method, bool External) const {
|
||||
auto Offset = (External ? 0 : 4);
|
||||
if(Method == Poco::Net::HTTPRequest::HTTP_GET)
|
||||
return LogFlags_[Offset+LOG_GET];
|
||||
if(Method == Poco::Net::HTTPRequest::HTTP_POST)
|
||||
return LogFlags_[Offset+LOG_POST];
|
||||
if(Method == Poco::Net::HTTPRequest::HTTP_PUT)
|
||||
return LogFlags_[Offset+LOG_PUT];
|
||||
if(Method == Poco::Net::HTTPRequest::HTTP_DELETE)
|
||||
return LogFlags_[Offset+LOG_DELETE];
|
||||
return false;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline bool LogBadTokens(bool External) const {
|
||||
return LogBadTokens_[ (External ? 0 : 1) ];
|
||||
};
|
||||
|
||||
private:
|
||||
std::array<bool,8> LogFlags_{false};
|
||||
std::array<bool,2> LogBadTokens_{false};
|
||||
};
|
||||
}
|
||||
8
src/framework/RESTAPI_Handler.cpp
Normal file
8
src/framework/RESTAPI_Handler.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include "RESTAPI_Handler.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
} // namespace OpenWifi
|
||||
759
src/framework/RESTAPI_Handler.h
Normal file
759
src/framework/RESTAPI_Handler.h
Normal file
@@ -0,0 +1,759 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "Poco/Net/HTTPRequestHandler.h"
|
||||
#include "Poco/Logger.h"
|
||||
#include "Poco/JSON/Object.h"
|
||||
#include "Poco/JSON/Parser.h"
|
||||
#include "Poco/Net/HTTPResponse.h"
|
||||
#include "Poco/Net/HTTPServerResponse.h"
|
||||
#include "Poco/DeflatingStream.h"
|
||||
#include "Poco/TemporaryFile.h"
|
||||
#include "Poco/Net/OAuth20Credentials.h"
|
||||
|
||||
#include "framework/ow_constants.h"
|
||||
#include "framework/RESTAPI_GenericServerAccounting.h"
|
||||
#include "framework/RESTAPI_RateLimiter.h"
|
||||
#include "framework/utils.h"
|
||||
#include "framework/RESTAPI_utils.h"
|
||||
#include "framework/AuthClient.h"
|
||||
#include "RESTObjects/RESTAPI_SecurityObjects.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
class RESTAPIHandler : public Poco::Net::HTTPRequestHandler {
|
||||
public:
|
||||
struct QueryBlock {
|
||||
uint64_t StartDate = 0 , EndDate = 0 , Offset = 0 , Limit = 0, LogType = 0 ;
|
||||
std::string SerialNumber, Filter;
|
||||
std::vector<std::string> Select;
|
||||
bool Lifetime=false, LastOnly=false, Newest=false, CountOnly=false, AdditionalInfo=false;
|
||||
};
|
||||
typedef std::map<std::string, std::string> BindingMap;
|
||||
|
||||
struct RateLimit {
|
||||
int64_t Interval=1000;
|
||||
int64_t MaxCalls=10;
|
||||
};
|
||||
|
||||
RESTAPIHandler( BindingMap map,
|
||||
Poco::Logger &l,
|
||||
std::vector<std::string> Methods,
|
||||
RESTAPI_GenericServerAccounting & Server,
|
||||
uint64_t TransactionId,
|
||||
bool Internal,
|
||||
bool AlwaysAuthorize=true,
|
||||
bool RateLimited=false,
|
||||
const RateLimit & Profile = RateLimit{.Interval=1000,.MaxCalls=100},
|
||||
bool SubscriberOnly=false)
|
||||
: Bindings_(std::move(map)),
|
||||
Logger_(l),
|
||||
Methods_(std::move(Methods)),
|
||||
Internal_(Internal),
|
||||
RateLimited_(RateLimited),
|
||||
SubOnlyService_(SubscriberOnly),
|
||||
AlwaysAuthorize_(AlwaysAuthorize),
|
||||
Server_(Server),
|
||||
MyRates_(Profile),
|
||||
TransactionId_(TransactionId)
|
||||
{
|
||||
}
|
||||
|
||||
inline bool RoleIsAuthorized([[maybe_unused]] const std::string & Path, [[maybe_unused]] const std::string & Method, [[maybe_unused]] std::string & Reason) {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void handleRequest(Poco::Net::HTTPServerRequest &RequestIn,
|
||||
Poco::Net::HTTPServerResponse &ResponseIn) final {
|
||||
try {
|
||||
Request = &RequestIn;
|
||||
Response = &ResponseIn;
|
||||
|
||||
// std::string th_name = "restsvr_" + std::to_string(TransactionId_);
|
||||
// Utils::SetThreadName(th_name.c_str());
|
||||
|
||||
if(Request->getContentLength()>0) {
|
||||
if(Request->getContentType().find("application/json")!=std::string::npos) {
|
||||
ParsedBody_ = IncomingParser_.parse(Request->stream()).extract<Poco::JSON::Object::Ptr>();
|
||||
}
|
||||
}
|
||||
|
||||
if(RateLimited_ && RESTAPI_RateLimiter()->IsRateLimited(RequestIn,MyRates_.Interval, MyRates_.MaxCalls)) {
|
||||
return UnAuthorized(RESTAPI::Errors::RATE_LIMIT_EXCEEDED);
|
||||
}
|
||||
|
||||
if (!ContinueProcessing())
|
||||
return;
|
||||
|
||||
bool Expired=false, Contacted=false;
|
||||
if (AlwaysAuthorize_ && !IsAuthorized(Expired, Contacted, SubOnlyService_)) {
|
||||
if(Expired)
|
||||
return UnAuthorized(RESTAPI::Errors::EXPIRED_TOKEN);
|
||||
if(Contacted)
|
||||
return UnAuthorized(RESTAPI::Errors::INVALID_TOKEN);
|
||||
else
|
||||
return UnAuthorized(RESTAPI::Errors::SECURITY_SERVICE_UNREACHABLE);
|
||||
}
|
||||
|
||||
std::string Reason;
|
||||
if(!RoleIsAuthorized(RequestIn.getURI(), Request->getMethod(), Reason)) {
|
||||
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
|
||||
}
|
||||
|
||||
ParseParameters();
|
||||
if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_GET)
|
||||
return DoGet();
|
||||
else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
|
||||
return DoPost();
|
||||
else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_DELETE)
|
||||
return DoDelete();
|
||||
else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_PUT)
|
||||
return DoPut();
|
||||
return BadRequest(RESTAPI::Errors::UnsupportedHTTPMethod);
|
||||
} catch (const Poco::Exception &E) {
|
||||
Logger_.log(E);
|
||||
return BadRequest(RESTAPI::Errors::InternalError);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool NeedAdditionalInfo() const { return QB_.AdditionalInfo; }
|
||||
[[nodiscard]] inline const std::vector<std::string> & SelectedRecords() const { return QB_.Select; }
|
||||
|
||||
inline static bool ParseBindings(const std::string & Request, const std::list<std::string> & EndPoints, BindingMap &bindings) {
|
||||
bindings.clear();
|
||||
auto PathItems = Poco::StringTokenizer(Request, "/");
|
||||
|
||||
for(const auto &EndPoint:EndPoints) {
|
||||
auto ParamItems = Poco::StringTokenizer(EndPoint, "/");
|
||||
if (PathItems.count() != ParamItems.count())
|
||||
continue;
|
||||
|
||||
bool Matched = true;
|
||||
for (size_t i = 0; i < PathItems.count(); i++) {
|
||||
if (PathItems[i] != ParamItems[i]) {
|
||||
if (ParamItems[i][0] == '{') {
|
||||
auto ParamName = ParamItems[i].substr(1, ParamItems[i].size() - 2);
|
||||
bindings[Poco::toLower(ParamName)] = PathItems[i];
|
||||
} else {
|
||||
Matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(Matched)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void PrintBindings() {
|
||||
for (const auto &[key, value] : Bindings_)
|
||||
std::cout << "Key = " << key << " Value= " << value << std::endl;
|
||||
}
|
||||
|
||||
inline void ParseParameters() {
|
||||
Poco::URI uri(Request->getURI());
|
||||
Parameters_ = uri.getQueryParameters();
|
||||
InitQueryBlock();
|
||||
}
|
||||
|
||||
inline static bool is_number(const std::string &s) {
|
||||
return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit);
|
||||
}
|
||||
|
||||
inline static bool is_bool(const std::string &s) {
|
||||
if (s == "true" || s == "false")
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint64_t GetParameter(const std::string &Name, const uint64_t Default) {
|
||||
auto Hint = std::find_if(Parameters_.begin(),Parameters_.end(),[&](const std::pair<std::string,std::string> &S){ return S.first==Name; });
|
||||
if(Hint==Parameters_.end() || !is_number(Hint->second))
|
||||
return Default;
|
||||
return std::stoull(Hint->second);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool GetBoolParameter(const std::string &Name, bool Default=false) {
|
||||
auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[&](const std::pair<std::string,std::string> &S){ return S.first==Name; });
|
||||
if(Hint==end(Parameters_) || !is_bool(Hint->second))
|
||||
return Default;
|
||||
return Hint->second=="true";
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::string GetParameter(const std::string &Name, const std::string &Default="") {
|
||||
auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[&](const std::pair<std::string,std::string> &S){ return S.first==Name; });
|
||||
if(Hint==end(Parameters_))
|
||||
return Default;
|
||||
return Hint->second;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool HasParameter(const std::string &Name, std::string &Value) {
|
||||
auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[&](const std::pair<std::string,std::string> &S){ return S.first==Name; });
|
||||
if(Hint==end(Parameters_))
|
||||
return false;
|
||||
Value = Hint->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool HasParameter(const std::string &Name, uint64_t & Value) {
|
||||
auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[&](const std::pair<std::string,std::string> &S){ return S.first==Name; });
|
||||
if(Hint==end(Parameters_))
|
||||
return false;
|
||||
Value = std::stoull(Hint->second);
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline const std::string & GetBinding(const std::string &Name, const std::string &Default="") {
|
||||
auto E = Bindings_.find(Poco::toLower(Name));
|
||||
if (E == Bindings_.end())
|
||||
return Default;
|
||||
|
||||
return E->second;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline static std::string MakeList(const std::vector<std::string> &L) {
|
||||
std::string Return;
|
||||
for (const auto &i : L) {
|
||||
if (Return.empty())
|
||||
Return = i;
|
||||
else
|
||||
Return += ", " + i;
|
||||
}
|
||||
return Return;
|
||||
}
|
||||
|
||||
static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, Types::UUIDvec_t & Value) {
|
||||
if(O->has(Field) && O->isArray(Field)) {
|
||||
auto Arr = O->getArray(Field);
|
||||
for(const auto &i:*Arr)
|
||||
Value.emplace_back(i.toString());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, std::string &Value) {
|
||||
if(O->has(Field)) {
|
||||
Value = O->get(Field).toString();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, uint64_t &Value) {
|
||||
if(O->has(Field)) {
|
||||
Value = O->get(Field);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, bool &Value) {
|
||||
if(O->has(Field)) {
|
||||
Value = O->get(Field).toString()=="true";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, double &Value) {
|
||||
if(O->has(Field)) {
|
||||
Value = (double) O->get(Field);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, Poco::Data::BLOB &Value) {
|
||||
if(O->has(Field)) {
|
||||
std::string Content = O->get(Field).toString();
|
||||
auto DecodedBlob = Utils::base64decode(Content);
|
||||
Value.assignRaw((const unsigned char *)&DecodedBlob[0],DecodedBlob.size());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
template <typename T> bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, const T &value, T & assignee) {
|
||||
if(O->has(Field)) {
|
||||
assignee = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void SetCommonHeaders(bool CloseConnection=false) {
|
||||
Response->setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
|
||||
Response->setChunkedTransferEncoding(true);
|
||||
Response->setContentType("application/json");
|
||||
auto Origin = Request->find("Origin");
|
||||
if (Origin != Request->end()) {
|
||||
Response->set("Access-Control-Allow-Origin", Origin->second);
|
||||
} else {
|
||||
Response->set("Access-Control-Allow-Origin", "*");
|
||||
}
|
||||
Response->set("Vary", "Origin, Accept-Encoding");
|
||||
if(CloseConnection) {
|
||||
Response->set("Connection", "close");
|
||||
Response->setKeepAlive(false);
|
||||
} else {
|
||||
Response->setKeepAlive(true);
|
||||
Response->set("Connection", "Keep-Alive");
|
||||
Response->set("Keep-Alive", "timeout=30, max=1000");
|
||||
}
|
||||
}
|
||||
|
||||
inline void ProcessOptions() {
|
||||
Response->setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
|
||||
Response->setChunkedTransferEncoding(true);
|
||||
auto Origin = Request->find("Origin");
|
||||
if (Origin != Request->end()) {
|
||||
Response->set("Access-Control-Allow-Origin", Origin->second);
|
||||
} else {
|
||||
Response->set("Access-Control-Allow-Origin", "*");
|
||||
}
|
||||
Response->set("Access-Control-Allow-Methods", MakeList(Methods_));
|
||||
auto RequestHeaders = Request->find("Access-Control-Request-Headers");
|
||||
if(RequestHeaders!=Request->end())
|
||||
Response->set("Access-Control-Allow-Headers", RequestHeaders->second);
|
||||
Response->set("Vary", "Origin, Accept-Encoding");
|
||||
Response->set("Access-Control-Allow-Credentials", "true");
|
||||
Response->set("Access-Control-Max-Age", "86400");
|
||||
Response->set("Connection", "Keep-Alive");
|
||||
Response->set("Keep-Alive", "timeout=30, max=1000");
|
||||
|
||||
Response->setContentLength(0);
|
||||
Response->setStatus(Poco::Net::HTTPResponse::HTTP_OK);
|
||||
Response->send();
|
||||
}
|
||||
|
||||
inline void PrepareResponse(Poco::Net::HTTPResponse::HTTPStatus Status = Poco::Net::HTTPResponse::HTTP_OK,
|
||||
bool CloseConnection = false) {
|
||||
Response->setStatus(Status);
|
||||
SetCommonHeaders(CloseConnection);
|
||||
}
|
||||
|
||||
inline void BadRequest(const OpenWifi::RESTAPI::Errors::msg &E, const std::string & Extra="") {
|
||||
PrepareResponse(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST);
|
||||
Poco::JSON::Object ErrorObject;
|
||||
ErrorObject.set("ErrorCode",400);
|
||||
ErrorObject.set("ErrorDetails",Request->getMethod());
|
||||
if(Extra.empty())
|
||||
ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ;
|
||||
else
|
||||
ErrorObject.set("ErrorDescription",fmt::format("{}: {} ({})",E.err_num,E.err_txt, Extra)) ;
|
||||
|
||||
std::ostream &Answer = Response->send();
|
||||
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
||||
}
|
||||
|
||||
inline void InternalError(const OpenWifi::RESTAPI::Errors::msg &E) {
|
||||
PrepareResponse(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR);
|
||||
Poco::JSON::Object ErrorObject;
|
||||
ErrorObject.set("ErrorCode",500);
|
||||
ErrorObject.set("ErrorDetails",Request->getMethod());
|
||||
ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ;
|
||||
std::ostream &Answer = Response->send();
|
||||
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
||||
}
|
||||
|
||||
inline void UnAuthorized(const OpenWifi::RESTAPI::Errors::msg &E) {
|
||||
PrepareResponse(Poco::Net::HTTPResponse::HTTP_FORBIDDEN);
|
||||
Poco::JSON::Object ErrorObject;
|
||||
ErrorObject.set("ErrorCode",E.err_num);
|
||||
ErrorObject.set("ErrorDetails",Request->getMethod());
|
||||
ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ;
|
||||
std::ostream &Answer = Response->send();
|
||||
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
||||
}
|
||||
|
||||
inline void NotFound() {
|
||||
PrepareResponse(Poco::Net::HTTPResponse::HTTP_NOT_FOUND);
|
||||
Poco::JSON::Object ErrorObject;
|
||||
ErrorObject.set("ErrorCode",404);
|
||||
ErrorObject.set("ErrorDetails",Request->getMethod());
|
||||
const auto & E = OpenWifi::RESTAPI::Errors::Error404;
|
||||
ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ;
|
||||
std::ostream &Answer = Response->send();
|
||||
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
||||
poco_debug(Logger_,fmt::format("RES-NOTFOUND: User='{}@{}' Method='{}' Path='{}",
|
||||
UserInfo_.userinfo.email,
|
||||
Utils::FormatIPv6(Request->clientAddress().toString()),
|
||||
Request->getMethod(),
|
||||
Request->getURI()));
|
||||
}
|
||||
|
||||
inline void OK() {
|
||||
PrepareResponse();
|
||||
if( Request->getMethod()==Poco::Net::HTTPRequest::HTTP_DELETE ||
|
||||
Request->getMethod()==Poco::Net::HTTPRequest::HTTP_OPTIONS) {
|
||||
Response->send();
|
||||
} else {
|
||||
Poco::JSON::Object ErrorObject;
|
||||
ErrorObject.set("Code", 0);
|
||||
ErrorObject.set("Operation", Request->getMethod());
|
||||
ErrorObject.set("Details", "Command completed.");
|
||||
std::ostream &Answer = Response->send();
|
||||
Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
|
||||
}
|
||||
}
|
||||
|
||||
inline void SendCompressedTarFile(const std::string & FileName, const std::string & Content) {
|
||||
Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK);
|
||||
SetCommonHeaders();
|
||||
Response->set("Content-Type","application/gzip");
|
||||
Response->set("Content-Disposition", "attachment; filename=" + FileName );
|
||||
Response->set("Content-Transfer-Encoding","binary");
|
||||
Response->set("Accept-Ranges", "bytes");
|
||||
Response->set("Cache-Control", "no-store");
|
||||
Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
||||
Response->setStatus(Poco::Net::HTTPResponse::HTTP_OK);
|
||||
Response->setContentLength(Content.size());
|
||||
Response->setChunkedTransferEncoding(true);
|
||||
std::ostream& OutputStream = Response->send();
|
||||
OutputStream << Content;
|
||||
}
|
||||
|
||||
inline void SendFile(Poco::File & File, const std::string & UUID) {
|
||||
Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK);
|
||||
SetCommonHeaders();
|
||||
Response->set("Content-Type","application/octet-stream");
|
||||
Response->set("Content-Disposition", "attachment; filename=" + UUID );
|
||||
Response->set("Content-Transfer-Encoding","binary");
|
||||
Response->set("Accept-Ranges", "bytes");
|
||||
Response->set("Cache-Control", "no-store");
|
||||
Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
||||
Response->setContentLength(File.getSize());
|
||||
Response->sendFile(File.path(),"application/octet-stream");
|
||||
}
|
||||
|
||||
inline void SendFile(Poco::File & File) {
|
||||
Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK);
|
||||
SetCommonHeaders();
|
||||
Poco::Path P(File.path());
|
||||
auto MT = Utils::FindMediaType(File);
|
||||
if(MT.Encoding==Utils::BINARY) {
|
||||
Response->set("Content-Transfer-Encoding","binary");
|
||||
Response->set("Accept-Ranges", "bytes");
|
||||
}
|
||||
Response->set("Cache-Control", "no-store");
|
||||
Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
||||
Response->sendFile(File.path(),MT.ContentType);
|
||||
}
|
||||
|
||||
inline void SendFile(Poco::TemporaryFile &TempAvatar, [[maybe_unused]] const std::string &Type, const std::string & Name) {
|
||||
Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK);
|
||||
SetCommonHeaders();
|
||||
auto MT = Utils::FindMediaType(Name);
|
||||
if(MT.Encoding==Utils::BINARY) {
|
||||
Response->set("Content-Transfer-Encoding","binary");
|
||||
Response->set("Accept-Ranges", "bytes");
|
||||
}
|
||||
Response->set("Content-Disposition", "attachment; filename=" + Name );
|
||||
Response->set("Accept-Ranges", "bytes");
|
||||
Response->set("Cache-Control", "no-store");
|
||||
Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
||||
Response->setContentLength(TempAvatar.getSize());
|
||||
Response->sendFile(TempAvatar.path(),MT.ContentType);
|
||||
}
|
||||
|
||||
inline void SendFileContent(const std::string &Content, const std::string &Type, const std::string & Name) {
|
||||
Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK);
|
||||
SetCommonHeaders();
|
||||
auto MT = Utils::FindMediaType(Name);
|
||||
if(MT.Encoding==Utils::BINARY) {
|
||||
Response->set("Content-Transfer-Encoding","binary");
|
||||
Response->set("Accept-Ranges", "bytes");
|
||||
}
|
||||
Response->set("Content-Disposition", "attachment; filename=" + Name );
|
||||
Response->set("Accept-Ranges", "bytes");
|
||||
Response->set("Cache-Control", "no-store");
|
||||
Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
||||
Response->setContentLength(Content.size());
|
||||
Response->setContentType(Type );
|
||||
auto & OutputStream = Response->send();
|
||||
OutputStream << Content ;
|
||||
}
|
||||
|
||||
inline void SendHTMLFileBack(Poco::File & File,
|
||||
const Types::StringPairVec & FormVars) {
|
||||
Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK);
|
||||
SetCommonHeaders();
|
||||
Response->set("Pragma", "private");
|
||||
Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
||||
std::string FormContent = Utils::LoadFile(File.path());
|
||||
Utils::ReplaceVariables(FormContent, FormVars);
|
||||
Response->setContentLength(FormContent.size());
|
||||
Response->setChunkedTransferEncoding(true);
|
||||
Response->setContentType("text/html");
|
||||
std::ostream& ostr = Response->send();
|
||||
ostr << FormContent;
|
||||
}
|
||||
|
||||
inline void ReturnStatus(Poco::Net::HTTPResponse::HTTPStatus Status, bool CloseConnection=false) {
|
||||
PrepareResponse(Status, CloseConnection);
|
||||
if(Status == Poco::Net::HTTPResponse::HTTP_NO_CONTENT) {
|
||||
Response->setContentLength(0);
|
||||
Response->erase("Content-Type");
|
||||
Response->setChunkedTransferEncoding(false);
|
||||
}
|
||||
Response->send();
|
||||
}
|
||||
|
||||
inline bool ContinueProcessing() {
|
||||
if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_OPTIONS) {
|
||||
ProcessOptions();
|
||||
return false;
|
||||
} else if (std::find(Methods_.begin(), Methods_.end(), Request->getMethod()) == Methods_.end()) {
|
||||
BadRequest(RESTAPI::Errors::UnsupportedHTTPMethod);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool IsAuthorized(bool & Expired, bool & Contacted, bool SubOnly = false );
|
||||
|
||||
inline void ReturnObject(Poco::JSON::Object &Object) {
|
||||
PrepareResponse();
|
||||
if(Request!= nullptr) {
|
||||
// can we compress ???
|
||||
auto AcceptedEncoding = Request->find("Accept-Encoding");
|
||||
if(AcceptedEncoding!=Request->end()) {
|
||||
if( AcceptedEncoding->second.find("gzip")!=std::string::npos ||
|
||||
AcceptedEncoding->second.find("compress")!=std::string::npos) {
|
||||
Response->set("Content-Encoding", "gzip");
|
||||
std::ostream &Answer = Response->send();
|
||||
Poco::DeflatingOutputStream deflater(Answer, Poco::DeflatingStreamBuf::STREAM_GZIP);
|
||||
Poco::JSON::Stringifier::stringify(Object, deflater);
|
||||
deflater.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::ostream &Answer = Response->send();
|
||||
Poco::JSON::Stringifier::stringify(Object, Answer);
|
||||
}
|
||||
|
||||
inline void ReturnCountOnly(uint64_t Count) {
|
||||
Poco::JSON::Object Answer;
|
||||
Answer.set("count", Count);
|
||||
ReturnObject(Answer);
|
||||
}
|
||||
|
||||
inline bool InitQueryBlock() {
|
||||
if(QueryBlockInitialized_)
|
||||
return true;
|
||||
QueryBlockInitialized_=true;
|
||||
QB_.SerialNumber = GetParameter(RESTAPI::Protocol::SERIALNUMBER, "");
|
||||
QB_.StartDate = GetParameter(RESTAPI::Protocol::STARTDATE, 0);
|
||||
QB_.EndDate = GetParameter(RESTAPI::Protocol::ENDDATE, 0);
|
||||
QB_.Offset = GetParameter(RESTAPI::Protocol::OFFSET, 0);
|
||||
QB_.Limit = GetParameter(RESTAPI::Protocol::LIMIT, 100);
|
||||
QB_.Filter = GetParameter(RESTAPI::Protocol::FILTER, "");
|
||||
QB_.Lifetime = GetBoolParameter(RESTAPI::Protocol::LIFETIME,false);
|
||||
QB_.LogType = GetParameter(RESTAPI::Protocol::LOGTYPE,0);
|
||||
QB_.LastOnly = GetBoolParameter(RESTAPI::Protocol::LASTONLY,false);
|
||||
QB_.Newest = GetBoolParameter(RESTAPI::Protocol::NEWEST,false);
|
||||
QB_.CountOnly = GetBoolParameter(RESTAPI::Protocol::COUNTONLY,false);
|
||||
QB_.AdditionalInfo = GetBoolParameter(RESTAPI::Protocol::WITHEXTENDEDINFO,false);
|
||||
|
||||
auto RawSelect = GetParameter(RESTAPI::Protocol::SELECT, "");
|
||||
|
||||
auto Entries = Poco::StringTokenizer(RawSelect,",");
|
||||
for(const auto &i:Entries) {
|
||||
QB_.Select.emplace_back(i);
|
||||
}
|
||||
if(QB_.Offset<1)
|
||||
QB_.Offset=0;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint64_t Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default=0){
|
||||
if(Obj->has(Parameter))
|
||||
return Obj->get(Parameter);
|
||||
return Default;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::string GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default=""){
|
||||
if(Obj->has(Parameter))
|
||||
return Obj->get(Parameter).toString();
|
||||
return Default;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default=false){
|
||||
if(Obj->has(Parameter))
|
||||
return Obj->get(Parameter).toString()=="true";
|
||||
return Default;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint64_t GetWhen(const Poco::JSON::Object::Ptr &Obj) {
|
||||
return RESTAPIHandler::Get(RESTAPI::Protocol::WHEN, Obj);
|
||||
}
|
||||
|
||||
template<typename T> void ReturnObject(const char *Name, const std::vector<T> & Objects) {
|
||||
Poco::JSON::Object Answer;
|
||||
RESTAPI_utils::field_to_json(Answer,Name,Objects);
|
||||
ReturnObject(Answer);
|
||||
}
|
||||
|
||||
Poco::Logger & Logger() { return Logger_; }
|
||||
|
||||
virtual void DoGet() = 0 ;
|
||||
virtual void DoDelete() = 0 ;
|
||||
virtual void DoPost() = 0 ;
|
||||
virtual void DoPut() = 0 ;
|
||||
|
||||
Poco::Net::HTTPServerRequest *Request= nullptr;
|
||||
Poco::Net::HTTPServerResponse *Response= nullptr;
|
||||
SecurityObjects::UserInfoAndPolicy UserInfo_;
|
||||
QueryBlock QB_;
|
||||
const std::string & Requester() const { return REST_Requester_; }
|
||||
protected:
|
||||
BindingMap Bindings_;
|
||||
Poco::URI::QueryParameters Parameters_;
|
||||
Poco::Logger &Logger_;
|
||||
std::string SessionToken_;
|
||||
std::vector<std::string> Methods_;
|
||||
bool Internal_=false;
|
||||
bool RateLimited_=false;
|
||||
bool QueryBlockInitialized_=false;
|
||||
bool SubOnlyService_=false;
|
||||
bool AlwaysAuthorize_=true;
|
||||
Poco::JSON::Parser IncomingParser_;
|
||||
RESTAPI_GenericServerAccounting & Server_;
|
||||
RateLimit MyRates_;
|
||||
uint64_t TransactionId_;
|
||||
Poco::JSON::Object::Ptr ParsedBody_;
|
||||
std::string REST_Requester_;
|
||||
};
|
||||
|
||||
#ifdef TIP_SECURITY_SERVICE
|
||||
[[nodiscard]] bool AuthServiceIsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, std::uint64_t TID, bool & Expired , bool Sub );
|
||||
#endif
|
||||
inline bool RESTAPIHandler::IsAuthorized( bool & Expired , [[maybe_unused]] bool & Contacted , bool Sub ) {
|
||||
if(Internal_ && Request->has("X-INTERNAL-NAME")) {
|
||||
auto Allowed = MicroServiceIsValidAPIKEY(*Request);
|
||||
Contacted = true;
|
||||
if(!Allowed) {
|
||||
if(Server_.LogBadTokens(false)) {
|
||||
poco_debug(Logger_,fmt::format("I-REQ-DENIED({}): TID={} Method={} Path={}",
|
||||
Utils::FormatIPv6(Request->clientAddress().toString()),
|
||||
TransactionId_,
|
||||
Request->getMethod(), Request->getURI()));
|
||||
}
|
||||
} else {
|
||||
auto Id = Request->get("X-INTERNAL-NAME", "unknown");
|
||||
REST_Requester_ = Id;
|
||||
if(Server_.LogIt(Request->getMethod(),true)) {
|
||||
poco_debug(Logger_,fmt::format("I-REQ-ALLOWED({}): TID={} User='{}' Method={} Path={}",
|
||||
Utils::FormatIPv6(Request->clientAddress().toString()),
|
||||
TransactionId_,
|
||||
Id,
|
||||
Request->getMethod(), Request->getURI()));
|
||||
}
|
||||
}
|
||||
return Allowed;
|
||||
} else {
|
||||
if (SessionToken_.empty()) {
|
||||
try {
|
||||
Poco::Net::OAuth20Credentials Auth(*Request);
|
||||
if (Auth.getScheme() == "Bearer") {
|
||||
SessionToken_ = Auth.getBearerToken();
|
||||
}
|
||||
} catch (const Poco::Exception &E) {
|
||||
Logger_.log(E);
|
||||
}
|
||||
}
|
||||
#ifdef TIP_SECURITY_SERVICE
|
||||
if (AuthServiceIsAuthorized(*Request, SessionToken_, UserInfo_, TransactionId_, Expired, Sub)) {
|
||||
#else
|
||||
if (AuthClient()->IsAuthorized( SessionToken_, UserInfo_, TransactionId_, Expired, Contacted, Sub)) {
|
||||
#endif
|
||||
REST_Requester_ = UserInfo_.userinfo.email;
|
||||
if(Server_.LogIt(Request->getMethod(),true)) {
|
||||
poco_debug(Logger_,fmt::format("X-REQ-ALLOWED({}): TID={} User='{}@{}' Method={} Path={}",
|
||||
UserInfo_.userinfo.email,
|
||||
TransactionId_,
|
||||
Utils::FormatIPv6(Request->clientAddress().toString()),
|
||||
Request->clientAddress().toString(),
|
||||
Request->getMethod(),
|
||||
Request->getURI()));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if(Server_.LogBadTokens(true)) {
|
||||
poco_debug(Logger_,fmt::format("X-REQ-DENIED({}): TID={} Method={} Path={}",
|
||||
Utils::FormatIPv6(Request->clientAddress().toString()),
|
||||
TransactionId_,
|
||||
Request->getMethod(),
|
||||
Request->getURI()));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class RESTAPI_UnknownRequestHandler : public RESTAPIHandler {
|
||||
public:
|
||||
RESTAPI_UnknownRequestHandler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServerAccounting & Server, uint64_t TransactionId, bool Internal)
|
||||
: RESTAPIHandler(bindings, L, std::vector<std::string>{}, Server, TransactionId, Internal) {}
|
||||
inline void DoGet() override {};
|
||||
inline void DoPost() override {};
|
||||
inline void DoPut() override {};
|
||||
inline void DoDelete() override {};
|
||||
};
|
||||
|
||||
template<class T>
|
||||
constexpr auto test_has_PathName_method(T*)
|
||||
-> decltype( T::PathName() , std::true_type{} )
|
||||
{
|
||||
return std::true_type{};
|
||||
}
|
||||
constexpr auto test_has_PathName_method(...) -> std::false_type
|
||||
{
|
||||
return std::false_type{};
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
RESTAPIHandler * RESTAPI_Router(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings,
|
||||
Poco::Logger & Logger, RESTAPI_GenericServerAccounting & Server, uint64_t TransactionId) {
|
||||
static_assert(test_has_PathName_method((T*)nullptr), "Class must have a static PathName() method.");
|
||||
if(RESTAPIHandler::ParseBindings(RequestedPath,T::PathName(),Bindings)) {
|
||||
return new T(Bindings, Logger, Server, TransactionId, false);
|
||||
}
|
||||
|
||||
if constexpr (sizeof...(Args) == 0) {
|
||||
return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server, TransactionId, false);
|
||||
} else {
|
||||
return RESTAPI_Router<Args...>(RequestedPath, Bindings, Logger, Server, TransactionId);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
RESTAPIHandler * RESTAPI_Router_I(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings,
|
||||
Poco::Logger & Logger, RESTAPI_GenericServerAccounting & Server, uint64_t TransactionId) {
|
||||
static_assert(test_has_PathName_method((T*)nullptr), "Class must have a static PathName() method.");
|
||||
if(RESTAPIHandler::ParseBindings(RequestedPath,T::PathName(),Bindings)) {
|
||||
return new T(Bindings, Logger, Server, TransactionId, true );
|
||||
}
|
||||
|
||||
if constexpr (sizeof...(Args) == 0) {
|
||||
return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server, TransactionId, true);
|
||||
} else {
|
||||
return RESTAPI_Router_I<Args...>(RequestedPath, Bindings, Logger, Server, TransactionId);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace OpenWifi
|
||||
17
src/framework/RESTAPI_IntServer.cpp
Normal file
17
src/framework/RESTAPI_IntServer.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include "RESTAPI_IntServer.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
Poco::Net::HTTPRequestHandler *
|
||||
IntRequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest &Request) {
|
||||
auto TID = NextTransactionId_++;
|
||||
Utils::SetThreadName(fmt::format("i-rest:{}", TID).c_str());
|
||||
Poco::URI uri(Request.getURI());
|
||||
return RESTAPI_IntServer()->CallServer(uri.getPath(), TID);
|
||||
}
|
||||
|
||||
} // namespace OpenWifi
|
||||
104
src/framework/RESTAPI_IntServer.h
Normal file
104
src/framework/RESTAPI_IntServer.h
Normal file
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Poco/Net/HTTPServer.h"
|
||||
|
||||
#include "framework/SubSystemServer.h"
|
||||
#include "framework/RESTAPI_Handler.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
Poco::Net::HTTPRequestHandler * RESTAPI_IntRouter(const std::string &Path, RESTAPIHandler::BindingMap &Bindings,
|
||||
Poco::Logger & L, RESTAPI_GenericServerAccounting & S, uint64_t Id);
|
||||
|
||||
class IntRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory {
|
||||
public:
|
||||
inline IntRequestHandlerFactory() = default;
|
||||
Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &Request) override;
|
||||
private:
|
||||
static inline std::atomic_uint64_t NextTransactionId_ = 1;
|
||||
};
|
||||
|
||||
class RESTAPI_IntServer : public SubSystemServer {
|
||||
public:
|
||||
static auto instance() {
|
||||
static auto instance_ = new RESTAPI_IntServer;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
inline int Start() override {
|
||||
Logger().information("Starting.");
|
||||
Server_.InitLogging();
|
||||
|
||||
for(const auto & Svr: ConfigServersList_) {
|
||||
|
||||
if(MicroServiceNoAPISecurity()) {
|
||||
Logger().information(fmt::format("Starting: {}:{}. Security has been disabled for APIs.", Svr.Address(), Svr.Port()));
|
||||
} else {
|
||||
Logger().information(fmt::format("Starting: {}:{}. Keyfile:{} CertFile: {}", Svr.Address(), Svr.Port(),
|
||||
Svr.KeyFile(),Svr.CertFile()));
|
||||
Svr.LogCert(Logger());
|
||||
if (!Svr.RootCA().empty())
|
||||
Svr.LogCas(Logger());
|
||||
}
|
||||
|
||||
auto Params = new Poco::Net::HTTPServerParams;
|
||||
Params->setKeepAlive(true);
|
||||
Params->setName("ws:irest");
|
||||
|
||||
std::unique_ptr<Poco::Net::HTTPServer> NewServer;
|
||||
if(MicroServiceNoAPISecurity()) {
|
||||
auto Sock{Svr.CreateSocket(Logger())};
|
||||
NewServer = std::make_unique<Poco::Net::HTTPServer>(new IntRequestHandlerFactory, Pool_, Sock, Params);
|
||||
} else {
|
||||
auto Sock{Svr.CreateSecureSocket(Logger())};
|
||||
NewServer = std::make_unique<Poco::Net::HTTPServer>(new IntRequestHandlerFactory, Pool_, Sock, Params);
|
||||
};
|
||||
NewServer->start();
|
||||
RESTServers_.push_back(std::move(NewServer));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline void Stop() override {
|
||||
Logger().information("Stopping...");
|
||||
for( const auto & svr : RESTServers_ )
|
||||
svr->stopAll(true);
|
||||
Pool_.stopAll();
|
||||
Pool_.joinAll();
|
||||
Logger().information("Stopped...");
|
||||
}
|
||||
|
||||
inline void reinitialize([[maybe_unused]] Poco::Util::Application &self) override {
|
||||
MicroServiceLoadConfigurationFile();
|
||||
Logger().information("Reinitializing.");
|
||||
Stop();
|
||||
Start();
|
||||
}
|
||||
|
||||
inline Poco::Net::HTTPRequestHandler *CallServer(const std::string &Path, uint64_t Id) {
|
||||
RESTAPIHandler::BindingMap Bindings;
|
||||
Utils::SetThreadName(fmt::format("i-rest:{}",Id).c_str());
|
||||
return RESTAPI_IntRouter(Path, Bindings, Logger(), Server_, Id);
|
||||
}
|
||||
const Poco::ThreadPool & Pool() { return Pool_; }
|
||||
private:
|
||||
std::vector<std::unique_ptr<Poco::Net::HTTPServer>> RESTServers_;
|
||||
Poco::ThreadPool Pool_{"i-rest",4,64};
|
||||
RESTAPI_GenericServerAccounting Server_;
|
||||
|
||||
RESTAPI_IntServer() noexcept:
|
||||
SubSystemServer("RESTAPI_IntServer", "REST-ISRV", "openwifi.internal.restapi")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
inline auto RESTAPI_IntServer() { return RESTAPI_IntServer::instance(); };
|
||||
|
||||
|
||||
} // namespace OpenWifi
|
||||
|
||||
66
src/framework/RESTAPI_PartHandler.h
Normal file
66
src/framework/RESTAPI_PartHandler.h
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-26.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "Poco/Net/PartHandler.h"
|
||||
#include "Poco/Net/MessageHeader.h"
|
||||
#include "Poco/CountingStream.h"
|
||||
#include "Poco/NullStream.h"
|
||||
#include "Poco/StreamCopier.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
class RESTAPI_PartHandler: public Poco::Net::PartHandler {
|
||||
public:
|
||||
RESTAPI_PartHandler():
|
||||
_length(0)
|
||||
{
|
||||
}
|
||||
|
||||
inline void handlePart(const Poco::Net::MessageHeader& header, std::istream& stream) override
|
||||
{
|
||||
_type = header.get("Content-Type", "(unspecified)");
|
||||
if (header.has("Content-Disposition"))
|
||||
{
|
||||
std::string disp;
|
||||
Poco::Net::NameValueCollection params;
|
||||
Poco::Net::MessageHeader::splitParameters(header["Content-Disposition"], disp, params);
|
||||
_name = params.get("name", "(unnamed)");
|
||||
_fileName = params.get("filename", "(unnamed)");
|
||||
}
|
||||
|
||||
Poco::CountingInputStream istr(stream);
|
||||
Poco::NullOutputStream ostr;
|
||||
Poco::StreamCopier::copyStream(istr, ostr);
|
||||
_length = (int)istr.chars();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline int length() const
|
||||
{
|
||||
return _length;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline const std::string& name() const
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline const std::string& fileName() const
|
||||
{
|
||||
return _fileName;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline const std::string& contentType() const
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
private:
|
||||
int _length;
|
||||
std::string _type;
|
||||
std::string _name;
|
||||
std::string _fileName;
|
||||
};
|
||||
}
|
||||
75
src/framework/RESTAPI_RateLimiter.h
Normal file
75
src/framework/RESTAPI_RateLimiter.h
Normal file
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "framework/SubSystemServer.h"
|
||||
|
||||
#include "Poco/URI.h"
|
||||
#include "Poco/Net/HTTPServerRequest.h"
|
||||
#include "Poco/ExpireLRUCache.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
class RESTAPI_RateLimiter : public SubSystemServer {
|
||||
public:
|
||||
|
||||
struct ClientCacheEntry {
|
||||
int64_t Start=0;
|
||||
int Count=0;
|
||||
};
|
||||
|
||||
static auto instance() {
|
||||
static auto instance_ = new RESTAPI_RateLimiter;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
inline int Start() final { return 0;};
|
||||
inline void Stop() final { };
|
||||
|
||||
inline bool IsRateLimited(const Poco::Net::HTTPServerRequest &R, int64_t Period, int64_t MaxCalls) {
|
||||
Poco::URI uri(R.getURI());
|
||||
auto H = str_hash(uri.getPath() + R.clientAddress().host().toString());
|
||||
auto E = Cache_.get(H);
|
||||
auto Now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
if(E.isNull()) {
|
||||
Cache_.add(H,ClientCacheEntry{.Start=Now, .Count=1});
|
||||
return false;
|
||||
}
|
||||
if((Now-E->Start)<Period) {
|
||||
E->Count++;
|
||||
Cache_.update(H,E);
|
||||
if(E->Count > MaxCalls) {
|
||||
poco_warning(Logger(),fmt::format("RATE-LIMIT-EXCEEDED: from '{}'", R.clientAddress().toString()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
E->Start = Now;
|
||||
E->Count = 1;
|
||||
Cache_.update(H,E);
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void Clear() {
|
||||
Cache_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
Poco::ExpireLRUCache<uint64_t,ClientCacheEntry> Cache_{2048};
|
||||
std::hash<std::string> str_hash;
|
||||
|
||||
RESTAPI_RateLimiter() noexcept:
|
||||
SubSystemServer("RateLimiter", "RATE-LIMITER", "rate.limiter")
|
||||
{
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
inline auto RESTAPI_RateLimiter() { return RESTAPI_RateLimiter::instance(); }
|
||||
|
||||
|
||||
}
|
||||
157
src/framework/RESTAPI_SystemCommand.h
Normal file
157
src/framework/RESTAPI_SystemCommand.h
Normal file
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "framework/RESTAPI_Handler.h"
|
||||
|
||||
#include "Poco/Environment.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
class RESTAPI_system_command : public RESTAPIHandler {
|
||||
public:
|
||||
RESTAPI_system_command(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServerAccounting & Server, uint64_t TransactionId, bool Internal)
|
||||
: RESTAPIHandler(bindings, L,
|
||||
std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST,
|
||||
Poco::Net::HTTPRequest::HTTP_GET,
|
||||
Poco::Net::HTTPRequest::HTTP_OPTIONS},
|
||||
Server,
|
||||
TransactionId,
|
||||
Internal) {}
|
||||
static auto PathName() { return std::list<std::string>{"/api/v1/system"};}
|
||||
|
||||
inline void DoGet() {
|
||||
std::string Arg;
|
||||
if(HasParameter("command",Arg) && Arg=="info") {
|
||||
Poco::JSON::Object Answer;
|
||||
Answer.set(RESTAPI::Protocol::VERSION, MicroServiceVersion());
|
||||
Answer.set(RESTAPI::Protocol::UPTIME, MicroServiceUptimeTotalSeconds());
|
||||
Answer.set(RESTAPI::Protocol::START, MicroServiceStartTimeEpochTime());
|
||||
Answer.set(RESTAPI::Protocol::OS, Poco::Environment::osName());
|
||||
Answer.set(RESTAPI::Protocol::PROCESSORS, Poco::Environment::processorCount());
|
||||
Answer.set(RESTAPI::Protocol::HOSTNAME, Poco::Environment::nodeName());
|
||||
Answer.set(RESTAPI::Protocol::UI, MicroServiceGetUIURI());
|
||||
|
||||
Poco::JSON::Array Certificates;
|
||||
auto SubSystems = MicroServiceGetFullSubSystems();
|
||||
std::set<std::string> CertNames;
|
||||
|
||||
for(const auto &i:SubSystems) {
|
||||
auto Hosts=i->HostSize();
|
||||
for(uint64_t j=0;j<Hosts;++j) {
|
||||
auto CertFileName = i->Host(j).CertFile();
|
||||
if(!CertFileName.empty()) {
|
||||
Poco::File F1(CertFileName);
|
||||
if(F1.exists()) {
|
||||
auto InsertResult = CertNames.insert(CertFileName);
|
||||
if(InsertResult.second) {
|
||||
Poco::JSON::Object Inner;
|
||||
Poco::Path F(CertFileName);
|
||||
Inner.set("filename", F.getFileName());
|
||||
Poco::Crypto::X509Certificate C(CertFileName);
|
||||
auto ExpiresOn = C.expiresOn();
|
||||
Inner.set("expiresOn", ExpiresOn.timestamp().epochTime());
|
||||
Certificates.add(Inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Answer.set("certificates", Certificates);
|
||||
return ReturnObject(Answer);
|
||||
}
|
||||
if(GetBoolParameter("extraConfiguration")) {
|
||||
Poco::JSON::Object Answer;
|
||||
MicroServiceGetExtraConfiguration(Answer);
|
||||
return ReturnObject(Answer);
|
||||
}
|
||||
BadRequest(RESTAPI::Errors::InvalidCommand);
|
||||
}
|
||||
|
||||
inline void DoPost() final {
|
||||
const auto & Obj = ParsedBody_;
|
||||
if (Obj->has(RESTAPI::Protocol::COMMAND)) {
|
||||
auto Command = Poco::toLower(Obj->get(RESTAPI::Protocol::COMMAND).toString());
|
||||
if (Command == RESTAPI::Protocol::SETLOGLEVEL) {
|
||||
if (Obj->has(RESTAPI::Protocol::SUBSYSTEMS) &&
|
||||
Obj->isArray(RESTAPI::Protocol::SUBSYSTEMS)) {
|
||||
auto ParametersBlock = Obj->getArray(RESTAPI::Protocol::SUBSYSTEMS);
|
||||
for (const auto &i : *ParametersBlock) {
|
||||
Poco::JSON::Parser pp;
|
||||
auto InnerObj = pp.parse(i).extract<Poco::JSON::Object::Ptr>();
|
||||
if (InnerObj->has(RESTAPI::Protocol::TAG) &&
|
||||
InnerObj->has(RESTAPI::Protocol::VALUE)) {
|
||||
auto Name = GetS(RESTAPI::Protocol::TAG, InnerObj);
|
||||
auto Value = GetS(RESTAPI::Protocol::VALUE, InnerObj);
|
||||
MicroServiceSetSubsystemLogLevel(Name, Value);
|
||||
Logger_.information(
|
||||
fmt::format("Setting log level for {} at {}", Name, Value));
|
||||
}
|
||||
}
|
||||
return OK();
|
||||
}
|
||||
} else if (Command == RESTAPI::Protocol::GETLOGLEVELS) {
|
||||
auto CurrentLogLevels = MicroServiceGetLogLevels();
|
||||
Poco::JSON::Object Result;
|
||||
Poco::JSON::Array Array;
|
||||
for (auto &[Name, Level] : CurrentLogLevels) {
|
||||
Poco::JSON::Object Pair;
|
||||
Pair.set(RESTAPI::Protocol::TAG, Name);
|
||||
Pair.set(RESTAPI::Protocol::VALUE, Level);
|
||||
Array.add(Pair);
|
||||
}
|
||||
Result.set(RESTAPI::Protocol::TAGLIST, Array);
|
||||
return ReturnObject(Result);
|
||||
} else if (Command == RESTAPI::Protocol::GETLOGLEVELNAMES) {
|
||||
Poco::JSON::Object Result;
|
||||
Poco::JSON::Array LevelNamesArray;
|
||||
const Types::StringVec &LevelNames = MicroServiceGetLogLevelNames();
|
||||
for (const auto &i : LevelNames)
|
||||
LevelNamesArray.add(i);
|
||||
Result.set(RESTAPI::Protocol::LIST, LevelNamesArray);
|
||||
return ReturnObject(Result);
|
||||
} else if (Command == RESTAPI::Protocol::GETSUBSYSTEMNAMES) {
|
||||
Poco::JSON::Object Result;
|
||||
Poco::JSON::Array LevelNamesArray;
|
||||
const Types::StringVec &SubSystemNames = MicroServiceGetSubSystems();
|
||||
for (const auto &i : SubSystemNames)
|
||||
LevelNamesArray.add(i);
|
||||
Result.set(RESTAPI::Protocol::LIST, LevelNamesArray);
|
||||
return ReturnObject(Result);
|
||||
} else if (Command == RESTAPI::Protocol::STATS) {
|
||||
|
||||
} else if (Command == RESTAPI::Protocol::RELOAD) {
|
||||
if (Obj->has(RESTAPI::Protocol::SUBSYSTEMS) &&
|
||||
Obj->isArray(RESTAPI::Protocol::SUBSYSTEMS)) {
|
||||
auto SubSystems = Obj->getArray(RESTAPI::Protocol::SUBSYSTEMS);
|
||||
std::vector<std::string> Names;
|
||||
for (const auto &i : *SubSystems)
|
||||
Names.push_back(i.toString());
|
||||
std::thread ReloadThread([Names](){
|
||||
std::this_thread::sleep_for(10000ms);
|
||||
for(const auto &i:Names) {
|
||||
if(i=="daemon")
|
||||
MicroServiceReload();
|
||||
else
|
||||
MicroServiceReload(i);
|
||||
}
|
||||
});
|
||||
ReloadThread.detach();
|
||||
}
|
||||
return OK();
|
||||
}
|
||||
} else {
|
||||
return BadRequest(RESTAPI::Errors::InvalidCommand);
|
||||
}
|
||||
BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
|
||||
}
|
||||
|
||||
void DoPut() final {};
|
||||
void DoDelete() final {};
|
||||
};
|
||||
|
||||
}
|
||||
45
src/framework/RESTAPI_WebSocketServer.h
Normal file
45
src/framework/RESTAPI_WebSocketServer.h
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-26.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "framework/RESTAPI_Handler.h"
|
||||
#include "Poco/Net/WebSocket.h"
|
||||
|
||||
#include "framework/UI_WebSocketClientServer.h"
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
class RESTAPI_webSocketServer : public RESTAPIHandler {
|
||||
public:
|
||||
inline RESTAPI_webSocketServer(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServerAccounting &Server, uint64_t TransactionId, bool Internal)
|
||||
: RESTAPIHandler(bindings, L,
|
||||
std::vector<std::string>{ Poco::Net::HTTPRequest::HTTP_GET,
|
||||
Poco::Net::HTTPRequest::HTTP_OPTIONS},
|
||||
Server, TransactionId, Internal,false) {}
|
||||
static auto PathName() { return std::list<std::string>{"/api/v1/ws"};}
|
||||
void DoGet() final {
|
||||
try
|
||||
{
|
||||
if(Request->find("Upgrade") != Request->end() && Poco::icompare((*Request)["Upgrade"], "websocket") == 0) {
|
||||
try
|
||||
{
|
||||
Poco::Net::WebSocket WS(*Request, *Response);
|
||||
auto Id = MicroServiceCreateUUID();
|
||||
UI_WebSocketClientServer()->NewClient(WS,Id,UserInfo_.userinfo.email);
|
||||
}
|
||||
catch (...) {
|
||||
std::cout << "Cannot create websocket client..." << std::endl;
|
||||
}
|
||||
}
|
||||
} catch(...) {
|
||||
std::cout << "Cannot upgrade connection..." << std::endl;
|
||||
}
|
||||
};
|
||||
void DoDelete() final {};
|
||||
void DoPost() final {};
|
||||
void DoPut() final {};
|
||||
private:
|
||||
};
|
||||
}
|
||||
484
src/framework/RESTAPI_utils.h
Normal file
484
src/framework/RESTAPI_utils.h
Normal file
@@ -0,0 +1,484 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Poco/JSON/Object.h"
|
||||
#include "Poco/JSON/Parser.h"
|
||||
#include "Poco/Data/LOB.h"
|
||||
#include "Poco/Net/HTTPServerRequest.h"
|
||||
|
||||
#include "framework/OpenWifiTypes.h"
|
||||
#include "framework/utils.h"
|
||||
|
||||
namespace OpenWifi::RESTAPI_utils {
|
||||
|
||||
inline void EmbedDocument(const std::string & ObjName, Poco::JSON::Object & Obj, const std::string &ObjStr) {
|
||||
std::string D = ObjStr.empty() ? "{}" : ObjStr;
|
||||
Poco::JSON::Parser P;
|
||||
Poco::Dynamic::Var result = P.parse(D);
|
||||
const auto &DetailsObj = result.extract<Poco::JSON::Object::Ptr>();
|
||||
Obj.set(ObjName, DetailsObj);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, bool V) {
|
||||
Obj.set(Field,V);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, double V) {
|
||||
Obj.set(Field,V);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, float V) {
|
||||
Obj.set(Field,V);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const std::string & S) {
|
||||
Obj.set(Field,S);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const char * S) {
|
||||
Obj.set(Field,S);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, int16_t Value) {
|
||||
Obj.set(Field, Value);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, int32_t Value) {
|
||||
Obj.set(Field, Value);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, int64_t Value) {
|
||||
Obj.set(Field, Value);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, uint16_t Value) {
|
||||
Obj.set(Field, Value);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, uint32_t Value) {
|
||||
Obj.set(Field, Value);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, uint64_t Value) {
|
||||
Obj.set(Field,Value);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Poco::Data::BLOB &Value) {
|
||||
auto Result = Utils::base64encode((const unsigned char *)Value.rawContent(),Value.size());
|
||||
Obj.set(Field,Result);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::StringPairVec & S) {
|
||||
Poco::JSON::Array Array;
|
||||
for(const auto &i:S) {
|
||||
Poco::JSON::Object O;
|
||||
O.set("tag",i.first);
|
||||
O.set("value", i.second);
|
||||
Array.add(O);
|
||||
}
|
||||
Obj.set(Field,Array);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::StringVec &V) {
|
||||
Poco::JSON::Array A;
|
||||
for(const auto &i:V)
|
||||
A.add(i);
|
||||
Obj.set(Field,A);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::TagList &V) {
|
||||
Poco::JSON::Array A;
|
||||
for(const auto &i:V)
|
||||
A.add(i);
|
||||
Obj.set(Field,A);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::CountedMap &M) {
|
||||
Poco::JSON::Array A;
|
||||
for(const auto &[Key,Value]:M) {
|
||||
Poco::JSON::Object O;
|
||||
O.set("tag",Key);
|
||||
O.set("value", Value);
|
||||
A.add(O);
|
||||
}
|
||||
Obj.set(Field,A);
|
||||
}
|
||||
|
||||
inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::Counted3DMapSII &M) {
|
||||
Poco::JSON::Array A;
|
||||
for(const auto &[OrgName,MonthlyNumberMap]:M) {
|
||||
Poco::JSON::Object OrgObject;
|
||||
OrgObject.set("tag",OrgName);
|
||||
Poco::JSON::Array MonthlyArray;
|
||||
for(const auto &[Month,Counter]:MonthlyNumberMap) {
|
||||
Poco::JSON::Object Inner;
|
||||
Inner.set("value", Month);
|
||||
Inner.set("counter", Counter);
|
||||
MonthlyArray.add(Inner);
|
||||
}
|
||||
OrgObject.set("index",MonthlyArray);
|
||||
A.add(OrgObject);
|
||||
}
|
||||
Obj.set(Field, A);
|
||||
}
|
||||
|
||||
template<typename T> void field_to_json(Poco::JSON::Object &Obj,
|
||||
const char *Field,
|
||||
const T &V,
|
||||
std::function<std::string(const T &)> F) {
|
||||
Obj.set(Field, F(V));
|
||||
}
|
||||
|
||||
template<class T> void field_to_json(Poco::JSON::Object &Obj, const char *Field, const std::vector<T> &Value) {
|
||||
Poco::JSON::Array Arr;
|
||||
for(const auto &i:Value) {
|
||||
Poco::JSON::Object AO;
|
||||
i.to_json(AO);
|
||||
Arr.add(AO);
|
||||
}
|
||||
Obj.set(Field, Arr);
|
||||
}
|
||||
|
||||
template<class T> void field_to_json(Poco::JSON::Object &Obj, const char *Field, const T &Value) {
|
||||
Poco::JSON::Object Answer;
|
||||
Value.to_json(Answer);
|
||||
Obj.set(Field, Answer);
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
///////////////////////////
|
||||
///////////////////////////
|
||||
///////////////////////////
|
||||
|
||||
template<typename T> bool field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, T & V,
|
||||
std::function<T(const std::string &)> F) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field))
|
||||
V = F(Obj->get(Field).toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, std::string &S) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field))
|
||||
S = Obj->get(Field).toString();
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, double & Value) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field))
|
||||
Value = (double)Obj->get(Field);
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, float & Value) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field))
|
||||
Value = (float)Obj->get(Field);
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, bool &Value) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field))
|
||||
Value = (Obj->get(Field).toString() == "true");
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, int16_t &Value) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field))
|
||||
Value = (int16_t)Obj->get(Field);
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, int32_t &Value) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field))
|
||||
Value = (int32_t) Obj->get(Field);
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, int64_t &Value) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field))
|
||||
Value = (int64_t)Obj->get(Field);
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, uint16_t &Value) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field))
|
||||
Value = (uint16_t)Obj->get(Field);
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, uint32_t &Value) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field))
|
||||
Value = (uint32_t)Obj->get(Field);
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, uint64_t &Value) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field))
|
||||
Value = (uint64_t)Obj->get(Field);
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Poco::Data::BLOB &Value) {
|
||||
if(Obj->has(Field) && !Obj->isNull(Field)) {
|
||||
auto Result = Utils::base64decode(Obj->get(Field).toString());
|
||||
Value.assignRaw((const unsigned char *)&Result[0],Result.size());
|
||||
}
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Types::StringPairVec &Vec) {
|
||||
if(Obj->isArray(Field) && !Obj->isNull(Field)) {
|
||||
auto O = Obj->getArray(Field);
|
||||
for(const auto &i:*O) {
|
||||
std::string S1,S2;
|
||||
auto Inner = i.extract<Poco::JSON::Object::Ptr>();
|
||||
if(Inner->has("tag"))
|
||||
S1 = Inner->get("tag").toString();
|
||||
if(Inner->has("value"))
|
||||
S2 = Inner->get("value").toString();
|
||||
auto P = std::make_pair(S1,S2);
|
||||
Vec.push_back(P);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Types::StringVec &Value) {
|
||||
if(Obj->isArray(Field) && !Obj->isNull(Field)) {
|
||||
Value.clear();
|
||||
Poco::JSON::Array::Ptr A = Obj->getArray(Field);
|
||||
for(const auto &i:*A) {
|
||||
Value.push_back(i.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Types::TagList &Value) {
|
||||
if(Obj->isArray(Field) && !Obj->isNull(Field)) {
|
||||
Value.clear();
|
||||
Poco::JSON::Array::Ptr A = Obj->getArray(Field);
|
||||
for(const auto &i:*A) {
|
||||
Value.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, std::vector<T> &Value) {
|
||||
if(Obj->isArray(Field) && !Obj->isNull(Field)) {
|
||||
Poco::JSON::Array::Ptr Arr = Obj->getArray(Field);
|
||||
for(auto &i:*Arr) {
|
||||
auto InnerObj = i.extract<Poco::JSON::Object::Ptr>();
|
||||
T NewItem;
|
||||
NewItem.from_json(InnerObj);
|
||||
Value.push_back(NewItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, T &Value) {
|
||||
if(Obj->isObject(Field) && !Obj->isNull(Field)) {
|
||||
Poco::JSON::Object::Ptr A = Obj->getObject(Field);
|
||||
Value.from_json(A);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string to_string(const Types::TagList & ObjectArray) {
|
||||
Poco::JSON::Array OutputArr;
|
||||
if(ObjectArray.empty())
|
||||
return "[]";
|
||||
for(auto const &i:ObjectArray) {
|
||||
OutputArr.add(i);
|
||||
}
|
||||
std::ostringstream OS;
|
||||
Poco::JSON::Stringifier::stringify(OutputArr,OS, 0,0, Poco::JSON_PRESERVE_KEY_ORDER );
|
||||
return OS.str();
|
||||
}
|
||||
|
||||
inline std::string to_string(const Types::StringVec & ObjectArray) {
|
||||
Poco::JSON::Array OutputArr;
|
||||
if(ObjectArray.empty())
|
||||
return "[]";
|
||||
for(auto const &i:ObjectArray) {
|
||||
OutputArr.add(i);
|
||||
}
|
||||
std::ostringstream OS;
|
||||
Poco::JSON::Stringifier::condense(OutputArr,OS);
|
||||
return OS.str();
|
||||
}
|
||||
|
||||
inline std::string to_string(const Types::StringPairVec & ObjectArray) {
|
||||
Poco::JSON::Array OutputArr;
|
||||
if(ObjectArray.empty())
|
||||
return "[]";
|
||||
for(auto const &i:ObjectArray) {
|
||||
Poco::JSON::Array InnerArray;
|
||||
InnerArray.add(i.first);
|
||||
InnerArray.add(i.second);
|
||||
OutputArr.add(InnerArray);
|
||||
}
|
||||
std::ostringstream OS;
|
||||
Poco::JSON::Stringifier::condense(OutputArr,OS);
|
||||
return OS.str();
|
||||
}
|
||||
|
||||
template<class T> std::string to_string(const std::vector<T> & ObjectArray) {
|
||||
Poco::JSON::Array OutputArr;
|
||||
if(ObjectArray.empty())
|
||||
return "[]";
|
||||
for(auto const &i:ObjectArray) {
|
||||
Poco::JSON::Object O;
|
||||
i.to_json(O);
|
||||
OutputArr.add(O);
|
||||
}
|
||||
std::ostringstream OS;
|
||||
Poco::JSON::Stringifier::condense(OutputArr,OS);
|
||||
return OS.str();
|
||||
}
|
||||
|
||||
template<class T> std::string to_string(const std::vector<std::vector<T>> & ObjectArray) {
|
||||
Poco::JSON::Array OutputArr;
|
||||
if(ObjectArray.empty())
|
||||
return "[]";
|
||||
for(auto const &i:ObjectArray) {
|
||||
Poco::JSON::Array InnerArr;
|
||||
for(auto const &j:i) {
|
||||
if constexpr(std::is_integral<T>::value) {
|
||||
InnerArr.add(j);
|
||||
} if constexpr(std::is_same_v<T,std::string>) {
|
||||
InnerArr.add(j);
|
||||
} else {
|
||||
InnerArr.add(j);
|
||||
Poco::JSON::Object O;
|
||||
j.to_json(O);
|
||||
InnerArr.add(O);
|
||||
}
|
||||
}
|
||||
OutputArr.add(InnerArr);
|
||||
}
|
||||
std::ostringstream OS;
|
||||
Poco::JSON::Stringifier::condense(OutputArr,OS);
|
||||
return OS.str();
|
||||
}
|
||||
|
||||
template<class T> std::string to_string(const T & Object) {
|
||||
Poco::JSON::Object OutputObj;
|
||||
Object.to_json(OutputObj);
|
||||
std::ostringstream OS;
|
||||
Poco::JSON::Stringifier::condense(OutputObj,OS);
|
||||
return OS.str();
|
||||
}
|
||||
|
||||
inline Types::StringVec to_object_array(const std::string & ObjectString) {
|
||||
|
||||
Types::StringVec Result;
|
||||
if(ObjectString.empty())
|
||||
return Result;
|
||||
|
||||
try {
|
||||
Poco::JSON::Parser P;
|
||||
auto Object = P.parse(ObjectString).template extract<Poco::JSON::Array::Ptr>();
|
||||
for (auto const &i : *Object) {
|
||||
Result.push_back(i.toString());
|
||||
}
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
inline OpenWifi::Types::TagList to_taglist(const std::string & ObjectString) {
|
||||
Types::TagList Result;
|
||||
if(ObjectString.empty())
|
||||
return Result;
|
||||
|
||||
try {
|
||||
Poco::JSON::Parser P;
|
||||
auto Object = P.parse(ObjectString).template extract<Poco::JSON::Array::Ptr>();
|
||||
for (auto const &i : *Object) {
|
||||
Result.push_back(i);
|
||||
}
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
inline Types::StringPairVec to_stringpair_array(const std::string &S) {
|
||||
Types::StringPairVec R;
|
||||
if(S.empty())
|
||||
return R;
|
||||
try {
|
||||
Poco::JSON::Parser P;
|
||||
auto Object = P.parse(S).template extract<Poco::JSON::Array::Ptr>();
|
||||
for (const auto &i : *Object) {
|
||||
auto InnerObject = i.template extract<Poco::JSON::Array::Ptr>();
|
||||
if(InnerObject->size()==2) {
|
||||
auto S1 = InnerObject->getElement<std::string>(0);
|
||||
auto S2 = InnerObject->getElement<std::string>(1);
|
||||
R.push_back(std::make_pair(S1,S2));
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
|
||||
return R;
|
||||
}
|
||||
|
||||
template<class T> std::vector<T> to_object_array(const std::string & ObjectString) {
|
||||
std::vector<T> Result;
|
||||
if(ObjectString.empty())
|
||||
return Result;
|
||||
|
||||
try {
|
||||
Poco::JSON::Parser P;
|
||||
auto Object = P.parse(ObjectString).template extract<Poco::JSON::Array::Ptr>();
|
||||
for (auto const &i : *Object) {
|
||||
auto InnerObject = i.template extract<Poco::JSON::Object::Ptr>();
|
||||
T Obj;
|
||||
Obj.from_json(InnerObject);
|
||||
Result.push_back(Obj);
|
||||
}
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
template<class T> std::vector<std::vector<T>> to_array_of_array_of_object(const std::string & ObjectString) {
|
||||
std::vector<std::vector<T>> Result;
|
||||
if(ObjectString.empty())
|
||||
return Result;
|
||||
try {
|
||||
Poco::JSON::Parser P1;
|
||||
auto OutterArray = P1.parse(ObjectString).template extract<Poco::JSON::Array::Ptr>();
|
||||
for (auto const &i : *OutterArray) {
|
||||
Poco::JSON::Parser P2;
|
||||
auto InnerArray = P2.parse(i).template extract<Poco::JSON::Array::Ptr>();
|
||||
std::vector<T> InnerVector;
|
||||
for(auto const &j: *InnerArray) {
|
||||
auto Object = j.template extract<Poco::JSON::Object::Ptr>();
|
||||
T Obj;
|
||||
Obj.from_json(Object);
|
||||
InnerVector.push_back(Obj);
|
||||
}
|
||||
Result.push_back(InnerVector);
|
||||
}
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
template<class T> T to_object(const std::string & ObjectString) {
|
||||
T Result;
|
||||
|
||||
if(ObjectString.empty())
|
||||
return Result;
|
||||
|
||||
Poco::JSON::Parser P;
|
||||
auto Object = P.parse(ObjectString).template extract<Poco::JSON::Object::Ptr>();
|
||||
Result.from_json(Object);
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
template<class T> bool from_request(T & Obj, Poco::Net::HTTPServerRequest &Request) {
|
||||
Poco::JSON::Parser IncomingParser;
|
||||
auto RawObject = IncomingParser.parse(Request.stream()).extract<Poco::JSON::Object::Ptr>();
|
||||
Obj.from_json(RawObject);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,8 @@
|
||||
#include "Poco/Data/MySQL/Connector.h"
|
||||
#endif
|
||||
|
||||
#include "framework/MicroService.h"
|
||||
#include "framework/SubSystemServer.h"
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
enum DBType {
|
||||
@@ -34,7 +35,7 @@ namespace OpenWifi {
|
||||
std::lock_guard Guard(Mutex_);
|
||||
|
||||
Logger().notice("Starting.");
|
||||
std::string DBType = MicroService::instance().ConfigGetString("storage.type");
|
||||
std::string DBType = MicroServiceConfigGetString("storage.type","");
|
||||
|
||||
if (DBType == "sqlite") {
|
||||
Setup_SQLite();
|
||||
@@ -72,9 +73,9 @@ namespace OpenWifi {
|
||||
inline int StorageClass::Setup_SQLite() {
|
||||
Logger().notice("SQLite StorageClass enabled.");
|
||||
dbType_ = sqlite;
|
||||
auto DBName = MicroService::instance().DataDir() + "/" + MicroService::instance().ConfigGetString("storage.type.sqlite.db");
|
||||
int NumSessions = (int) MicroService::instance().ConfigGetInt("storage.type.sqlite.maxsessions", 64);
|
||||
int IdleTime = (int) MicroService::instance().ConfigGetInt("storage.type.sqlite.idletime", 60);
|
||||
auto DBName = MicroServiceDataDirectory() + "/" + MicroServiceConfigGetString("storage.type.sqlite.db","");
|
||||
int NumSessions = (int) MicroServiceConfigGetInt("storage.type.sqlite.maxsessions", 64);
|
||||
int IdleTime = (int) MicroServiceConfigGetInt("storage.type.sqlite.idletime", 60);
|
||||
|
||||
Poco::Data::SQLite::Connector::registerConnector();
|
||||
// Pool_ = std::make_unique<Poco::Data::SessionPool>(new Poco::Data::SessionPool(SQLiteConn_.name(), DBName, 8,
|
||||
@@ -87,13 +88,13 @@ namespace OpenWifi {
|
||||
inline int StorageClass::Setup_MySQL() {
|
||||
Logger().notice("MySQL StorageClass enabled.");
|
||||
dbType_ = mysql;
|
||||
int NumSessions = (int) MicroService::instance().ConfigGetInt("storage.type.mysql.maxsessions", 64);
|
||||
int IdleTime = (int) MicroService::instance().ConfigGetInt("storage.type.mysql.idletime", 60);
|
||||
auto Host = MicroService::instance().ConfigGetString("storage.type.mysql.host");
|
||||
auto Username = MicroService::instance().ConfigGetString("storage.type.mysql.username");
|
||||
auto Password = MicroService::instance().ConfigGetString("storage.type.mysql.password");
|
||||
auto Database = MicroService::instance().ConfigGetString("storage.type.mysql.database");
|
||||
auto Port = MicroService::instance().ConfigGetString("storage.type.mysql.port");
|
||||
int NumSessions = (int) MicroServiceConfigGetInt("storage.type.mysql.maxsessions", 64);
|
||||
int IdleTime = (int) MicroServiceConfigGetInt("storage.type.mysql.idletime", 60);
|
||||
auto Host = MicroServiceConfigGetString("storage.type.mysql.host","");
|
||||
auto Username = MicroServiceConfigGetString("storage.type.mysql.username","");
|
||||
auto Password = MicroServiceConfigGetString("storage.type.mysql.password","");
|
||||
auto Database = MicroServiceConfigGetString("storage.type.mysql.database","");
|
||||
auto Port = MicroServiceConfigGetString("storage.type.mysql.port","");
|
||||
|
||||
std::string ConnectionStr =
|
||||
"host=" + Host +
|
||||
@@ -112,14 +113,14 @@ namespace OpenWifi {
|
||||
inline int StorageClass::Setup_PostgreSQL() {
|
||||
Logger().notice("PostgreSQL StorageClass enabled.");
|
||||
dbType_ = pgsql;
|
||||
int NumSessions = (int) MicroService::instance().ConfigGetInt("storage.type.postgresql.maxsessions", 64);
|
||||
int IdleTime = (int) MicroService::instance().ConfigGetInt("storage.type.postgresql.idletime", 60);
|
||||
auto Host = MicroService::instance().ConfigGetString("storage.type.postgresql.host");
|
||||
auto Username = MicroService::instance().ConfigGetString("storage.type.postgresql.username");
|
||||
auto Password = MicroService::instance().ConfigGetString("storage.type.postgresql.password");
|
||||
auto Database = MicroService::instance().ConfigGetString("storage.type.postgresql.database");
|
||||
auto Port = MicroService::instance().ConfigGetString("storage.type.postgresql.port");
|
||||
auto ConnectionTimeout = MicroService::instance().ConfigGetString("storage.type.postgresql.connectiontimeout");
|
||||
int NumSessions = (int) MicroServiceConfigGetInt("storage.type.postgresql.maxsessions", 64);
|
||||
int IdleTime = (int) MicroServiceConfigGetInt("storage.type.postgresql.idletime", 60);
|
||||
auto Host = MicroServiceConfigGetString("storage.type.postgresql.host", "");
|
||||
auto Username = MicroServiceConfigGetString("storage.type.postgresql.username", "");
|
||||
auto Password = MicroServiceConfigGetString("storage.type.postgresql.password", "");
|
||||
auto Database = MicroServiceConfigGetString("storage.type.postgresql.database", "");
|
||||
auto Port = MicroServiceConfigGetString("storage.type.postgresql.port", "");
|
||||
auto ConnectionTimeout = MicroServiceConfigGetString("storage.type.postgresql.connectiontimeout", "");
|
||||
|
||||
std::string ConnectionStr =
|
||||
"host=" + Host +
|
||||
|
||||
320
src/framework/SubSystemServer.cpp
Normal file
320
src/framework/SubSystemServer.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include "framework/SubSystemServer.h"
|
||||
|
||||
#include "Poco/Net/SSLManager.h"
|
||||
#include "Poco/DateTimeFormatter.h"
|
||||
#include "Poco/DateTimeFormat.h"
|
||||
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
PropertiesFileServerEntry::PropertiesFileServerEntry(std::string Address, uint32_t port, std::string Key_file,
|
||||
std::string Cert_file, std::string RootCa, std::string Issuer,
|
||||
std::string ClientCas, std::string Cas,
|
||||
std::string Key_file_password, std::string Name,
|
||||
Poco::Net::Context::VerificationMode M,
|
||||
int backlog)
|
||||
: address_(std::move(Address)),
|
||||
port_(port),
|
||||
cert_file_(std::move(Cert_file)),
|
||||
key_file_(std::move(Key_file)),
|
||||
root_ca_(std::move(RootCa)),
|
||||
key_file_password_(std::move(Key_file_password)),
|
||||
issuer_cert_file_(std::move(Issuer)),
|
||||
client_cas_(std::move(ClientCas)),
|
||||
cas_(std::move(Cas)),
|
||||
name_(std::move(Name)),
|
||||
backlog_(backlog),
|
||||
level_(M) {
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] Poco::Net::SecureServerSocket PropertiesFileServerEntry::CreateSecureSocket(Poco::Logger &L) const {
|
||||
Poco::Net::Context::Params P;
|
||||
|
||||
P.verificationMode = level_;
|
||||
P.verificationDepth = 9;
|
||||
P.loadDefaultCAs = root_ca_.empty();
|
||||
P.cipherList = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH";
|
||||
P.dhUse2048Bits = true;
|
||||
P.caLocation = cas_;
|
||||
|
||||
auto Context = Poco::AutoPtr<Poco::Net::Context>(new Poco::Net::Context(Poco::Net::Context::TLS_SERVER_USE, P));
|
||||
|
||||
if(!key_file_password_.empty()) {
|
||||
auto PassphraseHandler = Poco::SharedPtr<MyPrivateKeyPassphraseHandler>( new MyPrivateKeyPassphraseHandler(key_file_password_,L));
|
||||
Poco::Net::SSLManager::instance().initializeServer(PassphraseHandler, nullptr,Context);
|
||||
}
|
||||
|
||||
if (!cert_file_.empty() && !key_file_.empty()) {
|
||||
Poco::Crypto::X509Certificate Cert(cert_file_);
|
||||
Poco::Crypto::X509Certificate Root(root_ca_);
|
||||
|
||||
Context->useCertificate(Cert);
|
||||
Context->addChainCertificate(Root);
|
||||
|
||||
Context->addCertificateAuthority(Root);
|
||||
|
||||
if (level_ == Poco::Net::Context::VERIFY_STRICT) {
|
||||
if (issuer_cert_file_.empty()) {
|
||||
L.fatal("In strict mode, you must supply ans issuer certificate");
|
||||
}
|
||||
if (client_cas_.empty()) {
|
||||
L.fatal("In strict mode, client cas must be supplied");
|
||||
}
|
||||
Poco::Crypto::X509Certificate Issuing(issuer_cert_file_);
|
||||
Context->addChainCertificate(Issuing);
|
||||
Context->addCertificateAuthority(Issuing);
|
||||
}
|
||||
|
||||
Poco::Crypto::RSAKey Key("", key_file_, key_file_password_);
|
||||
Context->usePrivateKey(Key);
|
||||
|
||||
SSL_CTX *SSLCtx = Context->sslContext();
|
||||
if (!SSL_CTX_check_private_key(SSLCtx)) {
|
||||
L.fatal(fmt::format("Wrong Certificate({}) for Key({})", cert_file_, key_file_));
|
||||
}
|
||||
|
||||
SSL_CTX_set_verify(SSLCtx, SSL_VERIFY_PEER, nullptr);
|
||||
|
||||
if (level_ == Poco::Net::Context::VERIFY_STRICT) {
|
||||
SSL_CTX_set_client_CA_list(SSLCtx, SSL_load_client_CA_file(client_cas_.c_str()));
|
||||
}
|
||||
SSL_CTX_enable_ct(SSLCtx, SSL_CT_VALIDATION_STRICT);
|
||||
SSL_CTX_dane_enable(SSLCtx);
|
||||
|
||||
Context->enableSessionCache();
|
||||
Context->setSessionCacheSize(0);
|
||||
Context->setSessionTimeout(60);
|
||||
Context->enableExtendedCertificateVerification(true);
|
||||
Context->disableStatelessSessionResumption();
|
||||
}
|
||||
|
||||
if (address_ == "*") {
|
||||
Poco::Net::IPAddress Addr(Poco::Net::IPAddress::wildcard(
|
||||
Poco::Net::Socket::supportsIPv6() ? Poco::Net::AddressFamily::IPv6
|
||||
: Poco::Net::AddressFamily::IPv4));
|
||||
Poco::Net::SocketAddress SockAddr(Addr, port_);
|
||||
|
||||
return Poco::Net::SecureServerSocket(SockAddr, backlog_, Context);
|
||||
} else {
|
||||
Poco::Net::IPAddress Addr(address_);
|
||||
Poco::Net::SocketAddress SockAddr(Addr, port_);
|
||||
|
||||
return Poco::Net::SecureServerSocket(SockAddr, backlog_, Context);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] Poco::Net::ServerSocket PropertiesFileServerEntry::CreateSocket([[maybe_unused]] Poco::Logger &L) const {
|
||||
Poco::Net::Context::Params P;
|
||||
|
||||
if (address_ == "*") {
|
||||
Poco::Net::IPAddress Addr(Poco::Net::IPAddress::wildcard(
|
||||
Poco::Net::Socket::supportsIPv6() ? Poco::Net::AddressFamily::IPv6
|
||||
: Poco::Net::AddressFamily::IPv4));
|
||||
Poco::Net::SocketAddress SockAddr(Addr, port_);
|
||||
return Poco::Net::ServerSocket(SockAddr, backlog_);
|
||||
} else {
|
||||
Poco::Net::IPAddress Addr(address_);
|
||||
Poco::Net::SocketAddress SockAddr(Addr, port_);
|
||||
return Poco::Net::ServerSocket(SockAddr, backlog_);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertiesFileServerEntry::LogCertInfo(Poco::Logger &L, const Poco::Crypto::X509Certificate &C) const {
|
||||
L.information("=============================================================================================");
|
||||
L.information(fmt::format("> Issuer: {}", C.issuerName()));
|
||||
L.information("---------------------------------------------------------------------------------------------");
|
||||
L.information(fmt::format("> Common Name: {}",
|
||||
C.issuerName(Poco::Crypto::X509Certificate::NID_COMMON_NAME)));
|
||||
L.information(fmt::format("> Country: {}",
|
||||
C.issuerName(Poco::Crypto::X509Certificate::NID_COUNTRY)));
|
||||
L.information(fmt::format("> Locality: {}",
|
||||
C.issuerName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME)));
|
||||
L.information(fmt::format("> State/Prov: {}",
|
||||
C.issuerName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE)));
|
||||
L.information(fmt::format("> Org name: {}",
|
||||
C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME)));
|
||||
L.information(
|
||||
fmt::format("> Org unit: {}",
|
||||
C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME)));
|
||||
L.information(
|
||||
fmt::format("> Email: {}",
|
||||
C.issuerName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS)));
|
||||
L.information(fmt::format("> Serial#: {}",
|
||||
C.issuerName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER)));
|
||||
L.information("---------------------------------------------------------------------------------------------");
|
||||
L.information(fmt::format("> Subject: {}", C.subjectName()));
|
||||
L.information("---------------------------------------------------------------------------------------------");
|
||||
L.information(fmt::format("> Common Name: {}",
|
||||
C.subjectName(Poco::Crypto::X509Certificate::NID_COMMON_NAME)));
|
||||
L.information(fmt::format("> Country: {}",
|
||||
C.subjectName(Poco::Crypto::X509Certificate::NID_COUNTRY)));
|
||||
L.information(fmt::format("> Locality: {}",
|
||||
C.subjectName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME)));
|
||||
L.information(
|
||||
fmt::format("> State/Prov: {}",
|
||||
C.subjectName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE)));
|
||||
L.information(
|
||||
fmt::format("> Org name: {}",
|
||||
C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME)));
|
||||
L.information(
|
||||
fmt::format("> Org unit: {}",
|
||||
C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME)));
|
||||
L.information(
|
||||
fmt::format("> Email: {}",
|
||||
C.subjectName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS)));
|
||||
L.information(fmt::format("> Serial#: {}",
|
||||
C.subjectName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER)));
|
||||
L.information("---------------------------------------------------------------------------------------------");
|
||||
L.information(fmt::format("> Signature Algo: {}", C.signatureAlgorithm()));
|
||||
auto From = Poco::DateTimeFormatter::format(C.validFrom(), Poco::DateTimeFormat::HTTP_FORMAT);
|
||||
L.information(fmt::format("> Valid from: {}", From));
|
||||
auto Expires =
|
||||
Poco::DateTimeFormatter::format(C.expiresOn(), Poco::DateTimeFormat::HTTP_FORMAT);
|
||||
L.information(fmt::format("> Expires on: {}", Expires));
|
||||
L.information(fmt::format("> Version: {}", (int)C.version()));
|
||||
L.information(fmt::format("> Serial #: {}", C.serialNumber()));
|
||||
L.information("=============================================================================================");
|
||||
}
|
||||
|
||||
void PropertiesFileServerEntry::LogCert(Poco::Logger &L) const {
|
||||
try {
|
||||
Poco::Crypto::X509Certificate C(cert_file_);
|
||||
L.information("=============================================================================================");
|
||||
L.information("=============================================================================================");
|
||||
L.information(fmt::format("Certificate Filename: {}", cert_file_));
|
||||
LogCertInfo(L, C);
|
||||
L.information("=============================================================================================");
|
||||
|
||||
if (!issuer_cert_file_.empty()) {
|
||||
Poco::Crypto::X509Certificate C1(issuer_cert_file_);
|
||||
L.information("=============================================================================================");
|
||||
L.information("=============================================================================================");
|
||||
L.information(fmt::format("Issues Certificate Filename: {}", issuer_cert_file_));
|
||||
LogCertInfo(L, C1);
|
||||
L.information("=============================================================================================");
|
||||
}
|
||||
|
||||
if (!client_cas_.empty()) {
|
||||
std::vector<Poco::Crypto::X509Certificate> Certs =
|
||||
Poco::Net::X509Certificate::readPEM(client_cas_);
|
||||
|
||||
L.information("=============================================================================================");
|
||||
L.information("=============================================================================================");
|
||||
L.information(fmt::format("Client CAs Filename: {}", client_cas_));
|
||||
L.information("=============================================================================================");
|
||||
auto i = 1;
|
||||
for (const auto &C3 : Certs) {
|
||||
L.information(fmt::format(" Index: {}", i));
|
||||
L.information("=============================================================================================");
|
||||
LogCertInfo(L, C3);
|
||||
i++;
|
||||
}
|
||||
L.information("=============================================================================================");
|
||||
}
|
||||
|
||||
} catch (const Poco::Exception &E) {
|
||||
L.log(E);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertiesFileServerEntry::LogCas(Poco::Logger &L) const {
|
||||
try {
|
||||
std::vector<Poco::Crypto::X509Certificate> Certs =
|
||||
Poco::Net::X509Certificate::readPEM(root_ca_);
|
||||
|
||||
L.information("=============================================================================================");
|
||||
L.information("=============================================================================================");
|
||||
L.information(fmt::format("CA Filename: {}", root_ca_));
|
||||
L.information("=============================================================================================");
|
||||
auto i = 1;
|
||||
for (const auto &C : Certs) {
|
||||
L.information(fmt::format(" Index: {}", i));
|
||||
L.information("=============================================================================================");
|
||||
LogCertInfo(L, C);
|
||||
i++;
|
||||
}
|
||||
L.information("=============================================================================================");
|
||||
} catch (const Poco::Exception &E) {
|
||||
L.log(E);
|
||||
}
|
||||
}
|
||||
|
||||
SubSystemServer::SubSystemServer(const std::string &Name, const std::string &LoggingPrefix,
|
||||
const std::string &SubSystemConfigPrefix):
|
||||
Name_(Name),
|
||||
LoggerPrefix_(LoggingPrefix),
|
||||
SubSystemConfigPrefix_(SubSystemConfigPrefix) {
|
||||
}
|
||||
|
||||
void SubSystemServer::initialize([[maybe_unused]] Poco::Util::Application &self) {
|
||||
auto i = 0;
|
||||
bool good = true;
|
||||
|
||||
auto NewLevel = MicroServiceConfigGetString("logging.level." + Name_, "");
|
||||
if(NewLevel.empty())
|
||||
Logger_ = std::make_unique<LoggerWrapper>(Poco::Logger::create(LoggerPrefix_, Poco::Logger::root().getChannel(), Poco::Logger::root().getLevel()));
|
||||
else
|
||||
Logger_ = std::make_unique<LoggerWrapper>(Poco::Logger::create(LoggerPrefix_, Poco::Logger::root().getChannel(), Poco::Logger::parseLevel(NewLevel)));
|
||||
|
||||
ConfigServersList_.clear();
|
||||
while (good) {
|
||||
std::string root{SubSystemConfigPrefix_ + ".host." + std::to_string(i) + "."};
|
||||
|
||||
std::string address{root + "address"};
|
||||
if (MicroServiceConfigGetString(address, "").empty()) {
|
||||
good = false;
|
||||
} else {
|
||||
std::string port{root + "port"};
|
||||
std::string key{root + "key"};
|
||||
std::string key_password{root + "key.password"};
|
||||
std::string cert{root + "cert"};
|
||||
std::string name{root + "name"};
|
||||
std::string backlog{root + "backlog"};
|
||||
std::string rootca{root + "rootca"};
|
||||
std::string issuer{root + "issuer"};
|
||||
std::string clientcas(root + "clientcas");
|
||||
std::string cas{root + "cas"};
|
||||
|
||||
std::string level{root + "security"};
|
||||
Poco::Net::Context::VerificationMode M = Poco::Net::Context::VERIFY_RELAXED;
|
||||
|
||||
auto L = MicroServiceConfigGetString(level, "");
|
||||
|
||||
if (L == "strict") {
|
||||
M = Poco::Net::Context::VERIFY_STRICT;
|
||||
} else if (L == "none") {
|
||||
M = Poco::Net::Context::VERIFY_NONE;
|
||||
} else if (L == "relaxed") {
|
||||
M = Poco::Net::Context::VERIFY_RELAXED;
|
||||
} else if (L == "once")
|
||||
M = Poco::Net::Context::VERIFY_ONCE;
|
||||
|
||||
PropertiesFileServerEntry entry(MicroServiceConfigGetString(address, ""),
|
||||
MicroServiceConfigGetInt(port, 0),
|
||||
MicroServiceConfigPath(key, ""),
|
||||
MicroServiceConfigPath(cert, ""),
|
||||
MicroServiceConfigPath(rootca, ""),
|
||||
MicroServiceConfigPath(issuer, ""),
|
||||
MicroServiceConfigPath(clientcas, ""),
|
||||
MicroServiceConfigPath(cas, ""),
|
||||
MicroServiceConfigGetString(key_password, ""),
|
||||
MicroServiceConfigGetString(name, ""), M,
|
||||
(int)MicroServiceConfigGetInt(backlog, 64));
|
||||
ConfigServersList_.push_back(entry);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace OpenWifi
|
||||
124
src/framework/SubSystemServer.h
Normal file
124
src/framework/SubSystemServer.h
Normal file
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
#include "Poco/Util/Application.h"
|
||||
#include "Poco/Net/Context.h"
|
||||
#include "Poco/Net/SecureServerSocket.h"
|
||||
#include "Poco/Net/PrivateKeyPassphraseHandler.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
class MyPrivateKeyPassphraseHandler : public Poco::Net::PrivateKeyPassphraseHandler {
|
||||
public:
|
||||
explicit MyPrivateKeyPassphraseHandler(const std::string &Password, Poco::Logger & Logger):
|
||||
PrivateKeyPassphraseHandler(true),
|
||||
Password_(Password),
|
||||
Logger_(Logger) {
|
||||
}
|
||||
|
||||
void onPrivateKeyRequested([[maybe_unused]] const void * pSender,std::string & privateKey) {
|
||||
Logger_.information("Returning key passphrase.");
|
||||
privateKey = Password_;
|
||||
};
|
||||
inline Poco::Logger & Logger() { return Logger_; }
|
||||
private:
|
||||
std::string Password_;
|
||||
Poco::Logger & Logger_;
|
||||
};
|
||||
|
||||
class PropertiesFileServerEntry {
|
||||
public:
|
||||
PropertiesFileServerEntry(std::string Address, uint32_t port, std::string Key_file,
|
||||
std::string Cert_file, std::string RootCa, std::string Issuer,
|
||||
std::string ClientCas, std::string Cas,
|
||||
std::string Key_file_password = "", std::string Name = "",
|
||||
Poco::Net::Context::VerificationMode M =
|
||||
Poco::Net::Context::VerificationMode::VERIFY_RELAXED,
|
||||
int backlog = 64);
|
||||
|
||||
[[nodiscard]] inline const std::string &Address() const { return address_; };
|
||||
[[nodiscard]] inline uint32_t Port() const { return port_; };
|
||||
[[nodiscard]] inline auto KeyFile() const { return key_file_; };
|
||||
[[nodiscard]] inline auto CertFile() const { return cert_file_; };
|
||||
[[nodiscard]] inline auto RootCA() const { return root_ca_; };
|
||||
[[nodiscard]] inline auto KeyFilePassword() const { return key_file_password_; };
|
||||
[[nodiscard]] inline auto IssuerCertFile() const { return issuer_cert_file_; };
|
||||
[[nodiscard]] inline auto Name() const { return name_; };
|
||||
[[nodiscard]] inline int Backlog() const { return backlog_; }
|
||||
[[nodiscard]] inline auto Cas() const { return cas_; }
|
||||
|
||||
[[nodiscard]] Poco::Net::SecureServerSocket CreateSecureSocket(Poco::Logger &L) const;
|
||||
[[nodiscard]] Poco::Net::ServerSocket CreateSocket([[maybe_unused]] Poco::Logger &L) const;
|
||||
void LogCertInfo(Poco::Logger &L, const Poco::Crypto::X509Certificate &C) const;
|
||||
void LogCert(Poco::Logger &L) const;
|
||||
void LogCas(Poco::Logger &L) const;
|
||||
|
||||
private:
|
||||
std::string address_;
|
||||
uint32_t port_;
|
||||
std::string cert_file_;
|
||||
std::string key_file_;
|
||||
std::string root_ca_;
|
||||
std::string key_file_password_;
|
||||
std::string issuer_cert_file_;
|
||||
std::string client_cas_;
|
||||
std::string cas_;
|
||||
std::string name_;
|
||||
int backlog_;
|
||||
Poco::Net::Context::VerificationMode level_;
|
||||
};
|
||||
|
||||
class SubSystemServer : public Poco::Util::Application::Subsystem {
|
||||
public:
|
||||
SubSystemServer(const std::string & Name, const std::string &LoggingPrefix,
|
||||
const std::string & SubSystemConfigPrefix);
|
||||
|
||||
void initialize(Poco::Util::Application &self) override;
|
||||
inline void uninitialize() override {
|
||||
}
|
||||
inline void reinitialize([[maybe_unused]] Poco::Util::Application &self) override {
|
||||
Logger_->L_.information("Reloading of this subsystem is not supported.");
|
||||
}
|
||||
inline void defineOptions([[maybe_unused]] Poco::Util::OptionSet &options) override {
|
||||
}
|
||||
inline const std::string & Name() const { return Name_; };
|
||||
inline const char * name() const override { return Name_.c_str(); }
|
||||
|
||||
inline const PropertiesFileServerEntry & Host(uint64_t index) { return ConfigServersList_[index]; };
|
||||
inline uint64_t HostSize() const { return ConfigServersList_.size(); }
|
||||
inline Poco::Logger & Logger() const { return Logger_->L_; }
|
||||
inline void SetLoggingLevel(const std::string & levelName) {
|
||||
Logger_->L_.setLevel(Poco::Logger::parseLevel(levelName));
|
||||
}
|
||||
inline int GetLoggingLevel() { return Logger_->L_.getLevel(); }
|
||||
|
||||
virtual int Start() = 0;
|
||||
virtual void Stop() = 0;
|
||||
|
||||
struct LoggerWrapper {
|
||||
Poco::Logger & L_;
|
||||
LoggerWrapper(Poco::Logger &L) :
|
||||
L_(L) {
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
std::recursive_mutex Mutex_;
|
||||
std::vector<PropertiesFileServerEntry> ConfigServersList_;
|
||||
|
||||
private:
|
||||
std::unique_ptr<LoggerWrapper> Logger_;
|
||||
std::string Name_;
|
||||
std::string LoggerPrefix_;
|
||||
std::string SubSystemConfigPrefix_;
|
||||
};
|
||||
|
||||
typedef std::vector<SubSystemServer *> SubSystemVec;
|
||||
|
||||
} // namespace OpenWifi
|
||||
266
src/framework/UI_WebSocketClientServer.cpp
Normal file
266
src/framework/UI_WebSocketClientServer.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "Poco/NObserver.h"
|
||||
#include "Poco/JSON/JSONException.h"
|
||||
#include "Poco/JSON/Parser.h"
|
||||
#include "Poco/Logger.h"
|
||||
|
||||
#include "framework/UI_WebSocketClientServer.h"
|
||||
#include "framework/AuthClient.h"
|
||||
#include "framework/MicroServiceFuncs.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
void UI_WebSocketClientServer::NewClient(Poco::Net::WebSocket & WS, const std::string &Id, const std::string &UserName ) {
|
||||
std::lock_guard G(Mutex_);
|
||||
auto Client = std::make_unique<UI_WebSocketClient>(WS,Id,UserName,Logger(), Processor_);
|
||||
Clients_[Id] = std::make_pair(std::move(Client),"");
|
||||
}
|
||||
|
||||
void UI_WebSocketClientServer::SetProcessor( UI_WebSocketClientProcessor * F) {
|
||||
Processor_ = F;
|
||||
}
|
||||
|
||||
void UI_WebSocketClientServer::UnRegister(const std::string &Id) {
|
||||
std::lock_guard G(Mutex_);
|
||||
Clients_.erase(Id);
|
||||
}
|
||||
|
||||
void UI_WebSocketClientServer::SetUser(const std::string &Id, const std::string &UserId) {
|
||||
std::lock_guard G(Mutex_);
|
||||
|
||||
auto it=Clients_.find(Id);
|
||||
if(it!=Clients_.end()) {
|
||||
Clients_[Id] = std::make_pair(std::move(it->second.first),UserId);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool SendToUser(const std::string &userName, const std::string &Payload);
|
||||
UI_WebSocketClientServer::UI_WebSocketClientServer() noexcept:
|
||||
SubSystemServer("WebSocketClientServer", "UI-WSCLNT-SVR", "websocketclients")
|
||||
{
|
||||
}
|
||||
|
||||
void UI_WebSocketClientServer::run() {
|
||||
Running_ = true ;
|
||||
Utils::SetThreadName("ws:uiclnt-svr");
|
||||
while(Running_) {
|
||||
Poco::Thread::trySleep(2000);
|
||||
|
||||
if(!Running_)
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
int UI_WebSocketClientServer::Start() {
|
||||
GoogleApiKey_ = MicroServiceConfigGetString("google.apikey","");
|
||||
GeoCodeEnabled_ = !GoogleApiKey_.empty();
|
||||
// ReactorPool_ = std::make_unique<MyParallelSocketReactor>();
|
||||
ReactorThread_.start(Reactor_);
|
||||
Thr_.start(*this);
|
||||
return 0;
|
||||
};
|
||||
|
||||
void UI_WebSocketClientServer::Stop() {
|
||||
if(Running_) {
|
||||
Reactor_.stop();
|
||||
ReactorThread_.join();
|
||||
Running_ = false;
|
||||
Thr_.wakeUp();
|
||||
Thr_.join();
|
||||
}
|
||||
};
|
||||
|
||||
bool UI_WebSocketClientServer::Send(const std::string &Id, const std::string &Payload) {
|
||||
std::lock_guard G(Mutex_);
|
||||
|
||||
auto It = Clients_.find(Id);
|
||||
if(It!=Clients_.end())
|
||||
return It->second.first->Send(Payload);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UI_WebSocketClientServer::SendToUser(const std::string &UserName, const std::string &Payload) {
|
||||
std::lock_guard G(Mutex_);
|
||||
uint64_t Sent=0;
|
||||
|
||||
for(const auto &client:Clients_) {
|
||||
if(client.second.second == UserName) {
|
||||
try {
|
||||
if (client.second.first->Send(Payload))
|
||||
Sent++;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Sent>0;
|
||||
}
|
||||
|
||||
void UI_WebSocketClientServer::SendToAll(const std::string &Payload) {
|
||||
std::lock_guard G(Mutex_);
|
||||
|
||||
for(const auto &client:Clients_) {
|
||||
try {
|
||||
client.second.first->Send(Payload);
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UI_WebSocketClient::OnSocketError([[maybe_unused]] const Poco::AutoPtr<Poco::Net::ErrorNotification> &pNf) {
|
||||
EndConnection();
|
||||
}
|
||||
|
||||
void UI_WebSocketClient::OnSocketReadable([[maybe_unused]] const Poco::AutoPtr<Poco::Net::ReadableNotification> &pNf) {
|
||||
int flags;
|
||||
int n;
|
||||
bool Done=false;
|
||||
try {
|
||||
Poco::Buffer<char> IncomingFrame(0);
|
||||
n = WS_->receiveFrame(IncomingFrame, flags);
|
||||
auto Op = flags & Poco::Net::WebSocket::FRAME_OP_BITMASK;
|
||||
|
||||
if (n == 0) {
|
||||
poco_debug(Logger(),fmt::format("CLOSE({}): {} UI Client is closing WS connection.", Id_, UserName_));
|
||||
return EndConnection();
|
||||
}
|
||||
|
||||
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_CLOSE: {
|
||||
poco_debug(Logger(),fmt::format("CLOSE({}): {} UI Client is closing WS connection.", Id_, UserName_));
|
||||
Done = true;
|
||||
} break;
|
||||
case Poco::Net::WebSocket::FRAME_OP_TEXT: {
|
||||
IncomingFrame.append(0);
|
||||
if (!Authenticated_) {
|
||||
std::string Frame{IncomingFrame.begin()};
|
||||
auto Tokens = Utils::Split(Frame, ':');
|
||||
bool Expired = false, Contacted = false;
|
||||
if (Tokens.size() == 2 &&
|
||||
AuthClient()->IsAuthorized(Tokens[1], UserInfo_, 0, Expired, Contacted)) {
|
||||
Authenticated_ = true;
|
||||
UserName_ = UserInfo_.userinfo.email;
|
||||
poco_debug(Logger(),fmt::format("START({}): {} UI Client is starting WS connection.", Id_, UserName_));
|
||||
std::string S{"Welcome! Bienvenue! Bienvenidos!"};
|
||||
WS_->sendFrame(S.c_str(), S.size());
|
||||
UI_WebSocketClientServer()->SetUser(Id_, UserInfo_.userinfo.email);
|
||||
} 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<Poco::JSON::Object::Ptr>();
|
||||
std::string Answer;
|
||||
if (Processor_ != nullptr)
|
||||
Processor_->Processor(Obj, Answer, Done);
|
||||
if (!Answer.empty())
|
||||
WS_->sendFrame(Answer.c_str(), (int)Answer.size());
|
||||
else {
|
||||
WS_->sendFrame("{}", 2);
|
||||
}
|
||||
} catch (const Poco::JSON::JSONException &E) {
|
||||
Logger().log(E);
|
||||
Done=true;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
Done=true;
|
||||
}
|
||||
|
||||
if(Done) {
|
||||
return EndConnection();
|
||||
}
|
||||
}
|
||||
|
||||
void UI_WebSocketClient::OnSocketShutdown([[maybe_unused]] const Poco::AutoPtr<Poco::Net::ShutdownNotification> &pNf) {
|
||||
EndConnection();
|
||||
}
|
||||
|
||||
|
||||
UI_WebSocketClient::UI_WebSocketClient( Poco::Net::WebSocket & WS , const std::string &Id, const std::string &UserName, Poco::Logger & L, UI_WebSocketClientProcessor * Processor) :
|
||||
Reactor_(UI_WebSocketClientServer()->Reactor()),
|
||||
Id_(Id),
|
||||
UserName_(UserName),
|
||||
Logger_(L),
|
||||
Processor_(Processor) {
|
||||
WS_ = std::make_unique<Poco::Net::WebSocket>(WS);
|
||||
WS_->setNoDelay(true);
|
||||
WS_->setKeepAlive(true);
|
||||
WS_->setBlocking(false);
|
||||
Reactor_.addEventHandler(*WS_,
|
||||
Poco::NObserver<UI_WebSocketClient, Poco::Net::ReadableNotification>(
|
||||
*this, &UI_WebSocketClient::OnSocketReadable));
|
||||
Reactor_.addEventHandler(*WS_,
|
||||
Poco::NObserver<UI_WebSocketClient, Poco::Net::ShutdownNotification>(
|
||||
*this, &UI_WebSocketClient::OnSocketShutdown));
|
||||
Reactor_.addEventHandler(*WS_,
|
||||
Poco::NObserver<UI_WebSocketClient, Poco::Net::ErrorNotification>(
|
||||
*this, &UI_WebSocketClient::OnSocketError));
|
||||
SocketRegistered_ = true;
|
||||
}
|
||||
|
||||
void UI_WebSocketClient::EndConnection() {
|
||||
if(SocketRegistered_) {
|
||||
SocketRegistered_ = false;
|
||||
(*WS_).shutdown();
|
||||
Reactor_.removeEventHandler(*WS_,
|
||||
Poco::NObserver<UI_WebSocketClient,
|
||||
Poco::Net::ReadableNotification>(*this,&UI_WebSocketClient::OnSocketReadable));
|
||||
Reactor_.removeEventHandler(*WS_,
|
||||
Poco::NObserver<UI_WebSocketClient,
|
||||
Poco::Net::ShutdownNotification>(*this,&UI_WebSocketClient::OnSocketShutdown));
|
||||
Reactor_.removeEventHandler(*WS_,
|
||||
Poco::NObserver<UI_WebSocketClient,
|
||||
Poco::Net::ErrorNotification>(*this,&UI_WebSocketClient::OnSocketError));
|
||||
UI_WebSocketClientServer()->UnRegister(Id_);
|
||||
}
|
||||
}
|
||||
|
||||
UI_WebSocketClient::~UI_WebSocketClient() {
|
||||
EndConnection();
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::string & UI_WebSocketClient::Id() {
|
||||
return Id_;
|
||||
};
|
||||
|
||||
[[nodiscard]] Poco::Logger & UI_WebSocketClient::Logger() {
|
||||
return Logger_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool UI_WebSocketClient::Send(const std::string &Payload) {
|
||||
try {
|
||||
WS_->sendFrame(Payload.c_str(),Payload.size());
|
||||
return true;
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
} // namespace OpenWifi
|
||||
118
src/framework/UI_WebSocketClientServer.h
Normal file
118
src/framework/UI_WebSocketClientServer.h
Normal file
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "Poco/Runnable.h"
|
||||
#include "Poco/Net/SocketReactor.h"
|
||||
#include "Poco/Net/WebSocket.h"
|
||||
#include "Poco/JSON/Object.h"
|
||||
#include "Poco/Net/SocketNotification.h"
|
||||
|
||||
#include "RESTObjects/RESTAPI_SecurityObjects.h"
|
||||
#include "framework/SubSystemServer.h"
|
||||
#include "framework/WebSocketClientNotifications.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
class UI_WebSocketClient;
|
||||
|
||||
class UI_WebSocketClientProcessor {
|
||||
public:
|
||||
virtual void Processor(const Poco::JSON::Object::Ptr &O, std::string &Answer, bool &Done ) = 0;
|
||||
private:
|
||||
};
|
||||
|
||||
class UI_WebSocketClientServer : public SubSystemServer, Poco::Runnable {
|
||||
|
||||
public:
|
||||
static auto instance() {
|
||||
static auto instance_ = new UI_WebSocketClientServer;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
int Start() override;
|
||||
void Stop() override;
|
||||
void run() override;
|
||||
Poco::Net::SocketReactor & Reactor() { return Reactor_; }
|
||||
void NewClient(Poco::Net::WebSocket &WS, const std::string &Id, const std::string &UserName);
|
||||
void SetProcessor(UI_WebSocketClientProcessor *F);
|
||||
void UnRegister(const std::string &Id);
|
||||
void SetUser(const std::string &Id, const std::string &UserId);
|
||||
[[nodiscard]] inline bool GeoCodeEnabled() const { return GeoCodeEnabled_; }
|
||||
[[nodiscard]] inline std::string GoogleApiKey() const { return GoogleApiKey_; }
|
||||
[[nodiscard]] bool Send(const std::string &Id, const std::string &Payload);
|
||||
|
||||
template <typename T> bool
|
||||
SendUserNotification(const std::string &userName, const WebSocketNotification<T> &Notification) {
|
||||
|
||||
Poco::JSON::Object Payload;
|
||||
Notification.to_json(Payload);
|
||||
Poco::JSON::Object Msg;
|
||||
Msg.set("notification",Payload);
|
||||
std::ostringstream OO;
|
||||
Msg.stringify(OO);
|
||||
|
||||
return SendToUser(userName,OO.str());
|
||||
}
|
||||
|
||||
template <typename T> void SendNotification(const WebSocketNotification<T> &Notification) {
|
||||
Poco::JSON::Object Payload;
|
||||
Notification.to_json(Payload);
|
||||
Poco::JSON::Object Msg;
|
||||
Msg.set("notification",Payload);
|
||||
std::ostringstream OO;
|
||||
Msg.stringify(OO);
|
||||
SendToAll(OO.str());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool SendToUser(const std::string &userName, const std::string &Payload);
|
||||
void SendToAll(const std::string &Payload);
|
||||
|
||||
private:
|
||||
mutable std::atomic_bool Running_ = false;
|
||||
Poco::Thread Thr_;
|
||||
Poco::Net::SocketReactor Reactor_;
|
||||
Poco::Thread ReactorThread_;
|
||||
bool GeoCodeEnabled_ = false;
|
||||
std::string GoogleApiKey_;
|
||||
std::map<std::string, std::pair<std::unique_ptr<UI_WebSocketClient>, std::string>> Clients_;
|
||||
UI_WebSocketClientProcessor *Processor_ = nullptr;
|
||||
UI_WebSocketClientServer() noexcept;
|
||||
};
|
||||
|
||||
inline auto UI_WebSocketClientServer() { return UI_WebSocketClientServer::instance(); }
|
||||
|
||||
class UI_WebSocketClient {
|
||||
public:
|
||||
explicit UI_WebSocketClient(Poco::Net::WebSocket &WS,
|
||||
const std::string &Id,
|
||||
const std::string &UserName,
|
||||
Poco::Logger &L,
|
||||
UI_WebSocketClientProcessor *Processor);
|
||||
virtual ~UI_WebSocketClient();
|
||||
[[nodiscard]] inline const std::string &Id();
|
||||
[[nodiscard]] Poco::Logger &Logger();
|
||||
bool Send(const std::string &Payload);
|
||||
void EndConnection();
|
||||
private:
|
||||
std::unique_ptr<Poco::Net::WebSocket> WS_;
|
||||
Poco::Net::SocketReactor &Reactor_;
|
||||
std::string Id_;
|
||||
std::string UserName_;
|
||||
Poco::Logger &Logger_;
|
||||
std::atomic_bool Authenticated_ = false;
|
||||
volatile bool SocketRegistered_=false;
|
||||
SecurityObjects::UserInfoAndPolicy UserInfo_;
|
||||
UI_WebSocketClientProcessor *Processor_ = nullptr;
|
||||
void OnSocketReadable(const Poco::AutoPtr<Poco::Net::ReadableNotification> &pNf);
|
||||
void OnSocketShutdown(const Poco::AutoPtr<Poco::Net::ShutdownNotification> &pNf);
|
||||
void OnSocketError(const Poco::AutoPtr<Poco::Net::ErrorNotification> &pNf);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
146
src/framework/WebSocketClientNotification.cpp
Normal file
146
src/framework/WebSocketClientNotification.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include "framework/WebSocketClientNotifications.h"
|
||||
#include "framework/UI_WebSocketClientServer.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
|
||||
void WebSocketClientNotificationNumberOfConnections(std::uint64_t numberOfDevices,
|
||||
std::uint64_t averageConnectedTime,
|
||||
std::uint64_t numberOfConnectingDevices) {
|
||||
WebSocketNotification<WebSocketClientNotificationNumberOfConnection> N;
|
||||
N.content.numberOfDevices = numberOfDevices;
|
||||
N.content.averageConnectedTime = averageConnectedTime;
|
||||
N.content.numberOfConnectingDevices = numberOfConnectingDevices;
|
||||
N.type = "device_connections_statistics";
|
||||
UI_WebSocketClientServer()->SendNotification(N);
|
||||
}
|
||||
|
||||
void WebSocketClientNotificationDeviceConfigurationChange(const std::string &SerialNumber, uint64_t oldUUID, uint64_t newUUID) {
|
||||
WebSocketNotification<WebNotificationSingleDeviceConfigurationChange> N;
|
||||
N.content.serialNumber = SerialNumber;
|
||||
N.content.oldUUID = oldUUID;
|
||||
N.content.newUUID = newUUID;
|
||||
N.type = "device_configuration_upgrade";
|
||||
UI_WebSocketClientServer()->SendNotification(N);
|
||||
}
|
||||
|
||||
void WebSocketClientNotificationDeviceFirmwareUpdated(const std::string &SerialNumber, const std::string &Firmware) {
|
||||
WebSocketNotification<WebNotificationSingleDeviceFirmwareChange> N;
|
||||
N.content.serialNumber = SerialNumber;
|
||||
N.content.newFirmware = Firmware;
|
||||
N.type = "device_firmware_upgrade";
|
||||
UI_WebSocketClientServer()->SendNotification(N);
|
||||
}
|
||||
|
||||
void WebSocketClientNotificationDeviceConnected(const std::string &SerialNumber) {
|
||||
WebSocketNotification<WebNotificationSingleDevice> N;
|
||||
N.content.serialNumber = SerialNumber;
|
||||
N.type = "device_connection";
|
||||
UI_WebSocketClientServer()->SendNotification(N);
|
||||
}
|
||||
|
||||
void WebSocketClientNotificationDeviceDisconnected(const std::string & SerialNumber) {
|
||||
WebSocketNotification<WebNotificationSingleDevice> N;
|
||||
N.content.serialNumber = SerialNumber;
|
||||
N.type = "device_disconnection";
|
||||
UI_WebSocketClientServer()->SendNotification(N);
|
||||
}
|
||||
|
||||
void WebSocketNotificationJobContent::to_json(Poco::JSON::Object &Obj) const {
|
||||
RESTAPI_utils::field_to_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_to_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_to_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_to_json(Obj,"error",error);
|
||||
RESTAPI_utils::field_to_json(Obj,"warning",warning);
|
||||
RESTAPI_utils::field_to_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_to_json(Obj,"details",details);
|
||||
}
|
||||
|
||||
bool WebSocketNotificationJobContent::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
RESTAPI_utils::field_from_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_from_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_from_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_from_json(Obj,"error",error);
|
||||
RESTAPI_utils::field_from_json(Obj,"warning",warning);
|
||||
RESTAPI_utils::field_from_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_from_json(Obj,"details",details);
|
||||
return true;
|
||||
} catch(...) {
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebSocketClientNotificationVenueUpdateJobCompletionToUser( const std::string & User, WebSocketClientNotificationVenueUpdateJob_t &N) {
|
||||
N.type = "venue_configuration_update";
|
||||
UI_WebSocketClientServer()->SendUserNotification(User,N);
|
||||
}
|
||||
|
||||
void WebSocketNotificationRebootList::to_json(Poco::JSON::Object &Obj) const {
|
||||
RESTAPI_utils::field_to_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_to_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_to_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_to_json(Obj,"warning",warning);
|
||||
RESTAPI_utils::field_to_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_to_json(Obj,"details",details);
|
||||
}
|
||||
|
||||
bool WebSocketNotificationRebootList::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
RESTAPI_utils::field_from_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_from_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_from_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_from_json(Obj,"warning",warning);
|
||||
RESTAPI_utils::field_from_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_from_json(Obj,"details",details);
|
||||
return true;
|
||||
} catch(...) {
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebSocketClientNotificationVenueRebootCompletionToUser( const std::string & User, WebSocketClientNotificationVenueRebootList_t &N) {
|
||||
N.type = "venue_rebooter";
|
||||
UI_WebSocketClientServer()->SendUserNotification(User,N);
|
||||
}
|
||||
|
||||
void WebSocketNotificationUpgradeList::to_json(Poco::JSON::Object &Obj) const {
|
||||
RESTAPI_utils::field_to_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_to_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_to_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_to_json(Obj,"notConnected",not_connected);
|
||||
RESTAPI_utils::field_to_json(Obj,"noFirmware",no_firmware);
|
||||
RESTAPI_utils::field_to_json(Obj,"skipped",skipped);
|
||||
RESTAPI_utils::field_to_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_to_json(Obj,"details",details);
|
||||
}
|
||||
|
||||
bool WebSocketNotificationUpgradeList::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
RESTAPI_utils::field_from_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_from_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_from_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_from_json(Obj,"notConnected",not_connected);
|
||||
RESTAPI_utils::field_from_json(Obj,"noFirmware",no_firmware);
|
||||
RESTAPI_utils::field_from_json(Obj,"skipped",skipped);
|
||||
RESTAPI_utils::field_from_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_from_json(Obj,"details",details);
|
||||
return true;
|
||||
} catch(...) {
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebSocketClientNotificationVenueUpgradeCompletionToUser( const std::string & User, WebSocketClientNotificationVenueUpgradeList_t &N) {
|
||||
N.type = "venue_upgrader";
|
||||
UI_WebSocketClientServer()->SendUserNotification(User,N);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,39 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "framework/MicroService.h"
|
||||
#include "framework/RESTAPI_utils.h"
|
||||
#include "framework/utils.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
template <typename ContentStruct> struct WebSocketNotification {
|
||||
inline static uint64_t xid=1;
|
||||
uint64_t notification_id=++xid;
|
||||
std::string type;
|
||||
ContentStruct content;
|
||||
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||
};
|
||||
|
||||
template <typename ContentStruct> void WebSocketNotification<ContentStruct>::to_json(Poco::JSON::Object &Obj) const {
|
||||
RESTAPI_utils::field_to_json(Obj,"notification_id",notification_id);
|
||||
RESTAPI_utils::field_to_json(Obj,"type",type);
|
||||
RESTAPI_utils::field_to_json(Obj,"content",content);
|
||||
}
|
||||
|
||||
template <typename ContentStruct> bool WebSocketNotification<ContentStruct>::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
RESTAPI_utils::field_from_json(Obj,"notification_id",notification_id);
|
||||
RESTAPI_utils::field_from_json(Obj,"content",content);
|
||||
RESTAPI_utils::field_from_json(Obj,"type",type);
|
||||
return true;
|
||||
} catch(...) {
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct WebNotificationSingleDevice {
|
||||
std::string serialNumber;
|
||||
inline void to_json(Poco::JSON::Object &Obj) const {
|
||||
@@ -69,36 +98,37 @@ namespace OpenWifi {
|
||||
}
|
||||
};
|
||||
|
||||
inline void WebSocketClientNotificationDeviceConfigurationChange(const std::string &SerialNumber, uint64_t oldUUID, uint64_t newUUID) {
|
||||
WebSocketNotification<WebNotificationSingleDeviceConfigurationChange> N;
|
||||
N.content.serialNumber = SerialNumber;
|
||||
N.content.oldUUID = oldUUID;
|
||||
N.content.newUUID = newUUID;
|
||||
N.type = "device_configuration_upgrade";
|
||||
WebSocketClientServer()->SendNotification(N);
|
||||
}
|
||||
struct WebSocketClientNotificationNumberOfConnection {
|
||||
std::uint64_t numberOfDevices=0;
|
||||
std::uint64_t averageConnectedTime=0;
|
||||
std::uint64_t numberOfConnectingDevices=0;
|
||||
|
||||
inline void WebSocketClientNotificationDeviceFirmwareUpdated(const std::string &SerialNumber, const std::string &Firmware) {
|
||||
WebSocketNotification<WebNotificationSingleDeviceFirmwareChange> N;
|
||||
N.content.serialNumber = SerialNumber;
|
||||
N.content.newFirmware = Firmware;
|
||||
N.type = "device_firmware_upgrade";
|
||||
WebSocketClientServer()->SendNotification(N);
|
||||
}
|
||||
inline void to_json(Poco::JSON::Object &Obj) const {
|
||||
RESTAPI_utils::field_to_json(Obj,"numberOfDevices", numberOfDevices);
|
||||
RESTAPI_utils::field_to_json(Obj,"averageConnectedTime", averageConnectedTime);
|
||||
RESTAPI_utils::field_to_json(Obj,"numberOfConnectingDevices", numberOfConnectingDevices);
|
||||
}
|
||||
|
||||
inline void WebSocketClientNotificationDeviceConnected(const std::string &SerialNumber) {
|
||||
WebSocketNotification<WebNotificationSingleDevice> N;
|
||||
N.content.serialNumber = SerialNumber;
|
||||
N.type = "device_connection";
|
||||
WebSocketClientServer()->SendNotification(N);
|
||||
}
|
||||
inline bool from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
RESTAPI_utils::field_from_json(Obj,"numberOfDevices", numberOfDevices);
|
||||
RESTAPI_utils::field_from_json(Obj,"averageConnectedTime", averageConnectedTime);
|
||||
RESTAPI_utils::field_from_json(Obj,"numberOfConnectingDevices", numberOfConnectingDevices);
|
||||
return true;
|
||||
} catch (...) {
|
||||
|
||||
inline void WebSocketClientNotificationDeviceDisconnected(const std::string & SerialNumber) {
|
||||
WebSocketNotification<WebNotificationSingleDevice> N;
|
||||
N.content.serialNumber = SerialNumber;
|
||||
N.type = "device_disconnection";
|
||||
WebSocketClientServer()->SendNotification(N);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void WebSocketClientNotificationNumberOfConnections(std::uint64_t numberOfDevices,
|
||||
std::uint64_t averageConnectedTime,
|
||||
std::uint64_t numberOfConnectingDevices);
|
||||
void WebSocketClientNotificationDeviceConfigurationChange(const std::string &SerialNumber, uint64_t oldUUID, uint64_t newUUID);
|
||||
void WebSocketClientNotificationDeviceFirmwareUpdated(const std::string &SerialNumber, const std::string &Firmware);
|
||||
void WebSocketClientNotificationDeviceConnected(const std::string &SerialNumber);
|
||||
void WebSocketClientNotificationDeviceDisconnected(const std::string & SerialNumber);
|
||||
|
||||
struct WebSocketNotificationJobContent {
|
||||
std::string title,
|
||||
@@ -107,44 +137,15 @@ namespace OpenWifi {
|
||||
std::vector<std::string> success,
|
||||
error,
|
||||
warning;
|
||||
uint64_t timeStamp=OpenWifi::Now();
|
||||
uint64_t timeStamp=OpenWifi::Utils::Now();
|
||||
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||
};
|
||||
|
||||
inline void WebSocketNotificationJobContent::to_json(Poco::JSON::Object &Obj) const {
|
||||
RESTAPI_utils::field_to_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_to_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_to_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_to_json(Obj,"error",error);
|
||||
RESTAPI_utils::field_to_json(Obj,"warning",warning);
|
||||
RESTAPI_utils::field_to_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_to_json(Obj,"details",details);
|
||||
}
|
||||
|
||||
inline bool WebSocketNotificationJobContent::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
RESTAPI_utils::field_from_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_from_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_from_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_from_json(Obj,"error",error);
|
||||
RESTAPI_utils::field_from_json(Obj,"warning",warning);
|
||||
RESTAPI_utils::field_from_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_from_json(Obj,"details",details);
|
||||
return true;
|
||||
} catch(...) {
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef WebSocketNotification<WebSocketNotificationJobContent> WebSocketClientNotificationVenueUpdateJob_t;
|
||||
|
||||
inline void WebSocketClientNotificationVenueUpdateJobCompletionToUser( const std::string & User, WebSocketClientNotificationVenueUpdateJob_t &N) {
|
||||
N.type = "venue_configuration_update";
|
||||
WebSocketClientServer()->SendUserNotification(User,N);
|
||||
}
|
||||
void WebSocketClientNotificationVenueUpdateJobCompletionToUser( const std::string & User, WebSocketClientNotificationVenueUpdateJob_t &N);
|
||||
|
||||
/////
|
||||
/////
|
||||
@@ -156,7 +157,7 @@ namespace OpenWifi {
|
||||
jobId;
|
||||
std::vector<std::string> success,
|
||||
warning;
|
||||
uint64_t timeStamp=OpenWifi::Now();
|
||||
uint64_t timeStamp=OpenWifi::Utils::Now();
|
||||
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||
@@ -164,38 +165,7 @@ namespace OpenWifi {
|
||||
|
||||
typedef WebSocketNotification<WebSocketNotificationRebootList> WebSocketClientNotificationVenueRebootList_t;
|
||||
|
||||
inline void WebSocketNotificationRebootList::to_json(Poco::JSON::Object &Obj) const {
|
||||
RESTAPI_utils::field_to_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_to_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_to_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_to_json(Obj,"warning",warning);
|
||||
RESTAPI_utils::field_to_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_to_json(Obj,"details",details);
|
||||
}
|
||||
|
||||
inline bool WebSocketNotificationRebootList::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
RESTAPI_utils::field_from_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_from_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_from_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_from_json(Obj,"warning",warning);
|
||||
RESTAPI_utils::field_from_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_from_json(Obj,"details",details);
|
||||
return true;
|
||||
} catch(...) {
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void WebSocketClientNotificationVenueRebootCompletionToUser( const std::string & User, WebSocketClientNotificationVenueRebootList_t &N) {
|
||||
N.type = "venue_rebooter";
|
||||
WebSocketClientServer()->SendUserNotification(User,N);
|
||||
}
|
||||
|
||||
/////
|
||||
/////
|
||||
/////
|
||||
void WebSocketClientNotificationVenueRebootCompletionToUser( const std::string & User, WebSocketClientNotificationVenueRebootList_t &N);
|
||||
|
||||
struct WebSocketNotificationUpgradeList {
|
||||
std::string title,
|
||||
@@ -205,7 +175,7 @@ namespace OpenWifi {
|
||||
skipped,
|
||||
no_firmware,
|
||||
not_connected;
|
||||
uint64_t timeStamp=OpenWifi::Now();
|
||||
uint64_t timeStamp=OpenWifi::Utils::Now();
|
||||
|
||||
void to_json(Poco::JSON::Object &Obj) const;
|
||||
bool from_json(const Poco::JSON::Object::Ptr &Obj);
|
||||
@@ -213,38 +183,8 @@ namespace OpenWifi {
|
||||
|
||||
typedef WebSocketNotification<WebSocketNotificationUpgradeList> WebSocketClientNotificationVenueUpgradeList_t;
|
||||
|
||||
inline void WebSocketNotificationUpgradeList::to_json(Poco::JSON::Object &Obj) const {
|
||||
RESTAPI_utils::field_to_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_to_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_to_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_to_json(Obj,"notConnected",not_connected);
|
||||
RESTAPI_utils::field_to_json(Obj,"noFirmware",no_firmware);
|
||||
RESTAPI_utils::field_to_json(Obj,"skipped",skipped);
|
||||
RESTAPI_utils::field_to_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_to_json(Obj,"details",details);
|
||||
}
|
||||
|
||||
inline bool WebSocketNotificationUpgradeList::from_json(const Poco::JSON::Object::Ptr &Obj) {
|
||||
try {
|
||||
RESTAPI_utils::field_from_json(Obj,"title",title);
|
||||
RESTAPI_utils::field_from_json(Obj,"jobId",jobId);
|
||||
RESTAPI_utils::field_from_json(Obj,"success",success);
|
||||
RESTAPI_utils::field_from_json(Obj,"notConnected",not_connected);
|
||||
RESTAPI_utils::field_from_json(Obj,"noFirmware",no_firmware);
|
||||
RESTAPI_utils::field_from_json(Obj,"skipped",skipped);
|
||||
RESTAPI_utils::field_from_json(Obj,"timeStamp",timeStamp);
|
||||
RESTAPI_utils::field_from_json(Obj,"details",details);
|
||||
return true;
|
||||
} catch(...) {
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void WebSocketClientNotificationVenueUpgradeCompletionToUser( const std::string & User, WebSocketClientNotificationVenueUpgradeList_t &N) {
|
||||
N.type = "venue_upgrader";
|
||||
WebSocketClientServer()->SendUserNotification(User,N);
|
||||
}
|
||||
void WebSocketClientNotificationVenueUpgradeCompletionToUser( const std::string & User, WebSocketClientNotificationVenueUpgradeList_t &N);
|
||||
|
||||
} // namespace OpenWifi
|
||||
|
||||
|
||||
89
src/framework/WebSocketLogger.h
Normal file
89
src/framework/WebSocketLogger.h
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "framework/SubSystemServer.h"
|
||||
|
||||
namespace OpenWifi {
|
||||
|
||||
class WebSocketLogger : public Poco::Channel {
|
||||
public:
|
||||
|
||||
inline std::string getProperty( [[maybe_unused]] const std::string &p ) const final {
|
||||
return "";
|
||||
}
|
||||
|
||||
inline void close() final {
|
||||
}
|
||||
|
||||
inline void open() final {
|
||||
}
|
||||
|
||||
inline static std::string to_string(Poco::Message::Priority p) {
|
||||
switch(p) {
|
||||
case Poco::Message::PRIO_INFORMATION: return "information";
|
||||
case Poco::Message::PRIO_CRITICAL: return "critical";
|
||||
case Poco::Message::PRIO_DEBUG: return "debug";
|
||||
case Poco::Message::PRIO_ERROR: return "error";
|
||||
case Poco::Message::PRIO_FATAL: return "level";
|
||||
case Poco::Message::PRIO_NOTICE: return "notice";
|
||||
case Poco::Message::PRIO_TRACE: return "trace";
|
||||
case Poco::Message::PRIO_WARNING: return "warning";
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
|
||||
inline void log(const Poco::Message &m) final {
|
||||
if(Enabled_) {
|
||||
/*
|
||||
nlohmann::json log_msg;
|
||||
log_msg["msg"] = m.getText();
|
||||
log_msg["level"] = to_string(m.getPriority());
|
||||
log_msg["timestamp"] = Poco::DateTimeFormatter::format(m.getTime(), Poco::DateTimeFormat::ISO8601_FORMAT);
|
||||
log_msg["source"] = m.getSource();
|
||||
log_msg["thread_name"] = m.getThread();
|
||||
log_msg["thread_id"] = m.getTid();
|
||||
|
||||
std::cout << log_msg << std::endl;
|
||||
*/
|
||||
std::lock_guard G(Mutex_);
|
||||
std::vector<uint64_t> Remove;
|
||||
for(const auto &[Id,CallBack]:CallBacks_) {
|
||||
try {
|
||||
CallBack(m);
|
||||
} catch (...) {
|
||||
Remove.push_back(Id);
|
||||
}
|
||||
}
|
||||
for(const auto &i:Remove)
|
||||
CallBacks_.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
inline void setProperty([[maybe_unused]] const std::string &name, [[maybe_unused]] const std::string &value) final {
|
||||
|
||||
}
|
||||
|
||||
inline static auto instance() {
|
||||
static auto instance_ = new WebSocketLogger;
|
||||
return instance_;
|
||||
}
|
||||
inline void Enable(bool enable) { Enabled_ = enable; }
|
||||
typedef std::function<void(const Poco::Message &M)> logmuxer_callback_func_t;
|
||||
inline void RegisterCallback(const logmuxer_callback_func_t & R, uint64_t &Id) {
|
||||
std::lock_guard G(Mutex_);
|
||||
Id = CallBackId_++;
|
||||
CallBacks_[Id] = R;
|
||||
}
|
||||
private:
|
||||
std::recursive_mutex Mutex_;
|
||||
std::map<uint64_t,logmuxer_callback_func_t> CallBacks_;
|
||||
inline static uint64_t CallBackId_=1;
|
||||
bool Enabled_ = false;
|
||||
};
|
||||
|
||||
inline auto WebSocketLogger() { return WebSocketLogger::instance(); }
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
#include "Poco/String.h"
|
||||
|
||||
#if defined(__GNUC__)
|
||||
@@ -18,6 +19,26 @@
|
||||
#pragma clang diagnostic ignored "-Wunused-variable"
|
||||
#endif
|
||||
|
||||
namespace OpenWifi {
|
||||
enum UNAUTHORIZED_REASON {
|
||||
SUCCESS=0,
|
||||
PASSWORD_CHANGE_REQUIRED,
|
||||
INVALID_CREDENTIALS,
|
||||
PASSWORD_ALREADY_USED,
|
||||
USERNAME_PENDING_VERIFICATION,
|
||||
PASSWORD_INVALID,
|
||||
INTERNAL_ERROR,
|
||||
ACCESS_DENIED,
|
||||
INVALID_TOKEN,
|
||||
EXPIRED_TOKEN,
|
||||
RATE_LIMIT_EXCEEDED,
|
||||
BAD_MFA_TRANSACTION,
|
||||
MFA_FAILURE,
|
||||
SECURITY_SERVICE_UNREACHABLE,
|
||||
CANNOT_REFRESH_TOKEN
|
||||
};
|
||||
}
|
||||
|
||||
namespace OpenWifi::RESTAPI::Errors {
|
||||
struct msg { uint64_t err_num; std::string err_txt; };
|
||||
static const struct msg Error404{404,"Resource does not exist."};
|
||||
|
||||
523
src/framework/utils.cpp
Normal file
523
src/framework/utils.cpp
Normal file
@@ -0,0 +1,523 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#include "Poco/Path.h"
|
||||
|
||||
#include "framework/utils.h"
|
||||
#include "framework/AppServiceRegistry.h"
|
||||
|
||||
namespace OpenWifi::Utils {
|
||||
|
||||
bool NormalizeMac(std::string & Mac) {
|
||||
Poco::replaceInPlace(Mac,":","");
|
||||
Poco::replaceInPlace(Mac,"-","");
|
||||
if(Mac.size()!=12)
|
||||
return false;
|
||||
for(const auto &i:Mac) {
|
||||
if(!std::isxdigit(i))
|
||||
return false;
|
||||
}
|
||||
Poco::toLowerInPlace(Mac);
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ValidSerialNumber(const std::string &Serial) {
|
||||
return ((Serial.size() < uCentralProtocol::SERIAL_NUMBER_LENGTH) &&
|
||||
std::all_of(Serial.begin(),Serial.end(),[](auto i){return std::isxdigit(i);}));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ValidUUID(const std::string &UUID) {
|
||||
if(UUID.size()>36)
|
||||
return false;
|
||||
uint dashes=0;
|
||||
return (std::all_of(UUID.begin(),UUID.end(),[&](auto i){ if(i=='-') dashes++; return i=='-' || std::isxdigit(i);})) && (dashes>0);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<std::string> Split(const std::string &List, char Delimiter ) {
|
||||
std::vector<std::string> ReturnList;
|
||||
|
||||
unsigned long P=0;
|
||||
|
||||
while(P<List.size())
|
||||
{
|
||||
unsigned long P2 = List.find_first_of(Delimiter, P);
|
||||
if(P2==std::string::npos) {
|
||||
ReturnList.push_back(List.substr(P));
|
||||
break;
|
||||
}
|
||||
else
|
||||
ReturnList.push_back(List.substr(P,P2-P));
|
||||
P=P2+1;
|
||||
}
|
||||
return ReturnList;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] std::string FormatIPv6(const std::string & I )
|
||||
{
|
||||
if(I.substr(0,8) == "[::ffff:")
|
||||
{
|
||||
unsigned long PClosingBracket = I.find_first_of(']');
|
||||
|
||||
std::string ip = I.substr(8, PClosingBracket-8);
|
||||
std::string port = I.substr(PClosingBracket+1);
|
||||
return ip + port;
|
||||
}
|
||||
|
||||
return I;
|
||||
}
|
||||
|
||||
void padTo(std::string& str, size_t num, char paddingChar) {
|
||||
str.append(num - str.length() % num, paddingChar);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string SerialToMAC(const std::string &Serial) {
|
||||
std::string R = Serial;
|
||||
|
||||
if(R.size()<12)
|
||||
padTo(R,12,'0');
|
||||
else if (R.size()>12)
|
||||
R = R.substr(0,12);
|
||||
|
||||
char buf[18];
|
||||
|
||||
buf[0] = R[0]; buf[1] = R[1] ; buf[2] = ':' ;
|
||||
buf[3] = R[2] ; buf[4] = R[3]; buf[5] = ':' ;
|
||||
buf[6] = R[4]; buf[7] = R[5] ; buf[8] = ':' ;
|
||||
buf[9] = R[6] ; buf[10]= R[7]; buf[11] = ':';
|
||||
buf[12] = R[8] ; buf[13]= R[9]; buf[14] = ':';
|
||||
buf[15] = R[10] ; buf[16]= R[11];buf[17] = 0;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
uint64_t MACToInt(const std::string &MAC) {
|
||||
uint64_t Result = 0 ;
|
||||
for(const auto &c:MAC) {
|
||||
if(c==':')
|
||||
continue;
|
||||
Result <<= 4;
|
||||
if(c>='0' && c<='9') {
|
||||
Result += (c - '0');
|
||||
} else if (c>='a' && c<='f') {
|
||||
Result += (c-'a'+10);
|
||||
} else if (c>='A' && c<='F') {
|
||||
Result += (c-'A'+10);
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string ToHex(const std::vector<unsigned char> & B) {
|
||||
std::string R;
|
||||
R.reserve(B.size()*2);
|
||||
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
|
||||
for(const auto &i:B)
|
||||
{
|
||||
R += (hex[ (i & 0xf0) >> 4]);
|
||||
R += (hex[ (i & 0x0f) ]);
|
||||
}
|
||||
|
||||
return R;
|
||||
}
|
||||
|
||||
inline static const char kEncodeLookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
inline static const char kPadCharacter = '=';
|
||||
|
||||
using byte = std::uint8_t;
|
||||
|
||||
[[nodiscard]] std::string base64encode(const byte *input, uint32_t size) {
|
||||
std::string encoded;
|
||||
encoded.reserve(((size / 3) + (size % 3 > 0)) * 4);
|
||||
|
||||
std::uint32_t temp,i,ee;
|
||||
ee = (size/3);
|
||||
|
||||
for (i = 0; i < 3*ee; ++i) {
|
||||
temp = input[i++] << 16;
|
||||
temp += input[i++] << 8;
|
||||
temp += input[i];
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x0000003F)]);
|
||||
}
|
||||
|
||||
switch (size % 3) {
|
||||
case 1:
|
||||
temp = input[i] << 16;
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
|
||||
encoded.append(2, kPadCharacter);
|
||||
break;
|
||||
case 2:
|
||||
temp = input[i++] << 16;
|
||||
temp += input[i] << 8;
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
|
||||
encoded.append(1, kPadCharacter);
|
||||
break;
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<byte> base64decode(const std::string& input)
|
||||
{
|
||||
if(input.length() % 4)
|
||||
throw std::runtime_error("Invalid base64 length!");
|
||||
|
||||
std::size_t padding=0;
|
||||
|
||||
if(input.length())
|
||||
{
|
||||
if(input[input.length() - 1] == kPadCharacter) padding++;
|
||||
if(input[input.length() - 2] == kPadCharacter) padding++;
|
||||
}
|
||||
|
||||
std::vector<byte> decoded;
|
||||
decoded.reserve(((input.length() / 4) * 3) - padding);
|
||||
|
||||
std::uint32_t temp=0;
|
||||
auto it = input.begin();
|
||||
|
||||
while(it < input.end())
|
||||
{
|
||||
for(std::size_t i = 0; i < 4; ++i)
|
||||
{
|
||||
temp <<= 6;
|
||||
if (*it >= 0x41 && *it <= 0x5A) temp |= *it - 0x41;
|
||||
else if(*it >= 0x61 && *it <= 0x7A) temp |= *it - 0x47;
|
||||
else if(*it >= 0x30 && *it <= 0x39) temp |= *it + 0x04;
|
||||
else if(*it == 0x2B) temp |= 0x3E;
|
||||
else if(*it == 0x2F) temp |= 0x3F;
|
||||
else if(*it == kPadCharacter)
|
||||
{
|
||||
switch(input.end() - it)
|
||||
{
|
||||
case 1:
|
||||
decoded.push_back((temp >> 16) & 0x000000FF);
|
||||
decoded.push_back((temp >> 8 ) & 0x000000FF);
|
||||
return decoded;
|
||||
case 2:
|
||||
decoded.push_back((temp >> 10) & 0x000000FF);
|
||||
return decoded;
|
||||
default:
|
||||
throw std::runtime_error("Invalid padding in base64!");
|
||||
}
|
||||
}
|
||||
else throw std::runtime_error("Invalid character in base64!");
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
decoded.push_back((temp >> 16) & 0x000000FF);
|
||||
decoded.push_back((temp >> 8 ) & 0x000000FF);
|
||||
decoded.push_back((temp ) & 0x000000FF);
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
bool ParseTime(const std::string &Time, int & Hours, int & Minutes, int & Seconds) {
|
||||
Poco::StringTokenizer TimeTokens(Time,":",Poco::StringTokenizer::TOK_TRIM);
|
||||
|
||||
Hours = Minutes = Seconds = 0 ;
|
||||
if(TimeTokens.count()==1) {
|
||||
Hours = std::atoi(TimeTokens[0].c_str());
|
||||
} else if(TimeTokens.count()==2) {
|
||||
Hours = std::atoi(TimeTokens[0].c_str());
|
||||
Minutes = std::atoi(TimeTokens[1].c_str());
|
||||
} else if(TimeTokens.count()==3) {
|
||||
Hours = std::atoi(TimeTokens[0].c_str());
|
||||
Minutes = std::atoi(TimeTokens[1].c_str());
|
||||
Seconds = std::atoi(TimeTokens[2].c_str());
|
||||
} else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool ParseDate(const std::string &Time, int & Year, int & Month, int & Day) {
|
||||
Poco::StringTokenizer DateTokens(Time,"-",Poco::StringTokenizer::TOK_TRIM);
|
||||
|
||||
Year = Month = Day = 0 ;
|
||||
if(DateTokens.count()==3) {
|
||||
Year = std::atoi(DateTokens[0].c_str());
|
||||
Month = std::atoi(DateTokens[1].c_str());
|
||||
Day = std::atoi(DateTokens[2].c_str());
|
||||
} else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompareTime( int H1, int H2, int M1, int M2, int S1, int S2) {
|
||||
if(H1<H2)
|
||||
return true;
|
||||
if(H1>H2)
|
||||
return false;
|
||||
if(M1<M2)
|
||||
return true;
|
||||
if(M2>M1)
|
||||
return false;
|
||||
if(S1<=S2)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string LogLevelToString(int Level) {
|
||||
switch(Level) {
|
||||
case Poco::Message::PRIO_DEBUG: return "debug";
|
||||
case Poco::Message::PRIO_INFORMATION: return "information";
|
||||
case Poco::Message::PRIO_FATAL: return "fatal";
|
||||
case Poco::Message::PRIO_WARNING: return "warning";
|
||||
case Poco::Message::PRIO_NOTICE: return "notice";
|
||||
case Poco::Message::PRIO_CRITICAL: return "critical";
|
||||
case Poco::Message::PRIO_ERROR: return "error";
|
||||
case Poco::Message::PRIO_TRACE: return "trace";
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t SerialNumberToInt(const std::string & S) {
|
||||
return std::stoull(S,nullptr,16);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string IntToSerialNumber(uint64_t S) {
|
||||
char b[16];
|
||||
for(int i=0;i<12;++i) {
|
||||
int B = (S & 0x0f);
|
||||
if(B<10)
|
||||
b[11-i] = B+'0';
|
||||
else
|
||||
b[11-i] = B - 10 + 'a';
|
||||
S >>= 4 ;
|
||||
}
|
||||
b[12]=0;
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] bool SerialNumberMatch(const std::string &S1, const std::string &S2, int Bits) {
|
||||
auto S1_i = SerialNumberToInt(S1);
|
||||
auto S2_i = SerialNumberToInt(S2);
|
||||
return ((S1_i>>Bits)==(S2_i>>Bits));
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t SerialNumberToOUI(const std::string & S) {
|
||||
uint64_t Result = 0 ;
|
||||
int Digits=0;
|
||||
|
||||
for(const auto &i:S) {
|
||||
if(std::isxdigit(i)) {
|
||||
if(i>='0' && i<='9') {
|
||||
Result <<=4;
|
||||
Result += i-'0';
|
||||
} else if(i>='A' && i<='F') {
|
||||
Result <<=4;
|
||||
Result += i-'A'+10;
|
||||
} else if(i>='a' && i<='f') {
|
||||
Result <<=4;
|
||||
Result += i-'a'+10;
|
||||
}
|
||||
Digits++;
|
||||
if(Digits==6)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t GetDefaultMacAsInt64() {
|
||||
uint64_t Result=0;
|
||||
auto IFaceList = Poco::Net::NetworkInterface::list();
|
||||
|
||||
for(const auto &iface:IFaceList) {
|
||||
if(iface.isRunning() && !iface.isLoopback()) {
|
||||
auto MAC = iface.macAddress();
|
||||
for (auto const &i : MAC) {
|
||||
Result <<= 8;
|
||||
Result += (uint8_t)i;
|
||||
}
|
||||
if (Result != 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t InitializeSystemId() {
|
||||
std::random_device RDev;
|
||||
std::srand(RDev());
|
||||
std::chrono::high_resolution_clock Clock;
|
||||
auto Now = Clock.now().time_since_epoch().count();
|
||||
auto S = (GetDefaultMacAsInt64() + std::rand() + Now) ;
|
||||
OpenWifi::AppServiceRegistry().Set("systemid",S);
|
||||
return S;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t GetSystemId() {
|
||||
uint64_t ID=0;
|
||||
if(!AppServiceRegistry().Get("systemid",ID)) {
|
||||
return InitializeSystemId();
|
||||
}
|
||||
return ID;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ValidEMailAddress(const std::string &email) {
|
||||
// define a regular expression
|
||||
static const std::regex pattern
|
||||
("[_a-z0-9-]+(\\.[_a-z0-9-]+)*(\\+[a-z0-9-]+)?@[a-z0-9-]+(\\.[a-z0-9-]+)*");
|
||||
// try to match the string with the regular expression
|
||||
return std::regex_match(email, pattern);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string LoadFile( const Poco::File & F) {
|
||||
std::string Result;
|
||||
try {
|
||||
std::ostringstream OS;
|
||||
std::ifstream IF(F.path());
|
||||
Poco::StreamCopier::copyStream(IF, OS);
|
||||
Result = OS.str();
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
void ReplaceVariables( std::string & Content , const Types::StringPairVec & P) {
|
||||
for(const auto &[Variable,Value]:P) {
|
||||
Poco::replaceInPlace(Content,"${" + Variable + "}", Value);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] MediaTypeEncoding FindMediaType(const Poco::File &F) {
|
||||
const auto E = Poco::Path(F.path()).getExtension();
|
||||
if(E=="png")
|
||||
return MediaTypeEncoding{ .Encoding = BINARY,
|
||||
.ContentType = "image/png" };
|
||||
if(E=="gif")
|
||||
return MediaTypeEncoding{ .Encoding = BINARY,
|
||||
.ContentType = "image/gif" };
|
||||
if(E=="jpeg" || E=="jpg")
|
||||
return MediaTypeEncoding{ .Encoding = BINARY,
|
||||
.ContentType = "image/jpeg" };
|
||||
if(E=="svg" || E=="svgz")
|
||||
return MediaTypeEncoding{ .Encoding = PLAIN,
|
||||
.ContentType = "image/svg+xml" };
|
||||
if(E=="html")
|
||||
return MediaTypeEncoding{ .Encoding = PLAIN,
|
||||
.ContentType = "text/html" };
|
||||
if(E=="css")
|
||||
return MediaTypeEncoding{ .Encoding = PLAIN,
|
||||
.ContentType = "text/css" };
|
||||
if(E=="js")
|
||||
return MediaTypeEncoding{ .Encoding = PLAIN,
|
||||
.ContentType = "application/javascript" };
|
||||
return MediaTypeEncoding{ .Encoding = BINARY,
|
||||
.ContentType = "application/octet-stream" };
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string BinaryFileToHexString(const Poco::File &F) {
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
std::string Result;
|
||||
try {
|
||||
std::ifstream IF(F.path());
|
||||
|
||||
int Count = 0;
|
||||
while (IF.good()) {
|
||||
if (Count)
|
||||
Result += ", ";
|
||||
if ((Count % 32) == 0)
|
||||
Result += "\r\n";
|
||||
Count++;
|
||||
unsigned char C = IF.get();
|
||||
Result += "0x";
|
||||
Result += (char) (hex[(C & 0xf0) >> 4]);
|
||||
Result += (char) (hex[(C & 0x0f)]);
|
||||
}
|
||||
} catch(...) {
|
||||
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string SecondsToNiceText(uint64_t Seconds) {
|
||||
std::string Result;
|
||||
int Days = Seconds / (24*60*60);
|
||||
Seconds -= Days * (24*60*60);
|
||||
int Hours= Seconds / (60*60);
|
||||
Seconds -= Hours * (60*60);
|
||||
int Minutes = Seconds / 60;
|
||||
Seconds -= Minutes * 60;
|
||||
Result = std::to_string(Days) +" days, " + std::to_string(Hours) + ":" + std::to_string(Minutes) + ":" + std::to_string(Seconds);
|
||||
return Result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool wgets(const std::string &URL, std::string &Response) {
|
||||
try {
|
||||
Poco::URI uri(URL);
|
||||
Poco::Net::HTTPSClientSession session(uri.getHost(), uri.getPort());
|
||||
|
||||
// prepare path
|
||||
std::string path(uri.getPathAndQuery());
|
||||
if (path.empty()) {
|
||||
path = "/";
|
||||
}
|
||||
|
||||
// send request
|
||||
Poco::Net::HTTPRequest req(Poco::Net::HTTPRequest::HTTP_GET, path, Poco::Net::HTTPMessage::HTTP_1_1);
|
||||
session.sendRequest(req);
|
||||
|
||||
Poco::Net::HTTPResponse res;
|
||||
std::istream &is = session.receiveResponse(res);
|
||||
std::ostringstream os;
|
||||
|
||||
Poco::StreamCopier::copyStream(is,os);
|
||||
Response = os.str();
|
||||
|
||||
return true;
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExtractBase64CompressedData(const std::string &CompressedData,
|
||||
std::string &UnCompressedData, uint64_t compress_sz ) {
|
||||
std::istringstream ifs(CompressedData);
|
||||
Poco::Base64Decoder b64in(ifs);
|
||||
std::ostringstream ofs;
|
||||
Poco::StreamCopier::copyStream(b64in, ofs);
|
||||
|
||||
int factor = 20;
|
||||
unsigned long MaxSize = compress_sz ? (unsigned long) (compress_sz + 5000) : (unsigned long) (ofs.str().size() * factor);
|
||||
while(true) {
|
||||
std::vector<uint8_t> UncompressedBuffer(MaxSize);
|
||||
unsigned long FinalSize = MaxSize;
|
||||
auto status = uncompress((uint8_t *)&UncompressedBuffer[0], &FinalSize,
|
||||
(uint8_t *)ofs.str().c_str(), ofs.str().size());
|
||||
if(status==Z_OK) {
|
||||
UncompressedBuffer[FinalSize] = 0;
|
||||
UnCompressedData = (char *)&UncompressedBuffer[0];
|
||||
return true;
|
||||
}
|
||||
if(status==Z_BUF_ERROR) {
|
||||
if(factor<300) {
|
||||
factor+=10;
|
||||
MaxSize = ofs.str().size() * factor;
|
||||
continue;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
129
src/framework/utils.h
Normal file
129
src/framework/utils.h
Normal file
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// Created by stephane bourque on 2022-10-25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <thread>
|
||||
|
||||
#include "Poco/Thread.h"
|
||||
#include "Poco/StringTokenizer.h"
|
||||
#include "Poco/String.h"
|
||||
#include "Poco/SHA2Engine.h"
|
||||
#include "Poco/Message.h"
|
||||
#include "Poco/StreamCopier.h"
|
||||
#include "Poco/File.h"
|
||||
#include "Poco/Net/NetworkInterface.h"
|
||||
#include "Poco/URI.h"
|
||||
#include "Poco/Net/HTTPRequest.h"
|
||||
#include "Poco/Net/HTTPClientSession.h"
|
||||
#include "Poco/Net/HTTPSClientSession.h"
|
||||
#include "Poco/Net/HTTPResponse.h"
|
||||
#include "Poco/Base64Decoder.h"
|
||||
#include "Poco/Base64Encoder.h"
|
||||
#include "Poco/zlib.h"
|
||||
|
||||
#include "framework/ow_constants.h"
|
||||
#include "framework/OpenWifiTypes.h"
|
||||
|
||||
namespace OpenWifi::Utils {
|
||||
|
||||
inline uint64_t Now() { return std::time(nullptr); };
|
||||
|
||||
bool NormalizeMac(std::string & Mac);
|
||||
|
||||
inline void SetThreadName(const char *name) {
|
||||
#ifdef __linux__
|
||||
Poco::Thread::current()->setName(name);
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
Poco::Thread::current()->setName(name);
|
||||
pthread_setname_np(name);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void SetThreadName(Poco::Thread &thr, const char *name) {
|
||||
#ifdef __linux__
|
||||
thr.setName(name);
|
||||
pthread_setname_np(thr.tid(), name);
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
thr.setName(name);
|
||||
#endif
|
||||
}
|
||||
|
||||
enum MediaTypeEncodings {
|
||||
PLAIN,
|
||||
BINARY,
|
||||
BASE64
|
||||
};
|
||||
|
||||
struct MediaTypeEncoding {
|
||||
MediaTypeEncodings Encoding=PLAIN;
|
||||
std::string ContentType;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool ValidSerialNumber(const std::string &Serial);
|
||||
[[nodiscard]] bool ValidUUID(const std::string &UUID);
|
||||
|
||||
template <typename ...Args> std::string ComputeHash(Args&&... args) {
|
||||
Poco::SHA2Engine E;
|
||||
auto as_string = [](auto p) {
|
||||
if constexpr(std::is_arithmetic_v<decltype(p)>) {
|
||||
return std::to_string(p);
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
};
|
||||
(E.update(as_string(args)),...);
|
||||
return Poco::SHA2Engine::digestToHex(E.digest());
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<std::string> Split(const std::string &List, char Delimiter=',' );
|
||||
[[nodiscard]] std::string FormatIPv6(const std::string & I );
|
||||
void padTo(std::string& str, size_t num, char paddingChar = '\0');
|
||||
[[nodiscard]] std::string SerialToMAC(const std::string &Serial);
|
||||
uint64_t MACToInt(const std::string &MAC);
|
||||
[[nodiscard]] std::string ToHex(const std::vector<unsigned char> & B);
|
||||
|
||||
using byte = std::uint8_t;
|
||||
|
||||
[[nodiscard]] std::string base64encode(const byte *input, uint32_t size);
|
||||
[[nodiscard]] std::vector<byte> base64decode(const std::string& input);;
|
||||
bool ParseTime(const std::string &Time, int & Hours, int & Minutes, int & Seconds);
|
||||
bool ParseDate(const std::string &Time, int & Year, int & Month, int & Day);
|
||||
bool CompareTime( int H1, int H2, int M1, int M2, int S1, int S2);
|
||||
[[nodiscard]] std::string LogLevelToString(int Level);
|
||||
[[nodiscard]] uint64_t SerialNumberToInt(const std::string & S);
|
||||
[[nodiscard]] std::string IntToSerialNumber(uint64_t S);
|
||||
[[nodiscard]] bool SerialNumberMatch(const std::string &S1, const std::string &S2, int Bits=2);
|
||||
[[nodiscard]] uint64_t SerialNumberToOUI(const std::string & S);
|
||||
[[nodiscard]] uint64_t GetDefaultMacAsInt64();
|
||||
[[nodiscard]] uint64_t InitializeSystemId();
|
||||
[[nodiscard]] uint64_t GetSystemId();
|
||||
[[nodiscard]] bool ValidEMailAddress(const std::string &email);
|
||||
[[nodiscard]] std::string LoadFile( const Poco::File & F);
|
||||
void ReplaceVariables( std::string & Content , const Types::StringPairVec & P);
|
||||
[[nodiscard]] MediaTypeEncoding FindMediaType(const Poco::File &F);
|
||||
[[nodiscard]] std::string BinaryFileToHexString(const Poco::File &F);
|
||||
[[nodiscard]] std::string SecondsToNiceText(uint64_t Seconds);
|
||||
[[nodiscard]] bool wgets(const std::string &URL, std::string &Response);
|
||||
|
||||
template< typename T >
|
||||
std::string int_to_hex( T i )
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << std::setfill ('0') << std::setw(12)
|
||||
<< std::hex << i;
|
||||
return stream.str();
|
||||
}
|
||||
bool ExtractBase64CompressedData(const std::string &CompressedData,
|
||||
std::string &UnCompressedData, uint64_t compress_sz );
|
||||
}
|
||||
Reference in New Issue
Block a user