From a680b7eceb1267e4816bbfca860d09827eb4232f Mon Sep 17 00:00:00 2001 From: peri4 Date: Thu, 2 Apr 2026 17:35:25 +0300 Subject: [PATCH 01/35] fix PIValueTree --- libs/main/types/pivaluetree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/main/types/pivaluetree.cpp b/libs/main/types/pivaluetree.cpp index a02ce13c..a87303ad 100644 --- a/libs/main/types/pivaluetree.cpp +++ b/libs/main/types/pivaluetree.cpp @@ -217,7 +217,7 @@ PIValueTree & PIValueTree::child(const PIStringList & path) { if (_is_null || path.isEmpty()) return *this; PIValueTree * ret = &child(path[0]); for (int i = 1; i < path.size_s(); ++i) - ret = &child(path[i]); + ret = &(ret->child(path[i])); return *ret; } From 715d70d930e030bd03eb62e3b64e0a6b43c3b3a0 Mon Sep 17 00:00:00 2001 From: peri4 Date: Thu, 9 Apr 2026 13:56:08 +0300 Subject: [PATCH 02/35] add PIJSON de/serialization overloads for PIStringList --- libs/main/serialization/pijsonserialization.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libs/main/serialization/pijsonserialization.h b/libs/main/serialization/pijsonserialization.h index dd8bcc22..9401feaf 100644 --- a/libs/main/serialization/pijsonserialization.h +++ b/libs/main/serialization/pijsonserialization.h @@ -291,6 +291,13 @@ inline PIJSON piSerializeJSON(const PIMathVectorT & v) { return ret; } +template<> +//! \~english Serializes %PIStringList as JSON array. +//! \~russian Сериализует %PIStringList как JSON-массив. +inline PIJSON piSerializeJSON(const PIStringList & v) { + return piSerializeJSON((const PIDeque &)v); +} + // --- // deserialize, piDeserializeJSON(T, PIJSON) @@ -538,6 +545,13 @@ inline void piDeserializeJSON(PIMathVectorT & v, const PIJSON & js) { piDeserializeJSON(v[i], js[i]); } +template<> +//! \~english Deserializes %PIStringList from JSON array. +//! \~russian Десериализует %PIStringList из JSON-массива. +inline void piDeserializeJSON(PIStringList & v, const PIJSON & js) { + piDeserializeJSON((PIDeque &)v, js); +} + // --- // PIJSON static wrapper From a90c486094ce9908622b065f1d598fbeeb956d61 Mon Sep 17 00:00:00 2001 From: peri4 Date: Thu, 9 Apr 2026 14:23:41 +0300 Subject: [PATCH 03/35] PIJSON::print (and toJSON()) now use "null" for unknown type, resulting valid JSON --- libs/main/serialization/pijson.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/main/serialization/pijson.cpp b/libs/main/serialization/pijson.cpp index 8ef17798..b58085ee 100644 --- a/libs/main/serialization/pijson.cpp +++ b/libs/main/serialization/pijson.cpp @@ -547,7 +547,7 @@ void PIJSON::print(PIString & s, const PIJSON & v, PIString tab, bool spaces, bo if (spaces) s += ' '; } switch (v.c_type) { - case PIJSON::Invalid: break; + case PIJSON::Invalid: case PIJSON::Null: s += "null"; break; case PIJSON::Boolean: s += PIString::fromBool(v.c_value.toBool()); break; case PIJSON::Number: s += v.c_value.toString(); break; From a2e54c36947eda6c32fe27786520d25d4677206a Mon Sep 17 00:00:00 2001 From: peri4 Date: Fri, 8 May 2026 16:17:56 +0300 Subject: [PATCH 04/35] android fix unknown -W --- libs/main/types/pibytearray.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/main/types/pibytearray.h b/libs/main/types/pibytearray.h index 5d0ada7f..cf90ff53 100644 --- a/libs/main/types/pibytearray.h +++ b/libs/main/types/pibytearray.h @@ -1107,9 +1107,11 @@ public: PIByteArray & append(const PIByteArray & data_) { #ifdef CC_GCC # pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wstringop-overflow" # pragma GCC diagnostic ignored "-Warray-bounds" -# pragma GCC diagnostic ignored "-Wrestrict" +# ifndef ANDROID +# pragma GCC diagnostic ignored "-Wstringop-overflow" +# pragma GCC diagnostic ignored "-Wrestrict" +# endif #endif const size_t ps = size(); enlarge(data_.size_s()); From a2093e24a6e5224a8bd70314ca9b3bff7f493a4a Mon Sep 17 00:00:00 2001 From: peri4 Date: Tue, 26 May 2026 21:31:58 +0300 Subject: [PATCH 05/35] detach PIHTTPServer::Endpoint to base struct PIHTTP::ServerEndpoint (private header), no functionality changes in HTTP server Ready to implement this private base struct to MQTT client --- libs/http_server/pihttpserver.cpp | 158 ++++-------------------- libs/main/http_common/pihttptypes.h | 5 + libs/main/http_server/pihttpserver.h | 36 +----- main.cpp | 177 +++++++-------------------- 4 files changed, 76 insertions(+), 300 deletions(-) diff --git a/libs/http_server/pihttpserver.cpp b/libs/http_server/pihttpserver.cpp index 796f88c4..eb4d11ec 100644 --- a/libs/http_server/pihttpserver.cpp +++ b/libs/http_server/pihttpserver.cpp @@ -1,14 +1,25 @@ #include "pihttpserver.h" -#include "piliterals_string.h" +#include "piserverendpoint_p.h" + + +struct Endpoint: public PIHTTP::ServerEndpoint { + PIHTTP::Method method = PIHTTP::Method::Unknown; + PIHTTPServer::RequestFunction function; +}; + + +PRIVATE_DEFINITION_START(PIHTTPServer) + PIMap> endpoints; +PRIVATE_DEFINITION_END(PIHTTPServer) PIHTTPServer::PIHTTPServer() { setRequestCallback([this](const PIHTTP::MessageConst & r) -> PIHTTP::MessageMutable { PIHTTP::MessageMutable reply; reply.setCode(PIHTTP::Code::NotFound); - auto in_path = splitPath(r.path()); - auto it = endpoints.makeReverseIterator(); + auto in_path = PIHTTP::ServerEndpoint::splitPath(r.path()); + auto it = PRIVATE->endpoints.makeReverseIterator(); bool found = false; while (it.next()) { for (const auto & ep: it.value()) { @@ -41,159 +52,36 @@ PIHTTPServer::~PIHTTPServer() { } -bool PIHTTPServer::registerPath(const PIString & path, PIHTTP::Method method, RequestFunction functor) { +bool PIHTTPServer::registerPath(const PIString & path, PIHTTP::Method method, PIHTTP::RequestFunction functor) { Endpoint ep; if (!ep.create(path)) return false; ep.method = method; ep.function = std::move(functor); - endpoints[ep.priority] << ep; + PRIVATE->endpoints[ep.priority] << ep; return true; } -void PIHTTPServer::registerUnhandled(RequestFunction functor) { +void PIHTTPServer::registerUnhandled(PIHTTP::RequestFunction functor) { unhandled = functor; } void PIHTTPServer::unregisterPath(const PIString & path, PIHTTP::Method method) { - auto pl = splitPath(path); - auto it = endpoints.makeIterator(); + auto pl = PIHTTP::ServerEndpoint::splitPath(path); + auto it = PRIVATE->endpoints.makeIterator(); while (it.next()) { it.value().removeWhere([&pl, method](const Endpoint & ep) { return ep.path == pl && ep.method == method; }); } - endpoints.removeWhere([](uint, const PIVector & epl) { return epl.isEmpty(); }); + PRIVATE->endpoints.removeWhere([](uint, const PIVector & epl) { return epl.isEmpty(); }); } void PIHTTPServer::unregisterPath(const PIString & path) { - auto pl = splitPath(path); - auto it = endpoints.makeIterator(); + auto pl = PIHTTP::ServerEndpoint::splitPath(path); + auto it = PRIVATE->endpoints.makeIterator(); while (it.next()) { it.value().removeWhere([&pl](const Endpoint & ep) { return ep.path == pl; }); } - endpoints.removeWhere([](uint, const PIVector & epl) { return epl.isEmpty(); }); -} - - -PIStringList PIHTTPServer::splitPath(const PIString & path) { - auto ret = path.split("/"); - ret.removeAll({}); - return ret; -} - - -PIHTTPServer::PathElement::PathElement(const PIString & reg) { - source = reg; - if (reg == "*"_a) { - type = Type::AnyOne; - } else if (reg == "**"_a) { - type = Type::AnyMany; - } else if (reg.contains('*')) { - type = Type::AnyPart; - parts = reg.split('*'); - } else if (reg.contains('{')) { - type = Type::Arguments; - int ind = 0, eind = 0, pind = 0; - for (;;) { - ind = reg.find('{', ind); - if (ind < 0) break; - eind = reg.find('}', ind + 1); - if (eind < 0) break; - arguments.insert(arguments.size_s(), reg.mid(ind + 1, eind - ind - 1)); - if (ind == 0) - parts << PIString(); - else { - if (ind > pind) - parts << reg.mid(pind, ind - pind); - else if (parts.isNotEmpty()) { - piCout << "[PIHTTPServer] Warning: sequential arguments, ignoring this path!"; - type = Type::Invalid; - return; - } - } - ind = pind = eind + 1; - } - if (eind < reg.size_s() - 1) parts << reg.mid(eind + 1); - } -} - - -bool PIHTTPServer::PathElement::match(const PIString & in, PIMap & ext_args) const { - // piCout << "match" << source << "with" << in; - if (type == Type::AnyOne) return true; - if (type == Type::AnyPart) { - int ind = 0; - for (const auto & m: parts) { - ind = in.find(m, ind); - if (ind < 0) return false; - } - return true; - } - if (type == Type::Arguments) { - int ind = 0, eind = 0; - for (int i = 0; i < parts.size_s(); ++i) { - const auto & m(parts[i]); - if (m.isNotEmpty()) { - ind = in.find(m, eind); - if (ind < 0) return false; - } - if (i > 0) { - ext_args[arguments.value(i - 1)] = in.mid(eind, ind - eind); - } - eind = ind + m.size_s(); - } - if (parts.size() == arguments.size()) { - ext_args[arguments.value(arguments.size_s() - 1)] = in.mid(eind); - } - return true; - } - return source == in; -} - - -uint PIHTTPServer::PathElement::priority() const { - switch (type) { - case Type::Fixed: return 0x10000; break; - case Type::Arguments: return 0x1000; break; - case Type::AnyPart: return 0x100; break; - case Type::AnyOne: return 0x10; break; - case Type::AnyMany: return 0x1; break; - default: break; - } - return 0; -} - - -bool PIHTTPServer::Endpoint::create(const PIString & p) { - path = splitPath(p); - prepared_path.clear(); - priority = 0; - for (const auto & i: path) { - PathElement pe(i); - prepared_path << pe; - path_types |= pe.type; - priority += pe.priority(); - } - return !path_types[PathElement::Type::Invalid]; -} - - -bool PIHTTPServer::Endpoint::match(const PIStringList & in_path, PIMap & ext_args) const { - if (path_types[PathElement::Type::AnyMany]) { - int any_ind = path.indexOf("**"_a); - for (int i = 0; i < any_ind; ++i) { - if (!prepared_path[i].match(in_path[i], ext_args)) return false; - } - int si = prepared_path.size_s() - 1, ii = in_path.size_s() - 1; - for (; si > any_ind && ii >= 0; --si, --ii) { - if (!prepared_path[si].match(in_path[ii], ext_args)) return false; - } - } else { - if (in_path.size() != prepared_path.size()) return false; - for (int i = 0; i < prepared_path.size_s(); ++i) { - if (!prepared_path[i].match(in_path[i], ext_args)) return false; - } - } - return true; + PRIVATE->endpoints.removeWhere([](uint, const PIVector & epl) { return epl.isEmpty(); }); } diff --git a/libs/main/http_common/pihttptypes.h b/libs/main/http_common/pihttptypes.h index cb8cf15e..a105ecf0 100644 --- a/libs/main/http_common/pihttptypes.h +++ b/libs/main/http_common/pihttptypes.h @@ -210,6 +210,11 @@ public: PIP_EXPORT const char * methodName(Method m); +//! \~english Request handler used by registered routes and fallback processing. +//! \~russian Обработчик запроса, используемый зарегистрированными маршрутами и fallback-обработкой. +using RequestFunction = std::function; + + }; // namespace PIHTTP diff --git a/libs/main/http_server/pihttpserver.h b/libs/main/http_server/pihttpserver.h index 68fa2fe4..ca3abea1 100644 --- a/libs/main/http_server/pihttpserver.h +++ b/libs/main/http_server/pihttpserver.h @@ -102,43 +102,9 @@ public: void clearReplyHeaders() { reply_headers.clear(); } private: - struct PathElement { - enum class Type { - Invalid = 0x01, - Fixed = 0x02, - Arguments = 0x04, - AnyOne = 0x08, - AnyPart = 0x10, - AnyMany = 0x20 - }; - - Type type = Type::Fixed; - PIString source; - PIStringList parts; - PIMap arguments; - - PathElement(const PIString & reg = {}); - - bool match(const PIString & in, PIMap & ext_args) const; - uint priority() const; - }; - - struct Endpoint { - PIStringList path; - PIHTTP::Method method = PIHTTP::Method::Unknown; - RequestFunction function; - PIFlags path_types; - PIVector prepared_path; - uint priority = 0; - - bool create(const PIString & p); - bool match(const PIStringList & in_path, PIMap & ext_args) const; - }; - - static PIStringList splitPath(const PIString & path); + PRIVATE_DECLARATION(PIP_HTTP_SERVER_EXPORT) PIMap reply_headers; - PIMap> endpoints; RequestFunction unhandled; }; diff --git a/main.cpp b/main.cpp index fa83d4df..f92b7159 100644 --- a/main.cpp +++ b/main.cpp @@ -10,18 +10,57 @@ using namespace PICoutManipulators; using namespace PIHTTP; -using namespace PIUnits::Class; -int rcnt = 0, scnt = 0; - -inline PIByteArray SMBusTypeInfo_genHash(PIString n) { - PICrypt c; - return piSerialize(c.shorthash(n.removeAll(" "), PIString("SMBusDataHashKey").toByteArray())); -} PIKbdListener kbd; +MessageMutable createMessage(Code c, const char * path, const MessageConst & msg) { + piCout << "path" << path << "args" << msg.pathArguments(); + return MessageMutable().setCode(c); +}; int main(int argc, char * argv[]) { + piCout << "start ..."; + PIHTTPServer server; + server.registerUnhandled([](const MessageConst & msg) { return createMessage(Code::BadRequest, "unhadled", msg); }); + server.registerPath("api/v1/status", Method::Get, [](const MessageConst & msg) { + return createMessage(Code::Accepted, "api/v1/status", msg); + }); + server.registerPath("api/v1/plugins", Method::Get, [](const MessageConst & msg) { + return createMessage(Code::Accepted, "api/v1/plugins", msg); + }); + server.registerPath("api/v1/task-status", Method::Get, [](const MessageConst & msg) { + return createMessage(Code::Accepted, "api/v1/task-status", msg); + }); + server.registerPath("api/v1/task/{taskID}/status", Method::Get, [](const MessageConst & msg) { + return createMessage(Code::Accepted, "api/v1/task/{taskID}/status", msg); + }); + server.registerPath("api/v1/bort/list", Method::Get, [](const MessageConst & msg) { + return createMessage(Code::Accepted, "api/v1/bort/list", msg); + }); + server.registerPath("api/v1/all", Method::Get, [](const MessageConst & msg) { + return createMessage(Code::Accepted, "api/v1/all", msg); + }); + server.registerPath("api/v1/all/bort{A}/f", Method::Get, [](const MessageConst & msg) { + return createMessage(Code::Accepted, "api/v1/all/*/f", msg); + }); + server.registerPath("api/v1/all2/**", Method::Get, [](const MessageConst & msg) { + return createMessage(Code::Accepted, "api/v1/all2/**", msg); + }); + server.listenAll(12345); + + kbd.enableExitCapture('Q'); + WAIT_FOR_EXIT + piCout << "exiting ..."; + server.stop(); + + return 0; + + PISystemMonitor mon; + mon.startOnSelf(); + PISystemMonitor::totalRAM(); + 2_s .sleep(); + + return 0; PIMQTT::Client cl; cl.setConnectTimeout(2_s); CONNECTL(&cl, connected, [&cl] { @@ -36,7 +75,7 @@ int main(int argc, char * argv[]) { cl.connect("localhost", "PIP"); }); CONNECTL(&cl, received, [](const PIMQTT::MessageConst & message) { - piCout << "received" << message.topic() << message.payload().size(); + piCout << "received" << message.topic() << message.pathArguments() << message.payload().size(); }); cl.connect("localhost", "PIP"); @@ -45,126 +84,4 @@ int main(int argc, char * argv[]) { WAIT_FOR_EXIT piCout << "exiting ..."; - return 0; - - PICrypt _crypt; - // auto ba = PIFile::readAll("logo.png"); - PIString str = "hello!"_a; - PIByteArray ba = str.toAscii(); - PIByteArray key = PIString("SMBusDataHashKey").toByteArray(); - - const int times = 1000000; - PITimeMeasurer tm; - PISystemTime el; - - tm.reset(); - piForTimes(times) { - PIDigest::calculateWithKey(ba, key, PIDigest::Type::SipHash_2_4_128); - } - el = tm.elapsed(); - piCout << "PIDigest" << el.toString(); - - tm.reset(); - piForTimes(times) { - _crypt.shorthash(str, key); - } - el = tm.elapsed(); - piCout << " sodium" << el.toString(); - - tm.reset(); - piForTimes(times) { - PIDigest::calculateWithKey(ba, key, PIDigest::Type::BLAKE2b_128); - } - el = tm.elapsed(); - piCout << " blake" << el.toString(); - - return 0; - - - PIEthernet *eth_r, *eth_s; - eth_r = PIIODevice::createFromFullPath("eth://udp: 192.168.1.25 :10000")->cast(); - eth_s = PIIODevice::createFromFullPath("eth://udp: : : 192.168.1.25:10000")->cast(); - - eth_r->setReadBufferSize(1_MiB); - CONNECTL(eth_r, threadedReadEvent, [](const uchar * readed, ssize_t size) { - // piCout << "rec"; - piMSleep(1); - ++rcnt; - }); - eth_r->startThreadedRead(); - - PIByteArray _ba(1400); - for (int i = 0; i < 100; ++i) { - eth_s->write(_ba); - ++scnt; - } - - 0.2_s .sleep(); - - piCout << "snd" << scnt; - piCout << "rec" << rcnt; - - piDeleteSafety(eth_r); - piDeleteSafety(eth_s); - return 0; - - PITranslator::loadLang("ru"); - /*auto ucl = PIUnits::allClasses(); - for (auto c: ucl) { - piCout << (c->className() + ":"); - for (auto t: c->allTypes()) { - piCout << " " << c->name(t) << "->" << c->unit(t); - } - }*/ - - // PIUnits::Value(1); - // piCout << PIUnits::name(PIUnits::Class::Information::Bit); - // piCout << PIUnits::name(PIUnits::Class::Information::Byte); - // piCout << PIUnits::name(PIUnits::Class::Information::_LastType); - // piCout << PIUnits::name((int)PIUnits::Class::Angle::Degree); - - // piCout << PIUnits::unit(PIUnits::Class::Information::Bit); - // piCout << PIUnits::unit(PIUnits::Class::Information::Byte); - // piCout << PIUnits::unit(PIUnits::Class::Information::_LastType); - // piCout << PIUnits::unit((int)PIUnits::Class::Angle::Degree); - - // for (int i = -10; i < 10; ++i) - // piCout << PIUnits::Value(pow10(i * 0.99), PIUnits::Class::Distance::Meter).toString(); - - auto v = PIUnits::Value(M_PI, Angle::Radian); - piCout << v << "=" << v.converted(Angle::Degree); - - v = PIUnits::Value(45, Angle::Degree); - piCout << v << "=" << v.converted(Angle::Radian); - - piCout << PIUnits::Value(5E-5, Time::Second); - piCout << PIUnits::Value(3E-3, Time::Second); - piCout << PIUnits::Value(0.8, Time::Second); - piCout << PIUnits::Value(1.2, Time::Second); - piCout << PIUnits::Value(1001, Time::Second); - piCout << PIUnits::Value(1000001, Time::Second); - - piCout << PIUnits::Value(1_KB, Information::Byte); - piCout << PIUnits::Value(1_MB, Information::Byte); - piCout << PIUnits::Value(1_MiB, Information::Byte); - piCout << PIUnits::Value(1_MB, Information::Byte).converted(Information::Bit); - piCout << PIUnits::Value(1_MiB, Information::Byte).converted(Information::Bit); - - piCout << PIUnits::Value(0., Temperature::Celsius).converted(Temperature::Kelvin); - piCout << PIUnits::Value(0., Temperature::Celsius).converted(Temperature::Fahrenheit); - piCout << PIUnits::Value(100., Temperature::Celsius).converted(Temperature::Fahrenheit); - - piCout << PIUnits::Value(1., Pressure::Atmosphere).converted(Pressure::Pascal); - piCout << PIUnits::Value(1., Pressure::Atmosphere).converted(Pressure::MillimetreOfMercury); - piCout << PIUnits::Value(766., Pressure::MillimetreOfMercury).converted(Pressure::Atmosphere); - - piCout << PIUnits::Value(5E-5, Time::Second).converted(Time::Hertz); - piCout << PIUnits::Value(3E-3, Time::Second).converted(Time::Hertz); - piCout << PIUnits::Value(0.8, Time::Second).converted(Time::Hertz); - piCout << PIUnits::Value(1.2, Time::Second).converted(Time::Hertz); - piCout << PIUnits::Value(1001, Time::Second).converted(Time::Hertz); - piCout << PIUnits::Value(1000001, Time::Second).converted(Time::Hertz); - // piCout << PIUnits::Value(0.2, Time::Second).converted(Time::Hertz); - // piCout << PIUnits::Value(5E-5, Time::Second).converted(Time::Hertz); - return 0; } From 34b55de561ea4ccd4e1a44fc2eb3e8ae04839b3f Mon Sep 17 00:00:00 2001 From: peri4 Date: Tue, 26 May 2026 21:37:21 +0300 Subject: [PATCH 06/35] revert RequestFunction --- libs/http_server/pihttpserver.cpp | 4 ++-- libs/main/http_common/pihttptypes.h | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/libs/http_server/pihttpserver.cpp b/libs/http_server/pihttpserver.cpp index eb4d11ec..23d8f463 100644 --- a/libs/http_server/pihttpserver.cpp +++ b/libs/http_server/pihttpserver.cpp @@ -52,7 +52,7 @@ PIHTTPServer::~PIHTTPServer() { } -bool PIHTTPServer::registerPath(const PIString & path, PIHTTP::Method method, PIHTTP::RequestFunction functor) { +bool PIHTTPServer::registerPath(const PIString & path, PIHTTP::Method method, RequestFunction functor) { Endpoint ep; if (!ep.create(path)) return false; ep.method = method; @@ -62,7 +62,7 @@ bool PIHTTPServer::registerPath(const PIString & path, PIHTTP::Method method, PI } -void PIHTTPServer::registerUnhandled(PIHTTP::RequestFunction functor) { +void PIHTTPServer::registerUnhandled(RequestFunction functor) { unhandled = functor; } diff --git a/libs/main/http_common/pihttptypes.h b/libs/main/http_common/pihttptypes.h index a105ecf0..cb8cf15e 100644 --- a/libs/main/http_common/pihttptypes.h +++ b/libs/main/http_common/pihttptypes.h @@ -210,11 +210,6 @@ public: PIP_EXPORT const char * methodName(Method m); -//! \~english Request handler used by registered routes and fallback processing. -//! \~russian Обработчик запроса, используемый зарегистрированными маршрутами и fallback-обработкой. -using RequestFunction = std::function; - - }; // namespace PIHTTP From cf67072bed0e77bf3f7f5a5beef480af0d0f0566 Mon Sep 17 00:00:00 2001 From: peri4 Date: Tue, 26 May 2026 21:38:00 +0300 Subject: [PATCH 07/35] add files --- libs/main/http_common/piserverendpoint.cpp | 125 +++++++++++++++++++++ libs/main/http_common/piserverendpoint_p.h | 72 ++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 libs/main/http_common/piserverendpoint.cpp create mode 100644 libs/main/http_common/piserverendpoint_p.h diff --git a/libs/main/http_common/piserverendpoint.cpp b/libs/main/http_common/piserverendpoint.cpp new file mode 100644 index 00000000..cfcd253a --- /dev/null +++ b/libs/main/http_common/piserverendpoint.cpp @@ -0,0 +1,125 @@ +#include "piliterals_string.h" +#include "piserverendpoint_p.h" + + +PIStringList PIHTTP::ServerEndpoint::splitPath(const PIString & path) { + auto ret = path.split("/"); + ret.removeAll({}); + return ret; +} + + +PIHTTP::ServerEndpoint::PathElement::PathElement(const PIString & reg) { + source = reg; + if (reg == "*"_a) { + type = Type::AnyOne; + } else if (reg == "**"_a) { + type = Type::AnyMany; + } else if (reg.contains('*')) { + type = Type::AnyPart; + parts = reg.split('*'); + } else if (reg.contains('{')) { + type = Type::Arguments; + int ind = 0, eind = 0, pind = 0; + for (;;) { + ind = reg.find('{', ind); + if (ind < 0) break; + eind = reg.find('}', ind + 1); + if (eind < 0) break; + arguments.insert(arguments.size_s(), reg.mid(ind + 1, eind - ind - 1)); + if (ind == 0) + parts << PIString(); + else { + if (ind > pind) + parts << reg.mid(pind, ind - pind); + else if (parts.isNotEmpty()) { + piCout << "[PIHTTP::ServerEndpoint] Warning: sequential arguments, ignoring this path!"; + type = Type::Invalid; + return; + } + } + ind = pind = eind + 1; + } + if (eind < reg.size_s() - 1) parts << reg.mid(eind + 1); + } +} + + +bool PIHTTP::ServerEndpoint::PathElement::match(const PIString & in, PIMap & ext_args) const { + // piCout << "match" << source << "with" << in; + if (type == Type::AnyOne) return true; + if (type == Type::AnyPart) { + int ind = 0; + for (const auto & m: parts) { + ind = in.find(m, ind); + if (ind < 0) return false; + } + return true; + } + if (type == Type::Arguments) { + int ind = 0, eind = 0; + for (int i = 0; i < parts.size_s(); ++i) { + const auto & m(parts[i]); + if (m.isNotEmpty()) { + ind = in.find(m, eind); + if (ind < 0) return false; + } + if (i > 0) { + ext_args[arguments.value(i - 1)] = in.mid(eind, ind - eind); + } + eind = ind + m.size_s(); + } + if (parts.size() == arguments.size()) { + ext_args[arguments.value(arguments.size_s() - 1)] = in.mid(eind); + } + return true; + } + return source == in; +} + + +uint PIHTTP::ServerEndpoint::PathElement::priority() const { + switch (type) { + case Type::Fixed: return 0x10000; break; + case Type::Arguments: return 0x1000; break; + case Type::AnyPart: return 0x100; break; + case Type::AnyOne: return 0x10; break; + case Type::AnyMany: return 0x1; break; + default: break; + } + return 0; +} + + +bool PIHTTP::ServerEndpoint::create(const PIString & p) { + path = splitPath(p); + prepared_path.clear(); + priority = 0; + for (const auto & i: path) { + PathElement pe(i); + prepared_path << pe; + path_types |= pe.type; + priority += pe.priority(); + } + return !path_types[PathElement::Type::Invalid]; +} + + +bool PIHTTP::ServerEndpoint::match(const PIStringList & in_path, PIMap & ext_args) const { + if (path_types[PathElement::Type::AnyMany]) { + int any_ind = path.indexOf("**"_a); + for (int i = 0; i < any_ind; ++i) { + if (!prepared_path[i].match(in_path[i], ext_args)) return false; + } + int si = prepared_path.size_s() - 1, ii = in_path.size_s() - 1; + for (; si > any_ind && ii >= 0; --si, --ii) { + if (!prepared_path[si].match(in_path[ii], ext_args)) return false; + } + } else { + if (in_path.size() != prepared_path.size()) return false; + for (int i = 0; i < prepared_path.size_s(); ++i) { + if (!prepared_path[i].match(in_path[i], ext_args)) return false; + } + } + return true; +} diff --git a/libs/main/http_common/piserverendpoint_p.h b/libs/main/http_common/piserverendpoint_p.h new file mode 100644 index 00000000..22e4a181 --- /dev/null +++ b/libs/main/http_common/piserverendpoint_p.h @@ -0,0 +1,72 @@ +//! \~\file piserverendpoint_p.h +//! \~\ingroup HTTP +//! \~\brief +//! \~english Shared HTTP message container types +//! \~russian Общие типы контейнеров HTTP-сообщений +/* + PIP - Platform Independent Primitives + Shared HTTP message container types + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef piserverendpoint_p_h +#define piserverendpoint_p_h + +#include "pip_export.h" +#include "pistringlist.h" + + +namespace PIHTTP { + + +struct PIP_EXPORT ServerEndpoint { + struct PIP_EXPORT PathElement { + enum class Type { + Invalid = 0x01, + Fixed = 0x02, + Arguments = 0x04, + AnyOne = 0x08, + AnyPart = 0x10, + AnyMany = 0x20 + }; + + Type type = Type::Fixed; + PIString source; + PIStringList parts; + PIMap arguments; + + PathElement(const PIString & reg = {}); + + bool match(const PIString & in, PIMap & ext_args) const; + uint priority() const; + }; + + PIStringList path; + PIFlags path_types; + PIVector prepared_path; + uint priority = 0; + + bool create(const PIString & p); + bool match(const PIStringList & in_path, PIMap & ext_args) const; + + static PIStringList splitPath(const PIString & path); +}; + + +}; // namespace PIHTTP + + +#endif From 7b5c5d939b75d8cb8623f8c0c4701b45a66fc66c Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 27 May 2026 15:44:08 +0300 Subject: [PATCH 08/35] Add parents (de)serialization in pip_cmg --- utils/code_model_generator/getter.cpp | 16 ++++++++++++++++ utils/code_model_generator/json.cpp | 20 ++++++++++++-------- utils/code_model_generator/metainfo.cpp | 4 ++++ utils/code_model_generator/stream.cpp | 12 ++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/utils/code_model_generator/getter.cpp b/utils/code_model_generator/getter.cpp index 5f76f16c..c553e0f4 100644 --- a/utils/code_model_generator/getter.cpp +++ b/utils/code_model_generator/getter.cpp @@ -24,6 +24,10 @@ void writeGetterTypeMembers(Runtime & rt, const PICodeParser::Entity * e, PIString var_prefix) { if (var_prefix.isNotEmpty() && !var_prefix.endsWith('.')) var_prefix += "."; + for (const PICodeParser::Entity * p: e->parents) { + if (p->is_anonymous) continue; + writeGetterTypeMembers(rt, p, var_prefix); + } PISet used_id; for (const PICodeParser::Member & m: e->members) { if (m.is_type_ptr || !m.dims.isEmpty() || (m.visibility != PICodeParser::Public)) continue; @@ -43,6 +47,10 @@ void writeGetterTypeMembers(Runtime & rt, const PICodeParser::Entity * e, PIStri void writeGetterValueMembers(Runtime & rt, const PICodeParser::Entity * e, PIString var_prefix) { if (var_prefix.isNotEmpty() && !var_prefix.endsWith('.')) var_prefix += "."; + for (const PICodeParser::Entity * p: e->parents) { + if (p->is_anonymous) continue; + writeGetterValueMembers(rt, p, var_prefix); + } PISet used_id; for (const PICodeParser::Member & m: e->members) { if (m.is_type_ptr || !m.dims.isEmpty() || (m.visibility != PICodeParser::Public)) continue; @@ -65,6 +73,10 @@ void writeGetterValueMembers(Runtime & rt, const PICodeParser::Entity * e, PIStr void writeGetterOffsetMembers(Runtime & rt, const PICodeParser::Entity * e, PIString entity_name, PIString var_prefix) { if (var_prefix.isNotEmpty() && !var_prefix.endsWith('.')) var_prefix += "."; + for (const PICodeParser::Entity * p: e->parents) { + if (p->is_anonymous) continue; + writeGetterOffsetMembers(rt, p, entity_name, var_prefix); + } PISet used_id; for (const PICodeParser::Member & m: e->members) { if (m.is_type_ptr || !m.dims.isEmpty() || m.isBitfield() || (m.visibility != PICodeParser::Public)) continue; @@ -117,5 +129,9 @@ bool needClassGetter(const PICodeParser::Entity * e) { if (m.attributes[PICodeParser::Static]) continue; return true; } + for (const PICodeParser::Entity * p: e->parents) { + if (p->is_anonymous) continue; + if (needClassGetter(p)) return true; + } return false; } diff --git a/utils/code_model_generator/json.cpp b/utils/code_model_generator/json.cpp index 16ae6b97..07f547e9 100644 --- a/utils/code_model_generator/json.cpp +++ b/utils/code_model_generator/json.cpp @@ -24,6 +24,10 @@ bool writeClassJSONMembersOut(Runtime & rt, const PICodeParser::Entity * e, PIString var_prefix) { if (var_prefix.isNotEmpty() && !var_prefix.endsWith('.')) var_prefix += "."; + for (const PICodeParser::Entity * p: e->parents) { + if (p->is_anonymous) continue; + writeClassJSONMembersOut(rt, p, var_prefix); + } PIVector ml; for (const PICodeParser::Member & m: e->members) { if (m.is_type_ptr || (m.visibility != PICodeParser::Public)) continue; @@ -57,16 +61,16 @@ bool writeClassJSONMembersOut(Runtime & rt, const PICodeParser::Entity * e, PISt if (is_union) break; } if (is_union) return true; - /*for (const PICodeParser::Entity * ce: e->children) { - if (!ce->is_anonymous) continue; - if (!writeClassJSONMembersOut(rt, ce)) return false; - }*/ return true; } bool writeClassJSONMembersIn(Runtime & rt, const PICodeParser::Entity * e, PIString var_prefix) { if (var_prefix.isNotEmpty() && !var_prefix.endsWith('.')) var_prefix += "."; + for (const PICodeParser::Entity * p: e->parents) { + if (p->is_anonymous) continue; + writeClassJSONMembersIn(rt, p, var_prefix); + } PIVector ml; for (const PICodeParser::Member & m: e->members) { if (m.is_type_ptr || (m.visibility != PICodeParser::Public)) continue; @@ -105,10 +109,6 @@ bool writeClassJSONMembersIn(Runtime & rt, const PICodeParser::Entity * e, PIStr if (is_union) break; } if (is_union) return true; - /*for (const PICodeParser::Entity * ce: e->children) { - if (!ce->is_anonymous) continue; - if (!writeClassJSONMembersIn(rt, ce)) return false; - }*/ return true; } @@ -121,6 +121,10 @@ bool needClassJSON(const PICodeParser::Entity * e) { if (m.meta.value("id") == "-") continue; return true; } + for (const PICodeParser::Entity * p: e->parents) { + if (p->is_anonymous) continue; + if (needClassJSON(p)) return true; + } return false; } diff --git a/utils/code_model_generator/metainfo.cpp b/utils/code_model_generator/metainfo.cpp index c7664d7c..54b1efe4 100644 --- a/utils/code_model_generator/metainfo.cpp +++ b/utils/code_model_generator/metainfo.cpp @@ -22,6 +22,10 @@ void writeClassInfoMembers(Runtime & rt, const PICodeParser::Entity * e, PIString var_prefix) { if (var_prefix.isNotEmpty() && !var_prefix.endsWith('.')) var_prefix += "."; + for (const PICodeParser::Entity * p: e->parents) { + if (p->is_anonymous) continue; + writeClassInfoMembers(rt, p, var_prefix); + } for (const PICodeParser::Member & m: e->members) { auto type = findEntity(rt, m.type); if (type) { diff --git a/utils/code_model_generator/stream.cpp b/utils/code_model_generator/stream.cpp index f560d29e..b04e15cc 100644 --- a/utils/code_model_generator/stream.cpp +++ b/utils/code_model_generator/stream.cpp @@ -24,6 +24,10 @@ bool writeClassStreamMembersOut(Runtime & rt, const PICodeParser::Entity * e, int & cnt, bool simple, PIString var_prefix) { if (var_prefix.isNotEmpty() && !var_prefix.endsWith('.')) var_prefix += "."; + for (const PICodeParser::Entity * p: e->parents) { + if (p->is_anonymous) continue; + if (!writeClassStreamMembersOut(rt, p, cnt, simple, var_prefix)) return false; + } PIVector ml; for (const PICodeParser::Member & m: e->members) { if (m.is_type_ptr || (m.visibility != PICodeParser::Public)) continue; @@ -83,6 +87,10 @@ bool writeClassStreamMembersOut(Runtime & rt, const PICodeParser::Entity * e, in bool writeClassStreamMembersIn(Runtime & rt, const PICodeParser::Entity * e, int & cnt, bool simple, PIString var_prefix) { if (var_prefix.isNotEmpty() && !var_prefix.endsWith('.')) var_prefix += "."; + for (const PICodeParser::Entity * p: e->parents) { + if (p->is_anonymous) continue; + if (!writeClassStreamMembersIn(rt, p, cnt, simple, var_prefix)) return false; + } PIVector ml; for (const PICodeParser::Member & m: e->members) { if (m.is_type_ptr || (m.visibility != PICodeParser::Public)) continue; @@ -166,6 +174,10 @@ bool needClassStream(const PICodeParser::Entity * e) { if (m.meta.value("id") == "-") continue; return true; } + for (const PICodeParser::Entity * p: e->parents) { + if (p->is_anonymous) continue; + if (needClassStream(p)) return true; + } return false; } From 533f9cc640d509a02b92eeffa68b901eb1de1a9b Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 27 May 2026 16:37:04 +0300 Subject: [PATCH 09/35] parse only public inherited classes --- libs/main/code/picodeparser.cpp | 34 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/libs/main/code/picodeparser.cpp b/libs/main/code/picodeparser.cpp index 545059e3..a7a5a071 100644 --- a/libs/main/code/picodeparser.cpp +++ b/libs/main/code/picodeparser.cpp @@ -577,28 +577,36 @@ bool PICodeParser::parseFileContent(PIString & fc, bool main) { PICodeParser::Entity * PICodeParser::parseClassDeclaration(const PIString & fc) { - static const PIString s_ss = PIStringAscii(" "); - static const PIString s_M = PIStringAscii("$M"); - static const PIString s_class = PIStringAscii("class"); - PIString cd = fc.trimmed().removeAll('\n').replaceAll('\t', ' ').replaceAll(s_ss, ' '), pn; + static const PIString s_ss = PIStringAscii(" "); + static const PIString s_M = PIStringAscii("$M"); + static const PIString s_class = PIStringAscii("class"); + static const PIString s_public = PIStringAscii("public"); + PIString cd = fc.trimmed().removeAll('\n').replaceAll('\t', ' ').replaceAll(s_ss, ' '), pn; MetaMap meta; int ind = cd.find(s_M); if (ind >= 0) { meta = tmp_meta.value(cd.takeMid(ind, 5)); cd.replaceAll(s_ss, ' '); } + PIString typename_ = cd.left(6).trim(); + bool is_class = typename_ == s_class; + ind = cd.find(':'); // piCout << "found class <****\n" << cd << "\n****>"; - ind = cd.find(':'); PIVector parents; if (ind > 0) { PIStringList pl = cd.takeMid(ind + 1).trim().split(','); cd.cutRight(1); Entity * pe = 0; for (const auto & p: pl) { - if (p.contains(' ')) - pn = p.mid(p.find(' ') + 1); - else + PIString access; + if (p.contains(' ')) { + access = p.left(p.find(' ')).trim(); + pn = p.mid(p.find(' ') + 1); + } else { pn = p; + } + bool is_public = access.isEmpty() ? !is_class : access == s_public; + if (!is_public) continue; pe = findEntityByName(pn); if (pe == 0) ; //{piCout << "Error: can`t find" << pn;} @@ -606,12 +614,10 @@ PICodeParser::Entity * PICodeParser::parseClassDeclaration(const PIString & fc) parents << pe; } } - PIString typename_ = cd.left(6).trim(); - bool is_class = typename_ == s_class; - Visibility vis = cur_def_vis; - cur_def_vis = (is_class ? Private : Public); - PIString cn = cd.mid(6).trim(); - bool is_anonymous = cn.isEmpty(); + Visibility vis = cur_def_vis; + cur_def_vis = (is_class ? Private : Public); + PIString cn = cd.mid(6).trim(); + bool is_anonymous = cn.isEmpty(); if (cn.isEmpty()) cn = PIStringAscii("'; // piCout << "found " << typename_ << cn; Entity * e = new Entity(); From 38d09e272cafc6606dc86aff1fa70c3c51a4f80e Mon Sep 17 00:00:00 2001 From: peri4 Date: Thu, 28 May 2026 20:26:49 +0300 Subject: [PATCH 10/35] MQTT seems to work 1. subscribe now similar to HTTP server, with lambda 2. subscribe topic syntax support all HTTP features as path arguments and wildcards 3. event received() changed to receivedUnhandled() for unhandled messages (should never be called in proper work) 4. internal logic got more complicated, several endpoints may be serviced by single MQTT topic, so nested Map used --- libs/main/mqtt_client/pimqttclient.h | 35 +++++- libs/mqtt_client/pimqttclient.cpp | 171 +++++++++++++++++++++++++-- main.cpp | 111 ++++++++++------- 3 files changed, 261 insertions(+), 56 deletions(-) diff --git a/libs/main/mqtt_client/pimqttclient.h b/libs/main/mqtt_client/pimqttclient.h index 1e12c47d..1a1ae5d2 100644 --- a/libs/main/mqtt_client/pimqttclient.h +++ b/libs/main/mqtt_client/pimqttclient.h @@ -41,24 +41,38 @@ public: Client(); virtual ~Client(); + //! \~english Request handler used by registered routes and fallback processing. + //! \~russian Обработчик запроса, используемый зарегистрированными маршрутами и fallback-обработкой. + using MessageFunction = std::function; + void setConnectTimeout(PISystemTime time) { connect_timeout = time; } void connect(const PIString & address, const PIString & client, const PIString & username = {}, const PIString & password = {}); void disconnect(); - void subscribe(const PIString & topic, QoS qos = QoS::Level1); + + void subscribe(const PIString & topic, MessageFunction functor, QoS qos = QoS::Level1); + + template + void + subscribe(const PIString & topic, T * o, PIMQTT::MessageMutable (T::*function)(const PIMQTT::MessageConst &), QoS qos = QoS::Level1) { + subscribe(topic, [o, function](const PIMQTT::MessageConst & m) { return (o->*function)(m); }, qos); + } + void unsubscribe(const PIString & topic); + void unsubscribeAll(); void publish(const PIString & topic, const PIByteArray & msg, QoS qos = QoS::Level0); void publish(const MessageConst & msg); - void unsubscribeAll() { unsubscribe("#"); } bool isConnecting() const { return m_status == Connecting; } bool isConnected() const { return m_status == Connected; } EVENT0(connected); EVENT1(disconnected, PIMQTT::Error, code); - EVENT1(received, PIMQTT::MessageConst, message); + EVENT1(receivedUnhandled, PIMQTT::MessageConst, message); + + struct Endpoint; private: NO_COPY_CLASS(Client) @@ -79,23 +93,34 @@ private: }; struct Subscribe { PIString topic; + MessageFunction functor; QoS qos; }; void mqtt_connectionLost(); void mqtt_deliveryComplete(int token); - void mqtt_messageArrived(const MessageConst & msg); + void mqtt_messageArrived(MessageMutable & msg); + + PIString registerSubscribe(const Subscribe & sub); + PIString unregisterSubscribe(const PIString & mqtt_topic); + void unregisterAll(); void connectInternal(const ConnectInfo & ci); void disconnectInternal(); void publishInternal(const MessageConst & m); void subscribeInternal(const Subscribe & sub); - void unsubscribeInternal(const PIString & topic); + void unsubscribeInternal(const PIString & mqtt_topic); void destroy(); void changeStatus(Status s); void run(); + // from HTTP format + static PIString convertTopic2MQTT(const PIString & topic); + + // from MQTT format + static PIString convertTopic2HTTP(const PIString & topic); + std::atomic_int m_status = {Idle}; std::atomic_bool is_destoying = {false}; PISystemTime connect_timeout = 10_s; diff --git a/libs/mqtt_client/pimqttclient.cpp b/libs/mqtt_client/pimqttclient.cpp index 6518d7b2..ee28ab69 100644 --- a/libs/mqtt_client/pimqttclient.cpp +++ b/libs/mqtt_client/pimqttclient.cpp @@ -20,6 +20,31 @@ #include "pimqttclient.h" #include "MQTTClient.h" +#include "piliterals_string.h" +#include "piserverendpoint_p.h" + + +struct PIMQTT::Client::Endpoint: public PIHTTP::ServerEndpoint { + PIMQTT::Client::MessageFunction function; +}; + +struct EndpointsStorage { + PIMap>> prepared; // [priority][topic] -> endpoints + PIMap topics_binded; // [topic] -> count +}; + + +PIString topicFromEndpoint(const PIMQTT::Client::Endpoint & e) { + PIStringList ret; + for (const auto & i: e.prepared_path) { + switch (i.type) { + case PIHTTP::ServerEndpoint::PathElement::Type::Fixed: ret << i.source; break; + case PIHTTP::ServerEndpoint::PathElement::Type::AnyMany: ret << "#"_a; break; + default: ret << "+"_a; break; + } + } + return ret.join('/'); +} STATIC_INITIALIZER_BEGIN @@ -32,6 +57,8 @@ PRIVATE_DEFINITION_START(PIMQTT::Client) MQTTClient client = nullptr; bool connected = false; + PIProtectedVariable endpoints; + static void connectionLost_callback(void * context, char *) { ((PIMQTT::Client *)context)->mqtt_connectionLost(); } @@ -88,9 +115,9 @@ void PIMQTT::Client::disconnect() { } -void PIMQTT::Client::subscribe(const PIString & topic, QoS qos) { +void PIMQTT::Client::subscribe(const PIString & topic, MessageFunction functor, QoS qos) { if (is_destoying) return; - worker->enqueueTask([this, topic, qos] { subscribeInternal({topic, qos}); }); + worker->enqueueTask([this, topic, functor, qos] { subscribeInternal({topic, functor, qos}); }); } @@ -115,8 +142,15 @@ void PIMQTT::Client::publish(const MessageConst & msg) { } +void PIMQTT::Client::unsubscribeAll() { + unsubscribe("#"); + unregisterAll(); +} + + void PIMQTT::Client::mqtt_connectionLost() { piCoutObj << "mqtt_connectionLost"; + unregisterAll(); PRIVATE->connected = false; changeStatus(Idle); disconnected(Error::ServerUnavailable); @@ -126,9 +160,110 @@ void PIMQTT::Client::mqtt_connectionLost() { void PIMQTT::Client::mqtt_deliveryComplete(int token) {} -void PIMQTT::Client::mqtt_messageArrived(const MessageConst & msg) { +void PIMQTT::Client::mqtt_messageArrived(MessageMutable & msg) { + PIStringList in_path = msg.topicList(); + PIMQTT::Client::MessageFunction function; piCoutObj << "mqtt_messageArrived"; - received(msg); + { + bool found = false; + auto ref = PRIVATE->endpoints.getRef(); + auto pit = ref->prepared.makeReverseIterator(); + while (pit.next()) { // by priority + + auto tit = pit.value().makeIterator(); + while (tit.next()) { // by MQTT topic + + for (const auto & ep: tit.value()) { + PIMap ext_args; + if (ep.match(in_path, ext_args)) { + msg.pathArguments() = ext_args; + function = ep.function; + found = true; + break; + } + } + if (found) break; + } + if (found) break; + } + } + if (function) + function(msg); + else + receivedUnhandled(msg); +} + + +PIString PIMQTT::Client::registerSubscribe(const Subscribe & sub) { + Endpoint ep; + ep.create(convertTopic2HTTP(sub.topic)); + ep.function = sub.functor; + PIString topic = topicFromEndpoint(ep); + if (topic.isEmpty()) { + piCoutObj << "Warning: subscribe to empty topic, ignore"; + return {}; + } + piCout << sub.topic << "->" << topic << ep.priority; + bool is_new_topic = false; + auto ref = PRIVATE->endpoints.getRef(); + auto & eps_by_topic(ref->prepared[ep.priority][topic]); + for (const auto & i: eps_by_topic) { + if (i.path == ep.path) { + piCoutObj << "Warning: subscribe duplicate path, ignore"; + return {}; + } + } + eps_by_topic << ep; + auto & counter(ref->topics_binded[topic]); + is_new_topic = counter == 0; + ++counter; + if (!is_new_topic) return {}; + return topic; +} + + +PIString PIMQTT::Client::unregisterSubscribe(const PIString & mqtt_topic) { + Endpoint ep; + ep.create(convertTopic2HTTP(mqtt_topic)); + PIString topic = topicFromEndpoint(ep); + if (topic.isEmpty()) { + piCoutObj << "Warning: unsubscribe from empty topic, ignore"; + return {}; + } + piCout << mqtt_topic << "->" << topic << ep.priority; + auto ref = PRIVATE->endpoints.getRef(); + auto pit = ref->prepared.makeIterator(); + while (pit.next()) { // by priority + + auto tit = pit.value().makeIterator(); + while (tit.next()) { // by MQTT topic + + auto & eps(tit.value()); + for (int i = 0; i < eps.size_s(); ++i) { + if (eps[i].path == ep.path) { + eps.remove(i); + auto & counter(ref->topics_binded[tit.key()]); + --counter; + PIString ret; + if (counter <= 0) ret = tit.key(); + if (eps.isEmpty()) { + piCout << "remove topics" << tit.key(); + pit.value().remove(tit.key()); + } + return ret; + } + } + } + } + piCoutObj << "Warning: unsubscribe from" << mqtt_topic << ", topic not found"; + return {}; +} + + +void PIMQTT::Client::unregisterAll() { + auto ref = PRIVATE->endpoints.getRef(); + ref->prepared.clear(); + ref->topics_binded.clear(); } @@ -189,15 +324,27 @@ void PIMQTT::Client::publishInternal(const MessageConst & m) { void PIMQTT::Client::subscribeInternal(const Subscribe & sub) { if (!PRIVATE->client) return; - int ret = MQTTClient_subscribe(PRIVATE->client, sub.topic.dataUTF8(), static_cast(sub.qos)); + piCout << ""; + piCout << "subscribeInternal" << sub.topic; + PIString topic = registerSubscribe(sub); + if (topic.isEmpty()) return; + piCout << "NEW" << topic; // << PRIVATE->endpoints.getRef()->size(); + int ret = MQTTClient_subscribe(PRIVATE->client, topic.dataUTF8(), static_cast(sub.qos)); if (ret != MQTTCLIENT_SUCCESS) { - piCoutObj << "Failed to subscribe" << sub.topic << ", code" << ret; + piCoutObj << "Failed to subscribe" << topic << ", code" << ret; + + return; } } -void PIMQTT::Client::unsubscribeInternal(const PIString & topic) { +void PIMQTT::Client::unsubscribeInternal(const PIString & mqtt_topic) { if (!PRIVATE->client) return; + piCout << ""; + piCout << "unsubscribeInternal" << mqtt_topic; + PIString topic = unregisterSubscribe(mqtt_topic); + if (topic.isEmpty()) return; + piCout << "DEL" << topic; // << PRIVATE->endpoints.getRef()->size(); int ret = MQTTClient_unsubscribe(PRIVATE->client, topic.dataUTF8()); if (ret != MQTTCLIENT_SUCCESS) { piCoutObj << "Failed to unsubscribe" << topic << ", code" << ret; @@ -206,6 +353,7 @@ void PIMQTT::Client::unsubscribeInternal(const PIString & topic) { void PIMQTT::Client::destroy() { + unregisterAll(); if (!PRIVATE->client) return; if (PRIVATE->connected) MQTTClient_disconnect(PRIVATE->client, 1000); MQTTClient_destroy(&PRIVATE->client); @@ -217,3 +365,12 @@ void PIMQTT::Client::destroy() { void PIMQTT::Client::changeStatus(Status s) { m_status = s; } + +PIString PIMQTT::Client::convertTopic2MQTT(const PIString & topic) { + return topic.replacedAll("**", '#').replacedAll('*', '+'); +} + + +PIString PIMQTT::Client::convertTopic2HTTP(const PIString & topic) { + return topic.replacedAll('#', "**").replacedAll('+', '*'); +} diff --git a/main.cpp b/main.cpp index f92b7159..95ac62cd 100644 --- a/main.cpp +++ b/main.cpp @@ -19,63 +19,86 @@ MessageMutable createMessage(Code c, const char * path, const MessageConst & msg return MessageMutable().setCode(c); }; int main(int argc, char * argv[]) { - piCout << "start ..."; - PIHTTPServer server; - server.registerUnhandled([](const MessageConst & msg) { return createMessage(Code::BadRequest, "unhadled", msg); }); - server.registerPath("api/v1/status", Method::Get, [](const MessageConst & msg) { - return createMessage(Code::Accepted, "api/v1/status", msg); - }); - server.registerPath("api/v1/plugins", Method::Get, [](const MessageConst & msg) { - return createMessage(Code::Accepted, "api/v1/plugins", msg); - }); - server.registerPath("api/v1/task-status", Method::Get, [](const MessageConst & msg) { - return createMessage(Code::Accepted, "api/v1/task-status", msg); - }); - server.registerPath("api/v1/task/{taskID}/status", Method::Get, [](const MessageConst & msg) { - return createMessage(Code::Accepted, "api/v1/task/{taskID}/status", msg); - }); - server.registerPath("api/v1/bort/list", Method::Get, [](const MessageConst & msg) { - return createMessage(Code::Accepted, "api/v1/bort/list", msg); - }); - server.registerPath("api/v1/all", Method::Get, [](const MessageConst & msg) { - return createMessage(Code::Accepted, "api/v1/all", msg); - }); - server.registerPath("api/v1/all/bort{A}/f", Method::Get, [](const MessageConst & msg) { - return createMessage(Code::Accepted, "api/v1/all/*/f", msg); - }); - server.registerPath("api/v1/all2/**", Method::Get, [](const MessageConst & msg) { - return createMessage(Code::Accepted, "api/v1/all2/**", msg); - }); - server.listenAll(12345); + // piCout << "start ..."; + // PIHTTPServer server; + // server.registerUnhandled([](const MessageConst & msg) { return createMessage(Code::BadRequest, "unhadled", msg); }); + // server.registerPath("api/v1/status", Method::Get, [](const MessageConst & msg) { + // return createMessage(Code::Accepted, "api/v1/status", msg); + // }); + // server.registerPath("api/v1/plugins", Method::Get, [](const MessageConst & msg) { + // return createMessage(Code::Accepted, "api/v1/plugins", msg); + // }); + // server.registerPath("api/v1/task-status", Method::Get, [](const MessageConst & msg) { + // return createMessage(Code::Accepted, "api/v1/task-status", msg); + // }); + // server.registerPath("api/v1/task/{taskID}/status", Method::Get, [](const MessageConst & msg) { + // return createMessage(Code::Accepted, "api/v1/task/{taskID}/status", msg); + // }); + // server.registerPath("api/v1/bort/list", Method::Get, [](const MessageConst & msg) { + // return createMessage(Code::Accepted, "api/v1/bort/list", msg); + // }); + // server.registerPath("api/v1/all", Method::Get, [](const MessageConst & msg) { + // return createMessage(Code::Accepted, "api/v1/all", msg); + // }); + // server.registerPath("api/v1/all/bort{A}/f", Method::Get, [](const MessageConst & msg) { + // return createMessage(Code::Accepted, "api/v1/all/*/f", msg); + // }); + // server.registerPath("api/v1/all2/**", Method::Get, [](const MessageConst & msg) { + // return createMessage(Code::Accepted, "api/v1/all2/**", msg); + // }); + // server.listenAll(12345); - kbd.enableExitCapture('Q'); - WAIT_FOR_EXIT - piCout << "exiting ..."; - server.stop(); + // kbd.enableExitCapture('Q'); + // WAIT_FOR_EXIT + // piCout << "exiting ..."; + // server.stop(); - return 0; + // return 0; - PISystemMonitor mon; - mon.startOnSelf(); - PISystemMonitor::totalRAM(); - 2_s .sleep(); + // PISystemMonitor mon; + // mon.startOnSelf(); + // PISystemMonitor::totalRAM(); + // 2_s .sleep(); - return 0; + // return 0; PIMQTT::Client cl; cl.setConnectTimeout(2_s); + CONNECTL(&cl, connected, [&cl] { piCout << "connected"; - cl.subscribe("/zigbee2mqtt"); - cl.subscribe("/zigbee2mqtt/+"); - cl.unsubscribe("/zigbee2mqtt"); - cl.publish("/zigbee2mqtt/abc", "hello from PIP"_a.toAscii()); + // cl.subscribe("api/v1/plugins"); + // cl.subscribe("api/v1/task-status"); + // cl.subscribe("api/v1/*/{taskID}/status"); + // cl.subscribe("api/v1/bort/list"); + // cl.subscribe("api/v1/all"); + cl.subscribe("api/v1/all/bort{A}/f", [](const PIMQTT::MessageConst & msg) { + piCout << "1" << msg.topicList() << msg.pathArguments() << msg.body().size(); + }); + cl.subscribe("api/v1/all/task{T}/f", [](const PIMQTT::MessageConst & msg) { + piCout << "2" << msg.topicList() << msg.pathArguments() << msg.body().size(); + }); + cl.subscribe("api/v1/all/*/f", [](const PIMQTT::MessageConst & msg) { + piCout << "3" << msg.topicList() << msg.pathArguments() << msg.body().size(); + }); + cl.subscribe("api/v1/all2/**", [](const PIMQTT::MessageConst & msg) { + piCout << "4" << msg.topicList() << msg.pathArguments() << msg.body().size(); + }); + // cl.subscribe("/zigbee2mqtt"); + // cl.subscribe("/zigbee2mqtt/+/status/"); + // cl.subscribe("/zigbee2mqtt/*/status/"); + // cl.subscribe("test/#"); + // cl.subscribe("#"); + // cl.unsubscribe("/zigbee2mqtt"); + // cl.unsubscribe("api/v1/all/bort{A}/f"); + // cl.unsubscribe("api/v1/all/*/f"); + // cl.publish("/zigbee2mqtt/abc", "hello from PIP"_a.toAscii()); }); CONNECTL(&cl, disconnected, [&cl](PIMQTT::Error code) { piCout << "disconnected code" << (int)code; cl.connect("localhost", "PIP"); }); - CONNECTL(&cl, received, [](const PIMQTT::MessageConst & message) { - piCout << "received" << message.topic() << message.pathArguments() << message.payload().size(); + CONNECTL(&cl, receivedUnhandled, [](const PIMQTT::MessageConst & message) { + piCout << "receivedUnhandled" << message.topic() << message.pathArguments() << message.payload().size(); }); cl.connect("localhost", "PIP"); From afb4ae8126397e51dd38a99dcd4438663e953bed Mon Sep 17 00:00:00 2001 From: peri4 Date: Fri, 29 May 2026 09:56:29 +0300 Subject: [PATCH 11/35] MQTT ready to use change subscription logic - now keep subscriptions independently from connecting state. No unregisters on disconnect, but resubscriptions on connect. So one-time subscription on app start and just connect() on lost connection - all subscriptions keeps --- libs/main/mqtt_client/pimqttclient.h | 3 +- libs/mqtt_client/pimqttclient.cpp | 89 ++++++++++++++++++---------- main.cpp | 23 ++++--- 3 files changed, 70 insertions(+), 45 deletions(-) diff --git a/libs/main/mqtt_client/pimqttclient.h b/libs/main/mqtt_client/pimqttclient.h index 1a1ae5d2..6d32bf27 100644 --- a/libs/main/mqtt_client/pimqttclient.h +++ b/libs/main/mqtt_client/pimqttclient.h @@ -50,7 +50,6 @@ public: void connect(const PIString & address, const PIString & client, const PIString & username = {}, const PIString & password = {}); void disconnect(); - void subscribe(const PIString & topic, MessageFunction functor, QoS qos = QoS::Level1); template @@ -68,6 +67,8 @@ public: bool isConnecting() const { return m_status == Connecting; } bool isConnected() const { return m_status == Connected; } + PIStringList usedTopics() const; + EVENT0(connected); EVENT1(disconnected, PIMQTT::Error, code); EVENT1(receivedUnhandled, PIMQTT::MessageConst, message); diff --git a/libs/mqtt_client/pimqttclient.cpp b/libs/mqtt_client/pimqttclient.cpp index ee28ab69..60ffb958 100644 --- a/libs/mqtt_client/pimqttclient.cpp +++ b/libs/mqtt_client/pimqttclient.cpp @@ -28,9 +28,14 @@ struct PIMQTT::Client::Endpoint: public PIHTTP::ServerEndpoint { PIMQTT::Client::MessageFunction function; }; +struct TopicUsage { + int counter = 0; + PIMQTT::QoS qos = PIMQTT::QoS::Level1; +}; + struct EndpointsStorage { PIMap>> prepared; // [priority][topic] -> endpoints - PIMap topics_binded; // [topic] -> count + PIMap topics_binded; // [topic] -> TopicUsage }; @@ -117,13 +122,21 @@ void PIMQTT::Client::disconnect() { void PIMQTT::Client::subscribe(const PIString & topic, MessageFunction functor, QoS qos) { if (is_destoying) return; - worker->enqueueTask([this, topic, functor, qos] { subscribeInternal({topic, functor, qos}); }); + Subscribe sub{topic, functor, qos}; + // piCout << "subscribe" << topic; + PIString mqtt_topic = registerSubscribe(sub); + if (mqtt_topic.isEmpty()) return; + sub.topic = mqtt_topic; + worker->enqueueTask([this, sub] { subscribeInternal(sub); }); } void PIMQTT::Client::unsubscribe(const PIString & topic) { if (is_destoying) return; - worker->enqueueTask([this, topic] { unsubscribeInternal(topic); }); + // piCout << "unsubscribe" << topic; + PIString mqtt_topic = unregisterSubscribe(topic); + if (mqtt_topic.isEmpty()) return; + worker->enqueueTask([this, mqtt_topic] { unsubscribeInternal(mqtt_topic); }); } @@ -142,15 +155,27 @@ void PIMQTT::Client::publish(const MessageConst & msg) { } +PIStringList PIMQTT::Client::usedTopics() const { + return PRIVATE->endpoints.getRef()->topics_binded.keys(); +} + + void PIMQTT::Client::unsubscribeAll() { - unsubscribe("#"); + { + auto ref = PRIVATE->endpoints.getRef(); + auto tit = ref->topics_binded.makeIterator(); + while (tit.next()) { + if (tit.value().counter <= 0) continue; + PIString mqtt_topic = tit.key(); + worker->enqueueTask([this, mqtt_topic] { unsubscribeInternal(mqtt_topic); }); + } + } unregisterAll(); } void PIMQTT::Client::mqtt_connectionLost() { - piCoutObj << "mqtt_connectionLost"; - unregisterAll(); + // piCoutObj << "mqtt_connectionLost"; PRIVATE->connected = false; changeStatus(Idle); disconnected(Error::ServerUnavailable); @@ -163,7 +188,7 @@ void PIMQTT::Client::mqtt_deliveryComplete(int token) {} void PIMQTT::Client::mqtt_messageArrived(MessageMutable & msg) { PIStringList in_path = msg.topicList(); PIMQTT::Client::MessageFunction function; - piCoutObj << "mqtt_messageArrived"; + // piCoutObj << "mqtt_messageArrived"; { bool found = false; auto ref = PRIVATE->endpoints.getRef(); @@ -203,7 +228,7 @@ PIString PIMQTT::Client::registerSubscribe(const Subscribe & sub) { piCoutObj << "Warning: subscribe to empty topic, ignore"; return {}; } - piCout << sub.topic << "->" << topic << ep.priority; + // piCout << sub.topic << "->" << topic << ep.priority; bool is_new_topic = false; auto ref = PRIVATE->endpoints.getRef(); auto & eps_by_topic(ref->prepared[ep.priority][topic]); @@ -214,10 +239,11 @@ PIString PIMQTT::Client::registerSubscribe(const Subscribe & sub) { } } eps_by_topic << ep; - auto & counter(ref->topics_binded[topic]); - is_new_topic = counter == 0; - ++counter; + auto & usage(ref->topics_binded[topic]); + is_new_topic = usage.counter == 0; + ++usage.counter; if (!is_new_topic) return {}; + usage.qos = sub.qos; return topic; } @@ -230,7 +256,7 @@ PIString PIMQTT::Client::unregisterSubscribe(const PIString & mqtt_topic) { piCoutObj << "Warning: unsubscribe from empty topic, ignore"; return {}; } - piCout << mqtt_topic << "->" << topic << ep.priority; + // piCout << mqtt_topic << "->" << topic << ep.priority; auto ref = PRIVATE->endpoints.getRef(); auto pit = ref->prepared.makeIterator(); while (pit.next()) { // by priority @@ -242,12 +268,15 @@ PIString PIMQTT::Client::unregisterSubscribe(const PIString & mqtt_topic) { for (int i = 0; i < eps.size_s(); ++i) { if (eps[i].path == ep.path) { eps.remove(i); - auto & counter(ref->topics_binded[tit.key()]); - --counter; + auto & usage(ref->topics_binded[tit.key()]); + --usage.counter; PIString ret; - if (counter <= 0) ret = tit.key(); + if (usage.counter <= 0) { + ret = tit.key(); + ref->topics_binded.remove(tit.key()); + } if (eps.isEmpty()) { - piCout << "remove topics" << tit.key(); + // piCout << "remove topics" << tit.key(); pit.value().remove(tit.key()); } return ret; @@ -295,6 +324,12 @@ void PIMQTT::Client::connectInternal(const ConnectInfo & ci) { return; } PRIVATE->connected = true; + PIMap topics_binded; + { topics_binded = PRIVATE->endpoints.getRef()->topics_binded; } + auto it = topics_binded.makeIterator(); + while (it.next()) { + if (it.value().counter > 0) subscribeInternal({it.key(), nullptr, it.value().qos}); + } changeStatus(Connected); connected(); } @@ -324,15 +359,10 @@ void PIMQTT::Client::publishInternal(const MessageConst & m) { void PIMQTT::Client::subscribeInternal(const Subscribe & sub) { if (!PRIVATE->client) return; - piCout << ""; - piCout << "subscribeInternal" << sub.topic; - PIString topic = registerSubscribe(sub); - if (topic.isEmpty()) return; - piCout << "NEW" << topic; // << PRIVATE->endpoints.getRef()->size(); - int ret = MQTTClient_subscribe(PRIVATE->client, topic.dataUTF8(), static_cast(sub.qos)); + // piCout << "subscribeInternal" << sub.topic; + int ret = MQTTClient_subscribe(PRIVATE->client, sub.topic.dataUTF8(), static_cast(sub.qos)); if (ret != MQTTCLIENT_SUCCESS) { - piCoutObj << "Failed to subscribe" << topic << ", code" << ret; - + piCoutObj << "Failed to subscribe" << sub.topic << ", code" << ret; return; } } @@ -340,20 +370,15 @@ void PIMQTT::Client::subscribeInternal(const Subscribe & sub) { void PIMQTT::Client::unsubscribeInternal(const PIString & mqtt_topic) { if (!PRIVATE->client) return; - piCout << ""; - piCout << "unsubscribeInternal" << mqtt_topic; - PIString topic = unregisterSubscribe(mqtt_topic); - if (topic.isEmpty()) return; - piCout << "DEL" << topic; // << PRIVATE->endpoints.getRef()->size(); - int ret = MQTTClient_unsubscribe(PRIVATE->client, topic.dataUTF8()); + // piCout << "unsubscribeInternal" << mqtt_topic; + int ret = MQTTClient_unsubscribe(PRIVATE->client, mqtt_topic.dataUTF8()); if (ret != MQTTCLIENT_SUCCESS) { - piCoutObj << "Failed to unsubscribe" << topic << ", code" << ret; + piCoutObj << "Failed to unsubscribe" << mqtt_topic << ", code" << ret; } } void PIMQTT::Client::destroy() { - unregisterAll(); if (!PRIVATE->client) return; if (PRIVATE->connected) MQTTClient_disconnect(PRIVATE->client, 1000); MQTTClient_destroy(&PRIVATE->client); diff --git a/main.cpp b/main.cpp index 95ac62cd..6550c43a 100644 --- a/main.cpp +++ b/main.cpp @@ -64,6 +64,14 @@ int main(int argc, char * argv[]) { PIMQTT::Client cl; cl.setConnectTimeout(2_s); + cl.subscribe("api/v1/all/bort{A}/f", + [](const PIMQTT::MessageConst & msg) { piCout << "1" << msg.topicList() << msg.pathArguments() << msg.body().size(); }); + cl.subscribe("api/v1/all/task{T}/f", + [](const PIMQTT::MessageConst & msg) { piCout << "2" << msg.topicList() << msg.pathArguments() << msg.body().size(); }); + cl.subscribe("api/v1/all/*/f", + [](const PIMQTT::MessageConst & msg) { piCout << "3" << msg.topicList() << msg.pathArguments() << msg.body().size(); }); + cl.subscribe("api/v1/all2/**", + [](const PIMQTT::MessageConst & msg) { piCout << "4" << msg.topicList() << msg.pathArguments() << msg.body().size(); }); CONNECTL(&cl, connected, [&cl] { piCout << "connected"; // cl.subscribe("api/v1/plugins"); @@ -71,18 +79,6 @@ int main(int argc, char * argv[]) { // cl.subscribe("api/v1/*/{taskID}/status"); // cl.subscribe("api/v1/bort/list"); // cl.subscribe("api/v1/all"); - cl.subscribe("api/v1/all/bort{A}/f", [](const PIMQTT::MessageConst & msg) { - piCout << "1" << msg.topicList() << msg.pathArguments() << msg.body().size(); - }); - cl.subscribe("api/v1/all/task{T}/f", [](const PIMQTT::MessageConst & msg) { - piCout << "2" << msg.topicList() << msg.pathArguments() << msg.body().size(); - }); - cl.subscribe("api/v1/all/*/f", [](const PIMQTT::MessageConst & msg) { - piCout << "3" << msg.topicList() << msg.pathArguments() << msg.body().size(); - }); - cl.subscribe("api/v1/all2/**", [](const PIMQTT::MessageConst & msg) { - piCout << "4" << msg.topicList() << msg.pathArguments() << msg.body().size(); - }); // cl.subscribe("/zigbee2mqtt"); // cl.subscribe("/zigbee2mqtt/+/status/"); // cl.subscribe("/zigbee2mqtt/*/status/"); @@ -103,6 +99,9 @@ int main(int argc, char * argv[]) { cl.connect("localhost", "PIP"); + piSleep(6.); + cl.unsubscribeAll(); + kbd.enableExitCapture('Q'); WAIT_FOR_EXIT From 1689f560d5ad563dde681e656119080ef22d7ca8 Mon Sep 17 00:00:00 2001 From: peri4 Date: Fri, 29 May 2026 17:40:09 +0300 Subject: [PATCH 12/35] version 5.8.0 very important performance fix for normalizeAngleDeg...() methods, now fixed-speed (no "while") MQTT client release --- CMakeLists.txt | 2 +- libs/main/math/pimathbase.h | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c04ec22..d22b323d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ if (POLICY CMP0177) endif() project(PIP) set(PIP_MAJOR 5) -set(PIP_MINOR 7) +set(PIP_MINOR 8) set(PIP_REVISION 0) set(PIP_SUFFIX _beta) set(PIP_COMPANY SHS) diff --git a/libs/main/math/pimathbase.h b/libs/main/math/pimathbase.h index 79252451..9aeb126e 100644 --- a/libs/main/math/pimathbase.h +++ b/libs/main/math/pimathbase.h @@ -308,10 +308,12 @@ inline PIVector piAbs(const PIVector & v) { //! \~russian Нормализует угол к диапазону `[0; 360]` градусов на месте. template void normalizeAngleDeg360(T & a) { - while (a < 0.) - a += 360.; - while (a > 360.) - a -= 360.; + if (std::isnan(a) || std::isinf(a)) { + a = 0.; + return; + } + a -= std::floor(a / 360.) * 360.; + if (a < 0) a += 360; } //! \~english Returns an angle normalized to the `[0; 360]` degree range. @@ -327,10 +329,13 @@ double normalizedAngleDeg360(T a) { //! \~russian Нормализует угол к диапазону `[-180; 180]` градусов на месте. template void normalizeAngleDeg180(T & a) { - while (a < -180.) - a += 360.; - while (a > 180.) - a -= 360.; + if (std::isnan(a) || std::isinf(a)) { + a = 0.; + return; + } + a -= std::floor(a / 360.) * 360.; + if (a < -180) a += 360; + if (a >= 180) a -= 360; } //! \~english Returns an angle normalized to the `[-180; 180]` degree range. From 15277f8f322ca491213dd119ec62d79b862bdb15 Mon Sep 17 00:00:00 2001 From: peri4 Date: Thu, 4 Jun 2026 11:18:28 +0300 Subject: [PATCH 13/35] PIPoint operators *=, /=, * and / now also with PIPoint --- libs/main/math/pipoint.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/libs/main/math/pipoint.h b/libs/main/math/pipoint.h index 20e88e8e..820c1d38 100644 --- a/libs/main/math/pipoint.h +++ b/libs/main/math/pipoint.h @@ -167,6 +167,22 @@ public: return *this; } + //! \~english Multiplies by-coordinates by `v`. + //! \~russian Умножает по-координатно на `v`. + PIPoint & operator*=(const PIPoint & v) { + x *= v.x; + y *= v.y; + return *this; + } + + //! \~english Divides by-coordinates by `v`. + //! \~russian Делит по-координатно на `v`. + PIPoint & operator/=(const PIPoint & v) { + x /= v.x; + y /= v.y; + return *this; + } + //! \~english Returns sum of two points. //! \~russian Возвращает сумму двух точек. PIPoint operator+(const PIPoint & p) const { return PIPoint(x + p.x, y + p.y); } @@ -195,6 +211,14 @@ public: //! \~russian Возвращает точку, деленную на `v`. PIPoint operator/(Type v) const { return PIPoint(x / v, y / v); } + //! \~english Returns point multiplied by `v`. + //! \~russian Возвращает точку, умноженную на `v`. + PIPoint operator*(const PIPoint & v) const { return PIPoint(x * v.x, y * v.y); } + + //! \~english Returns point divided by `v`. + //! \~russian Возвращает точку, деленную на `v`. + PIPoint operator/(const PIPoint & v) const { return PIPoint(x / v.x, y / v.y); } + //! \~english Checks whether point coordinates are equal. //! \~russian Проверяет равенство координат точек. bool operator==(const PIPoint & p) const { return (x == p.x && y == p.y); } From a2f625292aee0aba2c53b5091ab2c83bc1545c00 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 12:00:59 +0300 Subject: [PATCH 14/35] piterminal nonzero exit code on fail --- libs/console/piterminal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/console/piterminal.cpp b/libs/console/piterminal.cpp index 795d9af3..768e37ae 100644 --- a/libs/console/piterminal.cpp +++ b/libs/console/piterminal.cpp @@ -880,7 +880,7 @@ bool PITerminal::initialize() { execvp(argv[0], argv); delete[] argv[0]; delete[] argv; - exit(0); + exit(127); } else { if (fr < 0 || PRIVATE->fd < 0) { piCoutObj << "forkpty error," << errorString(); From b0307e8323cb64b4fd44a4fc96f4a1f5581dfe2e Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 12:01:36 +0300 Subject: [PATCH 15/35] fix piprocess set workdir --- libs/main/system/piprocess.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index aa4c4713..ffa6a871 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -215,10 +215,10 @@ PRIVATE_DEFINITION_START(PIProcess) PeekNamedPipe(pipes[pipe_type][PipeRead], nullptr, 0, nullptr, &available, nullptr); if (available > 0) { BOOL ok = ReadFile(pipes[pipe_type][PipeRead], - read_buffer.data(offset), - piMini(available, read_buffer.size() - offset), - &bytes_read, - nullptr); + read_buffer.data(offset), + piMini(available, read_buffer.size() - offset), + &bytes_read, + nullptr); if (!ok) bytes_read = 0; } # else @@ -293,10 +293,10 @@ void PIProcess::startProc(bool detached) { si.dwFlags |= STARTF_USESTDHANDLES; const auto cmd = convertWindowsCmd(args); if (CreateProcessA(0, // No module name (use command line) - (LPSTR)cmd.data(), // Command line + (LPSTR)cmd.data(), // Command line 0, // Process handle not inheritable 0, // Thread handle not inheritable - true, // Set handle inheritance to FALSE + true, // Set handle inheritance to FALSE detached ? DETACHED_PROCESS /*CREATE_NEW_CONSOLE*/ : 0, // Creation flags 0, // Use environment wd.isEmpty() ? 0 : wd.data(), // Use working directory @@ -322,7 +322,7 @@ void PIProcess::startProc(bool detached) { if (!detached) PRIVATE->pid = pid_; if (pid_ == 0) { if (!wd.isEmpty()) { - if (!chdir(wd.data())) piCoutObj << "Error while set working directory"; + if (chdir(wd.data()) != 0) piCoutObj << "Error while set working directory"; } PRIVATE->closePipe(StdIn, PipeWrite); PRIVATE->closePipe(StdOut, PipeRead); From 6f11eeb895a80827a2c5c51b9686038bfccb4663 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 12:02:35 +0300 Subject: [PATCH 16/35] check PIBinaryStream negative size to fix memory overflow --- libs/main/serialization/pibinarystream.h | 46 ++++++++++++++---------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/libs/main/serialization/pibinarystream.h b/libs/main/serialization/pibinarystream.h index 80dcbe25..0d4d1ab9 100644 --- a/libs/main/serialization/pibinarystream.h +++ b/libs/main/serialization/pibinarystream.h @@ -56,17 +56,17 @@ #else -# define BINARY_STREAM_FRIEND(T) \ - template \ - friend PIBinaryStream

