diff --git a/.gitignore b/.gitignore index 80f55ec1..bba923a6 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,4 @@ helm/charts/* !helm/charts/.gitkeep /portal-test/ /src/ow_version.h - +.vscode/* diff --git a/CMakeLists.txt b/CMakeLists.txt index d9aff94d..3ecbc140 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,7 @@ add_executable( owgw src/storage/storage_blacklist.cpp src/storage/storage_tables.cpp src/storage/storage_logs.cpp src/storage/storage_command.cpp src/storage/storage_healthcheck.cpp src/storage/storage_statistics.cpp src/storage/storage_device.cpp src/storage/storage_capabilities.cpp src/storage/storage_defconfig.cpp + src/storage/storage_packages.cpp src/storage/storage_scripts.cpp src/storage/storage_scripts.h src/storage/storage_tables.cpp src/RESTAPI/RESTAPI_routers.cpp diff --git a/openapi/owgw.yaml b/openapi/owgw.yaml index 2ff02dae..b8622ac0 100644 --- a/openapi/owgw.yaml +++ b/openapi/owgw.yaml @@ -1600,6 +1600,73 @@ components: maximum: 60000 description: off time in milliseconds + PackageGetResponse: + type: object + properties: + packages: + type: array + items: + type: object + properties: + name: + type: string + version: + type: string + serialNumber: + type: string + + PackageInstallRequest: + type: object + properties: + serialNumber: + type: string + packages: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + + PackageInstallResponse: + type: object + properties: + serial: + type: string + status: + type: object + properties: + error: + type: number + packages: + type: array + items: + type: object + properties: + name: + type: string + result: + type: string + text: + type: string + uuid: + type: number + + PackageRemoveRequest: + type: object + properties: + serialNumber: + type: string + packages: + type: array + items: + type: object + properties: + name: + type: string + paths: /devices: get: @@ -3084,6 +3151,92 @@ paths: 404: $ref: '#/components/responses/NotFound' + /device/{serialNumber}/package: + get: + tags: + - Commands + summary: Get package installed on the remote device. + operationId: getDevicePackages + parameters: + - in: path + name: serialNumber + schema: + type: string + required: true + responses: + 200: + description: Successful command execution + content: + application/json: + schema: + $ref: '#/components/schemas/PackageGetResponse' + 403: + $ref: '#/components/responses/Unauthorized' + 404: + $ref: '#/components/responses/NotFound' + + post: + tags: + - Commands + summary: Install IPK files to remote device. + operationId: postDevicePackages + parameters: + - in: path + name: serialNumber + schema: + type: string + required: true + requestBody: + description: Packages to be installed + content: + application/json: + schema: + $ref: '#/components/schemas/PackageInstallRequest' + responses: + 200: + description: Successful command execution + content: + application/json: + schema: + $ref: '#/components/schemas/PackageInstallResponse' + + 400: + $ref: '#/components/responses/BadRequest' + 403: + $ref: '#/components/responses/Unauthorized' + 404: + $ref: '#/components/responses/NotFound' + + delete: + tags: + - Commands + summary: Remove install packages from remote device. + operationId: deleteDevicePackages + parameters: + - in: path + name: serialNumber + schema: + type: string + required: true + requestBody: + description: Packages to be removed + content: + application/json: + schema: + $ref: '#/components/schemas/PackageRemoveRequest' + responses: + 200: + content: + application/json: + schema: + $ref: '#/components/schemas/PackageInstallResponse' + 400: + $ref: '#/components/responses/BadRequest' + 403: + $ref: '#/components/responses/Unauthorized' + 404: + $ref: '#/components/responses/NotFound' + /ouis: get: tags: diff --git a/src/AP_WS_Connection.h b/src/AP_WS_Connection.h index 4acd5657..edb9907d 100644 --- a/src/AP_WS_Connection.h +++ b/src/AP_WS_Connection.h @@ -145,6 +145,7 @@ namespace OpenWifi { std::uint64_t uuid_=0; bool Simulated_=false; std::atomic_uint64_t LastContact_=0; + GWObjects::PackageList DevicePackages_; static inline std::atomic_uint64_t ConcurrentStartingDevices_ = 0; @@ -168,6 +169,9 @@ namespace OpenWifi { void Process_wifiscan(Poco::JSON::Object::Ptr ParamsObj); void Process_alarm(Poco::JSON::Object::Ptr ParamsObj); void Process_rebootLog(Poco::JSON::Object::Ptr ParamsObj); + void Process_packagelist(Poco::JSON::Object::Ptr ParamsObj); + void Process_packageinstall(Poco::JSON::Object::Ptr ParamsObj); + void Process_packageremove(Poco::JSON::Object::Ptr ParamsObj); inline void SetLastHealthCheck(const GWObjects::HealthCheck &H) { RawLastHealthcheck_ = H; diff --git a/src/AP_WS_Process_connect.cpp b/src/AP_WS_Process_connect.cpp index 933e816d..75db419a 100644 --- a/src/AP_WS_Process_connect.cpp +++ b/src/AP_WS_Process_connect.cpp @@ -105,10 +105,15 @@ namespace OpenWifi { Restrictions_.developer = Capabilities->getValue("developer"); } - if(Capabilities->has("secure-rtty")) { + if (Capabilities->has("secure-rtty")) { RTTYMustBeSecure_ = Capabilities->getValue("secure-rtty"); } + if (ParamsObj->has("packages")) { + auto Packages = ParamsObj->getArray("packages"); + DevicePackages_.from_json(Packages); + } + State_.locale = FindCountryFromIP()->Get(IP); GWObjects::Device DeviceInfo; std::lock_guard DbSessionLock(DbSession_->Mutex()); @@ -149,6 +154,10 @@ namespace OpenWifi { StorageService()->CreateDefaultDevice( DbSession_->Session(), SerialNumber_, Caps, Firmware, PeerAddress_, State_.VerifiedCertificate == GWObjects::SIMULATED); + if (ParamsObj->has("packages")) { + StorageService()->CreateDeviceInstalledPackages(SerialNumber_, + DevicePackages_); + } } } else if (!Daemon()->AutoProvisioning() && !DeviceExists) { SendKafkaDeviceNotProvisioned(SerialNumber_, Firmware, Compatible_, CId_); @@ -156,6 +165,9 @@ namespace OpenWifi { return EndConnection(); } else if (DeviceExists) { StorageService()->UpdateDeviceCapabilities(DbSession_->Session(), SerialNumber_, Caps); + if (ParamsObj->has("packages")) { + StorageService()->UpdateDeviceInstalledPackages(SerialNumber_, DevicePackages_); + } int Updated{0}; if (!Firmware.empty()) { if (Firmware != DeviceInfo.Firmware) { diff --git a/src/AP_WS_Process_packagelist.cpp b/src/AP_WS_Process_packagelist.cpp new file mode 100644 index 00000000..e048d9bd --- /dev/null +++ b/src/AP_WS_Process_packagelist.cpp @@ -0,0 +1,61 @@ +// +// Created by euphokumiko on 2025-05-19. +// + +#include "AP_WS_Connection.h" +#include "StorageService.h" + +#include "fmt/format.h" +#include "framework/ow_constants.h" +#include + +namespace OpenWifi { + void AP_WS_Connection::Process_packagelist(Poco::JSON::Object::Ptr ParamsObj) { + if (!State_.Connected) { + poco_warning(Logger_, + fmt::format("INVALID-PROTOCOL({}): Device '{}' is not following protocol", + CId_, CN_)); + Errors_++; + return; + } + + poco_trace(Logger_, fmt::format("PACKAGE_LIST({}): new entry.", CId_)); + return; + } + + void AP_WS_Connection::Process_packageinstall(Poco::JSON::Object::Ptr ParamsObj) { + if (!State_.Connected) { + poco_warning(Logger_, + fmt::format("INVALID-PROTOCOL({}): Device '{}' is not following protocol", + CId_, CN_)); + Errors_++; + return; + } + + if (ParamsObj->has(uCentralProtocol::PACKAGE) && ParamsObj->has(uCentralProtocol::CATEGORY)) { + poco_trace(Logger_, fmt::format("PACKAGE_INSTALL({}): new entry.", CId_)); + + } else { + poco_warning(Logger_, fmt::format("LOG({}): Missing parameters.", CId_)); + return; + } + } + + void AP_WS_Connection::Process_packageremove(Poco::JSON::Object::Ptr ParamsObj) { + if (!State_.Connected) { + poco_warning(Logger_, + fmt::format("INVALID-PROTOCOL({}): Device '{}' is not following protocol", + CId_, CN_)); + Errors_++; + return; + } + + if (ParamsObj->has(uCentralProtocol::PACKAGE)) { + poco_trace(Logger_, fmt::format("PACKAGE_REMOVE({}): new entry.", CId_)); + + } else { + poco_warning(Logger_, fmt::format("LOG({}): Missing parameters.", CId_)); + return; + } + } +} // namespace OpenWifi \ No newline at end of file diff --git a/src/ParsePackageList.h b/src/ParsePackageList.h new file mode 100644 index 00000000..623a7e03 --- /dev/null +++ b/src/ParsePackageList.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include +#include + +#include "nlohmann/json.hpp" + +namespace OpenWiFi { + +} \ No newline at end of file diff --git a/src/RESTAPI/RESTAPI_RPC.cpp b/src/RESTAPI/RESTAPI_RPC.cpp index 7911da03..9e766254 100644 --- a/src/RESTAPI/RESTAPI_RPC.cpp +++ b/src/RESTAPI/RESTAPI_RPC.cpp @@ -155,7 +155,13 @@ namespace OpenWifi::RESTAPI_RPC { auto ScanObj = rpc_answer->get(uCentralProtocol::RESULT) .extract(); ParseWifiScan(ScanObj, ResultText, Logger); - } else { + } + // else if (Cmd.Command == uCentralProtocol::PACKAGE) { + // auto PkgObj = rpc_answer->get(uCentralProtocol::RESULT) + // .extract(); + // ParsePackageList(PkgObj, ResultText, Logger); + // } + else { Poco::JSON::Stringifier::stringify(rpc_answer->get(uCentralProtocol::RESULT), ResultText); } diff --git a/src/RESTAPI/RESTAPI_device_commandHandler.cpp b/src/RESTAPI/RESTAPI_device_commandHandler.cpp index 6d1580a0..4b3f192b 100644 --- a/src/RESTAPI/RESTAPI_device_commandHandler.cpp +++ b/src/RESTAPI/RESTAPI_device_commandHandler.cpp @@ -91,6 +91,8 @@ namespace OpenWifi { TransactionId_, UUID, RPC, Poco::Thread::current()->id())); return Rtty(UUID, RPC, 60000ms, Restrictions); }; + case APCommands::Commands::package: + return GetPackages(); default: return BadRequest(RESTAPI::Errors::InvalidCommand); } @@ -128,6 +130,21 @@ namespace OpenWifi { return DeleteChecks(); case APCommands::Commands::statistics: return DeleteStatistics(); + case APCommands::Commands::package: { + GWObjects::DeviceRestrictions Restrictions; + if (!AP_WS_Server()->Connected(SerialNumberInt_, Restrictions)) { + CallCanceled(Command_.c_str(), RESTAPI::Errors::DeviceNotConnected); + return BadRequest(RESTAPI::Errors::DeviceNotConnected); + } + auto UUID = MicroServiceCreateUUID(); + auto RPC = CommandManager()->Next_RPC_ID(); + poco_debug( + Logger_, + fmt::format( + "Command RTTY TID={} can proceed. Identified as {} and RPCID as {}. thr_id={}", + TransactionId_, UUID, RPC, Poco::Thread::current()->id())); + return DeletePackages(UUID, RPC, 300000ms, Restrictions); + } default: return BadRequest(RESTAPI::Errors::InvalidCommand); } @@ -170,7 +187,7 @@ namespace OpenWifi { {APCommands::Commands::powercycle, false, true, &RESTAPI_device_commandHandler::PowerCycle, 60000ms}, {APCommands::Commands::fixedconfig, false, true, &RESTAPI_device_commandHandler::FixedConfig, 120000ms}, {APCommands::Commands::cablediagnostics, false, true, &RESTAPI_device_commandHandler::CableDiagnostics, 120000ms}, - + {APCommands::Commands::package, false, true, &RESTAPI_device_commandHandler::PackageInstall, 120000ms}, }; void RESTAPI_device_commandHandler::DoPost() { @@ -408,6 +425,196 @@ namespace OpenWifi { BadRequest(RESTAPI::Errors::NoRecordsDeleted); } + void RESTAPI_device_commandHandler::GetPackages() { + poco_debug(Logger_, fmt::format("GET-PACKAGES({},{}): TID={} user={} serial={}. thr_id={}", + TransactionId_, Requester(), SerialNumber_, + Poco::Thread::current()->id())); + + if(IsDeviceSimulated(SerialNumber_)) { + return BadRequest(RESTAPI::Errors::SimulatedDeviceNotSupported); + } + + GWObjects::PackageList Pkgs; + StorageService()->GetDeviceInstalledPackages(SerialNumber_, Pkgs); + + Poco::JSON::Array::Ptr ArrayObj = Poco::SharedPtr(new Poco::JSON::Array); + for (const auto &i : Pkgs.packageArray) { + Poco::JSON::Object::Ptr Obj = + Poco::SharedPtr(new Poco::JSON::Object); + i.to_json(*Obj); + ArrayObj->add(Obj); + } + + Poco::JSON::Object RetObj; + RetObj.set(RESTAPI::Protocol::PACKAGES, ArrayObj); + RetObj.set(RESTAPI::Protocol::SERIALNUMBER, SerialNumber_); + return ReturnObject(RetObj); + } + + void RESTAPI_device_commandHandler::PackageInstall( + const std::string &CMD_UUID, uint64_t CMD_RPC, + [[maybe_unused]] std::chrono::milliseconds timeout, + [[maybe_unused]] const GWObjects::DeviceRestrictions &Restrictions) { + + if (UserInfo_.userinfo.userRole != SecurityObjects::ROOT && + UserInfo_.userinfo.userRole != SecurityObjects::ADMIN) { + CallCanceled("INSTALLPACKAGE", CMD_UUID, CMD_RPC, RESTAPI::Errors::ACCESS_DENIED); + return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); + } + + poco_debug(Logger_, fmt::format("INSTALL-PACKAGES({},{}): TID={} user={} serial={}", CMD_UUID, + CMD_RPC, TransactionId_, Requester(), SerialNumber_)); + + if (IsDeviceSimulated(SerialNumber_)) { + CallCanceled("INSTALL-PACKAGES", CMD_UUID, CMD_RPC, RESTAPI::Errors::SimulatedDeviceNotSupported); + return BadRequest(RESTAPI::Errors::SimulatedDeviceNotSupported); + } + + const auto &Obj = ParsedBody_; + if (!Obj->has(RESTAPI::Protocol::SERIALNUMBER)) { + return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); + } + + auto SNum = Obj->get(RESTAPI::Protocol::SERIALNUMBER).toString(); + if (SerialNumber_ != SNum) { + CallCanceled("INSTALL-PACKAGES", CMD_UUID, CMD_RPC, RESTAPI::Errors::SerialNumberMismatch); + return BadRequest(RESTAPI::Errors::SerialNumberMismatch); + } + + std::ostringstream os; + ParsedBody_->stringify(os); + + poco_information(Logger_, fmt::format("INSTALL_OBJECT: {} for device {}", os.str(), SerialNumber_)); + + GWObjects::PackageInstall PI; + if (!PI.from_json(ParsedBody_)) { + return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); + } + + Poco::JSON::Array::Ptr ArrayObj = Poco::SharedPtr(new Poco::JSON::Array); + for (const auto &i : PI.pkgs) { + Poco::JSON::Object::Ptr Obj = + Poco::SharedPtr(new Poco::JSON::Object); + i.to_json(*Obj); + ArrayObj->add(Obj); + } + + Poco::JSON::Object Params; + Params.set(uCentralProtocol::OPERATION, "install"); + Params.set(uCentralProtocol::SERIAL, SerialNumber_); + Params.set(uCentralProtocol::PACKAGES, ArrayObj); + + std::ostringstream os2; + Params.stringify(os2); + + poco_information(Logger_, fmt::format("INSTALL_OBJECT2: {} for device {}", os2.str(), SerialNumber_)); + + + std::stringstream ParamStream; + Params.stringify(ParamStream); + + GWObjects::CommandDetails Cmd; + Cmd.SerialNumber = SerialNumber_; + Cmd.UUID = CMD_UUID; + Cmd.SubmittedBy = Requester(); + Cmd.Command = uCentralProtocol::PACKAGE; + Cmd.RunAt = 0; + Cmd.Details = ParamStream.str(); + + RESTAPI_RPC::WaitForCommand(CMD_RPC, APCommands::Commands::package, false, Cmd, Params, + *Request, *Response, timeout, nullptr, nullptr, Logger_); + + Poco::JSON::Object O, P; + Cmd.to_json(O); + + Poco::Dynamic::Var resultsVar = O.get("results"); + Poco::JSON::Object::Ptr resultsObj = resultsVar.extract(); + + return ReturnObject(*resultsObj); + } + + void RESTAPI_device_commandHandler::DeletePackages( + const std::string &CMD_UUID, uint64_t CMD_RPC, + [[maybe_unused]] std::chrono::milliseconds timeout, + [[maybe_unused]] const GWObjects::DeviceRestrictions &Restrictions) { + + if (UserInfo_.userinfo.userRole != SecurityObjects::ROOT && + UserInfo_.userinfo.userRole != SecurityObjects::ADMIN) { + CallCanceled("DELETE-PACKAGES", CMD_UUID, CMD_RPC, RESTAPI::Errors::ACCESS_DENIED); + return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); + } + + poco_debug(Logger_, fmt::format("DELETE-PACKAGES({},{}): TID={} user={} serial={}", CMD_UUID, + CMD_RPC, TransactionId_, Requester(), SerialNumber_)); + + if (IsDeviceSimulated(SerialNumber_)) { + CallCanceled("DELETE-PACKAGES", CMD_UUID, CMD_RPC, RESTAPI::Errors::SimulatedDeviceNotSupported); + return BadRequest(RESTAPI::Errors::SimulatedDeviceNotSupported); + } + + const auto &Obj = ParsedBody_; + if (!Obj->has(RESTAPI::Protocol::SERIALNUMBER)) { + return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); + } + + auto SNum = Obj->get(RESTAPI::Protocol::SERIALNUMBER).toString(); + if (SerialNumber_ != SNum) { + CallCanceled("DELETE-PACKAGES", CMD_UUID, CMD_RPC, RESTAPI::Errors::SerialNumberMismatch); + return BadRequest(RESTAPI::Errors::SerialNumberMismatch); + } + + std::ostringstream os; + ParsedBody_->stringify(os); + + poco_information(Logger_, fmt::format("DELETE_OBJECT: {} for device {}", os.str(), SerialNumber_)); + + GWObjects::PackageRemove PR; + if (!PR.from_json(ParsedBody_)) { + return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); + } + + Poco::JSON::Array::Ptr ArrayObj = Poco::SharedPtr(new Poco::JSON::Array); + for (const auto &i : PR.pkgs) { + Poco::JSON::Object::Ptr Obj = + Poco::SharedPtr(new Poco::JSON::Object); + i.to_json(*Obj); + ArrayObj->add(Obj); + } + + Poco::JSON::Object Params; + Params.set(uCentralProtocol::OPERATION, "delete"); + Params.set(uCentralProtocol::SERIAL, SerialNumber_); + Params.set(uCentralProtocol::PACKAGES, ArrayObj); + + std::ostringstream os2; + Params.stringify(os2); + + poco_information(Logger_, fmt::format("DELETE_OBJECT2: {} for device {}", os2.str(), SerialNumber_)); + + + std::stringstream ParamStream; + Params.stringify(ParamStream); + + GWObjects::CommandDetails Cmd; + Cmd.SerialNumber = SerialNumber_; + Cmd.UUID = CMD_UUID; + Cmd.SubmittedBy = Requester(); + Cmd.Command = uCentralProtocol::PACKAGE; + Cmd.RunAt = 0; + Cmd.Details = ParamStream.str(); + + RESTAPI_RPC::WaitForCommand(CMD_RPC, APCommands::Commands::package, false, Cmd, Params, + *Request, *Response, timeout, nullptr, nullptr, Logger_); + + Poco::JSON::Object O, P; + Cmd.to_json(O); + + Poco::Dynamic::Var resultsVar = O.get("results"); + Poco::JSON::Object::Ptr resultsObj = resultsVar.extract(); + + return ReturnObject(*resultsObj); + } + void RESTAPI_device_commandHandler::Ping( const std::string &CMD_UUID, uint64_t CMD_RPC, std::chrono::milliseconds timeout, [[maybe_unused]] const GWObjects::DeviceRestrictions &Restrictions) { diff --git a/src/RESTAPI/RESTAPI_device_commandHandler.h b/src/RESTAPI/RESTAPI_device_commandHandler.h index 25f22d9f..9a986ff0 100644 --- a/src/RESTAPI/RESTAPI_device_commandHandler.h +++ b/src/RESTAPI/RESTAPI_device_commandHandler.h @@ -33,6 +33,9 @@ namespace OpenWifi { void GetStatus(); void GetChecks(); void DeleteChecks(); + void GetPackages(); + void DeletePackages(const std::string &UUID, uint64_t RPC, std::chrono::milliseconds timeout, + const GWObjects::DeviceRestrictions &R); bool IsDeviceSimulated(std::string &Serial); @@ -74,6 +77,8 @@ namespace OpenWifi { const GWObjects::DeviceRestrictions &R); void CableDiagnostics(const std::string &UUID, uint64_t RPC, std::chrono::milliseconds timeout, const GWObjects::DeviceRestrictions &R); + void PackageInstall(const std::string &UUID, uint64_t RPC, std::chrono::milliseconds timeout, + const GWObjects::DeviceRestrictions &R); static auto PathName() { return std::list{"/api/v1/device/{serialNumber}/{command}"}; diff --git a/src/RESTAPI/RESTAPI_packages_handler.cpp b/src/RESTAPI/RESTAPI_packages_handler.cpp new file mode 100644 index 00000000..8ed4fe68 --- /dev/null +++ b/src/RESTAPI/RESTAPI_packages_handler.cpp @@ -0,0 +1,14 @@ +// +// Created by Euphokumiko on 2025-05-22. +// Accton Corp. +// + +#include "RESTAPI_packages_handler.h" +#include "StorageService.h" +#include "framework/ow_constants.h" + +namespace OpenWifi { + void RESTAPI_packages_handler::DoGet() { + + } +} // namespace OpenWifi \ No newline at end of file diff --git a/src/RESTAPI/RESTAPI_packages_handler.h b/src/RESTAPI/RESTAPI_packages_handler.h new file mode 100644 index 00000000..3d8ce9e2 --- /dev/null +++ b/src/RESTAPI/RESTAPI_packages_handler.h @@ -0,0 +1,25 @@ +// +// Created by Euphokumiko on 2025-05-22. +// + +#pragma once + +#include "framework/RESTAPI_Handler.h" + +namespace OpenWifi { + class RESTAPI_packages_handler : public RESTAPIHandler { + public: + RESTAPI_packages_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, + RESTAPI_GenericServerAccounting &Server, + uint64_t TransactionId, bool Internal) + : RESTAPIHandler(bindings, L, + std::vector{Poco::Net::HTTPRequest::HTTP_GET, + Poco::Net::HTTPRequest::HTTP_OPTIONS}, + Server, TransactionId, Internal) {} + static auto PathName() { return std::list{"/api/v1/packages"}; } + void DoGet() final; + void DoDelete() final{}; + void DoPost() final{}; + void DoPut() final{}; + }; +} // namespace OpenWifi diff --git a/src/RESTObjects/RESTAPI_GWobjects.cpp b/src/RESTObjects/RESTAPI_GWobjects.cpp index ead5968d..813e8f9f 100644 --- a/src/RESTObjects/RESTAPI_GWobjects.cpp +++ b/src/RESTObjects/RESTAPI_GWobjects.cpp @@ -12,9 +12,9 @@ #include "Daemon.h" #ifdef TIP_GATEWAY_SERVICE #include "AP_WS_Server.h" -#include "StorageService.h" #include "CapabilitiesCache.h" #include "RADIUSSessionTracker.h" +#include "StorageService.h" #endif #include "RESTAPI_GWobjects.h" @@ -31,7 +31,8 @@ namespace OpenWifi::GWObjects { field_to_json(Obj, "serialNumber", SerialNumber); #ifdef TIP_GATEWAY_SERVICE field_to_json(Obj, "deviceType", StorageService()->GetPlatform(SerialNumber)); - field_to_json(Obj, "blackListed", StorageService()->IsBlackListed(Utils::MACToInt(SerialNumber))); + field_to_json(Obj, "blackListed", + StorageService()->IsBlackListed(Utils::MACToInt(SerialNumber))); #endif field_to_json(Obj, "macAddress", MACAddress); field_to_json(Obj, "manufacturer", Manufacturer); @@ -70,12 +71,12 @@ namespace OpenWifi::GWObjects { #ifdef TIP_GATEWAY_SERVICE ConnectionState ConState; #ifdef USE_MEDUSA_CLIENT - auto Res = GS()->GetState(SerialNumber); - if (Res.has_value()) { - Res.value().to_json(SerialNumber,Obj); + auto Res = GS()->GetState(SerialNumber); + if (Res.has_value()) { + Res.value().to_json(SerialNumber, Obj); #else - if (AP_WS_Server()->GetState(SerialNumber, ConState)) { - ConState.to_json(SerialNumber,Obj); + if (AP_WS_Server()->GetState(SerialNumber, ConState)) { + ConState.to_json(SerialNumber, Obj); #endif } else { field_to_json(Obj, "ipAddress", ""); @@ -172,17 +173,16 @@ namespace OpenWifi::GWObjects { field_to_json(Obj, "recorded", Recorded); } - bool HealthCheck::from_json(const Poco::JSON::Object::Ptr &Obj) { - try { - field_from_json(Obj, "UUID", UUID); - field_from_json(Obj, "sanity", Sanity); - field_from_json(Obj, "recorded", Recorded); - return true; - } catch(...) { - - } - return false; - } + bool HealthCheck::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "UUID", UUID); + field_from_json(Obj, "sanity", Sanity); + field_from_json(Obj, "recorded", Recorded); + return true; + } catch (...) { + } + return false; + } void DefaultFirmware::to_json(Poco::JSON::Object &Obj) const { field_to_json(Obj, "deviceType", deviceType); @@ -275,7 +275,8 @@ namespace OpenWifi::GWObjects { return false; } - void ConnectionState::to_json([[maybe_unused]] const std::string &SerialNumber, Poco::JSON::Object &Obj) { + void ConnectionState::to_json([[maybe_unused]] const std::string &SerialNumber, + Poco::JSON::Object &Obj) { field_to_json(Obj, "ipAddress", Address); field_to_json(Obj, "txBytes", TX); field_to_json(Obj, "rxBytes", RX); @@ -299,12 +300,12 @@ namespace OpenWifi::GWObjects { field_to_json(Obj, "certificateExpiryDate", certificateExpiryDate); field_to_json(Obj, "connectReason", connectReason); field_to_json(Obj, "uptime", uptime); - field_to_json(Obj, "compatible", Compatible); + field_to_json(Obj, "compatible", Compatible); #ifdef TIP_GATEWAY_SERVICE hasRADIUSSessions = RADIUSSessionTracker()->HasSessions(SerialNumber); #endif - field_to_json(Obj, "hasRADIUSSessions", hasRADIUSSessions ); + field_to_json(Obj, "hasRADIUSSessions", hasRADIUSSessions); field_to_json(Obj, "hasGPS", hasGPS); field_to_json(Obj, "sanity", sanity); field_to_json(Obj, "memoryUsed", memoryUsed); @@ -334,44 +335,44 @@ namespace OpenWifi::GWObjects { } } - bool ConnectionState::from_json(const Poco::JSON::Object::Ptr &Obj) { - try { - field_from_json(Obj, "compatible", Compatible); - field_from_json(Obj, "ipAddress", Address); - field_from_json(Obj, "txBytes", TX); - field_from_json(Obj, "rxBytes", RX); - field_from_json(Obj, "messageCount", MessageCount); - field_from_json(Obj, "UUID", UUID); - field_from_json(Obj, "connected", Connected); - field_from_json(Obj, "firmware", Firmware); - field_from_json(Obj, "lastContact", LastContact); - field_from_json(Obj, "associations_2G", Associations_2G); - field_from_json(Obj, "associations_5G", Associations_5G); - field_from_json(Obj, "associations_6G", Associations_6G); - field_from_json(Obj, "webSocketClients", webSocketClients); - field_from_json(Obj, "websocketPackets", websocketPackets); - field_from_json(Obj, "kafkaClients", kafkaClients); - field_from_json(Obj, "kafkaPackets", kafkaPackets); - field_from_json(Obj, "locale", locale); - field_from_json(Obj, "started", started); - field_from_json(Obj, "sessionId", sessionId); - field_from_json(Obj, "connectionCompletionTime", connectionCompletionTime); - field_from_json(Obj, "totalConnectionTime", totalConnectionTime); - field_from_json(Obj, "certificateExpiryDate", certificateExpiryDate); - field_from_json(Obj, "connectReason", connectReason); - field_from_json(Obj, "uptime", uptime); - field_from_json(Obj, "hasRADIUSSessions", hasRADIUSSessions ); - field_from_json(Obj, "hasGPS", hasGPS); - field_from_json(Obj, "sanity", sanity); - field_from_json(Obj, "memoryUsed", memoryUsed); - field_from_json(Obj, "sanity", sanity); - field_from_json(Obj, "load", load); - field_from_json(Obj, "temperature", temperature); - return true; - } catch(const Poco::Exception &E) { - } - return false; - } + bool ConnectionState::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "compatible", Compatible); + field_from_json(Obj, "ipAddress", Address); + field_from_json(Obj, "txBytes", TX); + field_from_json(Obj, "rxBytes", RX); + field_from_json(Obj, "messageCount", MessageCount); + field_from_json(Obj, "UUID", UUID); + field_from_json(Obj, "connected", Connected); + field_from_json(Obj, "firmware", Firmware); + field_from_json(Obj, "lastContact", LastContact); + field_from_json(Obj, "associations_2G", Associations_2G); + field_from_json(Obj, "associations_5G", Associations_5G); + field_from_json(Obj, "associations_6G", Associations_6G); + field_from_json(Obj, "webSocketClients", webSocketClients); + field_from_json(Obj, "websocketPackets", websocketPackets); + field_from_json(Obj, "kafkaClients", kafkaClients); + field_from_json(Obj, "kafkaPackets", kafkaPackets); + field_from_json(Obj, "locale", locale); + field_from_json(Obj, "started", started); + field_from_json(Obj, "sessionId", sessionId); + field_from_json(Obj, "connectionCompletionTime", connectionCompletionTime); + field_from_json(Obj, "totalConnectionTime", totalConnectionTime); + field_from_json(Obj, "certificateExpiryDate", certificateExpiryDate); + field_from_json(Obj, "connectReason", connectReason); + field_from_json(Obj, "uptime", uptime); + field_from_json(Obj, "hasRADIUSSessions", hasRADIUSSessions); + field_from_json(Obj, "hasGPS", hasGPS); + field_from_json(Obj, "sanity", sanity); + field_from_json(Obj, "memoryUsed", memoryUsed); + field_from_json(Obj, "sanity", sanity); + field_from_json(Obj, "load", load); + field_from_json(Obj, "temperature", temperature); + return true; + } catch (const Poco::Exception &E) { + } + return false; + } void DeviceConnectionStatistics::to_json(Poco::JSON::Object &Obj) const { field_to_json(Obj, "averageConnectionTime", averageConnectionTime); @@ -819,4 +820,105 @@ namespace OpenWifi::GWObjects { } return false; } + + bool PackageInfo::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "name", name); + field_from_json(Obj, "version", version); + return true; + } catch (const Poco::Exception &E) { + } + return false; + } + + void PackageInfo::to_json(Poco::JSON::Object &Obj) const { + field_to_json(Obj, "name", name); + field_to_json(Obj, "version", version); + } + + bool PackageList::from_json(const Poco::JSON::Array::Ptr &Obj) { + try { + std::ostringstream oss; + Poco::JSON::Stringifier::stringify(Obj, oss); + packageStringArray = oss.str(); + + return true; + } catch (const Poco::Exception &E) { + } + return false; + } + + void PackageList::to_json(Poco::JSON::Object &Obj) const { + Obj.set("serialNumber", serialNumber); + + Poco::JSON::Array packageJsonArray; + for (const auto &pkg : packageArray) { + Poco::JSON::Object pkgObj; + pkg.to_json(pkgObj); + packageJsonArray.add(pkgObj); + } + Obj.set("packageArray", packageJsonArray); + + Obj.set("FirstUpdate", Poco::UInt64(FirstUpdate)); + Obj.set("LastUpdate", Poco::UInt64(LastUpdate)); + } + + bool ToBeInstalled::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "name", name); + field_from_json(Obj, "url", url); + + Poco::URI uri(url); + std::string scheme = uri.getScheme(); + if (scheme != "http" && scheme != "https") { + return false; + } + + return true; + } catch (const Poco::Exception &E) { + } + return false; + } + + void ToBeInstalled::to_json(Poco::JSON::Object &Obj) const { + Obj.set("name", name); + Obj.set("url", url); + } + + bool PackageInstall::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "serialNumber", serialNumber); + field_from_json(Obj, "when", when); + field_from_json(Obj, "packages", pkgs); + + return true; + } catch (const Poco::Exception &E) { + } + return false; + } + + bool ToBeRemoved::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "name", name); + + return true; + } catch (const Poco::Exception &E) { + } + return false; + } + + void ToBeRemoved::to_json(Poco::JSON::Object &Obj) const { + Obj.set("name", name); + } + + bool PackageRemove::from_json(const Poco::JSON::Object::Ptr &Obj) { + try { + field_from_json(Obj, "serialNumber", serialNumber); + field_from_json(Obj, "packages", pkgs); + + return true; + } catch (const Poco::Exception &E) { + } + return false; + } } // namespace OpenWifi::GWObjects diff --git a/src/RESTObjects/RESTAPI_GWobjects.h b/src/RESTObjects/RESTAPI_GWobjects.h index c2136c45..e8033255 100644 --- a/src/RESTObjects/RESTAPI_GWobjects.h +++ b/src/RESTObjects/RESTAPI_GWobjects.h @@ -545,6 +545,51 @@ namespace OpenWifi::GWObjects { std::uint64_t when; std::vector ports; + bool from_json(const Poco::JSON::Object::Ptr &Obj); + }; + struct PackageInfo { + std::string name; + std::string version; + + bool from_json(const Poco::JSON::Object::Ptr &Obj); + void to_json(Poco::JSON::Object &Obj) const; + }; + struct PackageList { + std::string serialNumber; + std::vector packageArray; + uint64_t FirstUpdate = 0; + uint64_t LastUpdate = 0; + std::string packageStringArray; + + bool from_json(const Poco::JSON::Array::Ptr &Obj); + void to_json(Poco::JSON::Object &Obj) const; + }; + struct ToBeInstalled { + std::string name; + std::string url; + + bool from_json(const Poco::JSON::Object::Ptr &Obj); + void to_json(Poco::JSON::Object &Obj) const; + }; + struct PackageInstall { + std::string serialNumber; + std::uint64_t when; + std::vector pkgs; + + bool from_json(const Poco::JSON::Object::Ptr &Obj); + void to_json(Poco::JSON::Object &Obj) const; + }; + struct ToBeRemoved { + std::string name; + + bool from_json(const Poco::JSON::Object::Ptr &Obj); + void to_json(Poco::JSON::Object &Obj) const; + }; + struct PackageRemove { + std::string serialNumber; + std::uint64_t when; + std::vector pkgs; + bool from_json(const Poco::JSON::Object::Ptr &Obj); }; } // namespace OpenWifi::GWObjects diff --git a/src/StorageService.h b/src/StorageService.h index 10a3025f..79042a2f 100644 --- a/src/StorageService.h +++ b/src/StorageService.h @@ -282,6 +282,12 @@ namespace OpenWifi { bool SetDeviceLastRecordedContact(std::string & SerialNumber, std::uint64_t lastRecordedContact); bool SetDeviceLastRecordedContact(Poco::Data::Session & Session, std::string & SerialNumber, std::uint64_t lastRecordedContact); + bool GetDeviceInstalledPackages(std::string &SerialNumber, GWObjects::PackageList &Pkgs); + bool CreateDeviceInstalledPackages(std::string &SerialNumber, GWObjects::PackageList &Pkgs); + bool UpdateDeviceInstalledPackages(std::string &SerialNumber, GWObjects::PackageList &Pkgs); + bool DeleteDeviceInstalledPackages(std::string &SerialNumber); + bool CheckPackageIsInstalled(std::string &SerialNumber, GWObjects::PackageInstall &Pkgs); + int Create_Tables(); int Create_Statistics(); int Create_Devices(); @@ -293,6 +299,7 @@ namespace OpenWifi { int Create_BlackList(); int Create_FileUploads(); int Create_DefaultFirmwares(); + int Create_Packages(); bool AnalyzeCommands(Types::CountedMap &R); bool AnalyzeDevices(GWObjects::Dashboard &D); diff --git a/src/framework/ow_constants.h b/src/framework/ow_constants.h index 0a617e55..027b1434 100644 --- a/src/framework/ow_constants.h +++ b/src/framework/ow_constants.h @@ -433,6 +433,9 @@ namespace OpenWifi::RESTAPI::Errors { static const struct msg InvalidRRMAction { 1192, "Invalid RRM Action." }; + static const struct msg InvalidPackageURL { 1193, "Invalid URL, must start with http:// or https://." }; + static const struct msg FailedToDownload { 1194, "Failed to download package." }; + static const struct msg SimulationDoesNotExist { 7000, "Simulation Instance ID does not exist." }; @@ -550,6 +553,10 @@ namespace OpenWifi::RESTAPI::Protocol { static const char *DEBUG = "debug"; static const char *SCRIPT = "script"; static const char *TIMEOUT = "timeout"; + static const char *PACKAGE = "package"; + static const char *PACKAGES = "packages"; + static const char *PACKAGEINST = "packageInstall"; + static const char *PACKAGEDEL = "packageDelete"; static const char *NEWPASSWORD = "newPassword"; static const char *USERS = "users"; @@ -668,6 +675,9 @@ namespace OpenWifi::uCentralProtocol { static const char *SIGNATURE = "signature"; static const char *INFO = "info"; static const char *DATE = "date"; + static const char *PACKAGE = "package"; + static const char *PACKAGES = "packages"; + static const char *CATEGORY = "category"; static const char *SERIALNUMBER = "serialNumber"; static const char *COMPATIBLE = "compatible"; @@ -699,6 +709,10 @@ namespace OpenWifi::uCentralProtocol { static const char *FIXEDCONFIG = "fixedconfig"; static const char *CABLEDIAGNOSTICS = "cable-diagnostics"; + static const char *OPERATION = "op"; + static const char *PACKAGEINST = "pkginst"; + static const char *PACKAGEDEL = "pkgdel"; + } // namespace OpenWifi::uCentralProtocol namespace OpenWifi::uCentralProtocol::Events { @@ -733,7 +747,8 @@ namespace OpenWifi::uCentralProtocol::Events { ET_EVENT, ET_WIFISCAN, ET_ALARM, - ET_REBOOTLOG + ET_REBOOTLOG, + ET_PACKAGE }; inline EVENT_MSG EventFromString(const std::string &Method) { @@ -767,6 +782,8 @@ namespace OpenWifi::uCentralProtocol::Events { return ET_ALARM; else if (strcmp(REBOOTLOG, Method.c_str()) == 0) return ET_REBOOTLOG; + else if (strcmp(PACKAGE, Method.c_str()) == 0) + return ET_PACKAGE; return ET_UNKNOWN; }; } // namespace OpenWifi::uCentralProtocol::Events @@ -797,6 +814,7 @@ namespace OpenWifi::APCommands { powercycle, fixedconfig, cablediagnostics, + package, unknown }; @@ -812,7 +830,8 @@ namespace OpenWifi::APCommands { RESTAPI::Protocol::PING, RESTAPI::Protocol::SCRIPT, RESTAPI::Protocol::RRM, RESTAPI::Protocol::CERTUPDATE, RESTAPI::Protocol::TRANSFER, RESTAPI::Protocol::POWERCYCLE, - RESTAPI::Protocol::FIXEDCONFIG, RESTAPI::Protocol::CABLEDIAGNOSTICS + RESTAPI::Protocol::FIXEDCONFIG, RESTAPI::Protocol::CABLEDIAGNOSTICS, + RESTAPI::Protocol::PACKAGE }; inline const char *to_string(Commands Cmd) { return uCentralAPCommands[(uint8_t)Cmd]; } diff --git a/src/storage/storage_packages.cpp b/src/storage/storage_packages.cpp new file mode 100644 index 00000000..afbab15e --- /dev/null +++ b/src/storage/storage_packages.cpp @@ -0,0 +1,151 @@ +// +// License type: BSD 3-Clause License +// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE +// +// Created by Euphokumiko on 2025-05-20. +// Accton Corp. +// + +#include "CentralConfig.h" +#include "Poco/Data/RecordSet.h" +#include "Poco/JSON/Object.h" +#include "Poco/JSON/Parser.h" +#include "StorageService.h" +#include "framework/utils.h" + +#include "fmt/format.h" + +namespace OpenWifi { + + const static std::string DB_PackageSelectField{"SerialNumber, Packages, FirstUpdate, LastUpdate "}; + + const static std::string DB_MYSQL_JSON_QUERY{"WHERE JSON_SEARCH(Packages, 'one', ?, NULL, '$[*].name') IS NOT NULL"}; + const static std::string DB_POSTGRES_JSON_QUERY{"WHERE Packages::jsonb @> '[{\"name\": \"?\"}]'"}; + + // serial pkgs f_update l_update + typedef Poco::Tuple PackageTuple; + + + bool Storage::CreateDeviceInstalledPackages(std::string &SerialNumber, + GWObjects::PackageList &Pkgs) { + try { + Poco::Data::Session Sess(Pool_->get()); + Poco::Data::Statement UpSert(Sess); + + uint64_t Now = Utils::Now(); + + std::string St{ + "INSERT INTO DevicePackages (" + + DB_PackageSelectField + ") " + "VALUES (?,?,?,?) " + "ON CONFLICT (SerialNumber) DO " + "UPDATE SET Packages = ?, LastUpdate = ?"}; + + UpSert << ConvertParams(St), Poco::Data::Keywords::use(SerialNumber), + Poco::Data::Keywords::use(Pkgs.packageStringArray), + Poco::Data::Keywords::use(Now), Poco::Data::Keywords::use(Now), + Poco::Data::Keywords::use(Pkgs.packageStringArray), + Poco::Data::Keywords::use(Now); + UpSert.execute(); + Sess.commit(); + return true; + } catch (const Poco::Exception &E) { + poco_warning(Logger(), fmt::format("{}: Failed with: {}", std::string(__func__), + E.displayText())); + } + return false; + } + + bool Storage::UpdateDeviceInstalledPackages(std::string &SerialNumber, GWObjects::PackageList &Pkgs) { + return CreateDeviceInstalledPackages(SerialNumber, Pkgs); + } + + bool Storage::GetDeviceInstalledPackages(std::string &SerialNumber, + GWObjects::PackageList &DevicePackages) { + try { + Poco::Data::Session Sess(Pool_->get()); + Poco::Data::Statement Select(Sess); + + PackageTuple packageTuple; + std::string TmpSerialNumber; + std::string packageStringArray; + std::string St{"SELECT " + DB_PackageSelectField + + "FROM DevicePackages WHERE SerialNumber=?"}; + + Select << ConvertParams(St), Poco::Data::Keywords::into(TmpSerialNumber), + Poco::Data::Keywords::into(packageStringArray), + Poco::Data::Keywords::into(DevicePackages.FirstUpdate), + Poco::Data::Keywords::into(DevicePackages.LastUpdate), + Poco::Data::Keywords::use(SerialNumber); + + Select.execute(); + + if (!TmpSerialNumber.empty()) { + Poco::JSON::Parser parser; + Poco::Dynamic::Var result = parser.parse(packageStringArray); + Poco::JSON::Array::Ptr jsonArray = result.extract(); + DevicePackages.serialNumber = TmpSerialNumber; + DevicePackages.packageArray.clear(); + + for (const auto &item : *jsonArray) { + Poco::JSON::Object::Ptr obj = item.extract(); + GWObjects::PackageInfo pkg; + pkg.name = obj->getValue("name"); + pkg.version = obj->getValue("version"); + DevicePackages.packageArray.emplace_back(pkg); + } + } else { + DevicePackages.packageArray.clear(); + } + + return true; + } catch (const Poco::Exception &E) { + poco_warning(Logger(), fmt::format("{}: Failed with: {}", std::string(__func__), + E.displayText())); + } + + return false; + } + + // bool Storage::CheckIfPackageAlreadyInstalled(std::string &SerialNumber, std::string packageName) { + // try { + // Poco::Data::Session Sess(Pool_->get()); + // Poco::Data::Statement Delete(Sess); + + // std::string St{ "SELECT 1 AS is_present FROM DevicePackages " + // "WHERE SerialNumber = ? " + // "AND " + // + + + + // }; + + // Delete << ConvertParams(St), Poco::Data::Keywords::use(SerialNumber); + // Delete.execute(); + // Sess.commit(); + // return true; + // } catch (const Poco::Exception &E) { + // poco_warning(Logger(), fmt::format("{}: Failed with: {}", std::string(__func__), + // E.displayText())); + // } + // return false; + // } + + bool Storage::DeleteDeviceInstalledPackages(std::string &SerialNumber) { + try { + Poco::Data::Session Sess = Pool_->get(); + Sess.begin(); + Poco::Data::Statement Delete(Sess); + + std::string St{"DELETE FROM Packages WHERE SerialNumber=?"}; + + Delete << ConvertParams(St), Poco::Data::Keywords::use(SerialNumber); + Delete.execute(); + Sess.commit(); + return true; + } catch (const Poco::Exception &E) { + poco_warning(Logger(), fmt::format("{}: Failed with: {}", std::string(__func__), + E.displayText())); + } + return false; + } +} // namespace OpenWifi \ No newline at end of file diff --git a/src/storage/storage_tables.cpp b/src/storage/storage_tables.cpp index 08d1cbb2..e264661c 100644 --- a/src/storage/storage_tables.cpp +++ b/src/storage/storage_tables.cpp @@ -22,6 +22,7 @@ namespace OpenWifi { Create_BlackList(); Create_FileUploads(); Create_DefaultFirmwares(); + Create_Packages(); return 0; } @@ -49,8 +50,7 @@ namespace OpenWifi { "Data TEXT, " "Recorded BIGINT, " "INDEX StatSerial0 (SerialNumber)), ", - "INDEX StatSerial (SerialNumber ASC, Recorded ASC))", - Poco::Data::Keywords::now; + "INDEX StatSerial (SerialNumber ASC, Recorded ASC))", Poco::Data::Keywords::now; } return 0; } catch (const Poco::Exception &E) { @@ -154,8 +154,7 @@ namespace OpenWifi { "alter table devices add column lastRecordedContact bigint", "alter table devices add column simulated boolean", "alter table devices add column certificateExpiryDate bigint", - "alter table devices add column connectReason TEXT" - }; + "alter table devices add column connectReason TEXT"}; for (const auto &i : Script) { try { @@ -279,9 +278,7 @@ namespace OpenWifi { Poco::Data::Keywords::now; } - std::vector Script{ - "alter table DefaultConfigs add column Platform text" - }; + std::vector Script{"alter table DefaultConfigs add column Platform text"}; for (const auto &i : Script) { try { @@ -454,4 +451,23 @@ namespace OpenWifi { return -1; } + int Storage::Create_Packages() { + try { + Poco::Data::Session Sess = Pool_->get(); + + Sess << "CREATE TABLE IF NOT EXISTS DevicePackages (" + "SerialNumber VARCHAR(30) PRIMARY KEY, " + "Packages JSON, " + "FirstUpdate BIGINT, " + "LastUpdate BIGINT" + ")", + Poco::Data::Keywords::now; + + return 0; + } catch (const Poco::Exception &E) { + Logger().log(E); + } + return -1; + } + } // namespace OpenWifi \ No newline at end of file diff --git a/verbosity.json b/verbosity.json new file mode 100644 index 00000000..928918c5 --- /dev/null +++ b/verbosity.json @@ -0,0 +1,19 @@ +[ + { + "serialNumber": "xxxxx", + "packages": "akiho - 98-10-16-71a3b533e-1 \n erichi - 98-12-06-98e79a27f-1 \n ucrun - 2022-02-19-05be6abeb-1 \n vxlan - 7 ..." + }, + { + "serialNumber": "xxxxx", + "packages": [ + { + "packageName": "akiho", + "version": "98-10-16-71a3b533e-1" + }, + { + "packageName": "erichi", + "version": "98-12-06-98e79a27f-1" + } + ] + } +] \ No newline at end of file