& operator<<(PIBinaryStream

& s, const T & v); \ - template \ - friend PIBinaryStream

& operator>>(PIBinaryStream

& s, T & v); +# define BINARY_STREAM_FRIEND(T) \ + template \ + friend PIBinaryStream

& operator<<(PIBinaryStream

& s, const T & v); \ + template \ + friend PIBinaryStream

& operator>>(PIBinaryStream

& s, T & v); # define BINARY_STREAM_WRITE(T) \ - template \ - inline PIBinaryStream

& operator<<(PIBinaryStream

& s, const T & v) + template \ + inline PIBinaryStream

& operator<<(PIBinaryStream

& s, const T & v) # define BINARY_STREAM_READ(T) \ - template \ - inline PIBinaryStream

& operator>>(PIBinaryStream

& s, T & v) + template \ + inline PIBinaryStream

& operator>>(PIBinaryStream

& s, T & v) #endif @@ -410,7 +410,7 @@ template & operator>>(PIBinaryStream

& s, PIVector & v) { // piCout << ">> vector trivial default"; int sz = s.binaryStreamTakeInt(); - if (s.wasReadError()) { + if (s.wasReadError() || sz < 0) { fprintf(stderr, "error with PIVector<%s>\n", __PIP_TYPENAME__(T)); v.clear(); return s; @@ -433,7 +433,7 @@ template & operator>>(PIBinaryStream

& s, PIVector & v) { // piCout << ">> vector trivial custom"; int sz = s.binaryStreamTakeInt(); - if (s.wasReadError()) { + if (s.wasReadError() || sz < 0) { fprintf(stderr, "error with PIVector<%s>\n", __PIP_TYPENAME__(T)); v.clear(); return s; @@ -462,7 +462,7 @@ template & operator>>(PIBinaryStream

& s, PIDeque & v) { // piCout << ">> deque trivial default"; int sz = s.binaryStreamTakeInt(); - if (s.wasReadError()) { + if (s.wasReadError() || sz < 0) { fprintf(stderr, "error with PIDeque<%s>\n", __PIP_TYPENAME__(T)); v.clear(); return s; @@ -485,7 +485,7 @@ template & operator>>(PIBinaryStream

& s, PIDeque & v) { // piCout << ">> deque trivial custom"; int sz = s.binaryStreamTakeInt(); - if (s.wasReadError()) { + if (s.wasReadError() || sz < 0) { fprintf(stderr, "error with PIDeque<%s>\n", __PIP_TYPENAME__(T)); v.clear(); return s; @@ -516,7 +516,7 @@ inline PIBinaryStream

& operator>>(PIBinaryStream

& s, PIVector2D & v) int r, c; r = s.binaryStreamTakeInt(); c = s.binaryStreamTakeInt(); - if (s.wasReadError()) { + if (s.wasReadError() || r < 0 || c < 0) { fprintf(stderr, "error with PIVector2D<%s>\n", __PIP_TYPENAME__(T)); v.clear(); return s; @@ -542,6 +542,11 @@ inline PIBinaryStream

& operator>>(PIBinaryStream

& s, PIVector2D & v) PIVector tmp; r = s.binaryStreamTakeInt(); c = s.binaryStreamTakeInt(); + if (s.wasReadError() || r < 0 || c < 0) { + fprintf(stderr, "error with PIVector2D<%s>\n", __PIP_TYPENAME__(T)); + v.clear(); + return s; + } s >> tmp; if (s.wasReadError()) { fprintf(stderr, "error with PIVector2D<%s>\n", __PIP_TYPENAME__(T)); @@ -618,7 +623,7 @@ template & operator>>(PIBinaryStream

& s, PIVector & v) { int sz = s.binaryStreamTakeInt(); - if (s.wasReadError()) { + if (s.wasReadError() || sz < 0) { fprintf(stderr, "error with PIVector<%s>\n", __PIP_TYPENAME__(T)); v.clear(); return s; @@ -641,7 +646,7 @@ template & operator>>(PIBinaryStream

& s, PIDeque & v) { int sz = s.binaryStreamTakeInt(); - if (s.wasReadError()) { + if (s.wasReadError() || sz < 0) { fprintf(stderr, "error with PIDeque<%s>\n", __PIP_TYPENAME__(T)); v.clear(); return s; @@ -667,6 +672,11 @@ inline PIBinaryStream

& operator>>(PIBinaryStream

& s, PIVector2D & v) PIVector tmp; r = s.binaryStreamTakeInt(); c = s.binaryStreamTakeInt(); + if (s.wasReadError() || r < 0 || c < 0) { + fprintf(stderr, "error with PIVector2D<%s>\n", __PIP_TYPENAME__(T)); + v.clear(); + return s; + } s >> tmp; if (s.wasReadError()) { fprintf(stderr, "error with PIVector2D<%s>\n", __PIP_TYPENAME__(T)); @@ -700,7 +710,7 @@ template //! \~russian Восстанавливает ключи и значения %PIMap. inline PIBinaryStream

& operator>>(PIBinaryStream

& s, PIMap & v) { int sz = s.binaryStreamTakeInt(); - if (s.wasReadError()) { + if (s.wasReadError() || sz < 0) { fprintf(stderr, "error with PIMap<%s, %s>\n", __PIP_TYPENAME__(Key), __PIP_TYPENAME__(T)); v.clear(); return s; @@ -749,7 +759,7 @@ template //! \~russian Восстанавливает ключи %PISet. inline PIBinaryStream

& operator>>(PIBinaryStream

& s, PISet & v) { int sz = s.binaryStreamTakeInt(); - if (s.wasReadError()) { + if (s.wasReadError() || sz < 0) { fprintf(stderr, "error with PISet<%s>\n", __PIP_TYPENAME__(Key)); v.clear(); return s; From 9da2d4fdb34f5654989c17879a5e0295bdacc7fb Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 12:03:22 +0300 Subject: [PATCH 17/35] fix select usage --- libs/main/core/piwaitevent_p.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/main/core/piwaitevent_p.cpp b/libs/main/core/piwaitevent_p.cpp index 3dff58bc..e816260c 100644 --- a/libs/main/core/piwaitevent_p.cpp +++ b/libs/main/core/piwaitevent_p.cpp @@ -89,12 +89,15 @@ bool PIWaitEvent::wait(int fd, CheckRole role) { FD_SET(pipe_fd[ReadEnd], &(fds[CheckRead])); FD_SET(fd, &(fds[CheckExeption])); if (fd_index != CheckExeption) FD_SET(fd, &(fds[fd_index])); - int sr = ::select(nfds, &(fds[CheckRead]), &(fds[CheckWrite]), &(fds[CheckExeption]), nullptr); + int sr = -1; + do { + sr = ::select(nfds, &(fds[CheckRead]), &(fds[CheckWrite]), &(fds[CheckExeption]), nullptr); + } while (sr < 0 && errno == EINTR); int buf = 0; while (::read(pipe_fd[ReadEnd], &buf, sizeof(buf)) > 0) ; // piCout << "wait result" << sr << FD_ISSET(fd, &(fds[CheckExeption])) << FD_ISSET(fd, &(fds[fd_index])); - if (sr == EBADF || sr == EINTR) return false; + if (sr < 0) return false; if (FD_ISSET(fd, &(fds[CheckExeption]))) return true; return FD_ISSET(fd, &(fds[fd_index])); #endif From c53f1d39aacdc00ea76836d3d5503ffcf5c0053f Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 12:03:56 +0300 Subject: [PATCH 18/35] fix piethernet blocking write return value --- libs/main/io_devices/piethernet.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index 7cb7a1dc..16f8e24c 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -861,6 +861,7 @@ ssize_t PIEthernet::writeDevice(const void * data, ssize_t max_size) { return -1; } } + ret += sr; remain_data += sr; remain_size -= sr; } From 11d6f7b0db53e8863cf73054e566d1080c4c8e8a Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 12:05:09 +0300 Subject: [PATCH 19/35] piethernet replace inet_ntoa by threadsafe inet_ntop --- libs/main/io_devices/piethernet.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index 16f8e24c..e4c086b0 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -102,7 +102,10 @@ #ifndef WINDOWS PIString getSockAddr(sockaddr * s) { - return s == 0 ? PIString() : PIStringAscii(inet_ntoa(((sockaddr_in *)s)->sin_addr)); + if (!s) return PIString(); + char buf[INET_ADDRSTRLEN]; + const char * r = inet_ntop(AF_INET, &((sockaddr_in *)s)->sin_addr, buf, sizeof(buf)); + return r ? PIStringAscii(r) : PIString(); } #endif From dc69ebe5ae9dbc4a6ccf4e9789673196343bc80a Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 12:06:03 +0300 Subject: [PATCH 20/35] fix memleak in PIEthernet::interfaces for android --- libs/main/io_devices/piethernet.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index e4c086b0..024d3a03 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -1179,13 +1179,19 @@ PIEthernet::InterfaceList PIEthernet::interfaces() { # else # ifdef ANDROID struct ifconf ifc; - int s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + int s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (s == -1) { + piCout << "[PIEthernet]" + << "Can`t create socket: %1"_tr("PIEthernet").arg(errorString()); + return il; + } ifc.ifc_len = 256; ifc.ifc_buf = new char[ifc.ifc_len]; if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { piCout << "[PIEthernet]" << "Can`t get interfaces: %1"_tr("PIEthernet").arg(errorString()); delete[] ifc.ifc_buf; + ::close(s); return il; } int icnt = ifc.ifc_len / sizeof(ifreq); @@ -1204,7 +1210,8 @@ PIEthernet::InterfaceList PIEthernet::interfaces() { if (ci.address == "127.0.0.1") ci.flags |= PIEthernet::ifLoopback; il << ci; } - delete ifc.ifc_buf; + delete[] ifc.ifc_buf; + ::close(s); # else struct ifaddrs *ret, *cif = 0; int s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP); @@ -1295,8 +1302,10 @@ PINetworkAddress PIEthernet::interfaceAddress(const PIString & interface_) { piZeroMemory(ifr); strcpy(ifr.ifr_name, interface_.dataAscii()); int s = ::socket(AF_INET, SOCK_DGRAM, 0); - ioctl(s, SIOCGIFADDR, &ifr); - ::close(s); + if (s != -1) { + ioctl(s, SIOCGIFADDR, &ifr); + ::close(s); + } struct sockaddr_in * sa = (struct sockaddr_in *)&ifr.ifr_addr; return PINetworkAddress(uint(sa->sin_addr.s_addr)); #endif @@ -1417,13 +1426,13 @@ void PIEthernet::ethClosesocket(int sock, bool shutdown) { int PIEthernet::ethSetsockopt(int sock, int level, int optname, const void * optval, int optlen) { if (sock < 0) return -1; auto ret = setsockopt(sock, - level, - optname, + level, + optname, #ifdef WINDOWS - (char *) + (char *) #endif - optval, - optlen); + optval, + optlen); if (ret != 0) piCout << "setsockopt error:" << ethErrorString(); return ret; } From ec989633474d84016e2b20a7141eb953ca9f7d1e Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 12:45:46 +0300 Subject: [PATCH 21/35] Revert "fix select usage" This reverts commit 9da2d4fdb34f5654989c17879a5e0295bdacc7fb. --- libs/main/core/piwaitevent_p.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libs/main/core/piwaitevent_p.cpp b/libs/main/core/piwaitevent_p.cpp index e816260c..3dff58bc 100644 --- a/libs/main/core/piwaitevent_p.cpp +++ b/libs/main/core/piwaitevent_p.cpp @@ -89,15 +89,12 @@ bool PIWaitEvent::wait(int fd, CheckRole role) { FD_SET(pipe_fd[ReadEnd], &(fds[CheckRead])); FD_SET(fd, &(fds[CheckExeption])); if (fd_index != CheckExeption) FD_SET(fd, &(fds[fd_index])); - int sr = -1; - do { - sr = ::select(nfds, &(fds[CheckRead]), &(fds[CheckWrite]), &(fds[CheckExeption]), nullptr); - } while (sr < 0 && errno == EINTR); + int sr = ::select(nfds, &(fds[CheckRead]), &(fds[CheckWrite]), &(fds[CheckExeption]), nullptr); int buf = 0; while (::read(pipe_fd[ReadEnd], &buf, sizeof(buf)) > 0) ; // piCout << "wait result" << sr << FD_ISSET(fd, &(fds[CheckExeption])) << FD_ISSET(fd, &(fds[fd_index])); - if (sr < 0) return false; + if (sr == EBADF || sr == EINTR) return false; if (FD_ISSET(fd, &(fds[CheckExeption]))) return true; return FD_ISSET(fd, &(fds[fd_index])); #endif From 42dd5945dde307320359a494f91f7044638d5852 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 12:46:43 +0300 Subject: [PATCH 22/35] review fixes --- libs/console/piterminal.cpp | 2 +- libs/main/core/piwaitevent_p.cpp | 3 ++- libs/main/io_devices/piethernet.cpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/console/piterminal.cpp b/libs/console/piterminal.cpp index 768e37ae..aeaffd9a 100644 --- a/libs/console/piterminal.cpp +++ b/libs/console/piterminal.cpp @@ -880,7 +880,7 @@ bool PITerminal::initialize() { execvp(argv[0], argv); delete[] argv[0]; delete[] argv; - exit(127); + exit(errno); } else { if (fr < 0 || PRIVATE->fd < 0) { piCoutObj << "forkpty error," << errorString(); diff --git a/libs/main/core/piwaitevent_p.cpp b/libs/main/core/piwaitevent_p.cpp index 3dff58bc..2e07f073 100644 --- a/libs/main/core/piwaitevent_p.cpp +++ b/libs/main/core/piwaitevent_p.cpp @@ -94,7 +94,8 @@ bool PIWaitEvent::wait(int fd, CheckRole role) { while (::read(pipe_fd[ReadEnd], &buf, sizeof(buf)) > 0) ; // piCout << "wait result" << sr << FD_ISSET(fd, &(fds[CheckExeption])) << FD_ISSET(fd, &(fds[fd_index])); - if (sr == EBADF || sr == EINTR) return false; + if (sr < 0) return false; + if (errno == EBADF || errno == EINTR) return false; if (FD_ISSET(fd, &(fds[CheckExeption]))) return true; return FD_ISSET(fd, &(fds[fd_index])); #endif diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index 024d3a03..a28da44f 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -104,6 +104,7 @@ PIString getSockAddr(sockaddr * s) { if (!s) return PIString(); char buf[INET_ADDRSTRLEN]; + memset(&buf, 0, INET_ADDRSTRLEN); const char * r = inet_ntop(AF_INET, &((sockaddr_in *)s)->sin_addr, buf, sizeof(buf)); return r ? PIStringAscii(r) : PIString(); } From 427130e2cadfe349aca8d44b922979e8491f9a74 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 12:48:40 +0300 Subject: [PATCH 23/35] fix 2 --- libs/main/core/piwaitevent_p.cpp | 2 +- libs/main/io_devices/piethernet.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/main/core/piwaitevent_p.cpp b/libs/main/core/piwaitevent_p.cpp index 2e07f073..dfb2b23c 100644 --- a/libs/main/core/piwaitevent_p.cpp +++ b/libs/main/core/piwaitevent_p.cpp @@ -90,11 +90,11 @@ bool PIWaitEvent::wait(int fd, CheckRole role) { FD_SET(fd, &(fds[CheckExeption])); if (fd_index != CheckExeption) FD_SET(fd, &(fds[fd_index])); int sr = ::select(nfds, &(fds[CheckRead]), &(fds[CheckWrite]), &(fds[CheckExeption]), nullptr); + if (sr < 0) return false; int buf = 0; while (::read(pipe_fd[ReadEnd], &buf, sizeof(buf)) > 0) ; // piCout << "wait result" << sr << FD_ISSET(fd, &(fds[CheckExeption])) << FD_ISSET(fd, &(fds[fd_index])); - if (sr < 0) return false; if (errno == EBADF || errno == EINTR) return false; if (FD_ISSET(fd, &(fds[CheckExeption]))) return true; return FD_ISSET(fd, &(fds[fd_index])); diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index a28da44f..ad4a08b3 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -104,7 +104,7 @@ PIString getSockAddr(sockaddr * s) { if (!s) return PIString(); char buf[INET_ADDRSTRLEN]; - memset(&buf, 0, INET_ADDRSTRLEN); + piZeroMemory(buf, sizeof(buf)); const char * r = inet_ntop(AF_INET, &((sockaddr_in *)s)->sin_addr, buf, sizeof(buf)); return r ? PIStringAscii(r) : PIString(); } From 3603f6df130f2b4c127eed15bb351dedb2da8333 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 12:59:36 +0300 Subject: [PATCH 24/35] fix piwaitevent --- libs/main/core/piwaitevent_p.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/main/core/piwaitevent_p.cpp b/libs/main/core/piwaitevent_p.cpp index dfb2b23c..47983a5d 100644 --- a/libs/main/core/piwaitevent_p.cpp +++ b/libs/main/core/piwaitevent_p.cpp @@ -89,11 +89,11 @@ bool PIWaitEvent::wait(int fd, CheckRole role) { FD_SET(pipe_fd[ReadEnd], &(fds[CheckRead])); FD_SET(fd, &(fds[CheckExeption])); if (fd_index != CheckExeption) FD_SET(fd, &(fds[fd_index])); - int sr = ::select(nfds, &(fds[CheckRead]), &(fds[CheckWrite]), &(fds[CheckExeption]), nullptr); + int sr = ::select(nfds, &(fds[CheckRead]), &(fds[CheckWrite]), &(fds[CheckExeption]), nullptr); if (sr < 0) return false; + errorClear(); int buf = 0; - while (::read(pipe_fd[ReadEnd], &buf, sizeof(buf)) > 0) - ; + while (::read(pipe_fd[ReadEnd], &buf, sizeof(buf)) > 0) {} // piCout << "wait result" << sr << FD_ISSET(fd, &(fds[CheckExeption])) << FD_ISSET(fd, &(fds[fd_index])); if (errno == EBADF || errno == EINTR) return false; if (FD_ISSET(fd, &(fds[CheckExeption]))) return true; From df9356393122bb3835cefafdff6f95ac6eaec157 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 13:11:27 +0300 Subject: [PATCH 25/35] version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d22b323d..102a42b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ endif() project(PIP) set(PIP_MAJOR 5) set(PIP_MINOR 8) -set(PIP_REVISION 0) +set(PIP_REVISION 1) set(PIP_SUFFIX _beta) set(PIP_COMPANY SHS) set(PIP_DOMAIN org.SHS) From 216109b8ed5dafa7c241f5309655ba6d86a9ba38 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 17:37:35 +0300 Subject: [PATCH 26/35] fix(PIString): initialize toChar() return value and fix operator+= OOB - toChar(): char v was uninitialized, causing UB when sscanf fails to match (empty string). Initialize to 0. - operator+=(PIConstChars): loop iterated l < d.size() instead of l < str.size(), reading past the end of str after d.enlarge(). This is a heap buffer overread with undefined behavior. --- libs/main/text/pistring.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/main/text/pistring.cpp b/libs/main/text/pistring.cpp index db3b9da8..65bf8035 100644 --- a/libs/main/text/pistring.cpp +++ b/libs/main/text/pistring.cpp @@ -671,7 +671,7 @@ PIString & PIString::operator+=(const PIConstChars & str) { if (!str.isEmpty()) { size_t os = d.size(); d.enlarge(str.size()); - for (size_t l = 0; l < d.size(); ++l) { + for (size_t l = 0; l < str.size(); ++l) { d[os + l] = str[l]; } } @@ -1763,7 +1763,7 @@ PIString PIString::toLowerCase() const { char PIString::toChar() const { - char v; + char v = 0; sscanf(dataAscii(), "%c", &v); return v; } From e89ae88f3a975ebcc5af8b1d0621f80ba13cca46 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 17:37:57 +0300 Subject: [PATCH 27/35] fix(PICAN): align socket handling with PIEthernet patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Sentinel: sock = 0 → sock = -1 (0 is stdin, valid fd) - openDevice(): close socket on ioctl/bind failure (resource leak) - openDevice(): set O_NONBLOCK via fcntl after socket() - openDevice(): check setsockopt() return value - closeDevice(): call shutdown() before close(), reset sock to -1 - readDevice() / writeDevice(): guard against sock == -1 - Add missing #include --- libs/main/io_devices/pican.cpp | 21 ++++++++++++++++++--- libs/main/io_devices/pican.h | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/libs/main/io_devices/pican.cpp b/libs/main/io_devices/pican.cpp index 2d6fe04e..6d1e2295 100644 --- a/libs/main/io_devices/pican.cpp +++ b/libs/main/io_devices/pican.cpp @@ -24,6 +24,7 @@ # define PIP_CAN #endif #ifdef PIP_CAN +# include # include # include # include @@ -49,7 +50,7 @@ PICAN::PICAN(const PIString & path, PIIODevice::DeviceMode mode): PIIODevice(pat setThreadedReadBufferSize(256); setPath(path); can_id = 0; - sock = 0; + sock = -1; PRIVATE->event.create(); } @@ -67,19 +68,25 @@ bool PICAN::openDevice() { sock = socket(PF_CAN, SOCK_RAW, CAN_RAW); if (sock < 0) { piCoutObj << "Error! while opening socket"; + sock = -1; return false; } + fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK); ifreq ifr; strcpy(ifr.ifr_name, path().dataAscii()); piCout << "PICAN try to get interface index..."; if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) { piCoutObj << "Error! while determin the interface ioctl"; + ::close(sock); + sock = -1; return false; } struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv); + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) < 0) { + piCoutObj << "Error! while setting socket receive timeout"; + } // bind socket to all CAN interface sockaddr_can addr; addr.can_family = AF_CAN; @@ -87,6 +94,8 @@ bool PICAN::openDevice() { piCout << "PICAN try to bind socket to interface" << ifr.ifr_ifindex; if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { piCoutObj << "Error! while binding socket"; + ::close(sock); + sock = -1; return false; } piCout << "PICAN Open OK!"; @@ -101,7 +110,11 @@ bool PICAN::openDevice() { bool PICAN::closeDevice() { #ifdef PIP_CAN interrupt(); - if (sock > 0) ::close(sock); + if (sock != -1) { + ::shutdown(sock, SHUT_RDWR); + ::close(sock); + sock = -1; + } #endif return true; } @@ -109,6 +122,7 @@ bool PICAN::closeDevice() { ssize_t PICAN::readDevice(void * read_to, ssize_t max_size) { #ifdef PIP_CAN + if (sock == -1) return -1; // piCout << "PICAN read"; can_frame frame; ssize_t ret = 0; @@ -127,6 +141,7 @@ ssize_t PICAN::readDevice(void * read_to, ssize_t max_size) { ssize_t PICAN::writeDevice(const void * data, ssize_t max_size) { #ifdef PIP_CAN + if (sock == -1) return -1; // piCout << "PICAN write" << can_id << max_size; if (max_size > 8) { piCoutObj << "Can't send CAN frame bigger than 8 bytes (requested " << max_size << ")!"; diff --git a/libs/main/io_devices/pican.h b/libs/main/io_devices/pican.h index 93bef921..37938818 100644 --- a/libs/main/io_devices/pican.h +++ b/libs/main/io_devices/pican.h @@ -73,7 +73,7 @@ protected: private: PRIVATE_DECLARATION(PIP_EXPORT) - int sock; + int sock = -1; int can_id, readed_id; }; From 6c5c4b73e0bae8b0a26201ebdd875bc62aab97cb Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 17:38:01 +0300 Subject: [PATCH 28/35] fix(PIClientServer): delete PIEthernet when client_factory returns nullptr When the custom client_factory() returned nullptr, the incoming PIEthernet pointer was leaked. Matches existing pattern used in the max_clients overflow branch. --- libs/client_server/piclientserver_server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/client_server/piclientserver_server.cpp b/libs/client_server/piclientserver_server.cpp index edfd2083..aaea5d21 100644 --- a/libs/client_server/piclientserver_server.cpp +++ b/libs/client_server/piclientserver_server.cpp @@ -37,6 +37,7 @@ PIClientServer::Server::Server() { auto sc = client_factory(); if (!sc) { piCout << "ClientFactory returns nullptr!"_tr("PIClientServer"); + delete c; return; } sc->createForServer(this, c); From e05abf16b5350e7ef2daf5d1a2fc283ca101e3c7 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 17:38:07 +0300 Subject: [PATCH 29/35] fix(PIWaitEvent): use -1 as pipe_fd sentinel instead of 0 File descriptor 0 is stdin (a valid fd). Using 0 as the 'not-open' sentinel caused isCreate() to return false and destroy() to skip close() when pipe() happened to allocate fds {0, 1} (e.g. in daemons with closed stdio). POSIX convention: -1 = invalid fd. --- libs/main/core/piwaitevent_p.cpp | 6 +++--- libs/main/core/piwaitevent_p.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/main/core/piwaitevent_p.cpp b/libs/main/core/piwaitevent_p.cpp index 47983a5d..36ec80bc 100644 --- a/libs/main/core/piwaitevent_p.cpp +++ b/libs/main/core/piwaitevent_p.cpp @@ -65,9 +65,9 @@ void PIWaitEvent::destroy() { } #else for (int i = 0; i < 2; ++i) { - if (pipe_fd[i] != 0) { + if (pipe_fd[i] != -1) { ::close(pipe_fd[i]); - pipe_fd[i] = 0; + pipe_fd[i] = -1; } } #endif @@ -140,7 +140,7 @@ bool PIWaitEvent::isCreate() const { #ifdef WINDOWS return event; #else - return pipe_fd[ReadEnd] != 0; + return pipe_fd[ReadEnd] != -1; #endif } diff --git a/libs/main/core/piwaitevent_p.h b/libs/main/core/piwaitevent_p.h index 9814d449..1af19ac1 100644 --- a/libs/main/core/piwaitevent_p.h +++ b/libs/main/core/piwaitevent_p.h @@ -55,7 +55,7 @@ private: #ifdef WINDOWS void * event = nullptr; #else - int pipe_fd[2] = {0, 0}; + int pipe_fd[2] = {-1, -1}; fd_set fds[3]; enum { ReadEnd = 0, From 9cd56ab66ceb1fd401cccc2814492587f738e192 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 17:38:12 +0300 Subject: [PATCH 30/35] fix(PIEthernet): check open() return value with >= 0 instead of != 0 open() returns -1 on error. The check fd != 0 treated -1 as a valid fd (since -1 != 0 is true), causing devctl(-1, ...) and close(-1) on QNX. Fixed to fd >= 0. --- libs/main/io_devices/piethernet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index ad4a08b3..a1245cc1 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -383,7 +383,7 @@ void PIEthernet::applyBuffers() { void PIEthernet::applyTimeout(int fd, int opt, PISystemTime tm) { if (fd == 0) return; - // piCoutObj << "setReadIsBlocking" << yes; + // piCoutObj << "setReadIsBlocking" << yes; #ifdef WINDOWS DWORD _tm = tm.toMilliseconds(); #else @@ -1234,7 +1234,7 @@ PIEthernet::InterfaceList PIEthernet::interfaces() { # ifdef QNX # ifndef BLACKBERRY int fd = ::open((PIString("/dev/io-net/") + ci.name).dataAscii(), O_RDONLY); - if (fd != 0) { + if (fd >= 0) { nic_config_t nic; devctl(fd, DCMD_IO_NET_GET_CONFIG, &nic, sizeof(nic), 0); ::close(fd); From b2e637ee2b4fde52f079c46db971f5d5eb29ea80 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 17:38:17 +0300 Subject: [PATCH 31/35] fix(PIIOByteArray): advance pos by actual bytes read, not requested size readDevice() did pos += size instead of pos += ret. The clamping on the next line (pos = min(pos, data_->size_s())) masked the issue in practice, but the semantics were wrong. Aligns with writeDevice() which correctly uses pos += rs.size_s(). --- libs/main/io_devices/piiobytearray.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/main/io_devices/piiobytearray.cpp b/libs/main/io_devices/piiobytearray.cpp index 3b6e8374..699bd1e9 100644 --- a/libs/main/io_devices/piiobytearray.cpp +++ b/libs/main/io_devices/piiobytearray.cpp @@ -62,8 +62,7 @@ ssize_t PIIOByteArray::readDevice(void * read_to, ssize_t size) { if (ret <= 0) return -1; memcpy(read_to, data_->data(pos), ret); // piCout << "readed" << ret; - pos += size; - if (pos > data_->size_s()) pos = data_->size_s(); + pos += ret; return ret; } From 13a9a75f79a97569d00a00b3ee395afabe7e5f9e Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 17:38:22 +0300 Subject: [PATCH 32/35] fix(PISerial): break infinite loop when readDevice returns <= 0 In the blocking read path (timeout_ms <= 0), the while loop had no exit condition for readDevice() returning 0 (EOF) or -1 (error). This caused an infinite busy-loop at 100% CPU. Added 'else break' to exit the loop on read failure, matching the timeout branch which already handles this case. --- libs/main/io_devices/piserial.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libs/main/io_devices/piserial.cpp b/libs/main/io_devices/piserial.cpp index 9859ef68..401d5dfc 100644 --- a/libs/main/io_devices/piserial.cpp +++ b/libs/main/io_devices/piserial.cpp @@ -513,7 +513,10 @@ bool PISerial::read(void * data, int size, double timeout_ms) { all = readDevice(data, 1); while (all < size) { ret = readDevice(&((uchar *)data)[all], size - all); - if (ret > 0) all += ret; + if (ret > 0) + all += ret; + else + break; } setOption(BlockingRead, br); received(data, all); @@ -1235,8 +1238,8 @@ PIVector PISerial::availableDevicesInfo(bool test) { for (const auto & e: de) { // TODO changes in FileInfo for (const auto & p: prefixes) { if (e.name().startsWith(p)) { - di = DeviceInfo(); - di.path = e.path; + di = DeviceInfo(); + di.path = e.path; # ifdef LINUX ssize_t lsz = readlink(("/sys/class/tty/" + e.name()).dataAscii(), linkbuf, 1024); if (lsz > 0) { From 27a9f05d65f8b50c4340e604795c63e4bf907c18 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 17:38:28 +0300 Subject: [PATCH 33/35] fix(PIProcess): handle fork() failure (return value -1) fork() can return -1 on error (too many processes, out of memory). The old code treated pid_ == -1 as a successful fork, entering the parent branch and calling waitpid(-1, ...) which waits for ANY child process. Added explicit check: close pipes, free memory, and return on fork failure. --- libs/main/system/piprocess.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index ffa6a871..115b9a69 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -319,6 +319,13 @@ void PIProcess::startProc(bool detached) { auto largs = convertToCharArrays(args); auto lenv = convertToCharArrays(env); int pid_ = fork(); + if (pid_ < 0) { + piCoutObj << "\"fork\" error: " << errorString(); + PRIVATE->closeAllPipes(); + delete[] largs; + delete[] lenv; + return; + } if (!detached) PRIVATE->pid = pid_; if (pid_ == 0) { if (!wd.isEmpty()) { From 3f293ebd65514bc3dd03591d0596a14df976e377 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 17:38:32 +0300 Subject: [PATCH 34/35] fix(PIFileTransfer): validate file id before array access processFile() accessed files_[id - 1] without validating id. When id == 0 (from a crafted network packet), id - 1 = -1 caused out-of-bounds access. Added bounds check: id must be in range [1, files_.size()]. Stops receive and logs error on invalid id. --- libs/main/io_utils/pifiletransfer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libs/main/io_utils/pifiletransfer.cpp b/libs/main/io_utils/pifiletransfer.cpp index 7389a769..acdeac73 100644 --- a/libs/main/io_utils/pifiletransfer.cpp +++ b/libs/main/io_utils/pifiletransfer.cpp @@ -131,6 +131,12 @@ bool PIFileTransfer::sendFiles(const PIVector & files) { void PIFileTransfer::processFile(int id, ullong start, PIByteArray & data) { // piCout << "processFile" << id << files_.size(); + if (id <= 0 || id > files_.size_s()) { + cur_file_string = "Error: Invalid file id " + PIString::fromNumber(id); + piCoutObj << cur_file_string; + stopReceive(); + return; + } PFTFileInfo fi = files_[id - 1]; bytes_file_all = fi.size; bytes_file_cur = start; From 1ddec15b874ca123ee384f5689d6f9ff9c2e3b39 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 5 Aug 2026 19:47:35 +0300 Subject: [PATCH 35/35] threadsafe fixes --- libs/main/io_devices/pipeer.cpp | 5 ++++- libs/mqtt_client/pimqttclient.cpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/main/io_devices/pipeer.cpp b/libs/main/io_devices/pipeer.cpp index c121dfea..0cf8bd6a 100644 --- a/libs/main/io_devices/pipeer.cpp +++ b/libs/main/io_devices/pipeer.cpp @@ -623,7 +623,10 @@ bool PIPeer::dataRead(const uchar * readed, ssize_t size) { return true; } cnt++; - if (cnt > _PIPEER_MSG_TTL || from == dp->name) return true; + if (cnt > _PIPEER_MSG_TTL || from == dp->name) { + eth_mutex.unlock(); + return true; + } sba << type << from << to << cnt << pba; // piCout << "translate packet" << from << "->" << to << ", ttl =" << cnt; sendToNeighbour(dp, sba); diff --git a/libs/mqtt_client/pimqttclient.cpp b/libs/mqtt_client/pimqttclient.cpp index 60ffb958..133639e5 100644 --- a/libs/mqtt_client/pimqttclient.cpp +++ b/libs/mqtt_client/pimqttclient.cpp @@ -60,7 +60,7 @@ STATIC_INITIALIZER_END PRIVATE_DEFINITION_START(PIMQTT::Client) MQTTClient client = nullptr; - bool connected = false; + std::atomic connected{false}; PIProtectedVariable endpoints;