From e5fc89821f1ef46de6f4aa9664d2207e5745553b Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 28 Apr 2023 11:03:28 -0700 Subject: [PATCH 01/41] use cpp-httplib for HTTP control plane (#1979) refactored the old control plane code to use [cpp-httplib](https://github.com/yhirose/cpp-httplib) instead of a hand rolled HTTP server. Makes the control plane code much more legible. Also no longer randomly stops responding. --- controller/DBMirrorSet.cpp | 9 +- controller/EmbeddedNetworkController.cpp | 1054 ++-- controller/EmbeddedNetworkController.hpp | 28 +- ext/cpp-httplib/httplib.h | 6604 +++++++++++++++------- service/OneService.cpp | 1245 ++-- 5 files changed, 5595 insertions(+), 3345 deletions(-) diff --git a/controller/DBMirrorSet.cpp b/controller/DBMirrorSet.cpp index fd7f32a22..5d64ebf0c 100644 --- a/controller/DBMirrorSet.cpp +++ b/controller/DBMirrorSet.cpp @@ -15,9 +15,12 @@ namespace ZeroTier { -DBMirrorSet::DBMirrorSet(DB::ChangeListener *listener) : - _listener(listener), - _running(true) +DBMirrorSet::DBMirrorSet(DB::ChangeListener *listener) + : _listener(listener) + , _running(true) + , _syncCheckerThread() + , _dbs() + , _dbs_l() { _syncCheckerThread = std::thread([this]() { for(;;) { diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 2ccc16b8c..1d5cee014 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #ifndef _WIN32 #include @@ -553,593 +554,528 @@ void EmbeddedNetworkController::request( _queue.post(qe); } -unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) +std::string EmbeddedNetworkController::networkUpdateFromPostData(uint64_t networkID, const std::string &body) { - if ((!path.empty())&&(path[0] == "network")) { + json b = OSUtils::jsonParse(body); - if ((path.size() >= 2)&&(path[1].length() == 16)) { - const uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); - json network; - if (!_db.get(nwid,network)) - return 404; + char nwids[24]; + OSUtils::ztsnprintf(nwids, sizeof(nwids), "%.16llx", networkID); - if (path.size() >= 3) { + json network; + _db.get(networkID, network); + DB::initNetwork(network); + if (b.count("name")) network["name"] = OSUtils::jsonString(b["name"],""); + if (b.count("private")) network["private"] = OSUtils::jsonBool(b["private"],true); + if (b.count("enableBroadcast")) network["enableBroadcast"] = OSUtils::jsonBool(b["enableBroadcast"],false); + if (b.count("multicastLimit")) network["multicastLimit"] = OSUtils::jsonInt(b["multicastLimit"],32ULL); + if (b.count("mtu")) network["mtu"] = std::max(std::min((unsigned int)OSUtils::jsonInt(b["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU); - if (path[2] == "member") { - - if (path.size() >= 4) { - // Get member - - const uint64_t address = Utils::hexStrToU64(path[3].c_str()); - json member; - if (!_db.get(nwid,network,address,member)) - return 404; - responseBody = OSUtils::jsonDump(member); - responseContentType = "application/json"; - - } else { - // List members and their revisions - - responseBody = "{"; - std::vector members; - if (_db.get(nwid,network,members)) { - responseBody.reserve((members.size() + 2) * 32); - std::string mid; - for(auto member=members.begin();member!=members.end();++member) { - mid = OSUtils::jsonString((*member)["id"], ""); - char tmp[128]; - OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s\"%s\":%llu",(responseBody.length() > 1) ? "," : "",mid.c_str(),(unsigned long long)OSUtils::jsonInt((*member)["revision"],0)); - responseBody.append(tmp); - } - } - responseBody.push_back('}'); - responseContentType = "application/json"; - - } - return 200; - - } // else 404 - - } else { - // Get network - - responseBody = OSUtils::jsonDump(network); - responseContentType = "application/json"; - return 200; - - } - } else if (path.size() == 1) { - // List networks - - std::set networkIds; - _db.networks(networkIds); - char tmp[64]; - responseBody = "["; - responseBody.reserve((networkIds.size() + 1) * 24); - for(std::set::const_iterator i(networkIds.begin());i!=networkIds.end();++i) { - if (responseBody.length() > 1) - responseBody.push_back(','); - OSUtils::ztsnprintf(tmp,sizeof(tmp),"\"%.16llx\"",(unsigned long long)*i); - responseBody.append(tmp); - } - responseBody.push_back(']'); - responseContentType = "application/json"; - - return 200; - - } // else 404 - - } else { - // Controller status - - char tmp[4096]; - const bool dbOk = _db.isReady(); - OSUtils::ztsnprintf(tmp,sizeof(tmp),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu,\n\t\"databaseReady\": %s\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now(),dbOk ? "true" : "false"); - responseBody = tmp; - responseContentType = "application/json"; - return dbOk ? 200 : 503; - - } - - return 404; -} - -unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) -{ - if (path.empty()) - return 404; - - json b; - try { - b = OSUtils::jsonParse(body); - if (!b.is_object()) { - responseBody = "{ \"message\": \"body is not a JSON object\" }"; - responseContentType = "application/json"; - return 400; + if (b.count("remoteTraceTarget")) { + const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],"")); + if (rtt.length() == 10) { + network["remoteTraceTarget"] = rtt; + } else { + network["remoteTraceTarget"] = json(); } - } catch ( ... ) { - responseBody = "{ \"message\": \"body JSON is invalid\" }"; - responseContentType = "application/json"; - return 400; } - const int64_t now = OSUtils::now(); + if (b.count("remoteTraceLevel")) network["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL); - if (path[0] == "network") { + if (b.count("v4AssignMode")) { + json nv4m; + json &v4m = b["v4AssignMode"]; + if (v4m.is_string()) { // backward compatibility + nv4m["zt"] = (OSUtils::jsonString(v4m,"") == "zt"); + } else if (v4m.is_object()) { + nv4m["zt"] = OSUtils::jsonBool(v4m["zt"],false); + } else nv4m["zt"] = false; + network["v4AssignMode"] = nv4m; + } - if ((path.size() >= 2)&&(path[1].length() == 16)) { - uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); - char nwids[24]; - OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + if (b.count("v6AssignMode")) { + json nv6m; + json &v6m = b["v6AssignMode"]; + if (!nv6m.is_object()) nv6m = json::object(); + if (v6m.is_string()) { // backward compatibility + std::vector v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","","")); + std::sort(v6ms.begin(),v6ms.end()); + v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end()); + nv6m["rfc4193"] = false; + nv6m["zt"] = false; + nv6m["6plane"] = false; + for(std::vector::iterator i(v6ms.begin());i!=v6ms.end();++i) { + if (*i == "rfc4193") + nv6m["rfc4193"] = true; + else if (*i == "zt") + nv6m["zt"] = true; + else if (*i == "6plane") + nv6m["6plane"] = true; + } + } else if (v6m.is_object()) { + if (v6m.count("rfc4193")) nv6m["rfc4193"] = OSUtils::jsonBool(v6m["rfc4193"],false); + if (v6m.count("zt")) nv6m["zt"] = OSUtils::jsonBool(v6m["zt"],false); + if (v6m.count("6plane")) nv6m["6plane"] = OSUtils::jsonBool(v6m["6plane"],false); + } else { + nv6m["rfc4193"] = false; + nv6m["zt"] = false; + nv6m["6plane"] = false; + } + network["v6AssignMode"] = nv6m; + } - if (path.size() >= 3) { - - if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { - uint64_t address = Utils::hexStrToU64(path[3].c_str()); - char addrs[24]; - OSUtils::ztsnprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)address); - - json member,network; - _db.get(nwid,network,address,member); - DB::initMember(member); - - try { - if (b.count("activeBridge")) member["activeBridge"] = OSUtils::jsonBool(b["activeBridge"], false); - if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = OSUtils::jsonBool(b["noAutoAssignIps"], false); - if (b.count("authenticationExpiryTime")) member["authenticationExpiryTime"] = (uint64_t)OSUtils::jsonInt(b["authenticationExpiryTime"], 0ULL); - if (b.count("authenticationURL")) member["authenticationURL"] = OSUtils::jsonString(b["authenticationURL"], ""); - - if (b.count("remoteTraceTarget")) { - const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],"")); - if (rtt.length() == 10) { - member["remoteTraceTarget"] = rtt; - } else { - member["remoteTraceTarget"] = json(); - } + if (b.count("routes")) { + json &rts = b["routes"]; + if (rts.is_array()) { + json nrts = json::array(); + for(unsigned long i=0;i().c_str()); + InetAddress v; + if (via.is_string()) v.fromString(via.get().c_str()); + if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) { + json tmp; + char tmp2[64]; + tmp["target"] = t.toString(tmp2); + if (v.ss_family == t.ss_family) + tmp["via"] = v.toIpString(tmp2); + else tmp["via"] = json(); + nrts.push_back(tmp); + if (nrts.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; } - if (b.count("remoteTraceLevel")) member["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL); - - if (b.count("authorized")) { - const bool newAuth = OSUtils::jsonBool(b["authorized"],false); - if (newAuth != OSUtils::jsonBool(member["authorized"],false)) { - member["authorized"] = newAuth; - member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = now; - if (newAuth) { - member["lastAuthorizedCredentialType"] = "api"; - member["lastAuthorizedCredential"] = json(); - } - } - } - - if (b.count("ipAssignments")) { - json &ipa = b["ipAssignments"]; - if (ipa.is_array()) { - json mipa(json::array()); - for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - } - member["ipAssignments"] = mipa; - } - } - - if (b.count("tags")) { - json &tags = b["tags"]; - if (tags.is_array()) { - std::map mtags; - for(unsigned long i=0;i::iterator t(mtags.begin());t!=mtags.end();++t) { - json ta = json::array(); - ta.push_back(t->first); - ta.push_back(t->second); - mtagsa.push_back(ta); - if (mtagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - member["tags"] = mtagsa; - } - } - - if (b.count("capabilities")) { - json &capabilities = b["capabilities"]; - if (capabilities.is_array()) { - json mcaps = json::array(); - for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - std::sort(mcaps.begin(),mcaps.end()); - mcaps.erase(std::unique(mcaps.begin(),mcaps.end()),mcaps.end()); - member["capabilities"] = mcaps; - } - } - } catch ( ... ) { - responseBody = "{ \"message\": \"exception while processing parameters in JSON body\" }"; - responseContentType = "application/json"; - return 400; } + } + } + network["routes"] = nrts; + } + } - member["id"] = addrs; - member["address"] = addrs; // legacy - member["nwid"] = nwids; - - DB::cleanMember(member); - _db.save(member,true); - responseBody = OSUtils::jsonDump(member); - responseContentType = "application/json"; - - return 200; - } // else 404 - - } else { - // POST to network ID - - // Magic ID ending with ______ picks a random unused network ID - if (path[1].substr(10) == "______") { - nwid = 0; - uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL; - uint64_t nwidPostfix = 0; - for(unsigned long k=0;k<100000;++k) { // sanity limit on trials - Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix)); - uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL); - if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL; - if (!_db.hasNetwork(tryNwid)) { - nwid = tryNwid; + if (b.count("ipAssignmentPools")) { + json &ipp = b["ipAssignmentPools"]; + if (ipp.is_array()) { + json nipp = json::array(); + for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) break; - } } - if (!nwid) - return 503; } - OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); - - json network; - _db.get(nwid,network); - DB::initNetwork(network); - - try { - if (b.count("name")) network["name"] = OSUtils::jsonString(b["name"],""); - if (b.count("private")) network["private"] = OSUtils::jsonBool(b["private"],true); - if (b.count("enableBroadcast")) network["enableBroadcast"] = OSUtils::jsonBool(b["enableBroadcast"],false); - if (b.count("multicastLimit")) network["multicastLimit"] = OSUtils::jsonInt(b["multicastLimit"],32ULL); - if (b.count("mtu")) network["mtu"] = std::max(std::min((unsigned int)OSUtils::jsonInt(b["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU); - - if (b.count("remoteTraceTarget")) { - const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],"")); - if (rtt.length() == 10) { - network["remoteTraceTarget"] = rtt; - } else { - network["remoteTraceTarget"] = json(); - } - } - if (b.count("remoteTraceLevel")) network["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL); - - if (b.count("v4AssignMode")) { - json nv4m; - json &v4m = b["v4AssignMode"]; - if (v4m.is_string()) { // backward compatibility - nv4m["zt"] = (OSUtils::jsonString(v4m,"") == "zt"); - } else if (v4m.is_object()) { - nv4m["zt"] = OSUtils::jsonBool(v4m["zt"],false); - } else nv4m["zt"] = false; - network["v4AssignMode"] = nv4m; - } - - if (b.count("v6AssignMode")) { - json nv6m; - json &v6m = b["v6AssignMode"]; - if (!nv6m.is_object()) nv6m = json::object(); - if (v6m.is_string()) { // backward compatibility - std::vector v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","","")); - std::sort(v6ms.begin(),v6ms.end()); - v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end()); - nv6m["rfc4193"] = false; - nv6m["zt"] = false; - nv6m["6plane"] = false; - for(std::vector::iterator i(v6ms.begin());i!=v6ms.end();++i) { - if (*i == "rfc4193") - nv6m["rfc4193"] = true; - else if (*i == "zt") - nv6m["zt"] = true; - else if (*i == "6plane") - nv6m["6plane"] = true; - } - } else if (v6m.is_object()) { - if (v6m.count("rfc4193")) nv6m["rfc4193"] = OSUtils::jsonBool(v6m["rfc4193"],false); - if (v6m.count("zt")) nv6m["zt"] = OSUtils::jsonBool(v6m["zt"],false); - if (v6m.count("6plane")) nv6m["6plane"] = OSUtils::jsonBool(v6m["6plane"],false); - } else { - nv6m["rfc4193"] = false; - nv6m["zt"] = false; - nv6m["6plane"] = false; - } - network["v6AssignMode"] = nv6m; - } - - if (b.count("routes")) { - json &rts = b["routes"]; - if (rts.is_array()) { - json nrts = json::array(); - for(unsigned long i=0;i().c_str()); - InetAddress v; - if (via.is_string()) v.fromString(via.get().c_str()); - if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) { - json tmp; - char tmp2[64]; - tmp["target"] = t.toString(tmp2); - if (v.ss_family == t.ss_family) - tmp["via"] = v.toIpString(tmp2); - else tmp["via"] = json(); - nrts.push_back(tmp); - if (nrts.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - } - } - } - network["routes"] = nrts; - } - } - - if (b.count("ipAssignmentPools")) { - json &ipp = b["ipAssignmentPools"]; - if (ipp.is_array()) { - json nipp = json::array(); - for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - } - } - network["ipAssignmentPools"] = nipp; - } - } - - if (b.count("rules")) { - json &rules = b["rules"]; - if (rules.is_array()) { - json nrules = json::array(); - for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - } - } - network["rules"] = nrules; - } - } - - if (b.count("authTokens")) { - json &authTokens = b["authTokens"]; - if (authTokens.is_object()) { - json nat; - for(json::iterator t(authTokens.begin());t!=authTokens.end();++t) { - if ((t.value().is_number())&&(t.value() >= 0)) - nat[t.key()] = t.value(); - } - network["authTokens"] = nat; - } else { - network["authTokens"] = {{}}; - } - } - - if (b.count("capabilities")) { - json &capabilities = b["capabilities"]; - if (capabilities.is_array()) { - std::map< uint64_t,json > ncaps; - for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - } - } - } - ncap["rules"] = nrules; - - ncaps[capId] = ncap; - } - } - - json ncapsa = json::array(); - for(std::map< uint64_t,json >::iterator c(ncaps.begin());c!=ncaps.end();++c) { - ncapsa.push_back(c->second); - if (ncapsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - network["capabilities"] = ncapsa; - } - } - - if (b.count("tags")) { - json &tags = b["tags"]; - if (tags.is_array()) { - std::map< uint64_t,json > ntags; - for(unsigned long i=0;i::iterator t(ntags.begin());t!=ntags.end();++t) { - ntagsa.push_back(t->second); - if (ntagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - network["tags"] = ntagsa; - } - } - - if (b.count("dns")) { - json &dns = b["dns"]; - if (dns.is_object()) { - json nd; - - nd["domain"] = dns["domain"]; - - json &srv = dns["servers"]; - if (srv.is_array()) { - json ns = json::array(); - for(unsigned int i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + } + } + network["rules"] = nrules; + } + } + + if (b.count("authTokens")) { + json &authTokens = b["authTokens"]; + if (authTokens.is_object()) { + json nat; + for(json::iterator t(authTokens.begin());t!=authTokens.end();++t) { + if ((t.value().is_number())&&(t.value() >= 0)) + nat[t.key()] = t.value(); + } + network["authTokens"] = nat; + } else { + network["authTokens"] = {{}}; + } + } + + if (b.count("capabilities")) { + json &capabilities = b["capabilities"]; + if (capabilities.is_array()) { + std::map< uint64_t,json > ncaps; + for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + } + } + } + ncap["rules"] = nrules; + + ncaps[capId] = ncap; + } + } + + json ncapsa = json::array(); + for(std::map< uint64_t,json >::iterator c(ncaps.begin());c!=ncaps.end();++c) { + ncapsa.push_back(c->second); + if (ncapsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + network["capabilities"] = ncapsa; + } + } + + if (b.count("tags")) { + json &tags = b["tags"]; + if (tags.is_array()) { + std::map< uint64_t,json > ntags; + for(unsigned long i=0;i::iterator t(ntags.begin());t!=ntags.end();++t) { + ntagsa.push_back(t->second); + if (ntagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + network["tags"] = ntagsa; + } + } + + if (b.count("dns")) { + json &dns = b["dns"]; + if (dns.is_object()) { + json nd; + + nd["domain"] = dns["domain"]; + + json &srv = dns["servers"]; + if (srv.is_array()) { + json ns = json::array(); + for(unsigned int i=0;i &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) +void EmbeddedNetworkController::configureHTTPControlPlane( + httplib::Server &s, + const std::function setContent) { - if (path.empty()) - return 404; + s.Get("/controller/network", [&](const httplib::Request &req, httplib::Response &res) { + std::set networkIds; + _db.networks(networkIds); + char tmp[64]; - if (path[0] == "network") { - if ((path.size() >= 2)&&(path[1].length() == 16)) { - const uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); - if (path.size() >= 3) { - if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { - const uint64_t address = Utils::hexStrToU64(path[3].c_str()); + auto out = json::array(); + for(std::set::const_iterator i(networkIds.begin()); i != networkIds.end(); ++i) { + OSUtils::ztsnprintf(tmp, sizeof(tmp), "%.16llx", *i); + out.push_back(tmp); + } - json network,member; - _db.get(nwid,network,address,member); - _db.eraseMember(nwid, address); + setContent(req, res, out.dump()); + }); - { - std::lock_guard l(_memberStatus_l); - _memberStatus.erase(_MemberStatusKey(nwid,address)); - } + s.Get("/controller/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1]; + uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); + json network; + if (!_db.get(nwid, network)) { + res.status = 404; + return; + } - if (!member.size()) - return 404; - responseBody = OSUtils::jsonDump(member); - responseContentType = "application/json"; - return 200; - } - } else { - json network; - _db.get(nwid,network); - _db.eraseNetwork(nwid); + setContent(req, res, network.dump()); + }); - { - std::lock_guard l(_memberStatus_l); - for(auto i=_memberStatus.begin();i!=_memberStatus.end();) { - if (i->first.networkId == nwid) - _memberStatus.erase(i++); - else ++i; - } - } - - if (!network.size()) - return 404; - responseBody = OSUtils::jsonDump(network); - responseContentType = "application/json"; - return 200; + auto createNewNetwork = [&](const httplib::Request &req, httplib::Response &res) { + fprintf(stderr, "creating new network (new style)\n"); + uint64_t nwid = 0; + uint64_t nwidPrefix = (Utils::hexStrToU64(_signingIdAddressString.c_str()) << 24) & 0xffffffffff000000ULL; + uint64_t nwidPostfix = 0; + for(unsigned long k=0;k<100000;++k) { // sanity limit on trials + Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix)); + uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL); + if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL; + if (!_db.hasNetwork(tryNwid)) { + nwid = tryNwid; + break; } - } // else 404 + } + if (!nwid) { + res.status = 503; + return; + } - } // else 404 + setContent(req, res, networkUpdateFromPostData(nwid, req.body)); + }; + s.Put("/controller/network", createNewNetwork); + s.Post("/controller/network", createNewNetwork); - return 404; + auto createNewNetworkOldAndBusted = [&](const httplib::Request &req, httplib::Response &res) { + auto inID = req.matches[1].str(); + + if (inID != _signingIdAddressString) { + res.status = 400; + return; + } + + uint64_t nwid = 0; + uint64_t nwidPrefix = (Utils::hexStrToU64(inID.c_str()) << 24) & 0xffffffffff000000ULL; + uint64_t nwidPostfix = 0; + for(unsigned long k=0;k<100000;++k) { // sanity limit on trials + Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix)); + uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL); + if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL; + if (!_db.hasNetwork(tryNwid)) { + nwid = tryNwid; + break; + } + } + if (!nwid) { + res.status = 503; + return; + } + setContent(req, res, networkUpdateFromPostData(nwid, req.body)); + }; + s.Put("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted); + s.Post("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted); + + s.Delete("/controller/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1].str(); + uint64_t nwid = Utils::hexStrToU64(networkID.c_str()); + + json network; + if (!_db.get(nwid,network)) { + res.status = 404; + return; + } + + _db.eraseNetwork(nwid); + setContent(req, res, network.dump()); + }); + + s.Get("/controller/network/([0-9a-fA-F]{16})/member", [&](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1]; + uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); + json network; + if (!_db.get(nwid, network)) { + res.status = 404; + return; + } + + json out = json::array(); + std::vector memTmp; + if (_db.get(nwid, network, memTmp)) { + for (auto m = memTmp.begin(); m != memTmp.end(); ++m) { + int revision = OSUtils::jsonInt((*m)["revsision"], 0); + std::string id = OSUtils::jsonString((*m)["id"], ""); + if (id.length() == 10) { + json tmp = json::object(); + tmp[id] = revision; + out.push_back(tmp); + } + } + } + + setContent(req, res, out.dump()); + }); + + s.Get("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1]; + auto memberID = req.matches[2]; + uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); + uint64_t memid = Utils::hexStrToU64(memberID.str().c_str()); + json network; + json member; + if (!_db.get(nwid, network, memid, member)) { + res.status = 404; + return; + } + + setContent(req, res, member.dump()); + }); + + auto memberPost = [&](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1].str(); + auto memberID = req.matches[2].str(); + uint64_t nwid = Utils::hexStrToU64(networkID.c_str()); + uint64_t memid = Utils::hexStrToU64(memberID.c_str()); + json network; + json member; + _db.get(nwid, network, memid, member); + DB::initMember(member); + + json b = OSUtils::jsonParse(req.body); + + if (b.count("activeBridge")) member["activeBridge"] = OSUtils::jsonBool(b["activeBridge"], false); + if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = OSUtils::jsonBool(b["noAutoAssignIps"], false); + if (b.count("authenticationExpiryTime")) member["authenticationExpiryTime"] = (uint64_t)OSUtils::jsonInt(b["authenticationExpiryTime"], 0ULL); + if (b.count("authenticationURL")) member["authenticationURL"] = OSUtils::jsonString(b["authenticationURL"], ""); + + if (b.count("remoteTraceTarget")) { + const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],"")); + if (rtt.length() == 10) { + member["remoteTraceTarget"] = rtt; + } else { + member["remoteTraceTarget"] = json(); + } + } + if (b.count("remoteTraceLevel")) member["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL); + + if (b.count("authorized")) { + const bool newAuth = OSUtils::jsonBool(b["authorized"],false); + if (newAuth != OSUtils::jsonBool(member["authorized"],false)) { + member["authorized"] = newAuth; + member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = OSUtils::now(); + if (newAuth) { + member["lastAuthorizedCredentialType"] = "api"; + member["lastAuthorizedCredential"] = json(); + } + } + } + + if (b.count("ipAssignments")) { + json &ipa = b["ipAssignments"]; + if (ipa.is_array()) { + json mipa(json::array()); + for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + } + member["ipAssignments"] = mipa; + } + } + + if (b.count("tags")) { + json &tags = b["tags"]; + if (tags.is_array()) { + std::map mtags; + for(unsigned long i=0;i::iterator t(mtags.begin());t!=mtags.end();++t) { + json ta = json::array(); + ta.push_back(t->first); + ta.push_back(t->second); + mtagsa.push_back(ta); + if (mtagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + member["tags"] = mtagsa; + } + } + + if (b.count("capabilities")) { + json &capabilities = b["capabilities"]; + if (capabilities.is_array()) { + json mcaps = json::array(); + for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + std::sort(mcaps.begin(),mcaps.end()); + mcaps.erase(std::unique(mcaps.begin(),mcaps.end()),mcaps.end()); + member["capabilities"] = mcaps; + } + } + + member["id"] = memberID; + member["address"] = memberID; + member["nwid"] = networkID; + + DB::cleanMember(member); + _db.save(member, true); + + setContent(req, res, member.dump()); + }; + s.Put("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost); + s.Post("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost); + + s.Delete("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1].str(); + auto memberID = req.matches[2].str(); + + uint64_t nwid = Utils::hexStrToU64(networkID.c_str()); + uint64_t address = Utils::hexStrToU64(memberID.c_str()); + json network, member; + + if (!_db.get(nwid, network, address, member)) { + res.status = 404; + return; + } + + if (!member.size()) { + res.status = 404; + return; + } + + _db.eraseMember(nwid, address); + + setContent(req, res, member.dump()); + }); } void EmbeddedNetworkController::handleRemoteTrace(const ZT_RemoteTrace &rt) diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index bc95acb58..4f2e20e0a 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -37,6 +37,8 @@ #include +#include + #include "DB.hpp" #include "DBMirrorSet.hpp" @@ -66,27 +68,9 @@ public: const Identity &identity, const Dictionary &metaData); - unsigned int handleControlPlaneHttpGET( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType); - unsigned int handleControlPlaneHttpPOST( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType); - unsigned int handleControlPlaneHttpDELETE( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType); + void configureHTTPControlPlane( + httplib::Server &s, + const std::function); void handleRemoteTrace(const ZT_RemoteTrace &rt); @@ -98,6 +82,8 @@ private: void _request(uint64_t nwid,const InetAddress &fromAddr,uint64_t requestPacketId,const Identity &identity,const Dictionary &metaData); void _startThreads(); + std::string networkUpdateFromPostData(uint64_t networkID, const std::string &body); + struct _RQEntry { uint64_t nwid; diff --git a/ext/cpp-httplib/httplib.h b/ext/cpp-httplib/httplib.h index 3947df060..28746000c 100644 --- a/ext/cpp-httplib/httplib.h +++ b/ext/cpp-httplib/httplib.h @@ -1,13 +1,15 @@ // // httplib.h // -// Copyright (c) 2020 Yuji Hirose. All rights reserved. +// Copyright (c) 2023 Yuji Hirose. All rights reserved. // MIT License // #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_VERSION "0.12.2" + /* * Configuration */ @@ -60,14 +62,26 @@ #define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 #endif +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + #ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 #endif +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + #ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH #define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) #endif +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + #ifndef CPPHTTPLIB_TCP_NODELAY #define CPPHTTPLIB_TCP_NODELAY false #endif @@ -87,6 +101,18 @@ : 0)) #endif +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + /* * Headers */ @@ -101,14 +127,16 @@ #endif //_CRT_NONSTDC_NO_DEPRECATE #if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + #ifdef _WIN64 using ssize_t = __int64; #else -using ssize_t = int; -#endif - -#if _MSC_VER < 1900 -#define snprintf _snprintf_s +using ssize_t = long; #endif #endif // _MSC_VER @@ -126,20 +154,12 @@ using ssize_t = int; #include #include - -#include #include #ifndef WSA_FLAG_NO_HANDLE_INHERIT #define WSA_FLAG_NO_HANDLE_INHERIT 0x80 #endif -#ifdef _MSC_VER -#pragma comment(lib, "ws2_32.lib") -#pragma comment(lib, "crypt32.lib") -#pragma comment(lib, "cryptui.lib") -#endif - #ifndef strcasecmp #define strcasecmp _stricmp #endif // strcasecmp @@ -152,8 +172,10 @@ using socket_t = SOCKET; #else // not _WIN32 #include -#include +#ifndef _AIX #include +#endif +#include #include #include #ifdef __linux__ @@ -167,22 +189,28 @@ using socket_t = SOCKET; #include #include #include +#include #include using socket_t = int; +#ifndef INVALID_SOCKET #define INVALID_SOCKET (-1) +#endif #endif //_WIN32 +#include #include #include #include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -190,14 +218,37 @@ using socket_t = int; #include #include #include +#include #include #include #include #include #ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + #include -#include +#include #include #include @@ -205,20 +256,15 @@ using socket_t = int; #include #endif -#include #include #include #if OPENSSL_VERSION_NUMBER < 0x1010100fL #error Sorry, OpenSSL versions prior to 1.1.1 are not supported +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#define SSL_get1_peer_certificate SSL_get_peer_certificate #endif -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#include -inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) { - return M_ASN1_STRING_data(asn1); -} -#endif #endif #ifdef CPPHTTPLIB_ZLIB_SUPPORT @@ -237,14 +283,65 @@ namespace httplib { namespace detail { +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + struct ci { bool operator()(const std::string &s1, const std::string &s2) const { - return std::lexicographical_compare( - s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return ::tolower(c1) < ::tolower(c2); }); + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); } }; +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + } // namespace detail using Headers = std::multimap; @@ -275,9 +372,9 @@ public: DataSink(DataSink &&) = delete; DataSink &operator=(DataSink &&) = delete; - std::function write; + std::function write; std::function done; - std::function is_writable; + std::function done_with_trailer; std::ostream os; private: @@ -304,6 +401,20 @@ using ContentProvider = using ContentProviderWithoutLength = std::function; +using ContentProviderResourceReleaser = std::function; + +struct MultipartFormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using MultipartFormDataProviderItems = std::vector; + +using ContentReceiverWithProgress = + std::function; + using ContentReceiver = std::function; @@ -317,14 +428,17 @@ public: ContentReceiver receiver)>; ContentReader(Reader reader, MultipartReader multipart_reader) - : reader_(reader), multipart_reader_(multipart_reader) {} + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} bool operator()(MultipartContentHeader header, ContentReceiver receiver) const { - return multipart_reader_(header, receiver); + return multipart_reader_(std::move(header), std::move(receiver)); } - bool operator()(ContentReceiver receiver) const { return reader_(receiver); } + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } Reader reader_; MultipartReader multipart_reader_; @@ -341,6 +455,8 @@ struct Request { std::string remote_addr; int remote_port = -1; + std::string local_addr; + int local_port = -1; // for server std::string version; @@ -351,35 +467,35 @@ struct Request { Match matches; // for client - size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT; ResponseHandler response_handler; - ContentReceiver content_receiver; - size_t content_length = 0; - ContentProvider content_provider; + ContentReceiverWithProgress content_receiver; Progress progress; - #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - const SSL *ssl; + const SSL *ssl = nullptr; #endif - bool has_header(const char *key) const; - std::string get_header_value(const char *key, size_t id = 0) const; + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; template - T get_header_value(const char *key, size_t id = 0) const; - size_t get_header_value_count(const char *key) const; - void set_header(const char *key, const char *val); - void set_header(const char *key, const std::string &val); + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); - bool has_param(const char *key) const; - std::string get_param_value(const char *key, size_t id = 0) const; - size_t get_param_value_count(const char *key) const; + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; bool is_multipart_form_data() const; - bool has_file(const char *key) const; - MultipartFormData get_file_value(const char *key) const; + bool has_file(const std::string &key) const; + MultipartFormData get_file_value(const std::string &key) const; + std::vector get_file_values(const std::string &key) const; // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; size_t authorization_count_ = 0; }; @@ -389,31 +505,30 @@ struct Response { std::string reason; Headers headers; std::string body; + std::string location; // Redirect location - bool has_header(const char *key) const; - std::string get_header_value(const char *key, size_t id = 0) const; + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; template - T get_header_value(const char *key, size_t id = 0) const; - size_t get_header_value_count(const char *key) const; - void set_header(const char *key, const char *val); - void set_header(const char *key, const std::string &val); + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); - void set_redirect(const char *url, int status = 302); void set_redirect(const std::string &url, int status = 302); - void set_content(const char *s, size_t n, const char *content_type); - void set_content(std::string s, const char *content_type); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); void set_content_provider( - size_t length, const char *content_type, ContentProvider provider, - const std::function &resource_releaser = nullptr); + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); void set_content_provider( - const char *content_type, ContentProviderWithoutLength provider, - const std::function &resource_releaser = nullptr); + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); void set_chunked_content_provider( - const char *content_type, ContentProviderWithoutLength provider, - const std::function &resource_releaser = nullptr); + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); Response() = default; Response(const Response &) = default; @@ -422,15 +537,16 @@ struct Response { Response &operator=(Response &&) = default; ~Response() { if (content_provider_resource_releaser_) { - content_provider_resource_releaser_(); + content_provider_resource_releaser_(content_provider_success_); } } // private members... size_t content_length_ = 0; ContentProvider content_provider_; - std::function content_provider_resource_releaser_; - bool is_chunked_content_provider = false; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; }; class Stream { @@ -443,9 +559,11 @@ public: virtual ssize_t read(char *ptr, size_t size) = 0; virtual ssize_t write(const char *ptr, size_t size) = 0; virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; template - ssize_t write_format(const char *fmt, const Args &... args); + ssize_t write_format(const char *fmt, const Args &...args); ssize_t write(const char *ptr); ssize_t write(const std::string &s); }; @@ -458,7 +576,7 @@ public: virtual void enqueue(std::function fn) = 0; virtual void shutdown() = 0; - virtual void on_idle(){}; + virtual void on_idle() {} }; class ThreadPool : public TaskQueue { @@ -474,8 +592,11 @@ public: ~ThreadPool() override = default; void enqueue(std::function fn) override { - std::unique_lock lock(mutex_); - jobs_.push_back(fn); + { + std::unique_lock lock(mutex_); + jobs_.push_back(std::move(fn)); + } + cond_.notify_one(); } @@ -509,7 +630,7 @@ private: if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } - fn = pool_.jobs_.front(); + fn = std::move(pool_.jobs_.front()); pool_.jobs_.pop_front(); } @@ -535,29 +656,25 @@ using Logger = std::function; using SocketOptions = std::function; -inline void default_socket_options(socket_t sock) { - int yes = 1; -#ifdef _WIN32 - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), - sizeof(yes)); - setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, - reinterpret_cast(&yes), sizeof(yes)); -#else -#ifdef SO_REUSEPORT - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), - sizeof(yes)); -#else - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), - sizeof(yes)); -#endif -#endif -} +void default_socket_options(socket_t sock); class Server { public: using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + using HandlerWithContentReader = std::function; + using Expect100ContinueHandler = std::function; @@ -567,46 +684,66 @@ public: virtual bool is_valid() const; - Server &Get(const char *pattern, Handler handler); - Server &Post(const char *pattern, Handler handler); - Server &Post(const char *pattern, HandlerWithContentReader handler); - Server &Put(const char *pattern, Handler handler); - Server &Put(const char *pattern, HandlerWithContentReader handler); - Server &Patch(const char *pattern, Handler handler); - Server &Patch(const char *pattern, HandlerWithContentReader handler); - Server &Delete(const char *pattern, Handler handler); - Server &Delete(const char *pattern, HandlerWithContentReader handler); - Server &Options(const char *pattern, Handler handler); + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); - bool set_base_dir(const char *dir, const char *mount_point = nullptr); - bool set_mount_point(const char *mount_point, const char *dir); - bool remove_mount_point(const char *mount_point); - void set_file_extension_and_mimetype_mapping(const char *ext, - const char *mime); - void set_file_request_handler(Handler handler); + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_file_request_handler(Handler handler); - void set_error_handler(Handler handler); - void set_expect_100_continue_handler(Expect100ContinueHandler handler); - void set_logger(Logger logger); + Server &set_error_handler(HandlerWithResponse handler); + Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); - void set_tcp_nodelay(bool on); - void set_socket_options(SocketOptions socket_options); + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); - void set_keep_alive_max_count(size_t count); - void set_keep_alive_timeout(time_t sec); - void set_read_timeout(time_t sec, time_t usec = 0); - void set_write_timeout(time_t sec, time_t usec = 0); - void set_idle_interval(time_t sec, time_t usec = 0); + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_socket_options(SocketOptions socket_options); - void set_payload_max_length(size_t length); + Server &set_default_headers(Headers headers); - bool bind_to_port(const char *host, int port, int socket_flags = 0); - int bind_to_any_port(const char *host, int socket_flags = 0); + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); bool listen_after_bind(); - bool listen(const char *host, int port, int socket_flags = 0); + bool listen(const std::string &host, int port, int socket_flags = 0); bool is_running() const; + void wait_until_ready() const; void stop(); std::function new_task_queue; @@ -616,7 +753,7 @@ protected: bool &connection_closed, const std::function &setup_request); - std::atomic svr_sock_; + std::atomic svr_sock_{INVALID_SOCKET}; size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; @@ -632,13 +769,15 @@ private: using HandlersForContentReader = std::vector>; - socket_t create_server_socket(const char *host, int port, int socket_flags, + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, SocketOptions socket_options) const; - int bind_internal(const char *host, int port, int socket_flags); + int bind_internal(const std::string &host, int port, int socket_flags); bool listen_internal(); bool routing(Request &req, Response &res, Stream &strm); - bool handle_file_request(Request &req, Response &res, bool head = false); + bool handle_file_request(const Request &req, Response &res, + bool head = false); bool dispatch_request(Request &req, Response &res, const Handlers &handlers); bool dispatch_request_for_content_reader(Request &req, Response &res, @@ -646,8 +785,15 @@ private: const HandlersForContentReader &handlers); bool parse_request_line(const char *s, Request &req); + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary); bool write_response(Stream &strm, bool close_connection, const Request &req, Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); bool write_content_with_provider(Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type); @@ -659,13 +805,20 @@ private: ContentReceiver multipart_receiver); bool read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader mulitpart_header, + MultipartContentHeader multipart_header, ContentReceiver multipart_receiver); virtual bool process_and_close_socket(socket_t sock); - std::atomic is_running_; - std::vector> base_dirs_; + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + + std::atomic is_running_{false}; + std::atomic done_{false}; std::map file_extension_and_mimetype_map_; Handler file_request_handler_; Handlers get_handlers_; @@ -678,15 +831,21 @@ private: Handlers delete_handlers_; HandlersForContentReader delete_handlers_for_content_reader_; Handlers options_handlers_; - Handler error_handler_; + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; Logger logger_; Expect100ContinueHandler expect_100_continue_handler_; + int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; }; -enum Error { +enum class Error { Success = 0, Unknown, Connection, @@ -697,24 +856,51 @@ enum Error { Canceled, SSLConnection, SSLLoadingCerts, - SSLServerVerification + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + + // For internal use only + SSLPeerCouldBeClosed_, }; +std::string to_string(const Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + class Result { public: - Result(const std::shared_ptr &res, Error err) - : res_(res), err_(err) {} + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response operator bool() const { return res_ != nullptr; } bool operator==(std::nullptr_t) const { return res_ == nullptr; } bool operator!=(std::nullptr_t) const { return res_ != nullptr; } const Response &value() const { return *res_; } + Response &value() { return *res_; } const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error Error error() const { return err_; } + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + size_t id = 0) const; + template + T get_request_header_value(const std::string &key, size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + private: - std::shared_ptr res_; + std::unique_ptr res_; Error err_; + Headers request_headers_; }; class ClientImpl { @@ -731,112 +917,206 @@ public: virtual bool is_valid() const; - Result Get(const char *path); - Result Get(const char *path, const Headers &headers); - Result Get(const char *path, Progress progress); - Result Get(const char *path, const Headers &headers, Progress progress); - Result Get(const char *path, ContentReceiver content_receiver); - Result Get(const char *path, const Headers &headers, - ContentReceiver content_receiver); - Result Get(const char *path, ContentReceiver content_receiver, + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, Progress progress); - Result Get(const char *path, const Headers &headers, - ContentReceiver content_receiver, Progress progress); - Result Get(const char *path, ResponseHandler response_handler, + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver); - Result Get(const char *path, const Headers &headers, + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver); - Result Get(const char *path, ResponseHandler response_handler, + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress); - Result Get(const char *path, const Headers &headers, + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress); - Result Head(const char *path); - Result Head(const char *path, const Headers &headers); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); - Result Post(const char *path); - Result Post(const char *path, const std::string &body, - const char *content_type); - Result Post(const char *path, const Headers &headers, const std::string &body, - const char *content_type); - Result Post(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Post(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Post(const char *path, const Params ¶ms); - Result Post(const char *path, const Headers &headers, const Params ¶ms); - Result Post(const char *path, const MultipartFormDataItems &items); - Result Post(const char *path, const Headers &headers, + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); - Result Put(const char *path); - Result Put(const char *path, const std::string &body, - const char *content_type); - Result Put(const char *path, const Headers &headers, const std::string &body, - const char *content_type); - Result Put(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Put(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Put(const char *path, const Params ¶ms); - Result Put(const char *path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); - Result Patch(const char *path, const std::string &body, - const char *content_type); - Result Patch(const char *path, const Headers &headers, - const std::string &body, const char *content_type); - Result Patch(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Patch(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); - Result Delete(const char *path); - Result Delete(const char *path, const std::string &body, - const char *content_type); - Result Delete(const char *path, const Headers &headers); - Result Delete(const char *path, const Headers &headers, - const std::string &body, const char *content_type); + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); - Result Options(const char *path); - Result Options(const char *path, const Headers &headers); + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); - bool send(const Request &req, Response &res); + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); size_t is_socket_open() const; + socket_t socket() const; + void stop(); + void set_hostname_addr_map(std::map addr_map); + void set_default_headers(Headers headers); + void set_address_family(int family); void set_tcp_nodelay(bool on); void set_socket_options(SocketOptions socket_options); void set_connection_timeout(time_t sec, time_t usec = 0); - void set_read_timeout(time_t sec, time_t usec = 0); - void set_write_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); - void set_basic_auth(const char *username, const char *password); - void set_bearer_token_auth(const char *token); + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_digest_auth(const char *username, const char *password); + void set_digest_auth(const std::string &username, + const std::string &password); #endif void set_keep_alive(bool on); void set_follow_location(bool on); + void set_url_encode(bool on); + void set_compress(bool on); void set_decompress(bool on); - void set_interface(const char *intf); + void set_interface(const std::string &intf); - void set_proxy(const char *host, int port); - void set_proxy_basic_auth(const char *username, const char *password); - void set_proxy_bearer_token_auth(const char *token); + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_proxy_digest_auth(const char *username, const char *password); + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -855,20 +1135,28 @@ protected: bool is_open() const { return sock != INVALID_SOCKET; } }; - virtual bool create_and_connect_socket(Socket &socket); - virtual void close_socket(Socket &socket, bool process_socket_ret); + virtual bool create_and_connect_socket(Socket &socket, Error &error); - bool process_request(Stream &strm, const Request &req, Response &res, - bool close_connection); + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket); + void close_socket(Socket &socket); - Error get_last_error() const; + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error); void copy_settings(const ClientImpl &rhs); - // Error state - mutable Error error_ = Error::Success; - - // Socket endoint information + // Socket endpoint information const std::string host_; const int port_; const std::string host_and_port_; @@ -878,6 +1166,14 @@ protected: mutable std::mutex socket_mutex_; std::recursive_mutex request_mutex_; + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + // Default headers Headers default_headers_; @@ -903,6 +1199,9 @@ protected: bool keep_alive_ = false; bool follow_location_ = false; + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; SocketOptions socket_options_ = nullptr; @@ -922,6 +1221,13 @@ protected: std::string proxy_digest_auth_password_; #endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT bool server_certificate_verification_ = true; #endif @@ -929,19 +1235,34 @@ protected: Logger logger_; private: - socket_t create_client_socket() const; - bool read_response_line(Stream &strm, Response &res); - bool write_request(Stream &strm, const Request &req, bool close_connection); - bool redirect(const Request &req, Response &res); - bool handle_request(Stream &strm, const Request &req, Response &res, - bool close_connection); - void stop_core(); - std::shared_ptr send_with_content_provider( - const char *method, const char *path, const Headers &headers, - const std::string &body, size_t content_length, - ContentProvider content_provider, const char *content_type); + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); - virtual bool process_socket(Socket &socket, + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, Response &res); + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + std::string adjust_host_string(const std::string &host) const; + + virtual bool process_socket(const Socket &socket, std::function callback); virtual bool is_ssl() const; }; @@ -949,9 +1270,9 @@ private: class Client { public: // Universal interface - explicit Client(const char *scheme_host_port); + explicit Client(const std::string &scheme_host_port); - explicit Client(const char *scheme_host_port, + explicit Client(const std::string &scheme_host_port, const std::string &client_cert_path, const std::string &client_key_path); @@ -962,114 +1283,206 @@ public: const std::string &client_cert_path, const std::string &client_key_path); + Client(Client &&) = default; + ~Client(); bool is_valid() const; - Result Get(const char *path); - Result Get(const char *path, const Headers &headers); - Result Get(const char *path, Progress progress); - Result Get(const char *path, const Headers &headers, Progress progress); - Result Get(const char *path, ContentReceiver content_receiver); - Result Get(const char *path, const Headers &headers, - ContentReceiver content_receiver); - Result Get(const char *path, ContentReceiver content_receiver, + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, Progress progress); - Result Get(const char *path, const Headers &headers, - ContentReceiver content_receiver, Progress progress); - Result Get(const char *path, ResponseHandler response_handler, + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver); - Result Get(const char *path, const Headers &headers, + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver); - Result Get(const char *path, const Headers &headers, + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress); - Result Get(const char *path, ResponseHandler response_handler, + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress); - Result Head(const char *path); - Result Head(const char *path, const Headers &headers); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); - Result Post(const char *path); - Result Post(const char *path, const std::string &body, - const char *content_type); - Result Post(const char *path, const Headers &headers, const std::string &body, - const char *content_type); - Result Post(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Post(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Post(const char *path, const Params ¶ms); - Result Post(const char *path, const Headers &headers, const Params ¶ms); - Result Post(const char *path, const MultipartFormDataItems &items); - Result Post(const char *path, const Headers &headers, + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); - Result Put(const char *path); - Result Put(const char *path, const std::string &body, - const char *content_type); - Result Put(const char *path, const Headers &headers, const std::string &body, - const char *content_type); - Result Put(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Put(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Put(const char *path, const Params ¶ms); - Result Put(const char *path, const Headers &headers, const Params ¶ms); - Result Patch(const char *path, const std::string &body, - const char *content_type); - Result Patch(const char *path, const Headers &headers, - const std::string &body, const char *content_type); - Result Patch(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Patch(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); - Result Delete(const char *path); - Result Delete(const char *path, const std::string &body, - const char *content_type); - Result Delete(const char *path, const Headers &headers); - Result Delete(const char *path, const Headers &headers, - const std::string &body, const char *content_type); + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); - Result Options(const char *path); - Result Options(const char *path, const Headers &headers); + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); - bool send(const Request &req, Response &res); + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); size_t is_socket_open() const; + socket_t socket() const; + void stop(); + void set_hostname_addr_map(std::map addr_map); + void set_default_headers(Headers headers); + void set_address_family(int family); void set_tcp_nodelay(bool on); void set_socket_options(SocketOptions socket_options); void set_connection_timeout(time_t sec, time_t usec = 0); - void set_read_timeout(time_t sec, time_t usec = 0); - void set_write_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); - void set_basic_auth(const char *username, const char *password); - void set_bearer_token_auth(const char *token); + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_digest_auth(const char *username, const char *password); + void set_digest_auth(const std::string &username, + const std::string &password); #endif void set_keep_alive(bool on); void set_follow_location(bool on); + void set_url_encode(bool on); + void set_compress(bool on); void set_decompress(bool on); - void set_interface(const char *intf); + void set_interface(const std::string &intf); - void set_proxy(const char *host, int port); - void set_proxy_basic_auth(const char *username, const char *password); - void set_proxy_bearer_token_auth(const char *token); + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_proxy_digest_auth(const char *username, const char *password); + void set_proxy_digest_auth(const std::string &username, + const std::string &password); #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -1080,8 +1493,8 @@ public: // SSL #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_ca_cert_path(const char *ca_cert_file_path, - const char *ca_cert_dir_path = nullptr); + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); void set_ca_cert_store(X509_STORE *ca_cert_store); @@ -1091,27 +1504,33 @@ public: #endif private: - std::shared_ptr cli_; + std::unique_ptr cli_; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT bool is_ssl_ = false; #endif -}; // namespace httplib +}; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT class SSLServer : public Server { public: SSLServer(const char *cert_path, const char *private_key_path, const char *client_ca_cert_file_path = nullptr, - const char *client_ca_cert_dir_path = nullptr); + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); SSLServer(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store = nullptr); + SSLServer( + const std::function &setup_ssl_ctx_callback); + ~SSLServer() override; bool is_valid() const override; + SSL_CTX *ssl_context() const; + private: bool process_and_close_socket(socket_t sock) override; @@ -1136,9 +1555,6 @@ public: bool is_valid() const override; - void set_ca_cert_path(const char *ca_cert_file_path, - const char *ca_cert_dir_path = nullptr); - void set_ca_cert_store(X509_STORE *ca_cert_store); long get_openssl_verify_result() const; @@ -1146,15 +1562,17 @@ public: SSL_CTX *ssl_context() const; private: - bool create_and_connect_socket(Socket &socket) override; - void close_socket(Socket &socket, bool process_socket_ret) override; + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_socket); - bool process_socket(Socket &socket, + bool process_socket(const Socket &socket, std::function callback) override; bool is_ssl() const override; - bool connect_with_proxy(Socket &sock, Response &res, bool &success); - bool initialize_ssl(Socket &socket); + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); bool load_certs(); @@ -1169,19 +1587,397 @@ private: std::vector host_components_; - std::string ca_cert_file_path_; - std::string ca_cert_dir_path_; - X509_STORE *ca_cert_store_ = nullptr; long verify_result_ = 0; friend class ClientImpl; }; #endif +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +template +inline T get_header_value(const Headers & /*headers*/, + const std::string & /*key*/, size_t /*id*/ = 0, + uint64_t /*def*/ = 0) {} + +template <> +inline uint64_t get_header_value(const Headers &headers, + const std::string &key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; +} + +} // namespace detail + +template +inline T Request::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline T Response::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { + const auto bufsiz = 2048; + std::array buf{}; + + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); + if (sn <= 0) { return sn; } + + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { + std::vector glowable_buf(buf.size()); + + while (n >= glowable_buf.size() - 1) { + glowable_buf.resize(glowable_buf.size() * 2); + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); + } + return write(&glowable_buf[0], n); + } else { + return write(buf.data(), n); + } +} + +inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), + sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); +#endif +#endif +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::Unknown: return "Unknown"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +template +inline T Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, 0); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(Ranges ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +std::string encode_query_param(const std::string &value); + +std::string decode_url(const std::string &s, bool convert_plus_to_space); + +void read_file(const std::string &path, std::string &out); + +std::string trim_copy(const std::string &s); + +void split(const char *b, const char *e, char d, + std::function fn); + +bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback); + +socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + size_t id = 0, const char *def = nullptr); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor : public compressor { +public: + virtual ~nocompressor() = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor : public compressor { +public: + gzip_compressor(); + ~gzip_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; +}; + +} // namespace detail + // ---------------------------------------------------------------------------- /* - * Implementation + * Implementation that will be part of the .cc file if split into .h + .cc. */ namespace detail { @@ -1227,14 +2023,6 @@ inline std::string from_i_to_hex(size_t n) { return ret; } -inline bool start_with(const std::string &a, const std::string &b) { - if (a.size() < b.size()) { return false; } - for (size_t i = 0; i < b.size(); i++) { - if (std::tolower(a[i]) != std::tolower(b[i])) { return false; } - } - return true; -} - inline size_t to_utf8(int code, char *buff) { if (code < 0x0080) { buff[0] = (code & 0x7F); @@ -1298,8 +2086,12 @@ inline std::string base64_encode(const std::string &in) { } inline bool is_file(const std::string &path) { +#ifdef _WIN32 + return _access_s(path.c_str(), 0) == 0; +#else struct stat st; return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +#endif } inline bool is_dir(const std::string &path) { @@ -1344,8 +2136,30 @@ inline bool is_valid_path(const std::string &path) { return true; } +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + inline std::string encode_url(const std::string &s) { std::string result; + result.reserve(s.size()); for (size_t i = 0; s[i]; i++) { switch (s[i]) { @@ -1446,7 +2260,8 @@ inline std::string trim_copy(const std::string &s) { return s.substr(r.first, r.second - r.first); } -template void split(const char *b, const char *e, char d, Fn fn) { +inline void split(const char *b, const char *e, char d, + std::function fn) { size_t i = 0; size_t beg = 0; @@ -1465,81 +2280,70 @@ template void split(const char *b, const char *e, char d, Fn fn) { } } -// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` -// to store data. The call can set memory on stack for performance. -class stream_line_reader { -public: - stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size) - : strm_(strm), fixed_buffer_(fixed_buffer), - fixed_buffer_size_(fixed_buffer_size) {} +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} - const char *ptr() const { - if (glowable_buffer_.empty()) { - return fixed_buffer_; - } else { - return glowable_buffer_.data(); - } +inline const char *stream_line_reader::ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); } +} - size_t size() const { - if (glowable_buffer_.empty()) { - return fixed_buffer_used_size_; - } else { - return glowable_buffer_.size(); - } +inline size_t stream_line_reader::size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); } +} - bool end_with_crlf() const { - auto end = ptr() + size(); - return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; - } +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} - bool getline() { - fixed_buffer_used_size_ = 0; - glowable_buffer_.clear(); +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); - for (size_t i = 0;; i++) { - char byte; - auto n = strm_.read(&byte, 1); + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); - if (n < 0) { + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { return false; - } else if (n == 0) { - if (i == 0) { - return false; - } else { - break; - } + } else { + break; } - - append(byte); - - if (byte == '\n') { break; } } - return true; + append(byte); + + if (byte == '\n') { break; } } -private: - void append(char c) { - if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { - fixed_buffer_[fixed_buffer_used_size_++] = c; - fixed_buffer_[fixed_buffer_used_size_] = '\0'; - } else { - if (glowable_buffer_.empty()) { - assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); - glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); - } - glowable_buffer_ += c; + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); } + glowable_buffer_ += c; } - - Stream &strm_; - char *fixed_buffer_; - const size_t fixed_buffer_size_; - size_t fixed_buffer_used_size_ = 0; - std::string glowable_buffer_; -}; +} inline int close_socket(socket_t sock) { #ifdef _WIN32 @@ -1559,6 +2363,31 @@ template inline ssize_t handle_EINTR(T fn) { return res; } +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { #ifdef CPPHTTPLIB_USE_POLL struct pollfd pfd_read; @@ -1569,6 +2398,10 @@ inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); #else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + fd_set fds; FD_ZERO(&fds); FD_SET(sock, &fds); @@ -1593,6 +2426,10 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); #else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + fd_set fds; FD_ZERO(&fds); FD_SET(sock, &fds); @@ -1607,7 +2444,8 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { #endif } -inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { #ifdef CPPHTTPLIB_USE_POLL struct pollfd pfd_read; pfd_read.fd = sock; @@ -1617,15 +2455,23 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + if (poll_res == 0) { return Error::ConnectionTimeout; } + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { int error = 0; socklen_t len = sizeof(error); auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len); - return res >= 0 && !error; + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; } - return false; + + return Error::Connection; #else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + fd_set fdsr; FD_ZERO(&fdsr); FD_SET(sock, &fdsr); @@ -1641,17 +2487,31 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); }); + if (ret == 0) { return Error::ConnectionTimeout; } + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { int error = 0; socklen_t len = sizeof(error); - return getsockopt(sock, SOL_SOCKET, SO_ERROR, - reinterpret_cast(&error), &len) >= 0 && - !error; + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; } - return false; + return Error::Connection; #endif } +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + class SocketStream : public Stream { public: SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, @@ -1663,6 +2523,8 @@ public: ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; private: socket_t sock_; @@ -1670,6 +2532,12 @@ private: time_t read_timeout_usec_; time_t write_timeout_sec_; time_t write_timeout_usec_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024 * 4; }; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -1685,6 +2553,8 @@ public: ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; private: socket_t sock_; @@ -1696,24 +2566,6 @@ private: }; #endif -class BufferStream : public Stream { -public: - BufferStream() = default; - ~BufferStream() override = default; - - bool is_readable() const override; - bool is_writable() const override; - ssize_t read(char *ptr, size_t size) override; - ssize_t write(const char *ptr, size_t size) override; - void get_remote_ip_and_port(std::string &ip, int &port) const override; - - const std::string &get_buffer() const; - -private: - std::string buffer; - size_t position = 0; -}; - inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { using namespace std::chrono; auto start = steady_clock::now(); @@ -1735,12 +2587,14 @@ inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { template inline bool -process_server_socket_core(socket_t sock, size_t keep_alive_max_count, +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, T callback) { assert(keep_alive_max_count > 0); auto ret = false; auto count = keep_alive_max_count; - while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) { + while (svr_sock != INVALID_SOCKET && count > 0 && + keep_alive(sock, keep_alive_timeout_sec)) { auto close_connection = count == 1; auto connection_closed = false; ret = callback(close_connection, connection_closed); @@ -1752,12 +2606,13 @@ process_server_socket_core(socket_t sock, size_t keep_alive_max_count, template inline bool -process_server_socket(socket_t sock, size_t keep_alive_max_count, +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec, T callback) { return process_server_socket_core( - sock, keep_alive_max_count, keep_alive_timeout_sec, + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, [&](bool close_connection, bool &connection_closed) { SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); @@ -1765,11 +2620,11 @@ process_server_socket(socket_t sock, size_t keep_alive_max_count, }); } -template inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, - time_t write_timeout_usec, T callback) { + time_t write_timeout_usec, + std::function callback) { SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); return callback(strm); @@ -1784,23 +2639,61 @@ inline int shutdown_socket(socket_t sock) { } template -socket_t create_socket(const char *host, int port, int socket_flags, - bool tcp_nodelay, SocketOptions socket_options, +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + SocketOptions socket_options, BindOrConnect bind_or_connect) { // Get address info + const char *node = nullptr; struct addrinfo hints; struct addrinfo *result; memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = socket_flags; hints.ai_protocol = 0; + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) return INVALID_SOCKET; + + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + std::copy(host.begin(), host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + + fcntl(sock, F_SETFD, FD_CLOEXEC); + if (socket_options) { socket_options(sock); } + + if (!bind_or_connect(sock, hints)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + auto service = std::to_string(port); - if (getaddrinfo(host, service.c_str(), &hints, &result)) { -#ifdef __linux__ + if (getaddrinfo(node, service.c_str(), &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ res_init(); #endif return INVALID_SOCKET; @@ -1809,8 +2702,9 @@ socket_t create_socket(const char *host, int port, int socket_flags, for (auto rp = result; rp; rp = rp->ai_next) { // Create a socket #ifdef _WIN32 - auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, - nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT); + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); /** * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 * and above the socket creation fails on older Windows Systems. @@ -1834,7 +2728,10 @@ socket_t create_socket(const char *host, int port, int socket_flags, if (sock == INVALID_SOCKET) { continue; } #ifndef _WIN32 - if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; } + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } #endif if (tcp_nodelay) { @@ -1883,7 +2780,7 @@ inline bool is_connection_error() { #endif } -inline bool bind_ip_address(socket_t sock, const char *host) { +inline bool bind_ip_address(socket_t sock, const std::string &host) { struct addrinfo hints; struct addrinfo *result; @@ -1892,7 +2789,7 @@ inline bool bind_ip_address(socket_t sock, const char *host) { hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; - if (getaddrinfo(host, "0", &hints, &result)) { return false; } + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } auto ret = false; for (auto rp = result; rp; rp = rp->ai_next) { @@ -1907,16 +2804,19 @@ inline bool bind_ip_address(socket_t sock, const char *host) { return ret; } -#if !defined _WIN32 && !defined ANDROID +#if !defined _WIN32 && !defined ANDROID && !defined _AIX #define USE_IF2IP #endif #ifdef USE_IF2IP -inline std::string if2ip(const std::string &ifn) { +inline std::string if2ip(int address_family, const std::string &ifn) { struct ifaddrs *ifap; getifaddrs(&ifap); + std::string addr_candidate; for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { - if (ifa->ifa_addr && ifn == ifa->ifa_name) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { if (ifa->ifa_addr->sa_family == AF_INET) { auto sa = reinterpret_cast(ifa->ifa_addr); char buf[INET_ADDRSTRLEN]; @@ -1924,48 +2824,94 @@ inline std::string if2ip(const std::string &ifn) { freeifaddrs(ifap); return std::string(buf, INET_ADDRSTRLEN); } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + freeifaddrs(ifap); + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } } } } freeifaddrs(ifap); - return std::string(); + return addr_candidate; } #endif -inline socket_t create_client_socket(const char *host, int port, - bool tcp_nodelay, - SocketOptions socket_options, - time_t timeout_sec, time_t timeout_usec, - const std::string &intf, Error &error) { +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { auto sock = create_socket( - host, port, 0, tcp_nodelay, socket_options, - [&](socket_t sock, struct addrinfo &ai) -> bool { + host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai) -> bool { if (!intf.empty()) { #ifdef USE_IF2IP - auto ip = if2ip(intf); - if (ip.empty()) { ip = intf; } - if (!bind_ip_address(sock, ip.c_str())) { + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if.c_str())) { error = Error::BindIPAddress; return false; } #endif } - set_nonblocking(sock, true); + set_nonblocking(sock2, true); auto ret = - ::connect(sock, ai.ai_addr, static_cast(ai.ai_addrlen)); + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); if (ret < 0) { - if (is_connection_error() || - !wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) { - close_socket(sock); + if (is_connection_error()) { error = Error::Connection; return false; } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { return false; } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif } - set_nonblocking(sock, false); error = Error::Success; return true; }); @@ -1979,21 +2925,34 @@ inline socket_t create_client_socket(const char *host, int port, return sock; } -inline void get_remote_ip_and_port(const struct sockaddr_storage &addr, - socklen_t addr_len, std::string &ip, - int &port) { +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { if (addr.ss_family == AF_INET) { port = ntohs(reinterpret_cast(&addr)->sin_port); } else if (addr.ss_family == AF_INET6) { port = ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; } std::array ipstr{}; - if (!getnameinfo(reinterpret_cast(&addr), addr_len, - ipstr.data(), static_cast(ipstr.size()), nullptr, - 0, NI_NUMERICHOST)) { - ip = ipstr.data(); + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); } } @@ -2003,10 +2962,52 @@ inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { if (!getpeername(sock, reinterpret_cast(&addr), &addr_len)) { - get_remote_ip_and_port(addr, addr_len, ip, port); +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); } } +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + inline const char * find_content_type(const std::string &path, const std::map &user_data) { @@ -2015,36 +3016,60 @@ find_content_type(const std::string &path, auto it = user_data.find(ext); if (it != user_data.end()) { return it->second.c_str(); } - if (ext == "txt") { - return "text/plain"; - } else if (ext == "html" || ext == "htm") { - return "text/html"; - } else if (ext == "css") { - return "text/css"; - } else if (ext == "jpeg" || ext == "jpg") { - return "image/jpg"; - } else if (ext == "png") { - return "image/png"; - } else if (ext == "gif") { - return "image/gif"; - } else if (ext == "svg") { - return "image/svg+xml"; - } else if (ext == "ico") { - return "image/x-icon"; - } else if (ext == "json") { - return "application/json"; - } else if (ext == "pdf") { - return "application/pdf"; - } else if (ext == "js") { - return "application/javascript"; - } else if (ext == "wasm") { - return "application/wasm"; - } else if (ext == "xml") { - return "application/xml"; - } else if (ext == "xhtml") { - return "application/xhtml+xml"; + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return nullptr; + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; } - return nullptr; } inline const char *status_message(int status) { @@ -2118,15 +3143,22 @@ inline const char *status_message(int status) { } inline bool can_compress_content_type(const std::string &content_type) { - return (!content_type.find("text/") && content_type != "text/event-stream") || - content_type == "image/svg+xml" || - content_type == "application/javascript" || - content_type == "application/json" || - content_type == "application/xml" || - content_type == "application/xhtml+xml"; -} + using udl::operator""_t; -enum class EncodingType { None = 0, Gzip, Brotli }; + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + default: + return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t; + } +} inline EncodingType encoding_type(const Request &req, const Response &res) { auto ret = @@ -2151,120 +3183,109 @@ inline EncodingType encoding_type(const Request &req, const Response &res) { return EncodingType::None; } -class compressor { -public: - virtual ~compressor(){}; - - typedef std::function Callback; - virtual bool compress(const char *data, size_t data_length, bool last, - Callback callback) = 0; -}; - -class decompressor { -public: - virtual ~decompressor() {} - - virtual bool is_valid() const = 0; - - typedef std::function Callback; - virtual bool decompress(const char *data, size_t data_length, - Callback callback) = 0; -}; - -class nocompressor : public compressor { -public: - ~nocompressor(){}; - - bool compress(const char *data, size_t data_length, bool /*last*/, - Callback callback) override { - if (!data_length) { return true; } - return callback(data, data_length); - } -}; +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} #ifdef CPPHTTPLIB_ZLIB_SUPPORT -class gzip_compressor : public compressor { -public: - gzip_compressor() { - std::memset(&strm_, 0, sizeof(strm_)); - strm_.zalloc = Z_NULL; - strm_.zfree = Z_NULL; - strm_.opaque = Z_NULL; +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; - is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, - Z_DEFAULT_STRATEGY) == Z_OK; - } + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} - ~gzip_compressor() { deflateEnd(&strm_); } +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } - bool compress(const char *data, size_t data_length, bool last, - Callback callback) override { - assert(is_valid_); +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); - auto flush = last ? Z_FINISH : Z_NO_FLUSH; + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); - strm_.avail_in = static_cast(data_length); + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); strm_.next_in = const_cast(reinterpret_cast(data)); + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; int ret = Z_OK; std::array buff{}; do { - strm_.avail_out = buff.size(); + strm_.avail_out = static_cast(buff.size()); strm_.next_out = reinterpret_cast(buff.data()); ret = deflate(&strm_, flush); - assert(ret != Z_STREAM_ERROR); + if (ret == Z_STREAM_ERROR) { return false; } if (!callback(buff.data(), buff.size() - strm_.avail_out)) { return false; } } while (strm_.avail_out == 0); - assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK)); + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); assert(strm_.avail_in == 0); - return true; - } + } while (data_length > 0); -private: - bool is_valid_ = false; - z_stream strm_; -}; + return true; +} -class gzip_decompressor : public decompressor { -public: - gzip_decompressor() { - std::memset(&strm_, 0, sizeof(strm_)); - strm_.zalloc = Z_NULL; - strm_.zfree = Z_NULL; - strm_.opaque = Z_NULL; +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; - // 15 is the value of wbits, which should be at the maximum possible value - // to ensure that any gzip stream can be decoded. The offset of 32 specifies - // that the stream type should be automatically detected either gzip or - // deflate. - is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; - } + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} - ~gzip_decompressor() { inflateEnd(&strm_); } +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } - bool is_valid() const override { return is_valid_; } +inline bool gzip_decompressor::is_valid() const { return is_valid_; } - bool decompress(const char *data, size_t data_length, - Callback callback) override { - assert(is_valid_); +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); - int ret = Z_OK; + int ret = Z_OK; - strm_.avail_in = static_cast(data_length); + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); strm_.next_in = const_cast(reinterpret_cast(data)); + data_length -= strm_.avail_in; + data += strm_.avail_in; + std::array buff{}; while (strm_.avail_in > 0) { - strm_.avail_out = buff.size(); + strm_.avail_out = static_cast(buff.size()); strm_.next_out = reinterpret_cast(buff.data()); + auto prev_avail_in = strm_.avail_in; + ret = inflate(&strm_, Z_NO_FLUSH); + + if (prev_avail_in - strm_.avail_in == 0) { return false; } + assert(ret != Z_STREAM_ERROR); switch (ret) { case Z_NEED_DICT: @@ -2277,118 +3298,107 @@ public: } } - return ret == Z_OK || ret == Z_STREAM_END; - } + if (ret != Z_OK && ret != Z_STREAM_END) return false; -private: - bool is_valid_ = false; - z_stream strm_; -}; + } while (data_length > 0); + + return true; +} #endif #ifdef CPPHTTPLIB_BROTLI_SUPPORT -class brotli_compressor : public compressor { -public: - brotli_compressor() { - state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); - } +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} - ~brotli_compressor() { BrotliEncoderDestroyInstance(state_); } +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} - bool compress(const char *data, size_t data_length, bool last, - Callback callback) override { - std::array buff{}; +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; - auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; - auto available_in = data_length; - auto next_in = reinterpret_cast(data); + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); - for (;;) { - if (last) { - if (BrotliEncoderIsFinished(state_)) { break; } - } else { - if (!available_in) { break; } - } - - auto available_out = buff.size(); - auto next_out = buff.data(); - - if (!BrotliEncoderCompressStream(state_, operation, &available_in, - &next_in, &available_out, &next_out, - nullptr)) { - return false; - } - - auto output_bytes = buff.size() - available_out; - if (output_bytes) { - callback(reinterpret_cast(buff.data()), output_bytes); - } + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } } - return true; - } + auto available_out = buff.size(); + auto next_out = buff.data(); -private: - BrotliEncoderState *state_ = nullptr; -}; - -class brotli_decompressor : public decompressor { -public: - brotli_decompressor() { - decoder_s = BrotliDecoderCreateInstance(0, 0, 0); - decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT - : BROTLI_DECODER_RESULT_ERROR; - } - - ~brotli_decompressor() { - if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } - } - - bool is_valid() const override { return decoder_s; } - - bool decompress(const char *data, size_t data_length, - Callback callback) override { - if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || - decoder_r == BROTLI_DECODER_RESULT_ERROR) { - return 0; + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; } - const uint8_t *next_in = (const uint8_t *)data; - size_t avail_in = data_length; - size_t total_out; - - decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; - - std::array buff{}; - while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { - char *next_out = buff.data(); - size_t avail_out = buff.size(); - - decoder_r = BrotliDecoderDecompressStream( - decoder_s, &avail_in, &next_in, &avail_out, - reinterpret_cast(&next_out), &total_out); - - if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } - - if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); } - - return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || - decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; } -private: - BrotliDecoderResult decoder_r; - BrotliDecoderState *decoder_s = nullptr; -}; + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + const uint8_t *next_in = (const uint8_t *)data; + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} #endif -inline bool has_header(const Headers &headers, const char *key) { +inline bool has_header(const Headers &headers, const std::string &key) { return headers.find(key) != headers.end(); } -inline const char *get_header_value(const Headers &headers, const char *key, - size_t id = 0, const char *def = nullptr) { +inline const char *get_header_value(const Headers &headers, + const std::string &key, size_t id, + const char *def) { auto rng = headers.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); @@ -2396,21 +3406,12 @@ inline const char *get_header_value(const Headers &headers, const char *key, return def; } -template -inline T get_header_value(const Headers & /*headers*/, const char * /*key*/, - size_t /*id*/ = 0, uint64_t /*def*/ = 0) {} - -template <> -inline uint64_t get_header_value(const Headers &headers, - const char *key, size_t id, - uint64_t def) { - auto rng = headers.equal_range(key); - auto it = rng.first; - std::advance(it, static_cast(id)); - if (it != rng.second) { - return std::strtoull(it->second.data(), nullptr, 10); +inline bool compare_case_ignore(const std::string &a, const std::string &b) { + if (a.size() != b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } } - return def; + return true; } template @@ -2436,7 +3437,11 @@ inline bool parse_header(const char *beg, const char *end, T fn) { } if (p < end) { - fn(std::string(beg, key_end), decode_url(std::string(p, end), false)); + auto key = std::string(beg, key_end); + auto val = compare_case_ignore(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); + fn(std::move(key), std::move(val)); return true; } @@ -2452,15 +3457,26 @@ inline bool read_headers(Stream &strm, Headers &headers) { if (!line_reader.getline()) { return false; } // Check if the line ends with CRLF. + auto line_terminator_len = 2; if (line_reader.end_with_crlf()) { // Blank line indicates end of headers. if (line_reader.size() == 2) { break; } +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + } else { + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; + } +#else } else { continue; // Skip invalid line. } +#endif - // Exclude CRLF - auto end = line_reader.ptr() + line_reader.size() - 2; + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; parse_header(line_reader.ptr(), end, [&](std::string &&key, std::string &&val) { @@ -2472,7 +3488,8 @@ inline bool read_headers(Stream &strm, Headers &headers) { } inline bool read_content_with_length(Stream &strm, uint64_t len, - Progress progress, ContentReceiver out) { + Progress progress, + ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; uint64_t r = 0; @@ -2481,8 +3498,7 @@ inline bool read_content_with_length(Stream &strm, uint64_t len, auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); if (n <= 0) { return false; } - if (!out(buf, static_cast(n))) { return false; } - + if (!out(buf, static_cast(n), r, len)) { return false; } r += static_cast(n); if (progress) { @@ -2504,8 +3520,10 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) { } } -inline bool read_content_without_length(Stream &strm, ContentReceiver out) { +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; for (;;) { auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); if (n < 0) { @@ -2513,13 +3531,17 @@ inline bool read_content_without_length(Stream &strm, ContentReceiver out) { } else if (n == 0) { return true; } - if (!out(buf, static_cast(n))) { return false; } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); } return true; } -inline bool read_content_chunked(Stream &strm, ContentReceiver out) { +template +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { const auto bufsiz = 16; char buf[bufsiz]; @@ -2544,15 +3566,29 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) { if (!line_reader.getline()) { return false; } - if (strcmp(line_reader.ptr(), "\r\n")) { break; } + if (strcmp(line_reader.ptr(), "\r\n")) { return false; } if (!line_reader.getline()) { return false; } } - if (chunk_len == 0) { - // Reader terminator after chunks - if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n")) - return false; + assert(chunk_len == 0); + + // Trailer + if (!line_reader.getline()) { return false; } + + while (strcmp(line_reader.ptr(), "\r\n")) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + x.headers.emplace(std::move(key), std::move(val)); + }); + + if (!line_reader.getline()) { return false; } } return true; @@ -2564,23 +3600,23 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) { } template -bool prepare_content_receiver(T &x, int &status, ContentReceiver receiver, +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, bool decompress, U callback) { if (decompress) { std::string encoding = x.get_header_value("Content-Encoding"); - std::shared_ptr decompressor; + std::unique_ptr decompressor; - if (encoding.find("gzip") != std::string::npos || - encoding.find("deflate") != std::string::npos) { + if (encoding == "gzip" || encoding == "deflate") { #ifdef CPPHTTPLIB_ZLIB_SUPPORT - decompressor = std::make_shared(); + decompressor = detail::make_unique(); #else status = 415; return false; #endif } else if (encoding.find("br") != std::string::npos) { #ifdef CPPHTTPLIB_BROTLI_SUPPORT - decompressor = std::make_shared(); + decompressor = detail::make_unique(); #else status = 415; return false; @@ -2589,12 +3625,14 @@ bool prepare_content_receiver(T &x, int &status, ContentReceiver receiver, if (decompressor) { if (decompressor->is_valid()) { - ContentReceiver out = [&](const char *buf, size_t n) { - return decompressor->decompress( - buf, n, - [&](const char *buf, size_t n) { return receiver(buf, n); }); + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); }; - return callback(out); + return callback(std::move(out)); } else { status = 500; return false; @@ -2602,23 +3640,25 @@ bool prepare_content_receiver(T &x, int &status, ContentReceiver receiver, } } - ContentReceiver out = [&](const char *buf, size_t n) { - return receiver(buf, n); + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); }; - return callback(out); + return callback(std::move(out)); } template bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, - Progress progress, ContentReceiver receiver, + Progress progress, ContentReceiverWithProgress receiver, bool decompress) { return prepare_content_receiver( - x, status, receiver, decompress, [&](const ContentReceiver &out) { + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { auto ret = true; auto exceed_payload_max_length = false; if (is_chunked_transfer_encoding(x.headers)) { - ret = read_content_chunked(strm, out); + ret = read_content_chunked(strm, x, out); } else if (!has_header(x.headers, "Content-Length")) { ret = read_content_without_length(strm, out); } else { @@ -2628,26 +3668,17 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, skip_content_with_length(strm, len); ret = false; } else if (len > 0) { - ret = read_content_with_length(strm, len, progress, out); + ret = read_content_with_length(strm, len, std::move(progress), out); } } if (!ret) { status = exceed_payload_max_length ? 413 : 400; } return ret; }); -} +} // namespace detail -template -inline ssize_t write_headers(Stream &strm, const T &info, - const Headers &headers) { +inline ssize_t write_headers(Stream &strm, const Headers &headers) { ssize_t write_len = 0; - for (const auto &x : info.headers) { - if (x.first == "EXCEPTION_WHAT") { continue; } - auto len = - strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); - if (len < 0) { return len; } - write_len += len; - } for (const auto &x : headers) { auto len = strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); @@ -2671,99 +3702,119 @@ inline bool write_data(Stream &strm, const char *d, size_t l) { } template -inline ssize_t write_content(Stream &strm, ContentProvider content_provider, - size_t offset, size_t length, T is_shutting_down) { - size_t begin_offset = offset; +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { size_t end_offset = offset + length; auto ok = true; DataSink data_sink; - data_sink.write = [&](const char *d, size_t l) { + data_sink.write = [&](const char *d, size_t l) -> bool { if (ok) { - offset += l; - if (!write_data(strm, d, l)) { ok = false; } + if (strm.is_writable() && write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } } + return ok; }; - data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; - while (offset < end_offset && !is_shutting_down()) { - if (!content_provider(offset, end_offset - offset, data_sink)) { - return -1; + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; } - if (!ok) { return -1; } } - return static_cast(offset - begin_offset); + error = Error::Success; + return true; } template -inline ssize_t write_content_without_length(Stream &strm, - ContentProvider content_provider, - T is_shutting_down) { +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { size_t offset = 0; auto data_available = true; auto ok = true; DataSink data_sink; - data_sink.write = [&](const char *d, size_t l) { + data_sink.write = [&](const char *d, size_t l) -> bool { if (ok) { offset += l; - if (!write_data(strm, d, l)) { ok = false; } + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } } + return ok; }; data_sink.done = [&](void) { data_available = false; }; - data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; - while (data_available && !is_shutting_down()) { - if (!content_provider(offset, 0, data_sink)) { return -1; } - if (!ok) { return -1; } + if (!strm.is_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } } - - return static_cast(offset); + return true; } template -inline ssize_t write_content_chunked(Stream &strm, - ContentProvider content_provider, - T is_shutting_down, U &compressor) { +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { size_t offset = 0; auto data_available = true; - ssize_t total_written_length = 0; auto ok = true; DataSink data_sink; - data_sink.write = [&](const char *d, size_t l) { - if (!ok) { return; } + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; - data_available = l > 0; - offset += l; - - std::string payload; - if (!compressor.compress(d, l, false, - [&](const char *data, size_t data_len) { - payload.append(data, data_len); - return true; - })) { - ok = false; - return; - } - - if (!payload.empty()) { - // Emit chunked response header and footer for each chunk - auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; - if (write_data(strm, chunk.data(), chunk.size())) { - total_written_length += chunk.size(); + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } } else { ok = false; - return; } } + return ok; }; - data_sink.done = [&](void) { + auto done_with_trailer = [&](const Headers *trailer) { if (!ok) { return; } data_available = false; @@ -2781,38 +3832,71 @@ inline ssize_t write_content_chunked(Stream &strm, if (!payload.empty()) { // Emit chunked response header and footer for each chunk auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; - if (write_data(strm, chunk.data(), chunk.size())) { - total_written_length += chunk.size(); - } else { + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { ok = false; return; } } - static const std::string done_marker("0\r\n\r\n"); - if (write_data(strm, done_marker.data(), done_marker.size())) { - total_written_length += done_marker.size(); - } else { + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { ok = false; } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } }; - data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; while (data_available && !is_shutting_down()) { - if (!content_provider(offset, 0, data_sink)) { return -1; } - if (!ok) { return -1; } + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } } - return total_written_length; + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); } template -inline bool redirect(T &cli, const Request &req, Response &res, - const std::string &path) { +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { Request new_req = req; new_req.path = path; - new_req.redirect_count -= 1; + new_req.redirect_count_ -= 1; if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { new_req.method = "GET"; @@ -2822,8 +3906,12 @@ inline bool redirect(T &cli, const Request &req, Response &res, Response new_res; - auto ret = cli.send(new_req, new_res); - if (ret) { res = new_res; } + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + res.location = location; + } return ret; } @@ -2834,13 +3922,18 @@ inline std::string params_to_query_str(const Params ¶ms) { if (it != params.begin()) { query += "&"; } query += it->first; query += "="; - query += encode_url(it->second); + query += encode_query_param(it->second); } return query; } inline void parse_query_text(const std::string &s, Params ¶ms) { + std::set cache; split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + std::string key; std::string val; split(b, e, '=', [&](const char *b2, const char *e2) { @@ -2859,9 +3952,12 @@ inline void parse_query_text(const std::string &s, Params ¶ms) { inline bool parse_multipart_boundary(const std::string &content_type, std::string &boundary) { - auto pos = content_type.find("boundary="); + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); if (pos == std::string::npos) { return false; } - boundary = content_type.substr(pos + 9); + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = content_type.substr(beg, end - beg); if (boundary.length() >= 2 && boundary.front() == '"' && boundary.back() == '"') { boundary = boundary.substr(1, boundary.size() - 2); @@ -2869,7 +3965,11 @@ inline bool parse_multipart_boundary(const std::string &content_type, return !boundary.empty(); } +#ifdef CPPHTTPLIB_NO_EXCEPTIONS inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); std::smatch m; if (std::regex_match(s, m, re_first_range)) { @@ -2901,37 +4001,41 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) { return all_valid_ranges; } return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS } +#else +} catch (...) { return false; } +#endif class MultipartFormDataParser { public: MultipartFormDataParser() = default; - void set_boundary(std::string &&boundary) { boundary_ = boundary; } + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } bool is_valid() const { return is_valid_; } - template - bool parse(const char *buf, size_t n, T content_callback, U header_callback) { + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + // TODO: support 'filename*' static const std::regex re_content_disposition( - "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename=" - "\"(.*?)\")?\\s*$", + R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~", std::regex_constants::icase); - static const std::string dash_ = "--"; - static const std::string crlf_ = "\r\n"; - buf_.append(buf, n); // TODO: performance improvement + buf_append(buf, n); - while (!buf_.empty()) { + while (buf_size() > 0) { switch (state_) { case 0: { // Initial boundary - auto pattern = dash_ + boundary_ + crlf_; - if (pattern.size() > buf_.size()) { return true; } - auto pos = buf_.find(pattern); - if (pos != 0) { return false; } - buf_.erase(0, pattern.size()); - off_ += pattern.size(); + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); state_ = 1; break; } @@ -2941,113 +4045,80 @@ public: break; } case 2: { // Headers - auto pos = buf_.find(crlf_); - while (pos != std::string::npos) { + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { // Empty line if (pos == 0) { if (!header_callback(file_)) { is_valid_ = false; return false; } - buf_.erase(0, crlf_.size()); - off_ += crlf_.size(); + buf_erase(crlf_.size()); state_ = 3; break; } static const std::string header_name = "content-type:"; - const auto header = buf_.substr(0, pos); - if (start_with(header, header_name)) { + const auto header = buf_head(pos); + if (start_with_case_ignore(header, header_name)) { file_.content_type = trim_copy(header.substr(header_name.size())); } else { std::smatch m; if (std::regex_match(header, m, re_content_disposition)) { file_.name = m[1]; file_.filename = m[2]; + } else { + is_valid_ = false; + return false; } } - - buf_.erase(0, pos + crlf_.size()); - off_ += pos + crlf_.size(); - pos = buf_.find(crlf_); + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); } if (state_ != 3) { return true; } break; } case 3: { // Body - { - auto pattern = crlf_ + dash_; - if (pattern.size() > buf_.size()) { return true; } - - auto pos = buf_.find(pattern); - if (pos == std::string::npos) { - pos = buf_.size(); - while (pos > 0) { - auto c = buf_[pos - 1]; - if (c != '\r' && c != '\n' && c != '-') { break; } - pos--; - } - } - - if (!content_callback(buf_.data(), pos)) { + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { is_valid_ = false; return false; } - - off_ += pos; - buf_.erase(0, pos); - } - - { - auto pattern = crlf_ + dash_ + boundary_; - if (pattern.size() > buf_.size()) { return true; } - - auto pos = buf_.find(pattern); - if (pos != std::string::npos) { - if (!content_callback(buf_.data(), pos)) { + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { is_valid_ = false; return false; } - - off_ += pos + pattern.size(); - buf_.erase(0, pos + pattern.size()); - state_ = 4; - } else { - if (!content_callback(buf_.data(), pattern.size())) { - is_valid_ = false; - return false; - } - - off_ += pattern.size(); - buf_.erase(0, pattern.size()); + buf_erase(len); } + return true; } break; } case 4: { // Boundary - if (crlf_.size() > buf_.size()) { return true; } - if (buf_.compare(0, crlf_.size(), crlf_) == 0) { - buf_.erase(0, crlf_.size()); - off_ += crlf_.size(); + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); state_ = 1; } else { - auto pattern = dash_ + crlf_; - if (pattern.size() > buf_.size()) { return true; } - if (buf_.compare(0, pattern.size(), pattern) == 0) { - buf_.erase(0, pattern.size()); - off_ += pattern.size(); + if (dash_crlf_.size() > buf_size()) { return true; } + if (buf_start_with(dash_crlf_)) { + buf_erase(dash_crlf_.size()); is_valid_ = true; - state_ = 5; + buf_erase(buf_size()); // Remove epilogue } else { return true; } } break; } - case 5: { // Done - is_valid_ = false; - return false; - } } } @@ -3061,13 +4132,92 @@ private: file_.content_type.clear(); } - std::string boundary_; + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + const std::string dash_crlf_ = "--\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; - std::string buf_; size_t state_ = 0; bool is_valid_ = false; - size_t off_ = 0; MultipartFormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; }; inline std::string to_lower(const char *beg, const char *end) { @@ -3084,8 +4234,14 @@ inline std::string make_multipart_data_boundary() { static const char data[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. std::random_device seed_gen; - std::mt19937 engine(seed_gen()); + + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + std::mt19937 engine(seed_sequence); std::string result = "--cpp-httplib-multipart-data-"; @@ -3096,6 +4252,63 @@ inline std::string make_multipart_data_boundary() { return result; } +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const MultipartFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) body += serialize_multipart_formdata_finish(boundary); + + return body; +} + inline std::pair get_range_offset_and_length(const Request &req, size_t content_length, size_t index) { @@ -3108,13 +4321,12 @@ get_range_offset_and_length(const Request &req, size_t content_length, auto slen = static_cast(content_length); if (r.first == -1) { - r.first = slen - r.second; + r.first = (std::max)(static_cast(0), slen - r.second); r.second = slen - 1; } if (r.second == -1) { r.second = slen - 1; } - - return std::make_pair(r.first, r.second - r.first + 1); + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); } inline std::string make_content_range_header_field(size_t offset, size_t length, @@ -3163,21 +4375,21 @@ bool process_multipart_ranges_data(const Request &req, Response &res, return true; } -inline std::string make_multipart_ranges_data(const Request &req, Response &res, - const std::string &boundary, - const std::string &content_type) { - std::string data; - - process_multipart_ranges_data( +inline bool make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + std::string &data) { + return process_multipart_ranges_data( req, res, boundary, content_type, [&](const std::string &token) { data += token; }, - [&](const char *token) { data += token; }, + [&](const std::string &token) { data += token; }, [&](size_t offset, size_t length) { - data += res.body.substr(offset, length); - return true; + if (offset < res.body.size()) { + data += res.body.substr(offset, length); + return true; + } + return false; }); - - return data; } inline size_t @@ -3189,7 +4401,7 @@ get_multipart_ranges_data_length(const Request &req, Response &res, process_multipart_ranges_data( req, res, boundary, content_type, [&](const std::string &token) { data_length += token.size(); }, - [&](const char *token) { data_length += strlen(token); }, + [&](const std::string &token) { data_length += token.size(); }, [&](size_t /*offset*/, size_t length) { data_length += length; return true; @@ -3203,14 +4415,14 @@ inline bool write_multipart_ranges_data(Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type, - T is_shutting_down) { + const T &is_shutting_down) { return process_multipart_ranges_data( req, res, boundary, content_type, [&](const std::string &token) { strm.write(token); }, - [&](const char *token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, [&](size_t offset, size_t length) { return write_content(strm, res.content_provider_, offset, length, - is_shutting_down) >= 0; + is_shutting_down); }); } @@ -3235,8 +4447,8 @@ inline bool expect_content(const Request &req) { return false; } -inline bool has_crlf(const char *s) { - auto p = s; +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); while (*p) { if (*p == '\r' || *p == '\n') { return true; } p++; @@ -3245,52 +4457,51 @@ inline bool has_crlf(const char *s) { } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -template -inline std::string message_digest(const std::string &s, Init init, - Update update, Final final, - size_t digest_length) { - using namespace std; +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); - std::vector md(digest_length, 0); - CTX ctx; - init(&ctx); - update(&ctx, s.data(), s.size()); - final(md.data(), &ctx); + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; - stringstream ss; - for (auto c : md) { - ss << setfill('0') << setw(2) << hex << (unsigned int)c; + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << (unsigned int)hash[i]; } + return ss.str(); } inline std::string MD5(const std::string &s) { - return message_digest(s, MD5_Init, MD5_Update, MD5_Final, - MD5_DIGEST_LENGTH); + return message_digest(s, EVP_md5()); } inline std::string SHA_256(const std::string &s) { - return message_digest(s, SHA256_Init, SHA256_Update, SHA256_Final, - SHA256_DIGEST_LENGTH); + return message_digest(s, EVP_sha256()); } inline std::string SHA_512(const std::string &s) { - return message_digest(s, SHA512_Init, SHA512_Update, SHA512_Final, - SHA512_DIGEST_LENGTH); + return message_digest(s, EVP_sha512()); } #endif -#ifdef _WIN32 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 // NOTE: This code came up with the following stackoverflow post: // https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store inline bool load_system_certs_on_windows(X509_STORE *store) { auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); - if (!hStore) { return false; } + auto result = false; PCCERT_CONTEXT pContext = NULL; - while (pContext = CertEnumCertificatesInStore(hStore, pContext)) { + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { auto encoded_cert = static_cast(pContext->pbCertEncoded); @@ -3298,24 +4509,121 @@ inline bool load_system_certs_on_windows(X509_STORE *store) { if (x509) { X509_STORE_add_cert(store, x509); X509_free(x509); + result = true; } } CertFreeCertificateContext(pContext); CertCloseStore(hStore, 0); + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); return true; } -#endif +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (int i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 class WSInit { public: WSInit() { WSADATA wsaData; - WSAStartup(0x0002, &wsaData); + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; } - ~WSInit() { WSACleanup(); } + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; }; static WSInit wsinit_; @@ -3326,45 +4634,57 @@ inline std::pair make_digest_authentication_header( const Request &req, const std::map &auth, size_t cnonce_count, const std::string &cnonce, const std::string &username, const std::string &password, bool is_proxy = false) { - using namespace std; - - string nc; + std::string nc; { - stringstream ss; - ss << setfill('0') << setw(8) << hex << cnonce_count; + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; nc = ss.str(); } - auto qop = auth.at("qop"); - if (qop.find("auth-int") != std::string::npos) { - qop = "auth-int"; - } else { - qop = "auth"; + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } } std::string algo = "MD5"; if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } - string response; + std::string response; { - auto H = algo == "SHA-256" - ? detail::SHA_256 - : algo == "SHA-512" ? detail::SHA_512 : detail::MD5; + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; auto A1 = username + ":" + auth.at("realm") + ":" + password; auto A2 = req.method + ":" + req.path; if (qop == "auth-int") { A2 += ":" + H(req.body); } - response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + - ":" + qop + ":" + H(A2)); + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } } + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + auto field = "Digest username=\"" + username + "\", realm=\"" + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path + "\", algorithm=" + algo + - ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce + - "\", response=\"" + response + "\""; + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; return std::make_pair(key, field); @@ -3411,7 +4731,7 @@ inline std::string random_string(size_t length) { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; const size_t max_index = (sizeof(charset) - 1); - return charset[static_cast(rand()) % max_index]; + return charset[static_cast(std::rand()) % max_index]; }; std::string str(length, 0); std::generate_n(str.begin(), length, randchar); @@ -3434,6 +4754,53 @@ private: } // namespace detail +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + int dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } + + freeaddrinfo(result); +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + // Header utilities inline std::pair make_range_header(Ranges ranges) { std::string field = "bytes="; @@ -3445,16 +4812,15 @@ inline std::pair make_range_header(Ranges ranges) { if (r.second != -1) { field += std::to_string(r.second); } i++; } - return std::make_pair("Range", field); + return std::make_pair("Range", std::move(field)); } inline std::pair make_basic_authentication_header(const std::string &username, - const std::string &password, - bool is_proxy = false) { + const std::string &password, bool is_proxy) { auto field = "Basic " + detail::base64_encode(username + ":" + password); auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; - return std::make_pair(key, field); + return std::make_pair(key, std::move(field)); } inline std::pair @@ -3462,45 +4828,37 @@ make_bearer_token_authentication_header(const std::string &token, bool is_proxy = false) { auto field = "Bearer " + token; auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; - return std::make_pair(key, field); + return std::make_pair(key, std::move(field)); } // Request implementation -inline bool Request::has_header(const char *key) const { +inline bool Request::has_header(const std::string &key) const { return detail::has_header(headers, key); } -inline std::string Request::get_header_value(const char *key, size_t id) const { +inline std::string Request::get_header_value(const std::string &key, + size_t id) const { return detail::get_header_value(headers, key, id, ""); } -template -inline T Request::get_header_value(const char *key, size_t id) const { - return detail::get_header_value(headers, key, id, 0); -} - -inline size_t Request::get_header_value_count(const char *key) const { +inline size_t Request::get_header_value_count(const std::string &key) const { auto r = headers.equal_range(key); return static_cast(std::distance(r.first, r.second)); } -inline void Request::set_header(const char *key, const char *val) { +inline void Request::set_header(const std::string &key, + const std::string &val) { if (!detail::has_crlf(key) && !detail::has_crlf(val)) { headers.emplace(key, val); } } -inline void Request::set_header(const char *key, const std::string &val) { - if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { - headers.emplace(key, val); - } -} - -inline bool Request::has_param(const char *key) const { +inline bool Request::has_param(const std::string &key) const { return params.find(key) != params.end(); } -inline std::string Request::get_param_value(const char *key, size_t id) const { +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { auto rng = params.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); @@ -3508,59 +4866,59 @@ inline std::string Request::get_param_value(const char *key, size_t id) const { return std::string(); } -inline size_t Request::get_param_value_count(const char *key) const { +inline size_t Request::get_param_value_count(const std::string &key) const { auto r = params.equal_range(key); return static_cast(std::distance(r.first, r.second)); } inline bool Request::is_multipart_form_data() const { const auto &content_type = get_header_value("Content-Type"); - return !content_type.find("multipart/form-data"); + return !content_type.rfind("multipart/form-data", 0); } -inline bool Request::has_file(const char *key) const { +inline bool Request::has_file(const std::string &key) const { return files.find(key) != files.end(); } -inline MultipartFormData Request::get_file_value(const char *key) const { +inline MultipartFormData Request::get_file_value(const std::string &key) const { auto it = files.find(key); if (it != files.end()) { return it->second; } return MultipartFormData(); } +inline std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + // Response implementation -inline bool Response::has_header(const char *key) const { +inline bool Response::has_header(const std::string &key) const { return headers.find(key) != headers.end(); } -inline std::string Response::get_header_value(const char *key, +inline std::string Response::get_header_value(const std::string &key, size_t id) const { return detail::get_header_value(headers, key, id, ""); } -template -inline T Response::get_header_value(const char *key, size_t id) const { - return detail::get_header_value(headers, key, id, 0); -} - -inline size_t Response::get_header_value_count(const char *key) const { +inline size_t Response::get_header_value_count(const std::string &key) const { auto r = headers.equal_range(key); return static_cast(std::distance(r.first, r.second)); } -inline void Response::set_header(const char *key, const char *val) { +inline void Response::set_header(const std::string &key, + const std::string &val) { if (!detail::has_crlf(key) && !detail::has_crlf(val)) { headers.emplace(key, val); } } -inline void Response::set_header(const char *key, const std::string &val) { - if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { - headers.emplace(key, val); - } -} - -inline void Response::set_redirect(const char *url, int stat) { +inline void Response::set_redirect(const std::string &url, int stat) { if (!detail::has_crlf(url)) { set_header("Location", url); if (300 <= stat && stat < 400) { @@ -3571,55 +4929,67 @@ inline void Response::set_redirect(const char *url, int stat) { } } -inline void Response::set_redirect(const std::string &url, int stat) { - set_redirect(url.c_str(), stat); -} - inline void Response::set_content(const char *s, size_t n, - const char *content_type) { + const std::string &content_type) { body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); set_header("Content-Type", content_type); } -inline void Response::set_content(std::string s, const char *content_type) { - body = std::move(s); - set_header("Content-Type", content_type); +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); } -inline void -Response::set_content_provider(size_t in_length, const char *content_type, - ContentProvider provider, - const std::function &resource_releaser) { - assert(in_length > 0); +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { set_header("Content-Type", content_type); content_length_ = in_length; - content_provider_ = std::move(provider); + if (in_length > 0) { content_provider_ = std::move(provider); } content_provider_resource_releaser_ = resource_releaser; - is_chunked_content_provider = false; + is_chunked_content_provider_ = false; } -inline void -Response::set_content_provider(const char *content_type, - ContentProviderWithoutLength provider, - const std::function &resource_releaser) { +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { set_header("Content-Type", content_type); content_length_ = 0; content_provider_ = detail::ContentProviderAdapter(std::move(provider)); content_provider_resource_releaser_ = resource_releaser; - is_chunked_content_provider = false; + is_chunked_content_provider_ = false; } inline void Response::set_chunked_content_provider( - const char *content_type, ContentProviderWithoutLength provider, - const std::function &resource_releaser) { + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { set_header("Content-Type", content_type); content_length_ = 0; content_provider_ = detail::ContentProviderAdapter(std::move(provider)); content_provider_resource_releaser_ = resource_releaser; - is_chunked_content_provider = true; + is_chunked_content_provider_ = true; } -// Rstream implementation +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, ""); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation inline ssize_t Stream::write(const char *ptr) { return write(ptr, strlen(ptr)); } @@ -3628,40 +4998,6 @@ inline ssize_t Stream::write(const std::string &s) { return write(s.data(), s.size()); } -template -inline ssize_t Stream::write_format(const char *fmt, const Args &... args) { - const auto bufsiz = 2048; - std::array buf; - -#if defined(_MSC_VER) && _MSC_VER < 1900 - auto sn = _snprintf_s(buf.data(), bufsiz - 1, buf.size() - 1, fmt, args...); -#else - auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); -#endif - if (sn <= 0) { return sn; } - - auto n = static_cast(sn); - - if (n >= buf.size() - 1) { - std::vector glowable_buf(buf.size()); - - while (n >= glowable_buf.size() - 1) { - glowable_buf.resize(glowable_buf.size() * 2); -#if defined(_MSC_VER) && _MSC_VER < 1900 - n = static_cast(_snprintf_s(&glowable_buf[0], glowable_buf.size(), - glowable_buf.size() - 1, fmt, - args...)); -#else - n = static_cast( - snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); -#endif - } - return write(&glowable_buf[0], n); - } else { - return write(buf.data(), n); - } -} - namespace detail { // Socket stream implementation @@ -3672,7 +5008,7 @@ inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, : sock_(sock), read_timeout_sec_(read_timeout_sec), read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), - write_timeout_usec_(write_timeout_usec) {} + write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {} inline SocketStream::~SocketStream() {} @@ -3681,33 +5017,65 @@ inline bool SocketStream::is_readable() const { } inline bool SocketStream::is_writable() const { - return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0; + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); } inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + if (!is_readable()) { return -1; } -#ifdef _WIN32 - if (size > static_cast((std::numeric_limits::max)())) { - return -1; + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); } - return recv(sock_, ptr, static_cast(size), 0); -#else - return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); }); -#endif } inline ssize_t SocketStream::write(const char *ptr, size_t size) { if (!is_writable()) { return -1; } -#ifdef _WIN32 - if (size > static_cast((std::numeric_limits::max)())) { - return -1; - } - return send(sock_, ptr, static_cast(size), 0); -#else - return handle_EINTR([&]() { return send(sock_, ptr, size, 0); }); +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); #endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); } inline void SocketStream::get_remote_ip_and_port(std::string &ip, @@ -3715,13 +5083,20 @@ inline void SocketStream::get_remote_ip_and_port(std::string &ip, return detail::get_remote_ip_and_port(sock_, ip, port); } +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + // Buffer stream implementation inline bool BufferStream::is_readable() const { return true; } inline bool BufferStream::is_writable() const { return true; } inline ssize_t BufferStream::read(char *ptr, size_t size) { -#if defined(_MSC_VER) && _MSC_VER <= 1900 +#if defined(_MSC_VER) && _MSC_VER < 1910 auto len_read = buffer._Copy_s(ptr, size, size, position); #else auto len_read = buffer.copy(ptr, size, position); @@ -3738,6 +5113,11 @@ inline ssize_t BufferStream::write(const char *ptr, size_t size) { inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, int & /*port*/) const {} +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + inline const std::string &BufferStream::get_buffer() const { return buffer; } } // namespace detail @@ -3745,8 +5125,7 @@ inline const std::string &BufferStream::get_buffer() const { return buffer; } // HTTP server implementation inline Server::Server() : new_task_queue( - [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }), - svr_sock_(INVALID_SOCKET), is_running_(false) { + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif @@ -3754,82 +5133,90 @@ inline Server::Server() inline Server::~Server() {} -inline Server &Server::Get(const char *pattern, Handler handler) { - get_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Post(const char *pattern, Handler handler) { - post_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Post(const char *pattern, +inline Server &Server::Post(const std::string &pattern, HandlerWithContentReader handler) { post_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), handler)); + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Put(const char *pattern, Handler handler) { - put_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Put(const char *pattern, +inline Server &Server::Put(const std::string &pattern, HandlerWithContentReader handler) { put_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), handler)); + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Patch(const char *pattern, Handler handler) { - patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Patch(const char *pattern, +inline Server &Server::Patch(const std::string &pattern, HandlerWithContentReader handler) { patch_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), handler)); + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Delete(const char *pattern, Handler handler) { - delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Delete(const char *pattern, +inline Server &Server::Delete(const std::string &pattern, HandlerWithContentReader handler) { delete_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), handler)); + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Options(const char *pattern, Handler handler) { - options_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline bool Server::set_base_dir(const char *dir, const char *mount_point) { +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { return set_mount_point(mount_point, dir); } -inline bool Server::set_mount_point(const char *mount_point, const char *dir) { +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { if (detail::is_dir(dir)) { - std::string mnt = mount_point ? mount_point : "/"; + std::string mnt = !mount_point.empty() ? mount_point : "/"; if (!mnt.empty() && mnt[0] == '/') { - base_dirs_.emplace_back(mnt, dir); + base_dirs_.push_back({mnt, dir, std::move(headers)}); return true; } } return false; } -inline bool Server::remove_mount_point(const char *mount_point) { +inline bool Server::remove_mount_point(const std::string &mount_point) { for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { - if (it->first == mount_point) { + if (it->mount_point == mount_point) { base_dirs_.erase(it); return true; } @@ -3837,75 +5224,139 @@ inline bool Server::remove_mount_point(const char *mount_point) { return false; } -inline void Server::set_file_extension_and_mimetype_mapping(const char *ext, - const char *mime) { +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { file_extension_and_mimetype_map_[ext] = mime; + return *this; } -inline void Server::set_file_request_handler(Handler handler) { +inline Server &Server::set_file_request_handler(Handler handler) { file_request_handler_ = std::move(handler); + return *this; } -inline void Server::set_error_handler(Handler handler) { +inline Server &Server::set_error_handler(HandlerWithResponse handler) { error_handler_ = std::move(handler); + return *this; } -inline void Server::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } - -inline void Server::set_socket_options(SocketOptions socket_options) { - socket_options_ = socket_options; +inline Server &Server::set_error_handler(Handler handler) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; } -inline void Server::set_logger(Logger logger) { logger_ = std::move(logger); } +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} -inline void +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server & Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { expect_100_continue_handler_ = std::move(handler); + + return *this; } -inline void Server::set_keep_alive_max_count(size_t count) { +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { keep_alive_max_count_ = count; + return *this; } -inline void Server::set_keep_alive_timeout(time_t sec) { +inline Server &Server::set_keep_alive_timeout(time_t sec) { keep_alive_timeout_sec_ = sec; + return *this; } -inline void Server::set_read_timeout(time_t sec, time_t usec) { +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { read_timeout_sec_ = sec; read_timeout_usec_ = usec; + return *this; } -inline void Server::set_write_timeout(time_t sec, time_t usec) { +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { write_timeout_sec_ = sec; write_timeout_usec_ = usec; + return *this; } -inline void Server::set_idle_interval(time_t sec, time_t usec) { +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { idle_interval_sec_ = sec; idle_interval_usec_ = usec; + return *this; } -inline void Server::set_payload_max_length(size_t length) { +inline Server &Server::set_payload_max_length(size_t length) { payload_max_length_ = length; + return *this; } -inline bool Server::bind_to_port(const char *host, int port, int socket_flags) { +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { if (bind_internal(host, port, socket_flags) < 0) return false; return true; } -inline int Server::bind_to_any_port(const char *host, int socket_flags) { +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { return bind_internal(host, 0, socket_flags); } -inline bool Server::listen_after_bind() { return listen_internal(); } +inline bool Server::listen_after_bind() { + auto se = detail::scope_exit([&]() { done_ = true; }); + return listen_internal(); +} -inline bool Server::listen(const char *host, int port, int socket_flags) { +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + auto se = detail::scope_exit([&]() { done_ = true; }); return bind_to_port(host, port, socket_flags) && listen_internal(); } inline bool Server::is_running() const { return is_running_; } +inline void Server::wait_until_ready() const { + while (!is_running() && !done_) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + inline void Server::stop() { if (is_running_) { assert(svr_sock_ != INVALID_SOCKET); @@ -3916,42 +5367,95 @@ inline void Server::stop() { } inline bool Server::parse_request_line(const char *s, Request &req) { - const static std::regex re( - "(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) " - "(([^?]+)(?:\\?(.*?))?) (HTTP/1\\.[01])\r\n"); + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; - std::cmatch m; - if (std::regex_match(s, m, re)) { - req.version = std::string(m[5]); - req.method = std::string(m[1]); - req.target = std::string(m[2]); - req.path = detail::decode_url(m[3], false); + { + size_t count = 0; - // Parse query text - auto len = std::distance(m[4].first, m[4].second); - if (len > 0) { detail::parse_query_text(m[4], req.params); } + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); - return true; + if (count != 3) { return false; } } - return false; + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + size_t count = 0; + + detail::split(req.target.data(), req.target.data() + req.target.size(), '?', + [&](const char *b, const char *e) { + switch (count) { + case 0: + req.path = detail::decode_url(std::string(b, e), false); + break; + case 1: { + if (e - b > 0) { + detail::parse_query_text(std::string(b, e), req.params); + } + break; + } + default: break; + } + count++; + }); + + if (count > 2) { return false; } + } + + return true; } inline bool Server::write_response(Stream &strm, bool close_connection, const Request &req, Response &res) { + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { assert(res.status != -1); - if (400 <= res.status && error_handler_) { error_handler_(req, res); } - - detail::BufferStream bstrm; - - // Response line - if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, - detail::status_message(res.status))) { - return false; + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; } - // Headers + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers if (close_connection || req.get_header_value("Connection") == "close") { res.set_header("Connection", "close"); } else { @@ -3966,13 +5470,452 @@ inline bool Server::write_response(Stream &strm, bool close_connection, res.set_header("Content-Type", "text/plain"); } + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { + res.set_header("Content-Length", "0"); + } + if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { res.set_header("Accept-Ranges", "bytes"); } - std::string content_type; - std::string boundary; + if (post_routing_handler_) { post_routing_handler_(req, res); } + // Response line and headers + { + detail::BufferStream bstrm; + + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + detail::status_message(res.status))) { + return false; + } + + if (!detail::write_headers(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + res.content_provider_success_ = false; + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + auto length = offsets.second; + return detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = 413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = 400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = 400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + if (detail::is_file(path)) { + detail::read_file(path, res.body); + auto type = + detail::find_content_type(path, file_extension_and_mimetype_map_); + if (type) { res.set_header("Content-Type", type); } + for (const auto &kv : entry.headers) { + res.set_header(kv.first.c_str(), kv.second); + } + res.status = req.has_header("Range") ? 206 : 200; + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + std::move(socket_options), + [](socket_t sock, struct addrinfo &ai) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + socket_t sock = accept(svr_sock_, nullptr, nullptr); + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + task_queue->enqueue([this, sock]() { process_and_close_socket(sock); }); + } + + task_queue->shutdown(); + } + + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + bool is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = 400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) { if (req.ranges.size() > 1) { boundary = detail::make_multipart_data_boundary(); @@ -4008,7 +5951,7 @@ inline bool Server::write_response(Stream &strm, bool close_connection, res.set_header("Content-Length", std::to_string(length)); } else { if (res.content_provider_) { - if (res.is_chunked_content_provider) { + if (res.is_chunked_content_provider_) { res.set_header("Transfer-Encoding", "chunked"); if (type == detail::EncodingType::Gzip) { res.set_header("Content-Encoding", "gzip"); @@ -4016,8 +5959,6 @@ inline bool Server::write_response(Stream &strm, bool close_connection, res.set_header("Content-Encoding", "br"); } } - } else { - res.set_header("Content-Length", "0"); } } } else { @@ -4031,435 +5972,55 @@ inline bool Server::write_response(Stream &strm, bool close_connection, auto content_range = detail::make_content_range_header_field( offset, length, res.body.size()); res.set_header("Content-Range", content_range); - res.body = res.body.substr(offset, length); + if (offset < res.body.size()) { + res.body = res.body.substr(offset, length); + } else { + res.body.clear(); + res.status = 416; + } } else { - res.body = - detail::make_multipart_ranges_data(req, res, boundary, content_type); + std::string data; + if (detail::make_multipart_ranges_data(req, res, boundary, content_type, + data)) { + res.body.swap(data); + } else { + res.body.clear(); + res.status = 416; + } } if (type != detail::EncodingType::None) { - std::shared_ptr compressor; + std::unique_ptr compressor; + std::string content_encoding; if (type == detail::EncodingType::Gzip) { #ifdef CPPHTTPLIB_ZLIB_SUPPORT - compressor = std::make_shared(); - res.set_header("Content-Encoding", "gzip"); + compressor = detail::make_unique(); + content_encoding = "gzip"; #endif } else if (type == detail::EncodingType::Brotli) { #ifdef CPPHTTPLIB_BROTLI_SUPPORT - compressor = std::make_shared(); - res.set_header("Content-Encoding", "brotli"); + compressor = detail::make_unique(); + content_encoding = "br"; #endif } if (compressor) { std::string compressed; - - if (!compressor->compress(res.body.data(), res.body.size(), true, - [&](const char *data, size_t data_len) { - compressed.append(data, data_len); - return true; - })) { - return false; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); } - - res.body.swap(compressed); } } auto length = std::to_string(res.body.size()); res.set_header("Content-Length", length); } - - if (!detail::write_headers(bstrm, res, Headers())) { return false; } - - // Flush buffer - auto &data = bstrm.get_buffer(); - strm.write(data.data(), data.size()); - - // Body - auto ret = true; - if (req.method != "HEAD") { - if (!res.body.empty()) { - if (!strm.write(res.body)) { ret = false; } - } else if (res.content_provider_) { - if (!write_content_with_provider(strm, req, res, boundary, - content_type)) { - ret = false; - } - } - } - - // Log - if (logger_) { logger_(req, res); } - - return ret; -} - -inline bool -Server::write_content_with_provider(Stream &strm, const Request &req, - Response &res, const std::string &boundary, - const std::string &content_type) { - auto is_shutting_down = [this]() { - return this->svr_sock_ == INVALID_SOCKET; - }; - - if (res.content_length_ > 0) { - if (req.ranges.empty()) { - if (detail::write_content(strm, res.content_provider_, 0, - res.content_length_, is_shutting_down) < 0) { - return false; - } - } else if (req.ranges.size() == 1) { - auto offsets = - detail::get_range_offset_and_length(req, res.content_length_, 0); - auto offset = offsets.first; - auto length = offsets.second; - if (detail::write_content(strm, res.content_provider_, offset, length, - is_shutting_down) < 0) { - return false; - } - } else { - if (!detail::write_multipart_ranges_data( - strm, req, res, boundary, content_type, is_shutting_down)) { - return false; - } - } - } else { - if (res.is_chunked_content_provider) { - auto type = detail::encoding_type(req, res); - - std::shared_ptr compressor; - if (type == detail::EncodingType::Gzip) { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - compressor = std::make_shared(); -#endif - } else if (type == detail::EncodingType::Brotli) { -#ifdef CPPHTTPLIB_BROTLI_SUPPORT - compressor = std::make_shared(); -#endif - } else { - compressor = std::make_shared(); - } - assert(compressor != nullptr); - - if (detail::write_content_chunked(strm, res.content_provider_, - is_shutting_down, *compressor) < 0) { - return false; - } - } else { - if (detail::write_content_without_length(strm, res.content_provider_, - is_shutting_down) < 0) { - return false; - } - } - } - return true; -} - -inline bool Server::read_content(Stream &strm, Request &req, Response &res) { - MultipartFormDataMap::iterator cur; - if (read_content_core( - strm, req, res, - // Regular - [&](const char *buf, size_t n) { - if (req.body.size() + n > req.body.max_size()) { return false; } - req.body.append(buf, n); - return true; - }, - // Multipart - [&](const MultipartFormData &file) { - cur = req.files.emplace(file.name, file); - return true; - }, - [&](const char *buf, size_t n) { - auto &content = cur->second.content; - if (content.size() + n > content.max_size()) { return false; } - content.append(buf, n); - return true; - })) { - const auto &content_type = req.get_header_value("Content-Type"); - if (!content_type.find("application/x-www-form-urlencoded")) { - detail::parse_query_text(req.body, req.params); - } - return true; - } - return false; -} - -inline bool Server::read_content_with_content_receiver( - Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader multipart_header, - ContentReceiver multipart_receiver) { - return read_content_core(strm, req, res, receiver, multipart_header, - multipart_receiver); -} - -inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, - ContentReceiver receiver, - MultipartContentHeader mulitpart_header, - ContentReceiver multipart_receiver) { - detail::MultipartFormDataParser multipart_form_data_parser; - ContentReceiver out; - - if (req.is_multipart_form_data()) { - const auto &content_type = req.get_header_value("Content-Type"); - std::string boundary; - if (!detail::parse_multipart_boundary(content_type, boundary)) { - res.status = 400; - return false; - } - - multipart_form_data_parser.set_boundary(std::move(boundary)); - out = [&](const char *buf, size_t n) { - /* For debug - size_t pos = 0; - while (pos < n) { - auto read_size = std::min(1, n - pos); - auto ret = multipart_form_data_parser.parse( - buf + pos, read_size, multipart_receiver, mulitpart_header); - if (!ret) { return false; } - pos += read_size; - } - return true; - */ - return multipart_form_data_parser.parse(buf, n, multipart_receiver, - mulitpart_header); - }; - } else { - out = receiver; - } - - if (req.method == "DELETE" && !req.has_header("Content-Length")) { - return true; - } - - if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, - out, true)) { - return false; - } - - if (req.is_multipart_form_data()) { - if (!multipart_form_data_parser.is_valid()) { - res.status = 400; - return false; - } - } - - return true; -} - -inline bool Server::handle_file_request(Request &req, Response &res, - bool head) { - for (const auto &kv : base_dirs_) { - const auto &mount_point = kv.first; - const auto &base_dir = kv.second; - - // Prefix match - if (!req.path.compare(0, mount_point.size(), mount_point)) { - std::string sub_path = "/" + req.path.substr(mount_point.size()); - if (detail::is_valid_path(sub_path)) { - auto path = base_dir + sub_path; - if (path.back() == '/') { path += "index.html"; } - - if (detail::is_file(path)) { - detail::read_file(path, res.body); - auto type = - detail::find_content_type(path, file_extension_and_mimetype_map_); - if (type) { res.set_header("Content-Type", type); } - res.status = 200; - if (!head && file_request_handler_) { - file_request_handler_(req, res); - } - return true; - } - } - } - } - return false; -} - -inline socket_t -Server::create_server_socket(const char *host, int port, int socket_flags, - SocketOptions socket_options) const { - return detail::create_socket( - host, port, socket_flags, tcp_nodelay_, socket_options, - [](socket_t sock, struct addrinfo &ai) -> bool { - if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { - return false; - } - if (::listen(sock, 5)) { // Listen through 5 channels - return false; - } - return true; - }); -} - -inline int Server::bind_internal(const char *host, int port, int socket_flags) { - if (!is_valid()) { return -1; } - - svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); - if (svr_sock_ == INVALID_SOCKET) { return -1; } - - if (port == 0) { - struct sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - if (getsockname(svr_sock_, reinterpret_cast(&addr), - &addr_len) == -1) { - return -1; - } - if (addr.ss_family == AF_INET) { - return ntohs(reinterpret_cast(&addr)->sin_port); - } else if (addr.ss_family == AF_INET6) { - return ntohs(reinterpret_cast(&addr)->sin6_port); - } else { - return -1; - } - } else { - return port; - } -} - -inline bool Server::listen_internal() { - auto ret = true; - is_running_ = true; - - { - std::unique_ptr task_queue(new_task_queue()); - - while (svr_sock_ != INVALID_SOCKET) { -#ifndef _WIN32 - if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { -#endif - auto val = detail::select_read(svr_sock_, idle_interval_sec_, - idle_interval_usec_); - if (val == 0) { // Timeout - task_queue->on_idle(); - continue; - } -#ifndef _WIN32 - } -#endif - socket_t sock = accept(svr_sock_, nullptr, nullptr); - - if (sock == INVALID_SOCKET) { - if (errno == EMFILE) { - // The per-process limit of open file descriptors has been reached. - // Try to accept new connections after a short sleep. - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } - if (svr_sock_ != INVALID_SOCKET) { - detail::close_socket(svr_sock_); - ret = false; - } else { - ; // The server socket was closed by user. - } - break; - } - -#if __cplusplus > 201703L - task_queue->enqueue([=, this]() { process_and_close_socket(sock); }); -#else - task_queue->enqueue([=]() { process_and_close_socket(sock); }); -#endif - } - - task_queue->shutdown(); - } - - is_running_ = false; - return ret; -} - -inline bool Server::routing(Request &req, Response &res, Stream &strm) { - // File handler - bool is_head_request = req.method == "HEAD"; - if ((req.method == "GET" || is_head_request) && - handle_file_request(req, res, is_head_request)) { - return true; - } - - if (detail::expect_content(req)) { - // Content reader handler - { - ContentReader reader( - [&](ContentReceiver receiver) { - return read_content_with_content_receiver(strm, req, res, receiver, - nullptr, nullptr); - }, - [&](MultipartContentHeader header, ContentReceiver receiver) { - return read_content_with_content_receiver(strm, req, res, nullptr, - header, receiver); - }); - - if (req.method == "POST") { - if (dispatch_request_for_content_reader( - req, res, reader, post_handlers_for_content_reader_)) { - return true; - } - } else if (req.method == "PUT") { - if (dispatch_request_for_content_reader( - req, res, reader, put_handlers_for_content_reader_)) { - return true; - } - } else if (req.method == "PATCH") { - if (dispatch_request_for_content_reader( - req, res, reader, patch_handlers_for_content_reader_)) { - return true; - } - } else if (req.method == "DELETE") { - if (dispatch_request_for_content_reader( - req, res, reader, delete_handlers_for_content_reader_)) { - return true; - } - } - } - - // Read content into `req.body` - if (!read_content(strm, req, res)) { return false; } - } - - // Regular handler - if (req.method == "GET" || req.method == "HEAD") { - return dispatch_request(req, res, get_handlers_); - } else if (req.method == "POST") { - return dispatch_request(req, res, post_handlers_); - } else if (req.method == "PUT") { - return dispatch_request(req, res, put_handlers_); - } else if (req.method == "DELETE") { - return dispatch_request(req, res, delete_handlers_); - } else if (req.method == "OPTIONS") { - return dispatch_request(req, res, options_handlers_); - } else if (req.method == "PATCH") { - return dispatch_request(req, res, patch_handlers_); - } - - res.status = 400; - return false; -} - -inline bool Server::dispatch_request(Request &req, Response &res, - const Handlers &handlers) { - - try { - for (const auto &x : handlers) { - const auto &pattern = x.first; - const auto &handler = x.second; - - if (std::regex_match(req.path, req.matches, pattern)) { - handler(req, res); - return true; - } - } - } catch (const std::exception &ex) { - res.status = 500; - res.set_header("EXCEPTION_WHAT", ex.what()); - } catch (...) { - res.status = 500; - res.set_header("EXCEPTION_WHAT", "UNKNOWN"); - } - return false; } inline bool Server::dispatch_request_for_content_reader( @@ -4493,6 +6054,26 @@ Server::process_request(Stream &strm, bool close_connection, res.version = "HTTP/1.1"; + for (const auto &header : default_headers_) { + if (res.headers.find(header.first) == res.headers.end()) { + res.headers.insert(header); + } + } + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + // Check if the request URI doesn't exceed the limit if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { Headers dummy; @@ -4521,10 +6102,15 @@ Server::process_request(Stream &strm, bool close_connection, req.set_header("REMOTE_ADDR", req.remote_addr); req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + strm.get_local_ip_and_port(req.local_addr, req.local_port); + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + if (req.has_header("Range")) { const auto &range_header_value = req.get_header_value("Range"); if (!detail::parse_range_header(range_header_value, req.ranges)) { - // TODO: error + res.status = 416; + return write_response(strm, close_connection, req, res); } } @@ -4546,21 +6132,58 @@ Server::process_request(Stream &strm, bool close_connection, } // Rounting - if (routing(req, res, strm)) { + bool routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + + if (routed) { if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } + return write_response_with_content(strm, close_connection, req, res); } else { if (res.status == -1) { res.status = 404; } + return write_response(strm, close_connection, req, res); } - - return write_response(strm, close_connection, req, res); } inline bool Server::is_valid() const { return true; } inline bool Server::process_and_close_socket(socket_t sock) { auto ret = detail::process_server_socket( - sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, - read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, [this](Stream &strm, bool close_connection, bool &connection_closed) { return process_request(strm, close_connection, connection_closed, nullptr); @@ -4582,15 +6205,17 @@ inline ClientImpl::ClientImpl(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) : host_(host), port_(port), - host_and_port_(host_ + ":" + std::to_string(port_)), + host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)), client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} -inline ClientImpl::~ClientImpl() { stop_core(); } +inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} inline bool ClientImpl::is_valid() const { return true; } -inline Error ClientImpl::get_last_error() const { return error_; } - inline void ClientImpl::copy_settings(const ClientImpl &rhs) { client_cert_path_ = rhs.client_cert_path_; client_key_path_ = rhs.client_key_path_; @@ -4608,6 +6233,8 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) { #endif keep_alive_ = rhs.keep_alive_; follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; tcp_nodelay_ = rhs.tcp_nodelay_; socket_options_ = rhs.socket_options_; compress_ = rhs.compress_; @@ -4622,50 +6249,96 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) { proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; #endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT server_certificate_verification_ = rhs.server_certificate_verification_; #endif logger_ = rhs.logger_; } -inline socket_t ClientImpl::create_client_socket() const { +inline socket_t ClientImpl::create_client_socket(Error &error) const { if (!proxy_host_.empty() && proxy_port_ != -1) { return detail::create_client_socket( - proxy_host_.c_str(), proxy_port_, tcp_nodelay_, socket_options_, - connection_timeout_sec_, connection_timeout_usec_, interface_, error_); + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) ip = it->second; + return detail::create_client_socket( - host_.c_str(), port_, tcp_nodelay_, socket_options_, - connection_timeout_sec_, connection_timeout_usec_, interface_, error_); + host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, + error); } -inline bool ClientImpl::create_and_connect_socket(Socket &socket) { - auto sock = create_client_socket(); +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); if (sock == INVALID_SOCKET) { return false; } socket.sock = sock; return true; } -inline void ClientImpl::close_socket(Socket &socket, - bool /*process_socket_ret*/) { - detail::close_socket(socket.sock); - socket_.sock = INVALID_SOCKET; -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT - socket_.ssl = nullptr; -#endif +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); } -inline bool ClientImpl::read_response_line(Stream &strm, Response &res) { - std::array buf; +inline void ClientImpl::shutdown_socket(Socket &socket) { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) { + std::array buf{}; detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); if (!line_reader.getline()) { return false; } - const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n"); +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#endif std::cmatch m; - if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } res.version = std::string(m[1]); res.status = std::stoi(std::string(m[2])); res.reason = std::string(m[3]); @@ -4684,74 +6357,144 @@ inline bool ClientImpl::read_response_line(Stream &strm, Response &res) { return true; } -inline bool ClientImpl::send(const Request &req, Response &res) { +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { { std::lock_guard guard(socket_mutex_); + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + auto is_alive = false; if (socket_.is_open()) { - is_alive = detail::select_write(socket_.sock, 0, 0) > 0; - if (!is_alive) { close_socket(socket_, false); } + is_alive = detail::is_socket_alive(socket_.sock); + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } } if (!is_alive) { - if (!create_and_connect_socket(socket_)) { return false; } + if (!create_and_connect_socket(socket_, error)) { return false; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT // TODO: refactoring if (is_ssl()) { auto &scli = static_cast(*this); if (!proxy_host_.empty() && proxy_port_ != -1) { - bool success = false; - if (!scli.connect_with_proxy(socket_, res, success)) { + auto success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { return success; } } - if (!scli.initialize_ssl(socket_)) { return false; } + if (!scli.initialize_ssl(socket_, error)) { return false; } } #endif } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); } + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; auto close_connection = !keep_alive_; - auto ret = process_socket(socket_, [&](Stream &strm) { - return handle_request(strm, req, res, close_connection); + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } }); - if (close_connection || !ret) { stop_core(); } + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); if (!ret) { - if (error_ == Error::Success) { error_ = Error::Unknown; } + if (error == Error::Success) { error = Error::Unknown; } } return ret; } -inline bool ClientImpl::handle_request(Stream &strm, const Request &req, - Response &res, bool close_connection) { +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { if (req.path.empty()) { - error_ = Error::Connection; + error = Error::Connection; return false; } + auto req_save = req; + bool ret; if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { auto req2 = req; req2.path = "http://" + host_and_port_ + req.path; - ret = process_request(strm, req2, res, close_connection); + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; } else { - ret = process_request(strm, req, res, close_connection); + ret = process_request(strm, req, res, close_connection, error); } if (!ret) { return false; } if (300 < res.status && res.status < 400 && follow_location_) { - ret = redirect(req, res); + req = req_save; + ret = redirect(req, res, error); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -4768,15 +6511,15 @@ inline bool ClientImpl::handle_request(Stream &strm, const Request &req, if (detail::parse_www_authenticate(res, auth, is_proxy)) { Request new_req = req; new_req.authorization_count_ += 1; - auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; - new_req.headers.erase(key); + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); new_req.headers.insert(detail::make_digest_authentication_header( req, auth, new_req.authorization_count_, detail::random_string(10), username, password, is_proxy)); Response new_res; - ret = send(new_req, new_res); + ret = send(new_req, new_res, error); if (ret) { res = new_res; } } } @@ -4786,17 +6529,17 @@ inline bool ClientImpl::handle_request(Stream &strm, const Request &req, return ret; } -inline bool ClientImpl::redirect(const Request &req, Response &res) { - if (req.redirect_count == 0) { - error_ = Error::ExceedRedirectCount; +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; return false; } - auto location = detail::decode_url(res.get_header_value("location"), true); + auto location = res.get_header_value("location"); if (location.empty()) { return false; } const static std::regex re( - R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); + R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); std::smatch m; if (!std::regex_match(location, m, re)) { return false; } @@ -4805,8 +6548,10 @@ inline bool ClientImpl::redirect(const Request &req, Response &res) { auto next_scheme = m[1].str(); auto next_host = m[2].str(); - auto port_str = m[3].str(); - auto next_path = m[4].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); auto next_port = port_; if (!port_str.empty()) { @@ -4819,179 +6564,202 @@ inline bool ClientImpl::redirect(const Request &req, Response &res) { if (next_host.empty()) { next_host = host_; } if (next_path.empty()) { next_path = "/"; } + auto path = detail::decode_url(next_path, true) + next_query; + if (next_scheme == scheme && next_host == host_ && next_port == port_) { - return detail::redirect(*this, req, res, next_path); + return detail::redirect(*this, req, res, path, location, error); } else { if (next_scheme == "https") { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT SSLClient cli(next_host.c_str(), next_port); cli.copy_settings(*this); - auto ret = detail::redirect(cli, req, res, next_path); - if (!ret) { error_ = cli.get_last_error(); } - return ret; + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); #else return false; #endif } else { ClientImpl cli(next_host.c_str(), next_port); cli.copy_settings(*this); - auto ret = detail::redirect(cli, req, res, next_path); - if (!ret) { error_ = cli.get_last_error(); } - return ret; + return detail::redirect(cli, req, res, path, location, error); } } } -inline bool ClientImpl::write_request(Stream &strm, const Request &req, - bool close_connection) { - detail::BufferStream bstrm; +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) { + auto is_shutting_down = []() { return false; }; - // Request line - const auto &path = detail::encode_url(req.path); + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } - bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} - // Additonal headers - Headers headers; - if (close_connection) { headers.emplace("Connection", "close"); } +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.headers.emplace("Connection", "close"); + } + } if (!req.has_header("Host")) { if (is_ssl()) { if (port_ == 443) { - headers.emplace("Host", host_); + req.headers.emplace("Host", host_); } else { - headers.emplace("Host", host_and_port_); + req.headers.emplace("Host", host_and_port_); } } else { if (port_ == 80) { - headers.emplace("Host", host_); + req.headers.emplace("Host", host_); } else { - headers.emplace("Host", host_and_port_); + req.headers.emplace("Host", host_and_port_); } } } - if (!req.has_header("Accept")) { headers.emplace("Accept", "*/*"); } + if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT if (!req.has_header("User-Agent")) { - headers.emplace("User-Agent", "cpp-httplib/0.7"); + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.headers.emplace("User-Agent", agent); } +#endif if (req.body.empty()) { - if (req.content_provider) { - auto length = std::to_string(req.content_length); - headers.emplace("Content-Length", length); + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.headers.emplace("Content-Length", length); + } + } } else { - headers.emplace("Content-Length", "0"); + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.headers.emplace("Content-Length", "0"); + } } } else { if (!req.has_header("Content-Type")) { - headers.emplace("Content-Type", "text/plain"); + req.headers.emplace("Content-Type", "text/plain"); } if (!req.has_header("Content-Length")) { auto length = std::to_string(req.body.size()); - headers.emplace("Content-Length", length); + req.headers.emplace("Content-Length", length); } } - if (!basic_auth_password_.empty()) { - headers.insert(make_basic_authentication_header( - basic_auth_username_, basic_auth_password_, false)); + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } } if (!proxy_basic_auth_username_.empty() && !proxy_basic_auth_password_.empty()) { - headers.insert(make_basic_authentication_header( - proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } } if (!bearer_token_auth_token_.empty()) { - headers.insert(make_bearer_token_authentication_header( - bearer_token_auth_token_, false)); + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } } if (!proxy_bearer_token_auth_token_.empty()) { - headers.insert(make_bearer_token_authentication_header( - proxy_bearer_token_auth_token_, true)); + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } } - detail::write_headers(bstrm, req, headers); + // Request line and headers + { + detail::BufferStream bstrm; - // Flush buffer - auto &data = bstrm.get_buffer(); - if (!detail::write_data(strm, data.data(), data.size())) { - error_ = Error::Write; - return false; + const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path; + bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + + detail::write_headers(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } } // Body if (req.body.empty()) { - if (req.content_provider) { - size_t offset = 0; - size_t end_offset = req.content_length; + return write_content_with_provider(strm, req, error); + } - bool ok = true; - - DataSink data_sink; - data_sink.write = [&](const char *d, size_t l) { - if (ok) { - if (detail::write_data(strm, d, l)) { - offset += l; - } else { - ok = false; - } - } - }; - data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; - - while (offset < end_offset) { - if (!req.content_provider(offset, end_offset - offset, data_sink)) { - error_ = Error::Canceled; - return false; - } - if (!ok) { - error_ = Error::Write; - return false; - } - } - } - } else { - return detail::write_data(strm, req.body.data(), req.body.size()); + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; } return true; } -inline std::shared_ptr ClientImpl::send_with_content_provider( - const char *method, const char *path, const Headers &headers, - const std::string &body, size_t content_length, - ContentProvider content_provider, const char *content_type) { - - Request req; - req.method = method; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); - req.path = path; - - if (content_type) { req.headers.emplace("Content-Type", content_type); } +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } #ifdef CPPHTTPLIB_ZLIB_SUPPORT - if (compress_) { + if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support detail::gzip_compressor compressor; if (content_provider) { auto ok = true; size_t offset = 0; - DataSink data_sink; - data_sink.write = [&](const char *data, size_t data_len) { + + data_sink.write = [&](const char *data, size_t data_len) -> bool { if (ok) { auto last = offset + data_len == content_length; auto ret = compressor.compress( - data, data_len, last, [&](const char *data, size_t data_len) { - req.body.append(data, data_len); + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); return true; }); @@ -5001,94 +6769,162 @@ inline std::shared_ptr ClientImpl::send_with_content_provider( ok = false; } } + return ok; }; - data_sink.is_writable = [&](void) { return ok && true; }; while (ok && offset < content_length) { if (!content_provider(offset, content_length - offset, data_sink)) { - error_ = Error::Canceled; + error = Error::Canceled; return nullptr; } } } else { - if (!compressor.compress(body.data(), body.size(), true, + if (!compressor.compress(body, content_length, true, [&](const char *data, size_t data_len) { req.body.append(data, data_len); return true; })) { + error = Error::Compression; return nullptr; } } - - req.headers.emplace("Content-Encoding", "gzip"); } else #endif { if (content_provider) { - req.content_length = content_length; - req.content_provider = content_provider; + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.headers.emplace("Transfer-Encoding", "chunked"); } else { - req.body = body; + req.body.assign(body, content_length); + ; } } - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; } -inline bool ClientImpl::process_request(Stream &strm, const Request &req, - Response &res, bool close_connection) { +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { // Send request - if (!write_request(strm, req, close_connection)) { return false; } + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + char buf[1]; + if (SSL_peek(socket_.ssl, buf, 1) == 0 && + SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif // Receive response and headers - if (!read_response_line(strm, res) || + if (!read_response_line(strm, req, res) || !detail::read_headers(strm, res.headers)) { - error_ = Error::Read; + error = Error::Read; return false; } - if (req.response_handler) { - if (!req.response_handler(res)) { - error_ = Error::Canceled; - return false; - } - } - // Body - if (req.method != "HEAD" && req.method != "CONNECT") { + if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + auto out = req.content_receiver - ? static_cast([&](const char *buf, size_t n) { - auto ret = req.content_receiver(buf, n); - if (!ret) { error_ = Error::Canceled; } - return ret; - }) - : static_cast([&](const char *buf, size_t n) { - if (res.body.size() + n > res.body.max_size()) { return false; } - res.body.append(buf, n); - return true; - }); + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); auto progress = [&](uint64_t current, uint64_t total) { - if (!req.progress) { return true; } + if (!req.progress || redirect) { return true; } auto ret = req.progress(current, total); - if (!ret) { error_ = Error::Canceled; } + if (!ret) { error = Error::Canceled; } return ret; }; int dummy_status; if (!detail::read_content(strm, res, (std::numeric_limits::max)(), - dummy_status, progress, out, decompress_)) { - if (error_ != Error::Canceled) { error_ = Error::Read; } + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } return false; } } if (res.get_header_value("Connection") == "close" || (res.version == "HTTP/1.0" && res.reason != "Connection established")) { - stop_core(); + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. Maybe a code refactor (such as moving this out to + // the send function and getting rid of the recursiveness of the mutex) + // could make this more obvious. + + // This is safe to call because process_request is only called by + // handle_request which is only called by send, which locks the request + // mutex during the process. It would be a bug to call it from a different + // thread since it's a thread-safety issue to do these things to the socket + // if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); } // Log @@ -5097,312 +6933,529 @@ inline bool ClientImpl::process_request(Stream &strm, const Request &req, return true; } +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + size_t cur_item = 0, cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && items.size()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + bool has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) + return false; + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + inline bool -ClientImpl::process_socket(Socket &socket, +ClientImpl::process_socket(const Socket &socket, std::function callback) { - return detail::process_client_socket(socket.sock, read_timeout_sec_, - read_timeout_usec_, write_timeout_sec_, - write_timeout_usec_, callback); + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); } inline bool ClientImpl::is_ssl() const { return false; } -inline Result ClientImpl::Get(const char *path) { +inline Result ClientImpl::Get(const std::string &path) { return Get(path, Headers(), Progress()); } -inline Result ClientImpl::Get(const char *path, Progress progress) { +inline Result ClientImpl::Get(const std::string &path, Progress progress) { return Get(path, Headers(), std::move(progress)); } -inline Result ClientImpl::Get(const char *path, const Headers &headers) { +inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { return Get(path, headers, Progress()); } -inline Result ClientImpl::Get(const char *path, const Headers &headers, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, Progress progress) { Request req; req.method = "GET"; req.path = path; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); + req.headers = headers; req.progress = std::move(progress); - auto res = std::make_shared(); - auto ret = send(req, *res); - return Result{ret ? res : nullptr, get_last_error()}; + return send_(std::move(req)); } -inline Result ClientImpl::Get(const char *path, +inline Result ClientImpl::Get(const std::string &path, ContentReceiver content_receiver) { return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const char *path, +inline Result ClientImpl::Get(const std::string &path, ContentReceiver content_receiver, Progress progress) { return Get(path, Headers(), nullptr, std::move(content_receiver), std::move(progress)); } -inline Result ClientImpl::Get(const char *path, const Headers &headers, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver) { return Get(path, headers, nullptr, std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const char *path, const Headers &headers, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, Progress progress) { return Get(path, headers, nullptr, std::move(content_receiver), std::move(progress)); } -inline Result ClientImpl::Get(const char *path, +inline Result ClientImpl::Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver) { - return Get(path, Headers(), std::move(response_handler), content_receiver, - nullptr); + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const char *path, const Headers &headers, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver) { - return Get(path, headers, std::move(response_handler), content_receiver, - nullptr); + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const char *path, +inline Result ClientImpl::Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { - return Get(path, Headers(), std::move(response_handler), content_receiver, - progress); + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); } -inline Result ClientImpl::Get(const char *path, const Headers &headers, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { Request req; req.method = "GET"; req.path = path; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); + req.headers = headers; req.response_handler = std::move(response_handler); - req.content_receiver = std::move(content_receiver); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; req.progress = std::move(progress); - auto res = std::make_shared(); - auto ret = send(req, *res); - return Result{ret ? res : nullptr, get_last_error()}; + return send_(std::move(req)); } -inline Result ClientImpl::Head(const char *path) { +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, content_receiver, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, response_handler, content_receiver, progress); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, response_handler, + content_receiver, progress); +} + +inline Result ClientImpl::Head(const std::string &path) { return Head(path, Headers()); } -inline Result ClientImpl::Head(const char *path, const Headers &headers) { +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { Request req; req.method = "HEAD"; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); + req.headers = headers; req.path = path; - auto res = std::make_shared(); - auto ret = send(req, *res); - return Result{ret ? res : nullptr, get_last_error()}; + return send_(std::move(req)); } -inline Result ClientImpl::Post(const char *path) { - return Post(path, std::string(), nullptr); +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); } -inline Result ClientImpl::Post(const char *path, const std::string &body, - const char *content_type) { +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Post(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { return Post(path, Headers(), body, content_type); } -inline Result ClientImpl::Post(const char *path, const Headers &headers, +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const std::string &body, - const char *content_type) { - auto ret = send_with_content_provider("POST", path, headers, body, 0, nullptr, - content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } -inline Result ClientImpl::Post(const char *path, const Params ¶ms) { +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { return Post(path, Headers(), params); } -inline Result ClientImpl::Post(const char *path, size_t content_length, +inline Result ClientImpl::Post(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return Post(path, Headers(), content_length, content_provider, content_type); + const std::string &content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline Result ClientImpl::Post(const char *path, const Headers &headers, +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const char *content_type) { - auto ret = send_with_content_provider("POST", path, headers, std::string(), - content_length, content_provider, - content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); } -inline Result ClientImpl::Post(const char *path, const Headers &headers, +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const Params ¶ms) { auto query = detail::params_to_query_str(params); return Post(path, headers, query, "application/x-www-form-urlencoded"); } -inline Result ClientImpl::Post(const char *path, +inline Result ClientImpl::Post(const std::string &path, const MultipartFormDataItems &items) { return Post(path, Headers(), items); } -inline Result ClientImpl::Post(const char *path, const Headers &headers, +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items) { - auto boundary = detail::make_multipart_data_boundary(); - - std::string body; - - for (const auto &item : items) { - body += "--" + boundary + "\r\n"; - body += "Content-Disposition: form-data; name=\"" + item.name + "\""; - if (!item.filename.empty()) { - body += "; filename=\"" + item.filename + "\""; - } - body += "\r\n"; - if (!item.content_type.empty()) { - body += "Content-Type: " + item.content_type + "\r\n"; - } - body += "\r\n"; - body += item.content + "\r\n"; - } - - body += "--" + boundary + "--\r\n"; - - std::string content_type = "multipart/form-data; boundary=" + boundary; + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); return Post(path, headers, body, content_type.c_str()); } -inline Result ClientImpl::Put(const char *path) { - return Put(path, std::string(), nullptr); +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); } -inline Result ClientImpl::Put(const char *path, const std::string &body, - const char *content_type) { +inline Result +ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { return Put(path, Headers(), body, content_type); } -inline Result ClientImpl::Put(const char *path, const Headers &headers, +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const std::string &body, - const char *content_type) { - auto ret = send_with_content_provider("PUT", path, headers, body, 0, nullptr, - content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } -inline Result ClientImpl::Put(const char *path, size_t content_length, +inline Result ClientImpl::Put(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return Put(path, Headers(), content_length, content_provider, content_type); + const std::string &content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline Result ClientImpl::Put(const char *path, const Headers &headers, +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const char *content_type) { - auto ret = send_with_content_provider("PUT", path, headers, std::string(), - content_length, content_provider, - content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); } -inline Result ClientImpl::Put(const char *path, const Params ¶ms) { +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { return Put(path, Headers(), params); } -inline Result ClientImpl::Put(const char *path, const Headers &headers, +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const Params ¶ms) { auto query = detail::params_to_query_str(params); return Put(path, headers, query, "application/x-www-form-urlencoded"); } -inline Result ClientImpl::Patch(const char *path, const std::string &body, - const char *content_type) { +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { + return Put(path, Headers(), items); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result +ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { return Patch(path, Headers(), body, content_type); } -inline Result ClientImpl::Patch(const char *path, const Headers &headers, +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const std::string &body, - const char *content_type) { - auto ret = send_with_content_provider("PATCH", path, headers, body, 0, - nullptr, content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } -inline Result ClientImpl::Patch(const char *path, size_t content_length, +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return Patch(path, Headers(), content_length, content_provider, content_type); + const std::string &content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline Result ClientImpl::Patch(const char *path, const Headers &headers, +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const char *content_type) { - auto ret = send_with_content_provider("PATCH", path, headers, std::string(), - content_length, content_provider, - content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); } -inline Result ClientImpl::Delete(const char *path) { - return Delete(path, Headers(), std::string(), nullptr); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); } -inline Result ClientImpl::Delete(const char *path, const std::string &body, - const char *content_type) { - return Delete(path, Headers(), body, content_type); +inline Result ClientImpl::Delete(const std::string &path) { + return Delete(path, Headers(), std::string(), std::string()); } -inline Result ClientImpl::Delete(const char *path, const Headers &headers) { - return Delete(path, headers, std::string(), nullptr); +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { + return Delete(path, headers, std::string(), std::string()); } -inline Result ClientImpl::Delete(const char *path, const Headers &headers, - const std::string &body, - const char *content_type) { +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { Request req; req.method = "DELETE"; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); + req.headers = headers; req.path = path; - if (content_type) { req.headers.emplace("Content-Type", content_type); } - req.body = body; + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + req.body.assign(body, content_length); - auto res = std::make_shared(); - auto ret = send(req, *res); - return Result{ret ? res : nullptr, get_last_error()}; + return send_(std::move(req)); } -inline Result ClientImpl::Options(const char *path) { +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Options(const std::string &path) { return Options(path, Headers()); } -inline Result ClientImpl::Options(const char *path, const Headers &headers) { +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { Request req; req.method = "OPTIONS"; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); + req.headers = headers; req.path = path; - auto res = std::make_shared(); - auto ret = send(req, *res); - return Result{ret ? res : nullptr, get_last_error()}; + return send_(std::move(req)); } inline size_t ClientImpl::is_socket_open() const { @@ -5410,19 +7463,29 @@ inline size_t ClientImpl::is_socket_open() const { return socket_.is_open(); } -inline void ClientImpl::stop() { - stop_core(); - error_ = Error::Canceled; -} +inline socket_t ClientImpl::socket() const { return socket_.sock; } -inline void ClientImpl::stop_core() { +inline void ClientImpl::stop() { std::lock_guard guard(socket_mutex_); - if (socket_.is_open()) { - detail::shutdown_socket(socket_.sock); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - close_socket(socket_, true); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); } inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { @@ -5440,19 +7503,19 @@ inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { write_timeout_usec_ = usec; } -inline void ClientImpl::set_basic_auth(const char *username, - const char *password) { +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { basic_auth_username_ = username; basic_auth_password_ = password; } -inline void ClientImpl::set_bearer_token_auth(const char *token) { +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { bearer_token_auth_token_ = token; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void ClientImpl::set_digest_auth(const char *username, - const char *password) { +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { digest_auth_username_ = username; digest_auth_password_ = password; } @@ -5462,45 +7525,72 @@ inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } +inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); +} + inline void ClientImpl::set_default_headers(Headers headers) { default_headers_ = std::move(headers); } +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} + inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } inline void ClientImpl::set_socket_options(SocketOptions socket_options) { - socket_options_ = socket_options; + socket_options_ = std::move(socket_options); } inline void ClientImpl::set_compress(bool on) { compress_ = on; } inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } -inline void ClientImpl::set_interface(const char *intf) { interface_ = intf; } +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} -inline void ClientImpl::set_proxy(const char *host, int port) { +inline void ClientImpl::set_proxy(const std::string &host, int port) { proxy_host_ = host; proxy_port_ = port; } -inline void ClientImpl::set_proxy_basic_auth(const char *username, - const char *password) { +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { proxy_basic_auth_username_ = username; proxy_basic_auth_password_ = password; } -inline void ClientImpl::set_proxy_bearer_token_auth(const char *token) { +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { proxy_bearer_token_auth_token_ = token; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void ClientImpl::set_proxy_digest_auth(const char *username, - const char *password) { +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { proxy_digest_auth_username_ = username; proxy_digest_auth_password_ = password; } #endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} +#endif + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT inline void ClientImpl::enable_server_certificate_verification(bool enabled) { server_certificate_verification_ = enabled; @@ -5527,7 +7617,9 @@ inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, } if (ssl) { + set_nonblocking(sock, true); auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); SSL_set_bio(ssl, bio, bio); if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { @@ -5536,32 +7628,58 @@ inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, std::lock_guard guard(ctx_mutex); SSL_free(ssl); } + set_nonblocking(sock, false); return nullptr; } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); } return ssl; } inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, - bool process_socket_ret) { - if (process_socket_ret) { - SSL_shutdown(ssl); // shutdown only if not already closed by remote - } + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { SSL_shutdown(ssl); } std::lock_guard guard(ctx_mutex); SSL_free(ssl); } +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + int res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + template -inline bool -process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count, - time_t keep_alive_timeout_sec, - time_t read_timeout_sec, time_t read_timeout_usec, - time_t write_timeout_sec, time_t write_timeout_usec, - T callback) { +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { return process_server_socket_core( - sock, keep_alive_max_count, keep_alive_timeout_sec, + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, [&](bool close_connection, bool &connection_closed) { SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); @@ -5579,55 +7697,12 @@ process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, return callback(strm); } -#if OPENSSL_VERSION_NUMBER < 0x10100000L -static std::shared_ptr> openSSL_locks_; - -class SSLThreadLocks { -public: - SSLThreadLocks() { - openSSL_locks_ = - std::make_shared>(CRYPTO_num_locks()); - CRYPTO_set_locking_callback(locking_callback); - } - - ~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); } - -private: - static void locking_callback(int mode, int type, const char * /*file*/, - int /*line*/) { - auto &lk = (*openSSL_locks_)[static_cast(type)]; - if (mode & CRYPTO_LOCK) { - lk.lock(); - } else { - lk.unlock(); - } - } -}; - -#endif - class SSLInit { public: SSLInit() { -#if OPENSSL_VERSION_NUMBER < 0x1010001fL - SSL_load_error_strings(); - SSL_library_init(); -#else OPENSSL_init_ssl( OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); -#endif } - - ~SSLInit() { -#if OPENSSL_VERSION_NUMBER < 0x1010001fL - ERR_free_strings(); -#endif - } - -private: -#if OPENSSL_VERSION_NUMBER < 0x10100000L - SSLThreadLocks thread_init_; -#endif }; // SSL socket stream implementation @@ -5640,22 +7715,7 @@ inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), write_timeout_usec_(write_timeout_usec) { - { - timeval tv; - tv.tv_sec = static_cast(read_timeout_sec); - tv.tv_usec = static_cast(read_timeout_usec); - - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), - sizeof(tv)); - } - { - timeval tv; - tv.tv_sec = static_cast(write_timeout_sec); - tv.tv_usec = static_cast(write_timeout_usec); - - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&tv), - sizeof(tv)); - } + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); } inline SSLSocketStream::~SSLSocketStream() {} @@ -5665,19 +7725,70 @@ inline bool SSLSocketStream::is_readable() const { } inline bool SSLSocketStream::is_writable() const { - return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) > - 0; + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); } inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { - if (SSL_pending(ssl_) > 0 || is_readable()) { + if (SSL_pending(ssl_) > 0) { return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; } return -1; } inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { - if (is_writable()) { return SSL_write(ssl_, ptr, static_cast(size)); } + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } return -1; } @@ -5686,6 +7797,13 @@ inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, detail::get_remote_ip_and_port(sock_, ip, port); } +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + static SSLInit sslinit_; } // namespace detail @@ -5693,18 +7811,22 @@ static SSLInit sslinit_; // SSL HTTP server implementation inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, const char *client_ca_cert_file_path, - const char *client_ca_cert_dir_path) { - ctx_ = SSL_CTX_new(SSLv23_server_method()); + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); if (ctx_) { SSL_CTX_set_options(ctx_, - SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | - SSL_OP_NO_COMPRESSION | + SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); - // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - // SSL_CTX_set_tmp_ecdh(ctx_, ecdh); - // EC_KEY_free(ecdh); + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + // add default password callback before opening encrypted private key + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata(ctx_, + (char *)private_key_password); + } if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != @@ -5712,46 +7834,46 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, SSL_CTX_free(ctx_); ctx_ = nullptr; } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { - // if (client_ca_cert_file_path) { - // auto list = SSL_load_client_CA_file(client_ca_cert_file_path); - // SSL_CTX_set_client_CA_list(ctx_, list); - // } - SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, client_ca_cert_dir_path); SSL_CTX_set_verify( - ctx_, - SSL_VERIFY_PEER | - SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE, - nullptr); + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); } } } inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store) { - ctx_ = SSL_CTX_new(SSLv23_server_method()); + ctx_ = SSL_CTX_new(TLS_server_method()); if (ctx_) { SSL_CTX_set_options(ctx_, - SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | - SSL_OP_NO_COMPRESSION | + SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { SSL_CTX_free(ctx_); ctx_ = nullptr; } else if (client_ca_cert_store) { - SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); SSL_CTX_set_verify( - ctx_, - SSL_VERIFY_PEER | - SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE, - nullptr); + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; } } } @@ -5762,13 +7884,21 @@ inline SSLServer::~SSLServer() { inline bool SSLServer::is_valid() const { return ctx_; } -inline bool SSLServer::process_and_close_socket(socket_t sock) { - auto ssl = detail::ssl_new(sock, ctx_, ctx_mutex_, SSL_accept, - [](SSL * /*ssl*/) { return true; }); +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; if (ssl) { - auto ret = detail::process_server_socket_ssl( - ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, [this, ssl](Stream &strm, bool close_connection, @@ -5777,12 +7907,15 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) { [&](Request &req) { req.ssl = ssl; }); }); - detail::ssl_delete(ctx_mutex_, ssl, ret); - return ret; + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); } + detail::shutdown_socket(sock); detail::close_socket(sock); - return false; + return ret; } // SSL HTTP client implementation @@ -5796,12 +7929,13 @@ inline SSLClient::SSLClient(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) : ClientImpl(host, port, client_cert_path, client_key_path) { - ctx_ = SSL_CTX_new(SSLv23_client_method()); + ctx_ = SSL_CTX_new(TLS_client_method()); detail::split(&host_[0], &host_[host_.size()], '.', [&](const char *b, const char *e) { host_components_.emplace_back(std::string(b, e)); }); + if (!client_cert_path.empty() && !client_key_path.empty()) { if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), SSL_FILETYPE_PEM) != 1 || @@ -5816,12 +7950,13 @@ inline SSLClient::SSLClient(const std::string &host, int port, inline SSLClient::SSLClient(const std::string &host, int port, X509 *client_cert, EVP_PKEY *client_key) : ClientImpl(host, port) { - ctx_ = SSL_CTX_new(SSLv23_client_method()); + ctx_ = SSL_CTX_new(TLS_client_method()); detail::split(&host_[0], &host_[host_.size()], '.', [&](const char *b, const char *e) { host_components_.emplace_back(std::string(b, e)); }); + if (client_cert != nullptr && client_key != nullptr) { if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { @@ -5833,18 +7968,25 @@ inline SSLClient::SSLClient(const std::string &host, int port, inline SSLClient::~SSLClient() { if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); } inline bool SSLClient::is_valid() const { return ctx_; } -inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path, - const char *ca_cert_dir_path) { - if (ca_cert_file_path) { ca_cert_file_path_ = ca_cert_file_path; } - if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; } -} - inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { - if (ca_cert_store) { ca_cert_store_ = ca_cert_store; } + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } } inline long SSLClient::get_openssl_verify_result() const { @@ -5853,24 +7995,28 @@ inline long SSLClient::get_openssl_verify_result() const { inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } -inline bool SSLClient::create_and_connect_socket(Socket &socket) { - return is_valid() && ClientImpl::create_and_connect_socket(socket); +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); } +// Assumes that socket_mutex_ is locked and that there are no requests in flight inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, - bool &success) { + bool &success, Error &error) { success = true; Response res2; - if (!detail::process_client_socket( socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { Request req2; req2.method = "CONNECT"; req2.path = host_and_port_; - return process_request(strm, req2, res2, false); + return process_request(strm, req2, res2, false, error); })) { - close_socket(socket, true); + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); success = false; return false; } @@ -5891,9 +8037,13 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, req3, auth, 1, detail::random_string(10), proxy_digest_auth_username_, proxy_digest_auth_password_, true)); - return process_request(strm, req3, res3, false); + return process_request(strm, req3, res3, false, error); })) { - close_socket(socket, true); + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); success = false; return false; } @@ -5922,57 +8072,60 @@ inline bool SSLClient::load_certs() { ca_cert_dir_path_.c_str())) { ret = false; } - } else if (ca_cert_store_ != nullptr) { - if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store_) { - SSL_CTX_set_cert_store(ctx_, ca_cert_store_); - } } else { + auto loaded = false; #ifdef _WIN32 - detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); -#else - SSL_CTX_set_default_verify_paths(ctx_); -#endif + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } } }); return ret; } -inline bool SSLClient::initialize_ssl(Socket &socket) { +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { auto ssl = detail::ssl_new( socket.sock, ctx_, ctx_mutex_, - [&](SSL *ssl) { + [&](SSL *ssl2) { if (server_certificate_verification_) { if (!load_certs()) { - error_ = Error::SSLLoadingCerts; + error = Error::SSLLoadingCerts; return false; } - SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr); + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); } - if (SSL_connect(ssl) != 1) { - error_ = Error::SSLConnection; + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; return false; } if (server_certificate_verification_) { - verify_result_ = SSL_get_verify_result(ssl); + verify_result_ = SSL_get_verify_result(ssl2); if (verify_result_ != X509_V_OK) { - error_ = Error::SSLServerVerification; + error = Error::SSLServerVerification; return false; } - auto server_cert = SSL_get_peer_certificate(ssl); + auto server_cert = SSL_get1_peer_certificate(ssl2); if (server_cert == nullptr) { - error_ = Error::SSLServerVerification; + error = Error::SSLServerVerification; return false; } if (!verify_host(server_cert)) { X509_free(server_cert); - error_ = Error::SSLServerVerification; + error = Error::SSLServerVerification; return false; } X509_free(server_cert); @@ -5980,8 +8133,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket) { return true; }, - [&](SSL *ssl) { - SSL_set_tlsext_host_name(ssl, host_.c_str()); + [&](SSL *ssl2) { + SSL_set_tlsext_host_name(ssl2, host_.c_str()); return true; }); @@ -5990,26 +8143,35 @@ inline bool SSLClient::initialize_ssl(Socket &socket) { return true; } - close_socket(socket, false); + shutdown_socket(socket); + close_socket(socket); return false; } -inline void SSLClient::close_socket(Socket &socket, bool process_socket_ret) { - detail::close_socket(socket.sock); - socket_.sock = INVALID_SOCKET; - if (socket.ssl) { - detail::ssl_delete(ctx_mutex_, socket.ssl, process_socket_ret); - socket_.ssl = nullptr; +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); } inline bool -SSLClient::process_socket(Socket &socket, +SSLClient::process_socket(const Socket &socket, std::function callback) { assert(socket.ssl); return detail::process_client_socket_ssl( socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, - write_timeout_sec_, write_timeout_usec_, callback); + write_timeout_sec_, write_timeout_usec_, std::move(callback)); } inline bool SSLClient::is_ssl() const { return true; } @@ -6065,7 +8227,7 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { if (alt_names) { auto dsn_matched = false; - auto ip_mached = false; + auto ip_matched = false; auto count = sk_GENERAL_NAME_num(alt_names); @@ -6075,22 +8237,20 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); auto name_len = (size_t)ASN1_STRING_length(val->d.ia5); - if (strlen(name) == name_len) { - switch (type) { - case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; - case GEN_IPADD: - if (!memcmp(&addr6, name, addr_len) || - !memcmp(&addr, name, addr_len)) { - ip_mached = true; - } - break; + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; } + break; } } } - if (dsn_matched || ip_mached) { ret = true; } + if (dsn_matched || ip_matched) { ret = true; } } GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); @@ -6143,15 +8303,16 @@ inline bool SSLClient::check_host_name(const char *pattern, #endif // Universal client implementation -inline Client::Client(const char *scheme_host_port) +inline Client::Client(const std::string &scheme_host_port) : Client(scheme_host_port, std::string(), std::string()) {} -inline Client::Client(const char *scheme_host_port, +inline Client::Client(const std::string &scheme_host_port, const std::string &client_cert_path, const std::string &client_key_path) { - const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)"); + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); - std::cmatch m; + std::smatch m; if (std::regex_match(scheme_host_port, m, re)) { auto scheme = m[1].str(); @@ -6159,6 +8320,10 @@ inline Client::Client(const char *scheme_host_port, if (!scheme.empty() && (scheme != "http" && scheme != "https")) { #else if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); #endif return; } @@ -6166,34 +8331,35 @@ inline Client::Client(const char *scheme_host_port, auto is_ssl = scheme == "https"; auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } - auto port_str = m[3].str(); + auto port_str = m[4].str(); auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); if (is_ssl) { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - cli_ = std::make_shared(host.c_str(), port, client_cert_path, - client_key_path); + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); is_ssl_ = is_ssl; #endif } else { - cli_ = std::make_shared(host.c_str(), port, client_cert_path, - client_key_path); + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); } } else { - cli_ = std::make_shared(scheme_host_port, 80, client_cert_path, - client_key_path); + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); } } inline Client::Client(const std::string &host, int port) - : cli_(std::make_shared(host, port)) {} + : cli_(detail::make_unique(host, port)) {} inline Client::Client(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) - : cli_(std::make_shared(host, port, client_cert_path, - client_key_path)) {} + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} inline Client::~Client() {} @@ -6201,198 +8367,351 @@ inline bool Client::is_valid() const { return cli_ != nullptr && cli_->is_valid(); } -inline Result Client::Get(const char *path) { return cli_->Get(path); } -inline Result Client::Get(const char *path, const Headers &headers) { +inline Result Client::Get(const std::string &path) { return cli_->Get(path); } +inline Result Client::Get(const std::string &path, const Headers &headers) { return cli_->Get(path, headers); } -inline Result Client::Get(const char *path, Progress progress) { - return cli_->Get(path, progress); +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); } -inline Result Client::Get(const char *path, const Headers &headers, +inline Result Client::Get(const std::string &path, const Headers &headers, Progress progress) { - return cli_->Get(path, headers, progress); + return cli_->Get(path, headers, std::move(progress)); } -inline Result Client::Get(const char *path, ContentReceiver content_receiver) { +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { return cli_->Get(path, std::move(content_receiver)); } -inline Result Client::Get(const char *path, const Headers &headers, +inline Result Client::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver) { return cli_->Get(path, headers, std::move(content_receiver)); } -inline Result Client::Get(const char *path, ContentReceiver content_receiver, - Progress progress) { +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, std::move(content_receiver), std::move(progress)); } -inline Result Client::Get(const char *path, const Headers &headers, +inline Result Client::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, headers, std::move(content_receiver), std::move(progress)); } -inline Result Client::Get(const char *path, ResponseHandler response_handler, +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, ContentReceiver content_receiver) { return cli_->Get(path, std::move(response_handler), std::move(content_receiver)); } -inline Result Client::Get(const char *path, const Headers &headers, +inline Result Client::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver) { return cli_->Get(path, headers, std::move(response_handler), std::move(content_receiver)); } -inline Result Client::Get(const char *path, ResponseHandler response_handler, +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, std::move(response_handler), std::move(content_receiver), std::move(progress)); } -inline Result Client::Get(const char *path, const Headers &headers, +inline Result Client::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { - return cli_->Get(path, headers, response_handler, content_receiver, progress); + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, content_receiver, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, response_handler, content_receiver, + progress); } -inline Result Client::Head(const char *path) { return cli_->Head(path); } -inline Result Client::Head(const char *path, const Headers &headers) { +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { return cli_->Head(path, headers); } -inline Result Client::Post(const char *path) { return cli_->Post(path); } -inline Result Client::Post(const char *path, const std::string &body, - const char *content_type) { +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { return cli_->Post(path, body, content_type); } -inline Result Client::Post(const char *path, const Headers &headers, - const std::string &body, const char *content_type) { +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { return cli_->Post(path, headers, body, content_type); } -inline Result Client::Post(const char *path, size_t content_length, +inline Result Client::Post(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return cli_->Post(path, content_length, content_provider, content_type); -} -inline Result Client::Post(const char *path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const char *content_type) { - return cli_->Post(path, headers, content_length, content_provider, + const std::string &content_type) { + return cli_->Post(path, content_length, std::move(content_provider), content_type); } -inline Result Client::Post(const char *path, const Params ¶ms) { +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { return cli_->Post(path, params); } -inline Result Client::Post(const char *path, const Headers &headers, +inline Result Client::Post(const std::string &path, const Headers &headers, const Params ¶ms) { return cli_->Post(path, headers, params); } -inline Result Client::Post(const char *path, +inline Result Client::Post(const std::string &path, const MultipartFormDataItems &items) { return cli_->Post(path, items); } -inline Result Client::Post(const char *path, const Headers &headers, +inline Result Client::Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items) { return cli_->Post(path, headers, items); } -inline Result Client::Put(const char *path) { return cli_->Put(path); } -inline Result Client::Put(const char *path, const std::string &body, - const char *content_type) { +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result +Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Post(path, headers, items, provider_items); +} +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { return cli_->Put(path, body, content_type); } -inline Result Client::Put(const char *path, const Headers &headers, - const std::string &body, const char *content_type) { +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { return cli_->Put(path, headers, body, content_type); } -inline Result Client::Put(const char *path, size_t content_length, +inline Result Client::Put(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return cli_->Put(path, content_length, content_provider, content_type); -} -inline Result Client::Put(const char *path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const char *content_type) { - return cli_->Put(path, headers, content_length, content_provider, + const std::string &content_type) { + return cli_->Put(path, content_length, std::move(content_provider), content_type); } -inline Result Client::Put(const char *path, const Params ¶ms) { +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { return cli_->Put(path, params); } -inline Result Client::Put(const char *path, const Headers &headers, +inline Result Client::Put(const std::string &path, const Headers &headers, const Params ¶ms) { return cli_->Put(path, headers, params); } -inline Result Client::Patch(const char *path, const std::string &body, - const char *content_type) { +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Put(path, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Put(path, headers, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Put(path, headers, items, boundary); +} +inline Result +Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Put(path, headers, items, provider_items); +} +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { return cli_->Patch(path, body, content_type); } -inline Result Client::Patch(const char *path, const Headers &headers, - const std::string &body, const char *content_type) { +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { return cli_->Patch(path, headers, body, content_type); } -inline Result Client::Patch(const char *path, size_t content_length, +inline Result Client::Patch(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return cli_->Patch(path, content_length, content_provider, content_type); -} -inline Result Client::Patch(const char *path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const char *content_type) { - return cli_->Patch(path, headers, content_length, content_provider, + const std::string &content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), content_type); } -inline Result Client::Delete(const char *path) { return cli_->Delete(path); } -inline Result Client::Delete(const char *path, const std::string &body, - const char *content_type) { - return cli_->Delete(path, body, content_type); +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); } -inline Result Client::Delete(const char *path, const Headers &headers) { +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { return cli_->Delete(path, headers); } -inline Result Client::Delete(const char *path, const Headers &headers, +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, const std::string &body, - const char *content_type) { + const std::string &content_type) { return cli_->Delete(path, headers, body, content_type); } -inline Result Client::Options(const char *path) { return cli_->Options(path); } -inline Result Client::Options(const char *path, const Headers &headers) { +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { return cli_->Options(path, headers); } -inline bool Client::send(const Request &req, Response &res) { - return cli_->send(req, res); +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); } +inline Result Client::send(const Request &req) { return cli_->send(req); } + inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } +inline socket_t Client::socket() const { return cli_->socket(); } + inline void Client::stop() { cli_->stop(); } +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} + inline void Client::set_default_headers(Headers headers) { cli_->set_default_headers(std::move(headers)); } +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} + inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + inline void Client::set_socket_options(SocketOptions socket_options) { - cli_->set_socket_options(socket_options); + cli_->set_socket_options(std::move(socket_options)); } inline void Client::set_connection_timeout(time_t sec, time_t usec) { cli_->set_connection_timeout(sec, usec); } + inline void Client::set_read_timeout(time_t sec, time_t usec) { cli_->set_read_timeout(sec, usec); } + inline void Client::set_write_timeout(time_t sec, time_t usec) { cli_->set_write_timeout(sec, usec); } -inline void Client::set_basic_auth(const char *username, const char *password) { +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { cli_->set_basic_auth(username, password); } -inline void Client::set_bearer_token_auth(const char *token) { +inline void Client::set_bearer_token_auth(const std::string &token) { cli_->set_bearer_token_auth(token); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_digest_auth(const char *username, - const char *password) { +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { cli_->set_digest_auth(username, password); } #endif @@ -6402,27 +8721,29 @@ inline void Client::set_follow_location(bool on) { cli_->set_follow_location(on); } +inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + inline void Client::set_compress(bool on) { cli_->set_compress(on); } inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } -inline void Client::set_interface(const char *intf) { +inline void Client::set_interface(const std::string &intf) { cli_->set_interface(intf); } -inline void Client::set_proxy(const char *host, int port) { +inline void Client::set_proxy(const std::string &host, int port) { cli_->set_proxy(host, port); } -inline void Client::set_proxy_basic_auth(const char *username, - const char *password) { +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { cli_->set_proxy_basic_auth(username, password); } -inline void Client::set_proxy_bearer_token_auth(const char *token) { +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { cli_->set_proxy_bearer_token_auth(token); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_proxy_digest_auth(const char *username, - const char *password) { +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { cli_->set_proxy_digest_auth(username, password); } #endif @@ -6436,17 +8757,16 @@ inline void Client::enable_server_certificate_verification(bool enabled) { inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_ca_cert_path(const char *ca_cert_file_path, - const char *ca_cert_dir_path) { - if (is_ssl_) { - static_cast(*cli_).set_ca_cert_path(ca_cert_file_path, - ca_cert_dir_path); - } +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); } inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { if (is_ssl_) { static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); } } @@ -6467,4 +8787,8 @@ inline SSL_CTX *Client::ssl_context() const { } // namespace httplib +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + #endif // CPPHTTPLIB_HTTPLIB_H diff --git a/service/OneService.cpp b/service/OneService.cpp index 2d3cfdf2e..2a6973497 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -11,6 +11,7 @@ */ /****/ +#include #include #include #include @@ -53,6 +54,8 @@ #include "OneService.hpp" #include "SoftwareUpdater.hpp" +#include + #if ZT_SSO_ENABLED #include #endif @@ -198,6 +201,58 @@ std::string ssoResponseTemplate = R"""( )"""; +#if ZT_DEBUG==1 +std::string dump_headers(const httplib::Headers &headers) { + std::string s; + char buf[BUFSIZ]; + + for (auto it = headers.begin(); it != headers.end(); ++it) { + const auto &x = *it; + snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str()); + s += buf; + } + + return s; +} + +std::string http_log(const httplib::Request &req, const httplib::Response &res) { + std::string s; + char buf[BUFSIZ]; + + s += "================================\n"; + + snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(), + req.version.c_str(), req.path.c_str()); + s += buf; + + std::string query; + for (auto it = req.params.begin(); it != req.params.end(); ++it) { + const auto &x = *it; + snprintf(buf, sizeof(buf), "%c%s=%s", + (it == req.params.begin()) ? '?' : '&', x.first.c_str(), + x.second.c_str()); + query += buf; + } + snprintf(buf, sizeof(buf), "%s\n", query.c_str()); + s += buf; + + s += dump_headers(req.headers); + + s += "--------------------------------\n"; + + snprintf(buf, sizeof(buf), "%d %s\n", res.status, res.version.c_str()); + s += buf; + s += dump_headers(res.headers); + s += "\n"; + + if (!res.body.empty()) { s += res.body; } + + s += "\n"; + + return s; +} +#endif + // Configured networks class NetworkState { @@ -715,10 +770,12 @@ public: Phy _phy; Node *_node; SoftwareUpdater *_updater; - PhySocket *_localControlSocket4; - PhySocket *_localControlSocket6; bool _updateAutoApply; - bool _allowTcpFallbackRelay; + + httplib::Server _controlPlane; + std::thread _serverThread; + + bool _allowTcpFallbackRelay; bool _forceTcpRelay; bool _allowSecondaryPort; @@ -815,9 +872,9 @@ public: ,_phy(this,false,true) ,_node((Node *)0) ,_updater((SoftwareUpdater *)0) - ,_localControlSocket4((PhySocket *)0) - ,_localControlSocket6((PhySocket *)0) ,_updateAutoApply(false) + ,_controlPlane() + ,_serverThread() ,_forceTcpRelay(false) ,_primaryPort(port) ,_udpPortPickerCounter(0) @@ -863,13 +920,13 @@ public: WinFWHelper::removeICMPRules(); #endif _binder.closeAll(_phy); - _phy.close(_localControlSocket4); - _phy.close(_localControlSocket6); #if ZT_VAULT_SUPPORT curl_global_cleanup(); #endif + _controlPlane.stop(); + _serverThread.join(); #ifdef ZT_USE_MINIUPNPC @@ -945,22 +1002,6 @@ public: return _termReason; } - // Bind TCP control socket to 127.0.0.1 and ::1 as well for loopback TCP control socket queries - { - struct sockaddr_in lo4; - memset(&lo4,0,sizeof(lo4)); - lo4.sin_family = AF_INET; - lo4.sin_addr.s_addr = Utils::hton((uint32_t)0x7f000001); - lo4.sin_port = Utils::hton((uint16_t)_ports[0]); - _localControlSocket4 = _phy.tcpListen((const struct sockaddr *)&lo4); - struct sockaddr_in6 lo6; - memset(&lo6,0,sizeof(lo6)); - lo6.sin6_family = AF_INET6; - lo6.sin6_addr.s6_addr[15] = 1; - lo6.sin6_port = lo4.sin_port; - _localControlSocket6 = _phy.tcpListen((const struct sockaddr *)&lo6); - } - // Save primary port to a file so CLIs and GUIs can learn it easily char portstr[64]; OSUtils::ztsnprintf(portstr,sizeof(portstr),"%u",_ports[0]); @@ -1013,6 +1054,8 @@ public: } _node->setNetconfMaster((void *)_controller); + startHTTPControlPlane(); + // Join existing networks in networks.d { std::vector networksDotD(OSUtils::listDirectory((_homePath + ZT_PATH_SEPARATOR_S "networks.d").c_str())); @@ -1402,559 +1445,567 @@ public: return true; } - // ========================================================================= - // Internal implementation methods for control plane, route setup, etc. - // ========================================================================= + // Internal HTTP Control Plane + void startHTTPControlPlane() { + std::vector noAuthEndpoints { "/sso", "/health" }; - inline unsigned int handleControlPlaneHttpRequest( - const InetAddress &fromAddress, - unsigned int httpMethod, - const std::string &path, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) - { - char tmp[256]; - unsigned int scode = 404; - json res; - std::vector ps(OSUtils::split(path.c_str(),"/","","")); - std::map urlArgs; - - /* Note: this is kind of restricted in what it'll take. It does not support - * URL encoding, and /'s in URL args will screw it up. But the only URL args - * it really uses in ?jsonp=functionName, and otherwise it just takes simple - * paths to simply-named resources. */ - if (!ps.empty()) { - std::size_t qpos = ps[ps.size() - 1].find('?'); - if (qpos != std::string::npos) { - std::string args(ps[ps.size() - 1].substr(qpos + 1)); - ps[ps.size() - 1] = ps[ps.size() - 1].substr(0,qpos); - std::vector asplit(OSUtils::split(args.c_str(),"&","","")); - for(std::vector::iterator a(asplit.begin());a!=asplit.end();++a) { - std::size_t eqpos = a->find('='); - if (eqpos == std::string::npos) - urlArgs[*a] = ""; - else urlArgs[a->substr(0,eqpos)] = a->substr(eqpos + 1); + auto setContent = [=] (const httplib::Request &req, httplib::Response &res, std::string content) { + if (req.has_param("jsonp")) { + if (content.length() > 0) { + res.set_content(req.get_param_value("jsonp") + "(" + content + ");", "application/javascript"); + } else { + res.set_content(req.get_param_value("jsonp") + "(null);", "application/javascript"); } - } - } else { - return 404; - } - - bool isAuth = false; - { - std::map::const_iterator ah(headers.find("x-zt1-auth")); - if ((ah != headers.end())&&(_authToken == ah->second)) { - isAuth = true; } else { - ah = urlArgs.find("auth"); - if ((ah != urlArgs.end())&&(_authToken == ah->second)) - isAuth = true; + if (content.length() > 0) { + res.set_content(content, "application/json"); + } else { + res.set_content("{}", "application/json"); + } } - } + }; -#ifdef __SYNOLOGY__ - // Authenticate via Synology's built-in cgi script - if (!isAuth) { - int synotoken_pos = path.find("SynoToken"); - int argpos = path.find('?'); - if(synotoken_pos != std::string::npos && argpos != std::string::npos) { - std::string cookie = path.substr(argpos+1, synotoken_pos-(argpos+1)); - std::string synotoken = path.substr(synotoken_pos); - std::string cookie_val = cookie.substr(cookie.find('=')+1); - std::string synotoken_val = synotoken.substr(synotoken.find('=')+1); - // Set necessary env for auth script - std::map::const_iterator ah2(headers.find("x-forwarded-for")); - setenv("HTTP_COOKIE", cookie_val.c_str(), true); - setenv("HTTP_X_SYNO_TOKEN", synotoken_val.c_str(), true); - setenv("REMOTE_ADDR", ah2->second.c_str(),true); - char user[256], buf[1024]; - FILE *fp = NULL; - bzero(user, 256); - fp = popen("/usr/syno/synoman/webman/modules/authenticate.cgi", "r"); - if(!fp) - isAuth = false; - else { - bzero(buf, sizeof(buf)); - fread(buf, 1024, 1, fp); - if(strlen(buf) > 0) { - snprintf(user, 256, "%s", buf); - isAuth = true; + + auto authCheck = [=] (const httplib::Request &req, httplib::Response &res) { + std::string r = req.remote_addr; + InetAddress remoteAddr(r.c_str()); + + bool ipAllowed = false; + bool isAuth = false; + // If localhost, allow + if (remoteAddr.ipScope() == InetAddress::IP_SCOPE_LOOPBACK) { + ipAllowed = true; + } + + if (!ipAllowed) { + for (auto i = _allowManagementFrom.begin(); i != _allowManagementFrom.end(); ++i) { + if (i->containsAddress(remoteAddr)) { + ipAllowed = true; + break; + } + } + } + + + if (ipAllowed) { + // auto-pass endpoints in `noAuthEndpoints`. No auth token required + if (std::find(noAuthEndpoints.begin(), noAuthEndpoints.end(), req.path) != noAuthEndpoints.end()) { + isAuth = true; + } + + if (!isAuth) { + // check auth token + if (req.has_header("x-zt1-auth")) { + std::string token = req.get_header_value("x-zt1-auth"); + if (token == _authToken) { + isAuth = true; + } + } else if (req.has_param("auth")) { + std::string token = req.get_param_value("auth"); + if (token == _authToken) { + isAuth = true; + } + } + } + } + + if (ipAllowed && isAuth) { + return httplib::Server::HandlerResponse::Unhandled; + } + setContent(req, res, "{}"); + res.status = 401; + return httplib::Server::HandlerResponse::Handled; + }; + + + + _controlPlane.Get("/bond/show/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) { + if (!_node->bondController()->inUse()) { + setContent(req, res, ""); + res.status = 400; + return; + } + + ZT_PeerList *pl = _node->peers(); + if (pl) { + auto id = req.matches[1]; + auto out = json::object(); + uint64_t wantp = Utils::hexStrToU64(id.str().c_str()); + for(unsigned long i=0;ipeerCount;++i) { + if (pl->peers[i].address == wantp) { + SharedPtr bond = _node->bondController()->getBondByPeerId(wantp); + if (bond) { + _peerToJson(out,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); + setContent(req, res, out.dump()); + } else { + setContent(req, res, ""); + res.status = 400; + } } } - pclose(fp); } - } -#endif - if (httpMethod == HTTP_GET) { - if (isAuth) { - if (ps[0] == "bond") { - if (_node->bondController()->inUse()) { - if (ps.size() == 3) { - if (ps[2].length() == 10) { - // check if hex string + _node->freeQueryResult((void *)pl); + }); - ZT_PeerList *pl = _node->peers(); - if (pl) { - uint64_t wantp = Utils::hexStrToU64(ps[2].c_str()); - for(unsigned long i=0;ipeerCount;++i) { - if (pl->peers[i].address == wantp) { - if (ps[1] == "show") { - SharedPtr bond = _node->bondController()->getBondByPeerId(wantp); - if (bond) { - _peerToJson(res,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); - scode = 200; - } else { - scode = 400; - } - } - } - } - } - _node->freeQueryResult((void *)pl); - } - } - } else { - scode = 400; /* bond controller is not enabled */ - } - } else if (ps[0] == "config") { - Mutex::Lock lc(_localConfig_m); - res = _localConfig; - scode = 200; - } else if (ps[0] == "status") { - ZT_NodeStatus status; - _node->status(&status); + auto bondRotate = [&](const httplib::Request &req, httplib::Response &res) { + if (!_node->bondController()->inUse()) { + setContent(req, res, ""); + res.status = 400; + return; + } - OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.10llx",status.address); - res["address"] = tmp; - res["publicIdentity"] = status.publicIdentity; - res["online"] = (bool)(status.online != 0); - res["tcpFallbackActive"] = (_tcpFallbackTunnel != (TcpConnection *)0); - res["versionMajor"] = ZEROTIER_ONE_VERSION_MAJOR; - res["versionMinor"] = ZEROTIER_ONE_VERSION_MINOR; - res["versionRev"] = ZEROTIER_ONE_VERSION_REVISION; - res["versionBuild"] = ZEROTIER_ONE_VERSION_BUILD; - OSUtils::ztsnprintf(tmp,sizeof(tmp),"%d.%d.%d",ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); - res["version"] = tmp; - res["clock"] = OSUtils::now(); + auto bondID = req.matches[1]; + uint64_t id = Utils::hexStrToU64(bondID.str().c_str()); - { - Mutex::Lock _l(_localConfig_m); - res["config"] = _localConfig; + exit(0); + SharedPtr bond = _node->bondController()->getBondByPeerId(id); + if (bond) { + if (bond->abForciblyRotateLink()) { + res.status = 200; + } else { + res.status = 400; + } + } else { + fprintf(stderr, "unable to find bond to peer %llx\n", (unsigned long long)id); + res.status = 400; + } + setContent(req, res, "{}"); + }; + _controlPlane.Post("/bond/rotate/([0-9a-fA-F]{10})", bondRotate); + _controlPlane.Put("/bond/rotate/([0-9a-fA-F]{10})", bondRotate); + + _controlPlane.Get("/config", [&](const httplib::Request &req, httplib::Response &res) { + std::string config; + { + Mutex::Lock lc(_localConfig_m); + config = _localConfig.dump(); + } + if (config == "null") { + config = "{}"; + } + setContent(req, res, config); + }); + + auto configPost = [&](const httplib::Request &req, httplib::Response &res) { + json j(OSUtils::jsonParse(req.body)); + if (j.is_object()) { + Mutex::Lock lcl(_localConfig_m); + json lc(_localConfig); + for(json::const_iterator s(j.begin()); s != j.end(); ++s) { + lc["settings"][s.key()] = s.value(); + } + std::string lcStr = OSUtils::jsonDump(lc, 4); + if (OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S "local.conf").c_str(), lcStr)) { + _localConfig = lc; + } + } + setContent(req, res, "{}"); + }; + _controlPlane.Post("/config/settings", configPost); + _controlPlane.Put("/config/settings", configPost); + + _controlPlane.Get("/health", [&](const httplib::Request &req, httplib::Response &res) { + json out = json::object(); + + char tmp[256]; + + ZT_NodeStatus status; + _node->status(&status); + + out["online"] = (bool)(status.online != 0); + out["versionMajor"] = ZEROTIER_ONE_VERSION_MAJOR; + out["versionMinor"] = ZEROTIER_ONE_VERSION_MINOR; + out["versionRev"] = ZEROTIER_ONE_VERSION_REVISION; + out["versionBuild"] = ZEROTIER_ONE_VERSION_BUILD; + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%d.%d.%d",ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); + out["version"] = tmp; + out["clock"] = OSUtils::now(); + + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/moon", [&](const httplib::Request &req, httplib::Response &res) { + std::vector moons(_node->moons()); + + auto out = json::array(); + for (auto i = moons.begin(); i != moons.end(); ++i) { + json mj; + _moonToJson(mj, *i); + out.push_back(mj); + } + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/moon/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res){ + std::vector moons(_node->moons()); + auto input = req.matches[1]; + auto out = json::object(); + const uint64_t id = Utils::hexStrToU64(input.str().c_str()); + for (auto i = moons.begin(); i != moons.end(); ++i) { + if (i->id() == id) { + _moonToJson(out, *i); + break; + } + } + setContent(req, res, out.dump()); + }); + + auto moonPost = [&](const httplib::Request &req, httplib::Response &res) { + auto input = req.matches[1]; + uint64_t seed = 0; + try { + json j(OSUtils::jsonParse(req.body)); + if (j.is_object()) { + seed = Utils::hexStrToU64(OSUtils::jsonString(j["seed"],"0").c_str()); + } + } catch ( ... ) { + // discard invalid JSON + } + + std::vector moons(_node->moons()); + const uint64_t id = Utils::hexStrToU64(input.str().c_str()); + bool found = false; + auto out = json::object(); + for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { + if (m->id() == id) { + _moonToJson(out,*m); + found = true; + break; + } + } + + if (!found && seed != 0) { + char tmp[64]; + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",id); + out["id"] = tmp; + out["roots"] = json::array(); + out["timestamp"] = 0; + out["signature"] = json(); + out["updatesMustBeSignedBy"] = json(); + out["waiting"] = true; + _node->orbit((void *)0,id,seed); + } + setContent(req, res, out.dump()); + }; + _controlPlane.Post("/moon/([0-9a-fA-F]{10})", moonPost); + _controlPlane.Put("/moon/([0-9a-fA-F]{10})", moonPost); + + _controlPlane.Delete("/moon/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) { + auto input = req.matches[1]; + uint64_t id = Utils::hexStrToU64(input.str().c_str()); + auto out = json::object(); + _node->deorbit((void*)0,id); + out["result"] = true; + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/network", [&](const httplib::Request &req, httplib::Response &res) { + Mutex::Lock _l(_nets_m); + auto out = json::array(); + + for (auto it = _nets.begin(); it != _nets.end(); ++it) { + NetworkState &ns = it->second; + json nj; + _networkToJson(nj, ns); + out.push_back(nj); + } + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) { + Mutex::Lock _l(_nets_m); + + auto input = req.matches[1]; + const uint64_t nwid = Utils::hexStrToU64(input.str().c_str()); + if (_nets.find(nwid) != _nets.end()) { + auto out = json::object(); + NetworkState &ns = _nets[nwid]; + _networkToJson(out, ns); + setContent(req, res, out.dump()); + return; + } + setContent(req, res, ""); + res.status = 404; + }); + + auto networkPost = [&](const httplib::Request &req, httplib::Response &res) { + auto input = req.matches[1]; + uint64_t wantnw = Utils::hexStrToU64(input.str().c_str()); + _node->join(wantnw, (void*)0, (void*)0); + auto out = json::object(); + Mutex::Lock l(_nets_m); + if (!_nets.empty()) { + NetworkState &ns = _nets[wantnw]; + try { + json j(OSUtils::jsonParse(req.body)); + + json &allowManaged = j["allowManaged"]; + if (allowManaged.is_boolean()) { + ns.setAllowManaged((bool)allowManaged); } - json &settings = res["config"]["settings"]; - settings["allowTcpFallbackRelay"] = OSUtils::jsonBool(settings["allowTcpFallbackRelay"],_allowTcpFallbackRelay); - settings["forceTcpRelay"] = OSUtils::jsonBool(settings["forceTcpRelay"],_forceTcpRelay); - settings["primaryPort"] = OSUtils::jsonInt(settings["primaryPort"],(uint64_t)_primaryPort) & 0xffff; - settings["secondaryPort"] = OSUtils::jsonInt(settings["secondaryPort"],(uint64_t)_secondaryPort) & 0xffff; - settings["tertiaryPort"] = OSUtils::jsonInt(settings["tertiaryPort"],(uint64_t)_tertiaryPort) & 0xffff; - // Enumerate all local address/port pairs that this node is listening on - std::vector boundAddrs(_binder.allBoundLocalInterfaceAddresses()); - auto boundAddrArray = json::array(); - for (int i = 0; i < boundAddrs.size(); i++) { - char ipBuf[64] = { 0 }; - boundAddrs[i].toString(ipBuf); - boundAddrArray.push_back(ipBuf); + json& allowGlobal = j["allowGlobal"]; + if (allowGlobal.is_boolean()) { + ns.setAllowGlobal((bool)allowGlobal); } - settings["listeningOn"] = boundAddrArray; - // Enumerate all external address/port pairs that are reported for this node - std::vector surfaceAddrs = _node->SurfaceAddresses(); - auto surfaceAddrArray = json::array(); - for (int i = 0; i < surfaceAddrs.size(); i++) { - char ipBuf[64] = { 0 }; - surfaceAddrs[i].toString(ipBuf); - surfaceAddrArray.push_back(ipBuf); + json& allowDefault = j["allowDefault"]; + if (allowDefault.is_boolean()) { + ns.setAllowDefault((bool)allowDefault); } - settings["surfaceAddresses"] = surfaceAddrArray; + json& allowDNS = j["allowDNS"]; + if (allowDNS.is_boolean()) { + ns.setAllowDNS((bool)allowDNS); + } + } catch (...) { + // discard invalid JSON + } + setNetworkSettings(wantnw, ns.settings()); + if (ns.tap()) { + syncManagedStuff(ns,true,true,true); + } + + _networkToJson(out, ns); + } + setContent(req, res, out.dump()); + }; + _controlPlane.Post("/network/([0-9a-fA-F]{16})", networkPost); + _controlPlane.Put("/network/([0-9a-fA-F]){16}", networkPost); + + _controlPlane.Delete("/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) { + auto input = req.matches[1]; + auto out = json::object(); + ZT_VirtualNetworkList *nws = _node->networks(); + uint64_t wantnw = Utils::hexStrToU64(input.str().c_str()); + for(unsigned long i=0; i < nws->networkCount; ++i) { + if (nws->networks[i].nwid == wantnw) { + _node->leave(wantnw, (void**)0, (void*)0); + out["result"] = true; + } + } + _node->freeQueryResult((void*)nws); + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/peer", [&](const httplib::Request &req, httplib::Response &res) { + ZT_PeerList *pl = _node->peers(); + auto out = nlohmann::json::array(); + + for(unsigned long i=0;ipeerCount;++i) { + nlohmann::json pj; + SharedPtr bond = SharedPtr(); + if (pl->peers[i].isBonded) { + const uint64_t id = pl->peers[i].address; + bond = _node->bondController()->getBondByPeerId(id); + } + _peerToJson(pj,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); + out.push_back(pj); + } + _node->freeQueryResult((void*)pl); + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/peer/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) { + ZT_PeerList *pl = _node->peers(); + + auto input = req.matches[1]; + uint64_t wantp = Utils::hexStrToU64(input.str().c_str()); + auto out = json::object(); + for(unsigned long i=0;ipeerCount;++i) { + if (pl->peers[i].address == wantp) { + SharedPtr bond = SharedPtr(); + if (pl->peers[i].isBonded) { + bond = _node->bondController()->getBondByPeerId(wantp); + } + _peerToJson(out,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); + break; + } + } + _node->freeQueryResult((void*)pl); + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/status", [&](const httplib::Request &req, httplib::Response &res) { + ZT_NodeStatus status; + _node->status(&status); + + auto out = json::object(); + char tmp[256] = {}; + + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.10llx",status.address); + out["address"] = tmp; + out["publicIdentity"] = status.publicIdentity; + out["online"] = (bool)(status.online != 0); + out["tcpFallbackActive"] = (_tcpFallbackTunnel != (TcpConnection *)0); + out["versionMajor"] = ZEROTIER_ONE_VERSION_MAJOR; + out["versionMinor"] = ZEROTIER_ONE_VERSION_MINOR; + out["versionRev"] = ZEROTIER_ONE_VERSION_REVISION; + out["versionBuild"] = ZEROTIER_ONE_VERSION_BUILD; + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%d.%d.%d",ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); + out["version"] = tmp; + out["clock"] = OSUtils::now(); + + { + Mutex::Lock _l(_localConfig_m); + out["config"] = _localConfig; + } + json &settings = out["config"]["settings"]; + settings["allowTcpFallbackRelay"] = OSUtils::jsonBool(settings["allowTcpFallbackRelay"],_allowTcpFallbackRelay); + settings["forceTcpRelay"] = OSUtils::jsonBool(settings["forceTcpRelay"],_forceTcpRelay); + settings["primaryPort"] = OSUtils::jsonInt(settings["primaryPort"],(uint64_t)_primaryPort) & 0xffff; + settings["secondaryPort"] = OSUtils::jsonInt(settings["secondaryPort"],(uint64_t)_secondaryPort) & 0xffff; + settings["tertiaryPort"] = OSUtils::jsonInt(settings["tertiaryPort"],(uint64_t)_tertiaryPort) & 0xffff; + // Enumerate all local address/port pairs that this node is listening on + std::vector boundAddrs(_binder.allBoundLocalInterfaceAddresses()); + auto boundAddrArray = json::array(); + for (int i = 0; i < boundAddrs.size(); i++) { + char ipBuf[64] = { 0 }; + boundAddrs[i].toString(ipBuf); + boundAddrArray.push_back(ipBuf); + } + settings["listeningOn"] = boundAddrArray; + // Enumerate all external address/port pairs that are reported for this node + std::vector surfaceAddrs = _node->SurfaceAddresses(); + auto surfaceAddrArray = json::array(); + for (int i = 0; i < surfaceAddrs.size(); i++) { + char ipBuf[64] = { 0 }; + surfaceAddrs[i].toString(ipBuf); + surfaceAddrArray.push_back(ipBuf); + } + settings["surfaceAddresses"] = surfaceAddrArray; #ifdef ZT_USE_MINIUPNPC - settings["portMappingEnabled"] = OSUtils::jsonBool(settings["portMappingEnabled"],true); + settings["portMappingEnabled"] = OSUtils::jsonBool(settings["portMappingEnabled"],true); #else - settings["portMappingEnabled"] = false; // not supported in build + settings["portMappingEnabled"] = false; // not supported in build #endif #ifndef ZT_SDK - settings["softwareUpdate"] = OSUtils::jsonString(settings["softwareUpdate"],ZT_SOFTWARE_UPDATE_DEFAULT); - settings["softwareUpdateChannel"] = OSUtils::jsonString(settings["softwareUpdateChannel"],ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL); + settings["softwareUpdate"] = OSUtils::jsonString(settings["softwareUpdate"],ZT_SOFTWARE_UPDATE_DEFAULT); + settings["softwareUpdateChannel"] = OSUtils::jsonString(settings["softwareUpdateChannel"],ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL); #endif - const World planet(_node->planet()); - res["planetWorldId"] = planet.id(); - res["planetWorldTimestamp"] = planet.timestamp(); + const World planet(_node->planet()); + out["planetWorldId"] = planet.id(); + out["planetWorldTimestamp"] = planet.timestamp(); - scode = 200; - } else if (ps[0] == "moon") { - std::vector moons(_node->moons()); - if (ps.size() == 1) { - // Return [array] of all moons + setContent(req, res, out.dump()); + }); - res = json::array(); - for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { - json mj; - _moonToJson(mj,*m); - res.push_back(mj); - } - - scode = 200; - } else { - // Return a single moon by ID - - const uint64_t id = Utils::hexStrToU64(ps[1].c_str()); - for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { - if (m->id() == id) { - _moonToJson(res,*m); - scode = 200; - break; - } - } - - } - } else if (ps[0] == "network") { - Mutex::Lock _l(_nets_m); - if (ps.size() == 1) { - // Return [array] of all networks - - res = nlohmann::json::array(); - - for (auto it = _nets.begin(); it != _nets.end(); ++it) { - NetworkState &ns = it->second; - nlohmann::json nj; - _networkToJson(nj, ns); - res.push_back(nj); - } - - scode = 200; - } else if (ps.size() == 2) { - // Return a single network by ID or 404 if not found - - const uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str()); - if (_nets.find(wantnw) != _nets.end()) { - res = json::object(); - NetworkState& ns = _nets[wantnw]; - _networkToJson(res, ns); - scode = 200; - } - } else { - scode = 404; - } - } else if (ps[0] == "peer") { - ZT_PeerList *pl = _node->peers(); - if (pl) { - if (ps.size() == 1) { - // Return [array] of all peers - - res = nlohmann::json::array(); - for(unsigned long i=0;ipeerCount;++i) { - nlohmann::json pj; - SharedPtr bond = SharedPtr(); - if (pl->peers[i].isBonded) { - const uint64_t id = pl->peers[i].address; - bond = _node->bondController()->getBondByPeerId(id); - } - _peerToJson(pj,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); - res.push_back(pj); - } - - scode = 200; - } else if (ps.size() == 2) { - // Return a single peer by ID or 404 if not found - - uint64_t wantp = Utils::hexStrToU64(ps[1].c_str()); - for(unsigned long i=0;ipeerCount;++i) { - if (pl->peers[i].address == wantp) { - SharedPtr bond = SharedPtr(); - if (pl->peers[i].isBonded) { - bond = _node->bondController()->getBondByPeerId(wantp); - } - _peerToJson(res,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); - scode = 200; - break; - } - } - - } else scode = 404; - _node->freeQueryResult((void *)pl); - } else scode = 500;\ - } else if (ps[0] == "metrics") { - std::string statspath = _homePath + ZT_PATH_SEPARATOR + "metrics.prom"; - if (!OSUtils::readFile(statspath.c_str(), responseBody)) { - scode = 500; - } else { - scode = 200; - responseContentType = "text/plain"; - } - } else { - if (_controller) { - scode = _controller->handleControlPlaneHttpGET(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); - } else scode = 404; - } #if ZT_SSO_ENABLED - } else if (ps[0] == "sso") { - std::string htmlTemplatePath = _homePath + ZT_PATH_SEPARATOR + "sso-auth.template.html"; - std::string htmlTemplate; - if (!OSUtils::readFile(htmlTemplatePath.c_str(), htmlTemplate)) { - htmlTemplate = ssoResponseTemplate; - } + _controlPlane.Get("/sso", [this](const httplib::Request &req, httplib::Response &res) { + std::string htmlTemplatePath = _homePath + ZT_PATH_SEPARATOR + "sso-auth.template.html"; + std::string htmlTemplate; + if (!OSUtils::readFile(htmlTemplatePath.c_str(), htmlTemplate)) { + htmlTemplate = ssoResponseTemplate; + } - responseContentType = "text/html"; - json outData; + std::string responseContentType = "text/html"; + std::string responseBody = ""; + json outData; - char *error = zeroidc::zeroidc_get_url_param_value("error", path.c_str()); - if (error != nullptr) { - char *desc = zeroidc::zeroidc_get_url_param_value("error_description", path.c_str()); - scode = 500; - json data; - outData["isError"] = true; - outData["messageText"] = (std::string("ERROR ") + error + std::string(": ") + desc); - responseBody = inja::render(htmlTemplate, outData); + if (req.has_param("error")) { + std::string error = req.get_param_value("error"); + std::string desc = req.get_param_value("error_description"); - zeroidc::free_cstr(desc); - zeroidc::free_cstr(error); + json data; + outData["isError"] = true; + outData["messageText"] = (std::string("ERROR ") + error + std::string(": ") + desc); + responseBody = inja::render(htmlTemplate, outData); - return scode; - } + res.set_content(responseBody, responseContentType); + res.status = 500; + return; + } - // SSO redirect handling - char* state = zeroidc::zeroidc_get_url_param_value("state", path.c_str()); - char* nwid = zeroidc::zeroidc_network_id_from_state(state); + // SSO redirect handling + std::string state = req.get_param_value("state"); + char* nwid = zeroidc::zeroidc_network_id_from_state(state.c_str()); - outData["networkId"] = std::string(nwid); + outData["networkId"] = std::string(nwid); - const uint64_t id = Utils::hexStrToU64(nwid); - - zeroidc::free_cstr(nwid); - zeroidc::free_cstr(state); + const uint64_t id = Utils::hexStrToU64(nwid); - Mutex::Lock l(_nets_m); - if (_nets.find(id) != _nets.end()) { - NetworkState& ns = _nets[id]; - char* code = zeroidc::zeroidc_get_url_param_value("code", path.c_str()); - char *ret = ns.doTokenExchange(code); - json ssoResult = json::parse(ret); - if (ssoResult.is_object()) { - if (ssoResult.contains("errorMessage")) { - outData["isError"] = true; - outData["messageText"] = ssoResult["errorMessage"]; - responseBody = inja::render(htmlTemplate, outData); - scode = 500; - } else { - scode = 200; - outData["isError"] = false; - outData["messageText"] = "Authentication Successful. You may now access the network."; - responseBody = inja::render(htmlTemplate, outData); - } - } else { - // not an object? We got a problem - outData["isError"] = true; - outData["messageText"] = "ERROR: Unkown SSO response. Please contact your administrator."; - responseBody = inja::render(htmlTemplate, outData); - scode= 500; - } + zeroidc::free_cstr(nwid); - zeroidc::free_cstr(code); - zeroidc::free_cstr(ret); + Mutex::Lock l(_nets_m); + if (_nets.find(id) != _nets.end()) { + NetworkState& ns = _nets[id]; + std::string code = req.get_param_value("code"); + char *ret = ns.doTokenExchange(code.c_str()); + json ssoResult = json::parse(ret); + if (ssoResult.is_object()) { + if (ssoResult.contains("errorMessage")) { + outData["isError"] = true; + outData["messageText"] = ssoResult["errorMessage"]; + responseBody = inja::render(htmlTemplate, outData); + res.set_content(responseBody, responseContentType); + res.status = 500; + } else { + outData["isError"] = false; + outData["messageText"] = "Authentication Successful. You may now access the network."; + responseBody = inja::render(htmlTemplate, outData); + res.set_content(responseBody, responseContentType); + } + } else { + // not an object? We got a problem + outData["isError"] = true; + outData["messageText"] = "ERROR: Unkown SSO response. Please contact your administrator."; + responseBody = inja::render(htmlTemplate, outData); + res.set_content(responseBody, responseContentType); + res.status = 500; + } - return scode; - } else { - scode = 404; - } + zeroidc::free_cstr(ret); + } + }); #endif - } else { - scode = 401; // isAuth == false && !sso + + _controlPlane.Get("/metrics", [this](const httplib::Request &req, httplib::Response &res) { + std::string statspath = _homePath + ZT_PATH_SEPARATOR + "metrics.prom"; + std::string metrics; + if (OSUtils::readFile(statspath.c_str(), metrics)) { + res.set_content(metrics, "text/plain"); + } else { + res.set_content("{}", "application/json"); + res.status = 500; + } + }); + + _controlPlane.set_exception_handler([&](const httplib::Request &req, httplib::Response &res, std::exception_ptr ep) { + char buf[1024]; + auto fmt = "{\"error\": %d, \"description\": \"%s\"}"; + try { + std::rethrow_exception(ep); + } catch (std::exception &e) { + snprintf(buf, sizeof(buf), fmt, 500, e.what()); + } catch (...) { + snprintf(buf, sizeof(buf), fmt, 500, "Unknown Exception"); } - } else if ((httpMethod == HTTP_POST)||(httpMethod == HTTP_PUT)) { - if (isAuth) { - if (ps[0] == "bond") { - if (_node->bondController()->inUse()) { - if (ps.size() == 3) { - if (ps[2].length() == 10) { - // check if hex string - const uint64_t id = Utils::hexStrToU64(ps[2].c_str()); - if (ps[1] == "rotate") { - exit(0); - SharedPtr bond = _node->bondController()->getBondByPeerId(id); - if (bond) { - scode = bond->abForciblyRotateLink() ? 200 : 400; - } else { - fprintf(stderr, "unable to find bond to peer %llx\n", (unsigned long long)id); - scode = 400; - } - } - } - } - } else { - scode = 400; /* bond controller is not enabled */ - } - } else if (ps[0] == "config") { - // Right now we only support writing the things the UI supports changing. - if (ps.size() == 2) { - if (ps[1] == "settings") { - try { - json j(OSUtils::jsonParse(body)); - if (j.is_object()) { - Mutex::Lock lcl(_localConfig_m); - json lc(_localConfig); - for(json::const_iterator s(j.begin());s!=j.end();++s) { - lc["settings"][s.key()] = s.value(); - } - std::string lcStr = OSUtils::jsonDump(lc, 4); - if (OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S "local.conf").c_str(), lcStr)) { - _localConfig = lc; - } - } else { - scode = 400; - } - } catch ( ... ) { - scode = 400; - } - } else { - scode = 404; - } - } else { - scode = 404; - } - } else if (ps[0] == "moon") { - if (ps.size() == 2) { + setContent(req, res, buf); + res.status = 500; + }); - uint64_t seed = 0; - try { - json j(OSUtils::jsonParse(body)); - if (j.is_object()) { - seed = Utils::hexStrToU64(OSUtils::jsonString(j["seed"],"0").c_str()); - } - } catch ( ... ) { - // discard invalid JSON - } + if (_controller) { + _controller->configureHTTPControlPlane(_controlPlane, setContent); + } - std::vector moons(_node->moons()); - const uint64_t id = Utils::hexStrToU64(ps[1].c_str()); - for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { - if (m->id() == id) { - _moonToJson(res,*m); - scode = 200; - break; - } - } + _controlPlane.set_pre_routing_handler(authCheck); - if ((scode != 200)&&(seed != 0)) { - char tmp[64]; - OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",id); - res["id"] = tmp; - res["roots"] = json::array(); - res["timestamp"] = 0; - res["signature"] = json(); - res["updatesMustBeSignedBy"] = json(); - res["waiting"] = true; - _node->orbit((void *)0,id,seed); - scode = 200; - } +#if ZT_DEBUG==1 + _controlPlane.set_logger([](const httplib::Request &req, const httplib::Response &res) { + fprintf(stderr, "%s", http_log(req, res).c_str()); + }); +#endif - } else scode = 404; - } else if (ps[0] == "network") { - if (ps.size() == 2) { - - uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str()); - _node->join(wantnw,(void *)0,(void *)0); // does nothing if we are a member - Mutex::Lock l(_nets_m); - if (!_nets.empty()) { - if (_nets.find(wantnw) != _nets.end()) { - NetworkState& ns = _nets[wantnw]; - try { - json j(OSUtils::jsonParse(body)); - - json &allowManaged = j["allowManaged"]; - if (allowManaged.is_boolean()) { - ns.setAllowManaged((bool)allowManaged); - } - json& allowGlobal = j["allowGlobal"]; - if (allowGlobal.is_boolean()) { - ns.setAllowGlobal((bool)allowGlobal); - } - json& allowDefault = j["allowDefault"]; - if (allowDefault.is_boolean()) { - ns.setAllowDefault((bool)allowDefault); - } - json& allowDNS = j["allowDNS"]; - if (allowDNS.is_boolean()) { - ns.setAllowDNS((bool)allowDNS); - } - } catch (...) { - // discard invalid JSON - } - setNetworkSettings(wantnw, ns.settings()); - if (ns.tap()) { - syncManagedStuff(ns,true,true,true); - } - - _networkToJson(res, ns); - - scode = 200; - } - } else scode = 500; - - } else scode = 404; - } else { - if (_controller) - scode = _controller->handleControlPlaneHttpPOST(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); - else scode = 404; - } - } else { - scode = 401; // isAuth == false + _serverThread = std::thread([&] { + if (_primaryPort==0) { + fprintf(stderr, "unable to determine local control port"); + exit(-1); } - } else if (httpMethod == HTTP_DELETE) { - if (isAuth) { + fprintf(stderr, "Starting Control Plane...\n"); + _controlPlane.listen("0.0.0.0", _primaryPort); + fprintf(stderr, "Control Plane Stopped\n"); + }); - if (ps[0] == "moon") { - if (ps.size() == 2) { - _node->deorbit((void *)0,Utils::hexStrToU64(ps[1].c_str())); - res["result"] = true; - scode = 200; - } // else 404 - } else if (ps[0] == "network") { - ZT_VirtualNetworkList *nws = _node->networks(); - if (nws) { - if (ps.size() == 2) { - uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str()); - for(unsigned long i=0;inetworkCount;++i) { - if (nws->networks[i].nwid == wantnw) { - _node->leave(wantnw,(void **)0,(void *)0); - res["result"] = true; - scode = 200; - break; - } - } - } // else 404 - _node->freeQueryResult((void *)nws); - } else scode = 500; - } else { - if (_controller) - scode = _controller->handleControlPlaneHttpDELETE(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); - else scode = 404; - } - } else scode = 401; // isAuth = false - } else { - scode = 400; - } - - if (responseBody.length() == 0) { - if ((res.is_object())||(res.is_array())) - responseBody = OSUtils::jsonDump(res); - else responseBody = "{}"; - responseContentType = "application/json"; - } - - // Wrap result in jsonp function call if the user included a jsonp= url argument. - // Also double-check isAuth since forbidding this without auth feels safer. - std::map::const_iterator jsonp(urlArgs.find("jsonp")); - if ((isAuth)&&(jsonp != urlArgs.end())&&(responseContentType == "application/json")) { - if (responseBody.length() > 0) - responseBody = jsonp->second + "(" + responseBody + ");"; - else responseBody = jsonp->second + "(null);"; - responseContentType = "application/javascript"; - } - - return scode; - } + } // Must be called after _localConfig is read or modified void applyLocalConfig() @@ -2566,44 +2617,45 @@ public: tc->lastReceive = OSUtils::now(); switch(tc->type) { - case TcpConnection::TCP_UNCATEGORIZED_INCOMING: - switch(reinterpret_cast(data)[0]) { - // HTTP: GET, PUT, POST, HEAD, DELETE - case 'G': - case 'P': - case 'D': - case 'H': { - // This is only allowed from IPs permitted to access the management - // backplane, which is just 127.0.0.1/::1 unless otherwise configured. - bool allow; - { - Mutex::Lock _l(_localConfig_m); - if (_allowManagementFrom.empty()) { - allow = (tc->remoteAddr.ipScope() == InetAddress::IP_SCOPE_LOOPBACK); - } else { - allow = false; - for(std::vector::const_iterator i(_allowManagementFrom.begin());i!=_allowManagementFrom.end();++i) { - if (i->containsAddress(tc->remoteAddr)) { - allow = true; - break; - } - } - } - } - if (allow) { - tc->type = TcpConnection::TCP_HTTP_INCOMING; - phyOnTcpData(sock,uptr,data,len); - } else { - _phy.close(sock); - } - } break; + // TODO: Remove Me + // case TcpConnection::TCP_UNCATEGORIZED_INCOMING: + // switch(reinterpret_cast(data)[0]) { + // // HTTP: GET, PUT, POST, HEAD, DELETE + // case 'G': + // case 'P': + // case 'D': + // case 'H': { + // // This is only allowed from IPs permitted to access the management + // // backplane, which is just 127.0.0.1/::1 unless otherwise configured. + // bool allow; + // { + // Mutex::Lock _l(_localConfig_m); + // if (_allowManagementFrom.empty()) { + // allow = (tc->remoteAddr.ipScope() == InetAddress::IP_SCOPE_LOOPBACK); + // } else { + // allow = false; + // for(std::vector::const_iterator i(_allowManagementFrom.begin());i!=_allowManagementFrom.end();++i) { + // if (i->containsAddress(tc->remoteAddr)) { + // allow = true; + // break; + // } + // } + // } + // } + // if (allow) { + // tc->type = TcpConnection::TCP_HTTP_INCOMING; + // phyOnTcpData(sock,uptr,data,len); + // } else { + // _phy.close(sock); + // } + // } break; - // Drop unknown protocols - default: - _phy.close(sock); - break; - } - return; + // // Drop unknown protocols + // default: + // _phy.close(sock); + // break; + // } + // return; case TcpConnection::TCP_HTTP_INCOMING: case TcpConnection::TCP_HTTP_OUTGOING: @@ -3335,56 +3387,6 @@ public: _node->processVirtualNetworkFrame((void*)0, OSUtils::now(), nwid, from.toInt(), to.toInt(), etherType, vlanId, data, len, &_nextBackgroundTaskDeadline); } - inline void onHttpRequestToServer(TcpConnection* tc) - { - char tmpn[4096]; - std::string data; - std::string contentType("text/plain"); // default if not changed in handleRequest() - unsigned int scode = 404; - - // Note that we check allowed IP ranges when HTTP connections are first detected in - // phyOnTcpData(). If we made it here the source IP is okay. - - try { - scode = handleControlPlaneHttpRequest(tc->remoteAddr, tc->parser.method, tc->url, tc->headers, tc->readq, data, contentType); - } - catch (std::exception& exc) { - fprintf(stderr, "WARNING: unexpected exception processing control HTTP request: %s" ZT_EOL_S, exc.what()); - scode = 500; - } - catch (...) { - fprintf(stderr, "WARNING: unexpected exception processing control HTTP request: unknown exception" ZT_EOL_S); - scode = 500; - } - - const char* scodestr; - switch (scode) { - case 200: scodestr = "OK"; break; - case 400: scodestr = "Bad Request"; break; - case 401: scodestr = "Unauthorized"; break; - case 403: scodestr = "Forbidden"; break; - case 404: scodestr = "Not Found"; break; - case 500: scodestr = "Internal Server Error"; break; - case 501: scodestr = "Not Implemented"; break; - case 503: scodestr = "Service Unavailable"; break; - default: scodestr = "Error"; break; - } - - OSUtils::ztsnprintf(tmpn, sizeof(tmpn), "HTTP/1.1 %.3u %s\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nContent-Type: %s\r\nContent-Length: %lu\r\nConnection: close\r\n\r\n", - scode, - scodestr, - contentType.c_str(), - (unsigned long)data.length()); - { - Mutex::Lock _l(tc->writeq_m); - tc->writeq = tmpn; - if (tc->parser.method != HTTP_HEAD) - tc->writeq.append(data); - } - - _phy.setNotifyWritable(tc->sock, true); - } - inline void onHttpResponseFromClient(TcpConnection* tc) { _phy.close(tc->sock); @@ -3599,7 +3601,6 @@ static int ShttpOnMessageComplete(http_parser *parser) { TcpConnection *tc = reinterpret_cast(parser->data); if (tc->type == TcpConnection::TCP_HTTP_INCOMING) { - tc->parent->onHttpRequestToServer(tc); } else { tc->parent->onHttpResponseFromClient(tc); } From 595e033776db24ee308b1841f4c00e49f7790a3c Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 28 Apr 2023 14:24:19 -0700 Subject: [PATCH 02/41] Outgoing Packet Metrics (#1980) add tx/rx labels to packet counters and add metrics for outgoing packets --- node/IncomingPacket.cpp | 55 ++++++------ node/Metrics.cpp | 179 ++++++++++++++++++++++++++++------------ node/Metrics.hpp | 93 +++++++++++++++------ node/Node.cpp | 5 +- node/Peer.cpp | 1 + node/Switch.cpp | 73 +++++++++++++++- node/Switch.hpp | 1 + 7 files changed, 297 insertions(+), 110 deletions(-) diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 12534117f..532a99fe0 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -86,6 +86,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr,int32_t f switch(v) { //case Packet::VERB_NOP: default: // ignore unknown verbs, but if they pass auth check they are "received" + Metrics::pkt_nop_in++; peer->received(tPtr,_path,hops(),packetId(),payloadLength(),v,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); break; case Packet::VERB_HELLO: r = _doHELLO(RR,tPtr,true); break; @@ -131,7 +132,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar const Packet::ErrorCode errorCode = (Packet::ErrorCode)(*this)[ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE]; uint64_t networkId = 0; - Metrics::pkt_error++; + Metrics::pkt_error_in++; /* Security note: we do not gate doERROR() with expectingReplyTo() to * avoid having to log every outgoing packet ID. Instead we put the @@ -148,7 +149,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar if ((network)&&(network->controller() == peer->address())) network->setNotFound(tPtr); } - Metrics::pkt_error_obj_not_found++; + Metrics::pkt_error_obj_not_found_in++; break; case Packet::ERROR_UNSUPPORTED_OPERATION: @@ -160,7 +161,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar if ((network)&&(network->controller() == peer->address())) network->setNotFound(tPtr); } - Metrics::pkt_error_unsupported_op++; + Metrics::pkt_error_unsupported_op_in++; break; case Packet::ERROR_IDENTITY_COLLISION: @@ -168,7 +169,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar if (RR->topology->isUpstream(peer->identity())) { RR->node->postEvent(tPtr,ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION); } - Metrics::pkt_error_identity_collision++; + Metrics::pkt_error_identity_collision_in++; break; case Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE: { @@ -179,7 +180,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar if ((network)&&(network->config().com)) { network->peerRequestedCredentials(tPtr,peer->address(),now); } - Metrics::pkt_error_need_membership_cert++; + Metrics::pkt_error_need_membership_cert_in++; } break; case Packet::ERROR_NETWORK_ACCESS_DENIED_: { @@ -188,7 +189,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar if ((network)&&(network->controller() == peer->address())) { network->setAccessDenied(tPtr); } - Metrics::pkt_error_network_access_denied++; + Metrics::pkt_error_network_access_denied_in++; } break; case Packet::ERROR_UNWANTED_MULTICAST: { @@ -200,7 +201,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8,6),6),at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 14)); RR->mc->remove(network->id(),mg,peer->address()); } - Metrics::pkt_error_unwanted_multicast++; + Metrics::pkt_error_unwanted_multicast_in++; } break; case Packet::ERROR_NETWORK_AUTHENTICATION_REQUIRED: { @@ -259,7 +260,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar network->setAuthenticationRequired(tPtr, ""); } } - Metrics::pkt_error_authentication_required++; + Metrics::pkt_error_authentication_required_in++; } break; default: break; @@ -286,13 +287,13 @@ bool IncomingPacket::_doACK(const RuntimeEnvironment* RR, void* tPtr, const Shar bond->receivedAck(_path, RR->node->now(), Utils::ntoh(ackedBytes)); } */ - Metrics::pkt_ack++; + Metrics::pkt_ack_in++; return true; } bool IncomingPacket::_doQOS_MEASUREMENT(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr& peer) { - Metrics::pkt_qos++; + Metrics::pkt_qos_in++; SharedPtr bond = peer->bond(); if (! bond || ! bond->rateGateQoS(RR->node->now(), _path)) { return true; @@ -323,7 +324,7 @@ bool IncomingPacket::_doQOS_MEASUREMENT(const RuntimeEnvironment* RR, void* tPtr bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool alreadyAuthenticated) { - Metrics::pkt_hello++; + Metrics::pkt_hello_in++; const int64_t now = RR->node->now(); const uint64_t pid = packetId(); @@ -526,7 +527,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_ok++; + Metrics::pkt_ok_in++; const Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB]; const uint64_t inRePacketId = at(ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID); uint64_t networkId = 0; @@ -644,7 +645,7 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar return true; } - Metrics::pkt_whois++; + Metrics::pkt_whois_in++; Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); outp.append((unsigned char)Packet::VERB_WHOIS); @@ -678,7 +679,7 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_rendezvous++; + Metrics::pkt_rendezvous_in++; if (RR->topology->isUpstream(peer->identity())) { const Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); const SharedPtr rendezvousWith(RR->topology->getPeer(tPtr,with)); @@ -732,7 +733,7 @@ static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsig bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,int32_t flowId) { - Metrics::pkt_frame++; + Metrics::pkt_frame_in++; int32_t _flowId = ZT_QOS_NO_FLOW; SharedPtr bond = peer->bond(); if (bond && bond->flowHashingSupported()) { @@ -825,7 +826,7 @@ bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,void *tPtr,const Shar bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,int32_t flowId) { - Metrics::pkt_ext_frame++; + Metrics::pkt_ext_frame_in++; const uint64_t nwid = at(ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID); const SharedPtr network(RR->node->network(nwid)); if (network) { @@ -908,7 +909,7 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_echo++; + Metrics::pkt_echo_in++; uint64_t now = RR->node->now(); if (!_path->rateGateEchoRequest(now)) { return true; @@ -931,7 +932,7 @@ bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const Share bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_multicast_like++; + Metrics::pkt_multicast_like_in++; const int64_t now = RR->node->now(); bool authorized = false; uint64_t lastNwid = 0; @@ -957,7 +958,7 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,void *tPtr,c bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_network_credentials++; + Metrics::pkt_network_credentials_in++; if (!peer->rateGateCredentialsReceived(RR->node->now())) { return true; } @@ -1082,7 +1083,7 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *t bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_network_config_request++; + Metrics::pkt_network_config_request_in++; const uint64_t nwid = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID); const unsigned int hopCount = hops(); const uint64_t requestPacketId = packetId(); @@ -1109,7 +1110,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_network_config++; + Metrics::pkt_network_config_in++; const SharedPtr network(RR->node->network(at(ZT_PACKET_IDX_PAYLOAD))); if (network) { const uint64_t configUpdateId = network->handleConfigChunk(tPtr,packetId(),source(),*this,ZT_PACKET_IDX_PAYLOAD); @@ -1133,7 +1134,7 @@ bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,c bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_multicast_gather++; + Metrics::pkt_multicast_gather_in++; const uint64_t nwid = at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID); const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS]; const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC,6),6),at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI)); @@ -1174,7 +1175,7 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_multicast_frame++; + Metrics::pkt_multicast_frame_in++; const uint64_t nwid = at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID); const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS]; @@ -1275,7 +1276,7 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_push_direct_paths++; + Metrics::pkt_push_direct_paths_in++; const int64_t now = RR->node->now(); if (!peer->rateGatePushDirectPaths(now)) { @@ -1338,7 +1339,7 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPt bool IncomingPacket::_doUSER_MESSAGE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_user_message++; + Metrics::pkt_user_message_in++; if (likely(size() >= (ZT_PACKET_IDX_PAYLOAD + 8))) { ZT_UserMessage um; um.origin = peer->address().toInt(); @@ -1355,7 +1356,7 @@ bool IncomingPacket::_doUSER_MESSAGE(const RuntimeEnvironment *RR,void *tPtr,con bool IncomingPacket::_doREMOTE_TRACE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_remote_trace++; + Metrics::pkt_remote_trace_in++; ZT_RemoteTrace rt; const char *ptr = reinterpret_cast(data()) + ZT_PACKET_IDX_PAYLOAD; const char *const eof = reinterpret_cast(data()) + size(); @@ -1380,7 +1381,7 @@ bool IncomingPacket::_doREMOTE_TRACE(const RuntimeEnvironment *RR,void *tPtr,con bool IncomingPacket::_doPATH_NEGOTIATION_REQUEST(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_path_negotiation_request++; + Metrics::pkt_path_negotiation_request_in++; uint64_t now = RR->node->now(); SharedPtr bond = peer->bond(); if (!bond || !bond->rateGatePathNegotiation(now, _path)) { diff --git a/node/Metrics.cpp b/node/Metrics.cpp index 8ccef869f..d57bb2d00 100644 --- a/node/Metrics.cpp +++ b/node/Metrics.cpp @@ -25,64 +25,135 @@ namespace ZeroTier { // Packet Type Counts prometheus::simpleapi::counter_family_t packets { "zt_packet_incoming", "incoming packet type counts"}; - prometheus::simpleapi::counter_metric_t pkt_error - { packets.Add({{"packet_type", "error"}}) }; - prometheus::simpleapi::counter_metric_t pkt_ack - { packets.Add({{"packet_type", "ack"}}) }; - prometheus::simpleapi::counter_metric_t pkt_qos - { packets.Add({{"packet_type", "qos"}}) }; - prometheus::simpleapi::counter_metric_t pkt_hello - { packets.Add({{"packet_type", "hello"}}) }; - prometheus::simpleapi::counter_metric_t pkt_ok - { packets.Add({{"packet_type", "ok"}}) }; - prometheus::simpleapi::counter_metric_t pkt_whois - { packets.Add({{"packet_type", "whois"}}) }; - prometheus::simpleapi::counter_metric_t pkt_rendezvous - { packets.Add({{"packet_type", "rendezvous"}}) }; - prometheus::simpleapi::counter_metric_t pkt_frame - { packets.Add({{"packet_type", "frame"}}) }; - prometheus::simpleapi::counter_metric_t pkt_ext_frame - { packets.Add({{"packet_type", "ext_frame"}}) }; - prometheus::simpleapi::counter_metric_t pkt_echo - { packets.Add({{"packet_type", "echo"}}) }; - prometheus::simpleapi::counter_metric_t pkt_multicast_like - { packets.Add({{"packet_type", "multicast_like"}}) }; - prometheus::simpleapi::counter_metric_t pkt_network_credentials - { packets.Add({{"packet_type", "network_credentials"}}) }; - prometheus::simpleapi::counter_metric_t pkt_network_config_request - { packets.Add({{"packet_type", "network_config_request"}}) }; - prometheus::simpleapi::counter_metric_t pkt_network_config - { packets.Add({{"packet_type", "network_config"}}) }; - prometheus::simpleapi::counter_metric_t pkt_multicast_gather - { packets.Add({{"packet_type", "multicast_gather"}}) }; - prometheus::simpleapi::counter_metric_t pkt_multicast_frame - { packets.Add({{"packet_type", "multicast_frame"}}) }; - prometheus::simpleapi::counter_metric_t pkt_push_direct_paths - { packets.Add({{"packet_type", "push_direct_paths"}}) }; - prometheus::simpleapi::counter_metric_t pkt_user_message - { packets.Add({{"packet_type", "user_message"}}) }; - prometheus::simpleapi::counter_metric_t pkt_remote_trace - { packets.Add({{"packet_type", "remote_trace"}}) }; - prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request - { packets.Add({{"packet_type", "path_negotiation_request"}}) }; + + // Incoming packets + prometheus::simpleapi::counter_metric_t pkt_nop_in + { packets.Add({{"packet_type", "nop"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_in + { packets.Add({{"packet_type", "error"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ack_in + { packets.Add({{"packet_type", "ack"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_qos_in + { packets.Add({{"packet_type", "qos"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_hello_in + { packets.Add({{"packet_type", "hello"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ok_in + { packets.Add({{"packet_type", "ok"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_whois_in + { packets.Add({{"packet_type", "whois"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_rendezvous_in + { packets.Add({{"packet_type", "rendezvous"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_frame_in + { packets.Add({{"packet_type", "frame"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ext_frame_in + { packets.Add({{"packet_type", "ext_frame"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_echo_in + { packets.Add({{"packet_type", "echo"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_like_in + { packets.Add({{"packet_type", "multicast_like"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_credentials_in + { packets.Add({{"packet_type", "network_credentials"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_config_request_in + { packets.Add({{"packet_type", "network_config_request"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_config_in + { packets.Add({{"packet_type", "network_config"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_gather_in + { packets.Add({{"packet_type", "multicast_gather"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_frame_in + { packets.Add({{"packet_type", "multicast_frame"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_push_direct_paths_in + { packets.Add({{"packet_type", "push_direct_paths"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_user_message_in + { packets.Add({{"packet_type", "user_message"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_remote_trace_in + { packets.Add({{"packet_type", "remote_trace"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request_in + { packets.Add({{"packet_type", "path_negotiation_request"}, {"direction", "rx"}}) }; + + // Outgoing packets + prometheus::simpleapi::counter_metric_t pkt_nop_out + { packets.Add({{"packet_type", "nop"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_out + { packets.Add({{"packet_type", "error"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ack_out + { packets.Add({{"packet_type", "ack"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_qos_out + { packets.Add({{"packet_type", "qos"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_hello_out + { packets.Add({{"packet_type", "hello"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ok_out + { packets.Add({{"packet_type", "ok"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_whois_out + { packets.Add({{"packet_type", "whois"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_rendezvous_out + { packets.Add({{"packet_type", "rendezvous"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_frame_out + { packets.Add({{"packet_type", "frame"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ext_frame_out + { packets.Add({{"packet_type", "ext_frame"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_echo_out + { packets.Add({{"packet_type", "echo"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_like_out + { packets.Add({{"packet_type", "multicast_like"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_credentials_out + { packets.Add({{"packet_type", "network_credentials"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_config_request_out + { packets.Add({{"packet_type", "network_config_request"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_config_out + { packets.Add({{"packet_type", "network_config"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_gather_out + { packets.Add({{"packet_type", "multicast_gather"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_frame_out + { packets.Add({{"packet_type", "multicast_frame"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_push_direct_paths_out + { packets.Add({{"packet_type", "push_direct_paths"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_user_message_out + { packets.Add({{"packet_type", "user_message"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_remote_trace_out + { packets.Add({{"packet_type", "remote_trace"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request_out + { packets.Add({{"packet_type", "path_negotiation_request"}, {"direction", "tx"}}) }; + // Packet Error Counts prometheus::simpleapi::counter_family_t packet_errors { "zt_packet_incoming_error", "incoming packet errors"}; - prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found - { packet_errors.Add({{"error_type", "obj_not_found"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op - { packet_errors.Add({{"error_type", "unsupported_operation"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_identity_collision - { packet_errors.Add({{"error_type", "identity_collision"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert - { packet_errors.Add({{"error_type", "need_membership_certificate"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied - { packet_errors.Add({{"error_type", "network_access_denied"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast - { packet_errors.Add({{"error_type", "unwanted_multicast"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_authentication_required - { packet_errors.Add({{"error_type", "authentication_required"}}) }; + + // Incoming Error Counts + prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found_in + { packet_errors.Add({{"error_type", "obj_not_found"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op_in + { packet_errors.Add({{"error_type", "unsupported_operation"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_identity_collision_in + { packet_errors.Add({{"error_type", "identity_collision"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert_in + { packet_errors.Add({{"error_type", "need_membership_certificate"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied_in + { packet_errors.Add({{"error_type", "network_access_denied"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast_in + { packet_errors.Add({{"error_type", "unwanted_multicast"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_authentication_required_in + { packet_errors.Add({{"error_type", "authentication_required"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_internal_server_error_in + { packet_errors.Add({{"error_type", "internal_server_error"}, {"direction", "rx"}}) }; + + // Outgoing Error Counts + prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found_out + { packet_errors.Add({{"error_type", "obj_not_found"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op_out + { packet_errors.Add({{"error_type", "unsupported_operation"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_identity_collision_out + { packet_errors.Add({{"error_type", "identity_collision"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert_out + { packet_errors.Add({{"error_type", "need_membership_certificate"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied_out + { packet_errors.Add({{"error_type", "network_access_denied"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast_out + { packet_errors.Add({{"error_type", "unwanted_multicast"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_authentication_required_out + { packet_errors.Add({{"error_type", "authentication_required"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_internal_server_error_out + { packet_errors.Add({{"error_type", "internal_server_error"}, {"direction", "tx"}}) }; // Data Sent/Received Metrics prometheus::simpleapi::counter_metric_t udp_send diff --git a/node/Metrics.hpp b/node/Metrics.hpp index 3b65ea4b9..f38bab6b9 100644 --- a/node/Metrics.hpp +++ b/node/Metrics.hpp @@ -24,36 +24,75 @@ namespace ZeroTier { namespace Metrics { // Packet Type Counts extern prometheus::simpleapi::counter_family_t packets; - extern prometheus::simpleapi::counter_metric_t pkt_error; - extern prometheus::simpleapi::counter_metric_t pkt_ack; - extern prometheus::simpleapi::counter_metric_t pkt_qos; - extern prometheus::simpleapi::counter_metric_t pkt_hello; - extern prometheus::simpleapi::counter_metric_t pkt_ok; - extern prometheus::simpleapi::counter_metric_t pkt_whois; - extern prometheus::simpleapi::counter_metric_t pkt_rendezvous; - extern prometheus::simpleapi::counter_metric_t pkt_frame; - extern prometheus::simpleapi::counter_metric_t pkt_ext_frame; - extern prometheus::simpleapi::counter_metric_t pkt_echo; - extern prometheus::simpleapi::counter_metric_t pkt_multicast_like; - extern prometheus::simpleapi::counter_metric_t pkt_network_credentials; - extern prometheus::simpleapi::counter_metric_t pkt_network_config_request; - extern prometheus::simpleapi::counter_metric_t pkt_network_config; - extern prometheus::simpleapi::counter_metric_t pkt_multicast_gather; - extern prometheus::simpleapi::counter_metric_t pkt_multicast_frame; - extern prometheus::simpleapi::counter_metric_t pkt_push_direct_paths; - extern prometheus::simpleapi::counter_metric_t pkt_user_message; - extern prometheus::simpleapi::counter_metric_t pkt_remote_trace; - extern prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request; + + // incoming packets + extern prometheus::simpleapi::counter_metric_t pkt_nop_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_in; + extern prometheus::simpleapi::counter_metric_t pkt_ack_in; + extern prometheus::simpleapi::counter_metric_t pkt_qos_in; + extern prometheus::simpleapi::counter_metric_t pkt_hello_in; + extern prometheus::simpleapi::counter_metric_t pkt_ok_in; + extern prometheus::simpleapi::counter_metric_t pkt_whois_in; + extern prometheus::simpleapi::counter_metric_t pkt_rendezvous_in; + extern prometheus::simpleapi::counter_metric_t pkt_frame_in; + extern prometheus::simpleapi::counter_metric_t pkt_ext_frame_in; + extern prometheus::simpleapi::counter_metric_t pkt_echo_in; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_like_in; + extern prometheus::simpleapi::counter_metric_t pkt_network_credentials_in; + extern prometheus::simpleapi::counter_metric_t pkt_network_config_request_in; + extern prometheus::simpleapi::counter_metric_t pkt_network_config_in; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_gather_in; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_frame_in; + extern prometheus::simpleapi::counter_metric_t pkt_push_direct_paths_in; + extern prometheus::simpleapi::counter_metric_t pkt_user_message_in; + extern prometheus::simpleapi::counter_metric_t pkt_remote_trace_in; + extern prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request_in; + + // outgoing packets + extern prometheus::simpleapi::counter_metric_t pkt_nop_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_out; + extern prometheus::simpleapi::counter_metric_t pkt_ack_out; + extern prometheus::simpleapi::counter_metric_t pkt_qos_out; + extern prometheus::simpleapi::counter_metric_t pkt_hello_out; + extern prometheus::simpleapi::counter_metric_t pkt_ok_out; + extern prometheus::simpleapi::counter_metric_t pkt_whois_out; + extern prometheus::simpleapi::counter_metric_t pkt_rendezvous_out; + extern prometheus::simpleapi::counter_metric_t pkt_frame_out; + extern prometheus::simpleapi::counter_metric_t pkt_ext_frame_out; + extern prometheus::simpleapi::counter_metric_t pkt_echo_out; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_like_out; + extern prometheus::simpleapi::counter_metric_t pkt_network_credentials_out; + extern prometheus::simpleapi::counter_metric_t pkt_network_config_request_out; + extern prometheus::simpleapi::counter_metric_t pkt_network_config_out; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_gather_out; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_frame_out; + extern prometheus::simpleapi::counter_metric_t pkt_push_direct_paths_out; + extern prometheus::simpleapi::counter_metric_t pkt_user_message_out; + extern prometheus::simpleapi::counter_metric_t pkt_remote_trace_out; + extern prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request_out; // Packet Error Counts extern prometheus::simpleapi::counter_family_t packet_errors; - extern prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found; - extern prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op; - extern prometheus::simpleapi::counter_metric_t pkt_error_identity_collision; - extern prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert; - extern prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied; - extern prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast; - extern prometheus::simpleapi::counter_metric_t pkt_error_authentication_required; + + // incoming errors + extern prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_identity_collision_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_authentication_required_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_internal_server_error_in; + + // outgoing errors + extern prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_identity_collision_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_authentication_required_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_internal_server_error_out; // Data Sent/Received Metrics diff --git a/node/Node.cpp b/node/Node.cpp index 46f3a4add..a32782edb 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -34,6 +34,7 @@ #include "SelfAwareness.hpp" #include "Network.hpp" #include "Trace.hpp" +#include "Metrics.hpp" namespace ZeroTier { @@ -769,7 +770,6 @@ void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &des break; case NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED: { //fprintf(stderr, "\n\nGot auth required\n\n"); - break; } @@ -784,12 +784,15 @@ void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &des //case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR: default: outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND); + Metrics::pkt_error_obj_not_found_out++; break; case NetworkController::NC_ERROR_ACCESS_DENIED: outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_); + Metrics::pkt_error_network_access_denied_out++; break; case NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED: outp.append((unsigned char)Packet::ERROR_NETWORK_AUTHENTICATION_REQUIRED); + Metrics::pkt_error_authentication_required_out++; break; } diff --git a/node/Peer.cpp b/node/Peer.cpp index 5cb55fa4b..34e3b9c4f 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -22,6 +22,7 @@ #include "InetAddress.hpp" #include "RingBuffer.hpp" #include "Utils.hpp" +#include "Metrics.hpp" namespace ZeroTier { diff --git a/node/Switch.cpp b/node/Switch.cpp index 59ed34a04..7b1d47ac9 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -31,6 +31,7 @@ #include "SelfAwareness.hpp" #include "Packet.hpp" #include "Trace.hpp" +#include "Metrics.hpp" namespace ZeroTier { @@ -850,8 +851,10 @@ void Switch::removeNetworkQoSControlBlock(uint64_t nwid) void Switch::send(void *tPtr,Packet &packet,bool encrypt,int32_t flowId) { const Address dest(packet.destination()); - if (dest == RR->identity.address()) + if (dest == RR->identity.address()) { return; + } + _recordOutgoingPacketMetrics(packet); if (!_trySend(tPtr,packet,encrypt,flowId)) { { Mutex::Lock _l(_txQueue_m); @@ -1080,4 +1083,72 @@ void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtr peer,SharedPtr viaPath,uint16_t userSpecifiedMtu, int64_t now,Packet &packet,bool encrypt,int32_t flowId); + void _recordOutgoingPacketMetrics(const Packet &p); const RuntimeEnvironment *const RR; int64_t _lastBeaconResponse; From e6802690b854a5ddf8a042f5b3f29ceb6358d447 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 1 May 2023 09:07:03 -0700 Subject: [PATCH 03/41] Add short-term validation test workflow (#1974) Add short-term validation test workflow --- .github/workflows/report.sh | 15 + .github/workflows/validate-1m-linux.sh | 401 +++++++++++++++++++++++++ .github/workflows/validate.yml | 57 ++++ 3 files changed, 473 insertions(+) create mode 100755 .github/workflows/report.sh create mode 100755 .github/workflows/validate-1m-linux.sh create mode 100644 .github/workflows/validate.yml diff --git a/.github/workflows/report.sh b/.github/workflows/report.sh new file mode 100755 index 000000000..cc6716213 --- /dev/null +++ b/.github/workflows/report.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +################################################################################ +# Set exit code depending on tool reports # +################################################################################ + +DEFINITELY_LOST=$(cat *test-results/*summary.json | jq .num_definite_bytes_lost) + +cat *test-results/*summary.json + +echo -e "\nBytes of memory definitely lost: $DEFINITELY_LOST" + +if [[ "$DEFINITELY_LOST" -gt 0 ]]; then + exit 1 +fi diff --git a/.github/workflows/validate-1m-linux.sh b/.github/workflows/validate-1m-linux.sh new file mode 100755 index 000000000..0a8dbf4d6 --- /dev/null +++ b/.github/workflows/validate-1m-linux.sh @@ -0,0 +1,401 @@ +#!/bin/bash + +# This test script joins Earth and pokes some stuff + +TEST_NETWORK=8056c2e21c000001 +RUN_LENGTH=10 +TEST_FINISHED=false +ZTO_VER=$(git describe --tags $(git rev-list --tags --max-count=1)) +ZTO_COMMIT=$(git rev-parse HEAD) +ZTO_COMMIT_SHORT=$(git rev-parse --short HEAD) +TEST_DIR_PREFIX="$ZTO_VER-$ZTO_COMMIT_SHORT-test-results" +echo "Performing test on: $ZTO_VER-$ZTO_COMMIT_SHORT" +TEST_FILEPATH_PREFIX="$TEST_DIR_PREFIX/$ZTO_COMMIT_SHORT" +mkdir $TEST_DIR_PREFIX + +################################################################################ +# Multi-node connectivity and performance test # +################################################################################ + +NS1="ip netns exec ns1" +NS2="ip netns exec ns2" + +ZT1="$NS1 ./zerotier-cli -D$(pwd)/node1" +ZT2="$NS2 ./zerotier-cli -D$(pwd)/node2" + +echo -e "Setting up network namespaces..." +echo "Setting up ns1" + +ip netns add ns1 +$NS1 ip link set dev lo up +ip link add veth0 type veth peer name veth1 +ip link set veth1 netns ns1 +ip addr add 192.168.0.1/24 dev veth0 +ip link set dev veth0 up + +$NS1 ip addr add 192.168.0.2/24 dev veth1 +$NS1 ip link set dev veth1 up + +# Add default route +$NS1 ip route add default via 192.168.0.1 + +iptables -t nat -A POSTROUTING -s 192.168.0.0/255.255.255.0 \ + -o eth0 -j MASQUERADE +iptables -A FORWARD -i eth0 -o veth0 -j ACCEPT +iptables -A FORWARD -o eth0 -i veth0 -j ACCEPT + +echo "Setting up ns2" +ip netns add ns2 +$NS2 ip link set dev lo up +ip link add veth2 type veth peer name veth3 +ip link set veth3 netns ns2 +ip addr add 192.168.1.1/24 dev veth2 +ip link set dev veth2 up + +$NS2 ip addr add 192.168.1.2/24 dev veth3 +$NS2 ip link set dev veth3 up +$NS2 ip route add default via 192.168.1.1 + +iptables -t nat -A POSTROUTING -s 192.168.1.0/255.255.255.0 \ + -o eth0 -j MASQUERADE +iptables -A FORWARD -i eth0 -o veth2 -j ACCEPT +iptables -A FORWARD -o eth0 -i veth2 -j ACCEPT + +# Allow forwarding +sysctl -w net.ipv4.ip_forward=1 + +echo -e "\nPing from host to namespaces" + +#ping -c 4 192.168.0.1 +#ping -c 4 192.168.1.1 + +echo -e "\nPing from namespace to host" + +#$NS1 ping -c 4 192.168.0.1 +#$NS1 ping -c 4 192.168.0.1 +#$NS2 ping -c 4 192.168.0.2 +#$NS2 ping -c 4 192.168.0.2 + +echo -e "\nPing from ns1 to ns2" + +#$NS1 ping -c 4 192.168.0.1 + +echo -e "\nPing from ns2 to ns1" + +#$NS2 ping -c 4 192.168.0.1 + +################################################################################ +# Memory Leak Check # +################################################################################ + +FILENAME_MEMORY_LOG="$TEST_FILEPATH_PREFIX-memory.log" + +echo -e "\nStarting a ZeroTier instance in each namespace..." + +time_test_start=`date +%s` + +echo "Starting memory leak check" +$NS1 sudo valgrind --demangle=yes --exit-on-first-error=yes \ + --error-exitcode=1 \ + --xml=yes \ + --xml-file=$FILENAME_MEMORY_LOG \ + --leak-check=full \ + ./zerotier-one node1 >>node_1.log 2>&1 & + +# Second instance, not run in memory profiler +$NS2 ./zerotier-one node2 >>node_2.log 2>&1 & + +################################################################################ +# Online Check # +################################################################################ + +echo "Waiting for ZeroTier to come online before attempting test..." +MAX_WAIT_SECS="${MAX_WAIT_SECS:-120}" +node1_online=false +node2_online=false +both_instances_online=false +time_zt_node1_start=`date +%s` +time_zt_node2_start=`date +%s` + +for ((s=0; s<=MAX_WAIT_SECS; s++)) +do + node1_online="$($ZT1 -j info | jq '.online' 2>/dev/null)" + node2_online="$($ZT2 -j info | jq '.online' 2>/dev/null)" + if [[ "$node1_online" == "true" ]] + then + time_zt_node1_online=`date +%s` + fi + if [[ "$node2_online" == "true" ]] + then + time_zt_node2_online=`date +%s` + fi + if [[ "$node2_online" == "true" && "$node1_online" == "true" ]] + then + both_instances_online=true + break + fi + sleep 1 +done + +if [[ "$both_instances_online" != "true" ]] +then + echo "One or more instances of ZeroTier failed to come online. Aborting test." >&2 + exit 1 +fi + +echo -e "\nChecking status of each instance:" + +$ZT1 status +$ZT2 status + +echo -e "\nJoining networks" + +$ZT1 join $TEST_NETWORK +$ZT2 join $TEST_NETWORK + +sleep 10 + +node1_ip4=$($ZT1 get $TEST_NETWORK ip4) +node2_ip4=$($ZT2 get $TEST_NETWORK ip4) + +echo "node1_ip4=$node1_ip4" +echo "node2_ip4=$node2_ip4" + +echo -e "\nPinging each node" + +PING12_FILENAME="$TEST_FILEPATH_PREFIX-ping-1-to-2.txt" +PING21_FILENAME="$TEST_FILEPATH_PREFIX-ping-2-to-1.txt" + +$NS1 ping -c 16 $node2_ip4 > $PING12_FILENAME +$NS2 ping -c 16 $node1_ip4 > $PING21_FILENAME + +# Parse ping statistics +ping_loss_percent_1_to_2="${ping_loss_percent_1_to_2:-100.0}" +ping_loss_percent_2_to_1="${ping_loss_percent_2_to_1:-100.0}" + +ping_loss_percent_1_to_2=$(cat $PING12_FILENAME | \ + grep "packet loss" | awk '{print $6}' | sed 's/%//') +ping_loss_percent_2_to_1=$(cat $PING21_FILENAME | \ + grep "packet loss" | awk '{print $6}' | sed 's/%//') + +# Normalize loss value +ping_loss_percent_1_to_2=$(echo "scale=2; $ping_loss_percent_1_to_2/100.0" | bc) +ping_loss_percent_2_to_1=$(echo "scale=2; $ping_loss_percent_2_to_1/100.0" | bc) + +################################################################################ +# CLI Check # +################################################################################ + +echo "Testing basic CLI functionality..." + +# Rapidly spam the CLI with joins/leaves + +SPAM_TRIES=128 + +for ((s=0; s<=SPAM_TRIES; s++)) +do + $ZT1 join $TEST_NETWORK +done + +for ((s=0; s<=SPAM_TRIES; s++)) +do + $ZT1 leave $TEST_NETWORK +done + +for ((s=0; s<=SPAM_TRIES; s++)) +do + $ZT1 leave $TEST_NETWORK + $ZT1 join $TEST_NETWORK +done + +$ZT1 join $TEST_NETWORK + +$ZT1 -h +$ZT1 -v +$ZT1 status +$ZT1 info +$ZT1 listnetworks +$ZT1 peers +$ZT1 listpeers + +$ZT1 -j status +$ZT1 -j info +$ZT1 -j listnetworks +$ZT1 -j peers +$ZT1 -j listpeers + +$ZT1 dump + +$ZT1 get $TEST_NETWORK allowDNS +$ZT1 get $TEST_NETWORK allowDefault +$ZT1 get $TEST_NETWORK allowGlobal +$ZT1 get $TEST_NETWORK allowManaged +$ZT1 get $TEST_NETWORK bridge +$ZT1 get $TEST_NETWORK broadcastEnabled +$ZT1 get $TEST_NETWORK dhcp +$ZT1 get $TEST_NETWORK id +$ZT1 get $TEST_NETWORK mac +$ZT1 get $TEST_NETWORK mtu +$ZT1 get $TEST_NETWORK name +$ZT1 get $TEST_NETWORK netconfRevision +$ZT1 get $TEST_NETWORK nwid +$ZT1 get $TEST_NETWORK portDeviceName +$ZT1 get $TEST_NETWORK portError +$ZT1 get $TEST_NETWORK status +$ZT1 get $TEST_NETWORK type + +# Test an invalid command +$ZT1 get $TEST_NETWORK derpderp + +# TODO: Validate JSON + +################################################################################ +# Performance Test # +################################################################################ + +FILENAME_PERF_JSON="$TEST_FILEPATH_PREFIX-iperf.json" + +echo -e "\nBeginning performance test:" + +echo -e "\nStarting server:" + +echo "$NS1 iperf3 -s &" +sleep 1 + +echo -e "\nStarting client:" +sleep 1 + +echo "$NS2 iperf3 --json -c $node1_ip4 > $FILENAME_PERF_JSON" + +cat $FILENAME_PERF_JSON + +################################################################################ +# Collect ZeroTier dump files # +################################################################################ + +echo -e "\nCollecting ZeroTier dump files" + +node1_id=$($ZT1 -j status | jq -r .address) +node2_id=$($ZT2 -j status | jq -r .address) + +$ZT1 dump +mv zerotier_dump.txt "$TEST_FILEPATH_PREFIX-node-dump-$node1_id.txt" + +$ZT2 dump +mv zerotier_dump.txt "$TEST_FILEPATH_PREFIX-node-dump-$node2_id.txt" + +################################################################################ +# Let ZeroTier idle long enough for various timers # +################################################################################ + +echo -e "\nIdling ZeroTier for $RUN_LENGTH seconds..." +sleep $RUN_LENGTH + +echo -e "\nLeaving networks" + +$ZT1 leave $TEST_NETWORK +$ZT2 leave $TEST_NETWORK + +sleep 5 + +################################################################################ +# Stop test # +################################################################################ + +echo -e "\nStopping memory check..." +sudo pkill -15 -f valgrind +sleep 10 + +time_test_end=`date +%s` + +################################################################################ +# Rename ZeroTier stdout/stderr logs # +################################################################################ + +mv node_1.log "$TEST_FILEPATH_PREFIX-node-log-$node1_id.txt" +mv node_2.log "$TEST_FILEPATH_PREFIX-node-log-$node2_id.txt" + +################################################################################ +# Generate report # +################################################################################ + +cat $FILENAME_MEMORY_LOG + +DEFINITELY_LOST=$(xmlstarlet sel -t -v '/valgrindoutput/error/xwhat' \ + $FILENAME_MEMORY_LOG | grep "definitely" | awk '{print $1;}') +POSSIBLY_LOST=$(xmlstarlet sel -t -v '/valgrindoutput/error/xwhat' \ + $FILENAME_MEMORY_LOG | grep "possibly" | awk '{print $1;}') + +################################################################################ +# Generate coverage report artifact and summary # +################################################################################ + +FILENAME_COVERAGE_JSON="$TEST_FILEPATH_PREFIX-coverage.json" +FILENAME_COVERAGE_HTML="$TEST_FILEPATH_PREFIX-coverage.html" + +echo -e "\nGenerating coverage test report..." + +gcovr -r . --exclude ext --json-summary $FILENAME_COVERAGE_JSON \ + --html > $FILENAME_COVERAGE_HTML + +cat $FILENAME_COVERAGE_JSON + +COVERAGE_LINE_COVERED=$(cat $FILENAME_COVERAGE_JSON | jq .line_covered) +COVERAGE_LINE_TOTAL=$(cat $FILENAME_COVERAGE_JSON | jq .line_total) +COVERAGE_LINE_PERCENT=$(cat $FILENAME_COVERAGE_JSON | jq .line_percent) + +COVERAGE_LINE_COVERED="${COVERAGE_LINE_COVERED:-0}" +COVERAGE_LINE_TOTAL="${COVERAGE_LINE_TOTAL:-0}" +COVERAGE_LINE_PERCENT="${COVERAGE_LINE_PERCENT:-0}" + +################################################################################ +# Default values # +################################################################################ + +DEFINITELY_LOST="${DEFINITELY_LOST:-0}" +POSSIBLY_LOST="${POSSIBLY_LOST:-0}" + +################################################################################ +# Summarize and emit json for trend reporting # +################################################################################ + +FILENAME_SUMMARY="$TEST_FILEPATH_PREFIX-summary.json" + +time_length_test=$((time_test_end-time_test_start)) +time_length_zt_node1_online=$((time_zt_node1_online-time_zt_start)) +time_length_zt_node2_online=$((time_zt_node2_online-time_zt_start)) +#time_length_zt_join=$((time_zt_join_end-time_zt_join_start)) +#time_length_zt_leave=$((time_zt_leave_end-time_zt_leave_start)) +#time_length_zt_can_still_ping=$((time_zt_can_still_ping-time_zt_leave_start)) + +summary=$(cat < $FILENAME_SUMMARY +cat $FILENAME_SUMMARY + diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 000000000..665c34945 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,57 @@ +on: [ push ] + +jobs: + build_ubuntu: + runs-on: ubuntu-latest + steps: + - name: gitconfig + run: | + git config --global core.autocrlf input + + - name: checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: x86_64-unknown-linux-gnu + override: true + components: rustfmt, clippy + + - name: Set up cargo cache + uses: actions/cache@v3 + continue-on-error: false + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + **/target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + + - name: validate-1m-linux + env: + CC: 'gcc' + CXX: 'g++' + BRANCH: ${{ github.ref_name }} + run: | + sudo apt install -y valgrind xmlstarlet gcovr iperf3 + make one ZT_COVERAGE=1 ZT_TRACE=1 + sudo chmod +x ./.github/workflows/validate-1m-linux.sh + sudo ./.github/workflows/validate-1m-linux.sh + + - name: Archive test results + uses: actions/upload-artifact@v3 + with: + name: ${{github.sha}}-test-results + path: "*test-results*" + + - name: final-report + run: | + sudo chmod +x ./.github/workflows/report.sh + sudo ./.github/workflows/report.sh From f73e51e94ccfcd1ed33f592dac3ad162c876a174 Mon Sep 17 00:00:00 2001 From: Brenton Bostick Date: Mon, 1 May 2023 14:48:16 -0400 Subject: [PATCH 04/41] Brenton/curly braces (#1971) * fix formatting * properly adjust various lines breakup multiple statements onto multiple lines * insert {} around if, for, etc. --- node/AES.cpp | 21 ++- node/AES.hpp | 9 +- node/AES_aesni.cpp | 18 +- node/AES_armcrypto.cpp | 21 ++- node/Address.hpp | 3 +- node/Bond.cpp | 66 +++---- node/Bond.hpp | 18 +- node/Buffer.hpp | 72 +++++--- node/C25519.cpp | 221 ++++++++++++++-------- node/Capability.cpp | 14 +- node/Capability.hpp | 56 ++++-- node/CertificateOfMembership.cpp | 24 ++- node/CertificateOfMembership.hpp | 33 ++-- node/CertificateOfOwnership.cpp | 9 +- node/CertificateOfOwnership.hpp | 52 ++++-- node/Dictionary.hpp | 81 +++++--- node/Hashtable.hpp | 33 ++-- node/Identity.cpp | 6 +- node/Identity.hpp | 28 ++- node/IncomingPacket.cpp | 215 +++++++++++++++------- node/InetAddress.cpp | 184 +++++++++++++------ node/InetAddress.hpp | 92 ++++++---- node/MAC.hpp | 18 +- node/Membership.cpp | 18 +- node/Membership.hpp | 15 +- node/MulticastGroup.hpp | 5 +- node/Multicaster.cpp | 67 ++++--- node/Network.cpp | 204 ++++++++++++++------- node/Network.hpp | 15 +- node/NetworkConfig.cpp | 233 ++++++++++++++++------- node/NetworkConfig.hpp | 36 ++-- node/Node.cpp | 172 ++++++++++++----- node/Node.hpp | 12 +- node/OutboundMulticast.cpp | 15 +- node/Packet.cpp | 304 ++++++++++++++++++++++--------- node/Packet.hpp | 21 ++- node/Path.hpp | 6 +- node/Peer.cpp | 35 ++-- node/Peer.hpp | 59 ++++-- node/Poly1305.cpp | 18 +- node/Revocation.cpp | 3 +- node/Revocation.hpp | 36 ++-- node/RingBuffer.hpp | 6 +- node/SHA512.cpp | 12 +- node/Salsa20.cpp | 18 +- node/SelfAwareness.cpp | 9 +- node/SharedPtr.hpp | 15 +- node/Switch.cpp | 188 +++++++++++++------ node/Switch.hpp | 3 +- node/Tag.cpp | 3 +- node/Tag.hpp | 35 ++-- node/Topology.cpp | 89 ++++++--- node/Topology.hpp | 40 ++-- node/Trace.cpp | 194 ++++++++++++++------ node/Utils.cpp | 14 +- node/Utils.hpp | 82 ++++++--- node/World.hpp | 53 ++++-- 57 files changed, 2247 insertions(+), 1082 deletions(-) diff --git a/node/AES.cpp b/node/AES.cpp index 8f2f30d20..90247b468 100644 --- a/node/AES.cpp +++ b/node/AES.cpp @@ -136,8 +136,9 @@ void AES::GMAC::update(const void *const data, unsigned int len) noexcept if (_rp) { for (;;) { - if (!len) + if (!len) { return; + } --len; _r[_rp++] = *(in++); if (_rp == 16) { @@ -160,8 +161,9 @@ void AES::GMAC::update(const void *const data, unsigned int len) noexcept _y[0] = y0; _y[1] = y1; - for (unsigned int i = 0; i < len; ++i) + for (unsigned int i = 0; i < len; ++i) { _r[i] = in[i]; + } _rp = len; // len is always less than 16 here } @@ -187,8 +189,9 @@ void AES::GMAC::finish(uint8_t tag[16]) noexcept uint64_t y1 = _y[1]; if (_rp) { - while (_rp < 16) + while (_rp < 16) { _r[_rp++] = 0; + } y0 ^= Utils::loadMachineEndian< uint64_t >(_r); y1 ^= Utils::loadMachineEndian< uint64_t >(_r + 8); s_gfmul(h0, h1, y0, y1); @@ -247,8 +250,9 @@ void AES::CTR::crypt(const void *const input, unsigned int len) noexcept _aes.p_encryptSW(reinterpret_cast(_ctr), reinterpret_cast(keyStream)); reinterpret_cast(_ctr)[3] = Utils::hton(++ctr); uint8_t *outblk = out + (totalLen - 16); - for (int i = 0; i < 16; ++i) + for (int i = 0; i < 16; ++i) { outblk[i] ^= reinterpret_cast(keyStream)[i]; + } break; } } @@ -442,8 +446,9 @@ void AES::CTR::finish() noexcept const unsigned int rem = _len & 15U; if (rem) { _aes.encrypt(_ctr, tmp); - for (unsigned int i = 0, j = _len - rem; i < rem; ++i) + for (unsigned int i = 0, j = _len - rem; i < rem; ++i) { _out[j + i] ^= tmp[i]; + } } } @@ -497,8 +502,9 @@ void AES::p_initSW(const uint8_t *key) noexcept rk[9] = rk[1] ^ rk[8]; rk[10] = rk[2] ^ rk[9]; rk[11] = rk[3] ^ rk[10]; - if (++i == 7) + if (++i == 7) { break; + } temp = rk[11]; rk[12] = rk[4] ^ (Te2_r(temp >> 24U) & 0xff000000U) ^ (Te3_r((temp >> 16U) & 0xffU) & 0x00ff0000U) ^ (Te0[(temp >> 8U) & 0xffU] & 0x0000ff00U) ^ (Te1_r((temp) & 0xffU) & 0x000000ffU); rk[13] = rk[5] ^ rk[12]; @@ -511,8 +517,9 @@ void AES::p_initSW(const uint8_t *key) noexcept p_k.sw.h[0] = Utils::ntoh(p_k.sw.h[0]); p_k.sw.h[1] = Utils::ntoh(p_k.sw.h[1]); - for (int i = 0; i < 60; ++i) + for (int i = 0; i < 60; ++i) { p_k.sw.dk[i] = p_k.sw.ek[i]; + } rk = p_k.sw.dk; for (int i = 0, j = 56; i < j; i += 4, j -= 4) { diff --git a/node/AES.hpp b/node/AES.hpp index b8f4ef996..0cae08656 100644 --- a/node/AES.hpp +++ b/node/AES.hpp @@ -197,8 +197,9 @@ public: *reinterpret_cast(_iv + 8) = *reinterpret_cast(iv + 8); *reinterpret_cast(_iv + 12) = 0x01000000; // 0x00000001 in big-endian byte order #else - for(int i=0;i<12;++i) + for(int i=0;i<12;++i) { _iv[i] = iv[i]; + } _iv[12] = 0; _iv[13] = 0; _iv[14] = 0; @@ -373,8 +374,9 @@ public: // End of AAD is padded to a multiple of 16 bytes to ensure unique encoding. len &= 0xfU; - if (len != 0) + if (len != 0) { _gmac.update(Utils::ZERO256, 16 - len); + } } /** @@ -495,8 +497,9 @@ public: { _gmac.update(aad, len); len &= 0xfU; - if (len != 0) + if (len != 0) { _gmac.update(Utils::ZERO256, 16 - len); + } } /** diff --git a/node/AES_aesni.cpp b/node/AES_aesni.cpp index a185b1b36..31b046608 100644 --- a/node/AES_aesni.cpp +++ b/node/AES_aesni.cpp @@ -228,8 +228,9 @@ void AES::GMAC::p_aesNIUpdate(const uint8_t *in, unsigned int len) noexcept // Handle anything left over from a previous run that wasn't a multiple of 16 bytes. if (_rp) { for (;;) { - if (!len) + if (!len) { return; + } --len; _r[_rp++] = *(in++); if (_rp == 16) { @@ -281,8 +282,9 @@ void AES::GMAC::p_aesNIUpdate(const uint8_t *in, unsigned int len) noexcept _mm_storeu_si128(reinterpret_cast<__m128i *>(_y), y); // Any overflow is cached for a later run or finish(). - for (unsigned int i = 0; i < len; ++i) + for (unsigned int i = 0; i < len; ++i) { _r[i] = in[i]; + } _rp = len; // len is always less than 16 here } @@ -295,8 +297,9 @@ void AES::GMAC::p_aesNIFinish(uint8_t tag[16]) noexcept // Handle any remaining bytes, padding the last block with zeroes. if (_rp) { - while (_rp < 16) + while (_rp < 16) { _r[_rp++] = 0; + } y = p_gmacPCLMUL128(_aes.p_k.ni.h[0], _mm_xor_si128(y, _mm_loadu_si128(reinterpret_cast<__m128i *>(_r)))); } @@ -438,9 +441,9 @@ void AES::CTR::p_aesNICrypt(const uint8_t *in, uint8_t *out, unsigned int len) n #if !defined(ZT_AES_VAES512) && defined(ZT_AES_VAES256) if (Utils::CPUID.vaes && (len >= 256)) { - p_aesCtrInnerVAES256(len, _ctr[0], c1, in, out, k); - goto skip_conventional_aesni_64; - } + p_aesCtrInnerVAES256(len, _ctr[0], c1, in, out, k); + goto skip_conventional_aesni_64; + } #endif const uint8_t *const eof64 = in + (len & ~((unsigned int)63)); @@ -552,8 +555,9 @@ void AES::CTR::p_aesNICrypt(const uint8_t *in, uint8_t *out, unsigned int len) n // Any remaining input is placed in _out. This will be picked up and crypted // on subsequent calls to crypt() or finish() as it'll mean _len will not be // an even multiple of 16. - for (unsigned int i = 0; i < len; ++i) + for (unsigned int i = 0; i < len; ++i) { out[i] = in[i]; + } _ctr[1] = Utils::hton(c1); } diff --git a/node/AES_armcrypto.cpp b/node/AES_armcrypto.cpp index c77aa0779..2f20c23de 100644 --- a/node/AES_armcrypto.cpp +++ b/node/AES_armcrypto.cpp @@ -56,8 +56,9 @@ void AES::GMAC::p_armUpdate(const uint8_t *in, unsigned int len) noexcept if (_rp) { for(;;) { - if (!len) + if (!len) { return; + } --len; _r[_rp++] = *(in++); if (_rp == 16) { @@ -75,8 +76,9 @@ void AES::GMAC::p_armUpdate(const uint8_t *in, unsigned int len) noexcept vst1q_u8(reinterpret_cast(_y), y); - for (unsigned int i = 0; i < len; ++i) + for (unsigned int i = 0; i < len; ++i) { _r[i] = in[i]; + } _rp = len; // len is always less than 16 here } @@ -87,8 +89,9 @@ void AES::GMAC::p_armFinish(uint8_t tag[16]) noexcept const uint8x16_t h = _aes.p_k.neon.h; if (_rp) { - while (_rp < 16) + while (_rp < 16) { _r[_rp++] = 0; + } y = s_clmul_armneon_crypto(h, y, _r); } @@ -255,8 +258,9 @@ void AES::CTR::p_armCrypt(const uint8_t *in, uint8_t *out, unsigned int len) noe in += 64; dd = (uint8x16_t)vaddq_u32((uint32x4_t)dd, four); - if (unlikely(len < 64)) + if (unlikely(len < 64)) { break; + } dd1 = (uint8x16_t)vaddq_u32((uint32x4_t)dd1, four); dd2 = (uint8x16_t)vaddq_u32((uint32x4_t)dd2, four); dd3 = (uint8x16_t)vaddq_u32((uint32x4_t)dd3, four); @@ -290,8 +294,9 @@ void AES::CTR::p_armCrypt(const uint8_t *in, uint8_t *out, unsigned int len) noe // Any remaining input is placed in _out. This will be picked up and crypted // on subsequent calls to crypt() or finish() as it'll mean _len will not be // an even multiple of 16. - for (unsigned int i = 0; i < len; ++i) + for (unsigned int i = 0; i < len; ++i) { out[i] = in[i]; + } vst1q_u8(reinterpret_cast(_ctr), vrev32q_u8(dd)); } @@ -327,12 +332,14 @@ void AES::p_init_armneon_crypto(const uint8_t *key) noexcept w[i] = w[i - ZT_INIT_ARMNEON_CRYPTO_NK] ^ t; } - for (unsigned int i=0;i<(ZT_INIT_ARMNEON_CRYPTO_NB * (ZT_INIT_ARMNEON_CRYPTO_NR + 1));++i) + for (unsigned int i=0;i<(ZT_INIT_ARMNEON_CRYPTO_NB * (ZT_INIT_ARMNEON_CRYPTO_NR + 1));++i) { w[i] = Utils::hton(w[i]); + } p_k.neon.dk[0] = p_k.neon.ek[14]; - for (int i=1;i<14;++i) + for (int i=1;i<14;++i) { p_k.neon.dk[i] = vaesimcq_u8(p_k.neon.ek[14 - i]); + } p_k.neon.dk[14] = p_k.neon.ek[0]; p_encrypt_armneon_crypto(Utils::ZERO256, h); diff --git a/node/Address.hpp b/node/Address.hpp index d72eb6af6..b6ccd86d7 100644 --- a/node/Address.hpp +++ b/node/Address.hpp @@ -71,8 +71,9 @@ public: */ inline void copyTo(void *const bits,const unsigned int len) const { - if (len < ZT_ADDRESS_LENGTH) + if (len < ZT_ADDRESS_LENGTH) { return; + } unsigned char *b = (unsigned char *)bits; *(b++) = (unsigned char)((_a >> 32) & 0xff); *(b++) = (unsigned char)((_a >> 24) & 0xff); diff --git a/node/Bond.cpp b/node/Bond.cpp index 8edd7e297..0eee7cff3 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -112,13 +112,11 @@ SharedPtr Bond::createBond(const RuntimeEnvironment* renv, const SharedPtr bond = new Bond(renv, _bondPolicyTemplates[_defaultPolicyStr].ptr(), peer); bond->debug("new default custom bond (based on %s)", bond->getPolicyStrByCode(bond->policy()).c_str()); } - } - else { + } else { if (! _bondPolicyTemplates[_policyTemplateAssignments[identity]]) { bond = new Bond(renv, _defaultPolicy, peer); bond->debug("peer-specific bond, was specified as %s but the bond definition was not found, using default %s", _policyTemplateAssignments[identity].c_str(), getPolicyStrByCode(_defaultPolicy).c_str()); - } - else { + } else { bond = new Bond(renv, _bondPolicyTemplates[_policyTemplateAssignments[identity]].ptr(), peer); bond->debug("new default bond"); } @@ -187,12 +185,10 @@ SharedPtr Bond::getLinkBySocket(const std::string& policyAlias, uint64_t l SharedPtr s = new Link(ifnameStr, 0, 0, 0, true, ZT_BOND_SLAVE_MODE_PRIMARY, ""); _interfaceToLinkMap[policyAlias].insert(std::pair >(ifnameStr, s)); return s; - } - else { + } else { return SharedPtr(); } - } - else { + } else { return search->second; } } @@ -359,8 +355,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) _rrPacketsSentOnCurrLink = 0; if (_numBondedPaths == 1 || _rrIdx >= (ZT_MAX_PEER_NETWORK_PATHS - 1)) { _rrIdx = 0; - } - else { + } else { int _tempIdx = _rrIdx; for (int searchCount = 0; searchCount < (_numBondedPaths - 1); searchCount++) { _tempIdx = (_tempIdx == (_numBondedPaths - 1)) ? 0 : _tempIdx + 1; @@ -390,8 +385,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) if (likely(it != _flows.end())) { it->second->lastActivity = now; return _paths[it->second->assignedPath].p; - } - else { + } else { unsigned char entropy; Utils::getSecureRandom(&entropy, 1); SharedPtr flow = createFlow(ZT_MAX_PEER_NETWORK_PATHS, flowId, entropy, now); @@ -469,8 +463,7 @@ void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, _paths[pathIdx].qosStatsIn[packetId] = now; ++(_paths[pathIdx].packetsReceivedSinceLastQoS); //_paths[pathIdx].packetValiditySamples.push(true); - } - else { + } else { // debug("QoS buffer full, will not record information"); } /* @@ -497,8 +490,7 @@ void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, SharedPtr flow; if (! _flows.count(flowId)) { flow = createFlow(pathIdx, flowId, 0, now); - } - else { + } else { flow = _flows[flowId]; } if (flow) { @@ -584,8 +576,7 @@ bool Bond::assignFlowToBondedPath(SharedPtr& flow, int64_t now, bool reass if (reassign) { log("attempting to re-assign out-flow %04x previously on idx %d (%u / %zu flows)", flow->id, flow->assignedPath, _paths[_realIdxMap[flow->assignedPath]].assignedFlowCount, _flows.size()); - } - else { + } else { debug("attempting to assign flow for the first time"); } @@ -599,8 +590,7 @@ bool Bond::assignFlowToBondedPath(SharedPtr& flow, int64_t now, bool reass if (reassign) { bondedIdx = (flow->assignedPath + offset) % (_numBondedPaths); - } - else { + } else { bondedIdx = abs((int)((entropy + offset) % (_numBondedPaths))); } // debug("idx=%d, offset=%d, randomCap=%f, actualCap=%f", bondedIdx, offset, randomLinkCapacity, _paths[_realIdxMap[bondedIdx]].relativeLinkCapacity); @@ -623,8 +613,7 @@ bool Bond::assignFlowToBondedPath(SharedPtr& flow, int64_t now, bool reass flow->assignPath(_realIdxMap[bondedIdx], now); ++(_paths[_realIdxMap[bondedIdx]].assignedFlowCount); // debug(" ABLE to find optimal link %f idx %d", _paths[_realIdxMap[bondedIdx]].relativeQuality, bondedIdx); - } - else { + } else { // We were (unable) to find a path that didn't violate at least one quality requirement, will choose next best option flow->assignPath(_realIdxMap[nextBestQualIdx], now); ++(_paths[_realIdxMap[nextBestQualIdx]].assignedFlowCount); @@ -684,13 +673,11 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) debug("forget flow %04x (age %" PRId64 ") (%u / %zu)", it->first, it->second->age(now), _paths[it->second->assignedPath].assignedFlowCount, (_flows.size() - 1)); _paths[it->second->assignedPath].assignedFlowCount--; it = _flows.erase(it); - } - else { + } else { ++it; } } - } - else if (oldest) { // Remove single oldest by natural expiration + } else if (oldest) { // Remove single oldest by natural expiration uint64_t maxAge = 0; while (it != _flows.end()) { if (it->second->age(now) > maxAge) { @@ -737,8 +724,7 @@ void Bond::processIncomingPathNegotiationRequest(uint64_t now, SharedPtr& if (_peer->_id.address().toInt() > RR->node->identity().address().toInt()) { debug("agree with peer to use alternate link %s/%s\n", link->ifname().c_str(), pathStr); _negotiatedPathIdx = pathIdx; - } - else { + } else { debug("ignore petition from peer to use alternate link %s/%s\n", link->ifname().c_str(), pathStr); } } @@ -852,8 +838,7 @@ void Bond::sendQOS_MEASUREMENT(void* tPtr, int pathIdx, int64_t localSocket, con if (atAddress) { outp.armor(_peer->key(), false, _peer->aesKeysIfSupported()); RR->node->putPacket(tPtr, localSocket, atAddress, outp.data(), outp.size()); - } - else { + } else { RR->sw->send(tPtr, outp, false); } _paths[pathIdx].packetsReceivedSinceLastQoS = 0; @@ -1192,8 +1177,7 @@ void Bond::estimatePathQuality(int64_t now) if ((now - it->second) >= qosRecordTimeout) { it = _paths[i].qosStatsOut.erase(it); ++numDroppedQosOutRecords; - } - else { + } else { ++it; } } @@ -1221,8 +1205,7 @@ void Bond::estimatePathQuality(int64_t now) if ((now - it->second) >= qosRecordTimeout) { it = _paths[i].qosStatsIn.erase(it); ++numDroppedQosInRecords; - } - else { + } else { ++it; } } @@ -1341,8 +1324,7 @@ void Bond::estimatePathQuality(int64_t now) shouldAvoid = true; } _paths[i].shouldAvoid = shouldAvoid; - } - else { + } else { if (! shouldAvoid) { log("no longer avoiding link %s", pathToStr(_paths[i].p).c_str()); _paths[i].shouldAvoid = false; @@ -1454,8 +1436,7 @@ void Bond::processActiveBackupTasks(void* tPtr, int64_t now) _lastBondStatusLog = now; if (_abPathIdx == ZT_MAX_PEER_NETWORK_PATHS) { log("no active link"); - } - else if (_paths[_abPathIdx].p) { + } else if (_paths[_abPathIdx].p) { log("active link is %s, failover queue size is %zu", pathToStr(_paths[_abPathIdx].p).c_str(), _abFailoverQueue.size()); } if (_abFailoverQueue.empty()) { @@ -1563,8 +1544,7 @@ void Bond::processActiveBackupTasks(void* tPtr, int64_t now) log("link %s is ineligible, removing from failover queue (%zu links remain in queue)", pathToStr(_paths[_abPathIdx].p).c_str(), _abFailoverQueue.size()); } continue; - } - else { + } else { ++it; } } @@ -1713,8 +1693,7 @@ void Bond::processActiveBackupTasks(void* tPtr, int64_t now) if (! _abFailoverQueue.empty()) { dequeueNextActiveBackupPath(now); log("active link switched to %s", pathToStr(_paths[_abPathIdx].p).c_str()); - } - else { + } else { log("failover queue is empty, no links to choose from"); } } @@ -1760,8 +1739,7 @@ void Bond::processActiveBackupTasks(void* tPtr, int64_t now) dequeueNextActiveBackupPath(now); _lastPathNegotiationCheck = now; log("switch negotiated link %s (select mode: optimize)", pathToStr(_paths[_abPathIdx].p).c_str()); - } - else { + } else { // Try to find a better path and automatically switch to it -- not too often, though. if ((now - _lastActiveBackupPathChange) > ZT_BOND_OPTIMIZE_INTERVAL) { if (! _abFailoverQueue.empty()) { diff --git a/node/Bond.hpp b/node/Bond.hpp index 389b970e9..81b4691b5 100644 --- a/node/Bond.hpp +++ b/node/Bond.hpp @@ -889,8 +889,7 @@ class Bond { _lastAckRateCheck = now; if (_ackCutoffCount > numToDrain) { _ackCutoffCount -= numToDrain; - } - else { + } else { _ackCutoffCount = 0; } return (_ackCutoffCount < ZT_ACK_CUTOFF_LIMIT); @@ -909,8 +908,7 @@ class Bond { uint64_t diff = now - _lastQoSRateCheck; if ((diff) <= (_qosSendInterval / ZT_MAX_PEER_NETWORK_PATHS)) { ++_qosCutoffCount; - } - else { + } else { _qosCutoffCount = 0; } _lastQoSRateCheck = now; @@ -930,8 +928,7 @@ class Bond { int diff = now - _lastPathNegotiationReceived; if ((diff) <= (ZT_PATH_NEGOTIATION_CUTOFF_TIME / ZT_MAX_PEER_NETWORK_PATHS)) { ++_pathNegotiationCutoffCount; - } - else { + } else { _pathNegotiationCutoffCount = 0; } _lastPathNegotiationReceived = now; @@ -1228,20 +1225,17 @@ class Bond { unsigned int suggestedRefractoryPeriod = refractoryPeriod ? punishment + (refractoryPeriod * 2) : punishment; refractoryPeriod = std::min(suggestedRefractoryPeriod, (unsigned int)ZT_BOND_MAX_REFRACTORY_PERIOD); lastRefractoryUpdate = 0; - } - else { + } else { uint32_t drainRefractory = 0; if (lastRefractoryUpdate) { drainRefractory = (now - lastRefractoryUpdate); - } - else { + } else { drainRefractory = (now - lastAliveToggle); } lastRefractoryUpdate = now; if (refractoryPeriod > drainRefractory) { refractoryPeriod -= drainRefractory; - } - else { + } else { refractoryPeriod = 0; lastRefractoryUpdate = 0; } diff --git a/node/Buffer.hpp b/node/Buffer.hpp index a8ca82ed8..7211f1879 100644 --- a/node/Buffer.hpp +++ b/node/Buffer.hpp @@ -81,8 +81,9 @@ public: Buffer(unsigned int l) { - if (l > C) + if (l > C) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } _l = l; } @@ -100,8 +101,9 @@ public: template inline Buffer &operator=(const Buffer &b) { - if (unlikely(b._l > C)) + if (unlikely(b._l > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } if (C2 == C) { memcpy(this,&b,sizeof(Buffer)); } else { @@ -112,23 +114,26 @@ public: inline void copyFrom(const void *b,unsigned int l) { - if (unlikely(l > C)) + if (unlikely(l > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } memcpy(_b,b,l); _l = l; } unsigned char operator[](const unsigned int i) const { - if (unlikely(i >= _l)) + if (unlikely(i >= _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } return (unsigned char)_b[i]; } unsigned char &operator[](const unsigned int i) { - if (unlikely(i >= _l)) + if (unlikely(i >= _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } return ((unsigned char *)_b)[i]; } @@ -147,14 +152,16 @@ public: */ unsigned char *field(unsigned int i,unsigned int l) { - if (unlikely((i + l) > _l)) + if (unlikely((i + l) > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } return (unsigned char *)(_b + i); } const unsigned char *field(unsigned int i,unsigned int l) const { - if (unlikely((i + l) > _l)) + if (unlikely((i + l) > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } return (const unsigned char *)(_b + i); } @@ -168,12 +175,14 @@ public: template inline void setAt(unsigned int i,const T v) { - if (unlikely((i + sizeof(T)) > _l)) + if (unlikely((i + sizeof(T)) > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } #ifdef ZT_NO_TYPE_PUNNING uint8_t *p = reinterpret_cast(_b + i); - for(unsigned int x=1;x<=sizeof(T);++x) + for(unsigned int x=1;x<=sizeof(T);++x) { *(p++) = (uint8_t)(v >> (8 * (sizeof(T) - x))); + } #else T *const ZT_VAR_MAY_ALIAS p = reinterpret_cast(_b + i); *p = Utils::hton(v); @@ -190,8 +199,9 @@ public: template inline T at(unsigned int i) const { - if (unlikely((i + sizeof(T)) > _l)) + if (unlikely((i + sizeof(T)) > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } #ifdef ZT_NO_TYPE_PUNNING T v = 0; const uint8_t *p = reinterpret_cast(_b + i); @@ -216,12 +226,14 @@ public: template inline void append(const T v) { - if (unlikely((_l + sizeof(T)) > C)) + if (unlikely((_l + sizeof(T)) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } #ifdef ZT_NO_TYPE_PUNNING uint8_t *p = reinterpret_cast(_b + _l); - for(unsigned int x=1;x<=sizeof(T);++x) + for(unsigned int x=1;x<=sizeof(T);++x) { *(p++) = (uint8_t)(v >> (8 * (sizeof(T) - x))); + } #else T *const ZT_VAR_MAY_ALIAS p = reinterpret_cast(_b + _l); *p = Utils::hton(v); @@ -238,10 +250,12 @@ public: */ inline void append(unsigned char c,unsigned int n) { - if (unlikely((_l + n) > C)) + if (unlikely((_l + n) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; - for(unsigned int i=0;i C)) + if (unlikely((_l + n) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } Utils::getSecureRandom(_b + _l,n); _l += n; } @@ -266,8 +281,9 @@ public: */ inline void append(const void *b,unsigned int l) { - if (unlikely((_l + l) > C)) + if (unlikely((_l + l) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } memcpy(_b + _l,b,l); _l += l; } @@ -281,10 +297,12 @@ public: inline void appendCString(const char *s) { for(;;) { - if (unlikely(_l >= C)) + if (unlikely(_l >= C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; - if (!(_b[_l++] = *(s++))) + } + if (!(_b[_l++] = *(s++))) { break; + } } } @@ -313,8 +331,9 @@ public: */ inline char *appendField(unsigned int l) { - if (unlikely((_l + l) > C)) + if (unlikely((_l + l) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } char *r = _b + _l; _l += l; return r; @@ -330,8 +349,9 @@ public: */ inline void addSize(unsigned int i) { - if (unlikely((i + _l) > C)) + if (unlikely((i + _l) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } _l += i; } @@ -345,8 +365,9 @@ public: */ inline void setSize(const unsigned int i) { - if (unlikely(i > C)) + if (unlikely(i > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } _l = i; } @@ -358,10 +379,12 @@ public: */ inline void behead(const unsigned int at) { - if (!at) + if (!at) { return; - if (unlikely(at > _l)) + } + if (unlikely(at > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } ::memmove(_b,_b + at,_l -= at); } @@ -375,8 +398,9 @@ public: inline void erase(const unsigned int at,const unsigned int length) { const unsigned int endr = at + length; - if (unlikely(endr > _l)) + if (unlikely(endr > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } ::memmove(_b + at,_b + endr,_l - endr); _l -= length; } diff --git a/node/C25519.cpp b/node/C25519.cpp index aec34835e..f425540d8 100644 --- a/node/C25519.cpp +++ b/node/C25519.cpp @@ -731,7 +731,9 @@ static void crypto_scalarmult(u8 *mypublic, const u8 *secret, const u8 *basepoin uint8_t e[32]; int i; - for (i = 0; i < 32; ++i) e[i] = secret[i]; + for (i = 0; i < 32; ++i) { + e[i] = secret[i]; + } e[0] &= 248; e[31] &= 127; e[31] |= 64; @@ -837,14 +839,12 @@ static inline void reduce_add_sub(fe25519 *r) crypto_uint32 t; int i,rep; - for(rep=0;rep<4;rep++) - { + for(rep=0;rep<4;rep++) { t = r->v[31] >> 7; r->v[31] &= 127; t = times19(t); r->v[0] += t; - for(i=0;i<31;i++) - { + for(i=0;i<31;i++) { t = r->v[i] >> 8; r->v[i+1] += t; r->v[i] &= 255; @@ -857,14 +857,12 @@ static inline void reduce_mul(fe25519 *r) crypto_uint32 t; int i,rep; - for(rep=0;rep<2;rep++) - { + for(rep=0;rep<2;rep++) { t = r->v[31] >> 7; r->v[31] &= 127; t = times19(t); r->v[0] += t; - for(i=0;i<31;i++) - { + for(i=0;i<31;i++) { t = r->v[i] >> 8; r->v[i+1] += t; r->v[i] &= 255; @@ -877,22 +875,26 @@ static inline void fe25519_freeze(fe25519 *r) { int i; crypto_uint32 m = equal(r->v[31],127); - for(i=30;i>0;i--) + for(i=30;i>0;i--) { m &= equal(r->v[i],255); + } m &= ge(r->v[0],237); m = -m; r->v[31] -= m&127; - for(i=30;i>0;i--) + for(i=30;i>0;i--) { r->v[i] -= m&255; + } r->v[0] -= m&237; } static inline void fe25519_unpack(fe25519 *r, const unsigned char x[32]) { int i; - for(i=0;i<32;i++) r->v[i] = x[i]; + for(i=0;i<32;i++) { + r->v[i] = x[i]; + } r->v[31] &= 127; } @@ -902,8 +904,9 @@ static inline void fe25519_pack(unsigned char r[32], const fe25519 *x) int i; fe25519 y = *x; fe25519_freeze(&y); - for(i=0;i<32;i++) + for(i=0;i<32;i++) { r[i] = y.v[i]; + } } static inline int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y) @@ -913,8 +916,11 @@ static inline int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y) fe25519 t2 = *y; fe25519_freeze(&t1); fe25519_freeze(&t2); - for(i=0;i<32;i++) - if(t1.v[i] != t2.v[i]) return 0; + for(i=0;i<32;i++) { + if (t1.v[i] != t2.v[i]) { + return 0; + } + } return 1; } @@ -923,7 +929,9 @@ static inline void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b) int i; crypto_uint32 mask = b; mask = -mask; - for(i=0;i<32;i++) r->v[i] ^= mask & (x->v[i] ^ r->v[i]); + for(i=0;i<32;i++) { + r->v[i] ^= mask & (x->v[i] ^ r->v[i]); + } } static inline unsigned char fe25519_getparity(const fe25519 *x) @@ -937,20 +945,26 @@ static inline void fe25519_setone(fe25519 *r) { int i; r->v[0] = 1; - for(i=1;i<32;i++) r->v[i]=0; + for(i=1;i<32;i++) { + r->v[i]=0; + } } static inline void fe25519_setzero(fe25519 *r) { int i; - for(i=0;i<32;i++) r->v[i]=0; + for(i=0;i<32;i++) { + r->v[i]=0; + } } static inline void fe25519_neg(fe25519 *r, const fe25519 *x) { fe25519 t; int i; - for(i=0;i<32;i++) t.v[i]=x->v[i]; + for(i=0;i<32;i++) { + t.v[i]=x->v[i]; + } fe25519_setzero(r); fe25519_sub(r, r, &t); } @@ -958,7 +972,9 @@ static inline void fe25519_neg(fe25519 *r, const fe25519 *x) static inline void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y) { int i; - for(i=0;i<32;i++) r->v[i] = x->v[i] + y->v[i]; + for(i=0;i<32;i++) { + r->v[i] = x->v[i] + y->v[i]; + } reduce_add_sub(r); } @@ -968,8 +984,12 @@ static inline void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y) crypto_uint32 t[32]; t[0] = x->v[0] + 0x1da; t[31] = x->v[31] + 0xfe; - for(i=1;i<31;i++) t[i] = x->v[i] + 0x1fe; - for(i=0;i<32;i++) r->v[i] = t[i] - y->v[i]; + for(i=1;i<31;i++) { + t[i] = x->v[i] + 0x1fe; + } + for(i=0;i<32;i++) { + r->v[i] = t[i] - y->v[i]; + } reduce_add_sub(r); } @@ -977,14 +997,19 @@ static inline void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y) { int i,j; crypto_uint32 t[63]; - for(i=0;i<63;i++)t[i] = 0; + for(i=0;i<63;i++) { + t[i] = 0; + } - for(i=0;i<32;i++) - for(j=0;j<32;j++) + for(i=0;i<32;i++) { + for(j=0;j<32;j++) { t[i+j] += x->v[i] * y->v[j]; + } + } - for(i=32;i<63;i++) + for(i=32;i<63;i++) { r->v[i-32] = t[i-32] + times38(t[i]); + } r->v[31] = t[31]; /* result now in r[0]...r[31] */ reduce_mul(r); @@ -1136,16 +1161,16 @@ static inline void reduce_add_sub(sc25519 *r) int i; unsigned char t[32]; - for(i=0;i<32;i++) - { + for(i=0;i<32;i++) { pb += m[i]; b = lt(r->v[i],pb); t[i] = r->v[i]-pb+(b<<8); pb = b; } mask = b - 1; - for(i=0;i<32;i++) + for(i=0;i<32;i++) { r->v[i] ^= mask & (r->v[i] ^ t[i]); + } } /* Reduce coefficients of x before calling barrett_reduce */ @@ -1161,31 +1186,43 @@ static inline void barrett_reduce(sc25519 *r, const crypto_uint32 x[64]) crypto_uint32 pb = 0; crypto_uint32 b; - for (i = 0;i < 66;++i) q2[i] = 0; - for (i = 0;i < 33;++i) r2[i] = 0; + for (i = 0;i < 66;++i) { + q2[i] = 0; + } + for (i = 0;i < 33;++i) { + r2[i] = 0; + } - for(i=0;i<33;i++) - for(j=0;j<33;j++) - if(i+j >= 31) q2[i+j] += mu[i]*x[j+31]; + for(i=0;i<33;i++) { + for(j=0;j<33;j++) { + if(i+j >= 31) { + q2[i+j] += mu[i]*x[j+31]; + } + } + } carry = q2[31] >> 8; q2[32] += carry; carry = q2[32] >> 8; q2[33] += carry; - for(i=0;i<33;i++)r1[i] = x[i]; - for(i=0;i<32;i++) - for(j=0;j<33;j++) - if(i+j < 33) r2[i+j] += m[i]*q3[j]; + for(i=0;i<33;i++) { + r1[i] = x[i]; + } + for(i=0;i<32;i++) { + for(j=0;j<33;j++) { + if(i+j < 33) { + r2[i+j] += m[i]*q3[j]; + } + } + } - for(i=0;i<32;i++) - { + for(i=0;i<32;i++) { carry = r2[i] >> 8; r2[i+1] += carry; r2[i] &= 0xff; } - for(i=0;i<32;i++) - { + for(i=0;i<32;i++) { pb += r2[i]; b = lt(r1[i],pb); r->v[i] = r1[i]-pb+(b<<8); @@ -1204,8 +1241,12 @@ static inline void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]) { int i; crypto_uint32 t[64]; - for(i=0;i<32;i++) t[i] = x[i]; - for(i=32;i<64;++i) t[i] = 0; + for(i=0;i<32;i++) { + t[i] = x[i]; + } + for(i=32;i<64;++i) { + t[i] = 0; + } barrett_reduce(r, t); } @@ -1213,22 +1254,27 @@ static inline void sc25519_from64bytes(sc25519 *r, const unsigned char x[64]) { int i; crypto_uint32 t[64]; - for(i=0;i<64;i++) t[i] = x[i]; + for(i=0;i<64;i++) { + t[i] = x[i]; + } barrett_reduce(r, t); } static inline void sc25519_to32bytes(unsigned char r[32], const sc25519 *x) { int i; - for(i=0;i<32;i++) r[i] = x->v[i]; + for(i=0;i<32;i++) { + r[i] = x->v[i]; + } } static inline void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y) { int i, carry; - for(i=0;i<32;i++) r->v[i] = x->v[i] + y->v[i]; - for(i=0;i<31;i++) - { + for(i=0;i<32;i++) { + r->v[i] = x->v[i] + y->v[i]; + } + for(i=0;i<31;i++) { carry = r->v[i] >> 8; r->v[i+1] += carry; r->v[i] &= 0xff; @@ -1240,14 +1286,17 @@ static inline void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y) { int i,j,carry; crypto_uint32 t[64]; - for(i=0;i<64;i++)t[i] = 0; + for(i=0;i<64;i++) { + t[i] = 0; + } - for(i=0;i<32;i++) - for(j=0;j<32;j++) + for(i=0;i<32;i++) { + for(j=0;j<32;j++) { t[i+j] += x->v[i] * y->v[j]; + } + } - for(i=0;i<63;i++) - { + for(i=0;i<63;i++) { carry = t[i] >> 8; t[i+1] += carry; t[i] &= 0xff; @@ -1260,8 +1309,7 @@ static inline void sc25519_window3(signed char r[85], const sc25519 *s) { char carry; int i; - for(i=0;i<10;i++) - { + for(i=0;i<10;i++) { r[8*i+0] = s->v[3*i+0] & 7; r[8*i+1] = (s->v[3*i+0] >> 3) & 7; r[8*i+2] = (s->v[3*i+0] >> 6) & 7; @@ -1282,8 +1330,7 @@ static inline void sc25519_window3(signed char r[85], const sc25519 *s) /* Making it signed */ carry = 0; - for(i=0;i<84;i++) - { + for(i=0;i<84;i++) { r[i] += carry; r[i+1] += r[i] >> 3; r[i] &= 7; @@ -1296,8 +1343,7 @@ static inline void sc25519_window3(signed char r[85], const sc25519 *s) static inline void sc25519_2interleave2(unsigned char r[127], const sc25519 *s1, const sc25519 *s2) { int i; - for(i=0;i<31;i++) - { + for(i=0;i<31;i++) { r[4*i] = ( s1->v[i] & 3) ^ (( s2->v[i] & 3) << 2); r[4*i+1] = ((s1->v[i] >> 2) & 3) ^ (((s2->v[i] >> 2) & 3) << 2); r[4*i+2] = ((s1->v[i] >> 4) & 3) ^ (((s2->v[i] >> 4) & 3) << 2); @@ -2341,18 +2387,21 @@ static inline int ge25519_unpackneg_vartime(ge25519_p3 *r, const unsigned char p /* 3. Check whether sqrt computation gave correct result, multiply by sqrt(-1) if not: */ fe25519_square(&chk, &r->x); fe25519_mul(&chk, &chk, &den); - if (!fe25519_iseq_vartime(&chk, &num)) + if (!fe25519_iseq_vartime(&chk, &num)) { fe25519_mul(&r->x, &r->x, &ge25519_sqrtm1); + } /* 4. Now we have one of the two square roots, except if input was not a square */ fe25519_square(&chk, &r->x); fe25519_mul(&chk, &chk, &den); - if (!fe25519_iseq_vartime(&chk, &num)) + if (!fe25519_iseq_vartime(&chk, &num)) { return -1; + } /* 5. Choose the desired square root according to parity: */ - if(fe25519_getparity(&r->x) != (1-par)) + if(fe25519_getparity(&r->x) != (1-par)) { fe25519_neg(&r->x, &r->x); + } fe25519_mul(&r->t, &r->x, &r->y); return 0; @@ -2399,18 +2448,19 @@ static inline void ge25519_double_scalarmult_vartime(ge25519_p3 *r, const ge2551 /* scalar multiplication */ *r = pre[b[126]]; - for(i=125;i>=0;i--) - { + for(i=125;i>=0;i--) { dbl_p1p1(&tp1p1, (ge25519_p2 *)r); p1p1_to_p2((ge25519_p2 *) r, &tp1p1); dbl_p1p1(&tp1p1, (ge25519_p2 *)r); - if(b[i]!=0) - { + if(b[i]!=0) { p1p1_to_p3(r, &tp1p1); add_p1p1(&tp1p1, r, &pre[b[i]]); } - if(i != 0) p1p1_to_p2((ge25519_p2 *)r, &tp1p1); - else p1p1_to_p3(r, &tp1p1); + if (i != 0) { + p1p1_to_p2((ge25519_p2 *)r, &tp1p1); + } else { + p1p1_to_p3(r, &tp1p1); + } } } @@ -2424,8 +2474,7 @@ static inline void ge25519_scalarmult_base(ge25519_p3 *r, const sc25519 *s) choose_t((ge25519_aff *)r, 0, b[0]); fe25519_setone(&r->z); fe25519_mul(&r->t, &r->x, &r->y); - for(i=1;i<85;i++) - { + for(i=1;i<85;i++) { choose_t(&t, (unsigned long long) i, b[i]); ge25519_mixadd2(r, &t); } @@ -2435,9 +2484,15 @@ static inline void get_hram(unsigned char *hram, const unsigned char *sm, const { unsigned long long i; - for (i = 0;i < 32;++i) playground[i] = sm[i]; - for (i = 32;i < 64;++i) playground[i] = pk[i-32]; - for (i = 64;i < smlen;++i) playground[i] = sm[i]; + for (i = 0;i < 32;++i) { + playground[i] = sm[i]; + } + for (i = 32;i < 64;++i) { + playground[i] = pk[i-32]; + } + for (i = 64;i < smlen;++i) { + playground[i] = sm[i]; + } ZeroTier::SHA512(hram,playground,(unsigned int)smlen); } @@ -2491,10 +2546,12 @@ void C25519::sign(const C25519::Private &myPrivate,const C25519::Public &myPubli extsk[31] &= 127; extsk[31] |= 64; - for(unsigned int i=0;i<32;i++) + for(unsigned int i=0;i<32;i++) { sig[32 + i] = extsk[32 + i]; - for(unsigned int i=0;i<32;i++) + } + for(unsigned int i=0;i<32;i++) { sig[64 + i] = digest[i]; + } SHA512(hmg,sig + 32,64); @@ -2504,8 +2561,9 @@ void C25519::sign(const C25519::Private &myPrivate,const C25519::Public &myPubli ge25519_pack(r, &ger); /* Computation of s */ - for(unsigned int i=0;i<32;i++) + for(unsigned int i=0;i<32;i++) { sig[i] = r[i]; + } get_hram(hram,sig,myPublic.data + 32,sig,96); @@ -2516,8 +2574,9 @@ void C25519::sign(const C25519::Private &myPrivate,const C25519::Public &myPubli sc25519_add(&scs, &scs, &sck); sc25519_to32bytes(s,&scs); /* cat s */ - for(unsigned int i=0;i<32;i++) + for(unsigned int i=0;i<32;i++) { sig[32 + i] = s[i]; + } #endif } @@ -2526,8 +2585,9 @@ bool C25519::verify(const C25519::Public &their,const void *msg,unsigned int len const unsigned char *const sig = (const unsigned char *)signature; unsigned char digest[64]; // we sign the first 32 bytes of SHA-512(msg) SHA512(digest,msg,len); - if (!Utils::secureEq(sig + 64,digest,32)) + if (!Utils::secureEq(sig + 64,digest,32)) { return false; + } unsigned char t2[32]; ge25519 get1, get2; @@ -2535,8 +2595,9 @@ bool C25519::verify(const C25519::Public &their,const void *msg,unsigned int len unsigned char hram[crypto_hash_sha512_BYTES]; unsigned char m[96]; - if (ge25519_unpackneg_vartime(&get1,their.data + 32)) + if (ge25519_unpackneg_vartime(&get1,their.data + 32)) { return false; + } get_hram(hram,sig,their.data + 32,m,96); diff --git a/node/Capability.cpp b/node/Capability.cpp index 21e7144f8..d4350ebf7 100644 --- a/node/Capability.cpp +++ b/node/Capability.cpp @@ -25,27 +25,31 @@ int Capability::verify(const RuntimeEnvironment *RR,void *tPtr) const { try { // There must be at least one entry, and sanity check for bad chain max length - if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) { return -1; + } // Validate all entries in chain of custody Buffer<(sizeof(Capability) * 2)> tmp; this->serialize(tmp,true); for(unsigned int c=0;c<_maxCustodyChainLength;++c) { if (c == 0) { - if ((!_custody[c].to)||(!_custody[c].from)||(_custody[c].from != Network::controllerFor(_nwid))) + if ((!_custody[c].to)||(!_custody[c].from)||(_custody[c].from != Network::controllerFor(_nwid))) { return -1; // the first entry must be present and from the network's controller + } } else { - if (!_custody[c].to) + if (!_custody[c].to) { return 0; // all previous entries were valid, so we are valid - else if ((!_custody[c].from)||(_custody[c].from != _custody[c-1].to)) + } else if ((!_custody[c].from)||(_custody[c].from != _custody[c-1].to)) { return -1; // otherwise if we have another entry it must be from the previous holder in the chain + } } const Identity id(RR->topology->getIdentity(tPtr,_custody[c].from)); if (id) { - if (!id.verify(tmp.data(),tmp.size(),_custody[c].signature)) + if (!id.verify(tmp.data(),tmp.size(),_custody[c].signature)) { return -1; + } } else { RR->sw->requestWhois(tPtr,RR->node->now(),_custody[c].from); return 1; diff --git a/node/Capability.hpp b/node/Capability.hpp index 115973d50..9e2409b93 100644 --- a/node/Capability.hpp +++ b/node/Capability.hpp @@ -85,8 +85,9 @@ public: _maxCustodyChainLength((mccl > 0) ? ((mccl < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) ? mccl : (unsigned int)ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) : 1), _ruleCount((ruleCount < ZT_MAX_CAPABILITY_RULES) ? ruleCount : ZT_MAX_CAPABILITY_RULES) { - if (_ruleCount > 0) + if (_ruleCount > 0) { memcpy(_rules,rules,sizeof(ZT_VirtualNetworkRule) * _ruleCount); + } } /** @@ -121,9 +122,11 @@ public: { Address i2; for(unsigned int i=0;i inline void serialize(Buffer &b,const bool forSign = false) const { - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } // These are the same between Tag and Capability b.append(_nwid); @@ -409,7 +414,9 @@ public: // This is the size of any additional fields, currently 0. b.append((uint16_t)0); - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } } template @@ -419,40 +426,53 @@ public: unsigned int p = startAt; - _nwid = b.template at(p); p += 8; - _ts = b.template at(p); p += 8; - _id = b.template at(p); p += 4; + _nwid = b.template at(p); + p += 8; + _ts = b.template at(p); + p += 8; + _id = b.template at(p); + p += 4; - const unsigned int rc = b.template at(p); p += 2; - if (rc > ZT_MAX_CAPABILITY_RULES) + const unsigned int rc = b.template at(p); + p += 2; + if (rc > ZT_MAX_CAPABILITY_RULES) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } deserializeRules(b,p,_rules,_ruleCount,rc); _maxCustodyChainLength = (unsigned int)b[p++]; - if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } for(unsigned int i=0;;++i) { - const Address to(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - if (!to) + const Address to(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; + if (!to) { break; - if ((i >= _maxCustodyChainLength)||(i >= ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + } + if ((i >= _maxCustodyChainLength)||(i >= ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } _custody[i].to = to; - _custody[i].from.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _custody[i].from.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; if (b[p++] == 1) { - if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) + if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } p += 2; - memcpy(_custody[i].signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + memcpy(_custody[i].signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; } else { p += 2 + b.template at(p); } } p += 2 + b.template at(p); - if (p > b.size()) + if (p > b.size()) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } return (p - startAt); } diff --git a/node/CertificateOfMembership.cpp b/node/CertificateOfMembership.cpp index dbda9939f..94effd028 100644 --- a/node/CertificateOfMembership.cpp +++ b/node/CertificateOfMembership.cpp @@ -48,25 +48,30 @@ CertificateOfMembership::CertificateOfMembership(uint64_t timestamp,uint64_t tim bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other, const Identity &otherIdentity) const { - if ((_qualifierCount == 0)||(other._qualifierCount == 0)) + if ((_qualifierCount == 0)||(other._qualifierCount == 0)) { return false; + } std::map< uint64_t, uint64_t > otherFields; - for(unsigned int i=0;i= 3)&&(qid <= 6)) + if ((qid >= 3)&&(qid <= 6)) { fullIdentityVerification = true; + } std::map< uint64_t, uint64_t >::iterator otherQ(otherFields.find(qid)); - if (otherQ == otherFields.end()) + if (otherQ == otherFields.end()) { return false; + } const uint64_t a = _qualifiers[i].value; const uint64_t b = otherQ->second; - if (((a >= b) ? (a - b) : (b - a)) > _qualifiers[i].maxDelta) + if (((a >= b) ? (a - b) : (b - a)) > _qualifiers[i].maxDelta) { return false; + } } // If this COM has a full hash of its identity, assume the other must have this as well. @@ -76,10 +81,12 @@ bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other, c otherIdentity.publicKeyHash(idHash); for(unsigned long i=0;i<4;++i) { std::map< uint64_t, uint64_t >::iterator otherQ(otherFields.find((uint64_t)(i + 3))); - if (otherQ == otherFields.end()) + if (otherQ == otherFields.end()) { return false; - if (otherQ->second != Utils::ntoh(idHash[i])) + } + if (otherQ->second != Utils::ntoh(idHash[i])) { return false; + } } } @@ -108,8 +115,9 @@ bool CertificateOfMembership::sign(const Identity &with) int CertificateOfMembership::verify(const RuntimeEnvironment *RR,void *tPtr) const { - if ((!_signedBy)||(_signedBy != Network::controllerFor(networkId()))||(_qualifierCount > ZT_NETWORK_COM_MAX_QUALIFIERS)) + if ((!_signedBy)||(_signedBy != Network::controllerFor(networkId()))||(_qualifierCount > ZT_NETWORK_COM_MAX_QUALIFIERS)) { return -1; + } const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); if (!id) { diff --git a/node/CertificateOfMembership.hpp b/node/CertificateOfMembership.hpp index 1948dd7b7..eadfbb090 100644 --- a/node/CertificateOfMembership.hpp +++ b/node/CertificateOfMembership.hpp @@ -142,8 +142,9 @@ public: inline int64_t timestamp() const { for(unsigned int i=0;i<_qualifierCount;++i) { - if (_qualifiers[i].id == COM_RESERVED_ID_TIMESTAMP) + if (_qualifiers[i].id == COM_RESERVED_ID_TIMESTAMP) { return _qualifiers[i].value; + } } return 0; } @@ -154,8 +155,9 @@ public: inline Address issuedTo() const { for(unsigned int i=0;i<_qualifierCount;++i) { - if (_qualifiers[i].id == COM_RESERVED_ID_ISSUED_TO) + if (_qualifiers[i].id == COM_RESERVED_ID_ISSUED_TO) { return Address(_qualifiers[i].value); + } } return Address(); } @@ -166,8 +168,9 @@ public: inline uint64_t networkId() const { for(unsigned int i=0;i<_qualifierCount;++i) { - if (_qualifiers[i].id == COM_RESERVED_ID_NETWORK_ID) + if (_qualifiers[i].id == COM_RESERVED_ID_NETWORK_ID) { return _qualifiers[i].value; + } } return 0ULL; } @@ -226,8 +229,9 @@ public: b.append(_qualifiers[i].maxDelta); } _signedBy.appendTo(b); - if (_signedBy) + if (_signedBy) { b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); + } } template @@ -238,16 +242,20 @@ public: _qualifierCount = 0; _signedBy.zero(); - if (b[p++] != 1) + if (b[p++] != 1) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE; + } - unsigned int numq = b.template at(p); p += sizeof(uint16_t); + unsigned int numq = b.template at(p); + p += sizeof(uint16_t); uint64_t lastId = 0; for(unsigned int i=0;i(p); - if (qid < lastId) + if (qid < lastId) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_BAD_ENCODING; - else lastId = qid; + } else { + lastId = qid; + } if (_qualifierCount < ZT_NETWORK_COM_MAX_QUALIFIERS) { _qualifiers[_qualifierCount].id = qid; _qualifiers[_qualifierCount].value = b.template at(p + 8); @@ -272,15 +280,18 @@ public: inline bool operator==(const CertificateOfMembership &c) const { - if (_signedBy != c._signedBy) + if (_signedBy != c._signedBy) { return false; - if (_qualifierCount != c._qualifierCount) + } + if (_qualifierCount != c._qualifierCount) { return false; + } for(unsigned int i=0;i<_qualifierCount;++i) { const _Qualifier &a = _qualifiers[i]; const _Qualifier &b = c._qualifiers[i]; - if ((a.id != b.id)||(a.value != b.value)||(a.maxDelta != b.maxDelta)) + if ((a.id != b.id)||(a.value != b.value)||(a.maxDelta != b.maxDelta)) { return false; + } } return (memcmp(_signature.data,c._signature.data,ZT_C25519_SIGNATURE_LEN) == 0); } diff --git a/node/CertificateOfOwnership.cpp b/node/CertificateOfOwnership.cpp index 70be0a044..11be4fe91 100644 --- a/node/CertificateOfOwnership.cpp +++ b/node/CertificateOfOwnership.cpp @@ -23,8 +23,9 @@ namespace ZeroTier { int CertificateOfOwnership::verify(const RuntimeEnvironment *RR,void *tPtr) const { - if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) { return -1; + } const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); if (!id) { RR->sw->requestWhois(tPtr,RR->node->now(),_signedBy); @@ -45,12 +46,14 @@ bool CertificateOfOwnership::_owns(const CertificateOfOwnership::Thing &t,const if (_thingTypes[i] == (uint8_t)t) { unsigned int k = 0; while (k < l) { - if (reinterpret_cast(v)[k] != _thingValues[i][k]) + if (reinterpret_cast(v)[k] != _thingValues[i][k]) { break; + } ++k; } - if (k == l) + if (k == l) { return true; + } } } return false; diff --git a/node/CertificateOfOwnership.hpp b/node/CertificateOfOwnership.hpp index abd62df1a..e7d549fdc 100644 --- a/node/CertificateOfOwnership.hpp +++ b/node/CertificateOfOwnership.hpp @@ -80,10 +80,12 @@ public: inline bool owns(const InetAddress &ip) const { - if (ip.ss_family == AF_INET) + if (ip.ss_family == AF_INET) { return this->_owns(THING_IPV4_ADDRESS,&(reinterpret_cast(&ip)->sin_addr.s_addr),4); - if (ip.ss_family == AF_INET6) + } + if (ip.ss_family == AF_INET6) { return this->_owns(THING_IPV6_ADDRESS,reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + } return false; } @@ -96,7 +98,9 @@ public: inline void addThing(const InetAddress &ip) { - if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) return; + if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) { + return; + } if (ip.ss_family == AF_INET) { _thingTypes[_thingCount] = THING_IPV4_ADDRESS; memcpy(_thingValues[_thingCount],&(reinterpret_cast(&ip)->sin_addr.s_addr),4); @@ -110,7 +114,9 @@ public: inline void addThing(const MAC &mac) { - if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) return; + if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) { + return; + } _thingTypes[_thingCount] = THING_MAC_ADDRESS; mac.copyTo(_thingValues[_thingCount],6); ++_thingCount; @@ -142,7 +148,9 @@ public: template inline void serialize(Buffer &b,const bool forSign = false) const { - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } b.append(_networkId); b.append(_ts); @@ -164,7 +172,9 @@ public: b.append((uint16_t)0); // length of additional fields, currently 0 - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } } template @@ -174,11 +184,16 @@ public: *this = CertificateOfOwnership(); - _networkId = b.template at(p); p += 8; - _ts = b.template at(p); p += 8; - _flags = b.template at(p); p += 8; - _id = b.template at(p); p += 4; - _thingCount = b.template at(p); p += 2; + _networkId = b.template at(p); + p += 8; + _ts = b.template at(p); + p += 8; + _flags = b.template at(p); + p += 8; + _id = b.template at(p); + p += 4; + _thingCount = b.template at(p); + p += 2; for(unsigned int i=0,j=_thingCount;i(p) != ZT_C25519_SIGNATURE_LEN) + if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } p += 2; - memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; } else { p += 2 + b.template at(p); } p += 2 + b.template at(p); - if (p > b.size()) + if (p > b.size()) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } return (p - startAt); } diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp index 7a4572427..c6a3c7bea 100644 --- a/node/Dictionary.hpp +++ b/node/Dictionary.hpp @@ -55,10 +55,14 @@ public: { for(unsigned int i=0;iget(key,tmp,sizeof(tmp)) >= 0) + if (this->get(key,tmp,sizeof(tmp)) >= 0) { return ((*tmp == '1')||(*tmp == 't')||(*tmp == 'T')); + } return dfl; } @@ -257,8 +280,9 @@ public: inline uint64_t getUI(const char *key,uint64_t dfl = 0) const { char tmp[128]; - if (this->get(key,tmp,sizeof(tmp)) >= 1) + if (this->get(key,tmp,sizeof(tmp)) >= 1) { return Utils::hexStrToU64(tmp); + } return dfl; } @@ -272,8 +296,9 @@ public: inline int64_t getI(const char *key,int64_t dfl = 0) const { char tmp[128]; - if (this->get(key,tmp,sizeof(tmp)) >= 1) + if (this->get(key,tmp,sizeof(tmp)) >= 1) { return Utils::hexStrTo64(tmp); + } return dfl; } @@ -335,11 +360,21 @@ public: return false; } switch(*p) { - case 0: _d[j++] = '0'; break; - case 13: _d[j++] = 'r'; break; - case 10: _d[j++] = 'n'; break; - case '\\': _d[j++] = '\\'; break; - case '=': _d[j++] = 'e'; break; + case 0: + _d[j++] = '0'; + break; + case 13: + _d[j++] = 'r'; + break; + case 10: + _d[j++] = 'n'; + break; + case '\\': + _d[j++] = '\\'; + break; + case '=': + _d[j++] = 'e'; + break; } if (j == C) { _d[i] = (char)0; diff --git a/node/Hashtable.hpp b/node/Hashtable.hpp index 5a38f29cf..5fc6e0bb7 100644 --- a/node/Hashtable.hpp +++ b/node/Hashtable.hpp @@ -81,8 +81,9 @@ public: return true; } ++_idx; - if (_idx >= _ht->_bc) + if (_idx >= _ht->_bc) { return false; + } _b = _ht->_t[_idx]; } } @@ -102,10 +103,12 @@ public: _bc(bc), _s(0) { - if (!_t) + if (!_t) { throw ZT_EXCEPTION_OUT_OF_MEMORY; - for(unsigned long i=0;i &ht) : @@ -113,10 +116,12 @@ public: _bc(ht._bc), _s(ht._s) { - if (!_t) + if (!_t) { throw ZT_EXCEPTION_OUT_OF_MEMORY; - for(unsigned long i=0;i<_bc;++i) + } + for(unsigned long i=0;i<_bc;++i) { _t[i] = (_Bucket *)0; + } for(unsigned long i=0;i<_bc;++i) { const _Bucket *b = ht._t[i]; while (b) { @@ -234,8 +239,9 @@ public: { _Bucket *b = _t[_hc(k) % _bc]; while (b) { - if (b->k == k) + if (b->k == k) { return &(b->v); + } b = b->next; } return (V *)0; @@ -268,8 +274,9 @@ public: { _Bucket *b = _t[_hc(k) % _bc]; while (b) { - if (b->k == k) + if (b->k == k) { return true; + } b = b->next; } return false; @@ -286,9 +293,11 @@ public: _Bucket *b = _t[bidx]; while (b) { if (b->k == k) { - if (lastb) + if (lastb) { lastb->next = b->next; - else _t[bidx] = b->next; + } else { + _t[bidx] = b->next; + } delete b; --_s; return true; @@ -341,8 +350,9 @@ public: _Bucket *b = _t[bidx]; while (b) { - if (b->k == k) + if (b->k == k) { return b->v; + } b = b->next; } @@ -396,8 +406,9 @@ private: const unsigned long nc = _bc * 2; _Bucket **nt = reinterpret_cast<_Bucket **>(::malloc(sizeof(_Bucket *) * nc)); if (nt) { - for(unsigned long i=0;i @@ -80,8 +81,9 @@ public: _address = id._address; _publicKey = id._publicKey; if (id._privateKey) { - if (!_privateKey) + if (!_privateKey) { _privateKey = new C25519::Private(); + } *_privateKey = *(id._privateKey); } else { delete _privateKey; @@ -144,8 +146,9 @@ public: */ inline C25519::Signature sign(const void *data,unsigned int len) const { - if (_privateKey) + if (_privateKey) { return C25519::sign(*_privateKey,_publicKey,data,len); + } throw ZT_EXCEPTION_PRIVATE_KEY_REQUIRED; } @@ -160,8 +163,9 @@ public: */ inline bool verify(const void *data,unsigned int len,const void *signature,unsigned int siglen) const { - if (siglen != ZT_C25519_SIGNATURE_LEN) + if (siglen != ZT_C25519_SIGNATURE_LEN) { return false; + } return C25519::verify(_publicKey,data,len,signature); } @@ -217,7 +221,9 @@ public: if ((_privateKey)&&(includePrivate)) { b.append((unsigned char)ZT_C25519_PRIVATE_KEY_LEN); b.append(_privateKey->data,ZT_C25519_PRIVATE_KEY_LEN); - } else b.append((unsigned char)0); + } else { + b.append((unsigned char)0); + } } /** @@ -243,16 +249,18 @@ public: _address.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - if (b[p++] != 0) + if (b[p++] != 0) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE; + } memcpy(_publicKey.data,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN); p += ZT_C25519_PUBLIC_KEY_LEN; unsigned int privateKeyLength = (unsigned int)b[p++]; if (privateKeyLength) { - if (privateKeyLength != ZT_C25519_PRIVATE_KEY_LEN) + if (privateKeyLength != ZT_C25519_PRIVATE_KEY_LEN) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } _privateKey = new C25519::Private(); memcpy(_privateKey->data,b.field(p,ZT_C25519_PRIVATE_KEY_LEN),ZT_C25519_PRIVATE_KEY_LEN); p += ZT_C25519_PRIVATE_KEY_LEN; @@ -293,9 +301,11 @@ public: { C25519::Pair pair; pair.pub = _publicKey; - if (_privateKey) + if (_privateKey) { pair.priv = *_privateKey; - else memset(pair.priv.data,0,ZT_C25519_PRIVATE_KEY_LEN); + } else { + memset(pair.priv.data,0,ZT_C25519_PRIVATE_KEY_LEN); + } return pair; } diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 532a99fe0..cc48caeb1 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -89,26 +89,66 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr,int32_t f Metrics::pkt_nop_in++; peer->received(tPtr,_path,hops(),packetId(),payloadLength(),v,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); break; - case Packet::VERB_HELLO: r = _doHELLO(RR,tPtr,true); break; - case Packet::VERB_ACK : r = _doACK(RR,tPtr,peer); break; - case Packet::VERB_QOS_MEASUREMENT: r = _doQOS_MEASUREMENT(RR,tPtr,peer); break; - case Packet::VERB_ERROR: r = _doERROR(RR,tPtr,peer); break; - case Packet::VERB_OK: r = _doOK(RR,tPtr,peer); break; - case Packet::VERB_WHOIS: r = _doWHOIS(RR,tPtr,peer); break; - case Packet::VERB_RENDEZVOUS: r = _doRENDEZVOUS(RR,tPtr,peer); break; - case Packet::VERB_FRAME: r = _doFRAME(RR,tPtr,peer,flowId); break; - case Packet::VERB_EXT_FRAME: r = _doEXT_FRAME(RR,tPtr,peer,flowId); break; - case Packet::VERB_ECHO: r = _doECHO(RR,tPtr,peer); break; - case Packet::VERB_MULTICAST_LIKE: r = _doMULTICAST_LIKE(RR,tPtr,peer); break; - case Packet::VERB_NETWORK_CREDENTIALS: r = _doNETWORK_CREDENTIALS(RR,tPtr,peer); break; - case Packet::VERB_NETWORK_CONFIG_REQUEST: r = _doNETWORK_CONFIG_REQUEST(RR,tPtr,peer); break; - case Packet::VERB_NETWORK_CONFIG: r = _doNETWORK_CONFIG(RR,tPtr,peer); break; - case Packet::VERB_MULTICAST_GATHER: r = _doMULTICAST_GATHER(RR,tPtr,peer); break; - case Packet::VERB_MULTICAST_FRAME: r = _doMULTICAST_FRAME(RR,tPtr,peer); break; - case Packet::VERB_PUSH_DIRECT_PATHS: r = _doPUSH_DIRECT_PATHS(RR,tPtr,peer); break; - case Packet::VERB_USER_MESSAGE: r = _doUSER_MESSAGE(RR,tPtr,peer); break; - case Packet::VERB_REMOTE_TRACE: r = _doREMOTE_TRACE(RR,tPtr,peer); break; - case Packet::VERB_PATH_NEGOTIATION_REQUEST: r = _doPATH_NEGOTIATION_REQUEST(RR,tPtr,peer); break; + case Packet::VERB_HELLO: + r = _doHELLO(RR, tPtr, true); + break; + case Packet::VERB_ACK: + r = _doACK(RR, tPtr, peer); + break; + case Packet::VERB_QOS_MEASUREMENT: + r = _doQOS_MEASUREMENT(RR, tPtr, peer); + break; + case Packet::VERB_ERROR: + r = _doERROR(RR, tPtr, peer); + break; + case Packet::VERB_OK: + r = _doOK(RR, tPtr, peer); + break; + case Packet::VERB_WHOIS: + r = _doWHOIS(RR, tPtr, peer); + break; + case Packet::VERB_RENDEZVOUS: + r = _doRENDEZVOUS(RR, tPtr, peer); + break; + case Packet::VERB_FRAME: + r = _doFRAME(RR, tPtr, peer, flowId); + break; + case Packet::VERB_EXT_FRAME: + r = _doEXT_FRAME(RR, tPtr, peer, flowId); + break; + case Packet::VERB_ECHO: + r = _doECHO(RR, tPtr, peer); + break; + case Packet::VERB_MULTICAST_LIKE: + r = _doMULTICAST_LIKE(RR, tPtr, peer); + break; + case Packet::VERB_NETWORK_CREDENTIALS: + r = _doNETWORK_CREDENTIALS(RR, tPtr, peer); + break; + case Packet::VERB_NETWORK_CONFIG_REQUEST: + r = _doNETWORK_CONFIG_REQUEST(RR, tPtr, peer); + break; + case Packet::VERB_NETWORK_CONFIG: + r = _doNETWORK_CONFIG(RR, tPtr, peer); + break; + case Packet::VERB_MULTICAST_GATHER: + r = _doMULTICAST_GATHER(RR, tPtr, peer); + break; + case Packet::VERB_MULTICAST_FRAME: + r = _doMULTICAST_FRAME(RR, tPtr, peer); + break; + case Packet::VERB_PUSH_DIRECT_PATHS: + r = _doPUSH_DIRECT_PATHS(RR, tPtr, peer); + break; + case Packet::VERB_USER_MESSAGE: + r = _doUSER_MESSAGE(RR, tPtr, peer); + break; + case Packet::VERB_REMOTE_TRACE: + r = _doREMOTE_TRACE(RR, tPtr, peer); + break; + case Packet::VERB_PATH_NEGOTIATION_REQUEST: + r = _doPATH_NEGOTIATION_REQUEST(RR, tPtr, peer); + break; } if (r) { RR->node->statsLogVerb((unsigned int)v,(unsigned int)size()); @@ -146,8 +186,9 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar // Object not found, currently only meaningful from network controllers. if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) { const SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); - if ((network)&&(network->controller() == peer->address())) + if ((network)&&(network->controller() == peer->address())) { network->setNotFound(tPtr); + } } Metrics::pkt_error_obj_not_found_in++; break; @@ -158,8 +199,9 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar // that the queried node does not support acting as a controller. if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) { const SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); - if ((network)&&(network->controller() == peer->address())) + if ((network)&&(network->controller() == peer->address())) { network->setNotFound(tPtr); + } } Metrics::pkt_error_unsupported_op_in++; break; @@ -263,7 +305,8 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar Metrics::pkt_error_authentication_required_in++; } break; - default: break; + default: + break; } peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_ERROR,inRePacketId,inReVerb,false,networkId,ZT_QOS_NO_FLOW); @@ -354,8 +397,9 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool // Identity is different from the one we already have -- address collision // Check rate limits - if (!RR->node->rateGateIdentityVerification(now,_path->address())) + if (!RR->node->rateGateIdentityVerification(now,_path->address())) { return true; + } uint8_t key[ZT_SYMMETRIC_KEY_SIZE]; if (RR->identity.agree(id,key)) { @@ -425,16 +469,19 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool InetAddress externalSurfaceAddress; if (ptr < size()) { ptr += externalSurfaceAddress.deserialize(*this,ptr); - if ((externalSurfaceAddress)&&(hops() == 0)) + if ((externalSurfaceAddress)&&(hops() == 0)) { RR->sa->iam(tPtr,id.address(),_path->localSocket(),_path->address(),externalSurfaceAddress,RR->topology->isUpstream(id),now); + } } // Get primary planet world ID and world timestamp if present uint64_t planetWorldId = 0; uint64_t planetWorldTimestamp = 0; if ((ptr + 16) <= size()) { - planetWorldId = at(ptr); ptr += 8; - planetWorldTimestamp = at(ptr); ptr += 8; + planetWorldId = at(ptr); + ptr += 8; + planetWorldTimestamp = at(ptr); + ptr += 8; } std::vector< std::pair > moonIdsAndTimestamps; @@ -444,10 +491,12 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool // Get moon IDs and timestamps if present if ((ptr + 2) <= size()) { - const unsigned int numMoons = at(ptr); ptr += 2; + const unsigned int numMoons = at(ptr); + ptr += 2; for(unsigned int i=0;i(at(ptr),at(ptr + 8))); + } ptr += 16; } } @@ -506,8 +555,9 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { for(std::vector< std::pair >::const_iterator i(moonIdsAndTimestamps.begin());i!=moonIdsAndTimestamps.end();++i) { if (i->first == m->id()) { - if (m->timestamp() > i->second) + if (m->timestamp() > i->second) { m->serialize(outp,false); + } break; } } @@ -532,8 +582,9 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP const uint64_t inRePacketId = at(ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID); uint64_t networkId = 0; - if (!RR->node->expectingReplyTo(inRePacketId)) + if (!RR->node->expectingReplyTo(inRePacketId)) { return true; + } switch(inReVerb) { @@ -543,19 +594,22 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP const unsigned int vMajor = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION]; const unsigned int vMinor = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION]; const unsigned int vRevision = at(ZT_PROTO_VERB_HELLO__OK__IDX_REVISION); - if (vProto < ZT_PROTO_VERSION_MIN) + if (vProto < ZT_PROTO_VERSION_MIN) { return true; + } InetAddress externalSurfaceAddress; unsigned int ptr = ZT_PROTO_VERB_HELLO__OK__IDX_REVISION + 2; // Get reported external surface address if present - if (ptr < size()) + if (ptr < size()) { ptr += externalSurfaceAddress.deserialize(*this,ptr); + } // Handle planet or moon updates if present if ((ptr + 2) <= size()) { - const unsigned int worldsLen = at(ptr); ptr += 2; + const unsigned int worldsLen = at(ptr); + ptr += 2; if (RR->topology->shouldAcceptWorldUpdateFrom(peer->address())) { const unsigned int endOfWorlds = ptr + worldsLen; while (ptr < endOfWorlds) { @@ -577,8 +631,9 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP peer->setRemoteVersion(vProto,vMajor,vMinor,vRevision); - if ((externalSurfaceAddress)&&(hops() == 0)) + if ((externalSurfaceAddress)&&(hops() == 0)) { RR->sa->iam(tPtr,peer->address(),_path->localSocket(),_path->address(),externalSurfaceAddress,RR->topology->isUpstream(peer->identity()),RR->node->now()); + } } break; case Packet::VERB_WHOIS: @@ -591,8 +646,9 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP case Packet::VERB_NETWORK_CONFIG_REQUEST: { networkId = at(ZT_PROTO_VERB_OK_IDX_PAYLOAD); const SharedPtr network(RR->node->network(networkId)); - if (network) + if (network) { network->handleConfigChunk(tPtr,packetId(),source(),*this,ZT_PROTO_VERB_OK_IDX_PAYLOAD); + } } break; case Packet::VERB_MULTICAST_GATHER: { @@ -617,21 +673,25 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP if ((flags & 0x01) != 0) { // deprecated but still used by older peers CertificateOfMembership com; offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS); - if (com) + if (com) { network->addCredential(tPtr,com); + } } if ((flags & 0x02) != 0) { // OK(MULTICAST_FRAME) includes implicit gather results offset += ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS; - unsigned int totalKnown = at(offset); offset += 4; - unsigned int count = at(offset); offset += 2; + unsigned int totalKnown = at(offset); + offset += 4; + unsigned int count = at(offset); + offset += 2; RR->mc->addMultiple(tPtr,RR->node->now(),networkId,mg,field(offset,count * 5),count,totalKnown); } } } break; - default: break; + default: + break; } peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_OK,inRePacketId,inReVerb,false,networkId,ZT_QOS_NO_FLOW); @@ -705,8 +765,9 @@ bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,void *tPtr,const // Returns true if packet appears valid; pos and proto will be set static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsigned int &pos,unsigned int &proto) { - if (frameLen < 40) + if (frameLen < 40) { return false; + } pos = 40; proto = frameData[6]; while (pos <= frameLen) { @@ -715,8 +776,9 @@ static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsig case 43: // routing case 60: // destination options case 135: // mobility options - if ((pos + 8) > frameLen) + if ((pos + 8) > frameLen) { return false; // invalid! + } proto = frameData[pos]; pos += ((unsigned int)frameData[pos + 1] * 8) + 8; break; @@ -811,8 +873,9 @@ bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,void *tPtr,const Shar const MAC sourceMac(peer->address(),nwid); const unsigned int frameLen = size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD; const uint8_t *const frameData = reinterpret_cast(data()) + ZT_PROTO_VERB_FRAME_IDX_PAYLOAD; - if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),sourceMac,network->mac(),frameData,frameLen,etherType,0) > 0) + if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),sourceMac,network->mac(),frameData,frameLen,etherType,0) > 0) { RR->node->putFrame(tPtr,nwid,network->userPtr(),sourceMac,network->mac(),etherType,0,(const void *)frameData,frameLen); + } } } else { _sendErrorNeedCredentials(RR,tPtr,peer,nwid); @@ -836,8 +899,9 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const if ((flags & 0x01) != 0) { // inline COM with EXT_FRAME is deprecated but still used with old peers CertificateOfMembership com; comLen = com.deserialize(*this,ZT_PROTO_VERB_EXT_FRAME_IDX_COM); - if (com) + if (com) { network->addCredential(tPtr,com); + } } if (!network->gate(tPtr,peer)) { @@ -919,8 +983,9 @@ bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const Share Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); outp.append((unsigned char)Packet::VERB_ECHO); outp.append((uint64_t)pid); - if (size() > ZT_PACKET_IDX_PAYLOAD) + if (size() > ZT_PACKET_IDX_PAYLOAD) { outp.append(reinterpret_cast(data()) + ZT_PACKET_IDX_PAYLOAD,size() - ZT_PACKET_IDX_PAYLOAD); + } outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); @@ -943,13 +1008,16 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,void *tPtr,c if (nwid != lastNwid) { lastNwid = nwid; SharedPtr network(RR->node->network(nwid)); - if (network) + if (network) { authorized = network->gate(tPtr,peer); - if (!authorized) + } + if (!authorized) { authorized = ((RR->topology->amUpstream())||(RR->node->localControllerHasAuthorized(now,nwid,peer->address()))); + } } - if (authorized) + if (authorized) { RR->mc->add(tPtr,now,nwid,MulticastGroup(MAC(field(ptr + 8,6),6),at(ptr + 14)),peer->address()); + } } peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_LIKE,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); @@ -993,11 +1061,13 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *t ++p; // skip trailing 0 after COMs if present if (p < size()) { // older ZeroTier versions do not send capabilities, tags, or revocations - const unsigned int numCapabilities = at(p); p += 2; + const unsigned int numCapabilities = at(p); + p += 2; for(unsigned int i=0;iid() != cap.networkId())) + if ((!network)||(network->id() != cap.networkId())) { network = RR->node->network(cap.networkId()); + } if (network) { switch (network->addCredential(tPtr,cap)) { case Membership::ADD_REJECTED: @@ -1012,13 +1082,17 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *t } } - if (p >= size()) return true; + if (p >= size()) { + return true; + } - const unsigned int numTags = at(p); p += 2; + const unsigned int numTags = at(p); + p += 2; for(unsigned int i=0;iid() != tag.networkId())) + if ((!network)||(network->id() != tag.networkId())) { network = RR->node->network(tag.networkId()); + } if (network) { switch (network->addCredential(tPtr,tag)) { case Membership::ADD_REJECTED: @@ -1033,13 +1107,17 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *t } } - if (p >= size()) return true; + if (p >= size()) { + return true; + } - const unsigned int numRevocations = at(p); p += 2; + const unsigned int numRevocations = at(p); + p += 2; for(unsigned int i=0;iid() != revocation.networkId())) + if ((!network)||(network->id() != revocation.networkId())) { network = RR->node->network(revocation.networkId()); + } if (network) { switch(network->addCredential(tPtr,peer->address(),revocation)) { case Membership::ADD_REJECTED: @@ -1054,13 +1132,17 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *t } } - if (p >= size()) return true; + if (p >= size()) { + return true; + } - const unsigned int numCoos = at(p); p += 2; + const unsigned int numCoos = at(p); + p += 2; for(unsigned int i=0;iid() != coo.networkId())) + if ((!network)||(network->id() != coo.networkId())) { network = RR->node->network(coo.networkId()); + } if (network) { switch(network->addCredential(tPtr,coo)) { case Membership::ADD_REJECTED: @@ -1146,8 +1228,9 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr try { CertificateOfMembership com; com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_GATHER_IDX_COM); - if ((com)&&(network)) + if ((com)&&(network)) { network->addCredential(tPtr,com); + } } catch ( ... ) {} // discard invalid COMs } @@ -1188,8 +1271,9 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, // This is deprecated but may still be sent by old peers CertificateOfMembership com; offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME_IDX_COM); - if (com) + if (com) { network->addCredential(tPtr,com); + } } if (!network->gate(tPtr,peer)) { @@ -1235,8 +1319,9 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, const uint8_t *const frameData = (const uint8_t *)field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME,frameLen); - if ((flags & 0x08)&&(network->config().isMulticastReplicator(RR->identity.address()))) + if ((flags & 0x08)&&(network->config().isMulticastReplicator(RR->identity.address()))) { RR->mc->send(tPtr,RR->node->now(),network,peer->address(),to,from,etherType,frameData,frameLen); + } if (from != MAC(peer->address(),nwid)) { if (network->config().permitsBridging(peer->address())) { @@ -1248,8 +1333,9 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, } } - if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),from,to.mac(),frameData,frameLen,etherType,0) > 0) + if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),from,to.mac(),frameData,frameLen,etherType,0) > 0) { RR->node->putFrame(tPtr,nwid,network->userPtr(),from,to.mac(),etherType,0,(const void *)frameData,frameLen); + } } if (gatherLimit) { @@ -1293,7 +1379,8 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPt while (count--) { // if ptr overflows Buffer will throw unsigned int flags = (*this)[ptr++]; - unsigned int extLen = at(ptr); ptr += 2; + unsigned int extLen = at(ptr); + ptr += 2; ptr += extLen; // unused right now unsigned int addrType = (*this)[ptr++]; unsigned int addrLen = (*this)[ptr++]; diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp index 07c53662f..da1c7294c 100644 --- a/node/InetAddress.cpp +++ b/node/InetAddress.cpp @@ -33,46 +33,80 @@ InetAddress::IpScope InetAddress::ipScope() const case AF_INET: { const uint32_t ip = Utils::ntoh((uint32_t)reinterpret_cast(this)->sin_addr.s_addr); switch(ip >> 24) { - case 0x00: return IP_SCOPE_NONE; // 0.0.0.0/8 (reserved, never used) - case 0x06: return IP_SCOPE_PSEUDOPRIVATE; // 6.0.0.0/8 (US Army) - case 0x0a: return IP_SCOPE_PRIVATE; // 10.0.0.0/8 - case 0x0b: return IP_SCOPE_PSEUDOPRIVATE; // 11.0.0.0/8 (US DoD) - case 0x15: return IP_SCOPE_PSEUDOPRIVATE; // 21.0.0.0/8 (US DDN-RVN) - case 0x16: return IP_SCOPE_PSEUDOPRIVATE; // 22.0.0.0/8 (US DISA) - case 0x19: return IP_SCOPE_PSEUDOPRIVATE; // 25.0.0.0/8 (UK Ministry of Defense) - case 0x1a: return IP_SCOPE_PSEUDOPRIVATE; // 26.0.0.0/8 (US DISA) - case 0x1c: return IP_SCOPE_PSEUDOPRIVATE; // 28.0.0.0/8 (US DSI-North) - case 0x1d: return IP_SCOPE_PSEUDOPRIVATE; // 29.0.0.0/8 (US DISA) - case 0x1e: return IP_SCOPE_PSEUDOPRIVATE; // 30.0.0.0/8 (US DISA) - case 0x33: return IP_SCOPE_PSEUDOPRIVATE; // 51.0.0.0/8 (UK Department of Social Security) - case 0x37: return IP_SCOPE_PSEUDOPRIVATE; // 55.0.0.0/8 (US DoD) - case 0x38: return IP_SCOPE_PSEUDOPRIVATE; // 56.0.0.0/8 (US Postal Service) + case 0x00: + return IP_SCOPE_NONE; // 0.0.0.0/8 (reserved, never used) + case 0x06: + return IP_SCOPE_PSEUDOPRIVATE; // 6.0.0.0/8 (US Army) + case 0x0a: + return IP_SCOPE_PRIVATE; // 10.0.0.0/8 + case 0x0b: + return IP_SCOPE_PSEUDOPRIVATE; // 11.0.0.0/8 (US DoD) + case 0x15: + return IP_SCOPE_PSEUDOPRIVATE; // 21.0.0.0/8 (US DDN-RVN) + case 0x16: + return IP_SCOPE_PSEUDOPRIVATE; // 22.0.0.0/8 (US DISA) + case 0x19: + return IP_SCOPE_PSEUDOPRIVATE; // 25.0.0.0/8 (UK Ministry of Defense) + case 0x1a: + return IP_SCOPE_PSEUDOPRIVATE; // 26.0.0.0/8 (US DISA) + case 0x1c: + return IP_SCOPE_PSEUDOPRIVATE; // 28.0.0.0/8 (US DSI-North) + case 0x1d: + return IP_SCOPE_PSEUDOPRIVATE; // 29.0.0.0/8 (US DISA) + case 0x1e: + return IP_SCOPE_PSEUDOPRIVATE; // 30.0.0.0/8 (US DISA) + case 0x33: + return IP_SCOPE_PSEUDOPRIVATE; // 51.0.0.0/8 (UK Department of Social Security) + case 0x37: + return IP_SCOPE_PSEUDOPRIVATE; // 55.0.0.0/8 (US DoD) + case 0x38: + return IP_SCOPE_PSEUDOPRIVATE; // 56.0.0.0/8 (US Postal Service) case 0x64: - if ((ip & 0xffc00000) == 0x64400000) return IP_SCOPE_PRIVATE; // 100.64.0.0/10 + if ((ip & 0xffc00000) == 0x64400000) { + return IP_SCOPE_PRIVATE; // 100.64.0.0/10 + } break; - case 0x7f: return IP_SCOPE_LOOPBACK; // 127.0.0.0/8 + case 0x7f: + return IP_SCOPE_LOOPBACK; // 127.0.0.0/8 case 0xa9: - if ((ip & 0xffff0000) == 0xa9fe0000) return IP_SCOPE_LINK_LOCAL; // 169.254.0.0/16 + if ((ip & 0xffff0000) == 0xa9fe0000) { + return IP_SCOPE_LINK_LOCAL; // 169.254.0.0/16 + } break; case 0xac: - if ((ip & 0xfff00000) == 0xac100000) return IP_SCOPE_PRIVATE; // 172.16.0.0/12 + if ((ip & 0xfff00000) == 0xac100000) { + return IP_SCOPE_PRIVATE; // 172.16.0.0/12 + } break; case 0xc0: - if ((ip & 0xffff0000) == 0xc0a80000) return IP_SCOPE_PRIVATE; // 192.168.0.0/16 - if ((ip & 0xffffff00) == 0xc0000200) return IP_SCOPE_PRIVATE; // 192.0.2.0/24 + if ((ip & 0xffff0000) == 0xc0a80000) { + return IP_SCOPE_PRIVATE; // 192.168.0.0/16 + } + if ((ip & 0xffffff00) == 0xc0000200) { + return IP_SCOPE_PRIVATE; // 192.0.2.0/24 + } break; case 0xc6: - if ((ip & 0xfffe0000) == 0xc6120000) return IP_SCOPE_PRIVATE; // 198.18.0.0/15 - if ((ip & 0xffffff00) == 0xc6336400) return IP_SCOPE_PRIVATE; // 198.51.100.0/24 + if ((ip & 0xfffe0000) == 0xc6120000) { + return IP_SCOPE_PRIVATE; // 198.18.0.0/15 + } + if ((ip & 0xffffff00) == 0xc6336400) { + return IP_SCOPE_PRIVATE; // 198.51.100.0/24 + } break; case 0xcb: - if ((ip & 0xffffff00) == 0xcb007100) return IP_SCOPE_PRIVATE; // 203.0.113.0/24 + if ((ip & 0xffffff00) == 0xcb007100) { + return IP_SCOPE_PRIVATE; // 203.0.113.0/24 + } break; - case 0xff: return IP_SCOPE_NONE; // 255.0.0.0/8 (broadcast, or unused/unusable) + case 0xff: + return IP_SCOPE_NONE; // 255.0.0.0/8 (broadcast, or unused/unusable) } switch(ip >> 28) { - case 0xe: return IP_SCOPE_MULTICAST; // 224.0.0.0/4 - case 0xf: return IP_SCOPE_PSEUDOPRIVATE; // 240.0.0.0/4 ("reserved," usually unusable) + case 0xe: + return IP_SCOPE_MULTICAST; // 224.0.0.0/4 + case 0xf: + return IP_SCOPE_PSEUDOPRIVATE; // 240.0.0.0/4 ("reserved," usually unusable) } return IP_SCOPE_GLOBAL; } break; @@ -80,21 +114,35 @@ InetAddress::IpScope InetAddress::ipScope() const case AF_INET6: { const unsigned char *ip = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); if ((ip[0] & 0xf0) == 0xf0) { - if (ip[0] == 0xff) return IP_SCOPE_MULTICAST; // ff00::/8 + if (ip[0] == 0xff) { + return IP_SCOPE_MULTICAST; // ff00::/8 + } if ((ip[0] == 0xfe)&&((ip[1] & 0xc0) == 0x80)) { unsigned int k = 2; - while ((!ip[k])&&(k < 15)) ++k; - if ((k == 15)&&(ip[15] == 0x01)) - return IP_SCOPE_LOOPBACK; // fe80::1/128 - else return IP_SCOPE_LINK_LOCAL; // fe80::/10 + while ((!ip[k])&&(k < 15)) { + ++k; + } + if ((k == 15)&&(ip[15] == 0x01)) { + return IP_SCOPE_LOOPBACK; // fe80::1/128 + } else { + return IP_SCOPE_LINK_LOCAL; // fe80::/10 + } + } + if ((ip[0] & 0xfe) == 0xfc) { + return IP_SCOPE_PRIVATE; // fc00::/7 } - if ((ip[0] & 0xfe) == 0xfc) return IP_SCOPE_PRIVATE; // fc00::/7 } unsigned int k = 0; - while ((!ip[k])&&(k < 15)) ++k; + while ((!ip[k])&&(k < 15)) { + ++k; + } if (k == 15) { // all 0's except last byte - if (ip[15] == 0x01) return IP_SCOPE_LOOPBACK; // ::1/128 - if (ip[15] == 0x00) return IP_SCOPE_NONE; // ::/128 + if (ip[15] == 0x01) { + return IP_SCOPE_LOOPBACK; // ::1/128 + } + if (ip[15] == 0x00) { + return IP_SCOPE_NONE; // ::/128 + } } return IP_SCOPE_GLOBAL; } break; @@ -124,7 +172,9 @@ char *InetAddress::toString(char buf[64]) const { char *p = toIpString(buf); if (*p) { - while (*p) ++p; + while (*p) { + ++p; + } *(p++) = '/'; Utils::decimal(port(),p); } @@ -160,14 +210,17 @@ bool InetAddress::fromString(const char *ipSlashPort) memset(this,0,sizeof(InetAddress)); - if (!*ipSlashPort) + if (!*ipSlashPort) { return true; - if (!Utils::scopy(buf,sizeof(buf),ipSlashPort)) + } + if (!Utils::scopy(buf,sizeof(buf),ipSlashPort)) { return false; + } char *portAt = buf; - while ((*portAt)&&(*portAt != '/')) + while ((*portAt)&&(*portAt != '/')) { ++portAt; + } unsigned int port = 0; if (*portAt) { *(portAt++) = (char)0; @@ -255,8 +308,9 @@ bool InetAddress::isEqualPrefix(const InetAddress &addr) const const uint8_t *a = reinterpret_cast(reinterpret_cast(&addr)->sin6_addr.s6_addr); const uint8_t *b = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); for(unsigned int i=0;i<16;++i) { - if ((a[i] & m[i]) != (b[i] & n[i])) + if ((a[i] & m[i]) != (b[i] & n[i])) { return false; + } } return true; } @@ -271,8 +325,9 @@ bool InetAddress::containsAddress(const InetAddress &addr) const switch(ss_family) { case AF_INET: { const unsigned int bits = netmaskBits(); - if (bits == 0) + if (bits == 0) { return true; + } return ( (Utils::ntoh((uint32_t)reinterpret_cast(&addr)->sin_addr.s_addr) >> (32 - bits)) == (Utils::ntoh((uint32_t)reinterpret_cast(this)->sin_addr.s_addr) >> (32 - bits)) ); } case AF_INET6: { @@ -281,8 +336,9 @@ bool InetAddress::containsAddress(const InetAddress &addr) const const uint8_t *a = reinterpret_cast(reinterpret_cast(&addr)->sin6_addr.s6_addr); const uint8_t *b = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); for(unsigned int i=0;i<16;++i) { - if ((a[i] & m[i]) != b[i]) + if ((a[i] & m[i]) != b[i]) { return false; + } } return true; } @@ -296,26 +352,32 @@ bool InetAddress::isNetwork() const switch(ss_family) { case AF_INET: { unsigned int bits = netmaskBits(); - if (bits <= 0) + if (bits <= 0) { return false; - if (bits >= 32) + } + if (bits >= 32) { return false; + } uint32_t ip = Utils::ntoh((uint32_t)reinterpret_cast(this)->sin_addr.s_addr); return ((ip & (0xffffffff >> bits)) == 0); } case AF_INET6: { unsigned int bits = netmaskBits(); - if (bits <= 0) + if (bits <= 0) { return false; - if (bits >= 128) + } + if (bits >= 128) { return false; + } const unsigned char *ip = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); unsigned int p = bits / 8; - if ((ip[p++] & (0xff >> (bits % 8))) != 0) + if ((ip[p++] & (0xff >> (bits % 8))) != 0) { return false; + } while (p < 16) { - if (ip[p++]) + if (ip[p++]) { return false; + } } return true; } @@ -348,30 +410,32 @@ bool InetAddress::operator==(const InetAddress &a) const bool InetAddress::operator<(const InetAddress &a) const { - if (ss_family < a.ss_family) + if (ss_family < a.ss_family) { return true; - else if (ss_family == a.ss_family) { + } else if (ss_family == a.ss_family) { switch(ss_family) { case AF_INET: - if (reinterpret_cast(this)->sin_port < reinterpret_cast(&a)->sin_port) + if (reinterpret_cast(this)->sin_port < reinterpret_cast(&a)->sin_port) { return true; - else if (reinterpret_cast(this)->sin_port == reinterpret_cast(&a)->sin_port) { - if (reinterpret_cast(this)->sin_addr.s_addr < reinterpret_cast(&a)->sin_addr.s_addr) + } else if (reinterpret_cast(this)->sin_port == reinterpret_cast(&a)->sin_port) { + if (reinterpret_cast(this)->sin_addr.s_addr < reinterpret_cast(&a)->sin_addr.s_addr) { return true; + } } break; case AF_INET6: - if (reinterpret_cast(this)->sin6_port < reinterpret_cast(&a)->sin6_port) + if (reinterpret_cast(this)->sin6_port < reinterpret_cast(&a)->sin6_port) { return true; - else if (reinterpret_cast(this)->sin6_port == reinterpret_cast(&a)->sin6_port) { - if (reinterpret_cast(this)->sin6_flowinfo < reinterpret_cast(&a)->sin6_flowinfo) + } else if (reinterpret_cast(this)->sin6_port == reinterpret_cast(&a)->sin6_port) { + if (reinterpret_cast(this)->sin6_flowinfo < reinterpret_cast(&a)->sin6_flowinfo) { return true; - else if (reinterpret_cast(this)->sin6_flowinfo == reinterpret_cast(&a)->sin6_flowinfo) { - if (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) < 0) + } else if (reinterpret_cast(this)->sin6_flowinfo == reinterpret_cast(&a)->sin6_flowinfo) { + if (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) < 0) { return true; - else if (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) == 0) { - if (reinterpret_cast(this)->sin6_scope_id < reinterpret_cast(&a)->sin6_scope_id) + } else if (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) == 0) { + if (reinterpret_cast(this)->sin6_scope_id < reinterpret_cast(&a)->sin6_scope_id) { return true; + } } } } diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp index a9a35dd20..4a3dd80cb 100644 --- a/node/InetAddress.hpp +++ b/node/InetAddress.hpp @@ -94,29 +94,33 @@ struct InetAddress : public sockaddr_storage inline InetAddress &operator=(const InetAddress &a) { - if (&a != this) + if (&a != this) { memcpy(this,&a,sizeof(InetAddress)); + } return *this; } inline InetAddress &operator=(const InetAddress *a) { - if (a != this) + if (a != this) { memcpy(this,a,sizeof(InetAddress)); + } return *this; } inline InetAddress &operator=(const struct sockaddr_storage &ss) { - if (reinterpret_cast(&ss) != this) + if (reinterpret_cast(&ss) != this) { memcpy(this,&ss,sizeof(InetAddress)); + } return *this; } inline InetAddress &operator=(const struct sockaddr_storage *ss) { - if (reinterpret_cast(ss) != this) + if (reinterpret_cast(ss) != this) { memcpy(this,ss,sizeof(InetAddress)); + } return *this; } @@ -230,8 +234,9 @@ struct InetAddress : public sockaddr_storage case AF_INET6: const uint8_t *ipb = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); for(int i=0;i<16;++i) { - if (ipb[i]) + if (ipb[i]) { return false; + } } return (reinterpret_cast(this)->sin6_port == 0); } @@ -260,9 +265,12 @@ struct InetAddress : public sockaddr_storage inline unsigned int port() const { switch(ss_family) { - case AF_INET: return Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin_port)); - case AF_INET6: return Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin6_port)); - default: return 0; + case AF_INET: + return Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin_port)); + case AF_INET6: + return Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin6_port)); + default: + return 0; } } @@ -284,8 +292,10 @@ struct InetAddress : public sockaddr_storage { const unsigned int n = port(); switch(ss_family) { - case AF_INET: return (n <= 32); - case AF_INET6: return (n <= 128); + case AF_INET: + return (n <= 32); + case AF_INET6: + return (n <= 128); } return false; } @@ -356,9 +366,12 @@ struct InetAddress : public sockaddr_storage inline const void *rawIpData() const { switch(ss_family) { - case AF_INET: return (const void *)&(reinterpret_cast(this)->sin_addr.s_addr); - case AF_INET6: return (const void *)(reinterpret_cast(this)->sin6_addr.s6_addr); - default: return 0; + case AF_INET: + return (const void *)&(reinterpret_cast(this)->sin_addr.s_addr); + case AF_INET6: + return (const void *)(reinterpret_cast(this)->sin6_addr.s6_addr); + default: + return 0; } } @@ -390,10 +403,12 @@ struct InetAddress : public sockaddr_storage inline bool ipsEqual(const InetAddress &a) const { if (ss_family == a.ss_family) { - if (ss_family == AF_INET) + if (ss_family == AF_INET) { return (reinterpret_cast(this)->sin_addr.s_addr == reinterpret_cast(&a)->sin_addr.s_addr); - if (ss_family == AF_INET6) + } + if (ss_family == AF_INET6) { return (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) == 0); + } return (memcmp(this,&a,sizeof(InetAddress)) == 0); } return false; @@ -410,10 +425,12 @@ struct InetAddress : public sockaddr_storage inline bool ipsEqual2(const InetAddress &a) const { if (ss_family == a.ss_family) { - if (ss_family == AF_INET) + if (ss_family == AF_INET) { return (reinterpret_cast(this)->sin_addr.s_addr == reinterpret_cast(&a)->sin_addr.s_addr); - if (ss_family == AF_INET6) - return (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,8) == 0); + } + if (ss_family == AF_INET6) { + return (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr, reinterpret_cast(&a)->sin6_addr.s6_addr, 8) == 0); + } return (memcmp(this,&a,sizeof(InetAddress)) == 0); } return false; @@ -426,14 +443,16 @@ struct InetAddress : public sockaddr_storage } else if (ss_family == AF_INET6) { unsigned long tmp = reinterpret_cast(this)->sin6_port; const uint8_t *a = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); - for(long i=0;i<16;++i) + for(long i=0;i<16;++i) { reinterpret_cast(&tmp)[i % sizeof(tmp)] ^= a[i]; + } return tmp; } else { unsigned long tmp = reinterpret_cast(this)->sin6_port; const uint8_t *a = reinterpret_cast(this); - for(long i=0;i<(long)sizeof(InetAddress);++i) + for(long i=0;i<(long)sizeof(InetAddress);++i) { reinterpret_cast(&tmp)[i % sizeof(tmp)] ^= a[i]; + } return tmp; } } @@ -470,8 +489,9 @@ struct InetAddress : public sockaddr_storage while ((ip0 >> 31) == (ip1 >> 31)) { ip0 <<= 1; ip1 <<= 1; - if (++c == 32) + if (++c == 32) { break; + } } } break; case AF_INET6: { @@ -485,8 +505,9 @@ struct InetAddress : public sockaddr_storage uint8_t ip1b = ip1[i]; uint8_t bit = 0x80; while (bit != 0) { - if ((ip0b & bit) != (ip1b & bit)) + if ((ip0b & bit) != (ip1b & bit)) { break; + } ++c; bit >>= 1; } @@ -512,11 +533,16 @@ struct InetAddress : public sockaddr_storage break; case AF_INET6: { const uint8_t *ip = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); - h = ((unsigned long)ip[0]); h <<= 1; - h += ((unsigned long)ip[1]); h <<= 1; - h += ((unsigned long)ip[2]); h <<= 1; - h += ((unsigned long)ip[3]); h <<= 1; - h += ((unsigned long)ip[4]); h <<= 1; + h = ((unsigned long)ip[0]); + h <<= 1; + h += ((unsigned long)ip[1]); + h <<= 1; + h += ((unsigned long)ip[2]); + h <<= 1; + h += ((unsigned long)ip[3]); + h <<= 1; + h += ((unsigned long)ip[4]); + h <<= 1; h += ((unsigned long)ip[5]); } break; } @@ -570,13 +596,17 @@ struct InetAddress : public sockaddr_storage return (unsigned int)(b.template at(p) + 3); // other addresses begin with 16-bit non-inclusive length case 0x04: ss_family = AF_INET; - memcpy(&(reinterpret_cast(this)->sin_addr.s_addr),b.field(p,4),4); p += 4; - reinterpret_cast(this)->sin_port = Utils::hton(b.template at(p)); p += 2; + memcpy(&(reinterpret_cast(this)->sin_addr.s_addr),b.field(p,4),4); + p += 4; + reinterpret_cast(this)->sin_port = Utils::hton(b.template at(p)); + p += 2; break; case 0x06: ss_family = AF_INET6; - memcpy(reinterpret_cast(this)->sin6_addr.s6_addr,b.field(p,16),16); p += 16; - reinterpret_cast(this)->sin_port = Utils::hton(b.template at(p)); p += 2; + memcpy(reinterpret_cast(this)->sin6_addr.s6_addr,b.field(p,16),16); + p += 16; + reinterpret_cast(this)->sin_port = Utils::hton(b.template at(p)); + p += 2; break; default: throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_BAD_ENCODING; diff --git a/node/MAC.hpp b/node/MAC.hpp index de72a1aed..ca4eacf6f 100644 --- a/node/MAC.hpp +++ b/node/MAC.hpp @@ -71,11 +71,16 @@ public: return; } const unsigned char *b = (const unsigned char *)bits; - _m = ((((uint64_t)*b) & 0xff) << 40); ++b; - _m |= ((((uint64_t)*b) & 0xff) << 32); ++b; - _m |= ((((uint64_t)*b) & 0xff) << 24); ++b; - _m |= ((((uint64_t)*b) & 0xff) << 16); ++b; - _m |= ((((uint64_t)*b) & 0xff) << 8); ++b; + _m = ((((uint64_t)*b) & 0xff) << 40); + ++b; + _m |= ((((uint64_t)*b) & 0xff) << 32); + ++b; + _m |= ((((uint64_t)*b) & 0xff) << 24); + ++b; + _m |= ((((uint64_t)*b) & 0xff) << 16); + ++b; + _m |= ((((uint64_t)*b) & 0xff) << 8); + ++b; _m |= (((uint64_t)*b) & 0xff); } @@ -85,8 +90,9 @@ public: */ inline void copyTo(void *buf,unsigned int len) const { - if (len < 6) + if (len < 6) { return; + } unsigned char *b = (unsigned char *)buf; *(b++) = (unsigned char)((_m >> 40) & 0xff); *(b++) = (unsigned char)((_m >> 32) & 0xff); diff --git a/node/Membership.cpp b/node/Membership.cpp index 8ddd61f09..26a89a271 100644 --- a/node/Membership.cpp +++ b/node/Membership.cpp @@ -39,18 +39,21 @@ void Membership::pushCredentials(const RuntimeEnvironment *RR,void *tPtr,const i { const Capability *sendCaps[ZT_MAX_NETWORK_CAPABILITIES]; unsigned int sendCapCount = 0; - for(unsigned int c=0;ct->credentialRejected(tPtr,com,"old"); return ADD_REJECTED; } - if (_com == com) + if (_com == com) { return ADD_ACCEPTED_REDUNDANT; + } switch(com.verify(RR,tPtr)) { default: @@ -141,8 +145,9 @@ static Membership::AddCredentialResult _addCredImpl(Hashtable &remot RR->t->credentialRejected(tPtr,cred,"old"); return Membership::ADD_REJECTED; } - if (*rc == cred) + if (*rc == cred) { return Membership::ADD_ACCEPTED_REDUNDANT; + } } const int64_t *const rt = revocations.get(Membership::credentialKey(C::credentialType(),cred.id())); @@ -156,8 +161,9 @@ static Membership::AddCredentialResult _addCredImpl(Hashtable &remot RR->t->credentialRejected(tPtr,cred,"invalid"); return Membership::ADD_REJECTED; case 0: - if (!rc) + if (!rc) { rc = &(remoteCreds[cred.id()]); + } *rc = cred; return Membership::ADD_ACCEPTED_NEW; case 1: diff --git a/node/Membership.hpp b/node/Membership.hpp index 21561a18e..35b341fd4 100644 --- a/node/Membership.hpp +++ b/node/Membership.hpp @@ -115,8 +115,9 @@ public: CertificateOfOwnership *v = (CertificateOfOwnership *)0; Hashtable< uint32_t,CertificateOfOwnership >::Iterator i(*(const_cast< Hashtable< uint32_t,CertificateOfOwnership> *>(&_remoteCoos))); while (i.next(k,v)) { - if (_isCredentialTimestampValid(nconf,*v)&&(v->owns(r))) + if (_isCredentialTimestampValid(nconf,*v)&&(v->owns(r))) { return true; + } } return _isV6NDPEmulated(nconf,r); } @@ -187,8 +188,9 @@ private: break; } } - if (prefixMatches) + if (prefixMatches) { return true; + } break; } } @@ -203,8 +205,9 @@ private: break; } } - if (prefixMatches) + if (prefixMatches) { return true; + } break; } } @@ -230,8 +233,9 @@ private: C *v = (C *)0; typename Hashtable::Iterator i(remoteCreds); while (i.next(k,v)) { - if (!_isCredentialTimestampValid(nconf,*v)) + if (!_isCredentialTimestampValid(nconf,*v)) { remoteCreds.erase(*k); + } } } @@ -271,8 +275,9 @@ public: inline Capability *next() { while (_hti.next(_k,_c)) { - if (_m._isCredentialTimestampValid(_nconf,*_c)) + if (_m._isCredentialTimestampValid(_nconf,*_c)) { return _c; + } } return (Capability *)0; } diff --git a/node/MulticastGroup.hpp b/node/MulticastGroup.hpp index 2a68d0ab9..e9ab9c07f 100644 --- a/node/MulticastGroup.hpp +++ b/node/MulticastGroup.hpp @@ -92,10 +92,11 @@ public: inline bool operator!=(const MulticastGroup &g) const { return ((_mac != g._mac)||(_adi != g._adi)); } inline bool operator<(const MulticastGroup &g) const { - if (_mac < g._mac) + if (_mac < g._mac) { return true; - else if (_mac == g._mac) + } else if (_mac == g._mac) { return (_adi < g._adi); + } return false; } inline bool operator>(const MulticastGroup &g) const { return (g < *this); } diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp index d93b2ae10..cc26cddda 100644 --- a/node/Multicaster.cpp +++ b/node/Multicaster.cpp @@ -69,10 +69,11 @@ unsigned int Multicaster::gather(const Address &queryingPeer,uint64_t nwid,const unsigned int added = 0,i,k,rptr,totalKnown = 0; uint64_t a,picked[(ZT_PROTO_MAX_PACKET_LENGTH / 5) + 2]; - if (!limit) + if (!limit) { return 0; - else if (limit > 0xffff) + } else if (limit > 0xffff) { limit = 0xffff; + } const unsigned int totalAt = appendTo.size(); appendTo.addSize(4); // sizeof(uint32_t) @@ -133,12 +134,14 @@ std::vector
Multicaster::getMembers(uint64_t nwid,const MulticastGroup std::vector
ls; Mutex::Lock _l(_groups_m); const MulticastGroupStatus *s = _groups.get(Multicaster::Key(nwid,mg)); - if (!s) + if (!s) { return ls; + } for(std::vector::const_reverse_iterator m(s->members.rbegin());m!=s->members.rend();++m) { ls.push_back(m->address); - if (ls.size() >= limit) + if (ls.size() >= limit) { break; + } } return ls; } @@ -193,7 +196,9 @@ void Multicaster::send( outp.append((uint32_t)mg.adi()); outp.append((uint16_t)etherType); outp.append(data,len); - if (!network->config().disableCompression()) outp.compress(); + if (!network->config().disableCompression()) { + outp.compress(); + } outp.armor(bestMulticastReplicator->key(),true,bestMulticastReplicator->aesKeysIfSupported()); bestMulticastReplicatorPath->send(RR,tPtr,outp.data(),outp.size(),now); return; @@ -208,12 +213,14 @@ void Multicaster::send( if (!gs.members.empty()) { // Allocate a memory buffer if group is monstrous - if (gs.members.size() > (sizeof(idxbuf) / sizeof(unsigned long))) + if (gs.members.size() > (sizeof(idxbuf) / sizeof(unsigned long))) { indexes = new unsigned long[gs.members.size()]; + } // Generate a random permutation of member indexes - for(unsigned long i=0;i0;--i) { unsigned long j = (unsigned long)RR->node->prng() % (i + 1); unsigned long tmp = indexes[j]; @@ -248,8 +255,9 @@ void Multicaster::send( for(unsigned int i=0;iidentity.address())&&(activeBridges[i] != origin)) { out.sendOnly(RR,tPtr,activeBridges[i]); // optimization: don't use dedup log if it's a one-pass send - if (++count >= limit) + if (++count >= limit) { break; + } } } @@ -276,16 +284,18 @@ void Multicaster::send( unsigned int numExplicitGatherPeers = 0; SharedPtr bestRoot(RR->topology->getUpstreamPeer()); - if (bestRoot) + if (bestRoot) { explicitGatherPeers[numExplicitGatherPeers++] = bestRoot->address(); + } explicitGatherPeers[numExplicitGatherPeers++] = network->controller(); Address ac[ZT_MAX_NETWORK_SPECIALISTS]; const unsigned int accnt = network->config().alwaysContactAddresses(ac); unsigned int shuffled[ZT_MAX_NETWORK_SPECIALISTS]; - for(unsigned int i=0;i>1;inode->prng(); const unsigned int x1 = shuffled[(unsigned int)x % accnt]; @@ -296,16 +306,18 @@ void Multicaster::send( } for(unsigned int i=0;i anchors(network->config().anchors()); for(std::vector
::const_iterator a(anchors.begin());a!=anchors.end();++a) { if (*a != RR->identity.address()) { explicitGatherPeers[numExplicitGatherPeers++] = *a; - if (numExplicitGatherPeers == 16) + if (numExplicitGatherPeers == 16) { break; + } } } @@ -317,8 +329,9 @@ void Multicaster::send( mg.mac().appendTo(outp); outp.append((uint32_t)mg.adi()); outp.append((uint32_t)gatherLimit); - if (com) + if (com) { com->serialize(outp); + } RR->node->expectReplyTo(outp.packetId()); RR->sw->send(tPtr,outp,true); } @@ -340,16 +353,18 @@ void Multicaster::send( data, len); - if (origin) + if (origin) { out.logAsSent(origin); + } unsigned int count = 0; for(unsigned int i=0;iidentity.address()) { out.sendAndLog(RR,tPtr,activeBridges[i]); - if (++count >= limit) + if (++count >= limit) { break; + } } } @@ -365,8 +380,9 @@ void Multicaster::send( } catch ( ... ) {} // this is a sanity check to catch any failures and make sure indexes[] still gets deleted // Free allocated memory buffer if any - if (indexes != idxbuf) + if (indexes != idxbuf) { delete [] indexes; + } } void Multicaster::clean(int64_t now) @@ -377,9 +393,11 @@ void Multicaster::clean(int64_t now) Hashtable::Iterator mm(_groups); while (mm.next(k,s)) { for(std::list::iterator tx(s->txQueue.begin());tx!=s->txQueue.end();) { - if ((tx->expired(now))||(tx->atLimit())) + if ((tx->expired(now))||(tx->atLimit())) { s->txQueue.erase(tx++); - else ++tx; + } else { + ++tx; + } } unsigned long count = 0; @@ -411,8 +429,9 @@ void Multicaster::_add(void *tPtr,int64_t now,uint64_t nwid,const MulticastGroup // assumes _groups_m is locked // Do not add self -- even if someone else returns it - if (member == RR->identity.address()) + if (member == RR->identity.address()) { return; + } std::vector::iterator m(std::lower_bound(gs.members.begin(),gs.members.end(),member)); if (m != gs.members.end()) { @@ -426,13 +445,15 @@ void Multicaster::_add(void *tPtr,int64_t now,uint64_t nwid,const MulticastGroup } for(std::list::iterator tx(gs.txQueue.begin());tx!=gs.txQueue.end();) { - if (tx->atLimit()) + if (tx->atLimit()) { gs.txQueue.erase(tx++); - else { + } else { tx->sendIfNew(RR,tPtr,member); - if (tx->atLimit()) + if (tx->atLimit()) { gs.txQueue.erase(tx++); - else ++tx; + } else { + ++tx; + } } } } diff --git a/node/Network.cpp b/node/Network.cpp index b03f4b3d0..80858126a 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -42,8 +42,9 @@ namespace { // Returns true if packet appears valid; pos and proto will be set static inline bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsigned int &pos,unsigned int &proto) { - if (frameLen < 40) + if (frameLen < 40) { return false; + } pos = 40; proto = frameData[6]; while (pos <= frameLen) { @@ -52,8 +53,9 @@ static inline bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLe case 43: // routing case 60: // destination options case 135: // mobility options - if ((pos + 8) > frameLen) + if ((pos + 8) > frameLen) { return false; // invalid! + } proto = frameData[pos]; pos += ((unsigned int)frameData[pos + 1] * 8) + 8; break; @@ -165,8 +167,9 @@ static _doZtFilterResult _doZtFilter( case ZT_NETWORK_RULE_ACTION_TEE: case ZT_NETWORK_RULE_ACTION_WATCH: case ZT_NETWORK_RULE_ACTION_REDIRECT: - if (RR->identity.address() == rules[rn].v.fwd.address) + if (RR->identity.address() == rules[rn].v.fwd.address) { superAccept = true; + } break; default: break; @@ -342,7 +345,9 @@ static _doZtFilterResult _doZtFilter( case 0x84: // SCTP case 0x88: // UDPLite if (frameLen > (pos + 4)) { - if (rt == ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE) pos += 2; + if (rt == ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE) { + pos += 2; + } p = (int)frameData[pos++] << 8; p |= (int)frameData[pos]; } @@ -358,8 +363,12 @@ static _doZtFilterResult _doZtFilter( break; case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: { uint64_t cf = (inbound) ? ZT_RULE_PACKET_CHARACTERISTICS_INBOUND : 0ULL; - if (macDest.isMulticast()) cf |= ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST; - if (macDest.isBroadcast()) cf |= ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST; + if (macDest.isMulticast()) { + cf |= ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST; + } + if (macDest.isBroadcast()) { + cf |= ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST; + } if (ownershipVerificationMask == 1) { ownershipVerificationMask = 0; InetAddress src; @@ -386,17 +395,21 @@ static _doZtFilterResult _doZtFilter( } if (inbound) { if (membership) { - if ((src)&&(membership->hasCertificateOfOwnershipFor(nconf,src))) + if ((src)&&(membership->hasCertificateOfOwnershipFor(nconf,src))) { ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED; - if (membership->hasCertificateOfOwnershipFor(nconf,macSource)) + } + if (membership->hasCertificateOfOwnershipFor(nconf,macSource)) { ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED; + } } } else { for(unsigned int i=0;i> 7) & 1)); - else thisSetMatches &= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1)); + } else { + thisSetMatches &= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1)); + } } return DOZTFILTER_NO_MATCH; @@ -552,15 +567,17 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u _netconfFailure(NETCONF_FAILURE_NONE), _portError(0) { - for(int i=0;isetConfiguration(tPtr,*nconf,false); _lastConfigUpdate = 0; // still want to re-request since it's likely outdated } else { uint64_t tmp[2]; - tmp[0] = nwid; tmp[1] = 0; + tmp[0] = nwid; + tmp[1] = 0; bool got = false; Dictionary *dict = new Dictionary(); @@ -580,8 +597,9 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u } catch ( ... ) {} delete dict; - if (!got) + if (!got) { RR->node->stateObjectPut(tPtr,ZT_STATE_OBJECT_NETWORK_CONFIG,tmp,"\n",1); + } } if (!_portInitialized) { @@ -664,14 +682,16 @@ bool Network::filterOutgoingPacket( break; } - if (accept) + if (accept) { break; + } } } break; case DOZTFILTER_DROP: - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(Trace::RuleResultLog *)0,(Capability *)0,ztSource,ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,noTee,false,0); + } return false; case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter() @@ -708,17 +728,20 @@ bool Network::filterOutgoingPacket( outp.compress(); RR->sw->send(tPtr,outp,true); - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog *)0,(localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability *)0,ztSource,ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,noTee,false,0); + } return false; // DROP locally, since we redirected } else { - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog *)0,(localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability *)0,ztSource,ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,noTee,false,1); + } return true; } } else { - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog *)0,(localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability *)0,ztSource,ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,noTee,false,0); + } return false; } } @@ -788,8 +811,9 @@ int Network::filterIncomingPacket( } break; case DOZTFILTER_DROP: - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(Trace::RuleResultLog *)0,(Capability *)0,sourcePeer->address(),ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,false,true,0); + } return 0; // DROP case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter() @@ -825,24 +849,27 @@ int Network::filterIncomingPacket( outp.compress(); RR->sw->send(tPtr,outp,true); - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(c) ? &crrl : (Trace::RuleResultLog *)0,c,sourcePeer->address(),ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,false,true,0); + } return 0; // DROP locally, since we redirected } } - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(c) ? &crrl : (Trace::RuleResultLog *)0,c,sourcePeer->address(),ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,false,true,accept); + } return accept; } bool Network::subscribedToMulticastGroup(const MulticastGroup &mg,bool includeBridgedGroups) const { Mutex::Lock _l(_lock); - if (std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)) + if (std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)) { return true; - else if (includeBridgedGroups) + } else if (includeBridgedGroups) { return _multicastGroupsBehindMe.contains(mg); + } return false; } @@ -859,20 +886,24 @@ void Network::multicastUnsubscribe(const MulticastGroup &mg) { Mutex::Lock _l(_lock); std::vector::iterator i(std::lower_bound(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)); - if ( (i != _myMulticastGroups.end()) && (*i == mg) ) + if ( (i != _myMulticastGroups.end()) && (*i == mg) ) { _myMulticastGroups.erase(i); + } } uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr) { - if (_destroyed) + if (_destroyed) { return 0; + } const unsigned int start = ptr; ptr += 8; // skip network ID, which is already obviously known - const unsigned int chunkLen = chunk.at(ptr); ptr += 2; - const void *chunkData = chunk.field(ptr,chunkLen); ptr += chunkLen; + const unsigned int chunkLen = chunk.at(ptr); + ptr += 2; + const void *chunkData = chunk.field(ptr,chunkLen); + ptr += chunkLen; NetworkConfig *nc = (NetworkConfig *)0; uint64_t configUpdateId; @@ -884,19 +915,25 @@ uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Add unsigned long totalLength,chunkIndex; if (ptr < chunk.size()) { const bool fastPropagate = ((chunk[ptr++] & 0x01) != 0); - configUpdateId = chunk.at(ptr); ptr += 8; - totalLength = chunk.at(ptr); ptr += 4; - chunkIndex = chunk.at(ptr); ptr += 4; + configUpdateId = chunk.at(ptr); + ptr += 8; + totalLength = chunk.at(ptr); + ptr += 4; + chunkIndex = chunk.at(ptr); + ptr += 4; - if (((chunkIndex + chunkLen) > totalLength)||(totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY)) // >= since we need room for a null at the end + if (((chunkIndex + chunkLen) > totalLength)||(totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY)) { // >= since we need room for a null at the end return 0; - if ((chunk[ptr] != 1)||(chunk.at(ptr + 1) != ZT_C25519_SIGNATURE_LEN)) + } + if ((chunk[ptr] != 1)||(chunk.at(ptr + 1) != ZT_C25519_SIGNATURE_LEN)) { return 0; + } const uint8_t *sig = reinterpret_cast(chunk.field(ptr + 3,ZT_C25519_SIGNATURE_LEN)); // We can use the signature, which is unique per chunk, to get a per-chunk ID for local deduplication use - for(unsigned int i=0;i<16;++i) + for(unsigned int i=0;i<16;++i) { reinterpret_cast(&chunkId)[i & 7] ^= sig[i]; + } // Find existing or new slot for this update and check if this is a duplicate chunk for(int i=0;ihaveChunks;++j) { - if (c->haveChunkIds[j] == chunkId) + if (c->haveChunkIds[j] == chunkId) { return 0; + } } break; @@ -916,10 +954,12 @@ uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Add // If it's not a duplicate, check chunk signature const Identity controllerId(RR->topology->getIdentity(tPtr,controller())); - if (!controllerId) // we should always have the controller identity by now, otherwise how would we have queried it the first time? + if (!controllerId) { // we should always have the controller identity by now, otherwise how would we have queried it the first time? return 0; - if (!controllerId.verify(chunk.field(start,ptr - start),ptr - start,sig,ZT_C25519_SIGNATURE_LEN)) + } + if (!controllerId.verify(chunk.field(start,ptr - start),ptr - start,sig,ZT_C25519_SIGNATURE_LEN)) { return 0; + } // New properly verified chunks can be flooded "virally" through the network if (fastPropagate) { @@ -941,12 +981,14 @@ uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Add totalLength = chunkLen; chunkIndex = 0; - if (totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY) + if (totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY) { return 0; + } for(int i=0;its)) + if ((!c)||(_incomingConfigChunks[i].ts < c->ts)) { c = &(_incomingConfigChunks[i]); + } } } else { // Single-chunk unsigned legacy configs are only allowed from the controller itself @@ -960,8 +1002,9 @@ uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Add c->haveChunks = 0; c->haveBytes = 0; } - if (c->haveChunks >= ZT_NETWORK_MAX_UPDATE_CHUNKS) + if (c->haveChunks >= ZT_NETWORK_MAX_UPDATE_CHUNKS) { return false; + } c->haveChunkIds[c->haveChunks++] = chunkId; memcpy(c->data.unsafeData() + chunkIndex,chunkData,chunkLen); @@ -996,15 +1039,18 @@ uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Add int Network::setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToDisk) { - if (_destroyed) + if (_destroyed) { return 0; + } // _lock is NOT locked when this is called try { - if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id)) + if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id)) { return 0; // invalid config that is not for us or not for this network - if (_config == nconf) + } + if (_config == nconf) { return 1; // OK config, but duplicate of what we already have + } ZT_VirtualNetworkConfig ctmp; bool oldPortInitialized; @@ -1029,7 +1075,8 @@ int Network::setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToD try { if (nconf.toDictionary(*d,false)) { uint64_t tmp[2]; - tmp[0] = _id; tmp[1] = 0; + tmp[0] = _id; + tmp[1] = 0; RR->node->stateObjectPut(tPtr,ZT_STATE_OBJECT_NETWORK_CONFIG,tmp,d->data(),d->sizeBytes()); } } catch ( ... ) {} @@ -1043,8 +1090,9 @@ int Network::setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToD void Network::requestConfiguration(void *tPtr) { - if (_destroyed) + if (_destroyed) { return; + } if ((_id >> 56) == 0xff) { if ((_id & 0xffffff) == 0) { @@ -1145,8 +1193,9 @@ void Network::requestConfiguration(void *tPtr) nconf->staticIpCount = 2; nconf->ruleCount = 1; - if (networkHub != 0) + if (networkHub != 0) { nconf->specialists[0] = networkHub; + } nconf->staticIps[0] = InetAddress::makeIpv66plane(_id,myAddress); nconf->staticIps[1].set(ipv4,4,8); @@ -1162,7 +1211,9 @@ void Network::requestConfiguration(void *tPtr) nconf->name[4] = 'c'; nconf->name[5] = '-'; unsigned long nn = 6; - while ((nconf->name[nn] = v4ascii[nn - 6])) ++nn; + while ((nconf->name[nn] = v4ascii[nn - 6])) { + ++nn; + } nconf->name[nn++] = '.'; nconf->name[nn++] = '0'; nconf->name[nn++] = '.'; @@ -1234,8 +1285,9 @@ bool Network::gate(void *tPtr,const SharedPtr &peer) // comRevocationThreshold = m->comRevocationThreshold(); //} if ( (_config.isPublic()) || ((m)&&(m->isAllowedOnNetwork(_config, peer->identity()))) ) { - if (!m) + if (!m) { m = &(_membership(peer->address())); + } if (m->multicastLikeGate(now)) { _announceMulticastGroupsTo(tPtr,peer->address(),_allMulticastGroups()); } @@ -1260,16 +1312,18 @@ void Network::clean() const int64_t now = RR->node->now(); Mutex::Lock _l(_lock); - if (_destroyed) + if (_destroyed) { return; + } { Hashtable< MulticastGroup,uint64_t >::Iterator i(_multicastGroupsBehindMe); MulticastGroup *mg = (MulticastGroup *)0; uint64_t *ts = (uint64_t *)0; while (i.next(mg,ts)) { - if ((now - *ts) > (ZT_MULTICAST_LIKE_EXPIRE * 2)) + if ((now - *ts) > (ZT_MULTICAST_LIKE_EXPIRE * 2)) { _multicastGroupsBehindMe.erase(*mg); + } } } @@ -1278,9 +1332,11 @@ void Network::clean() Membership *m = (Membership *)0; Hashtable::Iterator i(_memberships); while (i.next(a,m)) { - if (!RR->topology->getPeerNoCache(*a)) + if (!RR->topology->getPeerNoCache(*a)) { _memberships.erase(*a); - else m->clean(now,_config); + } else { + m->clean(now,_config); + } } } } @@ -1315,8 +1371,9 @@ void Network::learnBridgeRoute(const MAC &mac,const Address &addr) { Hashtable::Iterator i(_remoteBridgeRoutes); while (i.next(m,a)) { - if (*a == maxAddr) + if (*a == maxAddr) { _remoteBridgeRoutes.erase(*m); + } } } } @@ -1327,22 +1384,25 @@ void Network::learnBridgedMulticastGroup(void *tPtr,const MulticastGroup &mg,int Mutex::Lock _l(_lock); const unsigned long tmp = (unsigned long)_multicastGroupsBehindMe.size(); _multicastGroupsBehindMe.set(mg,now); - if (tmp != _multicastGroupsBehindMe.size()) + if (tmp != _multicastGroupsBehindMe.size()) { _sendUpdatesToMembers(tPtr,&mg); + } } Membership::AddCredentialResult Network::addCredential(void *tPtr,const CertificateOfMembership &com) { - if (com.networkId() != _id) + if (com.networkId() != _id) { return Membership::ADD_REJECTED; + } Mutex::Lock _l(_lock); return _membership(com.issuedTo()).addCredential(RR,tPtr,_config,com); } Membership::AddCredentialResult Network::addCredential(void *tPtr,const Address &sentFrom,const Revocation &rev) { - if (rev.networkId() != _id) + if (rev.networkId() != _id) { return Membership::ADD_REJECTED; + } Mutex::Lock _l(_lock); Membership &m = _membership(rev.target()); @@ -1379,8 +1439,9 @@ void Network::destroy() ZT_VirtualNetworkStatus Network::_status() const { // assumes _lock is locked - if (_portError) + if (_portError) { return ZT_NETWORK_STATUS_PORT_ERROR; + } switch(_netconfFailure) { case NETCONF_FAILURE_ACCESS_DENIED: return ZT_NETWORK_STATUS_ACCESS_DENIED; @@ -1400,9 +1461,11 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const // assumes _lock is locked ec->nwid = _id; ec->mac = _mac.toInt(); - if (_config) + if (_config) { Utils::scopy(ec->name,sizeof(ec->name),_config.name); - else ec->name[0] = (char)0; + } else { + ec->name[0] = (char)0; + } ec->status = _status(); ec->type = (_config) ? (_config.isPrivate() ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC) : ZT_NETWORK_TYPE_PRIVATE; ec->mtu = (_config) ? _config.mtu : ZT_DEFAULT_MTU; @@ -1459,23 +1522,28 @@ void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMu const int64_t now = RR->node->now(); std::vector groups; - if (newMulticastGroup) + if (newMulticastGroup) { groups.push_back(*newMulticastGroup); - else groups = _allMulticastGroups(); + } else { + groups = _allMulticastGroups(); + } std::vector
alwaysAnnounceTo; if ((newMulticastGroup)||((now - _lastAnnouncedMulticastGroupsUpstream) >= ZT_MULTICAST_ANNOUNCE_PERIOD)) { - if (!newMulticastGroup) + if (!newMulticastGroup) { _lastAnnouncedMulticastGroupsUpstream = now; + } alwaysAnnounceTo = _config.alwaysContactAddresses(); - if (std::find(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),controller()) == alwaysAnnounceTo.end()) + if (std::find(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),controller()) == alwaysAnnounceTo.end()) { alwaysAnnounceTo.push_back(controller()); + } const std::vector
upstreams(RR->topology->upstreamAddresses()); for(std::vector
::const_iterator a(upstreams.begin());a!=upstreams.end();++a) { - if (std::find(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),*a) == alwaysAnnounceTo.end()) + if (std::find(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),*a) == alwaysAnnounceTo.end()) { alwaysAnnounceTo.push_back(*a); + } } std::sort(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end()); @@ -1504,8 +1572,9 @@ void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMu while (i.next(a,m)) { const Identity remoteIdentity(RR->topology->getIdentity(tPtr, *a)); if (remoteIdentity) { - if ( ( m->multicastLikeGate(now) || (newMulticastGroup) ) && (m->isAllowedOnNetwork(_config, remoteIdentity)) && (!std::binary_search(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),*a)) ) + if ( ( m->multicastLikeGate(now) || (newMulticastGroup) ) && (m->isAllowedOnNetwork(_config, remoteIdentity)) && (!std::binary_search(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),*a)) ) { _announceMulticastGroupsTo(tPtr,*a,groups); + } } } } @@ -1544,8 +1613,9 @@ std::vector Network::_allMulticastGroups() const mgs.reserve(_myMulticastGroups.size() + _multicastGroupsBehindMe.size() + 1); mgs.insert(mgs.end(),_myMulticastGroups.begin(),_myMulticastGroups.end()); _multicastGroupsBehindMe.appendKeys(mgs); - if ((_config)&&(_config.enableBroadcast())) + if ((_config)&&(_config.enableBroadcast())) { mgs.push_back(Network::BROADCAST); + } std::sort(mgs.begin(),mgs.end()); mgs.erase(std::unique(mgs.begin(),mgs.end()),mgs.end()); return mgs; diff --git a/node/Network.hpp b/node/Network.hpp index 275e82f02..a86a5c48b 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -332,8 +332,9 @@ public: */ inline Membership::AddCredentialResult addCredential(void *tPtr,const Capability &cap) { - if (cap.networkId() != _id) + if (cap.networkId() != _id) { return Membership::ADD_REJECTED; + } Mutex::Lock _l(_lock); return _membership(cap.issuedTo()).addCredential(RR,tPtr,_config,cap); } @@ -343,8 +344,9 @@ public: */ inline Membership::AddCredentialResult addCredential(void *tPtr,const Tag &tag) { - if (tag.networkId() != _id) + if (tag.networkId() != _id) { return Membership::ADD_REJECTED; + } Mutex::Lock _l(_lock); return _membership(tag.issuedTo()).addCredential(RR,tPtr,_config,tag); } @@ -359,8 +361,9 @@ public: */ inline Membership::AddCredentialResult addCredential(void *tPtr,const CertificateOfOwnership &coo) { - if (coo.networkId() != _id) + if (coo.networkId() != _id) { return Membership::ADD_REJECTED; + } Mutex::Lock _l(_lock); return _membership(coo.issuedTo()).addCredential(RR,tPtr,_config,coo); } @@ -377,8 +380,9 @@ public: Mutex::Lock _l(_lock); Membership &m = _membership(to); const int64_t lastPushed = m.lastPushedCredentials(); - if ((lastPushed < _lastConfigUpdate)||((now - lastPushed) > ZT_PEER_CREDENTIALS_REQUEST_RATE_LIMIT)) + if ((lastPushed < _lastConfigUpdate)||((now - lastPushed) > ZT_PEER_CREDENTIALS_REQUEST_RATE_LIMIT)) { m.pushCredentials(RR,tPtr,now,to,_config); + } } /** @@ -393,8 +397,9 @@ public: Mutex::Lock _l(_lock); Membership &m = _membership(to); const int64_t lastPushed = m.lastPushedCredentials(); - if ((lastPushed < _lastConfigUpdate)||((now - lastPushed) > ZT_PEER_ACTIVITY_TIMEOUT)) + if ((lastPushed < _lastConfigUpdate)||((now - lastPushed) > ZT_PEER_ACTIVITY_TIMEOUT)) { m.pushCredentials(RR,tPtr,now,to,_config); + } } /** diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 3dc3b36d6..4b0b05f0f 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -29,48 +29,84 @@ bool NetworkConfig::toDictionary(Dictionary &d,b // Try to put the more human-readable fields first - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_VERSION,(uint64_t)ZT_NETWORKCONFIG_VERSION)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,this->networkId)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,this->timestamp)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA,this->credentialTimeMaxDelta)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REVISION,this->revision)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,this->issuedTo.toString(tmp2))) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_TARGET,this->remoteTraceTarget.toString(tmp2))) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_LEVEL,(uint64_t)this->remoteTraceLevel)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,this->flags)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,(uint64_t)this->multicastLimit)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)this->type)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MTU,(uint64_t)this->mtu)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_VERSION,(uint64_t)ZT_NETWORKCONFIG_VERSION)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,this->networkId)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,this->timestamp)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA,this->credentialTimeMaxDelta)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REVISION,this->revision)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,this->issuedTo.toString(tmp2))) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_TARGET,this->remoteTraceTarget.toString(tmp2))) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_LEVEL,(uint64_t)this->remoteTraceLevel)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,this->flags)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,(uint64_t)this->multicastLimit)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)this->type)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MTU,(uint64_t)this->mtu)) { + return false; + } #ifdef ZT_SUPPORT_OLD_STYLE_NETCONF if (includeLegacy) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD,this->enableBroadcast())) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD,this->isPrivate())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD,this->enableBroadcast())) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD,this->isPrivate())) { + return false; + } std::string v4s; for(unsigned int i=0;istaticIps[i].ss_family == AF_INET) { - if (v4s.length() > 0) + if (v4s.length() > 0) { v4s.push_back(','); + } char buf[64]; v4s.append(this->staticIps[i].toString(buf)); } } if (v4s.length() > 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD,v4s.c_str())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD,v4s.c_str())) { + return false; + } } std::string v6s; for(unsigned int i=0;istaticIps[i].ss_family == AF_INET6) { - if (v6s.length() > 0) + if (v6s.length() > 0) { v6s.push_back(','); + } char buf[64]; v6s.append(this->staticIps[i].toString(buf)); } } if (v6s.length() > 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD,v6s.c_str())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD,v6s.c_str())) { + return false; + } } std::string ets; @@ -82,8 +118,9 @@ bool NetworkConfig::toDictionary(Dictionary &d,b et = rules[i].v.etherType; } else if (rt == ZT_NETWORK_RULE_ACTION_ACCEPT) { if (((int)lastrt < 32)||(lastrt == ZT_NETWORK_RULE_MATCH_ETHERTYPE)) { - if (ets.length() > 0) + if (ets.length() > 0) { ets.push_back(','); + } char tmp2[16] = {0}; ets.append(Utils::hex((uint16_t)et,tmp2)); } @@ -92,24 +129,31 @@ bool NetworkConfig::toDictionary(Dictionary &d,b lastrt = rt; } if (ets.length() > 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD,ets.c_str())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD,ets.c_str())) { + return false; + } } if (this->com) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD,this->com.toString().c_str())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD,this->com.toString().c_str())) { + return false; + } } std::string ab; for(unsigned int i=0;ispecialistCount;++i) { if ((this->specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) { - if (ab.length() > 0) + if (ab.length() > 0) { ab.push_back(','); + } char tmp2[16] = {0}; ab.append(Address(this->specialists[i]).toString(tmp2)); } } if (ab.length() > 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD,ab.c_str())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD,ab.c_str())) { + return false; + } } } #endif // ZT_SUPPORT_OLD_STYLE_NETCONF @@ -119,35 +163,49 @@ bool NetworkConfig::toDictionary(Dictionary &d,b if (this->com) { tmp->clear(); this->com.serialize(*tmp); - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp)) { + return false; + } } tmp->clear(); - for(unsigned int i=0;icapabilityCount;++i) + for(unsigned int i=0;icapabilityCount;++i) { this->capabilities[i].serialize(*tmp); + } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES,*tmp)) { + return false; + } } tmp->clear(); - for(unsigned int i=0;itagCount;++i) + for(unsigned int i=0;itagCount;++i) { this->tags[i].serialize(*tmp); + } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TAGS,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TAGS,*tmp)) { + return false; + } } tmp->clear(); - for(unsigned int i=0;icertificateOfOwnershipCount;++i) + for(unsigned int i=0;icertificateOfOwnershipCount;++i) { this->certificatesOfOwnership[i].serialize(*tmp); + } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP,*tmp)) { + return false; + } } tmp->clear(); - for(unsigned int i=0;ispecialistCount;++i) + for(unsigned int i=0;ispecialistCount;++i) { tmp->append((uint64_t)this->specialists[i]); + } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS,*tmp)) { + return false; + } } tmp->clear(); @@ -158,50 +216,83 @@ bool NetworkConfig::toDictionary(Dictionary &d,b tmp->append((uint16_t)this->routes[i].metric); } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ROUTES,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ROUTES,*tmp)) { + return false; + } } tmp->clear(); - for(unsigned int i=0;istaticIpCount;++i) + for(unsigned int i=0;istaticIpCount;++i) { this->staticIps[i].serialize(*tmp); + } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS,*tmp)) { + return false; + } } if (this->ruleCount) { tmp->clear(); Capability::serializeRules(*tmp,rules,ruleCount); if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_RULES,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_RULES,*tmp)) { + return false; + } } } tmp->clear(); DNS::serializeDNS(*tmp, &dns); if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS,*tmp)) { + return false; + } } if (this->ssoVersion == 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) { + return false; + } if (this->ssoEnabled) { if (this->authenticationURL[0]) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) { + return false; + } + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, this->authenticationExpiryTime)) { + return false; } - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, this->authenticationExpiryTime)) return false; } } else if(this->ssoVersion == 1) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) { + return false; + } //if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUER_URL, this->issuerURL)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_PROVIDER, this->ssoProvider)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUER_URL, this->issuerURL)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_PROVIDER, this->ssoProvider)) { + return false; + } } delete tmp; @@ -241,37 +332,45 @@ bool NetworkConfig::fromDictionary(const Dictionaryname,sizeof(this->name)); this->mtu = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MTU,ZT_DEFAULT_MTU); - if (this->mtu < 1280) + if (this->mtu < 1280) { this->mtu = 1280; // minimum MTU allowed by IPv6 standard and others - else if (this->mtu > ZT_MAX_MTU) + } else if (this->mtu > ZT_MAX_MTU) { this->mtu = ZT_MAX_MTU; + } if (d.getUI(ZT_NETWORKCONFIG_DICT_KEY_VERSION,0) < 6) { #ifdef ZT_SUPPORT_OLD_STYLE_NETCONF char tmp2[1024] = {0}; // Decode legacy fields if version is old - if (d.getB(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD)) + if (d.getB(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD)) { this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; + } this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; // always enable for old-style netconf this->type = (d.getB(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD,true)) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD,tmp2,sizeof(tmp2)) > 0) { char *saveptr = (char *)0; for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { - if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) break; + if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + break; + } InetAddress ip(f); - if (!ip.isNetwork()) + if (!ip.isNetwork()) { this->staticIps[this->staticIpCount++] = ip; + } } } if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD,tmp2,sizeof(tmp2)) > 0) { char *saveptr = (char *)0; for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { - if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) break; + if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + break; + } InetAddress ip(f); - if (!ip.isNetwork()) + if (!ip.isNetwork()) { this->staticIps[this->staticIpCount++] = ip; + } } } @@ -283,7 +382,9 @@ bool NetworkConfig::fromDictionary(const DictionaryruleCount + 2) > ZT_MAX_NETWORK_RULES) break; + if ((this->ruleCount + 2) > ZT_MAX_NETWORK_RULES) { + break; + } if (et > 0) { this->rules[this->ruleCount].t = (uint8_t)ZT_NETWORK_RULE_MATCH_ETHERTYPE; this->rules[this->ruleCount].v.etherType = (uint16_t)et; @@ -311,8 +412,9 @@ bool NetworkConfig::fromDictionary(const Dictionaryflags = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,0); this->type = (ZT_VirtualNetworkType)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)ZT_NETWORK_TYPE_PRIVATE); - if (d.get(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp)) + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp)) { this->com.deserialize(*tmp,0); + } if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES,*tmp)) { try { @@ -341,9 +443,9 @@ bool NetworkConfig::fromDictionary(const Dictionarysize()) { - if (certificateOfOwnershipCount < ZT_MAX_CERTIFICATES_OF_OWNERSHIP) + if (certificateOfOwnershipCount < ZT_MAX_CERTIFICATES_OF_OWNERSHIP) { p += certificatesOfOwnership[certificateOfOwnershipCount++].deserialize(*tmp,p); - else { + } else { CertificateOfOwnership foo; p += foo.deserialize(*tmp,p); } @@ -353,8 +455,9 @@ bool NetworkConfig::fromDictionary(const Dictionarysize()) { - if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS) + if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS) { this->specialists[this->specialistCount++] = tmp->at(p); + } p += 8; } } @@ -364,8 +467,10 @@ bool NetworkConfig::fromDictionary(const Dictionarysize())&&(routeCount < ZT_MAX_NETWORK_ROUTES)) { p += reinterpret_cast(&(this->routes[this->routeCount].target))->deserialize(*tmp,p); p += reinterpret_cast(&(this->routes[this->routeCount].via))->deserialize(*tmp,p); - this->routes[this->routeCount].flags = tmp->at(p); p += 2; - this->routes[this->routeCount].metric = tmp->at(p); p += 2; + this->routes[this->routeCount].flags = tmp->at(p); + p += 2; + this->routes[this->routeCount].metric = tmp->at(p); + p += 2; ++this->routeCount; } } diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index cd713dde8..859d7212d 100644 --- a/node/NetworkConfig.hpp +++ b/node/NetworkConfig.hpp @@ -356,8 +356,9 @@ public: { std::vector
r; for(unsigned int i=0;i r; for(unsigned int i=0;i r; for(unsigned int i=0;i r; for(unsigned int i=0;iversion != 0) + if (callbacks->version != 0) { throw ZT_EXCEPTION_INVALID_ARGUMENT; + } memcpy(&_cb,callbacks,sizeof(ZT_Node_Callbacks)); // Initialize non-cryptographic PRNG from a good random source @@ -69,7 +70,8 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64 memset((void *)(&_stats),0,sizeof(_stats)); uint64_t idtmp[2]; - idtmp[0] = 0; idtmp[1] = 0; + idtmp[0] = 0; + idtmp[1] = 0; char tmp[2048]; int n = stateObjectGet(tptr,ZT_STATE_OBJECT_IDENTITY_SECRET,idtmp,tmp,sizeof(tmp) - 1); if (n > 0) { @@ -86,15 +88,18 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64 RR->identity.generate(); RR->identity.toString(false,RR->publicIdentityStr); RR->identity.toString(true,RR->secretIdentityStr); - idtmp[0] = RR->identity.address().toInt(); idtmp[1] = 0; + idtmp[0] = RR->identity.address().toInt(); + idtmp[1] = 0; stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_SECRET,idtmp,RR->secretIdentityStr,(unsigned int)strlen(RR->secretIdentityStr)); stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,RR->publicIdentityStr,(unsigned int)strlen(RR->publicIdentityStr)); } else { - idtmp[0] = RR->identity.address().toInt(); idtmp[1] = 0; + idtmp[0] = RR->identity.address().toInt(); + idtmp[1] = 0; n = stateObjectGet(tptr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,tmp,sizeof(tmp) - 1); if ((n > 0)&&(n < (int)sizeof(RR->publicIdentityStr))&&(n < (int)sizeof(tmp))) { - if (memcmp(tmp,RR->publicIdentityStr,n)) + if (memcmp(tmp,RR->publicIdentityStr,n)) { stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,RR->publicIdentityStr,(unsigned int)strlen(RR->publicIdentityStr)); + } } } @@ -108,10 +113,13 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64 const unsigned long bc = sizeof(Bond) + (((sizeof(Bond) & 0xf) != 0) ? (16 - (sizeof(Bond) & 0xf)) : 0); m = reinterpret_cast(::malloc(16 + ts + sws + mcs + topologys + sas + bc)); - if (!m) + if (!m) { throw std::bad_alloc(); + } RR->rtmem = m; - while (((uintptr_t)m & 0xf) != 0) ++m; + while (((uintptr_t)m & 0xf) != 0) { + ++m; + } RR->t = new (m) Trace(RR); m += ts; @@ -125,12 +133,24 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64 m += sas; RR->bc = new (m) Bond(RR); } catch ( ... ) { - if (RR->sa) RR->sa->~SelfAwareness(); - if (RR->topology) RR->topology->~Topology(); - if (RR->mc) RR->mc->~Multicaster(); - if (RR->sw) RR->sw->~Switch(); - if (RR->t) RR->t->~Trace(); - if (RR->bc) RR->bc->~Bond(); + if (RR->sa) { + RR->sa->~SelfAwareness(); + } + if (RR->topology) { + RR->topology->~Topology(); + } + if (RR->mc) { + RR->mc->~Multicaster(); + } + if (RR->sw) { + RR->sw->~Switch(); + } + if (RR->t) { + RR->t->~Trace(); + } + if (RR->bc) { + RR->bc->~Bond(); + } ::free(m); throw; } @@ -144,12 +164,24 @@ Node::~Node() Mutex::Lock _l(_networks_m); _networks.clear(); // destroy all networks before shutdown } - if (RR->sa) RR->sa->~SelfAwareness(); - if (RR->topology) RR->topology->~Topology(); - if (RR->mc) RR->mc->~Multicaster(); - if (RR->sw) RR->sw->~Switch(); - if (RR->t) RR->t->~Trace(); - if (RR->bc) RR->bc->~Bond(); + if (RR->sa) { + RR->sa->~SelfAwareness(); + } + if (RR->topology) { + RR->topology->~Topology(); + } + if (RR->mc) { + RR->mc->~Multicaster(); + } + if (RR->sw) { + RR->sw->~Switch(); + } + if (RR->t) { + RR->t->~Trace(); + } + if (RR->bc) { + RR->bc->~Bond(); + } ::free(RR->rtmem); } @@ -184,7 +216,9 @@ ZT_ResultCode Node::processVirtualNetworkFrame( if (nw) { RR->sw->onLocalEthernet(tptr,nw,MAC(sourceMac),MAC(destMac),etherType,vlanId,frameData,frameLength); return ZT_RESULT_OK; - } else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } else { + return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } } // Closure used to ping upstream and active/online peers @@ -239,8 +273,9 @@ public: if ((!contacted)&&(_bestCurrentUpstream)) { const SharedPtr up(_bestCurrentUpstream->getAppropriatePath(_now,true)); - if (up) + if (up) { p->sendHELLO(_tPtr,up->localSocket(),up->address(),_now); + } } _alwaysContact.erase(p->address()); // after this we'll WHOIS all upstreams that remain @@ -299,8 +334,9 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64 std::vector *upstreamStableEndpoints = (std::vector *)0; while (i.next(upstreamAddress,upstreamStableEndpoints)) { SharedPtr p(RR->topology->getPeerNoCache(*upstreamAddress)); - if (p) + if (p) { lastReceivedFromUpstream = std::max(p->lastReceive(),lastReceivedFromUpstream); + } } } @@ -311,8 +347,9 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64 _LocalControllerAuth *k = (_LocalControllerAuth *)0; int64_t *v = (int64_t *)0; while (i.next(k,v)) { - if ((*v - now) > (ZT_NETWORK_AUTOCONF_DELAY * 3)) + if ((*v - now) > (ZT_NETWORK_AUTOCONF_DELAY * 3)) { _localControllerAuthorizations.erase(*k); + } } _localControllerAuthorizations_m.unlock(); } @@ -341,8 +378,9 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64 Hashtable< Address,std::vector >::Iterator i(alwaysContact); Address *upstreamAddress = (Address *)0; std::vector *upstreamStableEndpoints = (std::vector *)0; - while (i.next(upstreamAddress,upstreamStableEndpoints)) + while (i.next(upstreamAddress,upstreamStableEndpoints)) { RR->sw->requestWhois(tptr,now,*upstreamAddress); + } } // Refresh network config or broadcast network updates to members as needed @@ -358,8 +396,9 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64 // Update online status, post status change as event const bool oldOnline = _online; _online = (((now - lastReceivedFromUpstream) < ZT_PEER_ACTIVITY_TIMEOUT)||(RR->topology->amUpstream())); - if (oldOnline != _online) + if (oldOnline != _online) { postEvent(tptr,_online ? ZT_EVENT_ONLINE : ZT_EVENT_OFFLINE); + } } catch ( ... ) { return ZT_RESULT_FATAL_ERROR_INTERNAL; } @@ -396,8 +435,9 @@ ZT_ResultCode Node::join(uint64_t nwid,void *uptr,void *tptr) { Mutex::Lock _l(_networks_m); SharedPtr &nw = _networks[nwid]; - if (!nw) + if (!nw) { nw = SharedPtr(new Network(RR,tptr,nwid,uptr,(const NetworkConfig *)0)); + } return ZT_RESULT_OK; } @@ -409,17 +449,20 @@ ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr) Mutex::Lock _l(_networks_m); SharedPtr *nw = _networks.get(nwid); RR->sw->removeNetworkQoSControlBlock(nwid); - if (!nw) + if (!nw) { return ZT_RESULT_OK; - if (uptr) + } + if (uptr) { *uptr = (*nw)->userPtr(); + } (*nw)->externalConfig(&ctmp); (*nw)->destroy(); nUserPtr = (*nw)->userPtr(); } - if (nUserPtr) + if (nUserPtr) { RR->node->configureVirtualNetworkPort(tptr,nwid,nUserPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp); + } { Mutex::Lock _l(_networks_m); @@ -427,7 +470,8 @@ ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr) } uint64_t tmp[2]; - tmp[0] = nwid; tmp[1] = 0; + tmp[0] = nwid; + tmp[1] = 0; RR->node->stateObjectDelete(tptr,ZT_STATE_OBJECT_NETWORK_CONFIG,tmp); return ZT_RESULT_OK; @@ -439,7 +483,9 @@ ZT_ResultCode Node::multicastSubscribe(void *tptr,uint64_t nwid,uint64_t multica if (nw) { nw->multicastSubscribe(tptr,MulticastGroup(MAC(multicastGroup),(uint32_t)(multicastAdi & 0xffffffff))); return ZT_RESULT_OK; - } else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } else { + return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } } ZT_ResultCode Node::multicastUnsubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi) @@ -448,7 +494,9 @@ ZT_ResultCode Node::multicastUnsubscribe(uint64_t nwid,uint64_t multicastGroup,u if (nw) { nw->multicastUnsubscribe(MulticastGroup(MAC(multicastGroup),(uint32_t)(multicastAdi & 0xffffffff))); return ZT_RESULT_OK; - } else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } else { + return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } } ZT_ResultCode Node::orbit(void *tptr,uint64_t moonWorldId,uint64_t moonSeed) @@ -482,8 +530,9 @@ ZT_PeerList *Node::peers() const std::sort(peers.begin(),peers.end()); char *buf = (char *)::malloc(sizeof(ZT_PeerList) + (sizeof(ZT_Peer) * peers.size())); - if (!buf) + if (!buf) { return (ZT_PeerList *)0; + } ZT_PeerList *pl = (ZT_PeerList *)buf; pl->peers = (ZT_Peer *)(buf + sizeof(ZT_PeerList)); @@ -502,8 +551,9 @@ ZT_PeerList *Node::peers() const p->versionRev = -1; } p->latency = pi->second->latency(_now); - if (p->latency >= 0xffff) + if (p->latency >= 0xffff) { p->latency = -1; + } p->role = RR->topology->role(pi->second->identity().address()); std::vector< SharedPtr > paths(pi->second->paths(_now)); @@ -563,8 +613,9 @@ ZT_VirtualNetworkList *Node::networks() const Mutex::Lock _l(_networks_m); char *buf = (char *)::malloc(sizeof(ZT_VirtualNetworkList) + (sizeof(ZT_VirtualNetworkConfig) * _networks.size())); - if (!buf) + if (!buf) { return (ZT_VirtualNetworkList *)0; + } ZT_VirtualNetworkList *nl = (ZT_VirtualNetworkList *)buf; nl->networks = (ZT_VirtualNetworkConfig *)(buf + sizeof(ZT_VirtualNetworkList)); @@ -572,16 +623,18 @@ ZT_VirtualNetworkList *Node::networks() const Hashtable< uint64_t,SharedPtr >::Iterator i(*const_cast< Hashtable< uint64_t,SharedPtr > *>(&_networks)); uint64_t *k = (uint64_t *)0; SharedPtr *v = (SharedPtr *)0; - while (i.next(k,v)) + while (i.next(k,v)) { (*v)->externalConfig(&(nl->networks[nl->networkCount++])); + } return nl; } void Node::freeQueryResult(void *qr) { - if (qr) + if (qr) { ::free(qr); + } } int Node::addLocalInterfaceAddress(const struct sockaddr_storage *addr) @@ -620,8 +673,9 @@ int Node::sendUserMessage(void *tptr,uint64_t dest,uint64_t typeId,const void *d void Node::setNetconfMaster(void *networkControllerInstance) { RR->localNetworkController = reinterpret_cast(networkControllerInstance); - if (networkControllerInstance) - RR->localNetworkController->init(RR->identity,this); + if (networkControllerInstance) { + RR->localNetworkController->init(RR->identity, this); + } } /****************************************************************************/ @@ -630,11 +684,13 @@ void Node::setNetconfMaster(void *networkControllerInstance) bool Node::shouldUsePathForZeroTierTraffic(void *tPtr,const Address &ztaddr,const int64_t localSocket,const InetAddress &remoteAddress) { - if (!Path::isAddressValidForPath(remoteAddress)) + if (!Path::isAddressValidForPath(remoteAddress)) { return false; + } - if (RR->topology->isProhibitedEndpoint(ztaddr,remoteAddress)) + if (RR->topology->isProhibitedEndpoint(ztaddr,remoteAddress)) { return false; + } { Mutex::Lock _l(_networks_m); @@ -644,8 +700,9 @@ bool Node::shouldUsePathForZeroTierTraffic(void *tPtr,const Address &ztaddr,cons while (i.next(k,v)) { if ((*v)->hasConfig()) { for(unsigned int k=0;k<(*v)->config().staticIpCount;++k) { - if ((*v)->config().staticIps[k].containsAddress(remoteAddress)) + if ((*v)->config().staticIps[k].containsAddress(remoteAddress)) { return false; + } } } } @@ -690,14 +747,18 @@ void Node::ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &de if (destination == RR->identity.address()) { SharedPtr n(network(nwid)); - if (!n) return; + if (!n) { + return; + } n->setConfiguration((void *)0,nc,true); } else { Dictionary *dconf = new Dictionary(); try { if (nc.toDictionary(*dconf,sendLegacyFormatConfig)) { uint64_t configUpdateId = prng(); - if (!configUpdateId) ++configUpdateId; + if (!configUpdateId) { + ++configUpdateId; + } const unsigned int totalSize = dconf->sizeBytes(); unsigned int chunkIndex = 0; @@ -741,7 +802,9 @@ void Node::ncSendRevocation(const Address &destination,const Revocation &rev) { if (destination == RR->identity.address()) { SharedPtr n(network(rev.networkId())); - if (!n) return; + if (!n) { + return; + } n->addCredential((void *)0,RR->identity.address(),rev); } else { Packet outp(destination,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS); @@ -759,7 +822,9 @@ void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &des { if (destination == RR->identity.address()) { SharedPtr n(network(nwid)); - if (!n) return; + if (!n) { + return; + } switch(errorCode) { case NetworkController::NC_ERROR_OBJECT_NOT_FOUND: case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR: @@ -773,7 +838,8 @@ void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &des break; } - default: break; + default: + break; } } else if (requestPacketId) { Packet outp(destination,RR->identity.address(),Packet::VERB_ERROR); @@ -1040,9 +1106,15 @@ enum ZT_ResultCode ZT_Node_setPhysicalPathConfiguration(ZT_Node *node,const stru void ZT_version(int *major,int *minor,int *revision) { - if (major) *major = ZEROTIER_ONE_VERSION_MAJOR; - if (minor) *minor = ZEROTIER_ONE_VERSION_MINOR; - if (revision) *revision = ZEROTIER_ONE_VERSION_REVISION; + if (major) { + *major = ZEROTIER_ONE_VERSION_MAJOR; + } + if (minor) { + *minor = ZEROTIER_ONE_VERSION_MINOR; + } + if (revision) { + *revision = ZEROTIER_ONE_VERSION_REVISION; + } } } // extern "C" diff --git a/node/Node.hpp b/node/Node.hpp index 0ddd5cb7b..1f74b8340 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -138,8 +138,9 @@ public: { Mutex::Lock _l(_networks_m); const SharedPtr *n = _networks.get(nwid); - if (n) + if (n) { return *n; + } return SharedPtr(); } @@ -156,8 +157,9 @@ public: Hashtable< uint64_t,SharedPtr >::Iterator i(*const_cast< Hashtable< uint64_t,SharedPtr > * >(&_networks)); uint64_t *k = (uint64_t *)0; SharedPtr *v = (SharedPtr *)0; - while (i.next(k,v)) + while (i.next(k,v)) { nw.push_back(*v); + } return nw; } @@ -223,8 +225,9 @@ public: const uint32_t pid2 = (uint32_t)(packetId >> 32); const unsigned long bucket = (unsigned long)(pid2 & ZT_EXPECTING_REPLIES_BUCKET_MASK1); for(unsigned long i=0;i<=ZT_EXPECTING_REPLIES_BUCKET_MASK2;++i) { - if (_expectingRepliesTo[bucket][i] == pid2) + if (_expectingRepliesTo[bucket][i] == pid2) { return true; + } } return false; } @@ -258,8 +261,9 @@ public: _localControllerAuthorizations_m.lock(); const int64_t *const at = _localControllerAuthorizations.get(_LocalControllerAuth(nwid,addr)); _localControllerAuthorizations_m.unlock(); - if (at) + if (at) { return ((now - *at) < (ZT_NETWORK_AUTOCONF_DELAY * 3)); + } return false; } diff --git a/node/OutboundMulticast.cpp b/node/OutboundMulticast.cpp index 2d05c49e1..a46a9575e 100644 --- a/node/OutboundMulticast.cpp +++ b/node/OutboundMulticast.cpp @@ -50,20 +50,27 @@ void OutboundMulticast::init( _frameLen = (len < ZT_MAX_MTU) ? len : ZT_MAX_MTU; _etherType = etherType; - if (gatherLimit) flags |= 0x02; + if (gatherLimit) { + flags |= 0x02; + } _packet.setSource(RR->identity.address()); _packet.setVerb(Packet::VERB_MULTICAST_FRAME); _packet.append((uint64_t)nwid); _packet.append(flags); - if (gatherLimit) _packet.append((uint32_t)gatherLimit); - if (src) src.appendTo(_packet); + if (gatherLimit) { + _packet.append((uint32_t)gatherLimit); + } + if (src) { + src.appendTo(_packet); + } dest.mac().appendTo(_packet); _packet.append((uint32_t)dest.adi()); _packet.append((uint16_t)etherType); _packet.append(payload,_frameLen); - if (!disableCompression) + if (!disableCompression) { _packet.compress(); + } memcpy(_frameData,payload,_frameLen); } diff --git a/node/Packet.cpp b/node/Packet.cpp index d9378cc5e..b37cc2640 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -295,7 +295,11 @@ static inline void LZ4_wildCopy(void* dstPtr, const void* srcPtr, void* dstEnd) BYTE* d = (BYTE*)dstPtr; const BYTE* s = (const BYTE*)srcPtr; BYTE* const e = (BYTE*)dstEnd; - do { LZ4_copy8(d,s); d+=8; s+=8; } while (d> 3); # else unsigned r; - if (!(val>>32)) { r=4; } else { r=0; val>>=32; } - if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + if (!(val>>32)) { + r=4; + } else { + r=0; + val>>=32; + } + if (!(val>>16)) { + r+=2; + val>>=8; + } else { + val>>=24; + } r += (!val); return r; # endif @@ -369,7 +383,13 @@ static inline unsigned LZ4_NbCommonBytes (reg_t val) return (__builtin_clz((U32)val) >> 3); # else unsigned r; - if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } + if (!(val>>16)) { + r=2; + val>>=8; + } else { + r=0; + val>>=24; + } r += (!val); return r; # endif @@ -384,14 +404,23 @@ static inline unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE while (likely(pIn> ((MINMATCH*8)-(LZ4_HASHLOG+1))); - else + } else { return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); + } } static inline U32 LZ4_hash5(U64 sequence, tableType_t const tableType) @@ -422,25 +452,36 @@ static inline U32 LZ4_hash5(U64 sequence, tableType_t const tableType) static const U64 prime5bytes = 889523592379ULL; static const U64 prime8bytes = 11400714785074694791ULL; const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; - if (LZ4_isLittleEndian()) + if (LZ4_isLittleEndian()) { return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); - else + } else { return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); + } } FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) { - if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + if ((sizeof(reg_t)==8) && (tableType != byU16)) { + return LZ4_hash5(LZ4_read_ARCH(p), tableType); + } return LZ4_hash4(LZ4_read32(p), tableType); } static inline void LZ4_putPositionOnHash(const BYTE* p, U32 h, void* tableBase, tableType_t const tableType, const BYTE* srcBase) { - switch (tableType) - { - case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } - case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } - case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + switch (tableType) { + case byPtr: { + const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; + return; + } + case byU32: { + U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); + return; + } + case byU16: { + U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); + return; + } } } @@ -452,9 +493,18 @@ FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t ta static inline const BYTE* LZ4_getPositionOnHash(U32 h, void* tableBase, tableType_t tableType, const BYTE* srcBase) { - if (tableType == byPtr) { const BYTE** hashTable = (const BYTE**) tableBase; return hashTable[h]; } - if (tableType == byU32) { const U32* const hashTable = (U32*) tableBase; return hashTable[h] + srcBase; } - { const U16* const hashTable = (U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ + if (tableType == byPtr) { + const BYTE** hashTable = (const BYTE**) tableBase; + return hashTable[h]; + } + if (tableType == byU32) { + const U32* const hashTable = (U32*) tableBase; + return hashTable[h] + srcBase; + } + { /* default, to ensure a return */ + const U16* const hashTable = (U16*) tableBase; + return hashTable[h] + srcBase; + } } FORCE_INLINE const BYTE* LZ4_getPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) @@ -493,9 +543,10 @@ FORCE_INLINE int LZ4_compress_generic( U32 forwardH; /* Init conditions */ - if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported inputSize, too large (or negative) */ - switch(dict) - { + if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) { + return 0; /* Unsupported inputSize, too large (or negative) */ + } + switch(dict) { case noDict: default: base = (const BYTE*)source; @@ -510,12 +561,17 @@ FORCE_INLINE int LZ4_compress_generic( lowLimit = (const BYTE*)source; break; } - if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ - if (inputSize=LZ4_64Klimit)) { + return 0; /* Size too large (not within 64K limit) */ + } + if (inputSizehashTable, tableType, base); - ip++; forwardH = LZ4_hashPosition(ip, tableType); + ip++; + forwardH = LZ4_hashPosition(ip, tableType); /* Main Loop */ for ( ; ; ) { @@ -524,7 +580,8 @@ FORCE_INLINE int LZ4_compress_generic( BYTE* token; /* Find a match */ - { const BYTE* forwardIp = ip; + { + const BYTE* forwardIp = ip; unsigned step = 1; unsigned searchMatchNb = acceleration << LZ4_skipTrigger; do { @@ -533,7 +590,9 @@ FORCE_INLINE int LZ4_compress_generic( forwardIp += step; step = (searchMatchNb++ >> LZ4_skipTrigger); - if (unlikely(forwardIp > mflimit)) goto _last_literals; + if (unlikely(forwardIp > mflimit)) { + goto _last_literals; + } match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); if (dict==usingExtDict) { @@ -543,7 +602,8 @@ FORCE_INLINE int LZ4_compress_generic( } else { refDelta = 0; lowLimit = (const BYTE*)source; - } } + } + } forwardH = LZ4_hashPosition(forwardIp, tableType); LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); @@ -553,21 +613,29 @@ FORCE_INLINE int LZ4_compress_generic( } /* Catch up */ - while (((ip>anchor) & (match+refDelta > lowLimit)) && (unlikely(ip[-1]==match[refDelta-1]))) { ip--; match--; } + while (((ip>anchor) & (match+refDelta > lowLimit)) && (unlikely(ip[-1]==match[refDelta-1]))) { + ip--; + match--; + } /* Encode Literals */ - { unsigned const litLength = (unsigned)(ip - anchor); + { + unsigned const litLength = (unsigned)(ip - anchor); token = op++; if ((outputLimited) && /* Check output buffer overflow */ - (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit))) + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit))) { return 0; + } if (litLength >= RUN_MASK) { int len = (int)litLength-RUN_MASK; *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; + for(; len >= 255 ; len-=255) { + *op++ = 255; + } *op++ = (BYTE)len; + } else { + *token = (BYTE)(litLength< matchlimit) limit = matchlimit; + if (limit > matchlimit) { + limit = matchlimit; + } matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); ip += MINMATCH + matchCode; if (ip==limit) { @@ -599,8 +671,9 @@ _next_match: } if ( outputLimited && /* Check output buffer overflow */ - (unlikely(op + (1 + LASTLITERALS) + (matchCode>>8) > olimit)) ) + (unlikely(op + (1 + LASTLITERALS) + (matchCode>>8) > olimit)) ) { return 0; + } if (matchCode >= ML_MASK) { *token += ML_MASK; matchCode -= ML_MASK; @@ -612,14 +685,17 @@ _next_match: } op += matchCode / 255; *op++ = (BYTE)(matchCode % 255); - } else + } else { *token += (BYTE)(matchCode); + } } anchor = ip; /* Test end of chunk */ - if (ip > mflimit) break; + if (ip > mflimit) { + break; + } /* Fill table */ LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); @@ -633,12 +709,16 @@ _next_match: } else { refDelta = 0; lowLimit = (const BYTE*)source; - } } + } + } LZ4_putPosition(ip, cctx->hashTable, tableType, base); if ( ((dictIssue==dictSmall) ? (match>=lowRefLimit) : 1) && (match+MAX_DISTANCE>=ip) - && (LZ4_read32(match+refDelta)==LZ4_read32(ip)) ) - { token=op++; *token=0; goto _next_match; } + && (LZ4_read32(match+refDelta)==LZ4_read32(ip)) ) { + token=op++; + *token=0; + goto _next_match; + } /* Prepare next loop */ forwardH = LZ4_hashPosition(++ip, tableType); @@ -646,14 +726,18 @@ _next_match: _last_literals: /* Encode Last Literals */ - { size_t const lastRun = (size_t)(iend - anchor); + { + size_t const lastRun = (size_t)(iend - anchor); if ( (outputLimited) && /* Check output buffer overflow */ - ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > (U32)maxOutputSize) ) + ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > (U32)maxOutputSize) ) { return 0; + } if (lastRun >= RUN_MASK) { size_t accumulator = lastRun - RUN_MASK; *op++ = RUN_MASK << ML_BITS; - for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + for(; accumulator >= 255 ; accumulator-=255) { + *op++ = 255; + } *op++ = (BYTE) accumulator; } else { *op++ = (BYTE)(lastRun<= LZ4_compressBound(inputSize)) { - if (inputSize < LZ4_64Klimit) + if (inputSize < LZ4_64Klimit) { return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, byU16, noDict, noDictIssue, acceleration); - else + } else { return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration); + } } else { - if (inputSize < LZ4_64Klimit) + if (inputSize < LZ4_64Klimit) { return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); - else + } else { return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration); + } } } @@ -741,9 +827,15 @@ FORCE_INLINE int LZ4_decompress_generic( /* Special cases */ - if ((partialDecoding) && (oexit > oend-MFLIMIT)) oexit = oend-MFLIMIT; /* targetOutputSize too high => decode everything */ - if ((endOnInput) && (unlikely(outputSize==0))) return ((inputSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ - if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0?1:-1); + if ((partialDecoding) && (oexit > oend-MFLIMIT)) { + oexit = oend-MFLIMIT; /* targetOutputSize too high => decode everything */ + } + if ((endOnInput) && (unlikely(outputSize==0))) { + return ((inputSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ + } + if ((!endOnInput) && (unlikely(outputSize==0))) { + return (*ip==0?1:-1); + } /* Main Loop : decode sequences */ while (1) { @@ -759,21 +851,32 @@ FORCE_INLINE int LZ4_decompress_generic( s = *ip++; length += s; } while ( likely(endOnInput ? ip(partialDecoding?oexit:oend-MFLIMIT)) || (ip+length>iend-(2+1+LASTLITERALS))) ) - || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) - { + || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) { if (partialDecoding) { - if (cpy > oend) goto _output_error; /* Error : write attempt beyond end of output buffer */ - if ((endOnInput) && (ip+length > iend)) goto _output_error; /* Error : read attempt beyond end of input buffer */ + if (cpy > oend) { + goto _output_error; /* Error : write attempt beyond end of output buffer */ + } + if ((endOnInput) && (ip+length > iend)) { + goto _output_error; /* Error : read attempt beyond end of input buffer */ + } } else { - if ((!endOnInput) && (cpy != oend)) goto _output_error; /* Error : block decoding must stop exactly there */ - if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) goto _output_error; /* Error : input must be consumed */ + if ((!endOnInput) && (cpy != oend)) { + goto _output_error; /* Error : block decoding must stop exactly there */ + } + if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) { + goto _output_error; /* Error : input must be consumed */ + } } memcpy(op, ip, length); ip += length; @@ -781,12 +884,16 @@ FORCE_INLINE int LZ4_decompress_generic( break; /* Necessarily EOF, due to parsing restrictions */ } LZ4_wildCopy(op, ip, cpy); - ip += length; op = cpy; + ip += length; + op = cpy; /* get offset */ - offset = LZ4_readLE16(ip); ip+=2; + offset = LZ4_readLE16(ip); + ip += 2; match = op - offset; - if ((checkOffset) && (unlikely(match < lowLimit))) goto _output_error; /* Error : offset outside buffers */ + if ((checkOffset) && (unlikely(match < lowLimit))) { + goto _output_error; /* Error : offset outside buffers */ + } LZ4_write32(op, (U32)offset); /* costs ~1%; silence an msan warning when offset==0 */ /* get matchlength */ @@ -795,16 +902,22 @@ FORCE_INLINE int LZ4_decompress_generic( unsigned s; do { s = *ip++; - if ((endOnInput) && (ip > iend-LASTLITERALS)) goto _output_error; + if ((endOnInput) && (ip > iend-LASTLITERALS)) { + goto _output_error; + } length += s; } while (s==255); - if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) { + goto _output_error; /* overflow detection */ + } } length += MINMATCH; /* check external dictionary */ if ((dict==usingExtDict) && (match < lowPrefix)) { - if (unlikely(op+length > oend-LASTLITERALS)) goto _output_error; /* doesn't respect parsing restriction */ + if (unlikely(op+length > oend-LASTLITERALS)) { + goto _output_error; /* doesn't respect parsing restriction */ + } if (length <= (size_t)(lowPrefix-match)) { /* match can be copied as a single segment from external dictionary */ @@ -819,11 +932,14 @@ FORCE_INLINE int LZ4_decompress_generic( if (restSize > (size_t)(op-lowPrefix)) { /* overlap copy */ BYTE* const endOfMatch = op + restSize; const BYTE* copyFrom = lowPrefix; - while (op < endOfMatch) *op++ = *copyFrom++; + while (op < endOfMatch) { + *op++ = *copyFrom++; + } } else { memcpy(op, lowPrefix, restSize); op += restSize; - } } + } + } continue; } @@ -838,31 +954,40 @@ FORCE_INLINE int LZ4_decompress_generic( match += dec32table[offset]; memcpy(op+4, match, 4); match -= dec64; - } else { LZ4_copy8(op, match); match+=8; } + } else { + LZ4_copy8(op, match); + match+=8; + } op += 8; if (unlikely(cpy>oend-12)) { BYTE* const oCopyLimit = oend-(WILDCOPYLENGTH-1); - if (cpy > oend-LASTLITERALS) goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (cpy > oend-LASTLITERALS) { + goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + } if (op < oCopyLimit) { LZ4_wildCopy(op, match, oCopyLimit); match += oCopyLimit - op; op = oCopyLimit; } - while (op16) LZ4_wildCopy(op+8, match+8, cpy); + if (length>16) { + LZ4_wildCopy(op+8, match+8, cpy); + } } op=cpy; /* correction */ } /* end of decoding */ - if (endOnInput) + if (endOnInput) { return (int) (((char*)op)-dest); /* Nb of output bytes decoded */ - else + } else { return (int) (((const char*)ip)-source); /* Nb of input bytes read */ - + } /* Overflow error detected */ _output_error: return (int) (-(((const char*)ip)-source))-1; @@ -931,8 +1056,9 @@ void Packet::armor(const void *key,bool encryptPayload,const AES aesKeys[2]) uint8_t *const payload = data + ZT_PACKET_IDX_VERB; const unsigned int payloadLen = size() - ZT_PACKET_IDX_VERB; - if (encryptPayload) + if (encryptPayload) { s20.crypt12(payload,payload,payloadLen); + } uint64_t mac[2]; Poly1305::compute(mac,payload,payloadLen,macKey); @@ -977,14 +1103,17 @@ bool Packet::dearmor(const void *key,const AES aesKeys[2]) uint64_t mac[2]; Poly1305::compute(mac,payload,payloadLen,keyStream); #ifdef ZT_NO_TYPE_PUNNING - if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) + if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) { return false; + } #else - if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) // also secure, constant time + if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) { // also secure, constant time return false; + } #endif - if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) + if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) { Salsa20::memxor(data + ZT_PACKET_IDX_VERB,reinterpret_cast(keyStream + 8),payloadLen); + } } else { Salsa20 s20(mangledKey,data + ZT_PACKET_IDX_IV); uint64_t macKey[4]; @@ -992,14 +1121,17 @@ bool Packet::dearmor(const void *key,const AES aesKeys[2]) uint64_t mac[2]; Poly1305::compute(mac,payload,payloadLen,macKey); #ifdef ZT_NO_TYPE_PUNNING - if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) + if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) { return false; + } #else - if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) // also secure, constant time + if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) { // also secure, constant time return false; + } #endif - if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) + if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) { s20.crypt12(payload,payload,payloadLen); + } } return true; } @@ -1011,7 +1143,9 @@ void Packet::cryptField(const void *key,unsigned int start,unsigned int len) { uint8_t *const data = reinterpret_cast(unsafeData()); uint8_t iv[8]; - for(int i=0;i<8;++i) iv[i] = data[i]; + for(int i=0;i<8;++i) { + iv[i] = data[i]; + } iv[7] &= 0xf8; // mask off least significant 3 bits of packet ID / IV since this is unset when this function gets called Salsa20 s20(key,iv); s20.crypt12(data + start,data + start,len); diff --git a/node/Packet.hpp b/node/Packet.hpp index d40e5eb43..e28fec6ab 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -461,8 +461,9 @@ public: */ inline void init(const Packet &p,unsigned int fragStart,unsigned int fragLen,unsigned int fragNo,unsigned int fragTotal) { - if ((fragStart + fragLen) > p.size()) + if ((fragStart + fragLen) > p.size()) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } setSize(fragLen + ZT_PROTO_MIN_FRAGMENT_LENGTH); // NOTE: this copies both the IV/packet ID and the destination address. @@ -1217,9 +1218,11 @@ public: */ inline void setFragmented(bool f) { - if (f) + if (f) { (*this)[ZT_PACKET_IDX_FLAGS] |= (char)ZT_PROTO_FLAG_FRAGMENTED; - else (*this)[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_FRAGMENTED); + } else { + (*this)[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_FRAGMENTED); + } } /** @@ -1265,9 +1268,11 @@ public: unsigned char &b = (*this)[ZT_PACKET_IDX_FLAGS]; b = (b & 0xc7) | (unsigned char)((c << 3) & 0x38); // bits: FFCCCHHH // Set DEPRECATED "encrypted" flag -- used by pre-1.0.3 peers - if (c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) + if (c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) { b |= ZT_PROTO_FLAG_ENCRYPTED; - else b &= (~ZT_PROTO_FLAG_ENCRYPTED); + } else { + b &= (~ZT_PROTO_FLAG_ENCRYPTED); + } } /** @@ -1405,8 +1410,9 @@ private: // IV and source/destination addresses. Using the addresses divides the // key space into two halves-- A->B and B->A (since order will change). - for(unsigned int i=0;i<18;++i) // 8 + (ZT_ADDRESS_LENGTH * 2) == 18 + for(unsigned int i=0;i<18;++i) { // 8 + (ZT_ADDRESS_LENGTH * 2) == 18 out[i] = in[i] ^ d[i]; + } // Flags, but with hop count masked off. Hop count is altered by forwarding // nodes. It's one of the only parts of a packet modifiable by people @@ -1419,8 +1425,9 @@ private: out[20] = in[20] ^ (unsigned char)((size() >> 8) & 0xff); // little endian // Rest of raw key is used unchanged - for(unsigned int i=21;i<32;++i) + for(unsigned int i=21;i<32;++i) { out[i] = in[i]; + } } }; diff --git a/node/Path.hpp b/node/Path.hpp index eb268695d..d7a40dbc5 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -165,8 +165,7 @@ public: unsigned int pl = _latency; if (pl < 0xffff) { _latency = (pl + l) / 2; - } - else { + } else { _latency = l; } } @@ -230,8 +229,9 @@ public: // tunnels due to very spotty performance and low MTU issues over // these IPv6 tunnel links. const uint8_t *ipd = reinterpret_cast(reinterpret_cast(&a)->sin6_addr.s6_addr); - if ((ipd[0] == 0x20)&&(ipd[1] == 0x01)&&(ipd[2] == 0x04)&&(ipd[3] == 0x70)) + if ((ipd[0] == 0x20)&&(ipd[1] == 0x01)&&(ipd[2] == 0x04)&&(ipd[3] == 0x70)) { return false; + } } return true; default: diff --git a/node/Peer.cpp b/node/Peer.cpp index 34e3b9c4f..f9db88ea9 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -52,8 +52,9 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident _localMultipathSupported(false), _lastComputedAggregateMeanLatency(0) { - if (!myIdentity.agree(peerIdentity,_key)) + if (!myIdentity.agree(peerIdentity,_key)) { throw ZT_EXCEPTION_INVALID_ARGUMENT; + } uint8_t ktmp[ZT_SYMMETRIC_KEY_SIZE]; KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K0,0,0,ktmp); @@ -146,8 +147,7 @@ void Peer::received( } } } - } - else { + } else { replacePath = i; break; } @@ -268,7 +268,9 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int32 bestPath = i; } } - } else break; + } else { + break; + } } if (bestPath != ZT_MAX_PEER_NETWORK_PATHS) { return _paths[bestPath].p; @@ -317,7 +319,9 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o } break; } - } else break; + } else { + break; + } } Mutex::Lock _l2(other->_paths_m); @@ -340,7 +344,9 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o } break; } - } else break; + } else { + break; + } } unsigned int mine = ZT_MAX_PEER_NETWORK_PATHS; @@ -518,8 +524,7 @@ unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now) for(unsigned int i=0;isent(now); sent |= (_paths[i].p->address().ss_family == AF_INET) ? 0x1 : 0x2; } - } - else { + } else { _paths[i] = _PeerPath(); deletionOccurred = true; } @@ -572,7 +576,9 @@ void Peer::clusterRedirect(void *tPtr,const SharedPtr &originatingPath,con newPriority = _paths[i].priority; break; } - } else break; + } else { + break; + } } newPriority += 2; @@ -582,8 +588,9 @@ void Peer::clusterRedirect(void *tPtr,const SharedPtr &originatingPath,con for(unsigned int i=0;i= newPriority)&&(!_paths[i].p->address().ipsEqual2(remoteAddress))) { - if (i != j) + if (i != j) { _paths[j] = _paths[i]; + } ++j; } } @@ -613,7 +620,9 @@ void Peer::resetWithinScope(void *tPtr,InetAddress::IpScope scope,int inetAddres _paths[i].p->sent(now); _paths[i].lr = 0; // path will not be used unless it speaks again } - } else break; + } else { + break; + } } } diff --git a/node/Peer.hpp b/node/Peer.hpp index e6e9b65ed..640ed1a2e 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -119,9 +119,12 @@ public: Mutex::Lock _l(_paths_m); for(unsigned int i=0;iaddress() == addr)) + if (((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION)&&(_paths[i].p->address() == addr)) { return true; - } else break; + } + } else { + break; + } } return false; } @@ -139,8 +142,9 @@ public: inline bool sendDirect(void *tPtr,const void *data,unsigned int len,int64_t now,bool force) { SharedPtr bp(getAppropriatePath(now,force)); - if (bp) + if (bp) { return bp->send(RR,tPtr,data,len,now); + } return false; } @@ -281,7 +285,9 @@ public: std::vector< SharedPtr > pp; Mutex::Lock _l(_paths_m); for(unsigned int i=0;i= ZT_PEER_ACTIVITY_TIMEOUT) + if (tsr >= ZT_PEER_ACTIVITY_TIMEOUT) { return (~(unsigned int)0); + } unsigned int l = latency(now); - if (!l) + if (!l) { l = 0xffff; + } return (l * (((unsigned int)tsr / (ZT_PEER_PING_PERIOD + 1000)) + 1)); } @@ -380,9 +388,11 @@ public: */ inline bool rateGatePushDirectPaths(const int64_t now) { - if ((now - _lastDirectPathPushReceive) <= ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME) + if ((now - _lastDirectPathPushReceive) <= ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME) { ++_directPathPushCutoffCount; - else _directPathPushCutoffCount = 0; + } else { + _directPathPushCutoffCount = 0; + } _lastDirectPathPushReceive = now; return (_directPathPushCutoffCount < ZT_PUSH_DIRECT_PATHS_CUTOFF_LIMIT); } @@ -444,13 +454,16 @@ public: Mutex::Lock _l(_paths_m); unsigned int pc = 0; for(unsigned int i=0;iaddress().serialize(b); + } } } @@ -459,31 +472,39 @@ public: { try { unsigned int ptr = 0; - if (b[ptr++] != 2) + if (b[ptr++] != 2) { return SharedPtr(); + } Identity id; ptr += id.deserialize(b,ptr); - if (!id) + if (!id) { return SharedPtr(); + } SharedPtr p(new Peer(renv,renv->identity,id)); - p->_vProto = b.template at(ptr); ptr += 2; - p->_vMajor = b.template at(ptr); ptr += 2; - p->_vMinor = b.template at(ptr); ptr += 2; - p->_vRevision = b.template at(ptr); ptr += 2; + p->_vProto = b.template at(ptr); + ptr += 2; + p->_vMajor = b.template at(ptr); + ptr += 2; + p->_vMinor = b.template at(ptr); + ptr += 2; + p->_vRevision = b.template at(ptr); + ptr += 2; // When we deserialize from the cache we don't actually restore paths. We // just try them and then re-learn them if they happen to still be up. // Paths are fairly ephemeral in the real world in most cases. - const unsigned int tryPathCount = b.template at(ptr); ptr += 2; + const unsigned int tryPathCount = b.template at(ptr); + ptr += 2; for(unsigned int i=0;iattemptToContactAt(tPtr,-1,inaddr,now,true); + } } catch ( ... ) { break; } diff --git a/node/Poly1305.cpp b/node/Poly1305.cpp index c670e5d05..382465135 100644 --- a/node/Poly1305.cpp +++ b/node/Poly1305.cpp @@ -192,8 +192,9 @@ static inline void poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) if (st->leftover) { size_t i = st->leftover; st->buffer[i] = 1; - for (i = i + 1; i < poly1305_block_size; i++) + for (i = i + 1; i < poly1305_block_size; i++) { st->buffer[i] = 0; + } st->final = 1; poly1305_blocks(st, st->buffer, poly1305_block_size); } @@ -391,8 +392,9 @@ poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) { if (st->leftover) { size_t i = st->leftover; st->buffer[i++] = 1; - for (; i < poly1305_block_size; i++) + for (; i < poly1305_block_size; i++) { st->buffer[i] = 0; + } st->final = 1; poly1305_blocks(st, st->buffer, poly1305_block_size); } @@ -477,15 +479,18 @@ static inline void poly1305_update(poly1305_context *ctx, const unsigned char *m /* handle leftover */ if (st->leftover) { size_t want = (poly1305_block_size - st->leftover); - if (want > bytes) + if (want > bytes) { want = bytes; - for (i = 0; i < want; i++) + } + for (i = 0; i < want; i++) { st->buffer[st->leftover + i] = m[i]; + } bytes -= want; m += want; st->leftover += want; - if (st->leftover < poly1305_block_size) + if (st->leftover < poly1305_block_size) { return; + } poly1305_blocks(st, st->buffer, poly1305_block_size); st->leftover = 0; } @@ -500,8 +505,9 @@ static inline void poly1305_update(poly1305_context *ctx, const unsigned char *m /* store leftover */ if (bytes) { - for (i = 0; i < bytes; i++) + for (i = 0; i < bytes; i++) { st->buffer[st->leftover + i] = m[i]; + } st->leftover += bytes; } } diff --git a/node/Revocation.cpp b/node/Revocation.cpp index f196b43c7..9af6d5681 100644 --- a/node/Revocation.cpp +++ b/node/Revocation.cpp @@ -23,8 +23,9 @@ namespace ZeroTier { int Revocation::verify(const RuntimeEnvironment *RR,void *tPtr) const { - if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) { return -1; + } const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); if (!id) { RR->sw->requestWhois(tPtr,RR->node->now(),_signedBy); diff --git a/node/Revocation.hpp b/node/Revocation.hpp index e1c4e18fc..dd717168d 100644 --- a/node/Revocation.hpp +++ b/node/Revocation.hpp @@ -118,7 +118,9 @@ public: template inline void serialize(Buffer &b,const bool forSign = false) const { - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } b.append((uint32_t)0); // 4 unused bytes, currently set to 0 b.append(_id); @@ -140,7 +142,9 @@ public: // This is the size of any additional fields, currently 0. b.append((uint16_t)0); - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } } template @@ -151,14 +155,21 @@ public: unsigned int p = startAt; p += 4; // 4 bytes, currently unused - _id = b.template at(p); p += 4; - _networkId = b.template at(p); p += 8; + _id = b.template at(p); + p += 4; + _networkId = b.template at(p); + p += 8; p += 4; // 4 bytes, currently unused - _credentialId = b.template at(p); p += 4; - _threshold = (int64_t)b.template at(p); p += 8; - _flags = b.template at(p); p += 8; - _target.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _credentialId = b.template at(p); + p += 4; + _threshold = (int64_t)b.template at(p); + p += 8; + _flags = b.template at(p); + p += 8; + _target.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; + _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; _type = (Credential::Type)b[p++]; if (b[p++] == 1) { @@ -166,14 +177,17 @@ public: p += 2; memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; - } else throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } else { + throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } } else { p += 2 + b.template at(p); } p += 2 + b.template at(p); - if (p > b.size()) + if (p > b.size()) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } return (p - startAt); } diff --git a/node/RingBuffer.hpp b/node/RingBuffer.hpp index e2b90ce5c..53834b4f7 100644 --- a/node/RingBuffer.hpp +++ b/node/RingBuffer.hpp @@ -193,11 +193,9 @@ public: { if (end == begin) { return wrap ? S : 0; - } - else if (end > begin) { + } else if (end > begin) { return end - begin; - } - else { + } else { return S + end - begin; } } diff --git a/node/SHA512.cpp b/node/SHA512.cpp index 2fe126a84..bd81647d0 100644 --- a/node/SHA512.cpp +++ b/node/SHA512.cpp @@ -58,12 +58,15 @@ static ZT_INLINE void sha512_compress(sha512_state *const md,uint8_t *const buf) uint64_t S[8], W[80], t0, t1; int i; - for (i = 0; i < 8; i++) + for (i = 0; i < 8; i++) { S[i] = md->state[i]; - for (i = 0; i < 16; i++) + } + for (i = 0; i < 16; i++) { LOAD64H(W[i], buf + (8*i)); - for (i = 16; i < 80; i++) + } + for (i = 16; i < 80; i++) { W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; + } #define RND(a,b,c,d,e,f,g,h,i) \ t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ @@ -82,8 +85,9 @@ static ZT_INLINE void sha512_compress(sha512_state *const md,uint8_t *const buf) RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7); } - for (i = 0; i < 8; i++) + for (i = 0; i < 8; i++) { md->state[i] = md->state[i] + S[i]; + } } static ZT_INLINE void sha384_init(sha512_state *const md) diff --git a/node/Salsa20.cpp b/node/Salsa20.cpp index ca9e47185..63573546e 100644 --- a/node/Salsa20.cpp +++ b/node/Salsa20.cpp @@ -121,8 +121,9 @@ void Salsa20::crypt12(const void *in,void *out,unsigned int bytes) uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; #endif - if (!bytes) + if (!bytes) { return; + } #ifndef ZT_SALSA20_SSE j0 = _state.i[0]; @@ -145,8 +146,9 @@ void Salsa20::crypt12(const void *in,void *out,unsigned int bytes) for (;;) { if (bytes < 64) { - for (i = 0;i < bytes;++i) + for (i = 0;i < bytes;++i) { tmp[i] = m[i]; + } m = tmp; ctarget = c; c = tmp; @@ -589,8 +591,9 @@ void Salsa20::crypt12(const void *in,void *out,unsigned int bytes) if (bytes <= 64) { if (bytes < 64) { - for (i = 0;i < bytes;++i) + for (i = 0;i < bytes;++i) { ctarget[i] = c[i]; + } } #ifndef ZT_SALSA20_SSE @@ -620,8 +623,9 @@ void Salsa20::crypt20(const void *in,void *out,unsigned int bytes) uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; #endif - if (!bytes) + if (!bytes) { return; + } #ifndef ZT_SALSA20_SSE j0 = _state.i[0]; @@ -644,8 +648,9 @@ void Salsa20::crypt20(const void *in,void *out,unsigned int bytes) for (;;) { if (bytes < 64) { - for (i = 0;i < bytes;++i) + for (i = 0;i < bytes;++i) { tmp[i] = m[i]; + } m = tmp; ctarget = c; c = tmp; @@ -1320,8 +1325,9 @@ void Salsa20::crypt20(const void *in,void *out,unsigned int bytes) if (bytes <= 64) { if (bytes < 64) { - for (i = 0;i < bytes;++i) + for (i = 0;i < bytes;++i) { ctarget[i] = c[i]; + } } #ifndef ZT_SALSA20_SSE diff --git a/node/SelfAwareness.cpp b/node/SelfAwareness.cpp index 45bc25410..c87f4e3e8 100644 --- a/node/SelfAwareness.cpp +++ b/node/SelfAwareness.cpp @@ -61,8 +61,9 @@ void SelfAwareness::iam(void *tPtr,const Address &reporter,const int64_t receive { const InetAddress::IpScope scope = myPhysicalAddress.ipScope(); - if ((scope != reporterPhysicalAddress.ipScope())||(scope == InetAddress::IP_SCOPE_NONE)||(scope == InetAddress::IP_SCOPE_LOOPBACK)||(scope == InetAddress::IP_SCOPE_MULTICAST)) + if ((scope != reporterPhysicalAddress.ipScope())||(scope == InetAddress::IP_SCOPE_NONE)||(scope == InetAddress::IP_SCOPE_LOOPBACK)||(scope == InetAddress::IP_SCOPE_MULTICAST)) { return; + } Mutex::Lock _l(_phy_m); PhySurfaceEntry &entry = _phy[PhySurfaceKey(reporter,receivedOnLocalSocket,reporterPhysicalAddress,scope)]; @@ -83,8 +84,9 @@ void SelfAwareness::iam(void *tPtr,const Address &reporter,const int64_t receive PhySurfaceKey *k = (PhySurfaceKey *)0; PhySurfaceEntry *e = (PhySurfaceEntry *)0; while (i.next(k,e)) { - if ((k->reporterPhysicalAddress != reporterPhysicalAddress)&&(k->scope == scope)) + if ((k->reporterPhysicalAddress != reporterPhysicalAddress)&&(k->scope == scope)) { _phy.erase(*k); + } } } @@ -121,8 +123,9 @@ void SelfAwareness::clean(int64_t now) PhySurfaceKey *k = (PhySurfaceKey *)0; PhySurfaceEntry *e = (PhySurfaceEntry *)0; while (i.next(k,e)) { - if ((now - e->ts) >= ZT_SELFAWARENESS_ENTRY_TIMEOUT) + if ((now - e->ts) >= ZT_SELFAWARENESS_ENTRY_TIMEOUT) { _phy.erase(*k); + } } } diff --git a/node/SharedPtr.hpp b/node/SharedPtr.hpp index dff0fa4e9..1116be40a 100644 --- a/node/SharedPtr.hpp +++ b/node/SharedPtr.hpp @@ -37,8 +37,9 @@ public: ~SharedPtr() { if (_ptr) { - if (--_ptr->__refCount <= 0) + if (--_ptr->__refCount <= 0) { delete _ptr; + } } } @@ -47,8 +48,9 @@ public: if (_ptr != sp._ptr) { T *p = sp._getAndInc(); if (_ptr) { - if (--_ptr->__refCount <= 0) + if (--_ptr->__refCount <= 0) { delete _ptr; + } } _ptr = p; } @@ -97,8 +99,9 @@ public: inline void zero() { if (_ptr) { - if (--_ptr->__refCount <= 0) + if (--_ptr->__refCount <= 0) { delete _ptr; + } _ptr = (T *)0; } } @@ -108,8 +111,9 @@ public: */ inline int references() { - if (_ptr) + if (_ptr) { return _ptr->__refCount.load(); + } return 0; } @@ -123,8 +127,9 @@ public: private: inline T *_getAndInc() const { - if (_ptr) + if (_ptr) { ++_ptr->__refCount; + } return _ptr; } T *_ptr; diff --git a/node/Switch.cpp b/node/Switch.cpp index 7b1d47ac9..5359180e4 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -46,8 +46,9 @@ Switch::Switch(const RuntimeEnvironment *renv) : // Returns true if packet appears valid; pos and proto will be set static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsigned int &pos,unsigned int &proto) { - if (frameLen < 40) + if (frameLen < 40) { return false; + } pos = 40; proto = frameData[6]; while (pos <= frameLen) { @@ -56,8 +57,9 @@ static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsig case 43: // routing case 60: // destination options case 135: // mobility options - if ((pos + 8) > frameLen) + if ((pos + 8) > frameLen) { return false; // invalid! + } proto = frameData[pos]; pos += ((unsigned int)frameData[pos + 1] * 8) + 8; break; @@ -88,10 +90,12 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre * locate peers with versions <1.0.4. */ const Address beaconAddr(reinterpret_cast(data) + 8,5); - if (beaconAddr == RR->identity.address()) + if (beaconAddr == RR->identity.address()) { return; - if (!RR->node->shouldUsePathForZeroTierTraffic(tPtr,beaconAddr,localSocket,fromAddr)) + } + if (!RR->node->shouldUsePathForZeroTierTraffic(tPtr,beaconAddr,localSocket,fromAddr)) { return; + } const SharedPtr peer(RR->topology->getPeer(tPtr,beaconAddr)); if (peer) { // we'll only respond to beacons from known peers if ((now - _lastBeaconResponse) >= 2500) { // limit rate of responses @@ -110,8 +114,9 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre const Address destination(fragment.destination()); if (destination != RR->identity.address()) { - if ( (!RR->topology->amUpstream()) && (!path->trustEstablished(now)) ) + if ( (!RR->topology->amUpstream()) && (!path->trustEstablished(now)) ) { return; + } if (fragment.hops() < ZT_RELAY_MAX_HOPS) { fragment.incrementHops(); @@ -122,8 +127,9 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if ((!relayTo)||(!relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now,false))) { // Don't know peer or no direct path -- so relay via someone upstream relayTo = RR->topology->getUpstreamPeer(); - if (relayTo) + if (relayTo) { relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now,true); + } } } } else { @@ -159,8 +165,9 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if (Utils::countBits(rq->haveFragments |= (1 << fragmentNumber)) == totalFragments) { // We have all fragments -- assemble and process full Packet - for(unsigned int f=1;ffrag0.append(rq->frags[f - 1].payload(),rq->frags[f - 1].payloadLength()); + } if (rq->frag0.tryDecode(RR,tPtr,flowId)) { rq->timestamp = 0; // packet decoded, free entry @@ -179,12 +186,14 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre const Address destination(reinterpret_cast(data) + 8,ZT_ADDRESS_LENGTH); const Address source(reinterpret_cast(data) + 13,ZT_ADDRESS_LENGTH); - if (source == RR->identity.address()) + if (source == RR->identity.address()) { return; + } if (destination != RR->identity.address()) { - if ( (!RR->topology->amUpstream()) && (!path->trustEstablished(now)) && (source != RR->identity.address()) ) + if ( (!RR->topology->amUpstream()) && (!path->trustEstablished(now)) && (source != RR->identity.address()) ) { return; + } Packet packet(data,len); @@ -194,16 +203,18 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if ((relayTo)&&(relayTo->sendDirect(tPtr,packet.data(),packet.size(),now,false))) { if ((source != RR->identity.address())&&(_shouldUnite(now,source,destination))) { const SharedPtr sourcePeer(RR->topology->getPeer(tPtr,source)); - if (sourcePeer) + if (sourcePeer) { relayTo->introduce(tPtr,now,sourcePeer); + } } } else { relayTo = RR->topology->getUpstreamPeer(); if ((relayTo)&&(relayTo->address() != source)) { if (relayTo->sendDirect(tPtr,packet.data(),packet.size(),now,true)) { const SharedPtr sourcePeer(RR->topology->getPeer(tPtr,source)); - if (sourcePeer) + if (sourcePeer) { relayTo->introduce(tPtr,now,sourcePeer); + } } } } @@ -241,8 +252,9 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre // We have all fragments -- assemble and process full Packet rq->frag0.init(data,len,path,now); - for(unsigned int f=1;ftotalFragments;++f) + for(unsigned int f=1;ftotalFragments;++f) { rq->frag0.append(rq->frags[f - 1].payload(),rq->frags[f - 1].payloadLength()); + } if (rq->frag0.tryDecode(RR,tPtr,flowId)) { rq->timestamp = 0; // packet decoded, free entry @@ -278,8 +290,9 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) { - if (!network->hasConfig()) + if (!network->hasConfig()) { return; + } // Check if this packet is from someone other than the tap -- i.e. bridged in bool fromBridged; @@ -402,8 +415,9 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const if ((sipNetmaskBits == 88)&&(my6[0] == 0xfd)&&(my6[9] == 0x99)&&(my6[10] == 0x93)) { // ZT-RFC4193 /88 ??? unsigned int ptr = 0; while (ptr != 11) { - if (pkt6[ptr] != my6[ptr]) + if (pkt6[ptr] != my6[ptr]) { break; + } ++ptr; } if (ptr == 11) { // prefix match! @@ -415,8 +429,9 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const if ( (my6[0] == 0xfc) && (my6[1] == (uint8_t)((nwid32 >> 24) & 0xff)) && (my6[2] == (uint8_t)((nwid32 >> 16) & 0xff)) && (my6[3] == (uint8_t)((nwid32 >> 8) & 0xff)) && (my6[4] == (uint8_t)(nwid32 & 0xff))) { unsigned int ptr = 0; while (ptr != 5) { - if (pkt6[ptr] != my6[ptr]) + if (pkt6[ptr] != my6[ptr]) { break; + } ++ptr; } if (ptr == 5) { // prefix match! @@ -432,27 +447,63 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const const MAC peerMac(v6EmbeddedAddress,network->id()); uint8_t adv[72]; - adv[0] = 0x60; adv[1] = 0x00; adv[2] = 0x00; adv[3] = 0x00; - adv[4] = 0x00; adv[5] = 0x20; - adv[6] = 0x3a; adv[7] = 0xff; - for(int i=0;i<16;++i) adv[8 + i] = pkt6[i]; - for(int i=0;i<16;++i) adv[24 + i] = my6[i]; - adv[40] = 0x88; adv[41] = 0x00; - adv[42] = 0x00; adv[43] = 0x00; // future home of checksum - adv[44] = 0x60; adv[45] = 0x00; adv[46] = 0x00; adv[47] = 0x00; - for(int i=0;i<16;++i) adv[48 + i] = pkt6[i]; - adv[64] = 0x02; adv[65] = 0x01; - adv[66] = peerMac[0]; adv[67] = peerMac[1]; adv[68] = peerMac[2]; adv[69] = peerMac[3]; adv[70] = peerMac[4]; adv[71] = peerMac[5]; + adv[0] = 0x60; + adv[1] = 0x00; + adv[2] = 0x00; + adv[3] = 0x00; + adv[4] = 0x00; + adv[5] = 0x20; + adv[6] = 0x3a; + adv[7] = 0xff; + for(int i=0;i<16;++i) { + adv[8 + i] = pkt6[i]; + } + for(int i=0;i<16;++i) { + adv[24 + i] = my6[i]; + } + adv[40] = 0x88; + adv[41] = 0x00; + adv[42] = 0x00; + adv[43] = 0x00; // future home of checksum + adv[44] = 0x60; + adv[45] = 0x00; + adv[46] = 0x00; + adv[47] = 0x00; + for(int i=0;i<16;++i) { + adv[48 + i] = pkt6[i]; + } + adv[64] = 0x02; + adv[65] = 0x01; + adv[66] = peerMac[0]; + adv[67] = peerMac[1]; + adv[68] = peerMac[2]; + adv[69] = peerMac[3]; + adv[70] = peerMac[4]; + adv[71] = peerMac[5]; uint16_t pseudo_[36]; uint8_t *const pseudo = reinterpret_cast(pseudo_); - for(int i=0;i<32;++i) pseudo[i] = adv[8 + i]; - pseudo[32] = 0x00; pseudo[33] = 0x00; pseudo[34] = 0x00; pseudo[35] = 0x20; - pseudo[36] = 0x00; pseudo[37] = 0x00; pseudo[38] = 0x00; pseudo[39] = 0x3a; - for(int i=0;i<32;++i) pseudo[40 + i] = adv[40 + i]; + for(int i=0;i<32;++i) { + pseudo[i] = adv[8 + i]; + } + pseudo[32] = 0x00; + pseudo[33] = 0x00; + pseudo[34] = 0x00; + pseudo[35] = 0x20; + pseudo[36] = 0x00; + pseudo[37] = 0x00; + pseudo[38] = 0x00; + pseudo[39] = 0x3a; + for(int i=0;i<32;++i) { + pseudo[40 + i] = adv[40 + i]; + } uint32_t checksum = 0; - for(int i=0;i<36;++i) checksum += Utils::hton(pseudo_[i]); - while ((checksum >> 16)) checksum = (checksum & 0xffff) + (checksum >> 16); + for(int i=0;i<36;++i) { + checksum += Utils::hton(pseudo_[i]); + } + while ((checksum >> 16)) { + checksum = (checksum & 0xffff) + (checksum >> 16); + } checksum = ~checksum; adv[42] = (checksum >> 8) & 0xff; adv[43] = checksum & 0xff; @@ -473,8 +524,9 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const * Note that some OSes, most notably Linux, do this for you by learning * multicast addresses on bridge interfaces and subscribing each slave. * But in that case this does no harm, as the sets are just merged. */ - if (fromBridged) + if (fromBridged) { network->learnBridgedMulticastGroup(tPtr,multicastGroup,RR->node->now()); + } // First pass sets noTee to false, but noTee is set to true in OutboundMulticast to prevent duplicates. if (!network->filterOutgoingPacket(tPtr,false,RR->identity.address(),Address(),from,to,(const uint8_t *)data,len,etherType,vlanId,qosBucket)) { @@ -563,12 +615,15 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const } else { // Otherwise pick a random set of them while (numBridges < ZT_MAX_BRIDGE_SPAM) { - if (ab == activeBridges.end()) + if (ab == activeBridges.end()) { ab = activeBridges.begin(); + } if (((unsigned long)RR->node->prng() % (unsigned long)activeBridges.size()) == 0) { bridges[numBridges++] = *ab; ++ab; - } else ++ab; + } else { + ++ab; + } } } } @@ -627,11 +682,13 @@ void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet & if (nqcb->oldQueues[i]->id == qosBucket) { selectedQueue = nqcb->oldQueues[i]; } - } if (i < nqcb->newQueues.size()) { // search new queues (this would imply not often-used queues) + } + if (i < nqcb->newQueues.size()) { // search new queues (this would imply not often-used queues) if (nqcb->newQueues[i]->id == qosBucket) { selectedQueue = nqcb->newQueues[i]; } - } if (i < nqcb->inactiveQueues.size()) { // search inactive queues + } + if (i < nqcb->inactiveQueues.size()) { // search inactive queues if (nqcb->inactiveQueues[i]->id == qosBucket) { selectedQueue = nqcb->inactiveQueues[i]; // move queue to end of NEW queue list @@ -655,8 +712,7 @@ void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet & // Drop a packet if necessary ManagedQueue *selectedQueueToDropFrom = nullptr; - if (nqcb->_currEnqueuedPackets > ZT_AQM_MAX_ENQUEUED_PACKETS) - { + if (nqcb->_currEnqueuedPackets > ZT_AQM_MAX_ENQUEUED_PACKETS) { // DEBUG_INFO("too many enqueued packets (%d), finding packet to drop", nqcb->_currEnqueuedPackets); int maxQueueLength = 0; for (size_t i=0; i &network, Packet & maxQueueLength = nqcb->oldQueues[i]->byteLength; selectedQueueToDropFrom = nqcb->oldQueues[i]; } - } if (i < nqcb->newQueues.size()) { + } + if (i < nqcb->newQueues.size()) { if (nqcb->newQueues[i]->byteLength > maxQueueLength) { maxQueueLength = nqcb->newQueues[i]->byteLength; selectedQueueToDropFrom = nqcb->newQueues[i]; } - } if (i < nqcb->inactiveQueues.size()) { + } + if (i < nqcb->inactiveQueues.size()) { if (nqcb->inactiveQueues[i]->byteLength > maxQueueLength) { maxQueueLength = nqcb->inactiveQueues[i]->byteLength; selectedQueueToDropFrom = nqcb->inactiveQueues[i]; @@ -785,8 +843,7 @@ void Switch::aqm_dequeue(void *tPtr) // DEBUG_INFO("moving q=%p from NEW to OLD list", queueAtFrontOfList); oldQueues->push_back(queueAtFrontOfList); currQueues->erase(currQueues->begin()); - } - else { + } else { int len = entryToEmit->packet.payloadLength(); queueAtFrontOfList->byteLength -= len; queueAtFrontOfList->byteCredit -= len; @@ -818,8 +875,7 @@ void Switch::aqm_dequeue(void *tPtr) // Move to inactive list of queues inactiveQueues->push_back(queueAtFrontOfList); currQueues->erase(currQueues->begin()); - } - else { + } else { int len = entryToEmit->packet.payloadLength(); queueAtFrontOfList->byteLength -= len; queueAtFrontOfList->byteCredit -= len; @@ -863,22 +919,26 @@ void Switch::send(void *tPtr,Packet &packet,bool encrypt,int32_t flowId) } _txQueue.push_back(TXQueueEntry(dest,RR->node->now(),packet,encrypt,flowId)); } - if (!RR->topology->getPeer(tPtr,dest)) + if (!RR->topology->getPeer(tPtr,dest)) { requestWhois(tPtr,RR->node->now(),dest); + } } } void Switch::requestWhois(void *tPtr,const int64_t now,const Address &addr) { - if (addr == RR->identity.address()) + if (addr == RR->identity.address()) { return; + } { Mutex::Lock _l(_lastSentWhoisRequest_m); int64_t &last = _lastSentWhoisRequest[addr]; - if ((now - last) < ZT_WHOIS_RETRY_DELAY) + if ((now - last) < ZT_WHOIS_RETRY_DELAY) { return; - else last = now; + } else { + last = now; + } } const SharedPtr upstream(RR->topology->getUpstreamPeer()); @@ -902,8 +962,9 @@ void Switch::doAnythingWaitingForPeer(void *tPtr,const SharedPtr &peer) RXQueueEntry *const rq = &(_rxQueue[ptr]); Mutex::Lock rql(rq->lock); if ((rq->timestamp)&&(rq->complete)) { - if ((rq->frag0.tryDecode(RR,tPtr,rq->flowId))||((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) + if ((rq->frag0.tryDecode(RR,tPtr,rq->flowId))||((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) { rq->timestamp = 0; + } } } @@ -926,8 +987,9 @@ void Switch::doAnythingWaitingForPeer(void *tPtr,const SharedPtr &peer) unsigned long Switch::doTimerTasks(void *tPtr,int64_t now) { const uint64_t timeSinceLastCheck = now - _lastCheckedQueues; - if (timeSinceLastCheck < ZT_WHOIS_RETRY_DELAY) + if (timeSinceLastCheck < ZT_WHOIS_RETRY_DELAY) { return (unsigned long)(ZT_WHOIS_RETRY_DELAY - timeSinceLastCheck); + } _lastCheckedQueues = now; std::vector
needWhois; @@ -940,14 +1002,16 @@ unsigned long Switch::doTimerTasks(void *tPtr,int64_t now) } else if ((now - txi->creationTime) > ZT_TRANSMIT_QUEUE_TIMEOUT) { _txQueue.erase(txi++); } else { - if (!RR->topology->getPeer(tPtr,txi->dest)) + if (!RR->topology->getPeer(tPtr,txi->dest)) { needWhois.push_back(txi->dest); + } ++txi; } } } - for(std::vector
::const_iterator i(needWhois.begin());i!=needWhois.end();++i) + for(std::vector
::const_iterator i(needWhois.begin());i!=needWhois.end();++i) { requestWhois(tPtr,now,*i); + } for(unsigned int ptr=0;ptrtimestamp = 0; } else { const Address src(rq->frag0.source()); - if (!RR->topology->getPeer(tPtr,src)) + if (!RR->topology->getPeer(tPtr,src)) { requestWhois(tPtr,now,src); + } } } } @@ -969,8 +1034,9 @@ unsigned long Switch::doTimerTasks(void *tPtr,int64_t now) _LastUniteKey *k = (_LastUniteKey *)0; uint64_t *v = (uint64_t *)0; while (i.next(k,v)) { - if ((now - *v) >= (ZT_MIN_UNITE_INTERVAL * 8)) + if ((now - *v) >= (ZT_MIN_UNITE_INTERVAL * 8)) { _lastUniteAttempt.erase(*k); + } } } @@ -980,8 +1046,9 @@ unsigned long Switch::doTimerTasks(void *tPtr,int64_t now) Address *a = (Address *)0; int64_t *ts = (int64_t *)0; while (i.next(a,ts)) { - if ((now - *ts) > (ZT_WHOIS_RETRY_DELAY * 2)) + if ((now - *ts) > (ZT_WHOIS_RETRY_DELAY * 2)) { _lastSentWhoisRequest.erase(*a); + } } } @@ -1018,15 +1085,15 @@ bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt,int32_t flowId) } } return true; - } - else { + } else { viaPath = peer->getAppropriatePath(now,false,flowId); if (!viaPath) { peer->tryMemorizedPath(tPtr,now); // periodically attempt memorized or statically defined paths, if any are known const SharedPtr relay(RR->topology->getUpstreamPeer()); if ( (!relay) || (!(viaPath = relay->getAppropriatePath(now,false,flowId))) ) { - if (!(viaPath = peer->getAppropriatePath(now,true,flowId))) + if (!(viaPath = peer->getAppropriatePath(now,true,flowId))) { return false; + } } } if (viaPath) { @@ -1068,8 +1135,9 @@ void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtr(_rxQueuePtr.load()); for(unsigned int k=1;k<=ZT_RX_QUEUE_SIZE;++k) { RXQueueEntry *rq = &(_rxQueue[(current - k) % ZT_RX_QUEUE_SIZE]); - if ((rq->packetId == packetId)&&(rq->timestamp)) + if ((rq->packetId == packetId)&&(rq->timestamp)) { return rq; + } } ++_rxQueuePtr; return &(_rxQueue[static_cast(current) % ZT_RX_QUEUE_SIZE]); diff --git a/node/Tag.cpp b/node/Tag.cpp index 78f5d1f3c..537da11c9 100644 --- a/node/Tag.cpp +++ b/node/Tag.cpp @@ -23,8 +23,9 @@ namespace ZeroTier { int Tag::verify(const RuntimeEnvironment *RR,void *tPtr) const { - if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) { return -1; + } const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); if (!id) { RR->sw->requestWhois(tPtr,RR->node->now(),_signedBy); diff --git a/node/Tag.hpp b/node/Tag.hpp index cad8a5c31..c8d00b0c6 100644 --- a/node/Tag.hpp +++ b/node/Tag.hpp @@ -116,7 +116,9 @@ public: template inline void serialize(Buffer &b,const bool forSign = false) const { - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } b.append(_networkId); b.append(_ts); @@ -133,7 +135,9 @@ public: b.append((uint16_t)0); // length of additional fields, currently 0 - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } } template @@ -143,26 +147,35 @@ public: *this = Tag(); - _networkId = b.template at(p); p += 8; - _ts = b.template at(p); p += 8; - _id = b.template at(p); p += 4; + _networkId = b.template at(p); + p += 8; + _ts = b.template at(p); + p += 8; + _id = b.template at(p); + p += 4; - _value = b.template at(p); p += 4; + _value = b.template at(p); + p += 4; - _issuedTo.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _issuedTo.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; + _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; if (b[p++] == 1) { - if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) + if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } p += 2; - memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; } else { p += 2 + b.template at(p); } p += 2 + b.template at(p); - if (p > b.size()) + if (p > b.size()) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } return (p - startAt); } diff --git a/node/Topology.cpp b/node/Topology.cpp index 6a6675ab2..c2e5bb4b3 100644 --- a/node/Topology.cpp +++ b/node/Topology.cpp @@ -32,7 +32,8 @@ Topology::Topology(const RuntimeEnvironment *renv,void *tPtr) : { uint8_t tmp[ZT_WORLD_MAX_SERIALIZED_LENGTH]; uint64_t idtmp[2]; - idtmp[0] = 0; idtmp[1] = 0; + idtmp[0] = 0; + idtmp[1] = 0; int n = RR->node->stateObjectGet(tPtr,ZT_STATE_OBJECT_PLANET,idtmp,tmp,sizeof(tmp)); if (n > 0) { try { @@ -55,8 +56,9 @@ Topology::~Topology() Hashtable< Address,SharedPtr >::Iterator i(_peers); Address *a = (Address *)0; SharedPtr *p = (SharedPtr *)0; - while (i.next(a,p)) + while (i.next(a,p)) { _savePeer((void *)0,*p); + } } SharedPtr Topology::addPeer(void *tPtr,const SharedPtr &peer) @@ -65,8 +67,9 @@ SharedPtr Topology::addPeer(void *tPtr,const SharedPtr &peer) { Mutex::Lock _l(_peers_m); SharedPtr &hp = _peers[peer->address()]; - if (!hp) + if (!hp) { hp = peer; + } np = hp; } return np; @@ -74,26 +77,31 @@ SharedPtr Topology::addPeer(void *tPtr,const SharedPtr &peer) SharedPtr Topology::getPeer(void *tPtr,const Address &zta) { - if (zta == RR->identity.address()) + if (zta == RR->identity.address()) { return SharedPtr(); + } { Mutex::Lock _l(_peers_m); const SharedPtr *const ap = _peers.get(zta); - if (ap) + if (ap) { return *ap; + } } try { Buffer buf; - uint64_t idbuf[2]; idbuf[0] = zta.toInt(); idbuf[1] = 0; + uint64_t idbuf[2]; + idbuf[0] = zta.toInt(); + idbuf[1] = 0; int len = RR->node->stateObjectGet(tPtr,ZT_STATE_OBJECT_PEER,idbuf,buf.unsafeData(),ZT_PEER_MAX_SERIALIZED_STATE_SIZE); if (len > 0) { buf.setSize(len); Mutex::Lock _l(_peers_m); SharedPtr &ap = _peers[zta]; - if (ap) + if (ap) { return ap; + } ap = Peer::deserializeFromCache(RR->node->now(),tPtr,buf,RR); if (!ap) { _peers.erase(zta); @@ -112,8 +120,9 @@ Identity Topology::getIdentity(void *tPtr,const Address &zta) } else { Mutex::Lock _l(_peers_m); const SharedPtr *const ap = _peers.get(zta); - if (ap) + if (ap) { return (*ap)->identity(); + } } return Identity(); } @@ -138,8 +147,9 @@ SharedPtr Topology::getUpstreamPeer() } } - if (!best) + if (!best) { return SharedPtr(); + } return *best; } @@ -152,11 +162,13 @@ bool Topology::isUpstream(const Identity &id) const bool Topology::shouldAcceptWorldUpdateFrom(const Address &addr) const { Mutex::Lock _l(_upstreams_m); - if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),addr) != _upstreamAddresses.end()) + if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),addr) != _upstreamAddresses.end()) { return true; + } for(std::vector< std::pair< uint64_t,Address> >::const_iterator s(_moonSeeds.begin());s!=_moonSeeds.end();++s) { - if (s->second == addr) + if (s->second == addr) { return true; + } } return false; } @@ -166,8 +178,9 @@ ZT_PeerRole Topology::role(const Address &ztaddr) const Mutex::Lock _l(_upstreams_m); if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),ztaddr) != _upstreamAddresses.end()) { for(std::vector::const_iterator i(_planet.roots().begin());i!=_planet.roots().end();++i) { - if (i->identity.address() == ztaddr) + if (i->identity.address() == ztaddr) { return ZT_PEER_ROLE_PLANET; + } } return ZT_PEER_ROLE_MOON; } @@ -183,22 +196,26 @@ bool Topology::isProhibitedEndpoint(const Address &ztaddr,const InetAddress &ipa if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),ztaddr) != _upstreamAddresses.end()) { for(std::vector::const_iterator r(_planet.roots().begin());r!=_planet.roots().end();++r) { if (r->identity.address() == ztaddr) { - if (r->stableEndpoints.empty()) + if (r->stableEndpoints.empty()) { return false; // no stable endpoints specified, so allow dynamic paths + } for(std::vector::const_iterator e(r->stableEndpoints.begin());e!=r->stableEndpoints.end();++e) { - if (ipaddr.ipsEqual(*e)) + if (ipaddr.ipsEqual(*e)) { return false; + } } } } for(std::vector::const_iterator m(_moons.begin());m!=_moons.end();++m) { for(std::vector::const_iterator r(m->roots().begin());r!=m->roots().end();++r) { if (r->identity.address() == ztaddr) { - if (r->stableEndpoints.empty()) + if (r->stableEndpoints.empty()) { return false; // no stable endpoints specified, so allow dynamic paths + } for(std::vector::const_iterator e(r->stableEndpoints.begin());e!=r->stableEndpoints.end();++e) { - if (ipaddr.ipsEqual(*e)) + if (ipaddr.ipsEqual(*e)) { return false; + } } } } @@ -211,8 +228,9 @@ bool Topology::isProhibitedEndpoint(const Address &ztaddr,const InetAddress &ipa bool Topology::addWorld(void *tPtr,const World &newWorld,bool alwaysAcceptNew) { - if ((newWorld.type() != World::TYPE_PLANET)&&(newWorld.type() != World::TYPE_MOON)) + if ((newWorld.type() != World::TYPE_PLANET)&&(newWorld.type() != World::TYPE_MOON)) { return false; + } Mutex::Lock _l2(_peers_m); Mutex::Lock _l1(_upstreams_m); @@ -235,9 +253,11 @@ bool Topology::addWorld(void *tPtr,const World &newWorld,bool alwaysAcceptNew) } if (existing) { - if (existing->shouldBeReplacedBy(newWorld)) + if (existing->shouldBeReplacedBy(newWorld)) { *existing = newWorld; - else return false; + } else { + return false; + } } else if (newWorld.type() == World::TYPE_MOON) { if (alwaysAcceptNew) { _moons.push_back(newWorld); @@ -253,13 +273,15 @@ bool Topology::addWorld(void *tPtr,const World &newWorld,bool alwaysAcceptNew) break; } } - if (existing) + if (existing) { break; + } } } } - if (!existing) + if (!existing) { return false; + } } else { return false; } @@ -268,7 +290,8 @@ bool Topology::addWorld(void *tPtr,const World &newWorld,bool alwaysAcceptNew) Buffer sbuf; existing->serialize(sbuf,false); uint64_t idtmp[2]; - idtmp[0] = existing->id(); idtmp[1] = 0; + idtmp[0] = existing->id(); + idtmp[1] = 0; RR->node->stateObjectPut(tPtr,(existing->type() == World::TYPE_PLANET) ? ZT_STATE_OBJECT_PLANET : ZT_STATE_OBJECT_MOON,idtmp,sbuf.data(),sbuf.size()); } catch ( ... ) {} @@ -281,7 +304,8 @@ void Topology::addMoon(void *tPtr,const uint64_t id,const Address &seed) { char tmp[ZT_WORLD_MAX_SERIALIZED_LENGTH]; uint64_t idtmp[2]; - idtmp[0] = id; idtmp[1] = 0; + idtmp[0] = id; + idtmp[1] = 0; int n = RR->node->stateObjectGet(tPtr,ZT_STATE_OBJECT_MOON,idtmp,tmp,sizeof(tmp)); if (n > 0) { try { @@ -296,8 +320,9 @@ void Topology::addMoon(void *tPtr,const uint64_t id,const Address &seed) if (seed) { Mutex::Lock _l(_upstreams_m); - if (std::find(_moonSeeds.begin(),_moonSeeds.end(),std::pair(id,seed)) == _moonSeeds.end()) + if (std::find(_moonSeeds.begin(),_moonSeeds.end(),std::pair(id,seed)) == _moonSeeds.end()) { _moonSeeds.push_back(std::pair(id,seed)); + } } } @@ -312,7 +337,8 @@ void Topology::removeMoon(void *tPtr,const uint64_t id) nm.push_back(*m); } else { uint64_t idtmp[2]; - idtmp[0] = id; idtmp[1] = 0; + idtmp[0] = id; + idtmp[1] = 0; RR->node->stateObjectDelete(tPtr,ZT_STATE_OBJECT_MOON,idtmp); } } @@ -320,8 +346,9 @@ void Topology::removeMoon(void *tPtr,const uint64_t id) std::vector< std::pair > cm; for(std::vector< std::pair >::const_iterator m(_moonSeeds.begin());m!=_moonSeeds.end();++m) { - if (m->first != id) + if (m->first != id) { cm.push_back(*m); + } } _moonSeeds.swap(cm); @@ -350,8 +377,9 @@ void Topology::doPeriodicTasks(void *tPtr,int64_t now) Path::HashKey *k = (Path::HashKey *)0; SharedPtr *p = (SharedPtr *)0; while (i.next(k,p)) { - if (p->references() <= 1) + if (p->references() <= 1) { _paths.erase(*k); + } } } } @@ -382,8 +410,9 @@ void Topology::_memoizeUpstreams(void *tPtr) } else if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),i->identity.address()) == _upstreamAddresses.end()) { _upstreamAddresses.push_back(i->identity.address()); SharedPtr &hp = _peers[i->identity.address()]; - if (!hp) + if (!hp) { hp = new Peer(RR,RR->identity,i->identity); + } } } } @@ -396,7 +425,9 @@ void Topology::_savePeer(void *tPtr,const SharedPtr &peer) try { Buffer buf; peer->serializeForCache(buf); - uint64_t tmpid[2]; tmpid[0] = peer->address().toInt(); tmpid[1] = 0; + uint64_t tmpid[2]; + tmpid[0] = peer->address().toInt(); + tmpid[1] = 0; RR->node->stateObjectPut(tPtr,ZT_STATE_OBJECT_PEER,tmpid,buf.data(),buf.size()); } catch ( ... ) {} // sanity check, discard invalid entries } diff --git a/node/Topology.hpp b/node/Topology.hpp index 2d4e5d92c..248b11782 100644 --- a/node/Topology.hpp +++ b/node/Topology.hpp @@ -89,8 +89,9 @@ public: { Mutex::Lock _l(_peers_m); const SharedPtr *const ap = _peers.get(zta); - if (ap) + if (ap) { return *ap; + } return SharedPtr(); } @@ -105,8 +106,9 @@ public: { Mutex::Lock _l(_paths_m); SharedPtr &p = _paths[Path::HashKey(l,r)]; - if (!p) + if (!p) { p.set(new Path(l,r)); + } return p; } @@ -163,8 +165,9 @@ public: if (i->identity != RR->identity) { std::vector &ips = eps[i->identity.address()]; for(std::vector::const_iterator j(i->stableEndpoints.begin());j!=i->stableEndpoints.end();++j) { - if (std::find(ips.begin(),ips.end(),*j) == ips.end()) + if (std::find(ips.begin(),ips.end(),*j) == ips.end()) { ips.push_back(*j); + } } } } @@ -173,14 +176,16 @@ public: if (i->identity != RR->identity) { std::vector &ips = eps[i->identity.address()]; for(std::vector::const_iterator j(i->stableEndpoints.begin());j!=i->stableEndpoints.end();++j) { - if (std::find(ips.begin(),ips.end(),*j) == ips.end()) + if (std::find(ips.begin(),ips.end(),*j) == ips.end()) { ips.push_back(*j); + } } } } } - for(std::vector< std::pair >::const_iterator m(_moonSeeds.begin());m!=_moonSeeds.end();++m) + for(std::vector< std::pair >::const_iterator m(_moonSeeds.begin());m!=_moonSeeds.end();++m) { eps[m->second]; + } } /** @@ -209,8 +214,9 @@ public: Mutex::Lock _l(_upstreams_m); std::vector mw; for(std::vector< std::pair >::const_iterator s(_moonSeeds.begin());s!=_moonSeeds.end();++s) { - if (std::find(mw.begin(),mw.end(),s->first) == mw.end()) + if (std::find(mw.begin(),mw.end(),s->first) == mw.end()) { mw.push_back(s->first); + } } return mw; } @@ -287,8 +293,9 @@ public: SharedPtr *p = (SharedPtr *)0; while (i.next(a,p)) { const SharedPtr pp((*p)->getAppropriatePath(now,false)); - if (pp) + if (pp) { ++cnt; + } } return cnt; } @@ -354,8 +361,9 @@ public: inline unsigned int getOutboundPathMtu(const InetAddress &physicalAddress) { for(unsigned int i=0,j=_numConfiguredPhysicalPaths;i cpaths; - for(unsigned int i=0,j=_numConfiguredPhysicalPaths;i ZT_MAX_PHYSMTU) + } else if (pc.mtu > ZT_MAX_PHYSMTU) { pc.mtu = ZT_MAX_PHYSMTU; + } cpaths[*(reinterpret_cast(pathNetwork))] = pc; } else { diff --git a/node/Trace.cpp b/node/Trace.cpp index 8443a1217..8a9b7b284 100644 --- a/node/Trace.cpp +++ b/node/Trace.cpp @@ -59,38 +59,47 @@ void Trace::resettingPathsInScope(void *const tPtr,const Address &reporter,const d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_PHYADDR,myPhysicalAddress.toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__IP_SCOPE,(uint64_t)scope); - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); + } _spamToAllNetworks(tPtr,d,Trace::LEVEL_NORMAL); } void Trace::peerConfirmingUnknownPath(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &path,const uint64_t packetId,const Packet::Verb verb) { char tmp[128]; - if (!path) return; // sanity check + if (!path) { + return; // sanity check + } ZT_LOCAL_TRACE(tPtr,RR,"trying unknown path %s to %.10llx (packet %.16llx verb %d local socket %lld network %.16llx)",path->address().toString(tmp),peer.address().toInt(),packetId,verb,path->localSocket(),networkId); std::pair byn; - if (networkId) { Mutex::Lock l(_byNet_m); _byNet.get(networkId,byn); } + if (networkId) { + Mutex::Lock l(_byNet_m); + _byNet.get(networkId,byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; d.add(ZT_REMOTE_TRACE_FIELD__EVENT,ZT_REMOTE_TRACE_EVENT__PEER_CONFIRMING_UNKNOWN_PATH_S); d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID,packetId); d.add(ZT_REMOTE_TRACE_FIELD__PACKET_VERB,(uint64_t)verb); - if (networkId) + if (networkId) { d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID,networkId); + } d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR,peer.address()); if (path) { d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR,path->address().toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,path->localSocket()); } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } @@ -102,53 +111,69 @@ void Trace::bondStateMessage(void *const tPtr,char *msg) void Trace::peerLearnedNewPath(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &newPath,const uint64_t packetId) { char tmp[128]; - if (!newPath) return; // sanity check + if (!newPath) { + return; // sanity check + } ZT_LOCAL_TRACE(tPtr,RR,"learned new path %s to %.10llx (packet %.16llx local socket %lld network %.16llx)",newPath->address().toString(tmp),peer.address().toInt(),packetId,newPath->localSocket(),networkId); std::pair byn; - if (networkId) { Mutex::Lock l(_byNet_m); _byNet.get(networkId,byn); } + if (networkId) { + Mutex::Lock l(_byNet_m); + _byNet.get(networkId,byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; d.add(ZT_REMOTE_TRACE_FIELD__EVENT,ZT_REMOTE_TRACE_EVENT__PEER_LEARNED_NEW_PATH_S); d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID,packetId); - if (networkId) - d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID,networkId); + if (networkId) { + d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, networkId); + } d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR,peer.address()); d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR,newPath->address().toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,newPath->localSocket()); - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } void Trace::peerRedirected(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &newPath) { char tmp[128]; - if (!newPath) return; // sanity check + if (!newPath) { + return; // sanity check + } ZT_LOCAL_TRACE(tPtr,RR,"explicit redirect from %.10llx to path %s",peer.address().toInt(),newPath->address().toString(tmp)); std::pair byn; - if (networkId) { Mutex::Lock l(_byNet_m); _byNet.get(networkId,byn); } + if (networkId) { + Mutex::Lock l(_byNet_m); + _byNet.get(networkId,byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; d.add(ZT_REMOTE_TRACE_FIELD__EVENT,ZT_REMOTE_TRACE_EVENT__PEER_REDIRECTED_S); - if (networkId) + if (networkId) { d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID,networkId); + } d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR,peer.address()); d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR,newPath->address().toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,newPath->localSocket()); - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } @@ -157,7 +182,9 @@ void Trace::outgoingNetworkFrameDropped(void *const tPtr,const SharedPtr %s etherType %.4x size %u (%s)",network->id(),sourceMac.toString(tmp),destMac.toString(tmp2),etherType,frameLen,(reason) ? reason : "unknown reason"); @@ -173,20 +200,25 @@ void Trace::outgoingNetworkFrameDropped(void *const tPtr,const SharedPtr= (int)Trace::LEVEL_VERBOSE)) + if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,_globalTarget); - if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) + } + if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,byn.first); + } } } void Trace::incomingNetworkAccessDenied(void *const tPtr,const SharedPtr &network,const SharedPtr &path,const uint64_t packetId,const unsigned int packetLength,const Address &source,const Packet::Verb verb,bool credentialsRequested) { char tmp[128]; - if (!network) return; // sanity check + if (!network) { + return; // sanity check + } ZT_LOCAL_TRACE(tPtr,RR,"%.16llx DENIED packet from %.10llx(%s) verb %d size %u%s",network->id(),source.toInt(),(path) ? (path->address().toString(tmp)) : "???",(int)verb,packetLength,credentialsRequested ? " (credentials requested)" : " (credentials not requested)"); @@ -205,17 +237,21 @@ void Trace::incomingNetworkAccessDenied(void *const tPtr,const SharedPtrid()); - if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) + if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,_globalTarget); - if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) + } + if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,byn.first); + } } } void Trace::incomingNetworkFrameDropped(void *const tPtr,const SharedPtr &network,const SharedPtr &path,const uint64_t packetId,const unsigned int packetLength,const Address &source,const Packet::Verb verb,const MAC &sourceMac,const MAC &destMac,const char *reason) { char tmp[128]; - if (!network) return; // sanity check + if (!network) { + return; // sanity check + } ZT_LOCAL_TRACE(tPtr,RR,"%.16llx DROPPED frame from %.10llx(%s) verb %d size %u",network->id(),source.toInt(),(path) ? (path->address().toString(tmp)) : "???",(int)verb,packetLength); @@ -235,13 +271,16 @@ void Trace::incomingNetworkFrameDropped(void *const tPtr,const SharedPtrid()); d.add(ZT_REMOTE_TRACE_FIELD__SOURCE_MAC,sourceMac.toInt()); d.add(ZT_REMOTE_TRACE_FIELD__DEST_MAC,destMac.toInt()); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } - if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) + if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,_globalTarget); - if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) + } + if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,byn.first); + } } } @@ -261,8 +300,9 @@ void Trace::incomingPacketMessageAuthenticationFailure(void *const tPtr,const Sh d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR,path->address().toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,path->localSocket()); } - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } _send(tPtr,d,_globalTarget); } @@ -285,8 +325,9 @@ void Trace::incomingPacketInvalid(void *const tPtr,const SharedPtr &path,c d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,path->localSocket()); } d.add(ZT_REMOTE_TRACE_FIELD__PACKET_HOPS,(uint64_t)hops); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } _send(tPtr,d,_globalTarget); } @@ -307,8 +348,9 @@ void Trace::incomingPacketDroppedHELLO(void *const tPtr,const SharedPtr &p d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR,path->address().toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,path->localSocket()); } - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } _send(tPtr,d,_globalTarget); } @@ -361,25 +403,33 @@ void Trace::networkFilter( d.add(ZT_REMOTE_TRACE_FIELD__FILTER_FLAG_INBOUND,inbound ? "1" : "0"); d.add(ZT_REMOTE_TRACE_FIELD__FILTER_RESULT,(int64_t)accept); d.add(ZT_REMOTE_TRACE_FIELD__FILTER_BASE_RULE_LOG,(const char *)primaryRuleSetLog.data(),(int)primaryRuleSetLog.sizeBytes()); - if (matchingCapabilityRuleSetLog) + if (matchingCapabilityRuleSetLog) { d.add(ZT_REMOTE_TRACE_FIELD__FILTER_CAP_RULE_LOG,(const char *)matchingCapabilityRuleSetLog->data(),(int)matchingCapabilityRuleSetLog->sizeBytes()); - if (matchingCapability) + } + if (matchingCapability) { d.add(ZT_REMOTE_TRACE_FIELD__FILTER_CAP_ID,(uint64_t)matchingCapability->id()); + } d.add(ZT_REMOTE_TRACE_FIELD__FRAME_LENGTH,(uint64_t)frameLen); - if (frameLen > 0) + if (frameLen > 0) { d.add(ZT_REMOTE_TRACE_FIELD__FRAME_DATA,(const char *)frameData,(frameLen > 256) ? (int)256 : (int)frameLen); + } - if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_RULES)) + if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_RULES)) { _send(tPtr,d,_globalTarget); - if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_RULES)) + } + if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_RULES)) { _send(tPtr,d,byn.first); + } } } void Trace::credentialRejected(void *const tPtr,const CertificateOfMembership &c,const char *reason) { std::pair byn; - if (c.networkId()) { Mutex::Lock l(_byNet_m); _byNet.get(c.networkId(),byn); } + if (c.networkId()) { + Mutex::Lock l(_byNet_m); + _byNet.get(c.networkId(),byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; @@ -389,20 +439,26 @@ void Trace::credentialRejected(void *const tPtr,const CertificateOfMembership &c d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID,(uint64_t)c.id()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP,c.timestamp()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO,c.issuedTo()); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } void Trace::credentialRejected(void *const tPtr,const CertificateOfOwnership &c,const char *reason) { std::pair byn; - if (c.networkId()) { Mutex::Lock l(_byNet_m); _byNet.get(c.networkId(),byn); } + if (c.networkId()) { + Mutex::Lock l(_byNet_m); + _byNet.get(c.networkId(),byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; @@ -412,20 +468,26 @@ void Trace::credentialRejected(void *const tPtr,const CertificateOfOwnership &c, d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID,(uint64_t)c.id()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP,c.timestamp()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO,c.issuedTo()); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } void Trace::credentialRejected(void *const tPtr,const Capability &c,const char *reason) { std::pair byn; - if (c.networkId()) { Mutex::Lock l(_byNet_m); _byNet.get(c.networkId(),byn); } + if (c.networkId()) { + Mutex::Lock l(_byNet_m); + _byNet.get(c.networkId(),byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; @@ -435,20 +497,26 @@ void Trace::credentialRejected(void *const tPtr,const Capability &c,const char * d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID,(uint64_t)c.id()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP,c.timestamp()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO,c.issuedTo()); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } void Trace::credentialRejected(void *const tPtr,const Tag &c,const char *reason) { std::pair byn; - if (c.networkId()) { Mutex::Lock l(_byNet_m); _byNet.get(c.networkId(),byn); } + if (c.networkId()) { + Mutex::Lock l(_byNet_m); + _byNet.get(c.networkId(),byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; @@ -459,20 +527,26 @@ void Trace::credentialRejected(void *const tPtr,const Tag &c,const char *reason) d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP,c.timestamp()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO,c.issuedTo()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_INFO,(uint64_t)c.value()); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } void Trace::credentialRejected(void *const tPtr,const Revocation &c,const char *reason) { std::pair byn; - if (c.networkId()) { Mutex::Lock l(_byNet_m); _byNet.get(c.networkId(),byn); } + if (c.networkId()) { + Mutex::Lock l(_byNet_m); + _byNet.get(c.networkId(),byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; @@ -481,13 +555,16 @@ void Trace::credentialRejected(void *const tPtr,const Revocation &c,const char * d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE,(uint64_t)c.credentialType()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID,(uint64_t)c.id()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_REVOCATION_TARGET,c.target()); - if (reason) - d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + if (reason) { + d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason); + } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } @@ -525,8 +602,9 @@ void Trace::_spamToAllNetworks(void *const tPtr,const Dictionary *v = (std::pair *)0; while (i.next(k,v)) { - if ((v)&&(v->first)&&((int)v->second >= (int)level)) + if ((v)&&(v->first)&&((int)v->second >= (int)level)) { _send(tPtr,d,v->first); + } } } diff --git a/node/Utils.cpp b/node/Utils.cpp index a0bf07077..be39aa0aa 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -188,18 +188,22 @@ const Utils::CPUIDRegisters Utils::CPUID; static void _Utils_doBurn(volatile uint8_t *ptr,unsigned int len) { volatile uint8_t *const end = ptr + len; - while (ptr != end) *(ptr++) = (uint8_t)0; + while (ptr != end) { + *(ptr++) = (uint8_t)0; + } } static void (*volatile _Utils_doBurn_ptr)(volatile uint8_t *,unsigned int) = _Utils_doBurn; void Utils::burn(void *ptr,unsigned int len) { (_Utils_doBurn_ptr)((volatile uint8_t *)ptr,len); } static unsigned long _Utils_itoa(unsigned long n,char *s) { - if (n == 0) + if (n == 0) { return 0; + } unsigned long pos = _Utils_itoa(n / 10,s); - if (pos >= 22) // sanity check, should be impossible + if (pos >= 22) { // sanity check, should be impossible pos = 22; + } s[pos] = '0' + (char)(n % 10); return pos + 1; } @@ -288,7 +292,9 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes) exit(1); return; } - } else break; + } else { + break; + } } randomPtr = 0; s20.crypt12(randomBuf,randomBuf,sizeof(randomBuf)); diff --git a/node/Utils.hpp b/node/Utils.hpp index 685fdf591..d13674c1f 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -102,10 +102,17 @@ public: */ static inline unsigned int log2(uint32_t v) { - uint32_t r = (v > 0xffff) << 4; v >>= r; - uint32_t shift = (v > 0xff) << 3; v >>= shift; r |= shift; - shift = (v > 0xf) << 2; v >>= shift; r |= shift; - shift = (v > 0x3) << 1; v >>= shift; r |= shift; + uint32_t r = (v > 0xffff) << 4; + v >>= r; + uint32_t shift = (v > 0xff) << 3; + v >>= shift; + r |= shift; + shift = (v > 0xf) << 2; + v >>= shift; + r |= shift; + shift = (v > 0x3) << 1; + v >>= shift; + r |= shift; r |= (v >> 1); return (unsigned int)r; } @@ -121,8 +128,9 @@ public: static inline bool secureEq(const void *a,const void *b,unsigned int len) { uint8_t diff = 0; - for(unsigned int i=0;i(a))[i] ^ (reinterpret_cast(b))[i] ); + } return (diff == 0); } @@ -225,26 +233,32 @@ public: unsigned int l = 0; while (l < buflen) { uint8_t hc = *(reinterpret_cast(h++)); - if (!hc) break; + if (!hc) { + break; + } uint8_t c = 0; - if ((hc >= 48)&&(hc <= 57)) // 0..9 + if ((hc >= 48)&&(hc <= 57)) { // 0..9 c = hc - 48; - else if ((hc >= 97)&&(hc <= 102)) // a..f + } else if ((hc >= 97)&&(hc <= 102)) { // a..f c = hc - 87; - else if ((hc >= 65)&&(hc <= 70)) // A..F + } else if ((hc >= 65)&&(hc <= 70)) { // A..F c = hc - 55; + } hc = *(reinterpret_cast(h++)); - if (!hc) break; + if (!hc) { + break; + } c <<= 4; - if ((hc >= 48)&&(hc <= 57)) + if ((hc >= 48)&&(hc <= 57)) { c |= hc - 48; - else if ((hc >= 97)&&(hc <= 102)) + } else if ((hc >= 97)&&(hc <= 102)) { c |= hc - 87; - else if ((hc >= 65)&&(hc <= 70)) + } else if ((hc >= 65)&&(hc <= 70)) { c |= hc - 55; + } reinterpret_cast(buf)[l++] = c; } @@ -256,29 +270,39 @@ public: unsigned int l = 0; const char *hend = h + hlen; while (l < buflen) { - if (h == hend) break; + if (h == hend) { + break; + } uint8_t hc = *(reinterpret_cast(h++)); - if (!hc) break; + if (!hc) { + break; + } uint8_t c = 0; - if ((hc >= 48)&&(hc <= 57)) + if ((hc >= 48)&&(hc <= 57)) { c = hc - 48; - else if ((hc >= 97)&&(hc <= 102)) + } else if ((hc >= 97)&&(hc <= 102)) { c = hc - 87; - else if ((hc >= 65)&&(hc <= 70)) + } else if ((hc >= 65)&&(hc <= 70)) { c = hc - 55; + } - if (h == hend) break; + if (h == hend) { + break; + } hc = *(reinterpret_cast(h++)); - if (!hc) break; + if (!hc) { + break; + } c <<= 4; - if ((hc >= 48)&&(hc <= 57)) + if ((hc >= 48)&&(hc <= 57)) { c |= hc - 48; - else if ((hc >= 97)&&(hc <= 102)) + } else if ((hc >= 97)&&(hc <= 102)) { c |= hc - 87; - else if ((hc >= 65)&&(hc <= 70)) + } else if ((hc >= 65)&&(hc <= 70)) { c |= hc - 55; + } reinterpret_cast(buf)[l++] = c; } @@ -375,8 +399,9 @@ public: */ static inline bool scopy(char *dest,unsigned int len,const char *src) { - if (!len) + if (!len) { return false; // sanity check + } if (!src) { *dest = (char)0; return true; @@ -428,8 +453,9 @@ public: static inline bool isZero(const void *p,unsigned int len) { for(unsigned int i=0;i(&tmp)[i] = reinterpret_cast(p)[i]; + } return tmp; #else return *reinterpret_cast(p); @@ -672,8 +699,9 @@ public: static ZT_INLINE void storeMachineEndian(void *const p, const I i) noexcept { #ifdef ZT_NO_UNALIGNED_ACCESS - for(unsigned int k=0;k(p)[k] = reinterpret_cast(&i)[k]; + } #else *reinterpret_cast(p) = i; #endif diff --git a/node/World.hpp b/node/World.hpp index a8b80e0ca..a13d59a25 100644 --- a/node/World.hpp +++ b/node/World.hpp @@ -148,8 +148,9 @@ public: */ inline bool shouldBeReplacedBy(const World &update) { - if ((_id == 0)||(_type == TYPE_NULL)) + if ((_id == 0)||(_type == TYPE_NULL)) { return true; + } if ((_id == update._id)&&(_ts < update._ts)&&(_type == update._type)) { Buffer tmp; update.serialize(tmp,true); @@ -166,25 +167,32 @@ public: template inline void serialize(Buffer &b,bool forSign = false) const { - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } b.append((uint8_t)_type); b.append((uint64_t)_id); b.append((uint64_t)_ts); b.append(_updatesMustBeSignedBy.data,ZT_C25519_PUBLIC_KEY_LEN); - if (!forSign) + if (!forSign) { b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); + } b.append((uint8_t)_roots.size()); for(std::vector::const_iterator r(_roots.begin());r!=_roots.end();++r) { r->identity.serialize(b); b.append((uint8_t)r->stableEndpoints.size()); - for(std::vector::const_iterator ep(r->stableEndpoints.begin());ep!=r->stableEndpoints.end();++ep) + for(std::vector::const_iterator ep(r->stableEndpoints.begin());ep!=r->stableEndpoints.end();++ep) { ep->serialize(b); + } } - if (_type == TYPE_MOON) + if (_type == TYPE_MOON) { b.append((uint16_t)0); // no attached dictionary (for future use) + } - if (forSign) b.append((uint64_t)0xf7f7f7f7f7f7f7f7ULL); + if (forSign) { + b.append((uint64_t)0xf7f7f7f7f7f7f7f7ULL); + } } template @@ -195,34 +203,47 @@ public: _roots.clear(); switch((Type)b[p++]) { - case TYPE_NULL: _type = TYPE_NULL; break; // shouldn't ever really happen in serialized data but it's not invalid - case TYPE_PLANET: _type = TYPE_PLANET; break; - case TYPE_MOON: _type = TYPE_MOON; break; + case TYPE_NULL: // shouldn't ever really happen in serialized data but it's not invalid + _type = TYPE_NULL; + break; + case TYPE_PLANET: + _type = TYPE_PLANET; + break; + case TYPE_MOON: + _type = TYPE_MOON; + break; default: throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE; } - _id = b.template at(p); p += 8; - _ts = b.template at(p); p += 8; - memcpy(_updatesMustBeSignedBy.data,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN); p += ZT_C25519_PUBLIC_KEY_LEN; - memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + _id = b.template at(p); + p += 8; + _ts = b.template at(p); + p += 8; + memcpy(_updatesMustBeSignedBy.data,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN); + p += ZT_C25519_PUBLIC_KEY_LEN; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; const unsigned int numRoots = (unsigned int)b[p++]; - if (numRoots > ZT_WORLD_MAX_ROOTS) + if (numRoots > ZT_WORLD_MAX_ROOTS) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } for(unsigned int k=0;k ZT_WORLD_MAX_STABLE_ENDPOINTS_PER_ROOT) + if (numStableEndpoints > ZT_WORLD_MAX_STABLE_ENDPOINTS_PER_ROOT) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } for(unsigned int kk=0;kk(p) + 2; + } return (p - startAt); } From 785a12182579277b7b1b0453b1acfbeaa0c325c2 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 1 May 2023 16:30:22 -0700 Subject: [PATCH 05/41] Fix rust dependency caching (#1983) * fun with rust caching * kick * comment out invalid yaml keys for now * Caching should now work * re-add/rename key directives * bump * bump * bump --- .github/workflows/build.yml | 45 ++++++++++++++-------------------- .github/workflows/validate.yml | 15 +++++------- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80f7c9a3d..657c8d8c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,17 +19,14 @@ jobs: components: rustfmt, clippy - name: Set up cargo cache - uses: actions/cache@v3 + uses: Swatinem/rust-cache@v2 continue-on-error: false with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: ${{ runner.os }}-cargo- + key: ${{ runner.os }}-cargo-${{ hashFiles('zeroidc//Cargo.lock') }} + shared-key: ${{ runner.os }}-cargo- + workspaces: | + zeroidc/ + - name: make run: make - name: selftest @@ -54,17 +51,14 @@ jobs: override: true components: rustfmt, clippy - name: Set up cargo cache - uses: actions/cache@v3 + uses: Swatinem/rust-cache@v2 continue-on-error: false with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: ${{ runner.os }}-cargo- + key: ${{ runner.os }}-cargo-${{ hashFiles('zeroidc//Cargo.lock') }} + shared-key: ${{ runner.os }}-cargo- + workspaces: | + zeroidc/ + - name: make run: make - name: selftest @@ -89,17 +83,14 @@ jobs: override: true components: rustfmt, clippy - name: Set up cargo cache - uses: actions/cache@v3 + uses: Swatinem/rust-cache@v2 continue-on-error: false with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: ${{ runner.os }}-cargo- + key: ${{ runner.os }}-cargo-${{ hashFiles('zeroidc//Cargo.lock') }} + shared-key: ${{ runner.os }}-cargo- + workspaces: | + zeroidc/ + - name: setup msbuild uses: microsoft/setup-msbuild@v1.1.3 - name: msbuild diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 665c34945..55f81e557 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -22,17 +22,13 @@ jobs: components: rustfmt, clippy - name: Set up cargo cache - uses: actions/cache@v3 + uses: Swatinem/rust-cache@v2 continue-on-error: false with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - **/target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} - restore-keys: ${{ runner.os }}-cargo- + key: ${{ runner.os }}-cargo-${{ hashFiles('zeroidc//Cargo.lock') }} + shared-key: ${{ runner.os }}-cargo- + workspaces: | + zeroidc/ - name: validate-1m-linux env: @@ -55,3 +51,4 @@ jobs: run: | sudo chmod +x ./.github/workflows/report.sh sudo ./.github/workflows/report.sh + From 4ca3f272933af3ba9c4fdc09681e1a9644599f9c Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 2 May 2023 08:53:29 -0700 Subject: [PATCH 06/41] Don't force rebuild on Windows build GH Action (#1985) Switching `/t:ZeroTierOne:Rebuild` to just `/t:ZeroTierOne` allows the Windows build to use the rust cache. `/t:ZeroTierOne:Rebuild` cleared the cache before building. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 657c8d8c7..b6d09a6a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -95,4 +95,4 @@ jobs: uses: microsoft/setup-msbuild@v1.1.3 - name: msbuild run: | - msbuild windows\ZeroTierOne.sln /m /p:Configuration=Release /property:Platform=x64 /t:ZeroTierOne:Rebuild + msbuild windows\ZeroTierOne.sln /m /p:Configuration=Release /property:Platform=x64 /t:ZeroTierOne From 06b487119def15f0c49883f86c604f6e3b3398c9 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 2 May 2023 11:16:55 -0700 Subject: [PATCH 07/41] More packet metrics (#1982) * found path negotation sends that weren't accounted for * Fix histogram so it will actually compile * Found more places for packet metrics --- .../core/include/prometheus/counter.h | 3 ++- .../core/include/prometheus/gauge.h | 1 + .../core/include/prometheus/histogram.h | 4 ++-- node/Bond.cpp | 2 ++ node/IncomingPacket.cpp | 14 ++++++++++++++ node/Membership.cpp | 1 + node/Metrics.cpp | 1 + node/Metrics.hpp | 2 +- node/Multicaster.cpp | 2 ++ node/Peer.cpp | 11 +++++++++++ node/Peer.hpp | 5 +++-- node/Switch.cpp | 1 + 12 files changed, 41 insertions(+), 6 deletions(-) diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h index 35231888f..5d8053739 100644 --- a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h @@ -38,7 +38,8 @@ namespace prometheus { static const Metric::Type static_type = Metric::Type::Counter; Counter() : Metric (Metric::Type::Counter) {} ///< \brief Create a counter that starts at 0. - + Counter(const Counter &rhs) : Metric(static_type), value{rhs.value.load()} {} + // original API void Increment() { ///< \brief Increment the counter by 1. diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h index f42308526..fcda14631 100644 --- a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h @@ -38,6 +38,7 @@ namespace prometheus { Gauge() : Metric (static_type) {} ///< \brief Create a gauge that starts at 0. Gauge(const Value value_) : Metric(static_type), value{ value_ } {} ///< \brief Create a gauge that starts at the given amount. + Gauge(const Gauge &rhs) : Metric(rhs.static_type), value{rhs.value.load()} {} // original API diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h index 03cf461ea..ac558fa21 100644 --- a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h @@ -27,7 +27,7 @@ namespace prometheus { /// The class is thread-safe. No concurrent call to any API of this type causes /// a data race. template - class Histogram : Metric { + class Histogram : public Metric { using BucketBoundaries = std::vector; @@ -105,7 +105,7 @@ namespace prometheus { auto cumulative_count = 0ULL; metric.histogram.bucket.reserve(bucket_counts_.size()); for (std::size_t i{0}; i < bucket_counts_.size(); ++i) { - cumulative_count += static_cast(bucket_counts_[i].Value()); + cumulative_count += static_cast(bucket_counts_[i].Get()); auto bucket = ClientMetric::Bucket{}; bucket.cumulative_count = cumulative_count; bucket.upper_bound = (i == bucket_boundaries_.size() diff --git a/node/Bond.cpp b/node/Bond.cpp index 0eee7cff3..57551a747 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -795,6 +795,7 @@ void Bond::sendPATH_NEGOTIATION_REQUEST(void* tPtr, int pathIdx) Packet outp(_peer->_id.address(), RR->identity.address(), Packet::VERB_PATH_NEGOTIATION_REQUEST); outp.append(_localUtility); if (_paths[pathIdx].p->address()) { + Metrics::pkt_path_negotiation_request_out++; outp.armor(_peer->key(), false, _peer->aesKeysIfSupported()); RR->node->putPacket(tPtr, _paths[pathIdx].p->localSocket(), _paths[pathIdx].p->address(), outp.data(), outp.size()); _overheadBytes += outp.size(); @@ -841,6 +842,7 @@ void Bond::sendQOS_MEASUREMENT(void* tPtr, int pathIdx, int64_t localSocket, con } else { RR->sw->send(tPtr, outp, false); } + Metrics::pkt_qos_out++; _paths[pathIdx].packetsReceivedSinceLastQoS = 0; _paths[pathIdx].lastQoSMeasurement = now; _overheadBytes += outp.size(); diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index cc48caeb1..77dcf6b6a 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -410,6 +410,8 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool outp.append((uint64_t)pid); outp.append((uint8_t)Packet::ERROR_IDENTITY_COLLISION); outp.armor(key,true,peer->aesKeysIfSupported()); + Metrics::pkt_error_out++; + Metrics::pkt_error_identity_collision_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } else { RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,pid,fromAddress,hops(),"invalid MAC"); @@ -567,6 +569,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),now); peer->setRemoteVersion(protoVersion,vMajor,vMinor,vRevision); // important for this to go first so received() knows the version @@ -728,7 +731,9 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar } if (count > 0) { + Metrics::pkt_ok_out++; outp.armor(peer->key(),true,peer->aesKeysIfSupported()); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -960,6 +965,7 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const const int64_t now = RR->node->now(); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -988,6 +994,7 @@ bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const Share } outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); peer->received(tPtr,_path,hops(),pid,payloadLength(),Packet::VERB_ECHO,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); @@ -1182,6 +1189,8 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION); outp.append(nwid); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); + Metrics::pkt_error_out++; + Metrics::pkt_error_unsupported_op_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -1205,6 +1214,7 @@ bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,c const int64_t now = RR->node->now(); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } @@ -1247,6 +1257,7 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr if (gatheredLocally > 0) { outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),now); } } @@ -1350,6 +1361,7 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, const int64_t now = RR->node->now(); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } @@ -1493,6 +1505,8 @@ void IncomingPacket::_sendErrorNeedCredentials(const RuntimeEnvironment *RR,void outp.append((uint8_t)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE); outp.append(nwid); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); + Metrics::pkt_error_out++; + Metrics::pkt_error_need_membership_cert_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } diff --git a/node/Membership.cpp b/node/Membership.cpp index 26a89a271..1b317d94f 100644 --- a/node/Membership.cpp +++ b/node/Membership.cpp @@ -100,6 +100,7 @@ void Membership::pushCredentials(const RuntimeEnvironment *RR,void *tPtr,const i outp.compress(); RR->sw->send(tPtr,outp,true); + Metrics::pkt_network_credentials_out++; } _lastPushedCredentials = now; diff --git a/node/Metrics.cpp b/node/Metrics.cpp index d57bb2d00..c90331982 100644 --- a/node/Metrics.cpp +++ b/node/Metrics.cpp @@ -11,6 +11,7 @@ */ #include +#include namespace prometheus { namespace simpleapi { diff --git a/node/Metrics.hpp b/node/Metrics.hpp index f38bab6b9..7530e48c0 100644 --- a/node/Metrics.hpp +++ b/node/Metrics.hpp @@ -13,6 +13,7 @@ #define METRICS_H_ #include +#include namespace prometheus { namespace simpleapi { @@ -94,7 +95,6 @@ namespace ZeroTier { extern prometheus::simpleapi::counter_metric_t pkt_error_authentication_required_out; extern prometheus::simpleapi::counter_metric_t pkt_error_internal_server_error_out; - // Data Sent/Received Metrics extern prometheus::simpleapi::counter_metric_t udp_send; extern prometheus::simpleapi::counter_metric_t udp_recv; diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp index cc26cddda..1306a53f2 100644 --- a/node/Multicaster.cpp +++ b/node/Multicaster.cpp @@ -200,6 +200,7 @@ void Multicaster::send( outp.compress(); } outp.armor(bestMulticastReplicator->key(),true,bestMulticastReplicator->aesKeysIfSupported()); + Metrics::pkt_multicast_frame_out++; bestMulticastReplicatorPath->send(RR,tPtr,outp.data(),outp.size(),now); return; } @@ -334,6 +335,7 @@ void Multicaster::send( } RR->node->expectReplyTo(outp.packetId()); RR->sw->send(tPtr,outp,true); + Metrics::pkt_multicast_gather_out++; } } diff --git a/node/Peer.cpp b/node/Peer.cpp index f9db88ea9..3aa2641cc 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -28,6 +28,11 @@ namespace ZeroTier { static unsigned char s_freeRandomByteCounter = 0; +char * peerIDString(const Identity &id) { + char out[16]; + return id.address().toString(out); +} + Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) : RR(renv), _lastReceive(0), @@ -234,9 +239,11 @@ void Peer::received( ++p; } if (count) { + Metrics::pkt_push_direct_paths_out++; outp->setAt(ZT_PACKET_IDX_PAYLOAD,(uint16_t)count); outp->compress(); outp->armor(_key,true,aesKeysIfSupported()); + Metrics::pkt_push_direct_paths_out++; path->send(RR,tPtr,outp->data(),outp->size(),now); } delete outp; @@ -382,6 +389,7 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o outp.append(other->_paths[theirs].p->address().rawIpData(),4); } outp.armor(_key,true,aesKeysIfSupported()); + Metrics::pkt_rendezvous_out++; _paths[mine].p->send(RR,tPtr,outp.data(),outp.size(),now); } else { Packet outp(other->_id.address(),RR->identity.address(),Packet::VERB_RENDEZVOUS); @@ -396,6 +404,7 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o outp.append(_paths[mine].p->address().rawIpData(),4); } outp.armor(other->_key,true,other->aesKeysIfSupported()); + Metrics::pkt_rendezvous_out++; other->_paths[theirs].p->send(RR,tPtr,outp.data(),outp.size(),now); } ++alt; @@ -438,6 +447,7 @@ void Peer::sendHELLO(void *tPtr,const int64_t localSocket,const InetAddress &atA if (atAddress) { outp.armor(_key,false,nullptr); // false == don't encrypt full payload, but add MAC + Metrics::pkt_hello_out++; RR->node->expectReplyTo(outp.packetId()); RR->node->putPacket(tPtr,RR->node->lowBandwidthModeEnabled() ? localSocket : -1,atAddress,outp.data(),outp.size()); } else { @@ -451,6 +461,7 @@ void Peer::attemptToContactAt(void *tPtr,const int64_t localSocket,const InetAdd if ( (!sendFullHello) && (_vProto >= 5) && (!((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0))) ) { Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO); outp.armor(_key,true,aesKeysIfSupported()); + Metrics::pkt_echo_out++; RR->node->expectReplyTo(outp.packetId()); RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size()); } else { diff --git a/node/Peer.hpp b/node/Peer.hpp index 640ed1a2e..427e78a58 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -34,6 +34,7 @@ #include "Mutex.hpp" #include "Bond.hpp" #include "AES.hpp" +#include "Metrics.hpp" #define ZT_PEER_MAX_SERIALIZED_STATE_SIZE (sizeof(Peer) + 32 + (sizeof(Path) * 2)) @@ -50,7 +51,7 @@ class Peer friend class Bond; private: - Peer() {} // disabled to prevent bugs -- should not be constructed uninitialized + Peer() = delete; // disabled to prevent bugs -- should not be constructed uninitialized public: ~Peer() { @@ -320,7 +321,7 @@ public: } else { SharedPtr bp(getAppropriatePath(now,false)); if (bp) { - return bp->latency(); + return (unsigned int)bp->latency(); } return 0xffff; } diff --git a/node/Switch.cpp b/node/Switch.cpp index 5359180e4..5cbbeff85 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -102,6 +102,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre _lastBeaconResponse = now; Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NOP); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); + Metrics::pkt_nop_out++; path->send(RR,tPtr,outp.data(),outp.size(),now); } } From 6b5c9b1b8e0bad08efc3e3d37fef39bf3ab9c80d Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 2 May 2023 16:46:06 -0700 Subject: [PATCH 08/41] separate the bind & listen calls on the http backplane (#1988) --- service/OneService.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/service/OneService.cpp b/service/OneService.cpp index 2a6973497..73012380d 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -1994,14 +1994,21 @@ public: fprintf(stderr, "%s", http_log(req, res).c_str()); }); #endif + if (_primaryPort==0) { + fprintf(stderr, "unable to determine local control port"); + exit(-1); + } + + if(!_controlPlane.bind_to_port("0.0.0.0", _primaryPort)) { + fprintf(stderr, "Error binding control plane to port %d\n", _primaryPort); + exit(-1); + } _serverThread = std::thread([&] { - if (_primaryPort==0) { - fprintf(stderr, "unable to determine local control port"); - exit(-1); - } fprintf(stderr, "Starting Control Plane...\n"); - _controlPlane.listen("0.0.0.0", _primaryPort); + if(!_controlPlane.listen_after_bind()) { + fprintf(stderr, "Error on listen_after_bind()\n"); + } fprintf(stderr, "Control Plane Stopped\n"); }); From 115b9147b9cb2542c0fac6abeb7267de9bb001f6 Mon Sep 17 00:00:00 2001 From: Brenton Bostick Date: Wed, 3 May 2023 13:14:18 -0400 Subject: [PATCH 09/41] fix memory leak (#1992) --- node/NetworkConfig.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 4b0b05f0f..d2fa844c2 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -30,42 +30,55 @@ bool NetworkConfig::toDictionary(Dictionary &d,b // Try to put the more human-readable fields first if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_VERSION,(uint64_t)ZT_NETWORKCONFIG_VERSION)) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,this->networkId)) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,this->timestamp)) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA,this->credentialTimeMaxDelta)) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REVISION,this->revision)) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,this->issuedTo.toString(tmp2))) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_TARGET,this->remoteTraceTarget.toString(tmp2))) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_LEVEL,(uint64_t)this->remoteTraceLevel)) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,this->flags)) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,(uint64_t)this->multicastLimit)) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)this->type)) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name)) { + delete tmp; return false; } if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MTU,(uint64_t)this->mtu)) { + delete tmp; return false; } From 54f339f0c0d2503a1fc0d18dd12bdc7a371cbf76 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 3 May 2023 10:23:06 -0700 Subject: [PATCH 10/41] fix a couple of metrics (#1989) --- node/Bond.cpp | 1 + node/IncomingPacket.cpp | 1 - node/Peer.cpp | 3 ++- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/node/Bond.cpp b/node/Bond.cpp index 57551a747..21a8a5638 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -879,6 +879,7 @@ void Bond::processBackgroundBondTasks(void* tPtr, int64_t now) RR->node->putPacket(tPtr, _paths[i].p->localSocket(), _paths[i].p->address(), outp.data(), outp.size()); _paths[i].p->_lastOut = now; _overheadBytes += outp.size(); + Metrics::pkt_echo_out++; // debug("tx: verb 0x%-2x of len %4d via %s (ECHO)", Packet::VERB_ECHO, outp.size(), pathToStr(_paths[i].p).c_str()); } } diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 77dcf6b6a..f748bdb35 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -733,7 +733,6 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar if (count > 0) { Metrics::pkt_ok_out++; outp.armor(peer->key(),true,peer->aesKeysIfSupported()); - Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } diff --git a/node/Peer.cpp b/node/Peer.cpp index 3aa2641cc..c46bdf9d2 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -445,9 +445,10 @@ void Peer::sendHELLO(void *tPtr,const int64_t localSocket,const InetAddress &atA outp.cryptField(_key,startCryptedPortionAt,outp.size() - startCryptedPortionAt); + Metrics::pkt_hello_out++; + if (atAddress) { outp.armor(_key,false,nullptr); // false == don't encrypt full payload, but add MAC - Metrics::pkt_hello_out++; RR->node->expectReplyTo(outp.packetId()); RR->node->putPacket(tPtr,RR->node->lowBandwidthModeEnabled() ? localSocket : -1,atAddress,outp.data(),outp.size()); } else { From d6c5a6cd59e48790012bf1c38b16bfcaf2927f00 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Wed, 3 May 2023 10:49:27 -0700 Subject: [PATCH 11/41] More aggressive CLI spamming (#1993) --- .github/workflows/validate-1m-linux.sh | 108 ++++++++++++++++--------- .github/workflows/validate.yml | 6 +- 2 files changed, 76 insertions(+), 38 deletions(-) diff --git a/.github/workflows/validate-1m-linux.sh b/.github/workflows/validate-1m-linux.sh index 0a8dbf4d6..7bd58dd79 100755 --- a/.github/workflows/validate-1m-linux.sh +++ b/.github/workflows/validate-1m-linux.sh @@ -21,7 +21,8 @@ NS1="ip netns exec ns1" NS2="ip netns exec ns2" ZT1="$NS1 ./zerotier-cli -D$(pwd)/node1" -ZT2="$NS2 ./zerotier-cli -D$(pwd)/node2" +# Specify custom port on one node to ensure that feature works +ZT2="$NS2 ./zerotier-cli -p9997 -D$(pwd)/node2" echo -e "Setting up network namespaces..." echo "Setting up ns1" @@ -66,23 +67,23 @@ sysctl -w net.ipv4.ip_forward=1 echo -e "\nPing from host to namespaces" -#ping -c 4 192.168.0.1 -#ping -c 4 192.168.1.1 +ping -c 3 192.168.0.1 +ping -c 3 192.168.1.1 echo -e "\nPing from namespace to host" -#$NS1 ping -c 4 192.168.0.1 -#$NS1 ping -c 4 192.168.0.1 -#$NS2 ping -c 4 192.168.0.2 -#$NS2 ping -c 4 192.168.0.2 +$NS1 ping -c 3 192.168.0.1 +$NS1 ping -c 3 192.168.0.1 +$NS2 ping -c 3 192.168.0.2 +$NS2 ping -c 3 192.168.0.2 echo -e "\nPing from ns1 to ns2" -#$NS1 ping -c 4 192.168.0.1 +$NS1 ping -c 3 192.168.0.1 echo -e "\nPing from ns2 to ns1" -#$NS2 ping -c 4 192.168.0.1 +$NS2 ping -c 3 192.168.0.1 ################################################################################ # Memory Leak Check # @@ -94,21 +95,57 @@ echo -e "\nStarting a ZeroTier instance in each namespace..." time_test_start=`date +%s` +# Spam the CLI as ZeroTier is starting +spam_cli 100 + echo "Starting memory leak check" $NS1 sudo valgrind --demangle=yes --exit-on-first-error=yes \ --error-exitcode=1 \ --xml=yes \ --xml-file=$FILENAME_MEMORY_LOG \ --leak-check=full \ - ./zerotier-one node1 >>node_1.log 2>&1 & + ./zerotier-one node1 -U >>node_1.log 2>&1 & # Second instance, not run in memory profiler -$NS2 ./zerotier-one node2 >>node_2.log 2>&1 & +$NS2 sudo ./zerotier-one node2 -U -p9997 >>node_2.log 2>&1 & ################################################################################ # Online Check # ################################################################################ +spam_cli() +{ + echo "Spamming CLI..." + # Rapidly spam the CLI with joins/leaves + + MAX_TRIES="${$1:-10}" + + for ((s=0; s<=MAX_TRIES; s++)) + do + $ZT1 status + $ZT2 status + sleep 0.1 + done + + SPAM_TRIES=128 + + for ((s=0; s<=SPAM_TRIES; s++)) + do + $ZT1 join $TEST_NETWORK + done + + for ((s=0; s<=SPAM_TRIES; s++)) + do + $ZT1 leave $TEST_NETWORK + done + + for ((s=0; s<=SPAM_TRIES; s++)) + do + $ZT1 leave $TEST_NETWORK + $ZT1 join $TEST_NETWORK + done +} + echo "Waiting for ZeroTier to come online before attempting test..." MAX_WAIT_SECS="${MAX_WAIT_SECS:-120}" node1_online=false @@ -121,6 +158,7 @@ for ((s=0; s<=MAX_WAIT_SECS; s++)) do node1_online="$($ZT1 -j info | jq '.online' 2>/dev/null)" node2_online="$($ZT2 -j info | jq '.online' 2>/dev/null)" + echo "Checking for online status: try #$s, node1:$node1_online, node2:$node2_online" if [[ "$node1_online" == "true" ]] then time_zt_node1_online=`date +%s` @@ -137,17 +175,32 @@ do sleep 1 done +echo -e "\n\nContents of ZeroTier home paths:" + +ls -lga node1 +tree node1 +ls -lga node2 +tree node2 + +echo -e "\n\nRunning ZeroTier processes:" +echo -e "\nNode 1:" +$NS1 ps aux | grep zerotier-one +echo -e "\nNode 2:" +$NS2 ps aux | grep zerotier-one + +echo -e "\n\nStatus of each instance:" + +echo -e "\n\nNode 1:" +$ZT1 status +echo -e "\n\nNode 2:" +$ZT2 status + if [[ "$both_instances_online" != "true" ]] then - echo "One or more instances of ZeroTier failed to come online. Aborting test." >&2 + echo "One or more instances of ZeroTier failed to come online. Aborting test." exit 1 fi -echo -e "\nChecking status of each instance:" - -$ZT1 status -$ZT2 status - echo -e "\nJoining networks" $ZT1 join $TEST_NETWORK @@ -188,25 +241,7 @@ ping_loss_percent_2_to_1=$(echo "scale=2; $ping_loss_percent_2_to_1/100.0" | bc) echo "Testing basic CLI functionality..." -# Rapidly spam the CLI with joins/leaves - -SPAM_TRIES=128 - -for ((s=0; s<=SPAM_TRIES; s++)) -do - $ZT1 join $TEST_NETWORK -done - -for ((s=0; s<=SPAM_TRIES; s++)) -do - $ZT1 leave $TEST_NETWORK -done - -for ((s=0; s<=SPAM_TRIES; s++)) -do - $ZT1 leave $TEST_NETWORK - $ZT1 join $TEST_NETWORK -done +spam_cli 10 $ZT1 join $TEST_NETWORK @@ -399,3 +434,4 @@ EOF echo $summary > $FILENAME_SUMMARY cat $FILENAME_SUMMARY +"$@" \ No newline at end of file diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 55f81e557..c95d8e599 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1,4 +1,6 @@ -on: [ push ] +on: + push: + workflow_dispatch: jobs: build_ubuntu: @@ -36,7 +38,7 @@ jobs: CXX: 'g++' BRANCH: ${{ github.ref_name }} run: | - sudo apt install -y valgrind xmlstarlet gcovr iperf3 + sudo apt install -y valgrind xmlstarlet gcovr iperf3 tree make one ZT_COVERAGE=1 ZT_TRACE=1 sudo chmod +x ./.github/workflows/validate-1m-linux.sh sudo ./.github/workflows/validate-1m-linux.sh From a43048a1ad205ab3dd3d7973eb0f7d4488e1d973 Mon Sep 17 00:00:00 2001 From: Brenton Bostick Date: Wed, 3 May 2023 14:18:04 -0400 Subject: [PATCH 12/41] fix type signatures (#1991) --- node/Path.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/node/Path.hpp b/node/Path.hpp index d7a40dbc5..31d8e60d1 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -303,48 +303,48 @@ public: /** * @return Mean latency as reported by the bonding layer */ - inline unsigned int latencyMean() const { return _latencyMean; } + inline float latencyMean() const { return _latencyMean; } /** * @return Latency variance as reported by the bonding layer */ - inline unsigned int latencyVariance() const { return _latencyVariance; } + inline float latencyVariance() const { return _latencyVariance; } /** * @return Packet Loss Ratio as reported by the bonding layer */ - inline unsigned int packetLossRatio() const { return _packetLossRatio; } + inline float packetLossRatio() const { return _packetLossRatio; } /** * @return Packet Error Ratio as reported by the bonding layer */ - inline unsigned int packetErrorRatio() const { return _packetErrorRatio; } + inline float packetErrorRatio() const { return _packetErrorRatio; } /** * @return Whether this path is valid as reported by the bonding layer. The bonding layer * actually checks with Phy to see if the interface is still up */ - inline unsigned int valid() const { return _valid; } + inline bool valid() const { return _valid; } /** * @return Whether this path is eligible for use in a bond as reported by the bonding layer */ - inline unsigned int eligible() const { return _eligible; } + inline bool eligible() const { return _eligible; } /** * @return Whether this path is bonded as reported by the bonding layer */ - inline unsigned int bonded() const { return _bonded; } + inline bool bonded() const { return _bonded; } /** * @return Whether the user-specified MTU for this path (determined by MTU for parent link) */ - inline unsigned int mtu() const { return _mtu; } + inline uint16_t mtu() const { return _mtu; } /** * @return Given link capacity as reported by the bonding layer */ - inline unsigned int givenLinkSpeed() const { return _givenLinkSpeed; } + inline uint32_t givenLinkSpeed() const { return _givenLinkSpeed; } /** * @return Path's quality as reported by the bonding layer From 925599cab0600ec99dd56dabb08c4dd889f2cfd5 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 3 May 2023 13:43:45 -0700 Subject: [PATCH 13/41] Network-metrics (#1994) * Add a couple quick functions for converting a uint64_t network ID/node ID into std::string * Network metrics --- node/Metrics.cpp | 10 ++++++++++ node/Metrics.hpp | 6 ++++++ node/Network.cpp | 20 ++++++++++++++++++-- node/Network.hpp | 8 ++++++++ osdep/OSUtils.cpp | 13 +++++++++++++ osdep/OSUtils.hpp | 16 ++++++++++++++++ 6 files changed, 71 insertions(+), 2 deletions(-) diff --git a/node/Metrics.cpp b/node/Metrics.cpp index c90331982..7c10540e5 100644 --- a/node/Metrics.cpp +++ b/node/Metrics.cpp @@ -166,6 +166,16 @@ namespace ZeroTier { prometheus::simpleapi::counter_metric_t tcp_recv { "zt_tcp_data_recv", "number of bytes ZeroTier has received via TCP" }; + // Network Metrics + prometheus::simpleapi::gauge_metric_t network_num_joined + { "zt_num_networks", "number of networks this instance is joined to" }; + prometheus::simpleapi::gauge_family_t network_num_multicast_groups + { "zt_network_multcast_groups_subscribed", "number of multicast groups networks are subscribed to" }; + prometheus::simpleapi::counter_family_t network_incoming_packets + { "zt_network_incoming_packets", "number of incoming packets per network" }; + prometheus::simpleapi::counter_family_t network_outgoing_packets + { "zt_network_outgoing_packets", "number of outgoing packets per network" }; + // General Controller Metrics prometheus::simpleapi::gauge_metric_t network_count {"controller_network_count", "number of networks the controller is serving"}; diff --git a/node/Metrics.hpp b/node/Metrics.hpp index 7530e48c0..a3efcc284 100644 --- a/node/Metrics.hpp +++ b/node/Metrics.hpp @@ -101,6 +101,12 @@ namespace ZeroTier { extern prometheus::simpleapi::counter_metric_t tcp_send; extern prometheus::simpleapi::counter_metric_t tcp_recv; + // Network Metrics + extern prometheus::simpleapi::gauge_metric_t network_num_joined; + extern prometheus::simpleapi::gauge_family_t network_num_multicast_groups; + extern prometheus::simpleapi::counter_family_t network_incoming_packets; + extern prometheus::simpleapi::counter_family_t network_outgoing_packets; + // General Controller Metrics extern prometheus::simpleapi::gauge_metric_t network_count; extern prometheus::simpleapi::gauge_metric_t member_count; diff --git a/node/Network.cpp b/node/Network.cpp index 80858126a..10436aedb 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -32,6 +32,7 @@ #include "Node.hpp" #include "Peer.hpp" #include "Trace.hpp" +#include "Metrics.hpp" #include @@ -559,13 +560,19 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u RR(renv), _uPtr(uptr), _id(nwid), + _nwidStr(OSUtils::networkIDStr(nwid)), _lastAnnouncedMulticastGroupsUpstream(0), _mac(renv->identity.address(),nwid), _portInitialized(false), _lastConfigUpdate(0), _destroyed(false), _netconfFailure(NETCONF_FAILURE_NONE), - _portError(0) + _portError(0), + _num_multicast_groups{Metrics::network_num_multicast_groups.Add({{"network_id", _nwidStr}})}, + _incoming_packets_accpeted{Metrics::network_incoming_packets.Add({{"network_id", _nwidStr},{"accepted","yes"}})}, + _incoming_packets_dropped{Metrics::network_incoming_packets.Add({{"network_id", _nwidStr},{"accepted","no"}})}, + _outgoing_packets_accepted{Metrics::network_outgoing_packets.Add({{"network_id", _nwidStr},{"accepted","yes"}})}, + _outgoing_packets_dropped{Metrics::network_outgoing_packets.Add({{"network_id", _nwidStr},{"accepted","no"}})} { for(int i=0;inode->configureVirtualNetworkPort(tPtr,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp); _portInitialized = true; } + + Metrics::network_num_joined++; } Network::~Network() { ZT_VirtualNetworkConfig ctmp; _externalConfig(&ctmp); - + Metrics::network_num_joined--; if (_destroyed) { // This is done in Node::leave() so we can pass tPtr properly //RR->node->configureVirtualNetworkPort((void *)0,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp); @@ -705,6 +714,7 @@ bool Network::filterOutgoingPacket( } if (accept) { + _outgoing_packets_accepted++; if ((!noTee)&&(cc)) { Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME); outp.append(_id); @@ -739,6 +749,7 @@ bool Network::filterOutgoingPacket( return true; } } else { + _outgoing_packets_dropped++; if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog *)0,(localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability *)0,ztSource,ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,noTee,false,0); } @@ -826,6 +837,7 @@ int Network::filterIncomingPacket( } if (accept) { + _incoming_packets_accpeted++; if (cc) { Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME); outp.append(_id); @@ -854,6 +866,8 @@ int Network::filterIncomingPacket( } return 0; // DROP locally, since we redirected } + } else { + _incoming_packets_dropped++; } if (_config.remoteTraceTarget) { @@ -879,6 +893,7 @@ void Network::multicastSubscribe(void *tPtr,const MulticastGroup &mg) if (!std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)) { _myMulticastGroups.insert(std::upper_bound(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg),mg); _sendUpdatesToMembers(tPtr,&mg); + _num_multicast_groups++; } } @@ -888,6 +903,7 @@ void Network::multicastUnsubscribe(const MulticastGroup &mg) std::vector::iterator i(std::lower_bound(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)); if ( (i != _myMulticastGroups.end()) && (*i == mg) ) { _myMulticastGroups.erase(i); + _num_multicast_groups--; } } diff --git a/node/Network.hpp b/node/Network.hpp index a86a5c48b..676e5556e 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -37,6 +37,7 @@ #include "Membership.hpp" #include "NetworkConfig.hpp" #include "CertificateOfMembership.hpp" +#include "Metrics.hpp" #define ZT_NETWORK_MAX_INCOMING_UPDATES 3 #define ZT_NETWORK_MAX_UPDATE_CHUNKS ((ZT_NETWORKCONFIG_DICT_CAPACITY / 1024) + 1) @@ -439,6 +440,7 @@ private: const RuntimeEnvironment *const RR; void *_uPtr; const uint64_t _id; + std::string _nwidStr; uint64_t _lastAnnouncedMulticastGroupsUpstream; MAC _mac; // local MAC address bool _portInitialized; @@ -479,6 +481,12 @@ private: Mutex _lock; AtomicCounter __refCount; + + prometheus::simpleapi::gauge_metric_t _num_multicast_groups; + prometheus::simpleapi::counter_metric_t _incoming_packets_accpeted; + prometheus::simpleapi::counter_metric_t _incoming_packets_dropped; + prometheus::simpleapi::counter_metric_t _outgoing_packets_accepted; + prometheus::simpleapi::counter_metric_t _outgoing_packets_dropped; }; } // namespace ZeroTier diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp index e237325c4..67536c56a 100644 --- a/osdep/OSUtils.cpp +++ b/osdep/OSUtils.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "../node/Constants.hpp" #include "../node/Utils.hpp" @@ -66,6 +67,18 @@ unsigned int OSUtils::ztsnprintf(char *buf,unsigned int len,const char *fmt,...) return (unsigned int)n; } +std::string OSUtils::networkIDStr(const uint64_t nwid) { + char tmp[32] = {}; + ztsnprintf(tmp, sizeof(tmp), "%.16" PRIx64, nwid); + return std::string(tmp); +} + +std::string OSUtils::nodeIDStr(const uint64_t nid) { + char tmp[32] = {}; + ztsnprintf(tmp, sizeof(tmp), "%.10" PRIx64, nid); + return std::string(tmp); +} + #ifdef __UNIX_LIKE__ bool OSUtils::redirectUnixOutputs(const char *stdoutPath,const char *stderrPath) throw() diff --git a/osdep/OSUtils.hpp b/osdep/OSUtils.hpp index 021b3876f..43df98cb8 100644 --- a/osdep/OSUtils.hpp +++ b/osdep/OSUtils.hpp @@ -68,6 +68,22 @@ public: */ static unsigned int ztsnprintf(char *buf,unsigned int len,const char *fmt,...); + /** + * Converts a uint64_t network ID into a string + * + * @param nwid network ID + * @throws std::length_error buf[] too short (buf[] will still be left null-terminated) + */ + static std::string networkIDStr(const uint64_t nwid); + + /** + * Converts a uint64_t node ID into a string + * + * @param nid node ID + * @throws std::length_error buf[] too short (buf[] will still be left null-terminated) + */ + static std::string nodeIDStr(const uint64_t nid); + #ifdef __UNIX_LIKE__ /** * Close STDOUT_FILENO and STDERR_FILENO and replace them with output to given path From 74dc41c7c73669f5575851c830050747e332e38d Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 4 May 2023 07:58:02 -0700 Subject: [PATCH 14/41] Peer metrics (#1995) * Adding peer metrics still need to be wired up for use * per peer packet metrics * Fix crash from bad instantiation of histogram * separate alive & dead path counts * Add peer metric update block * add peer latency values in doPingAndKeepalive * prevent deadlock * peer latency histogram actually works now * cleanup * capture counts of packets to specific peers --------- Co-authored-by: Joseph Henry --- .../core/include/prometheus/histogram.h | 18 +-- node/Metrics.cpp | 16 +++ node/Metrics.hpp | 7 ++ node/Peer.cpp | 107 +++++++++++------- node/Peer.hpp | 7 ++ 5 files changed, 102 insertions(+), 53 deletions(-) diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h index ac558fa21..0afe41758 100644 --- a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h @@ -28,15 +28,9 @@ namespace prometheus { /// a data race. template class Histogram : public Metric { - - using BucketBoundaries = std::vector; - - const BucketBoundaries bucket_boundaries_; - std::vector> bucket_counts_; - Gauge sum_; - public: using Value = Value_; + using BucketBoundaries = std::vector; using Family = CustomFamily>; static const Metric::Type static_type = Metric::Type::Histogram; @@ -69,7 +63,7 @@ namespace prometheus { bucket_boundaries_.begin(), std::find_if( std::begin(bucket_boundaries_), std::end(bucket_boundaries_), - [value](const double boundary) { return boundary >= value; }))); + [value](const Value boundary) { return boundary >= value; }))); sum_.Increment(value); bucket_counts_[bucket_index].Increment(); } @@ -110,7 +104,7 @@ namespace prometheus { bucket.cumulative_count = cumulative_count; bucket.upper_bound = (i == bucket_boundaries_.size() ? std::numeric_limits::infinity() - : bucket_boundaries_[i]); + : static_cast(bucket_boundaries_[i])); metric.histogram.bucket.push_back(std::move(bucket)); } metric.histogram.sample_count = cumulative_count; @@ -119,6 +113,12 @@ namespace prometheus { return metric; } + private: + const BucketBoundaries bucket_boundaries_; + std::vector> bucket_counts_; + Gauge sum_; + + }; /// \brief Return a builder to configure and register a Histogram metric. diff --git a/node/Metrics.cpp b/node/Metrics.cpp index 7c10540e5..e20f06c32 100644 --- a/node/Metrics.cpp +++ b/node/Metrics.cpp @@ -176,6 +176,22 @@ namespace ZeroTier { prometheus::simpleapi::counter_family_t network_outgoing_packets { "zt_network_outgoing_packets", "number of outgoing packets per network" }; + // PeerMetrics + prometheus::CustomFamily> &peer_latency = + prometheus::Builder>() + .Name("zt_peer_latency") + .Help("peer latency (ms)") + .Register(prometheus::simpleapi::registry); + + prometheus::simpleapi::gauge_family_t peer_path_count + { "zt_peer_path_count", "number of paths to peer" }; + prometheus::simpleapi::counter_family_t peer_incoming_packets + { "zt_peer_incoming_packets", "number of incoming packets from a peer" }; + prometheus::simpleapi::counter_family_t peer_outgoing_packets + { "zt_peer_outgoing_packets", "number of outgoing packets to a peer" }; + prometheus::simpleapi::counter_family_t peer_packet_errors + { "zt_peer_packet_errors" , "number of incoming packet errors from a peer" }; + // General Controller Metrics prometheus::simpleapi::gauge_metric_t network_count {"controller_network_count", "number of networks the controller is serving"}; diff --git a/node/Metrics.hpp b/node/Metrics.hpp index a3efcc284..f78a0f157 100644 --- a/node/Metrics.hpp +++ b/node/Metrics.hpp @@ -107,6 +107,13 @@ namespace ZeroTier { extern prometheus::simpleapi::counter_family_t network_incoming_packets; extern prometheus::simpleapi::counter_family_t network_outgoing_packets; + // Peer Metrics + extern prometheus::CustomFamily> &peer_latency; + extern prometheus::simpleapi::gauge_family_t peer_path_count; + extern prometheus::simpleapi::counter_family_t peer_incoming_packets; + extern prometheus::simpleapi::counter_family_t peer_outgoing_packets; + extern prometheus::simpleapi::counter_family_t peer_packet_errors; + // General Controller Metrics extern prometheus::simpleapi::gauge_metric_t network_count; extern prometheus::simpleapi::gauge_metric_t member_count; diff --git a/node/Peer.cpp b/node/Peer.cpp index c46bdf9d2..a08bebbf7 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -28,11 +28,6 @@ namespace ZeroTier { static unsigned char s_freeRandomByteCounter = 0; -char * peerIDString(const Identity &id) { - char out[16]; - return id.address().toString(out); -} - Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) : RR(renv), _lastReceive(0), @@ -55,7 +50,13 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident _directPathPushCutoffCount(0), _echoRequestCutoffCount(0), _localMultipathSupported(false), - _lastComputedAggregateMeanLatency(0) + _lastComputedAggregateMeanLatency(0), + _peer_latency{Metrics::peer_latency.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}}, std::vector{1,3,6,10,30,60,100,300,600,1000})}, + _alive_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","alive"}})}, + _dead_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","dead"}})}, + _incoming_packet{Metrics::peer_incoming_packets.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})}, + _outgoing_packet{Metrics::peer_outgoing_packets.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})}, + _packet_errors{Metrics::peer_packet_errors.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})} { if (!myIdentity.agree(peerIdentity,_key)) { throw ZT_EXCEPTION_INVALID_ARGUMENT; @@ -96,7 +97,7 @@ void Peer::received( default: break; } - + _incoming_packet++; recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now); if (trustEstablished) { @@ -519,54 +520,70 @@ void Peer::performMultipathStateCheck(void *tPtr, int64_t now) unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now) { unsigned int sent = 0; - Mutex::Lock _l(_paths_m); + { + Mutex::Lock _l(_paths_m); - performMultipathStateCheck(tPtr, now); + performMultipathStateCheck(tPtr, now); - const bool sendFullHello = ((now - _lastSentFullHello) >= ZT_PEER_PING_PERIOD); - if (sendFullHello) { - _lastSentFullHello = now; - } - - // Right now we only keep pinging links that have the maximum priority. The - // priority is used to track cluster redirections, meaning that when a cluster - // redirects us its redirect target links override all other links and we - // let those old links expire. - long maxPriority = 0; - for(unsigned int i=0;i= ZT_PEER_PING_PERIOD); + if (sendFullHello) { + _lastSentFullHello = now; } - } - bool deletionOccurred = false; - for(unsigned int i=0;ineedsHeartbeat(now))) { - attemptToContactAt(tPtr,_paths[i].p->localSocket(),_paths[i].p->address(),now,sendFullHello); - _paths[i].p->sent(now); - sent |= (_paths[i].p->address().ss_family == AF_INET) ? 0x1 : 0x2; - } + // Right now we only keep pinging links that have the maximum priority. The + // priority is used to track cluster redirections, meaning that when a cluster + // redirects us its redirect target links override all other links and we + // let those old links expire. + long maxPriority = 0; + for(unsigned int i=0;ineedsHeartbeat(now))) { + attemptToContactAt(tPtr,_paths[i].p->localSocket(),_paths[i].p->address(),now,sendFullHello); + _paths[i].p->sent(now); + sent |= (_paths[i].p->address().ss_family == AF_INET) ? 0x1 : 0x2; + } + } else { + _paths[i] = _PeerPath(); + deletionOccurred = true; } } - deletionOccurred = false; + if (!_paths[i].p || deletionOccurred) { + for(unsigned int j=i;jalive(now)) { + alive_path_count_tmp++; + } + else { + dead_path_count_tmp++; + } + } + } + _alive_path_count = alive_path_count_tmp; + _dead_path_count = dead_path_count_tmp; } + _peer_latency.Observe(latency(now)); return sent; } @@ -641,6 +658,7 @@ void Peer::resetWithinScope(void *tPtr,InetAddress::IpScope scope,int inetAddres void Peer::recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now) { + _outgoing_packet++; if (_localMultipathSupported && _bond) { _bond->recordOutgoingPacket(path, packetId, payloadLength, verb, flowId, now); } @@ -648,6 +666,7 @@ void Peer::recordOutgoingPacket(const SharedPtr &path, const uint64_t pack void Peer::recordIncomingInvalidPacket(const SharedPtr& path) { + _packet_errors++; if (_localMultipathSupported && _bond) { _bond->recordIncomingInvalidPacket(path); } diff --git a/node/Peer.hpp b/node/Peer.hpp index 427e78a58..cd6b871fe 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -598,6 +598,13 @@ private: int32_t _lastComputedAggregateMeanLatency; SharedPtr _bond; + + prometheus::Histogram &_peer_latency; + prometheus::simpleapi::gauge_metric_t _alive_path_count; + prometheus::simpleapi::gauge_metric_t _dead_path_count; + prometheus::simpleapi::counter_metric_t _incoming_packet; + prometheus::simpleapi::counter_metric_t _outgoing_packet; + prometheus::simpleapi::counter_metric_t _packet_errors; }; } // namespace ZeroTier From 00d55fc4b407eb91382ea412f99b007631f923b5 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 4 May 2023 11:12:55 -0700 Subject: [PATCH 15/41] Metrics consolidation (#1997) * Rename zt_packet_incoming -> zt_packet Also consolidate zt_peer_packets into a single metric with tx and rx labels. Same for ztc_tcp_data and ztc_udp_data * Further collapse tcp & udp into metric labels for zt_data * Fix zt_data metric description * zt_peer_packets description fix * Consolidate incoming/outgoing network packets to a single metric * zt_incoming_packet_error -> zt_packet_error * Disable peer metrics for central controllers Can change in the future if needed, but given the traffic our controllers serve, that's going to be a *lot* of data * Disable peer metrics for controllers pt 2 --- make-linux.mk | 2 +- node/Metrics.cpp | 34 +++++++++++------------ node/Metrics.hpp | 15 ++++++----- node/Network.cpp | 10 +++---- node/Network.hpp | 2 +- node/Peer.cpp | 70 ++++++++++++++++++++++++++++-------------------- node/Peer.hpp | 2 ++ 7 files changed, 75 insertions(+), 60 deletions(-) diff --git a/make-linux.mk b/make-linux.mk index ede843c5f..3b448afb0 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -311,7 +311,7 @@ endif ifeq ($(ZT_CONTROLLER),1) override CXXFLAGS+=-Wall -Wno-deprecated -std=c++17 -pthread $(INCLUDES) -DNDEBUG $(DEFS) override LDLIBS+=-Lext/libpqxx-7.7.3/install/ubuntu22.04/lib -lpqxx -lpq ext/hiredis-1.0.2/lib/ubuntu22.04/libhiredis.a ext/redis-plus-plus-1.3.3/install/ubuntu22.04/lib/libredis++.a -lssl -lcrypto - override DEFS+=-DZT_CONTROLLER_USE_LIBPQ + override DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_NO_PEER_METRICS override INCLUDES+=-I/usr/include/postgresql -Iext/libpqxx-7.7.3/install/ubuntu22.04/include -Iext/hiredis-1.0.2/include/ -Iext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/ endif diff --git a/node/Metrics.cpp b/node/Metrics.cpp index e20f06c32..ba168bcc9 100644 --- a/node/Metrics.cpp +++ b/node/Metrics.cpp @@ -25,7 +25,7 @@ namespace ZeroTier { namespace Metrics { // Packet Type Counts prometheus::simpleapi::counter_family_t packets - { "zt_packet_incoming", "incoming packet type counts"}; + { "zt_packet", "incoming packet type counts"}; // Incoming packets prometheus::simpleapi::counter_metric_t pkt_nop_in @@ -118,7 +118,7 @@ namespace ZeroTier { // Packet Error Counts prometheus::simpleapi::counter_family_t packet_errors - { "zt_packet_incoming_error", "incoming packet errors"}; + { "zt_packet_error", "incoming packet errors"}; // Incoming Error Counts prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found_in @@ -157,25 +157,26 @@ namespace ZeroTier { { packet_errors.Add({{"error_type", "internal_server_error"}, {"direction", "tx"}}) }; // Data Sent/Received Metrics - prometheus::simpleapi::counter_metric_t udp_send - { "zt_udp_data_sent", "number of bytes ZeroTier has sent via UDP" }; + prometheus::simpleapi::counter_family_t data + { "zt_data", "number of bytes ZeroTier has transmitted or received" }; prometheus::simpleapi::counter_metric_t udp_recv - { "zt_udp_data_recv", "number of bytes ZeroTier has received via UDP" }; + { data.Add({{"protocol","udp"},{"direction","rx"}}) }; + prometheus::simpleapi::counter_metric_t udp_send + { data.Add({{"protocol","udp"},{"direction","tx"}}) }; prometheus::simpleapi::counter_metric_t tcp_send - { "zt_tcp_data_sent", "number of bytes ZeroTier has sent via TCP" }; + { data.Add({{"protocol","tcp"},{"direction", "tx"}}) }; prometheus::simpleapi::counter_metric_t tcp_recv - { "zt_tcp_data_recv", "number of bytes ZeroTier has received via TCP" }; + { data.Add({{"protocol","tcp"},{"direction", "rx"}}) }; // Network Metrics prometheus::simpleapi::gauge_metric_t network_num_joined { "zt_num_networks", "number of networks this instance is joined to" }; prometheus::simpleapi::gauge_family_t network_num_multicast_groups - { "zt_network_multcast_groups_subscribed", "number of multicast groups networks are subscribed to" }; - prometheus::simpleapi::counter_family_t network_incoming_packets - { "zt_network_incoming_packets", "number of incoming packets per network" }; - prometheus::simpleapi::counter_family_t network_outgoing_packets - { "zt_network_outgoing_packets", "number of outgoing packets per network" }; - + { "zt_network_multicast_groups_subscribed", "number of multicast groups networks are subscribed to" }; + prometheus::simpleapi::counter_family_t network_packets + { "zt_network_packets", "number of incoming/outgoing packets per network" }; + +#ifndef ZT_NO_PEER_METRICS // PeerMetrics prometheus::CustomFamily> &peer_latency = prometheus::Builder>() @@ -185,12 +186,11 @@ namespace ZeroTier { prometheus::simpleapi::gauge_family_t peer_path_count { "zt_peer_path_count", "number of paths to peer" }; - prometheus::simpleapi::counter_family_t peer_incoming_packets - { "zt_peer_incoming_packets", "number of incoming packets from a peer" }; - prometheus::simpleapi::counter_family_t peer_outgoing_packets - { "zt_peer_outgoing_packets", "number of outgoing packets to a peer" }; + prometheus::simpleapi::counter_family_t peer_packets + { "zt_peer_packets", "number of packets to/from a peer" }; prometheus::simpleapi::counter_family_t peer_packet_errors { "zt_peer_packet_errors" , "number of incoming packet errors from a peer" }; +#endif // General Controller Metrics prometheus::simpleapi::gauge_metric_t network_count diff --git a/node/Metrics.hpp b/node/Metrics.hpp index f78a0f157..66b97c0d6 100644 --- a/node/Metrics.hpp +++ b/node/Metrics.hpp @@ -96,23 +96,24 @@ namespace ZeroTier { extern prometheus::simpleapi::counter_metric_t pkt_error_internal_server_error_out; // Data Sent/Received Metrics + extern prometheus::simpleapi::counter_family_t data; extern prometheus::simpleapi::counter_metric_t udp_send; extern prometheus::simpleapi::counter_metric_t udp_recv; extern prometheus::simpleapi::counter_metric_t tcp_send; extern prometheus::simpleapi::counter_metric_t tcp_recv; // Network Metrics - extern prometheus::simpleapi::gauge_metric_t network_num_joined; - extern prometheus::simpleapi::gauge_family_t network_num_multicast_groups; - extern prometheus::simpleapi::counter_family_t network_incoming_packets; - extern prometheus::simpleapi::counter_family_t network_outgoing_packets; + extern prometheus::simpleapi::gauge_metric_t network_num_joined; + extern prometheus::simpleapi::gauge_family_t network_num_multicast_groups; + extern prometheus::simpleapi::counter_family_t network_packets; +#ifndef ZT_NO_PEER_METRICS // Peer Metrics extern prometheus::CustomFamily> &peer_latency; - extern prometheus::simpleapi::gauge_family_t peer_path_count; - extern prometheus::simpleapi::counter_family_t peer_incoming_packets; - extern prometheus::simpleapi::counter_family_t peer_outgoing_packets; + extern prometheus::simpleapi::gauge_family_t peer_path_count; + extern prometheus::simpleapi::counter_family_t peer_packets; extern prometheus::simpleapi::counter_family_t peer_packet_errors; +#endif // General Controller Metrics extern prometheus::simpleapi::gauge_metric_t network_count; diff --git a/node/Network.cpp b/node/Network.cpp index 10436aedb..1e77e4636 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -569,10 +569,10 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u _netconfFailure(NETCONF_FAILURE_NONE), _portError(0), _num_multicast_groups{Metrics::network_num_multicast_groups.Add({{"network_id", _nwidStr}})}, - _incoming_packets_accpeted{Metrics::network_incoming_packets.Add({{"network_id", _nwidStr},{"accepted","yes"}})}, - _incoming_packets_dropped{Metrics::network_incoming_packets.Add({{"network_id", _nwidStr},{"accepted","no"}})}, - _outgoing_packets_accepted{Metrics::network_outgoing_packets.Add({{"network_id", _nwidStr},{"accepted","yes"}})}, - _outgoing_packets_dropped{Metrics::network_outgoing_packets.Add({{"network_id", _nwidStr},{"accepted","no"}})} + _incoming_packets_accepted{Metrics::network_packets.Add({{"direction","rx"},{"network_id", _nwidStr},{"accepted","yes"}})}, + _incoming_packets_dropped{Metrics::network_packets.Add({{"direction","rx"},{"network_id", _nwidStr},{"accepted","no"}})}, + _outgoing_packets_accepted{Metrics::network_packets.Add({{"direction","tx"},{"network_id", _nwidStr},{"accepted","yes"}})}, + _outgoing_packets_dropped{Metrics::network_packets.Add({{"direction","tx"},{"network_id", _nwidStr},{"accepted","no"}})} { for(int i=0;iidentity.address(),Packet::VERB_EXT_FRAME); outp.append(_id); diff --git a/node/Network.hpp b/node/Network.hpp index 676e5556e..a3bce14af 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -483,7 +483,7 @@ private: AtomicCounter __refCount; prometheus::simpleapi::gauge_metric_t _num_multicast_groups; - prometheus::simpleapi::counter_metric_t _incoming_packets_accpeted; + prometheus::simpleapi::counter_metric_t _incoming_packets_accepted; prometheus::simpleapi::counter_metric_t _incoming_packets_dropped; prometheus::simpleapi::counter_metric_t _outgoing_packets_accepted; prometheus::simpleapi::counter_metric_t _outgoing_packets_dropped; diff --git a/node/Peer.cpp b/node/Peer.cpp index a08bebbf7..6fcf193d9 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -28,35 +28,37 @@ namespace ZeroTier { static unsigned char s_freeRandomByteCounter = 0; -Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) : - RR(renv), - _lastReceive(0), - _lastNontrivialReceive(0), - _lastTriedMemorizedPath(0), - _lastDirectPathPushSent(0), - _lastDirectPathPushReceive(0), - _lastCredentialRequestSent(0), - _lastWhoisRequestReceived(0), - _lastCredentialsReceived(0), - _lastTrustEstablishedPacketReceived(0), - _lastSentFullHello(0), - _lastEchoCheck(0), - _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter), - _vProto(0), - _vMajor(0), - _vMinor(0), - _vRevision(0), - _id(peerIdentity), - _directPathPushCutoffCount(0), - _echoRequestCutoffCount(0), - _localMultipathSupported(false), - _lastComputedAggregateMeanLatency(0), - _peer_latency{Metrics::peer_latency.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}}, std::vector{1,3,6,10,30,60,100,300,600,1000})}, - _alive_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","alive"}})}, - _dead_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","dead"}})}, - _incoming_packet{Metrics::peer_incoming_packets.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})}, - _outgoing_packet{Metrics::peer_outgoing_packets.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})}, - _packet_errors{Metrics::peer_packet_errors.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})} +Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) + : RR(renv) + , _lastReceive(0) + , _lastNontrivialReceive(0) + , _lastTriedMemorizedPath(0) + , _lastDirectPathPushSent(0) + , _lastDirectPathPushReceive(0) + , _lastCredentialRequestSent(0) + , _lastWhoisRequestReceived(0) + , _lastCredentialsReceived(0) + , _lastTrustEstablishedPacketReceived(0) + , _lastSentFullHello(0) + , _lastEchoCheck(0) + , _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter) + , _vProto(0) + , _vMajor(0) + , _vMinor(0) + , _vRevision(0) + , _id(peerIdentity) + , _directPathPushCutoffCount(0) + , _echoRequestCutoffCount(0) + , _localMultipathSupported(false) + , _lastComputedAggregateMeanLatency(0) +#ifndef ZT_NO_PEER_METRICS + , _peer_latency{Metrics::peer_latency.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}}, std::vector{1,3,6,10,30,60,100,300,600,1000})} + , _alive_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","alive"}})} + , _dead_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","dead"}})} + , _incoming_packet{Metrics::peer_packets.Add({{"direction", "rx"},{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})} + , _outgoing_packet{Metrics::peer_packets.Add({{"direction", "tx"},{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})} + , _packet_errors{Metrics::peer_packet_errors.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})} +#endif { if (!myIdentity.agree(peerIdentity,_key)) { throw ZT_EXCEPTION_INVALID_ARGUMENT; @@ -97,7 +99,9 @@ void Peer::received( default: break; } +#ifndef ZT_NO_PEER_METRICS _incoming_packet++; +#endif recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now); if (trustEstablished) { @@ -569,6 +573,7 @@ unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now) deletionOccurred = false; } } +#ifndef ZT_NO_PEER_METRICS uint16_t alive_path_count_tmp = 0, dead_path_count_tmp = 0; for(unsigned int i=0;i &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now) { +#ifndef ZT_NO_PEER_METRICS _outgoing_packet++; +#endif if (_localMultipathSupported && _bond) { _bond->recordOutgoingPacket(path, packetId, payloadLength, verb, flowId, now); } @@ -666,7 +676,9 @@ void Peer::recordOutgoingPacket(const SharedPtr &path, const uint64_t pack void Peer::recordIncomingInvalidPacket(const SharedPtr& path) { +#ifndef ZT_NO_PEER_METRICS _packet_errors++; +#endif if (_localMultipathSupported && _bond) { _bond->recordIncomingInvalidPacket(path); } diff --git a/node/Peer.hpp b/node/Peer.hpp index cd6b871fe..d03e8f884 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -599,12 +599,14 @@ private: SharedPtr _bond; +#ifndef ZT_NO_PEER_METRICS prometheus::Histogram &_peer_latency; prometheus::simpleapi::gauge_metric_t _alive_path_count; prometheus::simpleapi::gauge_metric_t _dead_path_count; prometheus::simpleapi::counter_metric_t _incoming_packet; prometheus::simpleapi::counter_metric_t _outgoing_packet; prometheus::simpleapi::counter_metric_t _packet_errors; +#endif }; } // namespace ZeroTier From f621261ff919278d631d566591a2eaf7c4c918d8 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Fri, 5 May 2023 12:44:41 -0700 Subject: [PATCH 16/41] Update readme files for metrics (#2000) --- README.md | 63 +++++++++++- controller/README.md | 221 +++---------------------------------------- node/Metrics.cpp | 4 +- 3 files changed, 76 insertions(+), 212 deletions(-) diff --git a/README.md b/README.md index 66ee9e997..287e3b732 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,69 @@ On CentOS check `/etc/sysconfig/iptables` for IPTables rules. For other distribu ZeroTier One peers will automatically locate each other and communicate directly over a local wired LAN *if UDP port 9993 inbound is open*. If that port is filtered, they won't be able to see each others' LAN announcement packets. If you're experiencing poor performance between devices on the same physical network, check their firewall settings. Without LAN auto-location peers must attempt "loopback" NAT traversal, which sometimes fails and in any case requires that every packet traverse your external router twice. -Users behind certain types of firewalls and "symmetric" NAT devices may not able able to connect to external peers directly at all. ZeroTier has limited support for port prediction and will *attempt* to traverse symmetric NATs, but this doesn't always work. If P2P connectivity fails you'll be bouncing UDP packets off our relay servers resulting in slower performance. Some NAT router(s) have a configurable NAT mode, and setting this to "full cone" will eliminate this problem. If you do this you may also see a magical improvement for things like VoIP phones, Skype, BitTorrent, WebRTC, certain games, etc., since all of these use NAT traversal techniques similar to ours. +Users behind certain types of firewalls and "symmetric" NAT devices may not be able to connect to external peers directly at all. ZeroTier has limited support for port prediction and will *attempt* to traverse symmetric NATs, but this doesn't always work. If P2P connectivity fails you'll be bouncing UDP packets off our relay servers resulting in slower performance. Some NAT router(s) have a configurable NAT mode, and setting this to "full cone" will eliminate this problem. If you do this you may also see a magical improvement for things like VoIP phones, Skype, BitTorrent, WebRTC, certain games, etc., since all of these use NAT traversal techniques similar to ours. If a firewall between you and the Internet blocks ZeroTier's UDP traffic, you will fall back to last-resort TCP tunneling to rootservers over port 443 (https impersonation). This will work almost anywhere but is *very slow* compared to UDP or direct peer to peer connectivity. Additional help can be found in our [knowledge base](https://zerotier.atlassian.net/wiki/spaces/SD/overview). + +### Prometheus Metrics + +Prometheus Metrics are available at the `/metrics` API endpoint. This endpoint is protected by an API key stored in `authtoken.secret` because of the possibility of information leakage. Information that could be gleaned from the metrics include joined networks and peers your instance is talking to. + +Access control is via the ZeroTier control interface itself and `authtoken.secret`. This can be sent as the `X-ZT1-Auth` HTTP header field or appended to the URL as `?auth=`. You can see the current metrics via `cURL` with the following command: + + // Linux + curl -H "X-ZT1-Auth: $(sudo cat /var/lib/zerotier-one/authtoken.secret)" http://localhost:9993/metrics + + // macOS + curl -H "X-XT1-Auth: $(sudo cat /Library/Application\ Support/ZeroTier/One/authtoken.secret)" http://localhost:9993/metrics + + // Windows PowerShell (Admin) + Invoke-RestMethod -Headers @{'X-ZT1-Auth' = "$(Get-Content C:\ProgramData\ZeroTier\One\authtoken.secret)"; } -Uri http://localhost:9993/metrics + +To configure a scrape job in Prometheus on the machine ZeroTier is running on, add this to your Prometheus `scrape_config`: + + - job_name: zerotier-one + honor_labels: true + scrape_interval: 15s + metrics_path: /metrics + static_configs: + - targets: + - 127.0.0.1:9993 + labels: + group: zerotier-one + params: + auth: + - $YOUR_AUTHTOKEN_SECRET + +If your Prometheus instance is remote from the machine ZeroTier instance, you'll have to edit your `local.conf` file to allow remote access to the API control port. If your local lan is `10.0.0.0/24`, edit your `local.conf` as follows: + + { + "settings": { + "allowManagementFrom:" ["10.0.0.0/24"] + } + } + +Substitute your actual network IP ranges as necessary. + +It's also possible to access the metrics & control port over the ZeroTier network itself via the same method shown above. Just add the address range of your ZeroTier network to the list. NOTE: Using this method means that anyone with your auth token can control your ZeroTier instance, including leaving & joining other networks. + +If neither of these methods are desirable, it is probably possible to distribute metrics via [Prometheus Proxy](https://github.com/pambrose/prometheus-proxy) or some other tool. Note: We have not tested this internally, but will probably work with the correct configuration. + +#### Available Metrics + +| Metric Name | Labels | Metric Type | Description | +| --- | --- | --- | --- | +| zt_packet | packet_type, direction | Counter | ZeroTier packet type counts | +| zt_packet_error | error_type, direction | Counter | ZeroTier packet errors| +| zt_data | protocol, direction | Counter | number of bytes ZeroTier has transmitted or received | +| zt_num_networks | | Gauge | number of networks this instance is joined to | +| zt_network_multicast_groups_subscribed | network_id | Gauge | number of multicast groups networks are subscribed to | +| zt_network_packets | network_id, direction | Counter | number of incoming/outgoing packets per network | +| zt_peer_latency | node_id | Histogram | peer latency (ms) | +| zt_peer_path_count | node_id, status | Gauge | number of paths to peer | +| zt_peer_packets | node_id, direction | Counter | number of packets to/from a peer | +| zt_peer_packet_errors | node_id | Counter | number of incoming packet errors from a peer | + +If there are other metrics you'd like to see tracked, ask us in an Issue or send us a Pull Request! diff --git a/controller/README.md b/controller/README.md index 331710a15..41cfd3ff3 100644 --- a/controller/README.md +++ b/controller/README.md @@ -3,7 +3,7 @@ Network Controller Microservice Every ZeroTier virtual network has a *network controller* responsible for admitting members to the network, issuing certificates, and issuing default configuration information. -This is our reference controller implementation and is the same one we use to power our own hosted services at [my.zerotier.com](https://my.zerotier.com/). As of ZeroTier One version 1.2.0 this code is included in normal builds for desktop, laptop, and server (Linux, etc.) targets. +This is our reference controller implementation and is almost the same as the one we use to power our own hosted services at [my.zerotier.com](https://my.zerotier.com/). The only difference is the database backend used. Controller data is stored in JSON format under `controller.d` in the ZeroTier working directory. It can be copied, rsync'd, placed in `git`, etc. The files under `controller.d` should not be modified in place while the controller is running or data loss may result, and if they are edited directly take care not to save corrupt JSON since that can also lead to data loss when the controller is restarted. Going through the API is strongly preferred to directly modifying these files. @@ -19,10 +19,6 @@ Since ZeroTier nodes are mobile and do not need static IPs, implementing high av ZeroTier network controllers can easily be run in Docker or other container systems. Since containers do not need to actually join networks, extra privilege options like "--device=/dev/net/tun --privileged" are not needed. You'll just need to map the local JSON API port of the running controller and allow it to access the Internet (over UDP/9993 at a minimum) so things can reach and query it. -### PostgreSQL Database Implementation - -The default controller stores its data in the filesystem in `controller.d` under ZeroTier's home folder. There's an alternative implementation that stores data in PostgreSQL that can be built with `make central-controller`. Right now this is only guaranteed to build and run on Centos 7 Linux with PostgreSQL 10 installed via the [PostgreSQL Yum Repository](https://www.postgresql.org/download/linux/redhat/) and is designed for use with [ZeroTier Central](https://my.zerotier.com/). You're welcome to use it but we don't "officially" support it for end-user use and it could change at any time. - ### Upgrading from Older (1.1.14 or earlier) Versions Older versions of this code used a SQLite database instead of in-filesystem JSON. A migration utility called `migrate-sqlite` is included here and *must* be used to migrate this data to the new format. If the controller is started with an old `controller.db` in its working directory it will terminate after printing an error to *stderr*. This is done to prevent "surprises" for those running DIY controllers using the old code. @@ -43,210 +39,17 @@ While networks with any valid ID can be added to the controller's database, it w The controller JSON API is *very* sensitive about types. Integers must be integers and strings strings, etc. Incorrect types may be ignored, set to default values, or set to undefined values. -#### `/controller` +Full documentation of the Controller API can be found on our [documentation site](https://docs.zerotier.com/service/v1#tag/controller) - * Purpose: Check for controller function and return controller status - * Methods: GET - * Returns: { object } +### Prometheus Metrics -| Field | Type | Description | Writable | -| ------------------ | ----------- | ------------------------------------------------- | -------- | -| controller | boolean | Always 'true' | no | -| apiVersion | integer | Controller API version, currently 3 | no | -| clock | integer | Current clock on controller, ms since epoch | no | - -#### `/controller/network` - - * Purpose: List all networks hosted by this controller - * Methods: GET - * Returns: [ string, ... ] - -This returns an array of 16-digit hexadecimal network IDs. - -#### `/controller/network/` - - * Purpose: Create, configure, and delete hosted networks - * Methods: GET, POST, DELETE - * Returns: { object } - -By making queries to this path you can create, configure, and delete networks. DELETE is final, so don't do it unless you really mean it. - -When POSTing new networks take care that their IDs are not in use, otherwise you may overwrite an existing one. To create a new network with a random unused ID, POST to `/controller/network/##########______`. The #'s are the controller's 10-digit ZeroTier address and they're followed by six underscores. Check the `nwid` field of the returned JSON object for your network's newly allocated ID. Subsequent POSTs to this network must refer to its actual path. - -Example: - -`curl -X POST --header "X-ZT1-Auth: secret" -d '{"name":"my network"}' http://localhost:9993/controller/network/305f406058______` - -**Network object format:** - -| Field | Type | Description | Writable | -| --------------------- | ------------- | ------------------------------------------------- | -------- | -| id | string | 16-digit network ID | no | -| nwid | string | 16-digit network ID (legacy) | no | -| objtype | string | Always "network" | no | -| name | string | A short name for this network | YES | -| creationTime | integer | Time network record was created (ms since epoch) | no | -| private | boolean | Is access control enabled? | YES | -| enableBroadcast | boolean | Ethernet ff:ff:ff:ff:ff:ff allowed? | YES | -| v4AssignMode | object | IPv4 management and assign options (see below) | YES | -| v6AssignMode | object | IPv6 management and assign options (see below) | YES | -| mtu | integer | Network MTU (default: 2800) | YES | -| multicastLimit | integer | Maximum recipients for a multicast packet | YES | -| revision | integer | Network config revision counter | no | -| routes | array[object] | Managed IPv4 and IPv6 routes; see below | YES | -| ipAssignmentPools | array[object] | IP auto-assign ranges; see below | YES | -| rules | array[object] | Traffic rules; see below | YES | -| capabilities | array[object] | Array of capability objects (see below) | YES | -| tags | array[object] | Array of tag objects (see below) | YES | -| remoteTraceTarget | string | 10-digit ZeroTier ID of remote trace target | YES | -| remoteTraceLevel | integer | Remote trace verbosity level | YES | - - * Networks without rules won't carry any traffic. If you don't specify any on network creation an "accept anything" rule set will automatically be added. - * Managed IP address assignments and IP assignment pools that do not fall within a route configured in `routes` are ignored and won't be used or sent to members. - * The default for `private` is `true` and this is probably what you want. Turning `private` off means *anyone* can join your network with only its 16-digit network ID. It's also impossible to de-authorize a member as these networks don't issue or enforce certificates. Such "party line" networks are used for decentralized app backplanes, gaming, and testing but are otherwise not common. - * Changing the MTU can be disruptive and on some operating systems may require a leave/rejoin of the network or a restart of the ZeroTier service. - -**Auto-Assign Modes:** - -Auto assign modes (`v4AssignMode` and `v6AssignMode`) contain objects that map assignment modes to booleans. - -For IPv4 the only valid setting is `zt` which, if true, causes IPv4 addresses to be auto-assigned from `ipAssignmentPools` to members that do not have an IPv4 assignment. Note that active bridges are exempt and will not get auto-assigned IPs since this can interfere with bridging. (You can still manually assign one if you want.) - -IPv6 includes this option and two others: `6plane` and `rfc4193`. These assign private IPv6 addresses to each member based on a deterministic assignment scheme that allows members to emulate IPv6 NDP to skip multicast for better performance and scalability. The `rfc4193` mode gives every member a /128 on a /88 network, while `6plane` gives every member a /80 within a /40 network but uses NDP emulation to route *all* IPs under that /80 to its owner. The `6plane` mode is great for use cases like Docker since it allows every member to assign IPv6 addresses within its /80 that just work instantly and globally across the network. - -**IP assignment pool object format:** - -| Field | Type | Description | -| --------------------- | ------------- | ------------------------------------------------- | -| ipRangeStart | string | Starting IP address in range | -| ipRangeEnd | string | Ending IP address in range (inclusive) | - -Pools are only used if auto-assignment is on for the given address type (IPv4 or IPv6) and if the entire range falls within a managed route. - -IPv6 ranges work just like IPv4 ranges and look like this: - - { - "ipRangeStart": "fd00:feed:feed:beef:0000:0000:0000:0000", - "ipRangeEnd": "fd00:feed:feed:beef:ffff:ffff:ffff:ffff" - } - -(You can POST a shortened-form IPv6 address but the API will always report back un-shortened canonical form addresses.) - -That defines a range within network `fd00:feed:feed:beef::/64` that contains up to 2^64 addresses. If an IPv6 range is large enough, the controller will assign addresses by placing each member's device ID into the address in a manner similar to the RFC4193 and 6PLANE modes. Otherwise it will assign addresses at random. - -**Managed Route object format:** - -| Field | Type | Description | -| --------------------- | ------------- | ------------------------------------------------- | -| target | string | Subnet in CIDR notation | -| via | string/null | Next hop router IP address | - -Managed Route objects look like this: - - { - "target": "10.147.20.0/24" - } - -or - - { - "target": "192.168.168.0/24", - "via": "10.147.20.1" - } - -**Rule object format:** - -Each rule is actually a sequence of zero or more `MATCH_` entries in the rule array followed by an `ACTION_` entry that describes what to do if all the preceding entries match. An `ACTION_` without any preceding `MATCH_` entries is always taken, so setting a single `ACTION_ACCEPT` rule yields a network that allows all traffic. If no rules are present the default action is `ACTION_DROP`. - -Rules are evaluated in the order in which they appear in the array. There is currently a limit of 256 entries per network. Capabilities should be used if a larger and more complex rule set is needed since they allow rules to be grouped by purpose and only shipped to members that need them. - -Each rule table entry has two common fields. - -| Field | Type | Description | -| --------------------- | ------------- | ------------------------------------------------- | -| type | string | Entry type (all caps, case sensitive) | -| not | boolean | If true, MATCHes match if they don't match | - -The following fields may or may not be present depending on rule type: - -| Field | Type | Description | -| --------------------- | ------------- | ------------------------------------------------- | -| zt | string | 10-digit hex ZeroTier address | -| etherType | integer | Ethernet frame type | -| mac | string | Hex MAC address (with or without :'s) | -| ip | string | IPv4 or IPv6 address | -| ipTos | integer | IP type of service | -| ipProtocol | integer | IP protocol (e.g. TCP) | -| start | integer | Start of an integer range (e.g. port range) | -| end | integer | End of an integer range (inclusive) | -| id | integer | Tag ID | -| value | integer | Tag value or comparison value | -| mask | integer | Bit mask (for characteristics flags) | - -The entry types and their additional fields are: - -| Entry type | Description | Fields | -| ------------------------------- | ----------------------------------------------------------------- | -------------- | -| `ACTION_DROP` | Drop any packets matching this rule | (none) | -| `ACTION_ACCEPT` | Accept any packets matching this rule | (none) | -| `ACTION_TEE` | Send a copy of this packet to a node (rule parsing continues) | `zt` | -| `ACTION_REDIRECT` | Redirect this packet to another node | `zt` | -| `ACTION_DEBUG_LOG` | Output debug info on match (if built with rules engine debug) | (none) | -| `MATCH_SOURCE_ZEROTIER_ADDRESS` | Match VL1 ZeroTier address of packet sender. | `zt` | -| `MATCH_DEST_ZEROTIER_ADDRESS` | Match VL1 ZeroTier address of recipient | `zt` | -| `MATCH_ETHERTYPE` | Match Ethernet frame type | `etherType` | -| `MATCH_MAC_SOURCE` | Match source Ethernet MAC address | `mac` | -| `MATCH_MAC_DEST` | Match destination Ethernet MAC address | `mac` | -| `MATCH_IPV4_SOURCE` | Match source IPv4 address | `ip` | -| `MATCH_IPV4_DEST` | Match destination IPv4 address | `ip` | -| `MATCH_IPV6_SOURCE` | Match source IPv6 address | `ip` | -| `MATCH_IPV6_DEST` | Match destination IPv6 address | `ip` | -| `MATCH_IP_TOS` | Match IP TOS field | `ipTos` | -| `MATCH_IP_PROTOCOL` | Match IP protocol field | `ipProtocol` | -| `MATCH_IP_SOURCE_PORT_RANGE` | Match a source IP port range | `start`,`end` | -| `MATCH_IP_DEST_PORT_RANGE` | Match a destination IP port range | `start`,`end` | -| `MATCH_CHARACTERISTICS` | Match on characteristics flags | `mask`,`value` | -| `MATCH_FRAME_SIZE_RANGE` | Match a range of Ethernet frame sizes | `start`,`end` | -| `MATCH_TAGS_SAMENESS` | Match if both sides' tags differ by no more than value | `id`,`value` | -| `MATCH_TAGS_BITWISE_AND` | Match if both sides' tags AND to value | `id`,`value` | -| `MATCH_TAGS_BITWISE_OR` | Match if both sides' tags OR to value | `id`,`value` | -| `MATCH_TAGS_BITWISE_XOR` | Match if both sides' tags XOR to value | `id`,`value` | - -Important notes about rules engine behavior: - - * IPv4 and IPv6 IP address rules do not match for frames that are not IPv4 or IPv6 respectively. - * `ACTION_DEBUG_LOG` is a no-op on nodes not built with `ZT_RULES_ENGINE_DEBUGGING` enabled (see Network.cpp). If that is enabled nodes will dump a trace of rule evaluation results to *stdout* when this action is encountered but will otherwise keep evaluating rules. This is used for basic "smoke testing" of the rules engine. - * Multicast packets and packets destined for bridged devices treated a little differently. They are matched more than once. They are matched at the point of send with a NULL ZeroTier destination address, meaning that `MATCH_DEST_ZEROTIER_ADDRESS` is useless. That's because the true VL1 destination is not yet known. Then they are matched again for each true VL1 destination. On these later subsequent matches TEE actions are ignored and REDIRECT rules are interpreted as DROPs. This prevents multiple TEE or REDIRECT packets from being sent to third party devices. - * Rules in capabilities are always matched as if the current device is the sender (inbound == false). A capability specifies sender side rules that can be enforced on both sides. - -#### `/controller/network//member` - - * Purpose: Get a set of all members on this network - * Methods: GET - * Returns: { object } - -This returns a JSON object containing all member IDs as keys and their `memberRevisionCounter` values as values. - -#### `/controller/network//member/
` - - * Purpose: Create, authorize, or remove a network member - * Methods: GET, POST, DELETE - * Returns: { object } - -| Field | Type | Description | Writable | -| --------------------- | ------------- | ------------------------------------------------- | -------- | -| id | string | Member's 10-digit ZeroTier address | no | -| address | string | Member's 10-digit ZeroTier address | no | -| nwid | string | 16-digit network ID | no | -| authorized | boolean | Is member authorized? (for private networks) | YES | -| activeBridge | boolean | Member is able to bridge to other Ethernet nets | YES | -| identity | string | Member's public ZeroTier identity (if known) | no | -| ipAssignments | array[string] | Managed IP address assignments | YES | -| revision | integer | Member revision counter | no | -| vMajor | integer | Most recently known major version | no | -| vMinor | integer | Most recently known minor version | no | -| vRev | integer | Most recently known revision | no | -| vProto | integer | Most recently known protocol version | no | - -Note that managed IP assignments are only used if they fall within a managed route. Otherwise they are ignored. +Controller specific metrics are available from the `/metrics` endpoint. +| Metric Name | Type | Description | +| --- | --- | --- | +| controller_network_count | Gauge | number of networks the controller is serving | +| controller_member_count | Gauge | number of network members the controller is serving | +| controller_network_change_count | Counter | number of times a network configuration is changed | +| controller_member_change_count | Counter | number of times a network member configuration is changed | +| controller_member_auth_count | Counter | number of network member auths | +| controller_member_deauth_count | Counter | number of network member deauths| diff --git a/node/Metrics.cpp b/node/Metrics.cpp index ba168bcc9..623454761 100644 --- a/node/Metrics.cpp +++ b/node/Metrics.cpp @@ -25,7 +25,7 @@ namespace ZeroTier { namespace Metrics { // Packet Type Counts prometheus::simpleapi::counter_family_t packets - { "zt_packet", "incoming packet type counts"}; + { "zt_packet", "ZeroTier packet type counts"}; // Incoming packets prometheus::simpleapi::counter_metric_t pkt_nop_in @@ -118,7 +118,7 @@ namespace ZeroTier { // Packet Error Counts prometheus::simpleapi::counter_family_t packet_errors - { "zt_packet_error", "incoming packet errors"}; + { "zt_packet_error", "ZeroTier packet errors"}; // Incoming Error Counts prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found_in From adfbbc3fb00becc578afc0645c60b1de3d84bb4c Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 16 May 2023 11:56:58 -0700 Subject: [PATCH 17/41] Controller Metrics & Network Config Request Fix (#2003) * add new metrics for network config request queue size and sso expirations * move sso expiration to its own thread in the controller * fix potential undefined behavior when modifying a set --- controller/EmbeddedNetworkController.cpp | 75 ++++++++++++++---------- controller/EmbeddedNetworkController.hpp | 4 ++ node/Metrics.cpp | 9 +++ node/Metrics.hpp | 6 ++ osdep/BlockingQueue.hpp | 5 ++ 5 files changed, 68 insertions(+), 31 deletions(-) diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 1d5cee014..914cad476 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -468,6 +468,8 @@ EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPa _path(dbPath), _sender((NetworkController::Sender *)0), _db(this), + _ssoExpiryRunning(true), + _ssoExpiry(std::thread(&EmbeddedNetworkController::_ssoExpiryThread, this)), _rc(rc) { } @@ -476,8 +478,11 @@ EmbeddedNetworkController::~EmbeddedNetworkController() { std::lock_guard l(_threads_l); _queue.stop(); - for(auto t=_threads.begin();t!=_threads.end();++t) + for(auto t=_threads.begin();t!=_threads.end();++t) { t->join(); + } + _ssoExpiryRunning = false; + _ssoExpiry.join(); } void EmbeddedNetworkController::setSSORedirectURL(const std::string &url) { @@ -1543,7 +1548,7 @@ void EmbeddedNetworkController::_request( *(reinterpret_cast(&(r->target))) = t; if (v.ss_family == t.ss_family) *(reinterpret_cast(&(r->via))) = v; - ++nc->routeCount; + ++nc->routeCount; } } } @@ -1765,10 +1770,9 @@ void EmbeddedNetworkController::_startThreads() const long hwc = std::max((long)std::thread::hardware_concurrency(),(long)1); for(long t=0;t expired; - nlohmann::json network, member; for(;;) { _RQEntry *qe = (_RQEntry *)0; + Metrics::network_config_request_queue_size = _queue.size(); auto timedWaitResult = _queue.get(qe, 1000); if (timedWaitResult == BlockingQueue<_RQEntry *>::STOP) { break; @@ -1782,38 +1786,47 @@ void EmbeddedNetworkController::_startThreads() fprintf(stderr,"ERROR: exception in controller request handling thread: unknown exception" ZT_EOL_S); } delete qe; + qe = nullptr; } } - - expired.clear(); - int64_t now = OSUtils::now(); - { - std::lock_guard l(_expiringSoon_l); - for(auto s=_expiringSoon.begin();s!=_expiringSoon.end();) { - const int64_t when = s->first; - if (when <= now) { - // The user may have re-authorized, so we must actually look it up and check. - network.clear(); - member.clear(); - if (_db.get(s->second.networkId, network, s->second.nodeId, member)) { - int64_t authenticationExpiryTime = (int64_t)OSUtils::jsonInt(member["authenticationExpiryTime"], 0); - if (authenticationExpiryTime <= now) { - expired.push_back(s->second); - } - } - _expiringSoon.erase(s++); - } else { - // Don't bother going further into the future than necessary. - break; - } - } - } - for(auto e=expired.begin();e!=expired.end();++e) { - onNetworkMemberDeauthorize(nullptr, e->networkId, e->nodeId); - } } }); } } +void EmbeddedNetworkController::_ssoExpiryThread() { + while(_ssoExpiryRunning) { + std::vector<_MemberStatusKey> expired; + nlohmann::json network, member; + int64_t now = OSUtils::now(); + { + std::lock_guard l(_expiringSoon_l); + for(auto s=_expiringSoon.begin();s!=_expiringSoon.end();) { + Metrics::sso_expiration_checks++; + const int64_t when = s->first; + if (when <= now) { + // The user may have re-authorized, so we must actually look it up and check. + network.clear(); + member.clear(); + if (_db.get(s->second.networkId, network, s->second.nodeId, member)) { + int64_t authenticationExpiryTime = (int64_t)OSUtils::jsonInt(member["authenticationExpiryTime"], 0); + if (authenticationExpiryTime <= now) { + expired.push_back(s->second); + } + } + s = _expiringSoon.erase(s); + } else { + // Don't bother going further into the future than necessary. + break; + } + } + } + for(auto e=expired.begin();e!=expired.end();++e) { + Metrics::sso_member_deauth++; + onNetworkMemberDeauthorize(nullptr, e->networkId, e->nodeId); + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } +} + } // namespace ZeroTier diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index 4f2e20e0a..97692fa4c 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -81,6 +81,7 @@ public: private: void _request(uint64_t nwid,const InetAddress &fromAddr,uint64_t requestPacketId,const Identity &identity,const Dictionary &metaData); void _startThreads(); + void _ssoExpiryThread(); std::string networkUpdateFromPostData(uint64_t networkID, const std::string &body); @@ -138,6 +139,9 @@ private: std::vector _threads; std::mutex _threads_l; + bool _ssoExpiryRunning; + std::thread _ssoExpiry; + std::unordered_map< _MemberStatusKey,_MemberStatus,_MemberStatusHash > _memberStatus; std::mutex _memberStatus_l; diff --git a/node/Metrics.cpp b/node/Metrics.cpp index 623454761..633c1b853 100644 --- a/node/Metrics.cpp +++ b/node/Metrics.cpp @@ -206,6 +206,15 @@ namespace ZeroTier { prometheus::simpleapi::counter_metric_t member_deauths {"controller_member_deauth_count", "number of network member deauths"}; + prometheus::simpleapi::gauge_metric_t network_config_request_queue_size + { "controller_network_config_request_queue", "number of entries in the request queue for network configurations" }; + + prometheus::simpleapi::counter_metric_t sso_expiration_checks + { "controller_sso_expiration_checks", "number of sso expiration checks done" }; + + prometheus::simpleapi::counter_metric_t sso_member_deauth + { "controller_sso_timeouts", "number of sso timeouts" }; + #ifdef ZT_CONTROLLER_USE_LIBPQ // Central Controller Metrics prometheus::simpleapi::counter_metric_t pgsql_mem_notification diff --git a/node/Metrics.hpp b/node/Metrics.hpp index 66b97c0d6..492a6f9ea 100644 --- a/node/Metrics.hpp +++ b/node/Metrics.hpp @@ -123,6 +123,10 @@ namespace ZeroTier { extern prometheus::simpleapi::counter_metric_t member_auths; extern prometheus::simpleapi::counter_metric_t member_deauths; + extern prometheus::simpleapi::gauge_metric_t network_config_request_queue_size; + extern prometheus::simpleapi::counter_metric_t sso_expiration_checks; + extern prometheus::simpleapi::counter_metric_t sso_member_deauth; + #ifdef ZT_CONTROLLER_USE_LIBPQ // Central Controller Metrics extern prometheus::simpleapi::counter_metric_t pgsql_mem_notification; @@ -132,6 +136,8 @@ namespace ZeroTier { extern prometheus::simpleapi::counter_metric_t redis_net_notification; extern prometheus::simpleapi::counter_metric_t redis_node_checkin; + + // Central DB Pool Metrics extern prometheus::simpleapi::counter_metric_t conn_counter; extern prometheus::simpleapi::counter_metric_t max_pool_size; diff --git a/osdep/BlockingQueue.hpp b/osdep/BlockingQueue.hpp index cce37a04a..f3caff991 100644 --- a/osdep/BlockingQueue.hpp +++ b/osdep/BlockingQueue.hpp @@ -116,6 +116,11 @@ public: return OK; } + inline size_t size() const { + std::unique_lock lock(m); + return q.size(); + } + private: std::queue q; mutable std::mutex m; From 9b7ff43118b3b0a219321aa984138d04fe6d3bb5 Mon Sep 17 00:00:00 2001 From: travis laduke Date: Wed, 17 May 2023 13:17:32 -0700 Subject: [PATCH 18/41] Enable RTTI in Windows build The new prometheus histogram stuff needs it. Access violation - no RTTI data!INVALID packet 636ebd9ee8cac6c0 from cafe9efeb9(2605:9880:200:1200:30:571:e34:51/9993) (unexpected exception in tryDecode()) --- windows/ZeroTierOne/ZeroTierOne.vcxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index 3722b03ad..aca2d49af 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -417,7 +417,7 @@ $(SolutionDir)\..\ext;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\core\include;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\simpleapi\include;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories) ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions) 4996 - false + true stdcpp17 true stdc11 @@ -439,7 +439,7 @@ $(SolutionDir)\..\ext;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\core\include;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\simpleapi\include;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories) ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions) 4996 - false + true stdcpp17 stdc11 true @@ -461,11 +461,11 @@ ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_RULES_ENGINE_DEBUGGING;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions) true 4996 - false stdcpp17 stdc11 false false + true true @@ -507,11 +507,11 @@ ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions) true 4996 - false stdcpp17 stdc11 false false + true true @@ -558,7 +558,7 @@ true 4996 Guard - false + true stdcpp17 None false @@ -597,7 +597,6 @@ Guard false Cdecl - false stdcpp17 None false @@ -606,6 +605,7 @@ stdc11 false false + true false From e2dad367b4f751845b2c7c7be4a84e6370f4e312 Mon Sep 17 00:00:00 2001 From: travis laduke Date: Wed, 3 May 2023 14:22:40 -0700 Subject: [PATCH 19/41] Don't re-apply routes on BSD See issue #1986 --- osdep/ManagedRoute.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osdep/ManagedRoute.cpp b/osdep/ManagedRoute.cpp index a8f996839..36d1f07a2 100644 --- a/osdep/ManagedRoute.cpp +++ b/osdep/ManagedRoute.cpp @@ -509,13 +509,13 @@ bool ManagedRoute::sync() } } - //if (!_applied.count(leftt)) { + if (leftt && !_applied.count(leftt)) { _applied[leftt] = !_via; //_routeCmd("delete",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device); _routeCmd("add",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device); //_routeCmd("change",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device); - //} - if (rightt) { + } + if (rightt && !_applied.count(rightt)) { _applied[rightt] = !_via; //_routeCmd("delete",rightt,_via,(const char *)0,(_via) ? (const char *)0 : _device); _routeCmd("add",rightt,_via,(const char *)0,(_via) ? (const char *)0 : _device); From f3da2b403128b6c4ccc80c6ef2be1ac7b245b637 Mon Sep 17 00:00:00 2001 From: Brenton Bostick Date: Wed, 17 May 2023 20:55:32 -0400 Subject: [PATCH 20/41] Capture setContent by-value instead of by-reference (#2006) Co-authored-by: Grant Limberg --- controller/EmbeddedNetworkController.cpp | 18 ++++++------- service/OneService.cpp | 34 ++++++++++++------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 914cad476..cfc98aeab 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -819,7 +819,7 @@ void EmbeddedNetworkController::configureHTTPControlPlane( httplib::Server &s, const std::function setContent) { - s.Get("/controller/network", [&](const httplib::Request &req, httplib::Response &res) { + s.Get("/controller/network", [&, setContent](const httplib::Request &req, httplib::Response &res) { std::set networkIds; _db.networks(networkIds); char tmp[64]; @@ -833,7 +833,7 @@ void EmbeddedNetworkController::configureHTTPControlPlane( setContent(req, res, out.dump()); }); - s.Get("/controller/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) { + s.Get("/controller/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1]; uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); json network; @@ -845,7 +845,7 @@ void EmbeddedNetworkController::configureHTTPControlPlane( setContent(req, res, network.dump()); }); - auto createNewNetwork = [&](const httplib::Request &req, httplib::Response &res) { + auto createNewNetwork = [&, setContent](const httplib::Request &req, httplib::Response &res) { fprintf(stderr, "creating new network (new style)\n"); uint64_t nwid = 0; uint64_t nwidPrefix = (Utils::hexStrToU64(_signingIdAddressString.c_str()) << 24) & 0xffffffffff000000ULL; @@ -869,7 +869,7 @@ void EmbeddedNetworkController::configureHTTPControlPlane( s.Put("/controller/network", createNewNetwork); s.Post("/controller/network", createNewNetwork); - auto createNewNetworkOldAndBusted = [&](const httplib::Request &req, httplib::Response &res) { + auto createNewNetworkOldAndBusted = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto inID = req.matches[1].str(); if (inID != _signingIdAddressString) { @@ -898,7 +898,7 @@ void EmbeddedNetworkController::configureHTTPControlPlane( s.Put("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted); s.Post("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted); - s.Delete("/controller/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) { + s.Delete("/controller/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1].str(); uint64_t nwid = Utils::hexStrToU64(networkID.c_str()); @@ -912,7 +912,7 @@ void EmbeddedNetworkController::configureHTTPControlPlane( setContent(req, res, network.dump()); }); - s.Get("/controller/network/([0-9a-fA-F]{16})/member", [&](const httplib::Request &req, httplib::Response &res) { + s.Get("/controller/network/([0-9a-fA-F]{16})/member", [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1]; uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); json network; @@ -938,7 +938,7 @@ void EmbeddedNetworkController::configureHTTPControlPlane( setContent(req, res, out.dump()); }); - s.Get("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) { + s.Get("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1]; auto memberID = req.matches[2]; uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); @@ -953,7 +953,7 @@ void EmbeddedNetworkController::configureHTTPControlPlane( setContent(req, res, member.dump()); }); - auto memberPost = [&](const httplib::Request &req, httplib::Response &res) { + auto memberPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1].str(); auto memberID = req.matches[2].str(); uint64_t nwid = Utils::hexStrToU64(networkID.c_str()); @@ -1059,7 +1059,7 @@ void EmbeddedNetworkController::configureHTTPControlPlane( s.Put("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost); s.Post("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost); - s.Delete("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) { + s.Delete("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { auto networkID = req.matches[1].str(); auto memberID = req.matches[2].str(); diff --git a/service/OneService.cpp b/service/OneService.cpp index 73012380d..2945bfdae 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -1519,7 +1519,7 @@ public: - _controlPlane.Get("/bond/show/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) { + _controlPlane.Get("/bond/show/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { if (!_node->bondController()->inUse()) { setContent(req, res, ""); res.status = 400; @@ -1547,7 +1547,7 @@ public: _node->freeQueryResult((void *)pl); }); - auto bondRotate = [&](const httplib::Request &req, httplib::Response &res) { + auto bondRotate = [&, setContent](const httplib::Request &req, httplib::Response &res) { if (!_node->bondController()->inUse()) { setContent(req, res, ""); res.status = 400; @@ -1574,7 +1574,7 @@ public: _controlPlane.Post("/bond/rotate/([0-9a-fA-F]{10})", bondRotate); _controlPlane.Put("/bond/rotate/([0-9a-fA-F]{10})", bondRotate); - _controlPlane.Get("/config", [&](const httplib::Request &req, httplib::Response &res) { + _controlPlane.Get("/config", [&, setContent](const httplib::Request &req, httplib::Response &res) { std::string config; { Mutex::Lock lc(_localConfig_m); @@ -1586,7 +1586,7 @@ public: setContent(req, res, config); }); - auto configPost = [&](const httplib::Request &req, httplib::Response &res) { + auto configPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { json j(OSUtils::jsonParse(req.body)); if (j.is_object()) { Mutex::Lock lcl(_localConfig_m); @@ -1604,7 +1604,7 @@ public: _controlPlane.Post("/config/settings", configPost); _controlPlane.Put("/config/settings", configPost); - _controlPlane.Get("/health", [&](const httplib::Request &req, httplib::Response &res) { + _controlPlane.Get("/health", [&, setContent](const httplib::Request &req, httplib::Response &res) { json out = json::object(); char tmp[256]; @@ -1624,7 +1624,7 @@ public: setContent(req, res, out.dump()); }); - _controlPlane.Get("/moon", [&](const httplib::Request &req, httplib::Response &res) { + _controlPlane.Get("/moon", [&, setContent](const httplib::Request &req, httplib::Response &res) { std::vector moons(_node->moons()); auto out = json::array(); @@ -1636,7 +1636,7 @@ public: setContent(req, res, out.dump()); }); - _controlPlane.Get("/moon/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res){ + _controlPlane.Get("/moon/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res){ std::vector moons(_node->moons()); auto input = req.matches[1]; auto out = json::object(); @@ -1650,7 +1650,7 @@ public: setContent(req, res, out.dump()); }); - auto moonPost = [&](const httplib::Request &req, httplib::Response &res) { + auto moonPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto input = req.matches[1]; uint64_t seed = 0; try { @@ -1690,7 +1690,7 @@ public: _controlPlane.Post("/moon/([0-9a-fA-F]{10})", moonPost); _controlPlane.Put("/moon/([0-9a-fA-F]{10})", moonPost); - _controlPlane.Delete("/moon/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) { + _controlPlane.Delete("/moon/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { auto input = req.matches[1]; uint64_t id = Utils::hexStrToU64(input.str().c_str()); auto out = json::object(); @@ -1699,7 +1699,7 @@ public: setContent(req, res, out.dump()); }); - _controlPlane.Get("/network", [&](const httplib::Request &req, httplib::Response &res) { + _controlPlane.Get("/network", [&, setContent](const httplib::Request &req, httplib::Response &res) { Mutex::Lock _l(_nets_m); auto out = json::array(); @@ -1712,7 +1712,7 @@ public: setContent(req, res, out.dump()); }); - _controlPlane.Get("/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) { + _controlPlane.Get("/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { Mutex::Lock _l(_nets_m); auto input = req.matches[1]; @@ -1728,7 +1728,7 @@ public: res.status = 404; }); - auto networkPost = [&](const httplib::Request &req, httplib::Response &res) { + auto networkPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { auto input = req.matches[1]; uint64_t wantnw = Utils::hexStrToU64(input.str().c_str()); _node->join(wantnw, (void*)0, (void*)0); @@ -1770,7 +1770,7 @@ public: _controlPlane.Post("/network/([0-9a-fA-F]{16})", networkPost); _controlPlane.Put("/network/([0-9a-fA-F]){16}", networkPost); - _controlPlane.Delete("/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) { + _controlPlane.Delete("/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { auto input = req.matches[1]; auto out = json::object(); ZT_VirtualNetworkList *nws = _node->networks(); @@ -1785,7 +1785,7 @@ public: setContent(req, res, out.dump()); }); - _controlPlane.Get("/peer", [&](const httplib::Request &req, httplib::Response &res) { + _controlPlane.Get("/peer", [&, setContent](const httplib::Request &req, httplib::Response &res) { ZT_PeerList *pl = _node->peers(); auto out = nlohmann::json::array(); @@ -1803,7 +1803,7 @@ public: setContent(req, res, out.dump()); }); - _controlPlane.Get("/peer/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) { + _controlPlane.Get("/peer/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { ZT_PeerList *pl = _node->peers(); auto input = req.matches[1]; @@ -1823,7 +1823,7 @@ public: setContent(req, res, out.dump()); }); - _controlPlane.Get("/status", [&](const httplib::Request &req, httplib::Response &res) { + _controlPlane.Get("/status", [&, setContent](const httplib::Request &req, httplib::Response &res) { ZT_NodeStatus status; _node->status(&status); @@ -1969,7 +1969,7 @@ public: } }); - _controlPlane.set_exception_handler([&](const httplib::Request &req, httplib::Response &res, std::exception_ptr ep) { + _controlPlane.set_exception_handler([&, setContent](const httplib::Request &req, httplib::Response &res, std::exception_ptr ep) { char buf[1024]; auto fmt = "{\"error\": %d, \"description\": \"%s\"}"; try { From da71e2524c343f563ea9c37e9668eec7c5719aaa Mon Sep 17 00:00:00 2001 From: Brenton Bostick Date: Fri, 19 May 2023 13:21:24 -0400 Subject: [PATCH 21/41] fix typos (#2010) --- controller/EmbeddedNetworkController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index cfc98aeab..01a33050e 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -925,7 +925,7 @@ void EmbeddedNetworkController::configureHTTPControlPlane( std::vector memTmp; if (_db.get(nwid, network, memTmp)) { for (auto m = memTmp.begin(); m != memTmp.end(); ++m) { - int revision = OSUtils::jsonInt((*m)["revsision"], 0); + int revision = OSUtils::jsonInt((*m)["revision"], 0); std::string id = OSUtils::jsonString((*m)["id"], ""); if (id.length() == 10) { json tmp = json::object(); From 17f6b3a10b1f1b3bc18c950f762d87fd64a8ce09 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 23 May 2023 12:11:26 -0700 Subject: [PATCH 22/41] central controller metrics & request path updates (#2012) * internal db metrics * use shared mutexes for read/write locks * remove this lock. only used for a metric * more metrics * remove exploratory metrics place controller request benchmarks behind ifdef --- controller/DB.cpp | 49 +++--- controller/DB.hpp | 14 +- controller/DBMirrorSet.cpp | 32 ++-- controller/DBMirrorSet.hpp | 6 +- controller/EmbeddedNetworkController.cpp | 193 ++++++++++++++++++++--- controller/EmbeddedNetworkController.hpp | 25 +++ controller/PostgreSQL.cpp | 29 ++-- node/Metrics.cpp | 24 +++ node/Metrics.hpp | 14 ++ osdep/BlockingQueue.hpp | 1 - 10 files changed, 307 insertions(+), 80 deletions(-) diff --git a/controller/DB.cpp b/controller/DB.cpp index 1de2fbe8b..b1c820144 100644 --- a/controller/DB.cpp +++ b/controller/DB.cpp @@ -108,16 +108,17 @@ DB::~DB() {} bool DB::get(const uint64_t networkId,nlohmann::json &network) { waitForReady(); + Metrics::db_get_network++; std::shared_ptr<_Network> nw; { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); auto nwi = _networks.find(networkId); if (nwi == _networks.end()) return false; nw = nwi->second; } { - std::lock_guard l2(nw->lock); + std::shared_lock l2(nw->lock); network = nw->config; } return true; @@ -126,16 +127,17 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network) bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member) { waitForReady(); + Metrics::db_get_network_and_member++; std::shared_ptr<_Network> nw; { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); auto nwi = _networks.find(networkId); if (nwi == _networks.end()) return false; nw = nwi->second; } { - std::lock_guard l2(nw->lock); + std::shared_lock l2(nw->lock); network = nw->config; auto m = nw->members.find(memberId); if (m == nw->members.end()) @@ -148,16 +150,17 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t mem bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,NetworkSummaryInfo &info) { waitForReady(); + Metrics::db_get_network_and_member_and_summary++; std::shared_ptr<_Network> nw; { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); auto nwi = _networks.find(networkId); if (nwi == _networks.end()) return false; nw = nwi->second; } { - std::lock_guard l2(nw->lock); + std::shared_lock l2(nw->lock); network = nw->config; _fillSummaryInfo(nw,info); auto m = nw->members.find(memberId); @@ -171,16 +174,17 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t mem bool DB::get(const uint64_t networkId,nlohmann::json &network,std::vector &members) { waitForReady(); + Metrics::db_get_member_list++; std::shared_ptr<_Network> nw; { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); auto nwi = _networks.find(networkId); if (nwi == _networks.end()) return false; nw = nwi->second; } { - std::lock_guard l2(nw->lock); + std::shared_lock l2(nw->lock); network = nw->config; for(auto m=nw->members.begin();m!=nw->members.end();++m) { members.push_back(m->second); @@ -192,13 +196,15 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,std::vector &networks) { waitForReady(); - std::lock_guard l(_networks_l); + Metrics::db_get_member_list++; + std::shared_lock l(_networks_l); for(auto n=_networks.begin();n!=_networks.end();++n) networks.insert(n->first); } void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners) { + Metrics::db_member_change++; uint64_t memberId = 0; uint64_t networkId = 0; bool isAuth = false; @@ -210,14 +216,14 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no networkId = OSUtils::jsonIntHex(old["nwid"],0ULL); if ((memberId)&&(networkId)) { { - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); auto nw2 = _networks.find(networkId); if (nw2 != _networks.end()) { nw = nw2->second; } } if (nw) { - std::lock_guard l(nw->lock); + std::unique_lock l(nw->lock); if (OSUtils::jsonBool(old["activeBridge"],false)) { nw->activeBridgeMembers.erase(memberId); } @@ -247,7 +253,7 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no networkId = OSUtils::jsonIntHex(memberConfig["nwid"],0ULL); if ((!memberId)||(!networkId)) return; - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); std::shared_ptr<_Network> &nw2 = _networks[networkId]; if (!nw2) nw2.reset(new _Network); @@ -255,7 +261,7 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no } { - std::lock_guard l(nw->lock); + std::unique_lock l(nw->lock); nw->members[memberId] = memberConfig; @@ -288,18 +294,18 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no } if (notifyListeners) { - std::lock_guard ll(_changeListeners_l); + std::unique_lock ll(_changeListeners_l); for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) { (*i)->onNetworkMemberUpdate(this,networkId,memberId,memberConfig); } } } else if (memberId) { if (nw) { - std::lock_guard l(nw->lock); + std::unique_lock l(nw->lock); nw->members.erase(memberId); } if (networkId) { - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); auto er = _networkByMember.equal_range(memberId); for(auto i=er.first;i!=er.second;++i) { if (i->second == networkId) { @@ -329,7 +335,7 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no } if ((notifyListeners)&&((wasAuth)&&(!isAuth)&&(networkId)&&(memberId))) { - std::lock_guard ll(_changeListeners_l); + std::unique_lock ll(_changeListeners_l); for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) { (*i)->onNetworkMemberDeauthorize(this,networkId,memberId); } @@ -338,6 +344,7 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool notifyListeners) { + Metrics::db_network_change++; if (notifyListeners) { if (old.is_object() && old.contains("id") && networkConfig.is_object() && networkConfig.contains("id")) { Metrics::network_changes++; @@ -354,18 +361,18 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool if (networkId) { std::shared_ptr<_Network> nw; { - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); std::shared_ptr<_Network> &nw2 = _networks[networkId]; if (!nw2) nw2.reset(new _Network); nw = nw2; } { - std::lock_guard l2(nw->lock); + std::unique_lock l2(nw->lock); nw->config = networkConfig; } if (notifyListeners) { - std::lock_guard ll(_changeListeners_l); + std::unique_lock ll(_changeListeners_l); for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) { (*i)->onNetworkUpdate(this,networkId,networkConfig); } @@ -375,7 +382,7 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool const std::string ids = old["id"]; const uint64_t networkId = Utils::hexStrToU64(ids.c_str()); if (networkId) { - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); _networks.erase(networkId); } } diff --git a/controller/DB.hpp b/controller/DB.hpp index f70d66e03..89610f7c5 100644 --- a/controller/DB.hpp +++ b/controller/DB.hpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include @@ -109,7 +109,7 @@ public: inline bool hasNetwork(const uint64_t networkId) const { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); return (_networks.find(networkId) != _networks.end()); } @@ -124,7 +124,7 @@ public: inline void each(F f) { nlohmann::json nullJson; - std::lock_guard lck(_networks_l); + std::unique_lock lck(_networks_l); for(auto nw=_networks.begin();nw!=_networks.end();++nw) { f(nw->first,nw->second->config,0,nullJson); // first provide network with 0 for member ID for(auto m=nw->second->members.begin();m!=nw->second->members.end();++m) { @@ -142,7 +142,7 @@ public: inline void addListener(DB::ChangeListener *const listener) { - std::lock_guard l(_changeListeners_l); + std::unique_lock l(_changeListeners_l); _changeListeners.push_back(listener); } @@ -178,7 +178,7 @@ protected: std::unordered_set authorizedMembers; std::unordered_set allocatedIps; int64_t mostRecentDeauthTime; - std::mutex lock; + std::shared_mutex lock; }; virtual void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners); @@ -188,8 +188,8 @@ protected: std::vector _changeListeners; std::unordered_map< uint64_t,std::shared_ptr<_Network> > _networks; std::unordered_multimap< uint64_t,uint64_t > _networkByMember; - mutable std::mutex _changeListeners_l; - mutable std::mutex _networks_l; + mutable std::shared_mutex _changeListeners_l; + mutable std::shared_mutex _networks_l; }; } // namespace ZeroTier diff --git a/controller/DBMirrorSet.cpp b/controller/DBMirrorSet.cpp index 5d64ebf0c..9372eef65 100644 --- a/controller/DBMirrorSet.cpp +++ b/controller/DBMirrorSet.cpp @@ -32,7 +32,7 @@ DBMirrorSet::DBMirrorSet(DB::ChangeListener *listener) std::vector< std::shared_ptr > dbs; { - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); if (_dbs.size() <= 1) continue; // no need to do this if there's only one DB, so skip the iteration dbs = _dbs; @@ -79,7 +79,7 @@ DBMirrorSet::~DBMirrorSet() bool DBMirrorSet::hasNetwork(const uint64_t networkId) const { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if ((*d)->hasNetwork(networkId)) return true; @@ -89,7 +89,7 @@ bool DBMirrorSet::hasNetwork(const uint64_t networkId) const bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if ((*d)->get(networkId,network)) { return true; @@ -100,7 +100,7 @@ bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network) bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if ((*d)->get(networkId,network,memberId,member)) return true; @@ -110,7 +110,7 @@ bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uin bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,DB::NetworkSummaryInfo &info) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if ((*d)->get(networkId,network,memberId,member,info)) return true; @@ -120,7 +120,7 @@ bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uin bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,std::vector &members) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if ((*d)->get(networkId,network,members)) return true; @@ -130,7 +130,7 @@ bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,std::vect AuthInfo DBMirrorSet::getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { AuthInfo info = (*d)->getSSOAuthInfo(member, redirectURL); if (info.enabled) { @@ -142,7 +142,7 @@ AuthInfo DBMirrorSet::getSSOAuthInfo(const nlohmann::json &member, const std::st void DBMirrorSet::networks(std::set &networks) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { (*d)->networks(networks); } @@ -151,7 +151,7 @@ void DBMirrorSet::networks(std::set &networks) bool DBMirrorSet::waitForReady() { bool r = false; - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { r |= (*d)->waitForReady(); } @@ -160,7 +160,7 @@ bool DBMirrorSet::waitForReady() bool DBMirrorSet::isReady() { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if (!(*d)->isReady()) return false; @@ -172,7 +172,7 @@ bool DBMirrorSet::save(nlohmann::json &record,bool notifyListeners) { std::vector< std::shared_ptr > dbs; { - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); dbs = _dbs; } if (notifyListeners) { @@ -192,7 +192,7 @@ bool DBMirrorSet::save(nlohmann::json &record,bool notifyListeners) void DBMirrorSet::eraseNetwork(const uint64_t networkId) { - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { (*d)->eraseNetwork(networkId); } @@ -200,7 +200,7 @@ void DBMirrorSet::eraseNetwork(const uint64_t networkId) void DBMirrorSet::eraseMember(const uint64_t networkId,const uint64_t memberId) { - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { (*d)->eraseMember(networkId,memberId); } @@ -208,7 +208,7 @@ void DBMirrorSet::eraseMember(const uint64_t networkId,const uint64_t memberId) void DBMirrorSet::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { (*d)->nodeIsOnline(networkId,memberId,physicalAddress); } @@ -217,7 +217,7 @@ void DBMirrorSet::nodeIsOnline(const uint64_t networkId,const uint64_t memberId, void DBMirrorSet::onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network) { nlohmann::json record(network); - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if (d->get() != db) { (*d)->save(record,false); @@ -229,7 +229,7 @@ void DBMirrorSet::onNetworkUpdate(const void *db,uint64_t networkId,const nlohma void DBMirrorSet::onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member) { nlohmann::json record(member); - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if (d->get() != db) { (*d)->save(record,false); diff --git a/controller/DBMirrorSet.hpp b/controller/DBMirrorSet.hpp index 883c98fd7..e33b63ec9 100644 --- a/controller/DBMirrorSet.hpp +++ b/controller/DBMirrorSet.hpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include @@ -56,7 +56,7 @@ public: inline void addDB(const std::shared_ptr &db) { db->addListener(this); - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); _dbs.push_back(db); } @@ -65,7 +65,7 @@ private: std::atomic_bool _running; std::thread _syncCheckerThread; std::vector< std::shared_ptr< DB > > _dbs; - mutable std::mutex _dbs_l; + mutable std::shared_mutex _dbs_l; }; } // namespace ZeroTier diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 01a33050e..3f8316932 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -460,17 +460,41 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) } // anonymous namespace -EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, RedisConfig *rc) : - _startTime(OSUtils::now()), - _listenPort(listenPort), - _node(node), - _ztPath(ztPath), - _path(dbPath), - _sender((NetworkController::Sender *)0), - _db(this), - _ssoExpiryRunning(true), - _ssoExpiry(std::thread(&EmbeddedNetworkController::_ssoExpiryThread, this)), - _rc(rc) +EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, RedisConfig *rc) + : _startTime(OSUtils::now()) + , _listenPort(listenPort) + , _node(node) + , _ztPath(ztPath) + , _path(dbPath) + , _sender((NetworkController::Sender *)0) + , _db(this) + , _ssoExpiryRunning(true) + , _ssoExpiry(std::thread(&EmbeddedNetworkController::_ssoExpiryThread, this)) + , _rc(rc) +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + , _member_status_lookup{"nc_member_status_lookup",""} + , _member_status_lookup_count{"nc_member_status_lookup_count",""} + , _node_is_online{"nc_node_is_online",""} + , _node_is_online_count{"nc_node_is_online_count",""} + , _get_and_init_member{"nc_get_and_init_member",""} + , _get_and_init_member_count{"nc_get_and_init_member_count",""} + , _have_identity{"nc_have_identity",""} + , _have_identity_count{"nc_have_identity_count",""} + , _determine_auth{"nc_determine_auth",""} + , _determine_auth_count{"nc_determine_auth_count",""} + , _sso_check{"nc_sso_check",""} + , _sso_check_count{"nc_sso_check_count",""} + , _auth_check{"nc_auth_check",""} + , _auth_check_count{"nc_auth_check_count",""} + , _json_schlep{"nc_json_schlep",""} + , _json_schlep_count{"nc_json_schlep_count",""} + , _issue_certificate{"nc_issue_certificate", ""} + , _issue_certificate_count{"nc_issue_certificate_count",""} + , _save_member{"nc_save_member",""} + , _save_member_count{"nc_save_member_count",""} + , _send_netconf{"nc_send_netconf2",""} + , _send_netconf_count{"nc_send_netconf2_count",""} +#endif { } @@ -549,6 +573,18 @@ void EmbeddedNetworkController::request( if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) return; _startThreads(); + + const int64_t now = OSUtils::now(); + + if (requestPacketId) { + std::lock_guard l(_memberStatus_l); + _MemberStatus &ms = _memberStatus[_MemberStatusKey(nwid,identity.address().toInt())]; + if ((now - ms.lastRequestTime) <= ZT_NETCONF_MIN_REQUEST_PERIOD) { + return; + } + ms.lastRequestTime = now; + } + _RQEntry *qe = new _RQEntry; qe->nwid = nwid; qe->requestPacketId = requestPacketId; @@ -1176,36 +1212,66 @@ void EmbeddedNetworkController::_request( const Identity &identity, const Dictionary &metaData) { + Metrics::network_config_request++; + auto tid = std::this_thread::get_id(); + std::stringstream ss; ss << tid; + std::string threadID = ss.str(); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b1 = _member_status_lookup.Add({{"thread", threadID}}); + auto c1 = _member_status_lookup_count.Add({{"thread", threadID}}); + c1++; + b1.start(); +#endif + char nwids[24]; DB::NetworkSummaryInfo ns; json network,member; - if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) + if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) { return; + } const int64_t now = OSUtils::now(); - if (requestPacketId) { - std::lock_guard l(_memberStatus_l); - _MemberStatus &ms = _memberStatus[_MemberStatusKey(nwid,identity.address().toInt())]; - if ((now - ms.lastRequestTime) <= ZT_NETCONF_MIN_REQUEST_PERIOD) - return; - ms.lastRequestTime = now; - } - +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b1.stop(); + auto b2 = _node_is_online.Add({{"thread",threadID}}); + auto c2 = _node_is_online_count.Add({{"thread",threadID}}); + c2++; + b2.start(); +#endif _db.nodeIsOnline(nwid,identity.address().toInt(),fromAddr); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b2.stop(); + auto b3 = _get_and_init_member.Add({{"thread", threadID}}); + auto c3 = _get_and_init_member_count.Add({{"thread",threadID}}); + c3++; + b3.start(); +#endif Utils::hex(nwid,nwids); _db.get(nwid,network,identity.address().toInt(),member,ns); if ((!network.is_object())||(network.empty())) { _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_OBJECT_NOT_FOUND, nullptr, 0); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b3.stop(); +#endif return; } const bool newMember = ((!member.is_object())||(member.empty())); DB::initMember(member); _MemberStatusKey msk(nwid,identity.address().toInt()); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b3.stop(); +#endif { +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b4 = _have_identity.Add({{"thread",threadID}}); + auto c4 = _have_identity_count.Add({{"thread",threadID}}); + c4++; + b4.start(); +#endif const std::string haveIdStr(OSUtils::jsonString(member["identity"],"")); if (haveIdStr.length() > 0) { // If we already know this member's identity perform a full compare. This prevents @@ -1214,10 +1280,16 @@ void EmbeddedNetworkController::_request( try { if (Identity(haveIdStr.c_str()) != identity) { _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED, nullptr, 0); + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b4.stop(); + #endif return; } } catch ( ... ) { _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED, nullptr, 0); + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b4.stop(); + #endif return; } } else { @@ -1225,6 +1297,9 @@ void EmbeddedNetworkController::_request( char idtmp[1024]; member["identity"] = identity.toString(false,idtmp); } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b4.stop(); +#endif } // These are always the same, but make sure they are set @@ -1237,6 +1312,12 @@ void EmbeddedNetworkController::_request( } // Determine whether and how member is authorized +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b5 = _determine_auth.Add({{"thread",threadID}}); + auto c5 = _determine_auth_count.Add({{"thread",threadID}}); + c5++; + b5.start(); +#endif bool authorized = false; bool autoAuthorized = false; json autoAuthCredentialType,autoAuthCredential; @@ -1273,10 +1354,19 @@ void EmbeddedNetworkController::_request( member["lastAuthorizedCredentialType"] = autoAuthCredentialType; member["lastAuthorizedCredential"] = autoAuthCredential; } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b5.stop(); +#endif // Should we check SSO Stuff? // If network is configured with SSO, and the member is not marked exempt: yes // Otherwise no, we use standard auth logic. +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b6 = _sso_check.Add({{"thread",threadID}}); + auto c6 = _sso_check_count.Add({{"thread",threadID}}); + c6++; + b6.start(); +#endif AuthInfo info; int64_t authenticationExpiryTime = -1; bool networkSSOEnabled = OSUtils::jsonBool(network["ssoEnabled"], false); @@ -1303,10 +1393,20 @@ void EmbeddedNetworkController::_request( } DB::cleanMember(member); _db.save(member,true); + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b6.stop(); + #endif return; } } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b6.stop(); + auto b7 = _auth_check.Add({{"thread",threadID}}); + auto c7 = _auth_check_count.Add({{"thread",threadID}}); + c7++; + b7.start(); +#endif if (authorized) { // Update version info and meta-data if authorized and if this is a genuine request if (requestPacketId) { @@ -1342,8 +1442,14 @@ void EmbeddedNetworkController::_request( DB::cleanMember(member); _db.save(member,true); _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED, nullptr, 0); + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b7.stop(); + #endif return; } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b7.stop(); +#endif // ------------------------------------------------------------------------- // If we made it this far, they are authorized (and authenticated). @@ -1351,6 +1457,12 @@ void EmbeddedNetworkController::_request( // Default timeout: 15 minutes. Maximum: two hours. Can be specified by an optional field in the network config // if something longer than 15 minutes is desired. Minimum is 5 minutes since shorter than that would be flaky. +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b8 = _json_schlep.Add({{"thread",threadID}}); + auto c8 = _json_schlep_count.Add({{"thread", threadID}}); + c8++; + b8.start(); +#endif int64_t credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_DFL_MAX_DELTA; if (network.contains("certificateTimeoutWindowSize")) { credentialtmd = (int64_t)network["certificateTimeoutWindowSize"]; @@ -1418,8 +1530,9 @@ void EmbeddedNetworkController::_request( nc->remoteTraceLevel = (Trace::Level)OSUtils::jsonInt(network["remoteTraceLevel"],0ULL); } - for(std::vector
::const_iterator ab(ns.activeBridges.begin());ab!=ns.activeBridges.end();++ab) + for(std::vector
::const_iterator ab(ns.activeBridges.begin());ab!=ns.activeBridges.end();++ab) { nc->addSpecialist(*ab,ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); + } json &v4AssignMode = network["v4AssignMode"]; json &v6AssignMode = network["v6AssignMode"]; @@ -1739,12 +1852,22 @@ void EmbeddedNetworkController::_request( } else { dns = json::object(); } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b8.stop(); +#endif // Issue a certificate of ownership for all static IPs +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b9 = _issue_certificate.Add({{"thread",threadID}}); + auto c9 = _issue_certificate_count.Add({{"thread",threadID}}); + c9++; + b9.start(); +#endif if (nc->staticIpCount) { nc->certificatesOfOwnership[0] = CertificateOfOwnership(nwid,now,identity.address(),1); - for(unsigned int i=0;istaticIpCount;++i) + for(unsigned int i=0;istaticIpCount;++i) { nc->certificatesOfOwnership[0].addThing(nc->staticIps[i]); + } nc->certificatesOfOwnership[0].sign(_signingId); nc->certificateOfOwnershipCount = 1; } @@ -1754,22 +1877,45 @@ void EmbeddedNetworkController::_request( nc->com = com; } else { _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR, nullptr, 0); + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b9.stop(); + #endif return; } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b9.stop(); + auto b10 = _save_member.Add({{"thread",threadID}}); + auto c10 = _save_member_count.Add({{"thread",threadID}}); + c10++; + b10.start(); +#endif DB::cleanMember(member); _db.save(member,true); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b10.stop(); + + auto b11 = _send_netconf.Add({{"thread",threadID}}); + auto c11 = _send_netconf_count.Add({{"thread",threadID}}); + c11++; + b11.start(); +#endif _sender->ncSendConfig(nwid,requestPacketId,identity.address(),*(nc.get()),metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b11.stop(); +#endif } void EmbeddedNetworkController::_startThreads() { std::lock_guard l(_threads_l); - if (!_threads.empty()) + if (!_threads.empty()) { return; + } const long hwc = std::max((long)std::thread::hardware_concurrency(),(long)1); for(long t=0;t c; try { - auto c = _pool->borrow(); + c = _pool->borrow(); pqxx::work w(*c->c); char nonceBytes[16] = {0}; @@ -462,11 +470,11 @@ AuthInfo PostgreSQL::getSSOAuthInfo(const nlohmann::json &member, const std::str uint64_t sso_version = 0; if (r.size() == 1) { - client_id = r.at(0)[0].as(); - authorization_endpoint = r.at(0)[1].as(); - issuer = r.at(0)[2].as(); - provider = r.at(0)[3].as(); - sso_version = r.at(0)[4].as(); + client_id = r.at(0)[0].as>().value_or(""); + authorization_endpoint = r.at(0)[1].as>().value_or(""); + issuer = r.at(0)[2].as>().value_or(""); + provider = r.at(0)[3].as>().value_or(""); + sso_version = r.at(0)[4].as>().value_or(1); } else if (r.size() > 1) { fprintf(stderr, "ERROR: More than one auth endpoint for an organization?!?!? NetworkID: %s\n", networkId.c_str()); } else { @@ -519,6 +527,9 @@ AuthInfo PostgreSQL::getSSOAuthInfo(const nlohmann::json &member, const std::str _pool->unborrow(c); } catch (std::exception &e) { + if (c) { + _pool->unborrow(c); + } fprintf(stderr, "ERROR: Error updating member on load for network %s: %s\n", networkId.c_str(), e.what()); } @@ -776,7 +787,7 @@ void PostgreSQL::initializeMembers() if (_redisMemberStatus) { fprintf(stderr, "Initialize Redis for members...\n"); - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); std::unordered_set deletes; for ( auto it : _networks) { uint64_t nwid_i = it.first; @@ -1270,6 +1281,7 @@ void PostgreSQL::commitThread() continue; } + Metrics::pgsql_commit_ticks++; try { nlohmann::json &config = (qitem.first); const std::string objtype = config["objtype"]; @@ -1596,7 +1608,6 @@ void PostgreSQL::commitThread() } _pool->unborrow(c); c.reset(); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); } fprintf(stderr, "%s commitThread finished\n", _myAddressStr.c_str()); @@ -1812,7 +1823,7 @@ uint64_t PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &con sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); for (const auto &it : _networks) { uint64_t nwid_i = it.first; char nwidTmp[64]; diff --git a/node/Metrics.cpp b/node/Metrics.cpp index 633c1b853..0b4d3495b 100644 --- a/node/Metrics.cpp +++ b/node/Metrics.cpp @@ -215,6 +215,25 @@ namespace ZeroTier { prometheus::simpleapi::counter_metric_t sso_member_deauth { "controller_sso_timeouts", "number of sso timeouts" }; + prometheus::simpleapi::counter_metric_t network_config_request + { "controller_network_config_request", "count of config requests handled" }; + prometheus::simpleapi::gauge_metric_t network_config_request_threads + { "controller_network_config_request_threads", "number of active network config handling threads" }; + prometheus::simpleapi::counter_metric_t db_get_network + { "controller_db_get_network", "counter" }; + prometheus::simpleapi::counter_metric_t db_get_network_and_member + { "controller_db_get_network_and_member", "counter" }; + prometheus::simpleapi::counter_metric_t db_get_network_and_member_and_summary + { "controller_db_get_networK_and_member_summary", "counter" }; + prometheus::simpleapi::counter_metric_t db_get_member_list + { "controller_db_get_member_list", "counter" }; + prometheus::simpleapi::counter_metric_t db_get_network_list + { "controller_db_get_network_list", "counter" }; + prometheus::simpleapi::counter_metric_t db_member_change + { "controller_db_member_change", "counter" }; + prometheus::simpleapi::counter_metric_t db_network_change + { "controller_db_network_change", "counter" }; + #ifdef ZT_CONTROLLER_USE_LIBPQ // Central Controller Metrics prometheus::simpleapi::counter_metric_t pgsql_mem_notification @@ -223,6 +242,11 @@ namespace ZeroTier { { "controller_pgsql_network_notifications_received", "number of network change notifications received via pgsql NOTIFY" }; prometheus::simpleapi::counter_metric_t pgsql_node_checkin { "controller_pgsql_node_checkin_count", "number of node check-ins (pgsql)" }; + prometheus::simpleapi::counter_metric_t pgsql_commit_ticks + { "controller_pgsql_commit_ticks", "number of commit ticks run (pgsql)" }; + prometheus::simpleapi::counter_metric_t db_get_sso_info + { "controller_db_get_sso_info", "counter" }; + prometheus::simpleapi::counter_metric_t redis_mem_notification { "controller_redis_member_notifications_received", "number of member change notifications received via redis" }; prometheus::simpleapi::counter_metric_t redis_net_notification diff --git a/node/Metrics.hpp b/node/Metrics.hpp index 492a6f9ea..055288f68 100644 --- a/node/Metrics.hpp +++ b/node/Metrics.hpp @@ -126,12 +126,26 @@ namespace ZeroTier { extern prometheus::simpleapi::gauge_metric_t network_config_request_queue_size; extern prometheus::simpleapi::counter_metric_t sso_expiration_checks; extern prometheus::simpleapi::counter_metric_t sso_member_deauth; + extern prometheus::simpleapi::counter_metric_t network_config_request; + extern prometheus::simpleapi::gauge_metric_t network_config_request_threads; + + extern prometheus::simpleapi::counter_metric_t db_get_network; + extern prometheus::simpleapi::counter_metric_t db_get_network_and_member; + extern prometheus::simpleapi::counter_metric_t db_get_network_and_member_and_summary; + extern prometheus::simpleapi::counter_metric_t db_get_member_list; + extern prometheus::simpleapi::counter_metric_t db_get_network_list; + extern prometheus::simpleapi::counter_metric_t db_member_change; + extern prometheus::simpleapi::counter_metric_t db_network_change; + #ifdef ZT_CONTROLLER_USE_LIBPQ // Central Controller Metrics extern prometheus::simpleapi::counter_metric_t pgsql_mem_notification; extern prometheus::simpleapi::counter_metric_t pgsql_net_notification; extern prometheus::simpleapi::counter_metric_t pgsql_node_checkin; + extern prometheus::simpleapi::counter_metric_t pgsql_commit_ticks; + extern prometheus::simpleapi::counter_metric_t db_get_sso_info; + extern prometheus::simpleapi::counter_metric_t redis_mem_notification; extern prometheus::simpleapi::counter_metric_t redis_net_notification; extern prometheus::simpleapi::counter_metric_t redis_node_checkin; diff --git a/osdep/BlockingQueue.hpp b/osdep/BlockingQueue.hpp index f3caff991..c3695e708 100644 --- a/osdep/BlockingQueue.hpp +++ b/osdep/BlockingQueue.hpp @@ -117,7 +117,6 @@ public: } inline size_t size() const { - std::unique_lock lock(m); return q.size(); } From 524363dcf74f7b919d01fa404f7bd5592f2d645c Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Thu, 25 May 2023 10:54:26 -0700 Subject: [PATCH 23/41] Improve validation test (#2013) --- .github/workflows/validate-1m-linux.sh | 661 ++++++++++++------------- 1 file changed, 329 insertions(+), 332 deletions(-) diff --git a/.github/workflows/validate-1m-linux.sh b/.github/workflows/validate-1m-linux.sh index 7bd58dd79..3d852a8d7 100755 --- a/.github/workflows/validate-1m-linux.sh +++ b/.github/workflows/validate-1m-linux.sh @@ -3,7 +3,7 @@ # This test script joins Earth and pokes some stuff TEST_NETWORK=8056c2e21c000001 -RUN_LENGTH=10 +RUN_LENGTH=60 TEST_FINISHED=false ZTO_VER=$(git describe --tags $(git rev-list --tags --max-count=1)) ZTO_COMMIT=$(git rev-parse HEAD) @@ -16,394 +16,358 @@ mkdir $TEST_DIR_PREFIX ################################################################################ # Multi-node connectivity and performance test # ################################################################################ +main() { + echo -e "\nRunning test for $RUN_LENGTH seconds" + NS1="ip netns exec ns1" + NS2="ip netns exec ns2" -NS1="ip netns exec ns1" -NS2="ip netns exec ns2" + ZT1="$NS1 ./zerotier-cli -p9996 -D$(pwd)/node1" + # Specify custom port on one node to ensure that feature works + ZT2="$NS2 ./zerotier-cli -p9997 -D$(pwd)/node2" -ZT1="$NS1 ./zerotier-cli -D$(pwd)/node1" -# Specify custom port on one node to ensure that feature works -ZT2="$NS2 ./zerotier-cli -p9997 -D$(pwd)/node2" + echo -e "\nSetting up network namespaces..." + echo "Setting up ns1" -echo -e "Setting up network namespaces..." -echo "Setting up ns1" + ip netns add ns1 + $NS1 ip link set dev lo up + ip link add veth0 type veth peer name veth1 + ip link set veth1 netns ns1 + ip addr add 192.168.0.1/24 dev veth0 + ip link set dev veth0 up + + $NS1 ip addr add 192.168.0.2/24 dev veth1 + $NS1 ip link set dev veth1 up + + # Add default route + $NS1 ip route add default via 192.168.0.1 + + iptables -t nat -A POSTROUTING -s 192.168.0.0/255.255.255.0 \ + -o eth0 -j MASQUERADE + iptables -A FORWARD -i eth0 -o veth0 -j ACCEPT + iptables -A FORWARD -o eth0 -i veth0 -j ACCEPT + + echo "Setting up ns2" + ip netns add ns2 + $NS2 ip link set dev lo up + ip link add veth2 type veth peer name veth3 + ip link set veth3 netns ns2 + ip addr add 192.168.1.1/24 dev veth2 + ip link set dev veth2 up + + $NS2 ip addr add 192.168.1.2/24 dev veth3 + $NS2 ip link set dev veth3 up + $NS2 ip route add default via 192.168.1.1 + + iptables -t nat -A POSTROUTING -s 192.168.1.0/255.255.255.0 \ + -o eth0 -j MASQUERADE + iptables -A FORWARD -i eth0 -o veth2 -j ACCEPT + iptables -A FORWARD -o eth0 -i veth2 -j ACCEPT + + # Allow forwarding + sysctl -w net.ipv4.ip_forward=1 + + echo -e "\nPing from host to namespaces" + + ping -c 3 192.168.0.1 + ping -c 3 192.168.1.1 -ip netns add ns1 -$NS1 ip link set dev lo up -ip link add veth0 type veth peer name veth1 -ip link set veth1 netns ns1 -ip addr add 192.168.0.1/24 dev veth0 -ip link set dev veth0 up + echo -e "\nPing from namespace to host" + + $NS1 ping -c 3 192.168.0.1 + $NS1 ping -c 3 192.168.0.1 + $NS2 ping -c 3 192.168.0.2 + $NS2 ping -c 3 192.168.0.2 -$NS1 ip addr add 192.168.0.2/24 dev veth1 -$NS1 ip link set dev veth1 up + echo -e "\nPing from ns1 to ns2" -# Add default route -$NS1 ip route add default via 192.168.0.1 + $NS1 ping -c 3 192.168.0.1 -iptables -t nat -A POSTROUTING -s 192.168.0.0/255.255.255.0 \ - -o eth0 -j MASQUERADE -iptables -A FORWARD -i eth0 -o veth0 -j ACCEPT -iptables -A FORWARD -o eth0 -i veth0 -j ACCEPT + echo -e "\nPing from ns2 to ns1" -echo "Setting up ns2" -ip netns add ns2 -$NS2 ip link set dev lo up -ip link add veth2 type veth peer name veth3 -ip link set veth3 netns ns2 -ip addr add 192.168.1.1/24 dev veth2 -ip link set dev veth2 up + $NS2 ping -c 3 192.168.0.1 -$NS2 ip addr add 192.168.1.2/24 dev veth3 -$NS2 ip link set dev veth3 up -$NS2 ip route add default via 192.168.1.1 + ################################################################################ + # Memory Leak Check # + ################################################################################ -iptables -t nat -A POSTROUTING -s 192.168.1.0/255.255.255.0 \ - -o eth0 -j MASQUERADE -iptables -A FORWARD -i eth0 -o veth2 -j ACCEPT -iptables -A FORWARD -o eth0 -i veth2 -j ACCEPT + FILENAME_MEMORY_LOG="$TEST_FILEPATH_PREFIX-memory.log" -# Allow forwarding -sysctl -w net.ipv4.ip_forward=1 - -echo -e "\nPing from host to namespaces" - -ping -c 3 192.168.0.1 -ping -c 3 192.168.1.1 - -echo -e "\nPing from namespace to host" - -$NS1 ping -c 3 192.168.0.1 -$NS1 ping -c 3 192.168.0.1 -$NS2 ping -c 3 192.168.0.2 -$NS2 ping -c 3 192.168.0.2 - -echo -e "\nPing from ns1 to ns2" - -$NS1 ping -c 3 192.168.0.1 - -echo -e "\nPing from ns2 to ns1" - -$NS2 ping -c 3 192.168.0.1 - -################################################################################ -# Memory Leak Check # -################################################################################ - -FILENAME_MEMORY_LOG="$TEST_FILEPATH_PREFIX-memory.log" - -echo -e "\nStarting a ZeroTier instance in each namespace..." - -time_test_start=`date +%s` - -# Spam the CLI as ZeroTier is starting -spam_cli 100 - -echo "Starting memory leak check" -$NS1 sudo valgrind --demangle=yes --exit-on-first-error=yes \ - --error-exitcode=1 \ - --xml=yes \ - --xml-file=$FILENAME_MEMORY_LOG \ - --leak-check=full \ - ./zerotier-one node1 -U >>node_1.log 2>&1 & - -# Second instance, not run in memory profiler -$NS2 sudo ./zerotier-one node2 -U -p9997 >>node_2.log 2>&1 & - -################################################################################ -# Online Check # -################################################################################ - -spam_cli() -{ - echo "Spamming CLI..." - # Rapidly spam the CLI with joins/leaves - - MAX_TRIES="${$1:-10}" - - for ((s=0; s<=MAX_TRIES; s++)) - do - $ZT1 status - $ZT2 status - sleep 0.1 - done - - SPAM_TRIES=128 - - for ((s=0; s<=SPAM_TRIES; s++)) - do - $ZT1 join $TEST_NETWORK - done - - for ((s=0; s<=SPAM_TRIES; s++)) - do - $ZT1 leave $TEST_NETWORK - done - - for ((s=0; s<=SPAM_TRIES; s++)) - do - $ZT1 leave $TEST_NETWORK - $ZT1 join $TEST_NETWORK - done -} + echo -e "\nStarting a ZeroTier instance in each namespace..." -echo "Waiting for ZeroTier to come online before attempting test..." -MAX_WAIT_SECS="${MAX_WAIT_SECS:-120}" -node1_online=false -node2_online=false -both_instances_online=false -time_zt_node1_start=`date +%s` -time_zt_node2_start=`date +%s` + time_test_start=$(date +%s) -for ((s=0; s<=MAX_WAIT_SECS; s++)) -do - node1_online="$($ZT1 -j info | jq '.online' 2>/dev/null)" - node2_online="$($ZT2 -j info | jq '.online' 2>/dev/null)" - echo "Checking for online status: try #$s, node1:$node1_online, node2:$node2_online" - if [[ "$node1_online" == "true" ]] - then - time_zt_node1_online=`date +%s` - fi - if [[ "$node2_online" == "true" ]] - then - time_zt_node2_online=`date +%s` - fi - if [[ "$node2_online" == "true" && "$node1_online" == "true" ]] - then - both_instances_online=true - break - fi - sleep 1 -done - -echo -e "\n\nContents of ZeroTier home paths:" - -ls -lga node1 -tree node1 -ls -lga node2 -tree node2 - -echo -e "\n\nRunning ZeroTier processes:" -echo -e "\nNode 1:" -$NS1 ps aux | grep zerotier-one -echo -e "\nNode 2:" -$NS2 ps aux | grep zerotier-one - -echo -e "\n\nStatus of each instance:" - -echo -e "\n\nNode 1:" -$ZT1 status -echo -e "\n\nNode 2:" -$ZT2 status - -if [[ "$both_instances_online" != "true" ]] -then - echo "One or more instances of ZeroTier failed to come online. Aborting test." - exit 1 -fi - -echo -e "\nJoining networks" - -$ZT1 join $TEST_NETWORK -$ZT2 join $TEST_NETWORK - -sleep 10 - -node1_ip4=$($ZT1 get $TEST_NETWORK ip4) -node2_ip4=$($ZT2 get $TEST_NETWORK ip4) - -echo "node1_ip4=$node1_ip4" -echo "node2_ip4=$node2_ip4" - -echo -e "\nPinging each node" - -PING12_FILENAME="$TEST_FILEPATH_PREFIX-ping-1-to-2.txt" -PING21_FILENAME="$TEST_FILEPATH_PREFIX-ping-2-to-1.txt" - -$NS1 ping -c 16 $node2_ip4 > $PING12_FILENAME -$NS2 ping -c 16 $node1_ip4 > $PING21_FILENAME - -# Parse ping statistics -ping_loss_percent_1_to_2="${ping_loss_percent_1_to_2:-100.0}" -ping_loss_percent_2_to_1="${ping_loss_percent_2_to_1:-100.0}" - -ping_loss_percent_1_to_2=$(cat $PING12_FILENAME | \ - grep "packet loss" | awk '{print $6}' | sed 's/%//') -ping_loss_percent_2_to_1=$(cat $PING21_FILENAME | \ - grep "packet loss" | awk '{print $6}' | sed 's/%//') - -# Normalize loss value -ping_loss_percent_1_to_2=$(echo "scale=2; $ping_loss_percent_1_to_2/100.0" | bc) -ping_loss_percent_2_to_1=$(echo "scale=2; $ping_loss_percent_2_to_1/100.0" | bc) - -################################################################################ -# CLI Check # -################################################################################ + # Spam the CLI as ZeroTier is starting + spam_cli 100 -echo "Testing basic CLI functionality..." + echo "Starting memory leak check" + $NS1 sudo valgrind --demangle=yes --exit-on-first-error=yes \ + --error-exitcode=1 \ + --xml=yes \ + --xml-file=$FILENAME_MEMORY_LOG \ + --leak-check=full \ + ./zerotier-one node1 -p9996 -U >>node_1.log 2>&1 & -spam_cli 10 + # Second instance, not run in memory profiler + $NS2 sudo ./zerotier-one node2 -U -p9997 >>node_2.log 2>&1 & -$ZT1 join $TEST_NETWORK + ################################################################################ + # Online Check # + ################################################################################ -$ZT1 -h -$ZT1 -v -$ZT1 status -$ZT1 info -$ZT1 listnetworks -$ZT1 peers -$ZT1 listpeers + echo "Waiting for ZeroTier to come online before attempting test..." + MAX_WAIT_SECS="${MAX_WAIT_SECS:-120}" + node1_online=false + node2_online=false + both_instances_online=false + time_zt_node1_start=$(date +%s) + time_zt_node2_start=$(date +%s) -$ZT1 -j status -$ZT1 -j info -$ZT1 -j listnetworks -$ZT1 -j peers -$ZT1 -j listpeers + for ((s = 0; s <= MAX_WAIT_SECS; s++)); do + node1_online="$($ZT1 -j info | jq '.online' 2>/dev/null)" + node2_online="$($ZT2 -j info | jq '.online' 2>/dev/null)" + echo "Checking for online status: try #$s, node1:$node1_online, node2:$node2_online" + if [[ "$node1_online" == "true" ]]; then + time_zt_node1_online=$(date +%s) + fi + if [[ "$node2_online" == "true" ]]; then + time_zt_node2_online=$(date +%s) + fi + if [[ "$node2_online" == "true" && "$node1_online" == "true" ]]; then + both_instances_online=true + break + fi + sleep 1 + done + + echo -e "\n\nContents of ZeroTier home paths:" + + ls -lga node1 + tree node1 + ls -lga node2 + tree node2 + + echo -e "\n\nRunning ZeroTier processes:" + echo -e "\nNode 1:" + $NS1 ps aux | grep zerotier-one + echo -e "\nNode 2:" + $NS2 ps aux | grep zerotier-one -$ZT1 dump + echo -e "\n\nStatus of each instance:" -$ZT1 get $TEST_NETWORK allowDNS -$ZT1 get $TEST_NETWORK allowDefault -$ZT1 get $TEST_NETWORK allowGlobal -$ZT1 get $TEST_NETWORK allowManaged -$ZT1 get $TEST_NETWORK bridge -$ZT1 get $TEST_NETWORK broadcastEnabled -$ZT1 get $TEST_NETWORK dhcp -$ZT1 get $TEST_NETWORK id -$ZT1 get $TEST_NETWORK mac -$ZT1 get $TEST_NETWORK mtu -$ZT1 get $TEST_NETWORK name -$ZT1 get $TEST_NETWORK netconfRevision -$ZT1 get $TEST_NETWORK nwid -$ZT1 get $TEST_NETWORK portDeviceName -$ZT1 get $TEST_NETWORK portError -$ZT1 get $TEST_NETWORK status -$ZT1 get $TEST_NETWORK type + echo -e "\n\nNode 1:" + $ZT1 status + echo -e "\n\nNode 2:" + $ZT2 status -# Test an invalid command -$ZT1 get $TEST_NETWORK derpderp + if [[ "$both_instances_online" != "true" ]]; then + echo "One or more instances of ZeroTier failed to come online. Aborting test." + exit 1 + fi -# TODO: Validate JSON + echo -e "\nJoining networks" -################################################################################ -# Performance Test # -################################################################################ + $ZT1 join $TEST_NETWORK + $ZT2 join $TEST_NETWORK -FILENAME_PERF_JSON="$TEST_FILEPATH_PREFIX-iperf.json" + sleep 10 -echo -e "\nBeginning performance test:" + node1_ip4=$($ZT1 get $TEST_NETWORK ip4) + node2_ip4=$($ZT2 get $TEST_NETWORK ip4) -echo -e "\nStarting server:" + echo "node1_ip4=$node1_ip4" + echo "node2_ip4=$node2_ip4" -echo "$NS1 iperf3 -s &" -sleep 1 + echo -e "\nPinging each node" -echo -e "\nStarting client:" -sleep 1 + PING12_FILENAME="$TEST_FILEPATH_PREFIX-ping-1-to-2.txt" + PING21_FILENAME="$TEST_FILEPATH_PREFIX-ping-2-to-1.txt" -echo "$NS2 iperf3 --json -c $node1_ip4 > $FILENAME_PERF_JSON" + $NS1 ping -c 16 $node2_ip4 >$PING12_FILENAME + $NS2 ping -c 16 $node1_ip4 >$PING21_FILENAME -cat $FILENAME_PERF_JSON + # Parse ping statistics + ping_loss_percent_1_to_2="${ping_loss_percent_1_to_2:-100.0}" + ping_loss_percent_2_to_1="${ping_loss_percent_2_to_1:-100.0}" -################################################################################ -# Collect ZeroTier dump files # -################################################################################ + ping_loss_percent_1_to_2=$(cat $PING12_FILENAME | + grep "packet loss" | awk '{print $6}' | sed 's/%//') + ping_loss_percent_2_to_1=$(cat $PING21_FILENAME | + grep "packet loss" | awk '{print $6}' | sed 's/%//') -echo -e "\nCollecting ZeroTier dump files" + # Normalize loss value + ping_loss_percent_1_to_2=$(echo "scale=2; $ping_loss_percent_1_to_2/100.0" | bc) + ping_loss_percent_2_to_1=$(echo "scale=2; $ping_loss_percent_2_to_1/100.0" | bc) -node1_id=$($ZT1 -j status | jq -r .address) -node2_id=$($ZT2 -j status | jq -r .address) + ################################################################################ + # CLI Check # + ################################################################################ -$ZT1 dump -mv zerotier_dump.txt "$TEST_FILEPATH_PREFIX-node-dump-$node1_id.txt" + echo "Testing basic CLI functionality..." -$ZT2 dump -mv zerotier_dump.txt "$TEST_FILEPATH_PREFIX-node-dump-$node2_id.txt" + spam_cli 10 -################################################################################ -# Let ZeroTier idle long enough for various timers # -################################################################################ + $ZT1 join $TEST_NETWORK -echo -e "\nIdling ZeroTier for $RUN_LENGTH seconds..." -sleep $RUN_LENGTH + $ZT1 -h + $ZT1 -v + $ZT1 status + $ZT1 info + $ZT1 listnetworks + $ZT1 peers + $ZT1 listpeers -echo -e "\nLeaving networks" + $ZT1 -j status + $ZT1 -j info + $ZT1 -j listnetworks + $ZT1 -j peers + $ZT1 -j listpeers -$ZT1 leave $TEST_NETWORK -$ZT2 leave $TEST_NETWORK + $ZT1 dump -sleep 5 + $ZT1 get $TEST_NETWORK allowDNS + $ZT1 get $TEST_NETWORK allowDefault + $ZT1 get $TEST_NETWORK allowGlobal + $ZT1 get $TEST_NETWORK allowManaged + $ZT1 get $TEST_NETWORK bridge + $ZT1 get $TEST_NETWORK broadcastEnabled + $ZT1 get $TEST_NETWORK dhcp + $ZT1 get $TEST_NETWORK id + $ZT1 get $TEST_NETWORK mac + $ZT1 get $TEST_NETWORK mtu + $ZT1 get $TEST_NETWORK name + $ZT1 get $TEST_NETWORK netconfRevision + $ZT1 get $TEST_NETWORK nwid + $ZT1 get $TEST_NETWORK portDeviceName + $ZT1 get $TEST_NETWORK portError + $ZT1 get $TEST_NETWORK status + $ZT1 get $TEST_NETWORK type -################################################################################ -# Stop test # -################################################################################ + # Test an invalid command + $ZT1 get $TEST_NETWORK derpderp -echo -e "\nStopping memory check..." -sudo pkill -15 -f valgrind -sleep 10 + # TODO: Validate JSON -time_test_end=`date +%s` + ################################################################################ + # Performance Test # + ################################################################################ -################################################################################ -# Rename ZeroTier stdout/stderr logs # -################################################################################ + FILENAME_PERF_JSON="$TEST_FILEPATH_PREFIX-iperf.json" -mv node_1.log "$TEST_FILEPATH_PREFIX-node-log-$node1_id.txt" -mv node_2.log "$TEST_FILEPATH_PREFIX-node-log-$node2_id.txt" + echo -e "\nBeginning performance test:" -################################################################################ -# Generate report # -################################################################################ + echo -e "\nStarting server:" -cat $FILENAME_MEMORY_LOG + echo "$NS1 iperf3 -s &" + sleep 1 -DEFINITELY_LOST=$(xmlstarlet sel -t -v '/valgrindoutput/error/xwhat' \ - $FILENAME_MEMORY_LOG | grep "definitely" | awk '{print $1;}') -POSSIBLY_LOST=$(xmlstarlet sel -t -v '/valgrindoutput/error/xwhat' \ - $FILENAME_MEMORY_LOG | grep "possibly" | awk '{print $1;}') + echo -e "\nStarting client:" + sleep 1 -################################################################################ -# Generate coverage report artifact and summary # -################################################################################ + echo "$NS2 iperf3 --json -c $node1_ip4 > $FILENAME_PERF_JSON" -FILENAME_COVERAGE_JSON="$TEST_FILEPATH_PREFIX-coverage.json" -FILENAME_COVERAGE_HTML="$TEST_FILEPATH_PREFIX-coverage.html" + cat $FILENAME_PERF_JSON -echo -e "\nGenerating coverage test report..." + ################################################################################ + # Collect ZeroTier dump files # + ################################################################################ -gcovr -r . --exclude ext --json-summary $FILENAME_COVERAGE_JSON \ - --html > $FILENAME_COVERAGE_HTML + echo -e "\nCollecting ZeroTier dump files" -cat $FILENAME_COVERAGE_JSON + node1_id=$($ZT1 -j status | jq -r .address) + node2_id=$($ZT2 -j status | jq -r .address) -COVERAGE_LINE_COVERED=$(cat $FILENAME_COVERAGE_JSON | jq .line_covered) -COVERAGE_LINE_TOTAL=$(cat $FILENAME_COVERAGE_JSON | jq .line_total) -COVERAGE_LINE_PERCENT=$(cat $FILENAME_COVERAGE_JSON | jq .line_percent) + $ZT1 dump + mv zerotier_dump.txt "$TEST_FILEPATH_PREFIX-node-dump-$node1_id.txt" -COVERAGE_LINE_COVERED="${COVERAGE_LINE_COVERED:-0}" -COVERAGE_LINE_TOTAL="${COVERAGE_LINE_TOTAL:-0}" -COVERAGE_LINE_PERCENT="${COVERAGE_LINE_PERCENT:-0}" + $ZT2 dump + mv zerotier_dump.txt "$TEST_FILEPATH_PREFIX-node-dump-$node2_id.txt" -################################################################################ -# Default values # -################################################################################ + ################################################################################ + # Let ZeroTier idle long enough for various timers # + ################################################################################ -DEFINITELY_LOST="${DEFINITELY_LOST:-0}" -POSSIBLY_LOST="${POSSIBLY_LOST:-0}" + echo -e "\nIdling ZeroTier for $RUN_LENGTH seconds..." + sleep $RUN_LENGTH -################################################################################ -# Summarize and emit json for trend reporting # -################################################################################ + echo -e "\nLeaving networks" -FILENAME_SUMMARY="$TEST_FILEPATH_PREFIX-summary.json" + $ZT1 leave $TEST_NETWORK + $ZT2 leave $TEST_NETWORK -time_length_test=$((time_test_end-time_test_start)) -time_length_zt_node1_online=$((time_zt_node1_online-time_zt_start)) -time_length_zt_node2_online=$((time_zt_node2_online-time_zt_start)) -#time_length_zt_join=$((time_zt_join_end-time_zt_join_start)) -#time_length_zt_leave=$((time_zt_leave_end-time_zt_leave_start)) -#time_length_zt_can_still_ping=$((time_zt_can_still_ping-time_zt_leave_start)) + sleep 5 -summary=$(cat <$FILENAME_COVERAGE_HTML + + cat $FILENAME_COVERAGE_JSON + + COVERAGE_LINE_COVERED=$(cat $FILENAME_COVERAGE_JSON | jq .line_covered) + COVERAGE_LINE_TOTAL=$(cat $FILENAME_COVERAGE_JSON | jq .line_total) + COVERAGE_LINE_PERCENT=$(cat $FILENAME_COVERAGE_JSON | jq .line_percent) + + COVERAGE_LINE_COVERED="${COVERAGE_LINE_COVERED:-0}" + COVERAGE_LINE_TOTAL="${COVERAGE_LINE_TOTAL:-0}" + COVERAGE_LINE_PERCENT="${COVERAGE_LINE_PERCENT:-0}" + + ################################################################################ + # Default values # + ################################################################################ + + DEFINITELY_LOST="${DEFINITELY_LOST:-0}" + POSSIBLY_LOST="${POSSIBLY_LOST:-0}" + + ################################################################################ + # Summarize and emit json for trend reporting # + ################################################################################ + + FILENAME_SUMMARY="$TEST_FILEPATH_PREFIX-summary.json" + + time_length_test=$((time_test_end - time_test_start)) + time_length_zt_node1_online=$((time_zt_node1_online - time_zt_start)) + time_length_zt_node2_online=$((time_zt_node2_online - time_zt_start)) + #time_length_zt_join=$((time_zt_join_end-time_zt_join_start)) + #time_length_zt_leave=$((time_zt_leave_end-time_zt_leave_start)) + #time_length_zt_can_still_ping=$((time_zt_can_still_ping-time_zt_leave_start)) + + summary=$( + cat < $FILENAME_SUMMARY -cat $FILENAME_SUMMARY + echo $summary >$FILENAME_SUMMARY + cat $FILENAME_SUMMARY +} -"$@" \ No newline at end of file +################################################################################ +# CLI Check # +################################################################################ + +spam_cli() { + echo "Spamming CLI..." + # Rapidly spam the CLI with joins/leaves + + MAX_TRIES="${1:-10}" + + for ((s = 0; s <= MAX_TRIES; s++)); do + $ZT1 status + $ZT2 status + sleep 0.1 + done + + SPAM_TRIES=128 + + for ((s = 0; s <= SPAM_TRIES; s++)); do + $ZT1 join $TEST_NETWORK + done + + for ((s = 0; s <= SPAM_TRIES; s++)); do + $ZT1 leave $TEST_NETWORK + done + + for ((s = 0; s <= SPAM_TRIES; s++)); do + $ZT1 leave $TEST_NETWORK + $ZT1 join $TEST_NETWORK + done +} + +main "$@" From f42841a6ab332a4078d01e81d5726f442b81fd00 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 25 May 2023 11:09:08 -0700 Subject: [PATCH 24/41] fix init order for EmbeddedNetworkController (#2014) --- controller/EmbeddedNetworkController.cpp | 12 +++++++++++- controller/EmbeddedNetworkController.hpp | 6 +++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 3f8316932..b60c375c4 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -466,11 +466,21 @@ EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPa , _node(node) , _ztPath(ztPath) , _path(dbPath) + , _signingId() + , _signingIdAddressString() , _sender((NetworkController::Sender *)0) , _db(this) + , _queue() + , _threads() + , _threads_l() + , _memberStatus() + , _memberStatus_l() + , _expiringSoon() + , _expiringSoon_l() + , _rc(rc) , _ssoExpiryRunning(true) , _ssoExpiry(std::thread(&EmbeddedNetworkController::_ssoExpiryThread, this)) - , _rc(rc) + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK , _member_status_lookup{"nc_member_status_lookup",""} , _member_status_lookup_count{"nc_member_status_lookup_count",""} diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index d3f7e78ef..ef369be39 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -139,9 +139,6 @@ private: std::vector _threads; std::mutex _threads_l; - bool _ssoExpiryRunning; - std::thread _ssoExpiry; - std::unordered_map< _MemberStatusKey,_MemberStatus,_MemberStatusHash > _memberStatus; std::mutex _memberStatus_l; @@ -151,6 +148,9 @@ private: RedisConfig *_rc; std::string _ssoRedirectURL; + bool _ssoExpiryRunning; + std::thread _ssoExpiry; + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK prometheus::simpleapi::benchmark_family_t _member_status_lookup; prometheus::simpleapi::counter_family_t _member_status_lookup_count; From 54decda7a4b351455b4e146f93d27eb38cd29a08 Mon Sep 17 00:00:00 2001 From: travis laduke Date: Wed, 17 May 2023 07:25:09 -0700 Subject: [PATCH 25/41] add constant for getifaddrs cache time --- osdep/EthernetTap.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osdep/EthernetTap.hpp b/osdep/EthernetTap.hpp index 43beb440d..893e70c34 100644 --- a/osdep/EthernetTap.hpp +++ b/osdep/EthernetTap.hpp @@ -23,6 +23,8 @@ #include #include +#define GETIFADDRS_CACHE_TIME 1000 + namespace ZeroTier { class EthernetTap From 4192f6a6d92cd63ffb13c73b50be5f7d0fe4a3d9 Mon Sep 17 00:00:00 2001 From: travis laduke Date: Tue, 16 May 2023 16:16:12 -0700 Subject: [PATCH 26/41] cache getifaddrs - mac --- osdep/MacEthernetTap.cpp | 14 +++++++++++++- osdep/MacEthernetTap.hpp | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osdep/MacEthernetTap.cpp b/osdep/MacEthernetTap.cpp index 55822fd5a..392f222b8 100644 --- a/osdep/MacEthernetTap.cpp +++ b/osdep/MacEthernetTap.cpp @@ -90,7 +90,8 @@ MacEthernetTap::MacEthernetTap( _agentStdout2(-1), _agentStderr2(-1), _agentPid(-1), - _enabled(true) + _enabled(true), + _lastIfAddrsUpdate(0) { char ethaddr[64],mtustr[16],devnostr[16],devstr[16],metricstr[16]; OSUtils::ztsnprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]); @@ -341,8 +342,16 @@ bool MacEthernetTap::removeIp(const InetAddress &ip) std::vector MacEthernetTap::ips() const { + uint64_t now = OSUtils::now(); + + if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) { + return _ifaddrs; + } + _lastIfAddrsUpdate = now; + struct ifaddrs *ifa = (struct ifaddrs *)0; std::vector r; + if (!getifaddrs(&ifa)) { struct ifaddrs *p = ifa; while (p) { @@ -368,6 +377,9 @@ std::vector MacEthernetTap::ips() const } std::sort(r.begin(),r.end()); r.erase(std::unique(r.begin(),r.end()),r.end()); + + _ifaddrs = r; + return r; } diff --git a/osdep/MacEthernetTap.hpp b/osdep/MacEthernetTap.hpp index 4b02999b9..8ba378022 100644 --- a/osdep/MacEthernetTap.hpp +++ b/osdep/MacEthernetTap.hpp @@ -77,6 +77,9 @@ private: int _agentStdin,_agentStdout,_agentStderr,_agentStdin2,_agentStdout2,_agentStderr2; long _agentPid; volatile bool _enabled; + mutable std::vector _ifaddrs; + mutable uint64_t _lastIfAddrsUpdate; + }; } // namespace ZeroTier From 259ee610a60d43e8062e9289d54bcb7e7fcad513 Mon Sep 17 00:00:00 2001 From: travis laduke Date: Wed, 17 May 2023 07:16:09 -0700 Subject: [PATCH 27/41] cache getifaddrs - linux --- osdep/LinuxEthernetTap.cpp | 14 +++++++++++++- osdep/LinuxEthernetTap.hpp | 5 +++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osdep/LinuxEthernetTap.cpp b/osdep/LinuxEthernetTap.cpp index 8995b102b..a888db9d9 100644 --- a/osdep/LinuxEthernetTap.cpp +++ b/osdep/LinuxEthernetTap.cpp @@ -126,12 +126,14 @@ LinuxEthernetTap::LinuxEthernetTap( _mtu(mtu), _fd(0), _enabled(true), - _run(true) + _run(true), + _lastIfAddrsUpdate(0) { static std::mutex s_tapCreateLock; char procpath[128],nwids[32]; struct stat sbuf; + // Create only one tap at a time globally. std::lock_guard tapCreateLock(s_tapCreateLock); @@ -438,6 +440,14 @@ bool LinuxEthernetTap::removeIp(const InetAddress &ip) std::vector LinuxEthernetTap::ips() const { + + uint64_t now = OSUtils::now(); + + if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) { + return _ifaddrs; + } + _lastIfAddrsUpdate = now; + struct ifaddrs *ifa = (struct ifaddrs *)0; if (getifaddrs(&ifa)) return std::vector(); @@ -471,6 +481,8 @@ std::vector LinuxEthernetTap::ips() const std::sort(r.begin(),r.end()); r.erase(std::unique(r.begin(),r.end()),r.end()); + _ifaddrs = r; + return r; } diff --git a/osdep/LinuxEthernetTap.hpp b/osdep/LinuxEthernetTap.hpp index 3603740d4..424a6d37e 100644 --- a/osdep/LinuxEthernetTap.hpp +++ b/osdep/LinuxEthernetTap.hpp @@ -57,6 +57,9 @@ public: virtual void setMtu(unsigned int mtu); virtual void setDns(const char *domain, const std::vector &servers) {} + + + private: void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); void *_arg; @@ -71,6 +74,8 @@ private: std::atomic_bool _enabled; std::atomic_bool _run; std::thread _tapReaderThread; + mutable std::vector _ifaddrs; + mutable uint64_t _lastIfAddrsUpdate; }; } // namespace ZeroTier From 60d2138f30b0378a156c8b25d5a29682882aba0a Mon Sep 17 00:00:00 2001 From: travis laduke Date: Wed, 17 May 2023 07:16:26 -0700 Subject: [PATCH 28/41] cache getifaddrs - bsd --- osdep/BSDEthernetTap.cpp | 12 +++++++++++- osdep/BSDEthernetTap.hpp | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osdep/BSDEthernetTap.cpp b/osdep/BSDEthernetTap.cpp index efba03147..b2e1a8760 100644 --- a/osdep/BSDEthernetTap.cpp +++ b/osdep/BSDEthernetTap.cpp @@ -74,7 +74,8 @@ BSDEthernetTap::BSDEthernetTap( _mtu(mtu), _metric(metric), _fd(0), - _enabled(true) + _enabled(true), + _lastIfAddrsUpdate(0) { static Mutex globalTapCreateLock; char devpath[64],ethaddr[64],mtustr[32],metstr[32],tmpdevname[32]; @@ -287,6 +288,13 @@ bool BSDEthernetTap::removeIp(const InetAddress &ip) std::vector BSDEthernetTap::ips() const { + uint64_t now = OSUtils::now(); + + if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) { + return _ifaddrs; + } + _lastIfAddrsUpdate = now; + struct ifaddrs *ifa = (struct ifaddrs *)0; if (getifaddrs(&ifa)) return std::vector(); @@ -320,6 +328,8 @@ std::vector BSDEthernetTap::ips() const std::sort(r.begin(),r.end()); std::unique(r.begin(),r.end()); + _ifaddrs = r; + return r; } diff --git a/osdep/BSDEthernetTap.hpp b/osdep/BSDEthernetTap.hpp index c66fb6f79..fc4e4908e 100644 --- a/osdep/BSDEthernetTap.hpp +++ b/osdep/BSDEthernetTap.hpp @@ -71,6 +71,8 @@ private: int _fd; int _shutdownSignalPipe[2]; volatile bool _enabled; + mutable std::vector _ifaddrs; + mutable uint64_t _lastIfAddrsUpdate; }; } // namespace ZeroTier From e11d70e4080ffb7ebeb2b76e467d574f59afd5b4 Mon Sep 17 00:00:00 2001 From: travis laduke Date: Wed, 17 May 2023 07:18:42 -0700 Subject: [PATCH 29/41] cache getifaddrs - windows --- osdep/WindowsEthernetTap.cpp | 13 ++++++++++++- osdep/WindowsEthernetTap.hpp | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index db65bbf41..7a9d4b309 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -467,7 +467,8 @@ WindowsEthernetTap::WindowsEthernetTap( _pathToHelpers(hp), _run(true), _initialized(false), - _enabled(true) + _enabled(true), + _lastIfAddrsUpdate(0) { char subkeyName[1024]; char subkeyClass[1024]; @@ -749,6 +750,14 @@ std::vector WindowsEthernetTap::ips() const if (!_initialized) return addrs; + uint64_t now = OSUtils::now(); + + if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) { + return _ifaddrs; + } + + _lastIfAddrsUpdate = now; + try { MIB_UNICASTIPADDRESS_TABLE *ipt = (MIB_UNICASTIPADDRESS_TABLE *)0; if (GetUnicastIpAddressTable(AF_UNSPEC,&ipt) == NO_ERROR) { @@ -777,6 +786,8 @@ std::vector WindowsEthernetTap::ips() const std::sort(addrs.begin(),addrs.end()); addrs.erase(std::unique(addrs.begin(),addrs.end()),addrs.end()); + _ifaddrs = addrs; + return addrs; } diff --git a/osdep/WindowsEthernetTap.hpp b/osdep/WindowsEthernetTap.hpp index a08042d44..a79dac0ec 100644 --- a/osdep/WindowsEthernetTap.hpp +++ b/osdep/WindowsEthernetTap.hpp @@ -152,6 +152,9 @@ private: volatile bool _run; volatile bool _initialized; volatile bool _enabled; + + mutable std::vector _ifaddrs; + mutable uint64_t _lastIfAddrsUpdate; }; } // namespace ZeroTier From 9a8b74d74472c89f0d86970cfa0dd176e880732a Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 31 May 2023 09:34:31 -0700 Subject: [PATCH 30/41] Fix oidc client lookup query join condition referenced the wrong table. Worked fine unless there were multiple identical client IDs --- controller/PostgreSQL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 8ac9bf747..1ec46e891 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -460,7 +460,7 @@ AuthInfo PostgreSQL::getSSOAuthInfo(const nlohmann::json &member, const std::str "LEFT OUTER JOIN ztc_network_oidc_config noc " " ON noc.network_id = n.id " "LEFT OUTER JOIN ztc_oidc_config oc " - " ON noc.client_id = oc.client_id AND noc.org_id = o.org_id " + " ON noc.client_id = oc.client_id AND oc.org_id = o.org_id " "WHERE n.id = $1 AND n.sso_enabled = true", networkId); std::string client_id = ""; From 8ebe2c563a39175a7e568bac1de4e8c82b958106 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 31 May 2023 13:25:30 -0700 Subject: [PATCH 31/41] Fix udp sent metric was only incrementing by 1 for each packet sent --- osdep/Phy.hpp | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/osdep/Phy.hpp b/osdep/Phy.hpp index 54f9b7f8b..4a71629cc 100644 --- a/osdep/Phy.hpp +++ b/osdep/Phy.hpp @@ -453,15 +453,33 @@ public: inline bool udpSend(PhySocket *sock,const struct sockaddr *remoteAddress,const void *data,unsigned long len) { PhySocketImpl &sws = *(reinterpret_cast(sock)); + bool sent = false; #if defined(_WIN32) || defined(_WIN64) - int sent = ((long)::sendto(sws.sock,reinterpret_cast(data),len,0,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == (long)len); - Metrics::udp_send += sent; - return sent; + sent = ((long)::sendto( + sws.sock, + reinterpret_cast(data), + len, + 0, + remoteAddress, + (remoteAddress->sa_family == AF_INET6) ? + sizeof(struct sockaddr_in6) : + sizeof(struct sockaddr_in)) == (long)len); #else - ssize_t sent = ((long)::sendto(sws.sock,data,len,0,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == (long)len); - Metrics::udp_send += sent; - return sent; + sent = ((long)::sendto( + sws.sock, + data, + len, + 0, + remoteAddress, + (remoteAddress->sa_family == AF_INET6) ? + sizeof(struct sockaddr_in6) : + sizeof(struct sockaddr_in)) == (long)len); #endif + if (sent) { + Metrics::udp_send += len; + } + + return sent; } #ifdef __UNIX_LIKE__ From 5ad0212b9370ee2c0d8fc8a09a2552ddfb27f5b8 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Wed, 31 May 2023 15:02:17 -0700 Subject: [PATCH 32/41] Allow sending all surface addresses to peer in low-bandwidth mode --- node/Peer.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/node/Peer.cpp b/node/Peer.cpp index 6fcf193d9..d7f543ead 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -210,10 +210,8 @@ void Peer::received( if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH * timerScale : ZT_DIRECT_PATH_PUSH_INTERVAL)) { _lastDirectPathPushSent = now; std::vector pathsToPush(RR->node->directPaths()); - if (! lowBandwidth) { - std::vector ma = RR->sa->whoami(); - pathsToPush.insert(pathsToPush.end(), ma.begin(), ma.end()); - } + std::vector ma = RR->sa->whoami(); + pathsToPush.insert(pathsToPush.end(), ma.begin(), ma.end()); if (!pathsToPush.empty()) { std::vector::const_iterator p(pathsToPush.begin()); while (p != pathsToPush.end()) { From 405f96c4b7cfe143c3c01a0649c98cfe88a87cfe Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 31 May 2023 15:07:57 -0700 Subject: [PATCH 33/41] allow enabling of low bandwidth mode on controllers --- ext/central-controller-docker/main.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/central-controller-docker/main.sh b/ext/central-controller-docker/main.sh index a258c0902..1ec8e10f2 100755 --- a/ext/central-controller-docker/main.sh +++ b/ext/central-controller-docker/main.sh @@ -64,6 +64,7 @@ fi popd DEFAULT_PORT=9993 +DEFAULT_LB_MODE=false APP_NAME="controller-$(cat /var/lib/zerotier-one/identity.public | cut -d ':' -f 1)" @@ -76,6 +77,7 @@ echo "{ \"inot\", \"nat64\" ], + \"lowBandwidthMode\": ${ZT_LB_MODE:-$DEFAULT_LB_MODE}, \"ssoRedirectURL\": \"${ZT_SSO_REDIRECT_URL}\", \"allowManagementFrom\": [\"127.0.0.1\", \"::1\", \"10.0.0.0/8\"], ${REDIS} From 3efb731b40372c5937f33a8feacdcbcd6bb4764f Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 8 Jun 2023 08:45:50 -0700 Subject: [PATCH 34/41] don't unborrow bad connections pool will clean them up later --- controller/PostgreSQL.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 1ec46e891..319c02684 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -527,9 +527,6 @@ AuthInfo PostgreSQL::getSSOAuthInfo(const nlohmann::json &member, const std::str _pool->unborrow(c); } catch (std::exception &e) { - if (c) { - _pool->unborrow(c); - } fprintf(stderr, "ERROR: Error updating member on load for network %s: %s\n", networkId.c_str(), e.what()); } @@ -1051,7 +1048,6 @@ void PostgreSQL::heartbeat() w.commit(); } catch (std::exception &e) { fprintf(stderr, "%s: Heartbeat update failed: %s\n", controllerId, e.what()); - _pool->unborrow(c); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); continue; } From 0962af5e724a40a3856262dc134a541d59dfc816 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Thu, 29 Jun 2023 19:45:44 -0400 Subject: [PATCH 35/41] Multi-arch controller container (#2037) create arm64 & amd64 images for central controller --- ext/central-controller-docker/Dockerfile | 4 +- ext/central-controller-docker/Makefile | 16 + .../lib/ubuntu22.04/{ => amd64}/libhiredis.a | Bin .../lib/ubuntu22.04/arm64/libhiredis.a | Bin 0 -> 495416 bytes .../{ => amd64}/include/pqxx/array | 0 .../{ => amd64}/include/pqxx/array.hxx | 0 .../{ => amd64}/include/pqxx/binarystring | 0 .../{ => amd64}/include/pqxx/binarystring.hxx | 0 .../ubuntu22.04/{ => amd64}/include/pqxx/blob | 0 .../{ => amd64}/include/pqxx/blob.hxx | 0 .../{ => amd64}/include/pqxx/composite | 0 .../{ => amd64}/include/pqxx/composite.hxx | 0 .../include/pqxx/config-public-compiler.h | 0 .../{ => amd64}/include/pqxx/connection | 0 .../{ => amd64}/include/pqxx/connection.hxx | 0 .../{ => amd64}/include/pqxx/cursor | 0 .../{ => amd64}/include/pqxx/cursor.hxx | 0 .../{ => amd64}/include/pqxx/dbtransaction | 0 .../include/pqxx/dbtransaction.hxx | 0 .../{ => amd64}/include/pqxx/errorhandler | 0 .../{ => amd64}/include/pqxx/errorhandler.hxx | 0 .../{ => amd64}/include/pqxx/except | 0 .../{ => amd64}/include/pqxx/except.hxx | 0 .../{ => amd64}/include/pqxx/field | 0 .../{ => amd64}/include/pqxx/field.hxx | 0 .../include/pqxx/internal/array-composite.hxx | 0 .../include/pqxx/internal/callgate.hxx | 0 .../include/pqxx/internal/concat.hxx | 0 .../include/pqxx/internal/conversions.hxx | 0 .../include/pqxx/internal/encoding_group.hxx | 0 .../include/pqxx/internal/encodings.hxx | 0 .../gates/connection-errorhandler.hxx | 0 .../internal/gates/connection-largeobject.hxx | 0 .../connection-notification_receiver.hxx | 0 .../internal/gates/connection-pipeline.hxx | 0 .../internal/gates/connection-sql_cursor.hxx | 0 .../internal/gates/connection-stream_from.hxx | 0 .../internal/gates/connection-stream_to.hxx | 0 .../internal/gates/connection-transaction.hxx | 0 .../gates/errorhandler-connection.hxx | 0 .../gates/icursor_iterator-icursorstream.hxx | 0 .../gates/icursorstream-icursor_iterator.hxx | 0 .../pqxx/internal/gates/result-connection.hxx | 0 .../pqxx/internal/gates/result-creation.hxx | 0 .../pqxx/internal/gates/result-pipeline.hxx | 0 .../pqxx/internal/gates/result-sql_cursor.hxx | 0 .../internal/gates/transaction-sql_cursor.hxx | 0 .../gates/transaction-transaction_focus.hxx | 0 .../include/pqxx/internal/header-post.hxx | 0 .../include/pqxx/internal/header-pre.hxx | 0 .../pqxx/internal/ignore-deprecated-post.hxx | 0 .../pqxx/internal/ignore-deprecated-pre.hxx | 0 .../include/pqxx/internal/libpq-forward.hxx | 0 .../include/pqxx/internal/result_iter.hxx | 0 .../include/pqxx/internal/result_iterator.hxx | 0 .../include/pqxx/internal/sql_cursor.hxx | 0 .../pqxx/internal/statement_parameters.hxx | 0 .../include/pqxx/internal/stream_iterator.hxx | 0 .../include/pqxx/internal/wait.hxx | 0 .../{ => amd64}/include/pqxx/isolation | 0 .../{ => amd64}/include/pqxx/isolation.hxx | 0 .../{ => amd64}/include/pqxx/largeobject | 0 .../{ => amd64}/include/pqxx/largeobject.hxx | 0 .../{ => amd64}/include/pqxx/nontransaction | 0 .../include/pqxx/nontransaction.hxx | 0 .../{ => amd64}/include/pqxx/notification | 0 .../{ => amd64}/include/pqxx/notification.hxx | 0 .../{ => amd64}/include/pqxx/params | 0 .../{ => amd64}/include/pqxx/params.hxx | 0 .../{ => amd64}/include/pqxx/pipeline | 0 .../{ => amd64}/include/pqxx/pipeline.hxx | 0 .../ubuntu22.04/{ => amd64}/include/pqxx/pqxx | 0 .../include/pqxx/prepared_statement | 0 .../include/pqxx/prepared_statement.hxx | 0 .../{ => amd64}/include/pqxx/range | 0 .../{ => amd64}/include/pqxx/range.hxx | 0 .../{ => amd64}/include/pqxx/result | 0 .../{ => amd64}/include/pqxx/result.hxx | 0 .../include/pqxx/robusttransaction | 0 .../include/pqxx/robusttransaction.hxx | 0 .../ubuntu22.04/{ => amd64}/include/pqxx/row | 0 .../{ => amd64}/include/pqxx/row.hxx | 0 .../{ => amd64}/include/pqxx/separated_list | 0 .../include/pqxx/separated_list.hxx | 0 .../{ => amd64}/include/pqxx/strconv | 0 .../{ => amd64}/include/pqxx/strconv.hxx | 0 .../{ => amd64}/include/pqxx/stream_from | 0 .../{ => amd64}/include/pqxx/stream_from.hxx | 0 .../{ => amd64}/include/pqxx/stream_to | 0 .../{ => amd64}/include/pqxx/stream_to.hxx | 0 .../{ => amd64}/include/pqxx/subtransaction | 0 .../include/pqxx/subtransaction.hxx | 0 .../ubuntu22.04/{ => amd64}/include/pqxx/time | 0 .../{ => amd64}/include/pqxx/time.hxx | 0 .../{ => amd64}/include/pqxx/transaction | 0 .../{ => amd64}/include/pqxx/transaction.hxx | 0 .../{ => amd64}/include/pqxx/transaction_base | 0 .../include/pqxx/transaction_base.hxx | 0 .../include/pqxx/transaction_focus | 0 .../include/pqxx/transaction_focus.hxx | 0 .../{ => amd64}/include/pqxx/transactor | 0 .../{ => amd64}/include/pqxx/transactor.hxx | 0 .../{ => amd64}/include/pqxx/types | 0 .../{ => amd64}/include/pqxx/types.hxx | 0 .../ubuntu22.04/{ => amd64}/include/pqxx/util | 0 .../{ => amd64}/include/pqxx/util.hxx | 0 .../{ => amd64}/include/pqxx/version | 0 .../{ => amd64}/include/pqxx/version.hxx | 0 .../{ => amd64}/include/pqxx/zview | 0 .../{ => amd64}/include/pqxx/zview.hxx | 0 .../libpqxx/libpqxx-config-version.cmake | 0 .../lib/cmake/libpqxx/libpqxx-config.cmake | 0 .../libpqxx/libpqxx-targets-noconfig.cmake | 0 .../lib/cmake/libpqxx/libpqxx-targets.cmake | 0 .../ubuntu22.04/{ => amd64}/lib/libpqxx-7.7.a | Bin .../ubuntu22.04/{ => amd64}/lib/libpqxx.a | 0 .../share/doc/libpqxx/accessing-results.md | 0 .../share/doc/libpqxx/binary-data.md | 0 .../share/doc/libpqxx/datatypes.md | 0 .../{ => amd64}/share/doc/libpqxx/escaping.md | 0 .../share/doc/libpqxx/getting-started.md | 0 .../{ => amd64}/share/doc/libpqxx/mainpage.md | 0 .../share/doc/libpqxx/parameters.md | 0 .../share/doc/libpqxx/performance.md | 0 .../share/doc/libpqxx/prepared-statement.md | 0 .../{ => amd64}/share/doc/libpqxx/streams.md | 0 .../share/doc/libpqxx/thread-safety.md | 0 .../ubuntu22.04/arm64/include/pqxx/array | 6 + .../ubuntu22.04/arm64/include/pqxx/array.hxx | 103 + .../arm64/include/pqxx/binarystring | 6 + .../arm64/include/pqxx/binarystring.hxx | 236 ++ .../ubuntu22.04/arm64/include/pqxx/blob | 6 + .../ubuntu22.04/arm64/include/pqxx/blob.hxx | 351 ++ .../ubuntu22.04/arm64/include/pqxx/composite | 6 + .../arm64/include/pqxx/composite.hxx | 149 + .../include/pqxx/config-public-compiler.h | 81 + .../ubuntu22.04/arm64/include/pqxx/connection | 8 + .../arm64/include/pqxx/connection.hxx | 1261 ++++++ .../ubuntu22.04/arm64/include/pqxx/cursor | 8 + .../ubuntu22.04/arm64/include/pqxx/cursor.hxx | 483 +++ .../arm64/include/pqxx/dbtransaction | 8 + .../arm64/include/pqxx/dbtransaction.hxx | 70 + .../arm64/include/pqxx/errorhandler | 8 + .../arm64/include/pqxx/errorhandler.hxx | 92 + .../ubuntu22.04/arm64/include/pqxx/except | 8 + .../ubuntu22.04/arm64/include/pqxx/except.hxx | 447 ++ .../ubuntu22.04/arm64/include/pqxx/field | 8 + .../ubuntu22.04/arm64/include/pqxx/field.hxx | 542 +++ .../include/pqxx/internal/array-composite.hxx | 305 ++ .../arm64/include/pqxx/internal/callgate.hxx | 70 + .../arm64/include/pqxx/internal/concat.hxx | 45 + .../include/pqxx/internal/conversions.hxx | 1188 ++++++ .../include/pqxx/internal/encoding_group.hxx | 60 + .../arm64/include/pqxx/internal/encodings.hxx | 90 + .../gates/connection-errorhandler.hxx | 26 + .../internal/gates/connection-largeobject.hxx | 35 + .../connection-notification_receiver.hxx | 29 + .../internal/gates/connection-pipeline.hxx | 23 + .../internal/gates/connection-sql_cursor.hxx | 19 + .../internal/gates/connection-stream_from.hxx | 15 + .../internal/gates/connection-stream_to.hxx | 17 + .../internal/gates/connection-transaction.hxx | 44 + .../gates/errorhandler-connection.hxx | 13 + .../gates/icursor_iterator-icursorstream.hxx | 24 + .../gates/icursorstream-icursor_iterator.hxx | 32 + .../pqxx/internal/gates/result-connection.hxx | 14 + .../pqxx/internal/gates/result-creation.hxx | 24 + .../pqxx/internal/gates/result-pipeline.hxx | 16 + .../pqxx/internal/gates/result-sql_cursor.hxx | 13 + .../internal/gates/transaction-sql_cursor.hxx | 10 + .../gates/transaction-transaction_focus.hxx | 30 + .../include/pqxx/internal/header-post.hxx | 22 + .../include/pqxx/internal/header-pre.hxx | 169 + .../pqxx/internal/ignore-deprecated-post.hxx | 15 + .../pqxx/internal/ignore-deprecated-pre.hxx | 28 + .../include/pqxx/internal/libpq-forward.hxx | 31 + .../include/pqxx/internal/result_iter.hxx | 124 + .../include/pqxx/internal/result_iterator.hxx | 389 ++ .../include/pqxx/internal/sql_cursor.hxx | 118 + .../pqxx/internal/statement_parameters.hxx | 131 + .../include/pqxx/internal/stream_iterator.hxx | 105 + .../arm64/include/pqxx/internal/wait.hxx | 18 + .../ubuntu22.04/arm64/include/pqxx/isolation | 8 + .../arm64/include/pqxx/isolation.hxx | 75 + .../arm64/include/pqxx/largeobject | 8 + .../arm64/include/pqxx/largeobject.hxx | 735 ++++ .../arm64/include/pqxx/nontransaction | 8 + .../arm64/include/pqxx/nontransaction.hxx | 76 + .../arm64/include/pqxx/notification | 8 + .../arm64/include/pqxx/notification.hxx | 94 + .../ubuntu22.04/arm64/include/pqxx/params | 8 + .../ubuntu22.04/arm64/include/pqxx/params.hxx | 383 ++ .../ubuntu22.04/arm64/include/pqxx/pipeline | 8 + .../arm64/include/pqxx/pipeline.hxx | 237 ++ .../ubuntu22.04/arm64/include/pqxx/pqxx | 28 + .../arm64/include/pqxx/prepared_statement | 3 + .../arm64/include/pqxx/prepared_statement.hxx | 3 + .../ubuntu22.04/arm64/include/pqxx/range | 6 + .../ubuntu22.04/arm64/include/pqxx/range.hxx | 515 +++ .../ubuntu22.04/arm64/include/pqxx/result | 16 + .../ubuntu22.04/arm64/include/pqxx/result.hxx | 335 ++ .../arm64/include/pqxx/robusttransaction | 8 + .../arm64/include/pqxx/robusttransaction.hxx | 120 + .../ubuntu22.04/arm64/include/pqxx/row | 11 + .../ubuntu22.04/arm64/include/pqxx/row.hxx | 561 +++ .../arm64/include/pqxx/separated_list | 6 + .../arm64/include/pqxx/separated_list.hxx | 142 + .../ubuntu22.04/arm64/include/pqxx/strconv | 6 + .../arm64/include/pqxx/strconv.hxx | 468 +++ .../arm64/include/pqxx/stream_from | 8 + .../arm64/include/pqxx/stream_from.hxx | 361 ++ .../ubuntu22.04/arm64/include/pqxx/stream_to | 8 + .../arm64/include/pqxx/stream_to.hxx | 455 +++ .../arm64/include/pqxx/subtransaction | 8 + .../arm64/include/pqxx/subtransaction.hxx | 96 + .../ubuntu22.04/arm64/include/pqxx/time | 6 + .../ubuntu22.04/arm64/include/pqxx/time.hxx | 88 + .../arm64/include/pqxx/transaction | 8 + .../arm64/include/pqxx/transaction.hxx | 108 + .../arm64/include/pqxx/transaction_base | 9 + .../arm64/include/pqxx/transaction_base.hxx | 810 ++++ .../arm64/include/pqxx/transaction_focus | 7 + .../arm64/include/pqxx/transaction_focus.hxx | 89 + .../ubuntu22.04/arm64/include/pqxx/transactor | 8 + .../arm64/include/pqxx/transactor.hxx | 147 + .../ubuntu22.04/arm64/include/pqxx/types | 7 + .../ubuntu22.04/arm64/include/pqxx/types.hxx | 173 + .../ubuntu22.04/arm64/include/pqxx/util | 6 + .../ubuntu22.04/arm64/include/pqxx/util.hxx | 521 +++ .../ubuntu22.04/arm64/include/pqxx/version | 7 + .../arm64/include/pqxx/version.hxx | 55 + .../ubuntu22.04/arm64/include/pqxx/zview | 6 + .../ubuntu22.04/arm64/include/pqxx/zview.hxx | 163 + .../libpqxx/libpqxx-config-version.cmake | 70 + .../lib/cmake/libpqxx/libpqxx-config.cmake | 4 + .../libpqxx/libpqxx-targets-noconfig.cmake | 19 + .../lib/cmake/libpqxx/libpqxx-targets.cmake | 99 + .../ubuntu22.04/arm64/lib/libpqxx-7.7.a | Bin 0 -> 4997126 bytes .../install/ubuntu22.04/arm64/lib/libpqxx.a | 1 + .../share/doc/libpqxx/accessing-results.md | 157 + .../arm64/share/doc/libpqxx/binary-data.md | 56 + .../arm64/share/doc/libpqxx/datatypes.md | 373 ++ .../arm64/share/doc/libpqxx/escaping.md | 74 + .../share/doc/libpqxx/getting-started.md | 142 + .../arm64/share/doc/libpqxx/mainpage.md | 28 + .../arm64/share/doc/libpqxx/parameters.md | 90 + .../arm64/share/doc/libpqxx/performance.md | 24 + .../share/doc/libpqxx/prepared-statement.md | 125 + .../arm64/share/doc/libpqxx/streams.md | 107 + .../arm64/share/doc/libpqxx/thread-safety.md | 29 + .../ubuntu22.04/lib/pkgconfig/libpqxx.pc | 10 - .../include/sw/redis++/cmd_formatter.h | 0 .../{ => amd64}/include/sw/redis++/command.h | 0 .../include/sw/redis++/command_args.h | 0 .../include/sw/redis++/command_options.h | 0 .../include/sw/redis++/connection.h | 0 .../include/sw/redis++/connection_pool.h | 0 .../include/sw/redis++/cxx_utils.h | 0 .../{ => amd64}/include/sw/redis++/errors.h | 0 .../{ => amd64}/include/sw/redis++/pipeline.h | 0 .../include/sw/redis++/queued_redis.h | 0 .../include/sw/redis++/queued_redis.hpp | 0 .../{ => amd64}/include/sw/redis++/redis++.h | 0 .../{ => amd64}/include/sw/redis++/redis.h | 0 .../{ => amd64}/include/sw/redis++/redis.hpp | 0 .../include/sw/redis++/redis_cluster.h | 0 .../include/sw/redis++/redis_cluster.hpp | 0 .../{ => amd64}/include/sw/redis++/reply.h | 0 .../{ => amd64}/include/sw/redis++/sentinel.h | 0 .../{ => amd64}/include/sw/redis++/shards.h | 0 .../include/sw/redis++/shards_pool.h | 0 .../include/sw/redis++/subscriber.h | 0 .../{ => amd64}/include/sw/redis++/tls.h | 0 .../include/sw/redis++/transaction.h | 0 .../{ => amd64}/include/sw/redis++/utils.h | 0 .../ubuntu22.04/{ => amd64}/lib/libredis++.a | Bin .../{ => amd64}/lib/pkgconfig/redis++.pc | 0 .../redis++/redis++-config-version.cmake | 0 .../share/cmake/redis++/redis++-config.cmake | 0 .../redis++/redis++-targets-release.cmake | 0 .../share/cmake/redis++/redis++-targets.cmake | 0 .../arm64/include/sw/redis++/cmd_formatter.h | 775 ++++ .../arm64/include/sw/redis++/command.h | 2271 +++++++++++ .../arm64/include/sw/redis++/command_args.h | 180 + .../include/sw/redis++/command_options.h | 211 + .../arm64/include/sw/redis++/connection.h | 237 ++ .../include/sw/redis++/connection_pool.h | 182 + .../arm64/include/sw/redis++/cxx_utils.h | 46 + .../arm64/include/sw/redis++/errors.h | 166 + .../arm64/include/sw/redis++/pipeline.h | 49 + .../arm64/include/sw/redis++/queued_redis.h | 2013 +++++++++ .../arm64/include/sw/redis++/queued_redis.hpp | 275 ++ .../arm64/include/sw/redis++/redis++.h | 25 + .../arm64/include/sw/redis++/redis.h | 3631 +++++++++++++++++ .../arm64/include/sw/redis++/redis.hpp | 1342 ++++++ .../arm64/include/sw/redis++/redis_cluster.h | 1439 +++++++ .../include/sw/redis++/redis_cluster.hpp | 1403 +++++++ .../arm64/include/sw/redis++/reply.h | 435 ++ .../arm64/include/sw/redis++/sentinel.h | 141 + .../arm64/include/sw/redis++/shards.h | 115 + .../arm64/include/sw/redis++/shards_pool.h | 121 + .../arm64/include/sw/redis++/subscriber.h | 231 ++ .../arm64/include/sw/redis++/tls.h | 47 + .../arm64/include/sw/redis++/transaction.h | 77 + .../arm64/include/sw/redis++/utils.h | 193 + .../ubuntu22.04/arm64/lib/libredis++.a | Bin 0 -> 1453850 bytes .../arm64/lib/pkgconfig/redis++.pc | 12 + .../redis++/redis++-config-version.cmake | 48 + .../share/cmake/redis++/redis++-config.cmake | 36 + .../redis++/redis++-targets-release.cmake | 19 + .../share/cmake/redis++/redis++-targets.cmake | 94 + make-linux.mk | 31 +- make-mac.mk | 12 +- 313 files changed, 31474 insertions(+), 20 deletions(-) create mode 100644 ext/central-controller-docker/Makefile rename ext/hiredis-1.0.2/lib/ubuntu22.04/{ => amd64}/libhiredis.a (100%) create mode 100644 ext/hiredis-1.0.2/lib/ubuntu22.04/arm64/libhiredis.a rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/array (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/array.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/binarystring (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/binarystring.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/blob (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/blob.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/composite (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/composite.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/config-public-compiler.h (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/connection (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/connection.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/cursor (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/cursor.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/dbtransaction (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/dbtransaction.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/errorhandler (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/errorhandler.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/except (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/except.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/field (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/field.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/array-composite.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/callgate.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/concat.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/conversions.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/encoding_group.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/encodings.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/connection-errorhandler.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/connection-largeobject.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/connection-notification_receiver.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/connection-pipeline.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/connection-sql_cursor.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/connection-stream_from.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/connection-stream_to.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/connection-transaction.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/errorhandler-connection.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/result-connection.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/result-creation.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/result-pipeline.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/result-sql_cursor.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/transaction-sql_cursor.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/gates/transaction-transaction_focus.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/header-post.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/header-pre.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/ignore-deprecated-post.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/ignore-deprecated-pre.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/libpq-forward.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/result_iter.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/result_iterator.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/sql_cursor.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/statement_parameters.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/stream_iterator.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/internal/wait.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/isolation (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/isolation.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/largeobject (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/largeobject.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/nontransaction (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/nontransaction.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/notification (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/notification.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/params (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/params.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/pipeline (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/pipeline.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/pqxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/prepared_statement (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/prepared_statement.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/range (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/range.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/result (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/result.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/robusttransaction (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/robusttransaction.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/row (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/row.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/separated_list (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/separated_list.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/strconv (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/strconv.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/stream_from (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/stream_from.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/stream_to (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/stream_to.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/subtransaction (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/subtransaction.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/time (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/time.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/transaction (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/transaction.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/transaction_base (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/transaction_base.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/transaction_focus (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/transaction_focus.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/transactor (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/transactor.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/types (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/types.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/util (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/util.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/version (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/version.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/zview (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/include/pqxx/zview.hxx (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/lib/cmake/libpqxx/libpqxx-config-version.cmake (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/lib/cmake/libpqxx/libpqxx-config.cmake (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/lib/cmake/libpqxx/libpqxx-targets.cmake (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/lib/libpqxx-7.7.a (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/lib/libpqxx.a (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/share/doc/libpqxx/accessing-results.md (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/share/doc/libpqxx/binary-data.md (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/share/doc/libpqxx/datatypes.md (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/share/doc/libpqxx/escaping.md (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/share/doc/libpqxx/getting-started.md (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/share/doc/libpqxx/mainpage.md (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/share/doc/libpqxx/parameters.md (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/share/doc/libpqxx/performance.md (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/share/doc/libpqxx/prepared-statement.md (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/share/doc/libpqxx/streams.md (100%) rename ext/libpqxx-7.7.3/install/ubuntu22.04/{ => amd64}/share/doc/libpqxx/thread-safety.md (100%) create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/config-public-compiler.h create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/array-composite.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/callgate.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/concat.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/conversions.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encoding_group.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encodings.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-errorhandler.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-largeobject.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-notification_receiver.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-pipeline.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-sql_cursor.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_from.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_to.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-transaction.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/errorhandler-connection.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-connection.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-creation.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-pipeline.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-sql_cursor.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-sql_cursor.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-transaction_focus.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-post.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-pre.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-post.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-pre.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/libpq-forward.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iter.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iterator.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/sql_cursor.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/statement_parameters.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/stream_iterator.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/wait.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pqxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview.hxx create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config-version.cmake create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config.cmake create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets.cmake create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/libpqxx-7.7.a create mode 120000 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/libpqxx.a create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/accessing-results.md create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/binary-data.md create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/datatypes.md create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/escaping.md create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/getting-started.md create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/mainpage.md create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/parameters.md create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/performance.md create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/prepared-statement.md create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/streams.md create mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/thread-safety.md delete mode 100644 ext/libpqxx-7.7.3/install/ubuntu22.04/lib/pkgconfig/libpqxx.pc rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/cmd_formatter.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/command.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/command_args.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/command_options.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/connection.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/connection_pool.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/cxx_utils.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/errors.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/pipeline.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/queued_redis.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/queued_redis.hpp (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/redis++.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/redis.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/redis.hpp (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/redis_cluster.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/redis_cluster.hpp (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/reply.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/sentinel.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/shards.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/shards_pool.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/subscriber.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/tls.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/transaction.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/include/sw/redis++/utils.h (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/lib/libredis++.a (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/lib/pkgconfig/redis++.pc (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/share/cmake/redis++/redis++-config-version.cmake (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/share/cmake/redis++/redis++-config.cmake (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/share/cmake/redis++/redis++-targets-release.cmake (100%) rename ext/redis-plus-plus-1.3.3/install/ubuntu22.04/{ => amd64}/share/cmake/redis++/redis++-targets.cmake (100%) create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/cmd_formatter.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command_args.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command_options.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/connection.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/connection_pool.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/cxx_utils.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/errors.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/pipeline.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/queued_redis.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/queued_redis.hpp create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis++.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis.hpp create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis_cluster.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis_cluster.hpp create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/reply.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/sentinel.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/shards.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/shards_pool.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/subscriber.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/tls.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/transaction.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/utils.h create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/lib/libredis++.a create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/lib/pkgconfig/redis++.pc create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-config-version.cmake create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-config.cmake create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-targets-release.cmake create mode 100644 ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-targets.cmake diff --git a/ext/central-controller-docker/Dockerfile b/ext/central-controller-docker/Dockerfile index c59aa8f5d..9621b5d38 100644 --- a/ext/central-controller-docker/Dockerfile +++ b/ext/central-controller-docker/Dockerfile @@ -1,10 +1,10 @@ # Dockerfile for ZeroTier Central Controllers -FROM registry.zerotier.com/zerotier/controller-builder:latest as builder +FROM us-central1-docker.pkg.dev/zerotier-central/zerotier/ctlbuild:latest as builder MAINTAINER Adam Ierymekno , Grant Limberg ADD . /ZeroTierOne RUN export PATH=$PATH:~/.cargo/bin && cd ZeroTierOne && make clean && make central-controller -j8 -FROM registry.zerotier.com/zerotier/controller-run:latest +FROM us-central1-docker.pkg.dev/zerotier-central/zerotier/ctlrun:latest COPY --from=builder /ZeroTierOne/zerotier-one /usr/local/bin/zerotier-one RUN chmod a+x /usr/local/bin/zerotier-one RUN echo "/usr/local/lib64" > /etc/ld.so.conf.d/usr-local-lib64.conf && ldconfig diff --git a/ext/central-controller-docker/Makefile b/ext/central-controller-docker/Makefile new file mode 100644 index 000000000..27b3610e0 --- /dev/null +++ b/ext/central-controller-docker/Makefile @@ -0,0 +1,16 @@ +registry = us-central1-docker.pkg.dev/zerotier-central/zerotier + +all: controller-builder controller-runbase + +buildx: + @echo "docker buildx create" + # docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + docker run --privileged --rm tonistiigi/binfmt --install all + @echo docker buildx create --name multiarch --driver docker-container --use + @echo docker buildx inspect --bootstrap + +controller-builder: buildx + docker buildx build --no-cache --platform linux/amd64,linux/arm64 -t $(registry)/ctlbuild:latest -f Dockerfile.builder . --push + +controller-runbase: buildx + docker buildx build --no-cache --platform linux/amd64,linux/arm64 -t $(registry)/ctlrun:latest -f Dockerfile.run_base . --push diff --git a/ext/hiredis-1.0.2/lib/ubuntu22.04/libhiredis.a b/ext/hiredis-1.0.2/lib/ubuntu22.04/amd64/libhiredis.a similarity index 100% rename from ext/hiredis-1.0.2/lib/ubuntu22.04/libhiredis.a rename to ext/hiredis-1.0.2/lib/ubuntu22.04/amd64/libhiredis.a diff --git a/ext/hiredis-1.0.2/lib/ubuntu22.04/arm64/libhiredis.a b/ext/hiredis-1.0.2/lib/ubuntu22.04/arm64/libhiredis.a new file mode 100644 index 0000000000000000000000000000000000000000..081c92705dfdb5a83b5768db78d88f07d002b8c0 GIT binary patch literal 495416 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 PZ{Ys3qgU?J`^x_hY1ET< literal 0 HcmV?d00001 diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/array b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/array similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/array rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/array diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/array.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/array.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/array.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/array.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/binarystring b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/binarystring similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/binarystring rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/binarystring diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/binarystring.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/binarystring.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/binarystring.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/binarystring.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/blob b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/blob similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/blob rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/blob diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/blob.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/blob.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/blob.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/blob.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/composite b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/composite similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/composite rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/composite diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/composite.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/composite.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/composite.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/composite.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/config-public-compiler.h b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/config-public-compiler.h similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/config-public-compiler.h rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/config-public-compiler.h diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/connection b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/connection similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/connection rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/connection diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/connection.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/connection.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/connection.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/cursor b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/cursor similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/cursor rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/cursor diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/cursor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/cursor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/cursor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/dbtransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/dbtransaction similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/dbtransaction rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/dbtransaction diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/dbtransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/dbtransaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/dbtransaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/dbtransaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/errorhandler b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/errorhandler similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/errorhandler rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/errorhandler diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/errorhandler.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/errorhandler.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/errorhandler.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/errorhandler.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/except b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/except similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/except rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/except diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/except.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/except.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/except.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/except.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/field b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/field similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/field rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/field diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/field.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/field.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/field.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/field.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/array-composite.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/array-composite.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/array-composite.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/array-composite.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/callgate.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/callgate.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/callgate.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/callgate.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/concat.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/concat.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/concat.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/concat.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/conversions.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/conversions.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/conversions.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/conversions.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/encoding_group.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/encoding_group.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/encoding_group.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/encoding_group.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/encodings.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/encodings.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/encodings.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/encodings.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-errorhandler.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-errorhandler.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-errorhandler.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-errorhandler.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-largeobject.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-largeobject.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-largeobject.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-largeobject.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-notification_receiver.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-notification_receiver.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-notification_receiver.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-notification_receiver.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-pipeline.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-pipeline.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-pipeline.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-sql_cursor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-sql_cursor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-sql_cursor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-stream_from.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-stream_from.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-stream_from.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-stream_from.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-stream_to.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-stream_to.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-stream_to.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-stream_to.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-transaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-transaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-transaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-transaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/errorhandler-connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/errorhandler-connection.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/errorhandler-connection.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/errorhandler-connection.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-connection.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-connection.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-connection.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-creation.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-creation.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-creation.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-creation.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-pipeline.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-pipeline.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-pipeline.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-sql_cursor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-sql_cursor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-sql_cursor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/transaction-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/transaction-sql_cursor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/transaction-sql_cursor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/transaction-sql_cursor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/transaction-transaction_focus.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/transaction-transaction_focus.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/transaction-transaction_focus.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/transaction-transaction_focus.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/header-post.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/header-post.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/header-post.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/header-post.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/header-pre.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/header-pre.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/header-pre.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/header-pre.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/ignore-deprecated-post.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/ignore-deprecated-post.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/ignore-deprecated-post.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/ignore-deprecated-post.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/ignore-deprecated-pre.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/ignore-deprecated-pre.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/ignore-deprecated-pre.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/ignore-deprecated-pre.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/libpq-forward.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/libpq-forward.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/libpq-forward.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/libpq-forward.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/result_iter.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/result_iter.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/result_iter.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/result_iter.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/result_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/result_iterator.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/result_iterator.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/result_iterator.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/sql_cursor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/sql_cursor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/sql_cursor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/statement_parameters.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/statement_parameters.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/statement_parameters.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/statement_parameters.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/stream_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/stream_iterator.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/stream_iterator.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/stream_iterator.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/wait.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/wait.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/wait.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/wait.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/isolation b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/isolation similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/isolation rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/isolation diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/isolation.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/isolation.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/isolation.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/isolation.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/largeobject b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/largeobject similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/largeobject rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/largeobject diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/largeobject.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/largeobject.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/largeobject.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/largeobject.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/nontransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/nontransaction similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/nontransaction rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/nontransaction diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/nontransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/nontransaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/nontransaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/nontransaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/notification b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/notification similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/notification rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/notification diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/notification.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/notification.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/notification.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/notification.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/params b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/params similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/params rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/params diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/params.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/params.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/params.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/params.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pipeline b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pipeline similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pipeline rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pipeline diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pipeline.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pipeline.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pipeline.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pqxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pqxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pqxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pqxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/prepared_statement b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/prepared_statement similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/prepared_statement rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/prepared_statement diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/prepared_statement.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/prepared_statement.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/prepared_statement.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/prepared_statement.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/range b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/range similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/range rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/range diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/range.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/range.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/range.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/range.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/result b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/result similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/result rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/result diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/result.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/result.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/result.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/result.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/robusttransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/robusttransaction similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/robusttransaction rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/robusttransaction diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/robusttransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/robusttransaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/robusttransaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/robusttransaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/row b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/row rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/row.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/row.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/separated_list b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/separated_list similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/separated_list rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/separated_list diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/separated_list.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/separated_list.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/separated_list.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/separated_list.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/strconv b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/strconv similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/strconv rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/strconv diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/strconv.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/strconv.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/strconv.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/strconv.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_from b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_from similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_from rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_from diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_from.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_from.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_from.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_from.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_to b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_to similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_to rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_to diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_to.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_to.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_to.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_to.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/subtransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/subtransaction similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/subtransaction rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/subtransaction diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/subtransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/subtransaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/subtransaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/subtransaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/time b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/time similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/time rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/time diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/time.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/time.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/time.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/time.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_base b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_base similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_base rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_base diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_base.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_base.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_base.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_base.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_focus b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_focus similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_focus rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_focus diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_focus.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_focus.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_focus.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_focus.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transactor b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transactor similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transactor rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transactor diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transactor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transactor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transactor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transactor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/types b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/types similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/types rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/types diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/types.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/types.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/types.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/types.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/util b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/util similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/util rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/util diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/util.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/util.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/util.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/util.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/version b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/version similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/version rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/version diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/version.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/version.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/version.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/version.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/zview b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/zview similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/zview rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/zview diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/zview.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/zview.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/zview.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/zview.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-config-version.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-config-version.cmake similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-config-version.cmake rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-config-version.cmake diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-config.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-config.cmake similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-config.cmake rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-config.cmake diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-targets.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-targets.cmake similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-targets.cmake rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-targets.cmake diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/libpqxx-7.7.a b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/libpqxx-7.7.a similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/libpqxx-7.7.a rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/libpqxx-7.7.a diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/libpqxx.a b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/libpqxx.a similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/libpqxx.a rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/libpqxx.a diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/accessing-results.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/accessing-results.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/accessing-results.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/accessing-results.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/binary-data.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/binary-data.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/binary-data.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/binary-data.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/datatypes.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/datatypes.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/datatypes.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/datatypes.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/escaping.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/escaping.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/escaping.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/escaping.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/getting-started.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/getting-started.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/getting-started.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/getting-started.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/mainpage.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/mainpage.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/mainpage.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/mainpage.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/parameters.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/parameters.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/parameters.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/parameters.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/performance.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/performance.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/performance.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/performance.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/prepared-statement.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/prepared-statement.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/prepared-statement.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/prepared-statement.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/streams.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/streams.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/streams.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/streams.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/thread-safety.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/thread-safety.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/thread-safety.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/thread-safety.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array new file mode 100644 index 000000000..689f5b27b --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array @@ -0,0 +1,6 @@ +/** Handling of SQL arrays. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/array.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array.hxx new file mode 100644 index 000000000..8440a244f --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array.hxx @@ -0,0 +1,103 @@ +/* Handling of SQL arrays. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/field instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ARRAY +#define PQXX_H_ARRAY + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include + +#include "pqxx/internal/encoding_group.hxx" +#include "pqxx/internal/encodings.hxx" + + +namespace pqxx +{ +/// Low-level array parser. +/** Use this to read an array field retrieved from the database. + * + * This parser will only work reliably if your client encoding is UTF-8, ASCII, + * or a single-byte encoding which is a superset of ASCII (such as Latin-1). + * + * Also, the parser only supports array element types which use either a comma + * or a semicolon ("," or ";") as the separator between array elements. All + * built-in types use comma, except for one which uses semicolon, but some + * custom types may not work. + * + * The input is a C-style string containing the textual representation of an + * array, as returned by the database. The parser reads this representation + * on the fly. The string must remain in memory until parsing is done. + * + * Parse the array by making calls to @ref get_next until it returns a + * @ref juncture of "done". The @ref juncture tells you what the parser found + * in that step: did the array "nest" to a deeper level, or "un-nest" back up? + */ +class PQXX_LIBEXPORT array_parser +{ +public: + /// What's the latest thing found in the array? + enum class juncture + { + /// Starting a new row. + row_start, + /// Ending the current row. + row_end, + /// Found a NULL value. + null_value, + /// Found a string value. + string_value, + /// Parsing has completed. + done, + }; + + // TODO: constexpr noexcept. Breaks ABI. + /// Constructor. You don't need this; use @ref field::as_array instead. + /** The parser only remains valid while the data underlying the @ref result + * remains valid. Once all `result` objects referring to that data have been + * destroyed, the parser will no longer refer to valid memory. + */ + explicit array_parser( + std::string_view input, + internal::encoding_group = internal::encoding_group::MONOBYTE); + + /// Parse the next step in the array. + /** Returns what it found. If the juncture is @ref juncture::string_value, + * the string will contain the value. Otherwise, it will be empty. + * + * Call this until the @ref array_parser::juncture it returns is + * @ref juncture::done. + */ + std::pair get_next(); + +private: + std::string_view m_input; + internal::glyph_scanner_func *const m_scan; + + /// Current parsing position in the input. + std::string::size_type m_pos = 0u; + + std::string::size_type scan_single_quoted_string() const; + std::string parse_single_quoted_string(std::string::size_type end) const; + std::string::size_type scan_double_quoted_string() const; + std::string parse_double_quoted_string(std::string::size_type end) const; + std::string::size_type scan_unquoted_string() const; + std::string parse_unquoted_string(std::string::size_type end) const; + + std::string::size_type scan_glyph(std::string::size_type pos) const; + std::string::size_type + scan_glyph(std::string::size_type pos, std::string::size_type end) const; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring new file mode 100644 index 000000000..77551d9f7 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring @@ -0,0 +1,6 @@ +/** BYTEA (binary string) conversions. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/binarystring.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring.hxx new file mode 100644 index 000000000..47c82a035 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring.hxx @@ -0,0 +1,236 @@ +/* Deprecated representation for raw, binary data. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/binarystring instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_BINARYSTRING +#define PQXX_H_BINARYSTRING + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include + +#include "pqxx/result.hxx" +#include "pqxx/strconv.hxx" + +namespace pqxx +{ +class binarystring; +template<> struct string_traits; + + +/// Binary data corresponding to PostgreSQL's "BYTEA" binary-string type. +/** @ingroup escaping-functions + * @deprecated Use @c std::basic_string and + * @c std::basic_string_view for binary data. In C++20 or better, + * any @c contiguous_range of @c std::byte will do. + * + * This class represents a binary string as stored in a field of type @c bytea. + * + * Internally a binarystring is zero-terminated, but it may also contain null + * bytes, they're just like any other byte value. So don't assume that it's + * safe to treat the contents as a C-style string. + * + * The binarystring retains its value even if the result it was obtained from + * is destroyed, but it cannot be copied or assigned. + * + * \relatesalso transaction_base::quote_raw + * + * To include a @c binarystring value in an SQL query, escape and quote it + * using the transaction's @c quote_raw function. + * + * @warning This class is implemented as a reference-counting smart pointer. + * Copying, swapping, and destroying binarystring objects that refer to the + * same underlying data block is not thread-safe. If you wish to pass + * binarystrings around between threads, make sure that each of these + * operations is protected against concurrency with similar operations on the + * same object, or other objects pointing to the same data block. + */ +class PQXX_LIBEXPORT binarystring +{ +public: + using char_type = unsigned char; + using value_type = std::char_traits::char_type; + using size_type = std::size_t; + using difference_type = long; + using const_reference = value_type const &; + using const_pointer = value_type const *; + using const_iterator = const_pointer; + using const_reverse_iterator = std::reverse_iterator; + + [[deprecated("Use std::byte for binary data.")]] binarystring( + binarystring const &) = default; + + /// Read and unescape bytea field. + /** The field will be zero-terminated, even if the original bytea field + * isn't. + * @param F the field to read; must be a bytea field + */ + [[deprecated("Use std::byte for binary data.")]] explicit binarystring( + field const &); + + /// Copy binary data from std::string_view on binary data. + /** This is inefficient in that it copies the data to a buffer allocated on + * the heap. + */ + [[deprecated("Use std::byte for binary data.")]] explicit binarystring( + std::string_view); + + /// Copy binary data of given length straight out of memory. + [[deprecated("Use std::byte for binary data.")]] binarystring( + void const *, std::size_t); + + /// Efficiently wrap a buffer of binary data in a @c binarystring. + [[deprecated("Use std::byte for binary data.")]] binarystring( + std::shared_ptr ptr, size_type size) : + m_buf{std::move(ptr)}, m_size{size} + {} + + /// Size of converted string in bytes. + [[nodiscard]] size_type size() const noexcept { return m_size; } + /// Size of converted string in bytes. + [[nodiscard]] size_type length() const noexcept { return size(); } + [[nodiscard]] bool empty() const noexcept { return size() == 0; } + + [[nodiscard]] const_iterator begin() const noexcept { return data(); } + [[nodiscard]] const_iterator cbegin() const noexcept { return begin(); } + [[nodiscard]] const_iterator end() const noexcept { return data() + m_size; } + [[nodiscard]] const_iterator cend() const noexcept { return end(); } + + [[nodiscard]] const_reference front() const noexcept { return *begin(); } + [[nodiscard]] const_reference back() const noexcept + { + return *(data() + m_size - 1); + } + + [[nodiscard]] const_reverse_iterator rbegin() const + { + return const_reverse_iterator{end()}; + } + [[nodiscard]] const_reverse_iterator crbegin() const { return rbegin(); } + [[nodiscard]] const_reverse_iterator rend() const + { + return const_reverse_iterator{begin()}; + } + [[nodiscard]] const_reverse_iterator crend() const { return rend(); } + + /// Unescaped field contents. + [[nodiscard]] value_type const *data() const noexcept { return m_buf.get(); } + + [[nodiscard]] const_reference operator[](size_type i) const noexcept + { + return data()[i]; + } + + [[nodiscard]] PQXX_PURE bool operator==(binarystring const &) const noexcept; + [[nodiscard]] bool operator!=(binarystring const &rhs) const noexcept + { + return not operator==(rhs); + } + + binarystring &operator=(binarystring const &); + + /// Index contained string, checking for valid index. + const_reference at(size_type) const; + + /// Swap contents with other binarystring. + void swap(binarystring &); + + /// Raw character buffer (no terminating zero is added). + /** @warning No terminating zero is added! If the binary data did not end in + * a null character, you will not find one here. + */ + [[nodiscard]] char const *get() const noexcept + { + return reinterpret_cast(m_buf.get()); + } + + /// Read contents as a std::string_view. + [[nodiscard]] std::string_view view() const noexcept + { + return std::string_view(get(), size()); + } + + /// Read as regular C++ string (may include null characters). + /** This creates and returns a new string object. Don't call this + * repeatedly; retrieve your string once and keep it in a local variable. + * Also, do not expect to be able to compare the string's address to that of + * an earlier invocation. + */ + [[nodiscard]] std::string str() const; + + /// Access data as a pointer to @c std::byte. + [[nodiscard]] std::byte const *bytes() const + { + return reinterpret_cast(get()); + } + + /// Read data as a @c std::basic_string_view. + [[nodiscard]] std::basic_string_view bytes_view() const + { + return std::basic_string_view{bytes(), size()}; + } + +private: + std::shared_ptr m_buf; + size_type m_size{0}; +}; + + +template<> struct nullness : no_null +{}; + + +/// String conversion traits for @c binarystring. +/** Defines the conversions between a @c binarystring and its PostgreSQL + * textual format, for communication with the database. + * + * These conversions rely on the "hex" format which was introduced in + * PostgreSQL 9.0. Both your libpq and the server must be recent enough to + * speak this format. + */ +template<> struct string_traits +{ + static std::size_t size_buffer(binarystring const &value) noexcept + { + return internal::size_esc_bin(std::size(value)); + } + + static zview to_buf(char *begin, char *end, binarystring const &value) + { + return generic_to_buf(begin, end, value); + } + + static char *into_buf(char *begin, char *end, binarystring const &value) + { + auto const budget{size_buffer(value)}; + if (internal::cmp_less(end - begin, budget)) + throw conversion_overrun{ + "Not enough buffer space to escape binary data."}; + std::string_view text{value.view()}; + internal::esc_bin(binary_cast(text), begin); + return begin + budget; + } + + static binarystring from_string(std::string_view text) + { + auto const size{pqxx::internal::size_unesc_bin(std::size(text))}; + std::shared_ptr buf{ + new unsigned char[size], [](unsigned char const *x) { delete[] x; }}; + pqxx::internal::unesc_bin(text, reinterpret_cast(buf.get())); +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return binarystring{std::move(buf), size}; +#include "pqxx/internal/ignore-deprecated-post.hxx" + } +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob new file mode 100644 index 000000000..3fd0afac9 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob @@ -0,0 +1,6 @@ +/** Binary Large Objects interface. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/blob.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob.hxx new file mode 100644 index 000000000..6d77be724 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob.hxx @@ -0,0 +1,351 @@ +/* Binary Large Objects interface. + * + * Read or write large objects, stored in their own storage on the server. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/largeobject instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_BLOB +#define PQXX_H_BLOB + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#if defined(PQXX_HAVE_PATH) +# include +#endif + +#if defined(PQXX_HAVE_RANGES) && __has_include() +# include +#endif + +#if defined(PQXX_HAVE_SPAN) && __has_include() +# include +#endif + +#include "pqxx/dbtransaction.hxx" + + +namespace pqxx +{ +/** Binary large object. + * + * This is how you store data that may be too large for the `BYTEA` type. + * Access operations are similar to those for a file: you can read, write, + * query or set the current reading/writing position, and so on. + * + * These large objects live in their own storage on the server, indexed by an + * integer object identifier ("oid"). + * + * Two `blob` objects may refer to the same actual large object in the + * database at the same time. Each will have its own reading/writing position, + * but writes to the one will of course affect what the other sees. + */ +class PQXX_LIBEXPORT blob +{ +public: + /// Create a new, empty large object. + /** You may optionally specify an oid for the new blob. If you do, then + * the new object will have that oid -- or creation will fail if there + * already is an object with that oid. + */ + [[nodiscard]] static oid create(dbtransaction &, oid = 0); + + /// Delete a large object, or fail if it does not exist. + static void remove(dbtransaction &, oid); + + /// Open blob for reading. Any attempt to write to it will fail. + [[nodiscard]] static blob open_r(dbtransaction &, oid); + // Open blob for writing. Any attempt to read from it will fail. + [[nodiscard]] static blob open_w(dbtransaction &, oid); + // Open blob for reading and/or writing. + [[nodiscard]] static blob open_rw(dbtransaction &, oid); + + /// You can default-construct a blob, but it won't do anything useful. + /** Most operations on a default-constructed blob will throw @ref + * usage_error. + */ + blob() = default; + + /// You can move a blob, but not copy it. The original becomes unusable. + blob(blob &&); + /// You can move a blob, but not copy it. The original becomes unusable. + blob &operator=(blob &&); + + blob(blob const &) = delete; + blob &operator=(blob const &) = delete; + ~blob(); + + /// Maximum number of bytes that can be read or written at a time. + /** The underlying protocol only supports reads and writes up to 2 GB + * exclusive. + * + * If you need to read or write more data to or from a binary large object, + * you'll have to break it up into chunks. + */ + static constexpr std::size_t chunk_limit = 0x7fffffff; + + /// Read up to `size` bytes of the object into `buf`. + /** Uses a buffer that you provide, resizing it as needed. If it suits you, + * this lets you allocate the buffer once and then re-use it multiple times. + * + * Resizes `buf` as needed. + * + * @warning The underlying protocol only supports reads up to 2GB at a time. + * If you need to read more, try making repeated calls to @ref append_to_buf. + */ + std::size_t read(std::basic_string &buf, std::size_t size); + +#if defined(PQXX_HAVE_SPAN) + /// Read up to `std::size(buf)` bytes from the object. + /** Retrieves bytes from the blob, at the current position, until `buf` is + * full or there are no more bytes to read, whichever comes first. + * + * Returns the filled portion of `buf`. This may be empty. + */ + template + std::span read(std::span buf) + { + return buf.subspan(0, raw_read(std::data(buf), std::size(buf))); + } +#endif // PQXX_HAVE_SPAN + +#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) + /// Read up to `std::size(buf)` bytes from the object. + /** Retrieves bytes from the blob, at the current position, until `buf` is + * full or there are no more bytes to read, whichever comes first. + * + * Returns the filled portion of `buf`. This may be empty. + */ + template std::span read(DATA &buf) + { + return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; + } +#else // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN + /// Read up to `std::size(buf)` bytes from the object. + /** @deprecated As libpqxx moves to C++20 as its baseline language version, + * this will take and return `std::span`. + * + * Retrieves bytes from the blob, at the current position, until `buf` is + * full (i.e. its current size is reached), or there are no more bytes to + * read, whichever comes first. + * + * This function will not change either the size or the capacity of `buf`, + * only its contents. + * + * Returns the filled portion of `buf`. This may be empty. + */ + template + std::basic_string_view read(std::vector &buf) + { + return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; + } +#endif // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN + +#if defined(PQXX_HAVE_CONCEPTS) + /// Write `data` to large object, at the current position. + /** If the writing position is at the end of the object, this will append + * `data` to the object's contents and move the writing position so that + * it's still at the end. + * + * If the writing position was not at the end, writing will overwrite the + * prior data, but it will not remove data that follows the part where you + * wrote your new data. + * + * @warning This is a big difference from writing to a file. You can + * overwrite some data in a large object, but this does not truncate the + * data that was already there. For example, if the object contained binary + * data "abc", and you write "12" at the starting position, the object will + * contain "12c". + * + * @warning The underlying protocol only supports writes up to 2 GB at a + * time. If you need to write more, try making repeated calls to + * @ref append_from_buf. + */ + template void write(DATA const &data) + { + raw_write(std::data(data), std::size(data)); + } +#else + /// Write `data` large object, at the current position. + /** If the writing position is at the end of the object, this will append + * `data` to the object's contents and move the writing position so that + * it's still at the end. + * + * If the writing position was not at the end, writing will overwrite the + * prior data, but it will not remove data that follows the part where you + * wrote your new data. + * + * @warning This is a big difference from writing to a file. You can + * overwrite some data in a large object, but this does not truncate the + * data that was already there. For example, if the object contained binary + * data "abc", and you write "12" at the starting position, the object will + * contain "12c". + * + * @warning The underlying protocol only supports writes up to 2 GB at a + * time. If you need to write more, try making repeated calls to + * @ref append_from_buf. + */ + template void write(DATA const &data) + { + raw_write(std::data(data), std::size(data)); + } +#endif + + /// Resize large object to `size` bytes. + /** If the blob is more than `size` bytes long, this removes the end so as + * to make the blob the desired length. + * + * If the blob is less than `size` bytes long, it adds enough zero bytes to + * make it the desired length. + */ + void resize(std::int64_t size); + + /// Return the current reading/writing position in the large object. + [[nodiscard]] std::int64_t tell() const; + + /// Set the current reading/writing position to an absolute offset. + /** Returns the new file offset. */ + std::int64_t seek_abs(std::int64_t offset = 0); + /// Move the current reading/writing position forwards by an offset. + /** To move backwards, pass a negative offset. + * + * Returns the new file offset. + */ + std::int64_t seek_rel(std::int64_t offset = 0); + /// Set the current position to an offset relative to the end of the blob. + /** You'll probably want an offset of zero or less. + * + * Returns the new file offset. + */ + std::int64_t seek_end(std::int64_t offset = 0); + + /// Create a binary large object containing given `data`. + /** You may optionally specify an oid for the new object. If you do, and an + * object with that oid already exists, creation will fail. + */ + static oid from_buf( + dbtransaction &tx, std::basic_string_view data, oid id = 0); + + /// Append `data` to binary large object. + /** The underlying protocol only supports appending blocks up to 2 GB. + */ + static void append_from_buf( + dbtransaction &tx, std::basic_string_view data, oid id); + + /// Read client-side file and store it server-side as a binary large object. + [[nodiscard]] static oid from_file(dbtransaction &, char const path[]); + +#if defined(PQXX_HAVE_PATH) && !defined(_WIN32) + /// Read client-side file and store it server-side as a binary large object. + /** This overload is not available on Windows, where `std::filesystem::path` + * converts to a `wchar_t` string rather than a `char` string. + */ + [[nodiscard]] static oid + from_file(dbtransaction &tx, std::filesystem::path const &path) + { + return from_file(tx, path.c_str()); + } +#endif + + /// Read client-side file and store it server-side as a binary large object. + /** In this version, you specify the binary large object's oid. If that oid + * is already in use, the operation will fail. + */ + static oid from_file(dbtransaction &, char const path[], oid); + +#if defined(PQXX_HAVE_PATH) && !defined(_WIN32) + /// Read client-side file and store it server-side as a binary large object. + /** In this version, you specify the binary large object's oid. If that oid + * is already in use, the operation will fail. + * + * This overload is not available on Windows, where `std::filesystem::path` + * converts to a `wchar_t` string rather than a `char` string. + */ + static oid + from_file(dbtransaction &tx, std::filesystem::path const &path, oid id) + { + return from_file(tx, path.c_str(), id); + } +#endif + + /// Convenience function: Read up to `max_size` bytes from blob with `id`. + /** You could easily do this yourself using the @ref open_r and @ref read + * functions, but it can save you a bit of code to do it this way. + */ + static void to_buf( + dbtransaction &, oid, std::basic_string &, + std::size_t max_size); + + /// Read part of the binary large object with `id`, and append it to `buf`. + /** Use this to break up a large read from one binary large object into one + * massive buffer. Just keep calling this function until it returns zero. + * + * The `offset` is how far into the large object your desired chunk is, and + * `append_max` says how much to try and read in one go. + */ + static std::size_t append_to_buf( + dbtransaction &tx, oid id, std::int64_t offset, + std::basic_string &buf, std::size_t append_max); + + /// Write a binary large object's contents to a client-side file. + static void to_file(dbtransaction &, oid, char const path[]); + +#if defined(PQXX_HAVE_PATH) && !defined(_WIN32) + /// Write a binary large object's contents to a client-side file. + /** This overload is not available on Windows, where `std::filesystem::path` + * converts to a `wchar_t` string rather than a `char` string. + */ + static void + to_file(dbtransaction &tx, oid id, std::filesystem::path const &path) + { + to_file(tx, id, path.c_str()); + } +#endif + + /// Close this blob. + /** This does not delete the blob from the database; it only terminates your + * local object for accessing the blob. + * + * Resets the blob to a useless state similar to one that was + * default-constructed. + * + * The destructor will do this for you automatically. Still, there is a + * reason to `close()` objects explicitly where possible: if an error should + * occur while closing, `close()` can throw an exception. A destructor + * cannot. + */ + void close(); + +private: + PQXX_PRIVATE blob(connection &conn, int fd) noexcept : + m_conn{&conn}, m_fd{fd} + {} + static PQXX_PRIVATE blob open_internal(dbtransaction &, oid, int); + static PQXX_PRIVATE pqxx::internal::pq::PGconn * + raw_conn(pqxx::connection *) noexcept; + static PQXX_PRIVATE pqxx::internal::pq::PGconn * + raw_conn(pqxx::dbtransaction const &) noexcept; + static PQXX_PRIVATE std::string errmsg(connection const *); + static PQXX_PRIVATE std::string errmsg(dbtransaction const &tx) + { + return errmsg(&tx.conn()); + } + PQXX_PRIVATE std::string errmsg() const { return errmsg(m_conn); } + PQXX_PRIVATE std::int64_t seek(std::int64_t offset, int whence); + std::size_t raw_read(std::byte buf[], std::size_t size); + void raw_write(std::byte const buf[], std::size_t size); + + connection *m_conn = nullptr; + int m_fd = -1; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite new file mode 100644 index 000000000..2bfa7ade9 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite @@ -0,0 +1,6 @@ +/** Handling of SQL "composite types." + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/composite.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite.hxx new file mode 100644 index 000000000..439b133a8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite.hxx @@ -0,0 +1,149 @@ +#ifndef PQXX_H_COMPOSITE +#define PQXX_H_COMPOSITE + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/internal/array-composite.hxx" +#include "pqxx/internal/concat.hxx" +#include "pqxx/util.hxx" + +namespace pqxx +{ +/// Parse a string representation of a value of a composite type. +/** @warning This code is still experimental. Use with care. + * + * You may use this as a helper while implementing your own @ref string_traits + * for a composite type. + * + * This function interprets `text` as the string representation of a value of + * some composite type, and sets each of `fields` to the respective values of + * its fields. The field types must be copy-assignable. + * + * The number of fields must match the number of fields in the composite type, + * and there must not be any other text in the input. The function is meant to + * handle any value string that the backend can produce, but not necessarily + * every valid alternative spelling. + * + * Fields in composite types can be null. When this happens, the C++ type of + * the corresponding field reference must be of a type that can handle nulls. + * If you are working with a type that does not have an inherent null value, + * such as e.g. `int`, consider using `std::optional`. + */ +template +inline void parse_composite( + pqxx::internal::encoding_group enc, std::string_view text, T &...fields) +{ + static_assert(sizeof...(fields) > 0); + + auto const scan{pqxx::internal::get_glyph_scanner(enc)}; + auto const data{std::data(text)}; + auto const size{std::size(text)}; + if (size == 0) + throw conversion_error{"Cannot parse composite value from empty string."}; + + std::size_t here{0}, next{scan(data, size, here)}; + if (next != 1 or data[here] != '(') + throw conversion_error{ + internal::concat("Invalid composite value string: ", text)}; + + here = next; + + constexpr auto num_fields{sizeof...(fields)}; + std::size_t index{0}; + (pqxx::internal::parse_composite_field( + index, text, here, fields, scan, num_fields - 1), + ...); + if (here != std::size(text)) + throw conversion_error{internal::concat( + "Composite value did not end at the closing parenthesis: '", text, + "'.")}; + if (text[here - 1] != ')') + throw conversion_error{internal::concat( + "Composive value did not end in parenthesis: '", text, "'")}; +} + + +/// Parse a string representation of a value of a composite type. +/** @warning This version only works for UTF-8 and single-byte encodings. + * + * For proper encoding support, use the composite-type support in the + * `field` class. + */ +template +inline void parse_composite(std::string_view text, T &...fields) +{ + parse_composite(pqxx::internal::encoding_group::MONOBYTE, text, fields...); +} +} // namespace pqxx + + +namespace pqxx::internal +{ +constexpr char empty_composite_str[]{"()"}; +} // namespace pqxx::internal + + +namespace pqxx +{ +/// Estimate the buffer size needed to represent a value of a composite type. +/** Returns a conservative estimate. + */ +template +[[nodiscard]] inline std::size_t +composite_size_buffer(T const &...fields) noexcept +{ + constexpr auto num{sizeof...(fields)}; + + // Size for a multi-field composite includes room for... + // + opening parenthesis + // + field budgets + // + separating comma per field + // - comma after final field + // + closing parenthesis + // + terminating zero + + if constexpr (num == 0) + return std::size(pqxx::internal::empty_composite_str); + else + return 1 + (pqxx::internal::size_composite_field_buffer(fields) + ...) + + num + 1; +} + + +/// Render a series of values as a single composite SQL value. +/** @warning This code is still experimental. Use with care. + * + * You may use this as a helper while implementing your own `string_traits` + * for a composite type. + */ +template +inline char *composite_into_buf(char *begin, char *end, T const &...fields) +{ + if (std::size_t(end - begin) < composite_size_buffer(fields...)) + throw conversion_error{ + "Buffer space may not be enough to represent composite value."}; + + constexpr auto num_fields{sizeof...(fields)}; + if constexpr (num_fields == 0) + { + constexpr char empty[]{"()"}; + std::memcpy(begin, empty, std::size(empty)); + return begin + std::size(empty); + } + + char *pos{begin}; + *pos++ = '('; + + (pqxx::internal::write_composite_field(pos, end, fields), ...); + + // If we've got multiple fields, "backspace" that last comma. + if constexpr (num_fields > 1) + --pos; + *pos++ = ')'; + *pos++ = '\0'; + return pos; +} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/config-public-compiler.h b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/config-public-compiler.h new file mode 100644 index 000000000..3668a10f8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/config-public-compiler.h @@ -0,0 +1,81 @@ +/* include/pqxx/config.h.in. Generated from configure.ac by autoheader. */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DLFCN_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_INTTYPES_H */ +/* Define to 1 if you have the `pq' library (-lpq). */ +/* #undef HAVE_LIBPQ */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MEMORY_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STDINT_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STDLIB_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STRINGS_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STRING_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_STAT_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_TYPES_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_UNISTD_H */ +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +/* #undef LT_OBJDIR */ +/* Name of package */ +/* #undef PACKAGE */ +/* Define to the address where bug reports for this package should be sent. */ +/* #undef PACKAGE_BUGREPORT */ +/* Define to the full name of this package. */ +/* #undef PACKAGE_NAME */ +/* Define to the full name and version of this package. */ +/* #undef PACKAGE_STRING */ +/* Define to the one symbol short name of this package. */ +/* #undef PACKAGE_TARNAME */ +/* Define to the home page for this package. */ +/* #undef PACKAGE_URL */ +/* Define to the version of this package. */ +/* #undef PACKAGE_VERSION */ +/* Define if supports floating-point conversion. */ +#define PQXX_HAVE_CHARCONV_FLOAT +/* Define if supports integer conversion. */ +#define PQXX_HAVE_CHARCONV_INT +/* Define if compiler has C++20 std::cmp_greater etc. */ +/* #undef PQXX_HAVE_CMP */ +/* Define if compiler supports Concepts and header. */ +/* #undef PQXX_HAVE_CONCEPTS */ +/* Define if compiler supports __cxa_demangle */ +#define PQXX_HAVE_CXA_DEMANGLE +/* Define if g++ supports pure attribute */ +#define PQXX_HAVE_GCC_PURE +/* Define if g++ supports visibility attribute. */ +#define PQXX_HAVE_GCC_VISIBILITY +/* Define if likely & unlikely work. */ +/* #undef PQXX_HAVE_LIKELY */ +/* Define if operator[] can take multiple arguments. */ +/* #undef PQXX_HAVE_MULTIDIMENSIONAL_SUBSCRIPT */ +/* Define if compiler has usable std::filesystem::path. */ +#define PQXX_HAVE_PATH +/* Define if poll() is available. */ +#define PQXX_HAVE_POLL +/* Define if libpq has PQencryptPasswordConn (since pg 10). */ +#define PQXX_HAVE_PQENCRYPTPASSWORDCONN +/* Define if libpq has pipeline mode (since pg 14). */ +#define PQXX_HAVE_PQ_PIPELINE +/* Define if std::this_thread::sleep_for works. */ +#define PQXX_HAVE_SLEEP_FOR +/* Define if compiler has std::span. */ +/* #undef PQXX_HAVE_SPAN */ +/* Define if strerror_r() is available. */ +#define PQXX_HAVE_STRERROR_R +/* Define if strerror_s() is available. */ +/* #undef PQXX_HAVE_STRERROR_S */ +/* Define if thread_local is fully supported. */ +#define PQXX_HAVE_THREAD_LOCAL +/* Define if std::chrono has year_month_day etc. */ +/* #undef PQXX_HAVE_YEAR_MONTH_DAY */ +/* Define to 1 if you have the ANSI C header files. */ +/* #undef STDC_HEADERS */ +/* Version number of package */ +/* #undef VERSION */ diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection new file mode 100644 index 000000000..82ff43aa5 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection @@ -0,0 +1,8 @@ +/** pqxx::connection class. + * + * pqxx::connection encapsulates a connection to a database. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/connection.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection.hxx new file mode 100644 index 000000000..92454bb47 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection.hxx @@ -0,0 +1,1261 @@ +/* Definition of the connection class. + * + * pqxx::connection encapsulates a connection to a database. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/connection instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_CONNECTION +#define PQXX_H_CONNECTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Double-check in order to suppress an overzealous Visual C++ warning (#418). +#if defined(PQXX_HAVE_CONCEPTS) && __has_include() +# include +#endif + +#include "pqxx/errorhandler.hxx" +#include "pqxx/except.hxx" +#include "pqxx/internal/concat.hxx" +#include "pqxx/params.hxx" +#include "pqxx/separated_list.hxx" +#include "pqxx/strconv.hxx" +#include "pqxx/types.hxx" +#include "pqxx/util.hxx" +#include "pqxx/zview.hxx" + + +/** + * @addtogroup connections + * + * Use of the libpqxx library starts here. + * + * Everything that can be done with a database through libpqxx must go through + * a @ref pqxx::connection object. It connects to a database when you create + * it, and it terminates that communication during destruction. + * + * Many things come together in this class. Handling of error and warning + * messages, for example, is defined by @ref pqxx::errorhandler objects in the + * context of a connection. Prepared statements are also defined here. + * + * When you connect to a database, you pass a connection string containing any + * parameters and options, such as the server address and the database name. + * + * These are identical to the ones in libpq, the C language binding upon which + * libpqxx itself is built: + * + * https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING + * + * There are also environment variables you can set to provide defaults, again + * as defined by libpq: + * + * https://www.postgresql.org/docs/current/libpq-envars.html + * + * You can also create a database connection _asynchronously_ using an + * intermediate @ref pqxx::connecting object. + */ + +namespace pqxx::internal +{ +class sql_cursor; + +#if defined(PQXX_HAVE_CONCEPTS) +/// Concept: T is a range of pairs of zero-terminated strings. +template +concept ZKey_ZValues = std::ranges::input_range and requires(T t) +{ + {std::cbegin(t)}; + { + std::get<0>(*std::cbegin(t)) + } -> ZString; + { + std::get<1>(*std::cbegin(t)) + } -> ZString; +} and std::tuple_size_v::value_type> +== 2; +#endif // PQXX_HAVE_CONCEPTS +} // namespace pqxx::internal + + +namespace pqxx::internal::gate +{ +class connection_dbtransaction; +class connection_errorhandler; +class connection_largeobject; +class connection_notification_receiver; +class connection_pipeline; +class connection_sql_cursor; +class connection_stream_from; +class connection_stream_to; +class connection_transaction; +class const_connection_largeobject; +} // namespace pqxx::internal::gate + + +namespace pqxx +{ +/// Representation of a PostgreSQL table path. +/** A "table path" consists of a table name, optionally prefixed by a schema + * name, which in turn is optionally prefixed by a database name. + * + * A minimal example of a table path would be `{mytable}`. But a table path + * may also take the forms `{myschema,mytable}` or + * `{mydb,myschema,mytable}`. + */ +using table_path = std::initializer_list; + + +/// Encrypt a password. @deprecated Use connection::encrypt_password instead. +[[nodiscard, + deprecated("Use connection::encrypt_password instead.")]] std::string + PQXX_LIBEXPORT + encrypt_password(char const user[], char const password[]); + +/// Encrypt password. @deprecated Use connection::encrypt_password instead. +[[nodiscard, + deprecated("Use connection::encrypt_password instead.")]] inline std::string +encrypt_password(zview user, zview password) +{ +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return encrypt_password(user.c_str(), password.c_str()); +#include "pqxx/internal/ignore-deprecated-post.hxx" +} + + +/// Error verbosity levels. +enum class error_verbosity : int +{ + // These values must match those in libpq's PGVerbosity enum. + terse = 0, + normal = 1, + verbose = 2 +}; + + +/// Connection to a database. +/** This is the first class to look at when you wish to work with a database + * through libpqxx. The connection opens during construction, and closes upon + * destruction. + * + * When creating a connection, you can pass a connection URI or a postgres + * connection string, to specify the database server's address, a login + * username, and so on. If you don't, the connection will try to obtain them + * from certain environment variables. If those are not set either, the + * default is to try and connect to the local system's port 5432. + * + * Find more about connection strings here: + * + * https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING + * + * The variables are documented here: + * + * https://www.postgresql.org/docs/current/libpq-envars.html + * + * To query or manipulate the database once connected, use one of the + * transaction classes (see pqxx/transaction_base.hxx) and perhaps also the + * transactor framework (see pqxx/transactor.hxx). + * + * When a connection breaks, you will typically get a @ref broken_connection + * exception. This can happen at almost any point. + * + * @warning On Unix-like systems, including GNU and BSD systems, your program + * may receive the SIGPIPE signal when the connection to the backend breaks. By + * default this signal will abort your program. Use "signal(SIGPIPE, SIG_IGN)" + * if you want your program to continue running after a connection fails. + */ +class PQXX_LIBEXPORT connection +{ +public: + connection() : connection{""} {} + + /// Connect to a database, using `options` string. + explicit connection(char const options[]) + { + check_version(); + init(options); + } + + /// Connect to a database, using `options` string. + explicit connection(zview options) : connection{options.c_str()} + { + // (Delegates to other constructor which calls check_version for us.) + } + + /// Move constructor. + /** Moving a connection is not allowed if it has an open transaction, or has + * error handlers or notification receivers registered on it. In those + * situations, other objects may hold references to the old object which + * would become invalid and might produce hard-to-diagnose bugs. + */ + connection(connection &&rhs); + +#if defined(PQXX_HAVE_CONCEPTS) + /// Connect to a database, passing options as a range of key/value pairs. + /** @warning Experimental. Requires C++20 "concepts" support. Define + * `PQXX_HAVE_CONCEPTS` to enable it. + * + * There's no need to escape the parameter values. + * + * See the PostgreSQL libpq documentation for the full list of possible + * options: + * + * https://postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS + * + * The options can be anything that can be iterated as a series of pairs of + * zero-terminated strings: `std::pair`, or + * `std::tuple`, or + * `std::map`, and so on. + */ + template + inline connection(MAPPING const ¶ms); +#endif // PQXX_HAVE_CONCEPTS + + ~connection() + { + try + { + close(); + } + catch (std::exception const &) + {} + } + + /// Move assignment. + /** Neither connection can have an open transaction, registered error + * handlers, or registered notification receivers. + */ + connection &operator=(connection &&rhs); + + connection(connection const &) = delete; + connection &operator=(connection const &) = delete; + + /// Is this connection open at the moment? + /** @warning This function is **not** needed in most code. Resist the + * temptation to check it after opening a connection. The `connection` + * constructor will throw a @ref broken_connection exception if can't connect + * to the database. + */ + [[nodiscard]] bool PQXX_PURE is_open() const noexcept; + + /// Invoke notice processor function. The message should end in newline. + void process_notice(char const[]) noexcept; + /// Invoke notice processor function. Newline at end is recommended. + /** The zview variant, with a message ending in newline, is the most + * efficient way to call process_notice. + */ + void process_notice(zview) noexcept; + + /// Enable tracing to a given output stream, or nullptr to disable. + void trace(std::FILE *) noexcept; + + /** + * @name Connection properties + * + * These are probably not of great interest, since most are derived from + * information supplied by the client program itself, but they are included + * for completeness. + * + * The connection needs to be currently active for these to work. + */ + //@{ + /// Name of database we're connected to, if any. + [[nodiscard]] char const *dbname() const; + + /// Database user ID we're connected under, if any. + [[nodiscard]] char const *username() const; + + /// Address of server, or nullptr if none specified (i.e. default or local) + [[nodiscard]] char const *hostname() const; + + /// Server port number we're connected to. + [[nodiscard]] char const *port() const; + + /// Process ID for backend process, or 0 if inactive. + [[nodiscard]] int PQXX_PURE backendpid() const &noexcept; + + /// Socket currently used for connection, or -1 for none. Use with care! + /** Query the current socket number. This is intended for event loops based + * on functions such as select() or poll(), where you're waiting for any of + * multiple file descriptors to become ready for communication. + * + * Please try to stay away from this function. It is really only meant for + * event loops that need to wait on more than one file descriptor. If all + * you need is to block until a notification arrives, for instance, use + * await_notification(). If you want to issue queries and retrieve results + * in nonblocking fashion, check out the pipeline class. + */ + [[nodiscard]] int PQXX_PURE sock() const &noexcept; + + /// What version of the PostgreSQL protocol is this connection using? + /** The answer can be 0 (when there is no connection); 3 for protocol 3.0; or + * possibly higher values as newer protocol versions come into use. + */ + [[nodiscard]] int PQXX_PURE protocol_version() const noexcept; + + /// What version of the PostgreSQL server are we connected to? + /** The result is a bit complicated: each of the major, medium, and minor + * release numbers is written as a two-digit decimal number, and the three + * are then concatenated. Thus server version 9.4.2 will be returned as the + * decimal number 90402. If there is no connection to the server, this + * returns zero. + * + * @warning When writing version numbers in your code, don't add zero at the + * beginning! Numbers beginning with zero are interpreted as octal (base-8) + * in C++. Thus, 070402 is not the same as 70402, and 080000 is not a number + * at all because there is no digit "8" in octal notation. Use strictly + * decimal notation when it comes to these version numbers. + */ + [[nodiscard]] int PQXX_PURE server_version() const noexcept; + //@} + + /// @name Text encoding + /** + * Each connection is governed by a "client encoding," which dictates how + * strings and other text is represented in bytes. The database server will + * send text data to you in this encoding, and you should use it for the + * queries and data which you send to the server. + * + * Search the PostgreSQL documentation for "character set encodings" to find + * out more about the available encodings, how to extend them, and how to use + * them. Not all server-side encodings are compatible with all client-side + * encodings or vice versa. + * + * Encoding names are case-insensitive, so e.g. "UTF8" is equivalent to + * "utf8". + * + * You can change the client encoding, but this may not work when the + * connection is in a special state, such as when streaming a table. It's + * not clear what happens if you change the encoding during a transaction, + * and then abort the transaction. + */ + //@{ + /// Get client-side character encoding, by name. + [[nodiscard]] std::string get_client_encoding() const; + + /// Set client-side character encoding, by name. + /** + * @param encoding Name of the character set encoding to use. + */ + void set_client_encoding(zview encoding) & + { + set_client_encoding(encoding.c_str()); + } + + /// Set client-side character encoding, by name. + /** + * @param encoding Name of the character set encoding to use. + */ + void set_client_encoding(char const encoding[]) &; + + /// Get the connection's encoding, as a PostgreSQL-defined code. + [[nodiscard]] int PQXX_PRIVATE encoding_id() const; + + //@} + + /// Set session variable, using SQL's `SET` command. + /** @deprecated To set a session variable, use @ref set_session_var. To set + * a transaction-local variable, execute an SQL `SET` command. + * + * @warning When setting a string value, you must escape and quote it first. + * Use the @ref quote() function to do that. + * + * @warning This executes an SQL query, so do not get or set variables while + * a table stream or pipeline is active on the same connection. + * + * @param var Variable to set. + * @param value New value for Var. This can be any SQL expression. If it's + * a string, be sure that it's properly escaped and quoted. + */ + [[deprecated("To set session variables, use set_session_var.")]] void + set_variable(std::string_view var, std::string_view value) &; + + /// Set one of the session variables to a new value. + /** This executes SQL, so do not do it while a pipeline or stream is active + * on the connection. + * + * The value you set here will last for the rest of the connection's + * duration, or until you set a new value. + * + * If you set the value while in a @ref dbtransaction (i.e. any transaction + * that is not a @ref nontransaction), then rolling back the transaction will + * undo the change. + * + * All applies to setting _session_ variables. You can also set the same + * variables as _local_ variables, in which case they will always revert to + * their previous value when the transaction ends (or when you overwrite them + * of course). To set a local variable, simply execute an SQL statement + * along the lines of "`SET LOCAL var = 'value'`" inside your transaction. + * + * @param var The variable to set. + * @param value The new value for the variable. + * @throw @ref variable_set_to_null if the value is null; this is not + * allowed. + */ + template + void set_session_var(std::string_view var, TYPE const &value) & + { + if constexpr (nullness::has_null) + { + if (nullness::is_null(value)) + throw variable_set_to_null{ + internal::concat("Attempted to set variable ", var, " to null.")}; + } + exec(internal::concat("SET ", quote_name(var), "=", quote(value))); + } + + /// Read session variable, using SQL's `SHOW` command. + /** @warning This executes an SQL query, so do not get or set variables while + * a table stream or pipeline is active on the same connection. + */ + [[deprecated("Use get_var instead.")]] std::string + get_variable(std::string_view); + + /// Read currently applicable value of a variable. + /** This function executes an SQL statement, so it won't work while a + * @ref pipeline or query stream is active on the connection. + * + * @return a blank `std::optional` if the variable's value is null, or its + * string value otherwise. + */ + std::string get_var(std::string_view var); + + /// Read currently applicable value of a variable. + /** This function executes an SQL statement, so it won't work while a + * @ref pipeline or query stream is active on the connection. + * + * If there is any possibility that the variable is null, ensure that `TYPE` + * can represent null values. + */ + template TYPE get_var_as(std::string_view var) + { + return from_string(get_var(var)); + } + + /** + * @name Notifications and Receivers + */ + //@{ + /// Check for pending notifications and take appropriate action. + /** This does not block. To wait for incoming notifications, either call + * await_notification() (it calls this function); or wait for incoming data + * on the connection's socket (i.e. wait to read), and then call this + * function repeatedly until it returns zero. After that, there are no more + * pending notifications so you may want to wait again. + * + * If any notifications are pending when you call this function, it + * processes them by finding any receivers that match the notification string + * and invoking those. If no receivers match, there is nothing to invoke but + * we do consider the notification processed. + * + * If any of the client-registered receivers throws an exception, the + * function will report it using the connection's errorhandlers. It does not + * re-throw the exceptions. + * + * @return Number of notifications processed. + */ + int get_notifs(); + + /// Wait for a notification to come in. + /** There are other events that will also terminate the wait, such as the + * backend failing. It will also wake up periodically. + * + * If a notification comes in, the call will process it, along with any other + * notifications that may have been pending. + * + * To wait for notifications into your own event loop instead, wait until + * there is incoming data on the connection's socket to be read, then call + * @ref get_notifs() repeatedly until it returns zero. + * + * @return Number of notifications processed. + */ + int await_notification(); + + /// Wait for a notification to come in, or for given timeout to pass. + /** There are other events that will also terminate the wait, such as the + * backend failing, or timeout expiring. + * + * If a notification comes in, the call will process it, along with any other + * notifications that may have been pending. + * + * To wait for notifications into your own event loop instead, wait until + * there is incoming data on the connection's socket to be read, then call + * @ref get_notifs repeatedly until it returns zero. + * + * @return Number of notifications processed + */ + int await_notification(std::time_t seconds, long microseconds); + //@} + + /** + * @name Password encryption + * + * Use this when setting a new password for the user if password encryption + * is enabled. Inputs are the SQL name for the user for whom you with to + * encrypt a password; the plaintext password; and the hash algorithm. + * + * The algorithm must be one of "md5", "scram-sha-256" (introduced in + * PostgreSQL 10), or `nullptr`. If the pointer is null, this will query + * the `password_encryption setting` from the server, and use the default + * algorithm as defined there. + * + * @return encrypted version of the password, suitable for encrypted + * PostgreSQL authentication. + * + * Thus you can change a user's password with: + * ```cxx + * void setpw(transaction_base &t, string const &user, string const &pw) + * { + * t.exec0("ALTER USER " + user + " " + * "PASSWORD '" + t.conn().encrypt_password(user,pw) + "'"); + * } + * ``` + * + * When building this against a libpq older than version 10, this will use + * an older function which only supports md5. In that case, requesting a + * different algorithm than md5 will result in a @ref feature_not_supported + * exception. + */ + //@{ + /// Encrypt a password for a given user. + [[nodiscard]] std::string + encrypt_password(zview user, zview password, zview algorithm) + { + return encrypt_password(user.c_str(), password.c_str(), algorithm.c_str()); + } + /// Encrypt a password for a given user. + [[nodiscard]] std::string encrypt_password( + char const user[], char const password[], char const *algorithm = nullptr); + //@} + + /** + * @name Prepared statements + * + * PostgreSQL supports prepared SQL statements, i.e. statements that you can + * register under a name you choose, optimized once by the backend, and + * executed any number of times under the given name. + * + * Prepared statement definitions are not sensitive to transaction + * boundaries. A statement defined inside a transaction will remain defined + * outside that transaction, even if the transaction itself is subsequently + * aborted. Once a statement has been prepared, it will only go away if you + * close the connection or explicitly "unprepare" the statement. + * + * Use the `pqxx::transaction_base::exec_prepared` functions to execute a + * prepared statement. See @ref prepared for a full discussion. + * + * @warning Using prepared statements can save time, but if your statement + * takes parameters, it may also make your application significantly slower! + * The reason is that the server works out a plan for executing the query + * when you prepare it. At that time, of course it does not know the values + * for the parameters that you will pass. If you execute a query without + * preparing it, then the server works out the plan on the spot, with full + * knowledge of the parameter values. + * + * A statement's definition can refer to its parameters as `$1`, `$2`, etc. + * The first parameter you pass to the call provides a value for `$1`, and + * so on. + * + * Here's an example of how to use prepared statements. + * + * ```cxx + * using namespace pqxx; + * void foo(connection &c) + * { + * c.prepare("findtable", "select * from pg_tables where name=$1"); + * work tx{c}; + * result r = tx.exec_prepared("findtable", "mytable"); + * if (std::empty(r)) throw runtime_error{"mytable not found!"}; + * } + * ``` + */ + //@{ + + /// Define a prepared statement. + /** + * @param name unique name for the new prepared statement. + * @param definition SQL statement to prepare. + */ + void prepare(zview name, zview definition) & + { + prepare(name.c_str(), definition.c_str()); + } + + /** + * @param name unique name for the new prepared statement. + * @param definition SQL statement to prepare. + */ + void prepare(char const name[], char const definition[]) &; + + /// Define a nameless prepared statement. + /** + * This can be useful if you merely want to pass large binary parameters to a + * statement without otherwise wishing to prepare it. If you use this + * feature, always keep the definition and the use close together to avoid + * the nameless statement being redefined unexpectedly by code somewhere + * else. + */ + void prepare(char const definition[]) &; + void prepare(zview definition) & { return prepare(definition.c_str()); } + + /// Drop prepared statement. + void unprepare(std::string_view name); + + //@} + + // C++20: constexpr. Breaks ABI. + /// Suffix unique number to name to make it unique within session context. + /** Used internally to generate identifiers for SQL objects (such as cursors + * and nested transactions) based on a given human-readable base name. + */ + [[nodiscard]] std::string adorn_name(std::string_view); + + /** + * @defgroup escaping-functions String-escaping functions + */ + //@{ + + /// Escape string for use as SQL string literal on this connection. + /** @warning This accepts a length, and it does not require a terminating + * zero byte. But if there is a zero byte, escaping stops there even if + * it's not at the end of the string! + */ + [[deprecated("Use std::string_view or pqxx:zview.")]] std::string + esc(char const text[], std::size_t maxlen) const + { + return esc(std::string_view{text, maxlen}); + } + + /// Escape string for use as SQL string literal on this connection. + [[nodiscard]] std::string esc(char const text[]) const + { + return esc(std::string_view{text}); + } + +#if defined(PQXX_HAVE_SPAN) + /// Escape string for use as SQL string literal, into `buffer`. + /** Use this variant when you want to re-use the same buffer across multiple + * calls. If that's not the case, or convenience and simplicity are more + * important, use the single-argument variant. + * + * For every byte in `text`, there must be at least 2 bytes of space in + * `buffer`; plus there must be one byte of space for a trailing zero. + * Throws @ref range_error if this space is not available. + * + * Returns a reference to the escaped string, which is actually stored in + * `buffer`. + */ + [[nodiscard]] std::string_view + esc(std::string_view text, std::span buffer) + { + auto const size{std::size(text)}, space{std::size(buffer)}; + auto const needed{2 * size + 1}; + if (space < needed) + throw range_error{internal::concat( + "Not enough room to escape string of ", size, " byte(s): need ", + needed, " bytes of buffer space, but buffer size is ", space, ".")}; + auto const data{buffer.data()}; + return {data, esc_to_buf(text, data)}; + } +#endif + + /// Escape string for use as SQL string literal on this connection. + /** @warning This is meant for text strings only. It cannot contain bytes + * whose value is zero ("nul bytes"). + */ + [[nodiscard]] std::string esc(std::string_view text) const; + +#if defined(PQXX_HAVE_CONCEPTS) + /// Escape binary string for use as SQL string literal on this connection. + /** This is identical to `esc_raw(data)`. */ + template [[nodiscard]] std::string esc(DATA const &data) const + { + return esc_raw(data); + } +#endif + +#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) + /// Escape binary string for use as SQL string literal, into `buffer`. + /** Use this variant when you want to re-use the same buffer across multiple + * calls. If that's not the case, or convenience and simplicity are more + * important, use the single-argument variant. + * + * For every byte in `data`, there must be at least two bytes of space in + * `buffer`; plus there must be two bytes of space for a header and one for + * a trailing zero. Throws @ref range_error if this space is not available. + * + * Returns a reference to the escaped string, which is actually stored in + * `buffer`. + */ + template + [[nodiscard]] zview esc(DATA const &data, std::span buffer) const + { + auto const size{std::size(data)}, space{std::size(buffer)}; + auto const needed{internal::size_esc_bin(std::size(data))}; + if (space < needed) + throw range_error{internal::concat( + "Not enough room to escape binary string of ", size, " byte(s): need ", + needed, " bytes of buffer space, but buffer size is ", space, ".")}; + + std::basic_string_view view{std::data(data), std::size(data)}; + auto const out{std::data(buffer)}; + // Actually, in the modern format, we know beforehand exactly how many + // bytes we're going to fill. Just leave out the trailing zero. + internal::esc_bin(view, out); + return zview{out, needed - 1}; + } +#endif + + /// Escape binary string for use as SQL string literal on this connection. + [[deprecated("Use std::byte for binary data.")]] std::string + esc_raw(unsigned char const bin[], std::size_t len) const; + + /// Escape binary string for use as SQL string literal on this connection. + /** You can also just use @ref esc with a binary string. */ + [[nodiscard]] std::string esc_raw(std::basic_string_view) const; + +#if defined(PQXX_HAVE_SPAN) + /// Escape binary string for use as SQL string literal, into `buffer`. + /** You can also just use @ref esc with a binary string. */ + [[nodiscard]] std::string + esc_raw(std::basic_string_view, std::span buffer) const; +#endif + +#if defined(PQXX_HAVE_CONCEPTS) + /// Escape binary string for use as SQL string literal on this connection. + /** You can also just use @ref esc with a binary string. */ + template + [[nodiscard]] std::string esc_raw(DATA const &data) const + { + return esc_raw( + std::basic_string_view{std::data(data), std::size(data)}); + } +#endif + +#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) + /// Escape binary string for use as SQL string literal, into `buffer`. + template + [[nodiscard]] zview esc_raw(DATA const &data, std::span buffer) const + { + return this->esc(binary_cast(data), buffer); + } +#endif + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string + unesc_raw(zview text) const + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return unesc_raw(text.c_str()); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string + unesc_raw(char const text[]) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + * + * (The data must be encoded in PostgreSQL's "hex" format. The legacy + * "bytea" escape format, used prior to PostgreSQL 9.0, is no longer + * supported.) + */ + [[nodiscard]] std::basic_string + unesc_bin(std::string_view text) const + { + std::basic_string buf; + buf.resize(pqxx::internal::size_unesc_bin(std::size(text))); + pqxx::internal::unesc_bin(text, buf.data()); + return buf; + } + + /// Escape and quote a string of binary data. + [[deprecated("Use quote(std::basic_string_view).")]] std::string + quote_raw(unsigned char const bin[], std::size_t len) const; + + /// Escape and quote a string of binary data. + std::string quote_raw(std::basic_string_view) const; + +#if defined(PQXX_HAVE_CONCEPTS) + /// Escape and quote a string of binary data. + /** You can also just use @ref quote with binary data. */ + template + [[nodiscard]] std::string quote_raw(DATA const &data) const + { + return quote_raw( + std::basic_string_view{std::data(data), std::size(data)}); + } +#endif + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Escape and quote an SQL identifier for use in a query. + [[nodiscard]] std::string quote_name(std::string_view identifier) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Escape and quote a table name. + /** When passing just a table name, this is just another name for + * @ref quote_name. + */ + [[nodiscard]] std::string quote_table(std::string_view name) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Escape and quote a table path. + /** A table path consists of a table name, optionally prefixed by a schema + * name; and if both are given, they are in turn optionally prefixed by a + * database name. + * + * Each portion of the path (database name, schema name, table name) will be + * quoted separately, and they will be joined together by dots. So for + * example, `myschema.mytable` will become `"myschema"."mytable"`. + */ + [[nodiscard]] std::string quote_table(table_path) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Quote and comma-separate a series of column names. + /** Use this to save a bit of work in cases where you repeatedly need to pass + * the same list of column names, e.g. with @ref stream_to and @ref + * stream_from. Some functions that need to quote the columns list + * internally, will have a "raw" alternative which let you do the quoting + * yourself. It's a bit of extra work, but it can in rare cases let you + * eliminate some duplicate work in quoting them repeatedly. + */ + template + inline std::string quote_columns(STRINGS const &columns) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Represent object as SQL string, including quoting & escaping. + /** + * Recognises nulls and represents them as SQL nulls. They get no quotes. + */ + template + [[nodiscard]] inline std::string quote(T const &t) const; + + [[deprecated("Use std::byte for binary data.")]] std::string + quote(binarystring const &) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Escape and quote binary data for use as a BYTEA value in SQL statement. + [[nodiscard]] std::string + quote(std::basic_string_view bytes) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Escape string for literal LIKE match. + /** Use this when part of an SQL "LIKE" pattern should match only as a + * literal string, not as a pattern, even if it contains "%" or "_" + * characters that would normally act as wildcards. + * + * The string does not get string-escaped or quoted. You do that later. + * + * For instance, let's say you have a string `name` entered by the user, + * and you're searching a `file` column for items that match `name` + * followed by a dot and three letters. Even if `name` contains wildcard + * characters "%" or "_", you only want those to match literally, so "_" + * only matches "_" and "%" only matches a single "%". + * + * You do that by "like-escaping" `name`, appending the wildcard pattern + * `".___"`, and finally, escaping and quoting the result for inclusion in + * your query: + * + * ```cxx + * tx.exec( + * "SELECT file FROM item WHERE file LIKE " + + * tx.quote(tx.esc_like(name) + ".___")); + * ``` + * + * The SQL "LIKE" operator also lets you choose your own escape character. + * This is supported, but must be a single-byte character. + */ + [[nodiscard]] std::string + esc_like(std::string_view text, char escape_char = '\\') const; + //@} + + /// Attempt to cancel the ongoing query, if any. + /** You can use this from another thread, and/or while a query is executing + * in a pipeline, but it's up to you to ensure that you're not canceling the + * wrong query. This may involve locking. + */ + void cancel_query(); + +#if defined(_WIN32) || __has_include() + /// Set socket to blocking (true) or nonblocking (false). + /** @warning Do not use this unless you _really_ know what you're doing. + * @warning This function is available on most systems, but not necessarily + * all. + */ + void set_blocking(bool block) &; +#endif // defined(_WIN32) || __has_include() + + /// Set session verbosity. + /** Set the verbosity of error messages to "terse", "normal" (the default), + * or "verbose." + * + * If "terse", returned messages include severity, primary text, and + * position only; this will normally fit on a single line. "normal" produces + * messages that include the above plus any detail, hint, or context fields + * (these might span multiple lines). "verbose" includes all available + * fields. + */ + void set_verbosity(error_verbosity verbosity) &noexcept; + + /// Return pointers to the active errorhandlers. + /** The entries are ordered from oldest to newest handler. + * + * You may use this to find errorhandlers that your application wants to + * delete when destroying the connection. Be aware, however, that libpqxx + * may also add errorhandlers of its own, and those will be included in the + * list. If this is a problem for you, derive your errorhandlers from a + * custom base class derived from pqxx::errorhandler. Then use dynamic_cast + * to find which of the error handlers are yours. + * + * The pointers point to the real errorhandlers. The container it returns + * however is a copy of the one internal to the connection, not a reference. + */ + [[nodiscard]] std::vector get_errorhandlers() const; + + /// Return a connection string encapsulating this connection's options. + /** The connection must be currently open for this to work. + * + * Returns a reconstruction of this connection's connection string. It may + * not exactly match the connection string you passed in when creating this + * connection. + */ + [[nodiscard]] std::string connection_string() const; + + /// Explicitly close the connection. + /** The destructor will do this for you automatically. Still, there is a + * reason to `close()` objects explicitly where possible: if an error should + * occur while closing, `close()` can throw an exception. A destructor + * cannot. + * + * Closing a connection is idempotent. Closing a connection that's already + * closed does nothing. + */ + void close(); + + /// Seize control of a raw libpq connection. + /** @warning Do not do this. Please. It's for very rare, very specific + * use-cases. The mechanism may change (or break) in unexpected ways in + * future versions. + * + * @param raw_conn a raw libpq `PQconn` pointer. + */ + static connection seize_raw_connection(internal::pq::PGconn *raw_conn) + { + return connection{raw_conn}; + } + + /// Release the raw connection without closing it. + /** @warning Do not do this. It's for very rare, very specific use-cases. + * The mechanism may change (or break) in unexpected ways in future versions. + * + * The `connection` object becomes unusable after this. + */ + internal::pq::PGconn *release_raw_connection() && + { + return std::exchange(m_conn, nullptr); + } + +private: + friend class connecting; + enum connect_mode + { + connect_nonblocking + }; + connection(connect_mode, zview connection_string); + + /// For use by @ref seize_raw_connection. + explicit connection(internal::pq::PGconn *raw_conn) : m_conn{raw_conn} {} + + /// Poll for ongoing connection, try to progress towards completion. + /** Returns a pair of "now please wait to read data from socket" and "now + * please wait to write data to socket." Both will be false when done. + * + * Throws an exception if polling indicates that the connection has failed. + */ + std::pair poll_connect(); + + // Initialise based on connection string. + void init(char const options[]); + // Initialise based on parameter names and values. + void init(char const *params[], char const *values[]); + void complete_init(); + + result make_result( + internal::pq::PGresult *pgr, std::shared_ptr const &query, + std::string_view desc = ""sv); + + void PQXX_PRIVATE set_up_state(); + + int PQXX_PRIVATE PQXX_PURE status() const noexcept; + + /// Escape a string, into a buffer allocated by the caller. + /** The buffer must have room for at least `2*std::size(text) + 1` bytes. + * + * Returns the number of bytes written, including the trailing zero. + */ + std::size_t esc_to_buf(std::string_view text, char *buf) const; + + friend class internal::gate::const_connection_largeobject; + char const *PQXX_PURE err_msg() const noexcept; + + void PQXX_PRIVATE process_notice_raw(char const msg[]) noexcept; + + result exec_prepared(std::string_view statement, internal::c_params const &); + + /// Throw @ref usage_error if this connection is not in a movable state. + void check_movable() const; + /// Throw @ref usage_error if not in a state where it can be move-assigned. + void check_overwritable() const; + + friend class internal::gate::connection_errorhandler; + void PQXX_PRIVATE register_errorhandler(errorhandler *); + void PQXX_PRIVATE unregister_errorhandler(errorhandler *) noexcept; + + friend class internal::gate::connection_transaction; + result exec(std::string_view, std::string_view = ""sv); + result + PQXX_PRIVATE exec(std::shared_ptr, std::string_view = ""sv); + void PQXX_PRIVATE register_transaction(transaction_base *); + void PQXX_PRIVATE unregister_transaction(transaction_base *) noexcept; + + friend class internal::gate::connection_stream_from; + std::pair>, std::size_t> + PQXX_PRIVATE read_copy_line(); + + friend class internal::gate::connection_stream_to; + void PQXX_PRIVATE write_copy_line(std::string_view); + void PQXX_PRIVATE end_copy_write(); + + friend class internal::gate::connection_largeobject; + internal::pq::PGconn *raw_connection() const { return m_conn; } + + friend class internal::gate::connection_notification_receiver; + void add_receiver(notification_receiver *); + void remove_receiver(notification_receiver *) noexcept; + + friend class internal::gate::connection_pipeline; + void PQXX_PRIVATE start_exec(char const query[]); + bool PQXX_PRIVATE consume_input() noexcept; + bool PQXX_PRIVATE is_busy() const noexcept; + internal::pq::PGresult *get_result(); + + friend class internal::gate::connection_dbtransaction; + friend class internal::gate::connection_sql_cursor; + + result exec_params(std::string_view query, internal::c_params const &args); + + /// Connection handle. + internal::pq::PGconn *m_conn = nullptr; + + /// Active transaction on connection, if any. + /** We don't use this for anything, except to check for open transactions + * when we close the connection or start a new transaction. + * + * We also don't allow move construction or move assignment while there's a + * transaction, since moving the connection in that case would leave one or + * more pointers back from the transaction to the connection dangling. + */ + transaction_base const *m_trans = nullptr; + + std::list m_errorhandlers; + + using receiver_list = + std::multimap; + /// Notification receivers. + receiver_list m_receivers; + + /// Unique number to use as suffix for identifiers (see adorn_name()). + int m_unique_id = 0; +}; + + +/// @deprecated Old base class for connection. They are now the same class. +using connection_base = connection; + + +/// An ongoing, non-blocking stepping stone to a connection. +/** Use this when you want to create a connection to the database, but without + * blocking your whole thread. It is only available on systems that have + * the `` header, and Windows. + * + * Connecting in this way is probably not "faster" (it's more complicated and + * has some extra overhead), but in some situations you can use it to make your + * application as a whole faster. It all depends on having other useful work + * to do in the same thread, and being able to wait on a socket. If you have + * other I/O going on at the same time, your event loop can wait for both the + * libpqxx socket and your own sockets, and wake up whenever any of them is + * ready to do work. + * + * Connecting in this way is not properly "asynchronous;" it's merely + * "nonblocking." This means it's not a super-high-performance mechanism like + * you might get with e.g. `io_uring`. In particular, if we need to look up + * the database hostname in DNS, that will happen synchronously. + * + * To use this, create the `connecting` object, passing a connection string. + * Then loop: If @ref wait_to_read returns true, wait for the socket to have + * incoming data on it. If @ref wait_to_write returns true, wait for the + * socket to be ready for writing. Then call @ref process to process any + * incoming or outgoing data. Do all of this until @ref done returns true (or + * there is an exception). Finally, call @ref produce to get the completed + * connection. + * + * For example: + * + * ```cxx + * pqxx::connecting cg{}; + * + * // Loop until we're done connecting. + * while (!cg.done()) + * { + * wait_for_fd(cg.sock(), cg.wait_to_read(), cg.wait_to_write()); + * cg.process(); + * } + * + * pqxx::connection conn = std::move(cg).produce(); + * + * // At this point, conn is a working connection. You can no longer use + * // cg at all. + * ``` + */ +class PQXX_LIBEXPORT connecting +{ +public: + /// Start connecting. + connecting(zview connection_string = ""_zv); + + connecting(connecting const &) = delete; + connecting(connecting &&) = default; + connecting &operator=(connecting const &) = delete; + connecting &operator=(connecting &&) = default; + + /// Get the socket. The socket may change during the connection process. + [[nodiscard]] int sock() const &noexcept { return m_conn.sock(); } + + /// Should we currently wait to be able to _read_ from the socket? + [[nodiscard]] constexpr bool wait_to_read() const &noexcept + { + return m_reading; + } + + /// Should we currently wait to be able to _write_ to the socket? + [[nodiscard]] constexpr bool wait_to_write() const &noexcept + { + return m_writing; + } + + /// Progress towards completion (but don't block). + void process() &; + + /// Is our connection finished? + [[nodiscard]] constexpr bool done() const &noexcept + { + return not m_reading and not m_writing; + } + + /// Produce the completed connection object. + /** Use this only once, after @ref done returned `true`. Once you have + * called this, the `connecting` instance has no more use or meaning. You + * can't call any of its member functions afterwards. + * + * This member function is rvalue-qualified, meaning that you can only call + * it on an rvalue instance of the class. If what you have is not an rvalue, + * turn it into one by wrapping it in `std::move()`. + */ + [[nodiscard]] connection produce() &&; + +private: + connection m_conn; + bool m_reading{false}; + bool m_writing{true}; +}; + + +template inline std::string connection::quote(T const &t) const +{ + if constexpr (nullness::always_null) + { + return "NULL"; + } + else + { + if (is_null(t)) + return "NULL"; + auto const text{to_string(t)}; + + // Okay, there's an easy way to do this and there's a hard way. The easy + // way was "quote, esc(to_string(t)), quote". I'm going with the hard way + // because it's going to save some string manipulation that will probably + // incur some unnecessary memory allocations and deallocations. + std::string buf{'\''}; + buf.resize(2 + 2 * std::size(text) + 1); + auto const content_bytes{esc_to_buf(text, buf.data() + 1)}; + auto const closing_quote{1 + content_bytes}; + buf[closing_quote] = '\''; + auto const end{closing_quote + 1}; + buf.resize(end); + return buf; + } +} + + +template +inline std::string connection::quote_columns(STRINGS const &columns) const +{ + return separated_list( + ","sv, std::cbegin(columns), std::cend(columns), + [this](auto col) { return this->quote_name(*col); }); +} + + +#if defined(PQXX_HAVE_CONCEPTS) +template +inline connection::connection(MAPPING const ¶ms) +{ + check_version(); + + std::vector keys, values; + if constexpr (std::ranges::sized_range) + { + auto const size{std::ranges::size(params) + 1}; + keys.reserve(size); + values.reserve(size); + } + for (auto const &[key, value] : params) + { + keys.push_back(internal::as_c_string(key)); + values.push_back(internal::as_c_string(value)); + } + keys.push_back(nullptr); + values.push_back(nullptr); + init(std::data(keys), std::data(values)); +} +#endif // PQXX_HAVE_CONCEPTS +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor new file mode 100644 index 000000000..e20b3a4fa --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor @@ -0,0 +1,8 @@ +/** Definition of the iterator/container-style cursor classes. + * + * C++-style wrappers for SQL cursors + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/cursor.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor.hxx new file mode 100644 index 000000000..b392e2407 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor.hxx @@ -0,0 +1,483 @@ +/* Definition of the iterator/container-style cursor classes. + * + * C++-style wrappers for SQL cursors. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/cursor instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_CURSOR +#define PQXX_H_CURSOR + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + +#include "pqxx/result.hxx" +#include "pqxx/transaction_base.hxx" + + +namespace pqxx +{ +/// Common definitions for cursor types +/** In C++ terms, fetches are always done in pre-increment or pre-decrement + * fashion--i.e. the result does not include the row the cursor is on at the + * beginning of the fetch, and the cursor ends up being positioned on the last + * row in the result. + * + * There are singular positions akin to `end()` at both the beginning and the + * end of the cursor's range of movement, although these fit in so naturally + * with the semantics that one rarely notices them. The cursor begins at the + * first of these, but any fetch in the forward direction will move the cursor + * off this position and onto the first row before returning anything. + */ +class PQXX_LIBEXPORT cursor_base +{ +public: + using size_type = result_size_type; + using difference_type = result_difference_type; + + /// Cursor access-pattern policy + /** Allowing a cursor to move forward only can result in better performance, + * so use this access policy whenever possible. + */ + enum access_policy + { + /// Cursor can move forward only + forward_only, + /// Cursor can move back and forth + random_access + }; + + /// Cursor update policy + /** + * @warning Not all PostgreSQL versions support updatable cursors. + */ + enum update_policy + { + /// Cursor can be used to read data but not to write + read_only, + /// Cursor can be used to update data as well as read it + update + }; + + /// Cursor destruction policy + /** The normal thing to do is to make a cursor object the owner of the SQL + * cursor it represents. There may be cases, however, where a cursor needs + * to persist beyond the end of the current transaction (and thus also beyond + * the lifetime of the cursor object that created it!), where it can be + * "adopted" into a new cursor object. See the basic_cursor documentation + * for an explanation of cursor adoption. + * + * If a cursor is created with "loose" ownership policy, the object + * representing the underlying SQL cursor will not take the latter with it + * when its own lifetime ends, nor will its originating transaction. + * + * @warning Use this feature with care and moderation. Only one cursor + * object should be responsible for any one underlying SQL cursor at any + * given time. + */ + enum ownership_policy + { + /// Destroy SQL cursor when cursor object is closed at end of transaction + owned, + /// Leave SQL cursor in existence after close of object and transaction + loose + }; + + cursor_base() = delete; + cursor_base(cursor_base const &) = delete; + cursor_base &operator=(cursor_base const &) = delete; + + /** + * @name Special movement distances. + */ + //@{ + + // TODO: Make constexpr inline (but breaks ABI). + /// Special value: read until end. + /** @return Maximum value for result::difference_type, so the cursor will + * attempt to read the largest possible result set. + */ + [[nodiscard]] static difference_type all() noexcept; + + /// Special value: read one row only. + /** @return Unsurprisingly, 1. + */ + [[nodiscard]] static constexpr difference_type next() noexcept { return 1; } + + /// Special value: read backwards, one row only. + /** @return Unsurprisingly, -1. + */ + [[nodiscard]] static constexpr difference_type prior() noexcept + { + return -1; + } + + // TODO: Make constexpr inline (but breaks ABI). + /// Special value: read backwards from current position back to origin. + /** @return Minimum value for result::difference_type. + */ + [[nodiscard]] static difference_type backward_all() noexcept; + + //@} + + /// Name of underlying SQL cursor + /** + * @returns Name of SQL cursor, which may differ from original given name. + * @warning Don't use this to access the SQL cursor directly without going + * through the provided wrapper classes! + */ + [[nodiscard]] constexpr std::string const &name() const noexcept + { + return m_name; + } + +protected: + cursor_base(connection &, std::string_view Name, bool embellish_name = true); + + std::string const m_name; +}; +} // namespace pqxx + + +#include + + +namespace pqxx +{ +/// "Stateless cursor" class: easy API for retrieving parts of result sets +/** This is a front-end for SQL cursors, but with a more C++-like API. + * + * Actually, stateless_cursor feels entirely different from SQL cursors. You + * don't keep track of positions, fetches, and moves; you just say which rows + * you want. See the retrieve() member function. + */ +template +class stateless_cursor +{ +public: + using size_type = result_size_type; + using difference_type = result_difference_type; + + /// Create cursor. + /** + * @param tx The transaction within which you want to create the cursor. + * @param query The SQL query whose results the cursor should traverse. + * @param cname A hint for the cursor's name. The actual SQL cursor's name + * will be based on this (though not necessarily identical). + * @param hold Create a `WITH HOLD` cursor? Such cursors stay alive after + * the transaction has ended, so you can continue to use it. + */ + stateless_cursor( + transaction_base &tx, std::string_view query, std::string_view cname, + bool hold) : + m_cur{tx, query, cname, cursor_base::random_access, up, op, hold} + {} + + /// Adopt an existing scrolling SQL cursor. + /** This lets you define a cursor yourself, and then wrap it in a + * libpqxx-managed `stateless_cursor` object. + * + * @param tx The transaction within which you want to manage the cursor. + * @param adopted_cursor Your cursor's SQL name. + */ + stateless_cursor(transaction_base &tx, std::string_view adopted_cursor) : + m_cur{tx, adopted_cursor, op} + { + // Put cursor in known position + m_cur.move(cursor_base::backward_all()); + } + + /// Close this cursor. + /** The destructor will do this for you automatically. + * + * Closing a cursor is idempotent. Closing a cursor that's already closed + * does nothing. + */ + void close() noexcept { m_cur.close(); } + + /// Number of rows in cursor's result set + /** @note This function is not const; it may need to scroll to find the size + * of the result set. + */ + [[nodiscard]] size_type size() + { + return internal::obtain_stateless_cursor_size(m_cur); + } + + /// Retrieve rows from begin_pos (inclusive) to end_pos (exclusive) + /** Rows are numbered starting from 0 to size()-1. + * + * @param begin_pos First row to retrieve. May be one row beyond the end of + * the result set, to avoid errors for empty result sets. Otherwise, must be + * a valid row number in the result set. + * @param end_pos Row up to which to fetch. Rows are returned ordered from + * begin_pos to end_pos, i.e. in ascending order if begin_pos < end_pos but + * in descending order if begin_pos > end_pos. The end_pos may be + * arbitrarily inside or outside the result set; only existing rows are + * included in the result. + */ + result retrieve(difference_type begin_pos, difference_type end_pos) + { + return internal::stateless_cursor_retrieve( + m_cur, result::difference_type(size()), begin_pos, end_pos); + } + + /// Return this cursor's name. + [[nodiscard]] constexpr std::string const &name() const noexcept + { + return m_cur.name(); + } + +private: + internal::sql_cursor m_cur; +}; + + +class icursor_iterator; +} // namespace pqxx + + +namespace pqxx::internal::gate +{ +class icursor_iterator_icursorstream; +class icursorstream_icursor_iterator; +} // namespace pqxx::internal::gate + + +namespace pqxx +{ +/// Simple read-only cursor represented as a stream of results +/** SQL cursors can be tricky, especially in C++ since the two languages seem + * to have been designed on different planets. An SQL cursor has two singular + * positions akin to `end()` on either side of the underlying result set. + * + * These cultural differences are hidden from view somewhat by libpqxx, which + * tries to make SQL cursors behave more like familiar C++ entities such as + * iterators, sequences, streams, and containers. + * + * Data is fetched from the cursor as a sequence of result objects. Each of + * these will contain the number of rows defined as the stream's stride, except + * of course the last block of data which may contain fewer rows. + * + * This class can create or adopt cursors that live outside any backend + * transaction, which your backend version may not support. + */ +class PQXX_LIBEXPORT icursorstream +{ +public: + using size_type = cursor_base::size_type; + using difference_type = cursor_base::difference_type; + + /// Set up a read-only, forward-only cursor. + /** Roughly equivalent to a C++ Standard Library istream, this cursor type + * supports only two operations: reading a block of rows while moving + * forward, and moving forward without reading any data. + * + * @param context Transaction context in which this cursor will be active. + * @param query SQL query whose results this cursor shall iterate. + * @param basename Suggested name for the SQL cursor; the library will append + * a unique code to ensure its uniqueness. + * @param sstride Number of rows to fetch per read operation; must be a + * positive number. + */ + icursorstream( + transaction_base &context, std::string_view query, + std::string_view basename, difference_type sstride = 1); + + /// Adopt existing SQL cursor. Use with care. + /** Forms a cursor stream around an existing SQL cursor, as returned by e.g. + * a server-side function. The SQL cursor will be cleaned up by the stream's + * destructor as if it had been created by the stream; cleaning it up by hand + * or adopting the same cursor twice is an error. + * + * Passing the name of the cursor as a string is not allowed, both to avoid + * confusion with the other constructor and to discourage unnecessary use of + * adopted cursors. + * + * @warning It is technically possible to adopt a "WITH HOLD" cursor, i.e. a + * cursor that stays alive outside its creating transaction. However, any + * cursor stream (including the underlying SQL cursor, naturally) must be + * destroyed before its transaction context object is destroyed. Therefore + * the only way to use SQL's WITH HOLD feature is to adopt the cursor, but + * defer doing so until after entering the transaction context that will + * eventually destroy it. + * + * @param context Transaction context in which this cursor will be active. + * @param cname Result field containing the name of the SQL cursor to adopt. + * @param sstride Number of rows to fetch per read operation; must be a + * positive number. + * @param op Ownership policy. Determines whether the cursor underlying this + * stream will be destroyed when the stream is closed. + */ + icursorstream( + transaction_base &context, field const &cname, difference_type sstride = 1, + cursor_base::ownership_policy op = cursor_base::owned); + + /// Return `true` if this stream may still return more data. + constexpr operator bool() const &noexcept { return not m_done; } + + /// Read new value into given result object; same as operator `>>`. + /** The result set may continue any number of rows from zero to the chosen + * stride, inclusive. An empty result will only be returned if there are no + * more rows to retrieve. + * + * @param res Write the retrieved data into this result object. + * @return Reference to this very stream, to facilitate "chained" invocations + * ("C.get(r1).get(r2);") + */ + icursorstream &get(result &res) + { + res = fetchblock(); + return *this; + } + /// Read new value into given result object; same as `get(result&)`. + /** The result set may continue any number of rows from zero to the chosen + * stride, inclusive. An empty result will only be returned if there are no + * more rows to retrieve. + * + * @param res Write the retrieved data into this result object. + * @return Reference to this very stream, to facilitate "chained" invocations + * ("C >> r1 >> r2;") + */ + icursorstream &operator>>(result &res) { return get(res); } + + /// Move given number of rows forward without reading data. + /** Ignores any stride that you may have set. It moves by a given number of + * rows, not a number of strides. + * + * @return Reference to this stream itself, to facilitate "chained" + * invocations. + */ + icursorstream &ignore(std::streamsize n = 1) &; + + /// Change stride, i.e. the number of rows to fetch per read operation. + /** + * @param stride Must be a positive number. + */ + void set_stride(difference_type stride) &; + [[nodiscard]] constexpr difference_type stride() const noexcept + { + return m_stride; + } + +private: + result fetchblock(); + + friend class internal::gate::icursorstream_icursor_iterator; + size_type forward(size_type n = 1); + void insert_iterator(icursor_iterator *) noexcept; + void remove_iterator(icursor_iterator *) const noexcept; + + void service_iterators(difference_type); + + internal::sql_cursor m_cur; + + difference_type m_stride; + difference_type m_realpos, m_reqpos; + + mutable icursor_iterator *m_iterators; + + bool m_done; +}; + + +/// Approximate istream_iterator for icursorstream. +/** Intended as an implementation of an input_iterator (as defined by the C++ + * Standard Library), this class supports only two basic operations: reading + * the current element, and moving forward. In addition to the minimal + * guarantees for istream_iterators, this class supports multiple successive + * reads of the same position (the current result set is cached in the + * iterator) even after copying and even after new data have been read from the + * stream. This appears to be a requirement for input_iterators. Comparisons + * are also supported in the general case. + * + * The iterator does not care about its own position, however. Moving an + * iterator forward moves the underlying stream forward and reads the data from + * the new stream position, regardless of the iterator's old position in the + * stream. + * + * The stream's stride defines the granularity for all iterator movement or + * access operations, i.e. "ici += 1" advances the stream by one stride's worth + * of rows, and "*ici++" reads one stride's worth of rows from the stream. + * + * @warning Do not read from the underlying stream or its cursor, move its read + * position, or change its stride, between the time the first icursor_iterator + * on it is created and the time its last icursor_iterator is destroyed. + * + * @warning Manipulating these iterators within the context of a single cursor + * stream is not thread-safe. Creating a new iterator, copying one, + * or destroying one affects the stream as a whole. + */ +class PQXX_LIBEXPORT icursor_iterator +{ +public: + using iterator_category = std::input_iterator_tag; + using value_type = result; + using pointer = result const *; + using reference = result const &; + using istream_type = icursorstream; + using size_type = istream_type::size_type; + using difference_type = istream_type::difference_type; + + icursor_iterator() noexcept; + explicit icursor_iterator(istream_type &) noexcept; + icursor_iterator(icursor_iterator const &) noexcept; + ~icursor_iterator() noexcept; + + result const &operator*() const + { + refresh(); + return m_here; + } + result const *operator->() const + { + refresh(); + return &m_here; + } + icursor_iterator &operator++(); + icursor_iterator operator++(int); + icursor_iterator &operator+=(difference_type); + icursor_iterator &operator=(icursor_iterator const &) noexcept; + + [[nodiscard]] bool operator==(icursor_iterator const &rhs) const; + [[nodiscard]] bool operator!=(icursor_iterator const &rhs) const noexcept + { + return not operator==(rhs); + } + [[nodiscard]] bool operator<(icursor_iterator const &rhs) const; + [[nodiscard]] bool operator>(icursor_iterator const &rhs) const + { + return rhs < *this; + } + [[nodiscard]] bool operator<=(icursor_iterator const &rhs) const + { + return not(*this > rhs); + } + [[nodiscard]] bool operator>=(icursor_iterator const &rhs) const + { + return not(*this < rhs); + } + +private: + void refresh() const; + + friend class internal::gate::icursor_iterator_icursorstream; + difference_type pos() const noexcept { return m_pos; } + void fill(result const &); + + icursorstream *m_stream{nullptr}; + result m_here; + difference_type m_pos; + icursor_iterator *m_prev{nullptr}, *m_next{nullptr}; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction new file mode 100644 index 000000000..fa8d26476 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction @@ -0,0 +1,8 @@ +/** pqxx::dbtransaction abstract base class. + * + * pqxx::dbransaction defines a real transaction on the database. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/dbtransaction.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction.hxx new file mode 100644 index 000000000..d85cb170f --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction.hxx @@ -0,0 +1,70 @@ +/* Definition of the pqxx::dbtransaction abstract base class. + * + * pqxx::dbransaction defines a real transaction on the database. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/dbtransaction instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_DBTRANSACTION +#define PQXX_H_DBTRANSACTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/transaction_base.hxx" + +namespace pqxx +{ +/// Abstract transaction base class: bracket transactions on the database. +/** + * @ingroup transactions + * + * Use a dbtransaction-derived object such as "work" (transaction<>) to enclose + * operations on a database in a single "unit of work." This ensures that the + * whole series of operations either succeeds as a whole or fails completely. + * In no case will it leave half-finished work behind in the database. + * + * Once processing on a transaction has succeeded and any changes should be + * allowed to become permanent in the database, call commit(). If something + * has gone wrong and the changes should be forgotten, call abort() instead. + * If you do neither, an implicit abort() is executed at destruction time. + * + * It is an error to abort a transaction that has already been committed, or to + * commit a transaction that has already been aborted. Aborting an already + * aborted transaction or committing an already committed one is allowed, to + * make error handling easier. Repeated aborts or commits have no effect after + * the first one. + * + * Database transactions are not suitable for guarding long-running processes. + * If your transaction code becomes too long or too complex, consider ways to + * break it up into smaller ones. Unfortunately there is no universal recipe + * for this. + * + * The actual operations for committing/aborting the backend transaction are + * implemented by a derived class. The implementing concrete class must also + * call @ref close from its destructor. + */ +class PQXX_LIBEXPORT PQXX_NOVTABLE dbtransaction : public transaction_base +{ +protected: + /// Begin transaction. + explicit dbtransaction(connection &c) : transaction_base{c} {} + /// Begin transaction. + dbtransaction(connection &c, std::string_view tname) : + transaction_base{c, tname} + {} + /// Begin transaction. + dbtransaction( + connection &c, std::string_view tname, + std::shared_ptr rollback_cmd) : + transaction_base{c, tname, rollback_cmd} + {} +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler new file mode 100644 index 000000000..ea572ee79 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler @@ -0,0 +1,8 @@ +/** pqxx::errorhandler class. + * + * Callbacks for handling errors and warnings. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/errorhandler.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler.hxx new file mode 100644 index 000000000..2ffb5703c --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler.hxx @@ -0,0 +1,92 @@ +/* Definition of the pqxx::errorhandler class. + * + * pqxx::errorhandler handlers errors and warnings in a database session. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/errorhandler instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ERRORHANDLER +#define PQXX_H_ERRORHANDLER + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/types.hxx" + + +namespace pqxx::internal::gate +{ +class errorhandler_connection; +} + + +namespace pqxx +{ +/** + * @addtogroup errorhandler + */ +//@{ + +/// Base class for error-handler callbacks. +/** To receive errors and warnings from a connection, subclass this with your + * own error-handler functor, and instantiate it for the connection. Destroying + * the handler un-registers it. + * + * A connection can have multiple error handlers at the same time. When the + * database connection emits an error or warning message, it passes the message + * to each error handler, starting with the most recently registered one and + * progressing towards the oldest one. However an error handler may also + * instruct the connection not to pass the message to further handlers by + * returning "false." + * + * @warning Strange things happen when a result object outlives its parent + * connection. If you register an error handler on a connection, then you must + * not access the result after destroying the connection. This applies even if + * you destroy the error handler first! + */ +class PQXX_LIBEXPORT errorhandler +{ +public: + explicit errorhandler(connection &); + virtual ~errorhandler(); + + /// Define in subclass: receive an error or warning message from the + /// database. + /** + * @return Whether the same error message should also be passed to the + * remaining, older errorhandlers. + */ + virtual bool operator()(char const msg[]) noexcept = 0; + + errorhandler() = delete; + errorhandler(errorhandler const &) = delete; + errorhandler &operator=(errorhandler const &) = delete; + +private: + connection *m_home; + + friend class internal::gate::errorhandler_connection; + void unregister() noexcept; +}; + + +/// An error handler that suppresses any previously registered error handlers. +class quiet_errorhandler : public errorhandler +{ +public: + /// Suppress error notices. + quiet_errorhandler(connection &conn) : errorhandler{conn} {} + + /// Revert to previous handling of error notices. + virtual bool operator()(char const[]) noexcept override { return false; } +}; + +//@} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except new file mode 100644 index 000000000..e5dd508bf --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except @@ -0,0 +1,8 @@ +/** libpqxx exception classes. + * + * pqxx::sql_error, pqxx::broken_connection, pqxx::in_doubt_error, ... + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/except.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except.hxx new file mode 100644 index 000000000..24f959437 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except.hxx @@ -0,0 +1,447 @@ +/* Definition of libpqxx exception classes. + * + * pqxx::sql_error, pqxx::broken_connection, pqxx::in_doubt_error, ... + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/except instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_EXCEPT +#define PQXX_H_EXCEPT + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + + +namespace pqxx +{ +/** + * @addtogroup exception Exception classes + * + * These exception classes follow, roughly, the two-level hierarchy defined by + * the PostgreSQL SQLSTATE error codes (see Appendix A of the PostgreSQL + * documentation corresponding to your server version). This is not a complete + * mapping though. There are other differences as well, e.g. the error code + * for `statement_completion_unknown` has a separate status in libpqxx as + * @ref in_doubt_error, and `too_many_connections` is classified as a + * `broken_connection` rather than a subtype of `insufficient_resources`. + * + * @see http://www.postgresql.org/docs/9.4/interactive/errcodes-appendix.html + * + * @{ + */ + +/// Run-time failure encountered by libpqxx, similar to std::runtime_error. +struct PQXX_LIBEXPORT failure : std::runtime_error +{ + explicit failure(std::string const &); +}; + + +/// Exception class for lost or failed backend connection. +/** + * @warning When this happens on Unix-like systems, you may also get a SIGPIPE + * signal. That signal aborts the program by default, so if you wish to be + * able to continue after a connection breaks, be sure to disarm this signal. + * + * If you're working on a Unix-like system, see the manual page for + * `signal` (2) on how to deal with SIGPIPE. The easiest way to make this + * signal harmless is to make your program ignore it: + * + * ```cxx + * #include + * + * int main() + * { + * signal(SIGPIPE, SIG_IGN); + * // ... + * ``` + */ +struct PQXX_LIBEXPORT broken_connection : failure +{ + broken_connection(); + explicit broken_connection(std::string const &); +}; + + +/// The caller attempted to set a variable to null, which is not allowed. +struct PQXX_LIBEXPORT variable_set_to_null : failure +{ + variable_set_to_null(); + explicit variable_set_to_null(std::string const &); +}; + + +/// Exception class for failed queries. +/** Carries, in addition to a regular error message, a copy of the failed query + * and (if available) the SQLSTATE value accompanying the error. + */ +class PQXX_LIBEXPORT sql_error : public failure +{ + /// Query string. Empty if unknown. + std::string const m_query; + /// SQLSTATE string describing the error type, if known; or empty string. + std::string const m_sqlstate; + +public: + explicit sql_error( + std::string const &whatarg = "", std::string const &Q = "", + char const sqlstate[] = nullptr); + virtual ~sql_error() noexcept override; + + /// The query whose execution triggered the exception + [[nodiscard]] PQXX_PURE std::string const &query() const noexcept; + + /// SQLSTATE error code if known, or empty string otherwise. + [[nodiscard]] PQXX_PURE std::string const &sqlstate() const noexcept; +}; + + +/// "Help, I don't know whether transaction was committed successfully!" +/** Exception that might be thrown in rare cases where the connection to the + * database is lost while finishing a database transaction, and there's no way + * of telling whether it was actually executed by the backend. In this case + * the database is left in an indeterminate (but consistent) state, and only + * manual inspection will tell which is the case. + */ +struct PQXX_LIBEXPORT in_doubt_error : failure +{ + explicit in_doubt_error(std::string const &); +}; + + +/// The backend saw itself forced to roll back the ongoing transaction. +struct PQXX_LIBEXPORT transaction_rollback : sql_error +{ + explicit transaction_rollback( + std::string const &whatarg, std::string const &q = "", + char const sqlstate[] = nullptr); +}; + + +/// Transaction failed to serialize. Please retry it. +/** Can only happen at transaction isolation levels REPEATABLE READ and + * SERIALIZABLE. + * + * The current transaction cannot be committed without violating the guarantees + * made by its isolation level. This is the effect of a conflict with another + * ongoing transaction. The transaction may still succeed if you try to + * perform it again. + */ +struct PQXX_LIBEXPORT serialization_failure : transaction_rollback +{ + explicit serialization_failure( + std::string const &whatarg, std::string const &q, + char const sqlstate[] = nullptr); +}; + + +/// We can't tell whether our last statement succeeded. +struct PQXX_LIBEXPORT statement_completion_unknown : transaction_rollback +{ + explicit statement_completion_unknown( + std::string const &whatarg, std::string const &q, + char const sqlstate[] = nullptr); +}; + + +/// The ongoing transaction has deadlocked. Retrying it may help. +struct PQXX_LIBEXPORT deadlock_detected : transaction_rollback +{ + explicit deadlock_detected( + std::string const &whatarg, std::string const &q, + char const sqlstate[] = nullptr); +}; + + +/// Internal error in libpqxx library +struct PQXX_LIBEXPORT internal_error : std::logic_error +{ + explicit internal_error(std::string const &); +}; + + +/// Error in usage of libpqxx library, similar to std::logic_error +struct PQXX_LIBEXPORT usage_error : std::logic_error +{ + explicit usage_error(std::string const &); +}; + + +/// Invalid argument passed to libpqxx, similar to std::invalid_argument +struct PQXX_LIBEXPORT argument_error : std::invalid_argument +{ + explicit argument_error(std::string const &); +}; + + +/// Value conversion failed, e.g. when converting "Hello" to int. +struct PQXX_LIBEXPORT conversion_error : std::domain_error +{ + explicit conversion_error(std::string const &); +}; + + +/// Could not convert value to string: not enough buffer space. +struct PQXX_LIBEXPORT conversion_overrun : conversion_error +{ + explicit conversion_overrun(std::string const &); +}; + + +/// Something is out of range, similar to std::out_of_range +struct PQXX_LIBEXPORT range_error : std::out_of_range +{ + explicit range_error(std::string const &); +}; + + +/// Query returned an unexpected number of rows. +struct PQXX_LIBEXPORT unexpected_rows : public range_error +{ + explicit unexpected_rows(std::string const &msg) : range_error{msg} {} +}; + + +/// Database feature not supported in current setup. +struct PQXX_LIBEXPORT feature_not_supported : sql_error +{ + explicit feature_not_supported( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +/// Error in data provided to SQL statement. +struct PQXX_LIBEXPORT data_exception : sql_error +{ + explicit data_exception( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT integrity_constraint_violation : sql_error +{ + explicit integrity_constraint_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT restrict_violation : integrity_constraint_violation +{ + explicit restrict_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + integrity_constraint_violation{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT not_null_violation : integrity_constraint_violation +{ + explicit not_null_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + integrity_constraint_violation{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT foreign_key_violation : integrity_constraint_violation +{ + explicit foreign_key_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + integrity_constraint_violation{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT unique_violation : integrity_constraint_violation +{ + explicit unique_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + integrity_constraint_violation{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT check_violation : integrity_constraint_violation +{ + explicit check_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + integrity_constraint_violation{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT invalid_cursor_state : sql_error +{ + explicit invalid_cursor_state( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT invalid_sql_statement_name : sql_error +{ + explicit invalid_sql_statement_name( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT invalid_cursor_name : sql_error +{ + explicit invalid_cursor_name( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT syntax_error : sql_error +{ + /// Approximate position in string where error occurred, or -1 if unknown. + int const error_position; + + explicit syntax_error( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, int pos = -1) : + sql_error{err, Q, sqlstate}, error_position{pos} + {} +}; + +struct PQXX_LIBEXPORT undefined_column : syntax_error +{ + explicit undefined_column( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + syntax_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT undefined_function : syntax_error +{ + explicit undefined_function( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + syntax_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT undefined_table : syntax_error +{ + explicit undefined_table( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + syntax_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT insufficient_privilege : sql_error +{ + explicit insufficient_privilege( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +/// Resource shortage on the server +struct PQXX_LIBEXPORT insufficient_resources : sql_error +{ + explicit insufficient_resources( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT disk_full : insufficient_resources +{ + explicit disk_full( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + insufficient_resources{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT out_of_memory : insufficient_resources +{ + explicit out_of_memory( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + insufficient_resources{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT too_many_connections : broken_connection +{ + explicit too_many_connections(std::string const &err) : + broken_connection{err} + {} +}; + +/// PL/pgSQL error +/** Exceptions derived from this class are errors from PL/pgSQL procedures. + */ +struct PQXX_LIBEXPORT plpgsql_error : sql_error +{ + explicit plpgsql_error( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +/// Exception raised in PL/pgSQL procedure +struct PQXX_LIBEXPORT plpgsql_raise : plpgsql_error +{ + explicit plpgsql_raise( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + plpgsql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT plpgsql_no_data_found : plpgsql_error +{ + explicit plpgsql_no_data_found( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + plpgsql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT plpgsql_too_many_rows : plpgsql_error +{ + explicit plpgsql_too_many_rows( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + plpgsql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT blob_already_exists : failure +{ + explicit blob_already_exists(std::string const &); +}; + +/** + * @} + */ +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field new file mode 100644 index 000000000..37cb69e84 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field @@ -0,0 +1,8 @@ +/** pqxx::field class. + * + * pqxx::field refers to a field in a query result. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/field.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field.hxx new file mode 100644 index 000000000..b8b869fe4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field.hxx @@ -0,0 +1,542 @@ +/* Definitions for the pqxx::field class. + * + * pqxx::field refers to a field in a query result. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/field instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_FIELD +#define PQXX_H_FIELD + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#include "pqxx/array.hxx" +#include "pqxx/composite.hxx" +#include "pqxx/result.hxx" +#include "pqxx/strconv.hxx" +#include "pqxx/types.hxx" + +namespace pqxx +{ +/// Reference to a field in a result set. +/** A field represents one entry in a row. It represents an actual value + * in the result set, and can be converted to various types. + */ +class PQXX_LIBEXPORT field +{ +public: + using size_type = field_size_type; + + /// Constructor. Do not call this yourself; libpqxx will do it for you. + /** Create field as reference to a field in a result set. + * @param r Row that this field is part of. + * @param c Column number of this field. + */ + [[deprecated( + "Do not construct fields yourself. Get them from the row.")]] field(row const &r, row_size_type c) noexcept; + + /// Constructor. Do not call this yourself; libpqxx will do it for you. + [[deprecated( + "Do not construct fields yourself. Get them from the " + "row.")]] field() noexcept = default; + + /** + * @name Comparison + */ + //@{ + // TODO: noexcept. Breaks ABI. + /// Byte-by-byte comparison of two fields (all nulls are considered equal) + /** @warning null handling is still open to discussion and change! + * + * Handling of null values differs from that in SQL where a comparison + * involving a null value yields null, so nulls are never considered equal + * to one another or even to themselves. + * + * Null handling also probably differs from the closest equivalent in C++, + * which is the NaN (Not-a-Number) value, a singularity comparable to + * SQL's null. This is because the builtin == operator demands that a == a. + * + * The usefulness of this operator is questionable. No interpretation + * whatsoever is imposed on the data; 0 and 0.0 are considered different, + * as are null vs. the empty string, or even different (but possibly + * equivalent and equally valid) encodings of the same Unicode character + * etc. + */ + [[nodiscard]] PQXX_PURE bool operator==(field const &) const; + + /// Byte-by-byte comparison (all nulls are considered equal) + /** @warning See operator==() for important information about this operator + */ + [[nodiscard]] PQXX_PURE bool operator!=(field const &rhs) const noexcept + { + return not operator==(rhs); + } + //@} + + /** + * @name Column information + */ + //@{ + /// Column name. + [[nodiscard]] PQXX_PURE char const *name() const &; + + /// Column type. + [[nodiscard]] oid PQXX_PURE type() const; + + /// What table did this column come from? + [[nodiscard]] PQXX_PURE oid table() const; + + /// Return row number. The first row is row 0, the second is row 1, etc. + PQXX_PURE constexpr row_size_type num() const noexcept { return col(); } + + /// What column number in its originating table did this column come from? + [[nodiscard]] PQXX_PURE row_size_type table_column() const; + //@} + + /** + * @name Content access + */ + //@{ + /// Read as `string_view`, or an empty one if null. + /** The result only remains usable while the data for the underlying + * @ref result exists. Once all `result` objects referring to that data have + * been destroyed, the `string_view` will no longer point to valid memory. + */ + [[nodiscard]] PQXX_PURE std::string_view view() const & + { + return std::string_view(c_str(), size()); + } + + /// Read as plain C string. + /** Since the field's data is stored internally in the form of a + * zero-terminated C string, this is the fastest way to read it. Use the + * to() or as() functions to convert the string to other types such as + * `int`, or to C++ strings. + * + * Do not use this for BYTEA values, or other binary values. To read those, + * convert the value to your desired type using `to()` or `as()`. For + * example: `f.as>()`. + */ + [[nodiscard]] PQXX_PURE char const *c_str() const &; + + /// Is this field's value null? + [[nodiscard]] PQXX_PURE bool is_null() const noexcept; + + /// Return number of bytes taken up by the field's value. + [[nodiscard]] PQXX_PURE size_type size() const noexcept; + + /// Read value into obj; or if null, leave obj untouched and return `false`. + /** This can be used with optional types (except pointers other than C-style + * strings). + */ + template + auto to(T &obj) const -> typename std::enable_if_t< + (not std::is_pointer::value or std::is_same::value), + bool> + { + if (is_null()) + { + return false; + } + else + { + auto const bytes{c_str()}; + from_string(bytes, obj); + return true; + } + } + + /// Read field as a composite value, write its components into `fields`. + /** @warning This is still experimental. It may change or be replaced. + * + * Returns whether the field was null. If it was, it will not touch the + * values in `fields`. + */ + template bool composite_to(T &...fields) const + { + if (is_null()) + { + return false; + } + else + { + parse_composite(m_home.m_encoding, view(), fields...); + return true; + } + } + + /// Read value into obj; or leave obj untouched and return `false` if null. + template bool operator>>(T &obj) const { return to(obj); } + + /// Read value into obj; or if null, use default value and return `false`. + /** This can be used with `std::optional`, as well as with standard smart + * pointer types, but not with raw pointers. If the conversion from a + * PostgreSQL string representation allocates a pointer (e.g. using `new`), + * then the object's later deallocation should be baked in as well, right + * from the point where the object is created. So if you want a pointer, use + * a smart pointer, not a raw pointer. + * + * There is one exception, of course: C-style strings. Those are just + * pointers to the field's internal text data. + */ + template + auto to(T &obj, T const &default_value) const -> typename std::enable_if_t< + (not std::is_pointer::value or std::is_same::value), + bool> + { + bool const null{is_null()}; + if (null) + obj = default_value; + else + obj = from_string(this->view()); + return not null; + } + + /// Return value as object of given type, or default value if null. + /** Note that unless the function is instantiated with an explicit template + * argument, the Default value's type also determines the result type. + */ + template T as(T const &default_value) const + { + if (is_null()) + return default_value; + else + return from_string(this->view()); + } + + /// Return value as object of given type, or throw exception if null. + /** Use as `as>()` or `as()` as + * an alternative to `get()`; this is disabled for use with raw pointers + * (other than C-strings) because storage for the value can't safely be + * allocated here + */ + template T as() const + { + if (is_null()) + { + if constexpr (not nullness::has_null) + internal::throw_null_conversion(type_name); + else + return nullness::null(); + } + else + { + return from_string(this->view()); + } + } + + /// Return value wrapped in some optional type (empty for nulls). + /** Use as `get()` as before to obtain previous behavior, or specify + * container type with `get()` + */ + template class O = std::optional> + constexpr O get() const + { + return as>(); + } + + // TODO: constexpr noexcept, once array_parser constructor gets those. + /// Parse the field as an SQL array. + /** Call the parser to retrieve values (and structure) from the array. + * + * Make sure the @ref result object stays alive until parsing is finished. If + * you keep the @ref row of `field` object alive, it will keep the @ref + * result object alive as well. + */ + array_parser as_array() const & + { + return array_parser{c_str(), m_home.m_encoding}; + } + //@} + + +protected: + constexpr result const &home() const noexcept { return m_home; } + constexpr result::size_type idx() const noexcept { return m_row; } + constexpr row_size_type col() const noexcept { return m_col; } + + // TODO: Create gates. + friend class pqxx::result; + friend class pqxx::row; + field( + result const &r, result_size_type row_num, row_size_type col_num) noexcept + : + m_col{col_num}, m_home{r}, m_row{row_num} + {} + + /** + * You'd expect this to be unsigned, but due to the way reverse iterators + * are related to regular iterators, it must be allowed to underflow to -1. + */ + row_size_type m_col; + +private: + result m_home; + result::size_type m_row; +}; + + +template<> inline bool field::to(std::string &obj) const +{ + bool const null{is_null()}; + if (not null) + obj = std::string{view()}; + return not null; +} + + +template<> +inline bool field::to( + std::string &obj, std::string const &default_value) const +{ + bool const null{is_null()}; + if (null) + obj = default_value; + else + obj = std::string{view()}; + return not null; +} + + +/// Specialization: `to(char const *&)`. +/** The buffer has the same lifetime as the data in this result (i.e. of this + * result object, or the last remaining one copied from it etc.), so take care + * not to use it after the last result object referring to this query result is + * destroyed. + */ +template<> inline bool field::to(char const *&obj) const +{ + bool const null{is_null()}; + if (not null) + obj = c_str(); + return not null; +} + + +template<> inline bool field::to(std::string_view &obj) const +{ + bool const null{is_null()}; + if (not null) + obj = view(); + return not null; +} + + +template<> +inline bool field::to( + std::string_view &obj, std::string_view const &default_value) const +{ + bool const null{is_null()}; + if (null) + obj = default_value; + else + obj = view(); + return not null; +} + + +template<> inline std::string_view field::as() const +{ + if (is_null()) + PQXX_UNLIKELY + internal::throw_null_conversion(type_name); + return view(); +} + + +template<> +inline std::string_view +field::as(std::string_view const &default_value) const +{ + return is_null() ? default_value : view(); +} + + +template<> inline bool field::to(zview &obj) const +{ + bool const null{is_null()}; + if (not null) + obj = zview{c_str(), size()}; + return not null; +} + + +template<> +inline bool field::to(zview &obj, zview const &default_value) const +{ + bool const null{is_null()}; + if (null) + obj = default_value; + else + obj = zview{c_str(), size()}; + return not null; +} + + +template<> inline zview field::as() const +{ + if (is_null()) + PQXX_UNLIKELY + internal::throw_null_conversion(type_name); + return zview{c_str(), size()}; +} + + +template<> inline zview field::as(zview const &default_value) const +{ + return is_null() ? default_value : zview{c_str(), size()}; +} + + +template> +class field_streambuf : public std::basic_streambuf +{ +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + using openmode = std::ios::openmode; + using seekdir = std::ios::seekdir; + + explicit field_streambuf(field const &f) : m_field{f} { initialize(); } + +protected: + virtual int sync() override { return traits_type::eof(); } + + virtual pos_type seekoff(off_type, seekdir, openmode) override + { + return traits_type::eof(); + } + virtual pos_type seekpos(pos_type, openmode) override + { + return traits_type::eof(); + } + virtual int_type overflow(int_type) override { return traits_type::eof(); } + virtual int_type underflow() override { return traits_type::eof(); } + +private: + field const &m_field; + + int_type initialize() + { + auto g{static_cast(const_cast(m_field.c_str()))}; + this->setg(g, g, g + std::size(m_field)); + return int_type(std::size(m_field)); + } +}; + + +/// Input stream that gets its data from a result field +/** Use this class exactly as you would any other istream to read data from a + * field. All formatting and streaming operations of `std::istream` are + * supported. What you'll typically want to use, however, is the fieldstream + * alias (which defines a @ref basic_fieldstream for `char`). This is similar + * to how e.g. `std::ifstream` relates to `std::basic_ifstream`. + * + * This class has only been tested for the char type (and its default traits). + */ +template> +class basic_fieldstream : public std::basic_istream +{ + using super = std::basic_istream; + +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + + basic_fieldstream(field const &f) : super{nullptr}, m_buf{f} + { + super::init(&m_buf); + } + +private: + field_streambuf m_buf; +}; + +using fieldstream = basic_fieldstream; + +/// Write a result field to any type of stream +/** This can be convenient when writing a field to an output stream. More + * importantly, it lets you write a field to e.g. a `stringstream` which you + * can then use to read, format and convert the field in ways that to() does + * not support. + * + * Example: parse a field into a variable of the nonstandard + * "long long" type. + * + * ```cxx + * extern result R; + * long long L; + * stringstream S; + * + * // Write field's string into S + * S << R[0][0]; + * + * // Parse contents of S into L + * S >> L; + * ``` + */ +template +inline std::basic_ostream & +operator<<(std::basic_ostream &s, field const &value) +{ + s.write(value.c_str(), std::streamsize(std::size(value))); + return s; +} + + +/// Convert a field's value to type `T`. +/** Unlike the "regular" `from_string`, this knows how to deal with null + * values. + */ +template inline T from_string(field const &value) +{ + if (value.is_null()) + { + if constexpr (nullness::has_null) + return nullness::null(); + else + internal::throw_null_conversion(type_name); + } + else + { + return from_string(value.view()); + } +} + + +/// Convert a field's value to `nullptr_t`. +/** Yes, you read that right. This conversion does nothing useful. It always + * returns `nullptr`. + * + * Except... what if the field is not null? In that case, this throws + * @ref conversion_error. + */ +template<> +inline std::nullptr_t from_string(field const &value) +{ + if (not value.is_null()) + throw conversion_error{ + "Extracting non-null field into nullptr_t variable."}; + return nullptr; +} + + +/// Convert a field to a string. +template<> PQXX_LIBEXPORT std::string to_string(field const &value); +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/array-composite.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/array-composite.hxx new file mode 100644 index 000000000..d2b6603e5 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/array-composite.hxx @@ -0,0 +1,305 @@ +#if !defined(PQXX_ARRAY_COMPOSITE_HXX) +# define PQXX_ARRAY_COMPOSITE_HXX + +# include + +# include "pqxx/strconv.hxx" + +namespace pqxx::internal +{ +// Find the end of a double-quoted string. +/** `input[pos]` must be the opening double quote. + * + * Returns the offset of the first position after the closing quote. + */ +inline std::size_t scan_double_quoted_string( + char const input[], std::size_t size, std::size_t pos, + pqxx::internal::glyph_scanner_func *scan) +{ + // XXX: find_char<'"', '\\'>(). + auto next{scan(input, size, pos)}; + bool at_quote{false}; + for (pos = next, next = scan(input, size, pos); pos < size; + pos = next, next = scan(input, size, pos)) + { + if (at_quote) + { + if (next - pos == 1 and input[pos] == '"') + { + // We just read a pair of double quotes. Carry on. + at_quote = false; + } + else + { + // We just read one double quote, and now we're at a character that's + // not a second double quote. Ergo, that last character was the + // closing double quote and this is the position right after it. + return pos; + } + } + else if (next - pos == 1) + { + switch (input[pos]) + { + case '\\': + // Backslash escape. Skip ahead by one more character. + pos = next; + next = scan(input, size, pos); + break; + + case '"': + // This is either the closing double quote, or the first of a pair of + // double quotes. + at_quote = true; + break; + } + } + else + { + // Multibyte character. Carry on. + } + } + if (not at_quote) + throw argument_error{ + "Missing closing double-quote: " + std::string{input}}; + return pos; +} + + +/// Un-quote and un-escape a double-quoted SQL string. +inline std::string parse_double_quoted_string( + char const input[], std::size_t end, std::size_t pos, + pqxx::internal::glyph_scanner_func *scan) +{ + std::string output; + // Maximum output size is same as the input size, minus the opening and + // closing quotes. Or in the extreme opposite case, the real number could be + // half that. Usually it'll be a pretty close estimate. + output.reserve(std::size_t(end - pos - 2)); + + for (auto here{scan(input, end, pos)}, next{scan(input, end, here)}; + here < end - 1; here = next, next = scan(input, end, here)) + { + // A backslash here is always an escape. So is a double-quote, since we're + // inside the double-quoted string. In either case, we can just ignore the + // escape character and use the next character. This is the one redeeming + // feature of SQL's escaping system. + if ((next - here == 1) and (input[here] == '\\' or input[here] == '"')) + { + // Skip escape. + here = next; + next = scan(input, end, here); + } + output.append(input + here, input + next); + } + return output; +} + + +/// Find the end of an unquoted string in an array or composite-type value. +/** Stops when it gets to the end of the input; or when it sees any of the + * characters in STOP which has not been escaped. + * + * For array values, STOP is a comma, a semicolon, or a closing brace. For + * a value of a composite type, STOP is a comma or a closing parenthesis. + */ +template +inline std::size_t scan_unquoted_string( + char const input[], std::size_t size, std::size_t pos, + pqxx::internal::glyph_scanner_func *scan) +{ + bool at_backslash{false}; + auto next{scan(input, size, pos)}; + while ((pos < size) and + ((next - pos) > 1 or at_backslash or ((input[pos] != STOP) and ...))) + { + pos = next; + next = scan(input, size, pos); + at_backslash = + ((not at_backslash) and ((next - pos) == 1) and (input[pos] == '\\')); + } + return pos; +} + + +/// Parse an unquoted array entry or cfield of a composite-type field. +inline std::string parse_unquoted_string( + char const input[], std::size_t end, std::size_t pos, + pqxx::internal::glyph_scanner_func *scan) +{ + std::string output; + bool at_backslash{false}; + output.reserve(end - pos); + for (auto next{scan(input, end, pos)}; pos < end; + pos = next, next = scan(input, end, pos)) + { + at_backslash = + ((not at_backslash) and ((next - pos) == 1) and (input[pos] == '\\')); + if (not at_backslash) + output.append(input + pos, next - pos); + } + return output; +} + + +/// Parse a field of a composite-type value. +/** `T` is the C++ type of the field we're parsing, and `index` is its + * zero-based number. + * + * Strip off the leading parenthesis or bracket yourself before parsing. + * However, this function will parse the lcosing parenthesis or bracket. + * + * After a successful parse, `pos` will point at `std::end(text)`. + * + * For the purposes of parsing, ranges and arrays count as compositve values, + * so this function supports parsing those. If you specifically need a closing + * parenthesis, check afterwards that `text` did not end in a bracket instead. + * + * @param index Index of the current field, zero-based. It will increment for + * the next field. + * @param input Full input text for the entire composite-type value. + * @param pos Starting position (in `input`) of the field that we're parsing. + * After parsing, this will point at the beginning of the next field if + * there is one, or one position past the last character otherwise. + * @param field Destination for the parsed value. + * @param scan Glyph scanning function for the relevant encoding type. + * @param last_field Number of the last field in the value (zero-based). When + * parsing the last field, this will equal `index`. + */ +template +inline void parse_composite_field( + std::size_t &index, std::string_view input, std::size_t &pos, T &field, + glyph_scanner_func *scan, std::size_t last_field) +{ + assert(index <= last_field); + auto next{scan(std::data(input), std::size(input), pos)}; + if ((next - pos) != 1) + throw conversion_error{"Non-ASCII character in composite-type syntax."}; + + // Expect a field. + switch (input[pos]) + { + case ',': + case ')': + case ']': + // The field is empty, i.e, null. + if constexpr (nullness::has_null) + field = nullness::null(); + else + throw conversion_error{ + "Can't read composite field " + to_string(index) + ": C++ type " + + type_name + " does not support nulls."}; + break; + + case '"': { + auto const stop{scan_double_quoted_string( + std::data(input), std::size(input), pos, scan)}; + auto const text{ + parse_double_quoted_string(std::data(input), stop, pos, scan)}; + field = from_string(text); + pos = stop; + } + break; + + default: { + auto const stop{scan_unquoted_string<',', ')', ']'>( + std::data(input), std::size(input), pos, scan)}; + auto const text{parse_unquoted_string(std::data(input), stop, pos, scan)}; + field = from_string(text); + pos = stop; + } + break; + } + + // Expect a comma or a closing parenthesis. + next = scan(std::data(input), std::size(input), pos); + + if ((next - pos) != 1) + throw conversion_error{ + "Unexpected non-ASCII character after composite field: " + + std::string{input}}; + + if (index < last_field) + { + if (input[pos] != ',') + throw conversion_error{ + "Found '" + std::string{input[pos]} + + "' in composite value where comma was expected: " + std::data(input)}; + } + else + { + if (input[pos] == ',') + throw conversion_error{ + "Composite value contained more fields than the expected " + + to_string(last_field) + ": " + std::data(input)}; + if (input[pos] != ')' and input[pos] != ']') + throw conversion_error{ + "Composite value has unexpected characters where closing parenthesis " + "was expected: " + + std::string{input}}; + if (next != std::size(input)) + throw conversion_error{ + "Composite value has unexpected text after closing parenthesis: " + + std::string{input}}; + } + + pos = next; + ++index; +} + + +/// Conservatively estimate buffer size needed for a composite field. +template +inline std::size_t size_composite_field_buffer(T const &field) +{ + if constexpr (is_unquoted_safe) + { + // Safe to copy, without quotes or escaping. Drop the terminating zero. + return size_buffer(field) - 1; + } + else + { + // + Opening quote. + // + Field budget. + // - Terminating zero. + // + Escaping for each byte in the field's string representation. + // - Escaping for terminating zero. + // + Closing quote. + return 1 + 2 * (size_buffer(field) - 1) + 1; + } +} + + +template +inline void write_composite_field(char *&pos, char *end, T const &field) +{ + if constexpr (is_unquoted_safe) + { + // No need for quoting or escaping. Convert it straight into its final + // place in the buffer, and "backspace" the trailing zero. + pos = string_traits::into_buf(pos, end, field) - 1; + } + else + { + // The field may need escaping, which means we need an intermediate buffer. + // To avoid allocating that at run time, we use the end of the buffer that + // we have. + auto const budget{size_buffer(field)}; + *pos++ = '"'; + + // Now escape buf into its final position. + for (char const c : string_traits::to_buf(end - budget, end, field)) + { + if ((c == '"') or (c == '\\')) + *pos++ = '\\'; + + *pos++ = c; + } + + *pos++ = '"'; + } + + *pos++ = ','; +} +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/callgate.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/callgate.hxx new file mode 100644 index 000000000..42f7703e3 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/callgate.hxx @@ -0,0 +1,70 @@ +#ifndef PQXX_H_CALLGATE +#define PQXX_H_CALLGATE + +/* +Here's what a typical gate class definition looks like: + +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE @gateclass@ : callgate<@host@> +{ + friend class @client@; + + @gateclass@(reference x) : super(x) {} + + // Methods here. Use home() to access the host-class object. +}; +} // namespace pqxx::internal::gate +*/ + +namespace pqxx::internal +{ +/// Base class for call gates. +/** + * A call gate defines a limited, private interface on the host class that + * specified client classes can access. + * + * The metaphor works as follows: the gate stands in front of a "home," which + * is really a class, and only lets specific friends in. + * + * To implement a call gate that gives client C access to host H, + * * derive a gate class from callgate; + * * make the gate class a friend of H; + * * make C a friend of the gate class; and + * * implement "stuff C can do with H" as private members in the gate class. + * + * This special kind of "gated" friendship gives C private access to H, but + * only through an expressly limited interface. The gate class can access its + * host object as home(). + * + * Keep gate classes entirely stateless. They should be ultra-lightweight + * wrappers for their host classes, and be optimized away as much as possible + * by the compiler. Once you start adding state, you're on a slippery slope + * away from the pure, clean, limited interface pattern that gate classes are + * meant to implement. + * + * Ideally, all member functions of the gate class should be one-liners passing + * calls straight on to the host class. It can be useful however to break this + * rule temporarily during inter-class refactoring. + */ +template class PQXX_PRIVATE callgate +{ +protected: + /// This class, to keep constructors easy. + using super = callgate; + /// A reference to the host class. Helps keep constructors easy. + using reference = HOME &; + + callgate(reference x) : m_home(x) {} + + /// The home object. The gate class has full "private" access. + reference home() const noexcept { return m_home; } + +private: + reference m_home; +}; +} // namespace pqxx::internal + +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/concat.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/concat.hxx new file mode 100644 index 000000000..cd28bde7c --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/concat.hxx @@ -0,0 +1,45 @@ +#if !defined(PQXX_CONCAT_HXX) +# define PQXX_CONCAT_HXX + +# include +# include + +# include "pqxx/strconv.hxx" + +namespace pqxx::internal +{ +/// Convert item to a string, write it into [here, end). +template +void render_item(TYPE const &item, char *&here, char *end) +{ + here = string_traits::into_buf(here, end, item) - 1; +} + + +// C++20: Support non-random_access_range ranges. +/// Efficiently combine a bunch of items into one big string. +/** Use this as an optimised version of string concatentation. It takes just + * about any type; it will represent each item as a string according to its + * @ref string_traits. + * + * This is a simpler, more specialised version of @ref separated_list for a + * statically known series of items, possibly of different types. + */ +template +[[nodiscard]] inline std::string concat(TYPE... item) +{ + std::string buf; + // Size to accommodate string representations of all inputs, minus their + // terminating zero bytes. + buf.resize(size_buffer(item...)); + + char *const data{buf.data()}; + char *here = data; + char *end = data + std::size(buf); + (render_item(item, here, end), ...); + + buf.resize(static_cast(here - data)); + return buf; +} +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/conversions.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/conversions.hxx new file mode 100644 index 000000000..1df4fdead --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/conversions.hxx @@ -0,0 +1,1188 @@ +#include +#include +#include +#include +#include +#include + +#if defined(PQXX_HAVE_SPAN) && __has_include() +# include +#endif + +#include +#include +#include + +#include "pqxx/types.hxx" +#include "pqxx/util.hxx" + + +/* Internal helpers for string conversion, and conversion implementations. + * + * Do not include this header directly. The libpqxx headers do it for you. + */ +namespace pqxx::internal +{ +/// Convert a number in [0, 9] to its ASCII digit. +inline constexpr char number_to_digit(int i) noexcept +{ + return static_cast(i + '0'); +} + + +/// Compute numeric value of given textual digit (assuming that it is a digit). +constexpr int digit_to_number(char c) noexcept +{ + return c - '0'; +} + + +/// Summarize buffer overrun. +/** Don't worry about the exact parameter types: the sizes will be reasonably + * small, and nonnegative. + */ +std::string PQXX_LIBEXPORT +state_buffer_overrun(int have_bytes, int need_bytes); + + +template +inline std::string state_buffer_overrun(HAVE have_bytes, NEED need_bytes) +{ + return state_buffer_overrun( + static_cast(have_bytes), static_cast(need_bytes)); +} + + +/// Throw exception for attempt to convert null to given type. +[[noreturn]] PQXX_LIBEXPORT void +throw_null_conversion(std::string const &type); + + +/// Deliberately nonfunctional conversion traits for `char` types. +/** There are no string conversions for `char` and its signed and unsigned + * variants. Such a conversion would be dangerously ambiguous: should we treat + * it as text, or as a small integer? It'd be an open invitation for bugs. + * + * But the error message when you get this wrong is very cryptic. So, we + * derive dummy @ref string_traits implementations from this dummy type, and + * ensure that the compiler disallows their use. The compiler error message + * will at least contain a hint of the root of the problem. + */ +template struct disallowed_ambiguous_char_conversion +{ + static char *into_buf(char *, char *, CHAR_TYPE) = delete; + static constexpr zview + to_buf(char *, char *, CHAR_TYPE const &) noexcept = delete; + + static constexpr std::size_t + size_buffer(CHAR_TYPE const &) noexcept = delete; + static CHAR_TYPE from_string(std::string_view) = delete; +}; + + +template PQXX_LIBEXPORT extern std::string to_string_float(T); + + +/// Generic implementation for into_buf, on top of to_buf. +template +inline char *generic_into_buf(char *begin, char *end, T const &value) +{ + zview const text{string_traits::to_buf(begin, end, value)}; + auto const space{end - begin}; + // Include the trailing zero. + auto const len = std::size(text) + 1; + if (internal::cmp_greater(len, space)) + throw conversion_overrun{ + "Not enough buffer space to insert " + type_name + ". " + + state_buffer_overrun(space, len)}; + std::memmove(begin, text.data(), len); + return begin + len; +} + + +/// String traits for builtin integral types (though not bool). +template struct integral_traits +{ + static PQXX_LIBEXPORT T from_string(std::string_view text); + static PQXX_LIBEXPORT zview to_buf(char *begin, char *end, T const &value); + static PQXX_LIBEXPORT char *into_buf(char *begin, char *end, T const &value); + + static constexpr std::size_t size_buffer(T const &) noexcept + { + /** Includes a sign if needed; the number of base-10 digits which the type + * can reliably represent; the one extra base-10 digit which the type can + * only partially represent; and the terminating zero. + */ + return std::is_signed_v + std::numeric_limits::digits10 + 1 + 1; + } +}; + + +/// String traits for builtin floating-point types. +template struct float_traits +{ + static PQXX_LIBEXPORT T from_string(std::string_view text); + static PQXX_LIBEXPORT zview to_buf(char *begin, char *end, T const &value); + static PQXX_LIBEXPORT char *into_buf(char *begin, char *end, T const &value); + + // Return a nonnegative integral value's number of decimal digits. + static constexpr std::size_t digits10(std::size_t value) noexcept + { + if (value < 10) + return 1; + else + return 1 + digits10(value / 10); + } + + static constexpr std::size_t size_buffer(T const &) noexcept + { + using lims = std::numeric_limits; + // See #328 for a detailed discussion on the maximum number of digits. + // + // In a nutshell: for the big cases, the scientific notation is always + // the shortest one, and therefore the one that to_chars will pick. + // + // So... How long can the scientific notation get? 1 (for sign) + 1 (for + // decimal point) + 1 (for 'e') + 1 (for exponent sign) + max_digits10 + + // max number of digits in the exponent + 1 (terminating zero). + // + // What's the max number of digits in the exponent? It's the max number of + // digits out of the most negative exponent and the most positive one. + // + // The longest positive exponent is easy: 1 + ceil(log10(max_exponent10)). + // (The extra 1 is because 10^n takes up 1 + n digits, not n.) + // + // The longest negative exponent is a bit harder: min_exponent10 gives us + // the smallest power of 10 which a normalised version of T can represent. + // But the smallest denormalised power of 10 that T can represent is + // another max_digits10 powers of 10 below that. + // needs a minus sign. + // + // All this stuff messes with my head a bit because it's on the order of + // log10(log10(n)). It's easy to get the number of logs wrong. + auto const max_pos_exp{digits10(lims::max_exponent10)}; + // Really want std::abs(lims::min_exponent10), but MSVC 2017 apparently has + // problems with std::abs. So we use -lims::min_exponent10 instead. + auto const max_neg_exp{ + digits10(lims::max_digits10 - lims::min_exponent10)}; + return 1 + // Sign. + 1 + // Decimal point. + std::numeric_limits::max_digits10 + // Mantissa digits. + 1 + // Exponent "e". + 1 + // Exponent sign. + // Spell this weirdly to stop Windows compilers from reading this as + // a call to their "max" macro when NOMINMAX is not defined. + (std::max)(max_pos_exp, max_neg_exp) + // Exponent digits. + 1; // Terminating zero. + } +}; +} // namespace pqxx::internal + + +namespace pqxx +{ +/// The built-in arithmetic types do not have inherent null values. +template +struct nullness>> : no_null +{}; + + +template<> struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> +struct string_traits + : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> +struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> +struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> +struct string_traits + : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> struct string_traits : internal::float_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> struct string_traits : internal::float_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> +struct string_traits : internal::float_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; + + +template<> struct string_traits +{ + static PQXX_LIBEXPORT bool from_string(std::string_view text); + + static constexpr zview to_buf(char *, char *, bool const &value) noexcept + { + return value ? "true"_zv : "false"_zv; + } + + static char *into_buf(char *begin, char *end, bool const &value) + { + return pqxx::internal::generic_into_buf(begin, end, value); + } + + static constexpr std::size_t size_buffer(bool const &) noexcept { return 6; } +}; + + +/// We don't support conversion to/from `char` types. +/** Why are these disallowed? Because they are ambiguous. It's not inherently + * clear whether we should treat values of these types as text or as small + * integers. Either choice would lead to bugs. + */ +template<> +struct string_traits + : internal::disallowed_ambiguous_char_conversion +{}; + +/// We don't support conversion to/from `char` types. +/** Why are these disallowed? Because they are ambiguous. It's not inherently + * clear whether we should treat values of these types as text or as small + * integers. Either choice would lead to bugs. + */ +template<> +struct string_traits + : internal::disallowed_ambiguous_char_conversion +{}; + +/// We don't support conversion to/from `char` types. +/** Why are these disallowed? Because they are ambiguous. It's not inherently + * clear whether we should treat values of these types as text or as small + * integers. Either choice would lead to bugs. + */ +template<> +struct string_traits + : internal::disallowed_ambiguous_char_conversion +{}; + + +template<> inline constexpr bool is_unquoted_safe{true}; + + +template struct nullness> +{ + static constexpr bool has_null = true; + /// Technically, you could have an optional of an always-null type. + static constexpr bool always_null = nullness::always_null; + static constexpr bool is_null(std::optional const &v) noexcept + { + return ((not v.has_value()) or pqxx::is_null(*v)); + } + static constexpr std::optional null() { return {}; } +}; + + +template +inline constexpr format param_format(std::optional const &value) +{ + return param_format(*value); +} + + +template struct string_traits> +{ + static char *into_buf(char *begin, char *end, std::optional const &value) + { + return string_traits::into_buf(begin, end, *value); + } + + static zview to_buf(char *begin, char *end, std::optional const &value) + { + if (value.has_value()) + return string_traits::to_buf(begin, end, *value); + else + return {}; + } + + static std::optional from_string(std::string_view text) + { + return std::optional{ + std::in_place, string_traits::from_string(text)}; + } + + static std::size_t size_buffer(std::optional const &value) noexcept + { + return pqxx::size_buffer(value.value()); + } +}; + + +template +inline constexpr bool is_unquoted_safe>{is_unquoted_safe}; + + +template struct nullness> +{ + static constexpr bool has_null = (nullness::has_null or ...); + static constexpr bool always_null = (nullness::always_null and ...); + static constexpr bool is_null(std::variant const &value) noexcept + { + return std::visit( + [](auto const &i) noexcept { + return nullness>::is_null(i); + }, + value); + } + + // We don't support `null()` for `std::variant`. + /** It would be technically possible to have a `null` in the case where just + * one of the types has a null, but it gets complicated and arbitrary. + */ + static constexpr std::variant null() = delete; +}; + + +template struct string_traits> +{ + static char * + into_buf(char *begin, char *end, std::variant const &value) + { + return std::visit( + [begin, end](auto const &i) { + return string_traits>::into_buf(begin, end, i); + }, + value); + } + static zview to_buf(char *begin, char *end, std::variant const &value) + { + return std::visit( + [begin, end](auto const &i) { + return string_traits>::to_buf(begin, end, i); + }, + value); + } + static std::size_t size_buffer(std::variant const &value) noexcept + { + return std::visit( + [](auto const &i) noexcept { return pqxx::size_buffer(i); }, value); + } + + /** There's no from_string for std::variant. We could have one with a rule + * like "pick the first type which fits the value," but we'd have to look + * into how natural that API feels to users. + */ + static std::variant from_string(std::string_view) = delete; +}; + + +template +inline constexpr format param_format(std::variant const &value) +{ + return std::visit([](auto &v) { return param_format(v); }, value); +} + + +template +inline constexpr bool is_unquoted_safe>{ + (is_unquoted_safe and ...)}; + + +template inline T from_string(std::stringstream const &text) +{ + return from_string(text.str()); +} + + +template<> struct string_traits +{ + static char *into_buf(char *, char *, std::nullptr_t) = delete; + + static constexpr zview + to_buf(char *, char *, std::nullptr_t const &) noexcept + { + return {}; + } + + static constexpr std::size_t size_buffer(std::nullptr_t = nullptr) noexcept + { + return 0; + } + static std::nullptr_t from_string(std::string_view) = delete; +}; + + +template<> struct string_traits +{ + static char *into_buf(char *, char *, std::nullopt_t) = delete; + + static constexpr zview + to_buf(char *, char *, std::nullopt_t const &) noexcept + { + return {}; + } + + static constexpr std::size_t size_buffer(std::nullopt_t) noexcept + { + return 0; + } + static std::nullopt_t from_string(std::string_view) = delete; +}; + + +template<> struct string_traits +{ + static char *into_buf(char *, char *, std::monostate) = delete; + + static constexpr zview + to_buf(char *, char *, std::monostate const &) noexcept + { + return {}; + } + + static constexpr std::size_t size_buffer(std::monostate) noexcept + { + return 0; + } + static std::monostate from_string(std::string_view) = delete; +}; + + +template<> inline constexpr bool is_unquoted_safe{true}; + + +template<> struct nullness +{ + static constexpr bool has_null = true; + static constexpr bool always_null = false; + static constexpr bool is_null(char const *t) noexcept + { + return t == nullptr; + } + static constexpr char const *null() noexcept { return nullptr; } +}; + + +/// String traits for C-style string ("pointer to char const"). +template<> struct string_traits +{ + static char const *from_string(std::string_view text) { return text.data(); } + + static zview to_buf(char *begin, char *end, char const *const &value) + { + return generic_to_buf(begin, end, value); + } + + static char *into_buf(char *begin, char *end, char const *const &value) + { + auto const space{end - begin}; + // Count the trailing zero, even though std::strlen() and friends don't. + auto const len{std::strlen(value) + 1}; + if (space < ptrdiff_t(len)) + throw conversion_overrun{ + "Could not copy string: buffer too small. " + + pqxx::internal::state_buffer_overrun(space, len)}; + std::memmove(begin, value, len); + return begin + len; + } + + static std::size_t size_buffer(char const *const &value) noexcept + { + return std::strlen(value) + 1; + } +}; + + +template<> struct nullness +{ + static constexpr bool has_null = true; + static constexpr bool always_null = false; + static constexpr bool is_null(char const *t) noexcept + { + return t == nullptr; + } + static constexpr char const *null() { return nullptr; } +}; + + +/// String traits for non-const C-style string ("pointer to char"). +template<> struct string_traits +{ + static char *into_buf(char *begin, char *end, char *const &value) + { + return string_traits::into_buf(begin, end, value); + } + static zview to_buf(char *begin, char *end, char *const &value) + { + return string_traits::to_buf(begin, end, value); + } + static std::size_t size_buffer(char *const &value) noexcept + { + return string_traits::size_buffer(value); + } + + /// Don't allow conversion to this type since it breaks const-safety. + static char *from_string(std::string_view) = delete; +}; + + +template struct nullness : no_null +{}; + + +/// String traits for C-style string constant ("array of char"). +/** @warning This assumes that every array-of-char is a C-style string literal. + * So, it must include a trailing zero. and it must have static duration. + */ +template struct string_traits +{ + static constexpr zview + to_buf(char *, char *, char const (&value)[N]) noexcept + { + return zview{value, N - 1}; + } + + static char *into_buf(char *begin, char *end, char const (&value)[N]) + { + if (internal::cmp_less(end - begin, size_buffer(value))) + throw conversion_overrun{ + "Could not convert char[] to string: too long for buffer."}; + std::memcpy(begin, value, N); + return begin + N; + } + static constexpr std::size_t size_buffer(char const (&)[N]) noexcept + { + return N; + } + + /// Don't allow conversion to this type. + static void from_string(std::string_view) = delete; +}; + + +template<> struct nullness : no_null +{}; + + +template<> struct string_traits +{ + static std::string from_string(std::string_view text) + { + return std::string{text}; + } + + static char *into_buf(char *begin, char *end, std::string const &value) + { + if (internal::cmp_greater_equal(std::size(value), end - begin)) + throw conversion_overrun{ + "Could not convert string to string: too long for buffer."}; + // Include the trailing zero. + value.copy(begin, std::size(value)); + begin[std::size(value)] = '\0'; + return begin + std::size(value) + 1; + } + + static zview to_buf(char *begin, char *end, std::string const &value) + { + return generic_to_buf(begin, end, value); + } + + static std::size_t size_buffer(std::string const &value) noexcept + { + return std::size(value) + 1; + } +}; + + +/// There's no real null for `std::string_view`. +/** I'm not sure how clear-cut this is: a `string_view` may have a null + * data pointer, which is analogous to a null `char` pointer. + */ +template<> struct nullness : no_null +{}; + + +/// String traits for `string_view`. +template<> struct string_traits +{ + static constexpr std::size_t + size_buffer(std::string_view const &value) noexcept + { + return std::size(value) + 1; + } + + static char *into_buf(char *begin, char *end, std::string_view const &value) + { + if (internal::cmp_greater_equal(std::size(value), end - begin)) + throw conversion_overrun{ + "Could not store string_view: too long for buffer."}; + value.copy(begin, std::size(value)); + begin[std::size(value)] = '\0'; + return begin + std::size(value) + 1; + } + + /// Don't convert to this type; it has nowhere to store its contents. + static std::string_view from_string(std::string_view) = delete; +}; + + +template<> struct nullness : no_null +{}; + + +/// String traits for `zview`. +template<> struct string_traits +{ + static constexpr std::size_t + size_buffer(std::string_view const &value) noexcept + { + return std::size(value) + 1; + } + + static char *into_buf(char *begin, char *end, zview const &value) + { + auto const size{std::size(value)}; + if (internal::cmp_less_equal(end - begin, std::size(value))) + throw conversion_overrun{"Not enough buffer space to store this zview."}; + value.copy(begin, size); + begin[size] = '\0'; + return begin + size + 1; + } + + static std::string_view to_buf(char *begin, char *end, zview const &value) + { + return {into_buf(begin, end, value), std::size(value)}; + } + + /// Don't convert to this type; it has nowhere to store its contents. + static zview from_string(std::string_view) = delete; +}; + + +template<> struct nullness : no_null +{}; + + +template<> struct string_traits +{ + static std::size_t size_buffer(std::stringstream const &) = delete; + + static std::stringstream from_string(std::string_view text) + { + std::stringstream stream; + stream.write(text.data(), std::streamsize(std::size(text))); + return stream; + } + + static char *into_buf(char *, char *, std::stringstream const &) = delete; + static std::string_view + to_buf(char *, char *, std::stringstream const &) = delete; +}; + + +template<> struct nullness +{ + static constexpr bool has_null = true; + static constexpr bool always_null = true; + static constexpr bool is_null(std::nullptr_t const &) noexcept + { + return true; + } + static constexpr std::nullptr_t null() noexcept { return nullptr; } +}; + + +template<> struct nullness +{ + static constexpr bool has_null = true; + static constexpr bool always_null = true; + static constexpr bool is_null(std::nullopt_t const &) noexcept + { + return true; + } + static constexpr std::nullopt_t null() noexcept { return std::nullopt; } +}; + + +template<> struct nullness +{ + static constexpr bool has_null = true; + static constexpr bool always_null = true; + static constexpr bool is_null(std::monostate const &) noexcept + { + return true; + } + static constexpr std::monostate null() noexcept { return {}; } +}; + + +template struct nullness> +{ + static constexpr bool has_null = true; + static constexpr bool always_null = false; + static constexpr bool is_null(std::unique_ptr const &t) noexcept + { + return not t or pqxx::is_null(*t); + } + static constexpr std::unique_ptr null() { return {}; } +}; + + +template +struct string_traits> +{ + static std::unique_ptr from_string(std::string_view text) + { + return std::make_unique(string_traits::from_string(text)); + } + + static char * + into_buf(char *begin, char *end, std::unique_ptr const &value) + { + return string_traits::into_buf(begin, end, *value); + } + + static zview + to_buf(char *begin, char *end, std::unique_ptr const &value) + { + if (value) + return string_traits::to_buf(begin, end, *value); + else + return {}; + } + + static std::size_t + size_buffer(std::unique_ptr const &value) noexcept + { + return pqxx::size_buffer(*value.get()); + } +}; + + +template +inline format param_format(std::unique_ptr const &value) +{ + return param_format(*value); +} + + +template +inline constexpr bool is_unquoted_safe>{ + is_unquoted_safe}; + + +template struct nullness> +{ + static constexpr bool has_null = true; + static constexpr bool always_null = false; + static constexpr bool is_null(std::shared_ptr const &t) noexcept + { + return not t or pqxx::is_null(*t); + } + static constexpr std::shared_ptr null() { return {}; } +}; + + +template struct string_traits> +{ + static std::shared_ptr from_string(std::string_view text) + { + return std::make_shared(string_traits::from_string(text)); + } + + static zview to_buf(char *begin, char *end, std::shared_ptr const &value) + { + return string_traits::to_buf(begin, end, *value); + } + static char * + into_buf(char *begin, char *end, std::shared_ptr const &value) + { + return string_traits::into_buf(begin, end, *value); + } + static std::size_t size_buffer(std::shared_ptr const &value) noexcept + { + return pqxx::size_buffer(*value); + } +}; + + +template format param_format(std::shared_ptr const &value) +{ + return param_format(*value); +} + + +template +inline constexpr bool is_unquoted_safe>{ + is_unquoted_safe}; + + +template<> +struct nullness> + : no_null> +{}; + + +#if defined(PQXX_HAVE_CONCEPTS) +template struct nullness : no_null +{}; + + +template inline constexpr format param_format(DATA const &) +{ + return format::binary; +} + + +template struct string_traits +{ + static std::size_t size_buffer(DATA const &value) noexcept + { + return internal::size_esc_bin(std::size(value)); + } + + static zview to_buf(char *begin, char *end, DATA const &value) + { + return generic_to_buf(begin, end, value); + } + + static char *into_buf(char *begin, char *end, DATA const &value) + { + auto const budget{size_buffer(value)}; + if (internal::cmp_less(end - begin, budget)) + throw conversion_overrun{ + "Not enough buffer space to escape binary data."}; + internal::esc_bin(value, begin); + return begin + budget; + } + + static DATA from_string(std::string_view text) + { + auto const size{pqxx::internal::size_unesc_bin(std::size(text))}; + std::basic_string buf; + buf.resize(size); + pqxx::internal::unesc_bin(text, reinterpret_cast(buf.data())); + return buf; + } +}; +#endif // PQXX_HAVE_CONCEPTS + + +template<> struct string_traits> +{ + static std::size_t + size_buffer(std::basic_string const &value) noexcept + { + return internal::size_esc_bin(std::size(value)); + } + + static zview + to_buf(char *begin, char *end, std::basic_string const &value) + { + return generic_to_buf(begin, end, value); + } + + static char * + into_buf(char *begin, char *end, std::basic_string const &value) + { + auto const budget{size_buffer(value)}; + if (internal::cmp_less(end - begin, budget)) + throw conversion_overrun{ + "Not enough buffer space to escape binary data."}; + internal::esc_bin(value, begin); + return begin + budget; + } + + static std::basic_string from_string(std::string_view text) + { + auto const size{pqxx::internal::size_unesc_bin(std::size(text))}; + std::basic_string buf; + buf.resize(size); + pqxx::internal::unesc_bin(text, reinterpret_cast(buf.data())); + return buf; + } +}; + + +template<> +inline constexpr format param_format(std::basic_string const &) +{ + return format::binary; +} + + +template<> +struct nullness> + : no_null> +{}; + + +template<> struct string_traits> +{ + static std::size_t + size_buffer(std::basic_string_view const &value) noexcept + { + return internal::size_esc_bin(std::size(value)); + } + + static zview to_buf( + char *begin, char *end, std::basic_string_view const &value) + { + return generic_to_buf(begin, end, value); + } + + static char *into_buf( + char *begin, char *end, std::basic_string_view const &value) + { + auto const budget{size_buffer(value)}; + if (internal::cmp_less(end - begin, budget)) + throw conversion_overrun{ + "Not enough buffer space to escape binary data."}; + internal::esc_bin(value, begin); + return begin + budget; + } + + // There's no from_string, because there's nobody to hold the data. +}; + +template<> +inline constexpr format param_format(std::basic_string_view const &) +{ + return format::binary; +} +} // namespace pqxx + + +namespace pqxx::internal +{ +/// String traits for SQL arrays. +template struct array_string_traits +{ +private: + using elt_type = strip_t>; + using elt_traits = string_traits; + static constexpr zview s_null{"NULL"}; + +public: + static zview to_buf(char *begin, char *end, Container const &value) + { + return generic_to_buf(begin, end, value); + } + + static char *into_buf(char *begin, char *end, Container const &value) + { + std::size_t const budget{size_buffer(value)}; + if (internal::cmp_less(end - begin, budget)) + throw conversion_overrun{ + "Not enough buffer space to convert array to string."}; + + char *here = begin; + *here++ = '{'; + + bool nonempty{false}; + for (auto const &elt : value) + { + if (is_null(elt)) + { + s_null.copy(here, std::size(s_null)); + here += std::size(s_null); + } + else if constexpr (is_sql_array) + { + // Render nested array in-place. Then erase the trailing zero. + here = elt_traits::into_buf(here, end, elt) - 1; + } + else if constexpr (is_unquoted_safe) + { + // No need to quote or escape. Just convert the value straight into + // its place in the array, and "backspace" the trailing zero. + here = elt_traits::into_buf(here, end, elt) - 1; + } + else + { + *here++ = '"'; + + // Use the tail end of the destination buffer as an intermediate + // buffer. + auto const elt_budget{pqxx::size_buffer(elt)}; + for (char const c : elt_traits::to_buf(end - elt_budget, end, elt)) + { + if (c == '\\' or c == '"') + *here++ = '\\'; + *here++ = c; + } + *here++ = '"'; + } + *here++ = array_separator; + nonempty = true; + } + + // Erase that last comma, if present. + if (nonempty) + here--; + + *here++ = '}'; + *here++ = '\0'; + + return here; + } + + static std::size_t size_buffer(Container const &value) noexcept + { + if constexpr (is_unquoted_safe) + return 3 + std::accumulate( + std::begin(value), std::end(value), std::size_t{}, + [](std::size_t acc, elt_type const &elt) { + return acc + + (pqxx::is_null(elt) ? + std::size(s_null) : + elt_traits::size_buffer(elt)) - + 1; + }); + else + return 3 + std::accumulate( + std::begin(value), std::end(value), std::size_t{}, + [](std::size_t acc, elt_type const &elt) { + // Opening and closing quotes, plus worst-case escaping, + // but don't count the trailing zeroes. + std::size_t const elt_size{ + pqxx::is_null(elt) ? std::size(s_null) : + elt_traits::size_buffer(elt) - 1}; + return acc + 2 * elt_size + 2; + }); + } + + // We don't yet support parsing of array types using from_string. Doing so + // would require a reference to the connection. +}; +} // namespace pqxx::internal + + +namespace pqxx +{ +template +struct nullness> : no_null> +{}; + + +template +struct string_traits> + : internal::array_string_traits> +{}; + + +/// We don't know how to pass array params in binary format, so pass as text. +template +inline constexpr format param_format(std::vector const &) +{ + return format::text; +} + + +/// A `std::vector` is a binary string. Other vectors are not. +template +inline constexpr format param_format(std::vector const &) +{ + return format::binary; +} + + +template inline constexpr bool is_sql_array>{true}; + + +template +struct nullness> : no_null> +{}; + + +template +struct string_traits> + : internal::array_string_traits> +{}; + + +/// We don't know how to pass array params in binary format, so pass as text. +template +inline constexpr format param_format(std::array const &) +{ + return format::text; +} + + +/// An array of `std::byte` is a binary string. +template +inline constexpr format param_format(std::array const &) +{ + return format::binary; +} + + +template +inline constexpr bool is_sql_array>{true}; +} // namespace pqxx + + +namespace pqxx +{ +template inline std::string to_string(T const &value) +{ + if (is_null(value)) + throw conversion_error{ + "Attempt to convert null " + type_name + " to a string."}; + + std::string buf; + // We can't just reserve() space; modifying the terminating zero leads to + // undefined behaviour. + buf.resize(size_buffer(value)); + auto const data{buf.data()}; + auto const end{ + string_traits::into_buf(data, data + std::size(buf), value)}; + buf.resize(static_cast(end - data - 1)); + return buf; +} + + +template<> inline std::string to_string(float const &value) +{ + return internal::to_string_float(value); +} +template<> inline std::string to_string(double const &value) +{ + return internal::to_string_float(value); +} +template<> inline std::string to_string(long double const &value) +{ + return internal::to_string_float(value); +} +template<> inline std::string to_string(std::stringstream const &value) +{ + return value.str(); +} + + +template inline void into_string(T const &value, std::string &out) +{ + if (is_null(value)) + throw conversion_error{ + "Attempt to convert null " + type_name + " to a string."}; + + // We can't just reserve() data; modifying the terminating zero leads to + // undefined behaviour. + out.resize(size_buffer(value) + 1); + auto const data{out.data()}; + auto const end{ + string_traits::into_buf(data, data + std::size(out), value)}; + out.resize(static_cast(end - data - 1)); +} +} // namespace pqxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encoding_group.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encoding_group.hxx new file mode 100644 index 000000000..e17736e5b --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encoding_group.hxx @@ -0,0 +1,60 @@ +/** Enum type for supporting encodings in libpqxx + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ENCODING_GROUP +#define PQXX_H_ENCODING_GROUP + +#include + +namespace pqxx::internal +{ +// Types of encodings supported by PostgreSQL, see +// https://www.postgresql.org/docs/current/static/multibyte.html#CHARSET-TABLE +enum class encoding_group +{ + // Handles all single-byte fixed-width encodings + MONOBYTE, + + // Multibyte encodings. + // Many of these can embed ASCII-like bytes inside multibyte characters, + // notably Big5, SJIS, SHIFT_JIS_2004, GP18030, GBK, JOHAB, UHC. + BIG5, + EUC_CN, + // TODO: Merge EUC_JP and EUC_JIS_2004? + EUC_JP, + EUC_JIS_2004, + EUC_KR, + EUC_TW, + GB18030, + GBK, + JOHAB, + MULE_INTERNAL, + // TODO: Merge SJIS and SHIFT_JIS_2004? + SJIS, + SHIFT_JIS_2004, + UHC, + UTF8, +}; + + +// TODO:: Can we just use string_view now? +/// Function type: "find the end of the current glyph." +/** This type of function takes a text buffer, and a location in that buffer, + * and returns the location one byte past the end of the current glyph. + * + * The start offset marks the beginning of the current glyph. It must fall + * within the buffer. + * + * There are multiple different glyph scanner implementations, for different + * kinds of encodings. + */ +using glyph_scanner_func = + std::size_t(char const buffer[], std::size_t buffer_len, std::size_t start); +} // namespace pqxx::internal + +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encodings.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encodings.hxx new file mode 100644 index 000000000..ba7fecc70 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encodings.hxx @@ -0,0 +1,90 @@ +/** Internal string encodings support for libpqxx + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ENCODINGS +#define PQXX_H_ENCODINGS + +#include "pqxx/internal/encoding_group.hxx" + +#include +#include + + +namespace pqxx::internal +{ +char const *name_encoding(int encoding_id); + +/// Convert libpq encoding enum or encoding name to its libpqxx group. +encoding_group enc_group(int /* libpq encoding ID */); +encoding_group enc_group(std::string_view); + + +/// Look up the glyph scanner function for a given encoding group. +/** To identify the glyph boundaries in a buffer, call this to obtain the + * scanner function appropriate for the buffer's encoding. Then, repeatedly + * call the scanner function to find the glyphs. + */ +PQXX_LIBEXPORT glyph_scanner_func *get_glyph_scanner(encoding_group); + + +// TODO: For ASCII search, treat UTF8/EUC_*/MULE_INTERNAL as MONOBYTE. + +/// Find any of the ASCII characters `NEEDLE` in `haystack`. +/** Scans through `haystack` until it finds a single-byte character that + * matches any value in `NEEDLE`. + * + * If it finds one, returns its offset. If not, returns the end of the + * haystack. + */ +template +inline std::size_t find_char( + glyph_scanner_func *scanner, std::string_view haystack, + std::size_t here = 0u) +{ + auto const sz{std::size(haystack)}; + auto const data{std::data(haystack)}; + while (here < sz) + { + auto next{scanner(data, sz, here)}; + // (For some reason gcc had a problem with a right-fold here. But clang + // was fine.) + if ((... or (data[here] == NEEDLE))) + { + // Also check against a multibyte character starting with a bytes which + // just happens to match one of the ASCII bytes we're looking for. It'd + // be cleaner to check that first, but either works. So, let's apply the + // most selective filter first and skip this check in almost all cases. + if (next == here + 1) + return here; + } + + // Nope, no hit. Move on. + here = next; + } + return sz; +} + + +/// Iterate over the glyphs in a buffer. +/** Scans the glyphs in the buffer, and for each, passes its begin and its + * one-past-end pointers to `callback`. + */ +template +inline void for_glyphs( + encoding_group enc, CALLABLE callback, char const buffer[], + std::size_t buffer_len, std::size_t start = 0) +{ + auto const scan{get_glyph_scanner(enc)}; + for (std::size_t here = start, next; here < buffer_len; here = next) + { + next = scan(buffer, buffer_len, here); + callback(buffer + here, buffer + next); + } +} +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-errorhandler.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-errorhandler.hxx new file mode 100644 index 000000000..ffc12a6cf --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-errorhandler.hxx @@ -0,0 +1,26 @@ +#include + +namespace pqxx +{ +class connection; +class errorhandler; +} // namespace pqxx + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_errorhandler : callgate +{ + friend class pqxx::errorhandler; + + connection_errorhandler(reference x) : super(x) {} + + void register_errorhandler(errorhandler *h) + { + home().register_errorhandler(h); + } + void unregister_errorhandler(errorhandler *h) + { + home().unregister_errorhandler(h); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-largeobject.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-largeobject.hxx new file mode 100644 index 000000000..49feaf9e6 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-largeobject.hxx @@ -0,0 +1,35 @@ +#include + +#include +#include + +namespace pqxx +{ +class blob; +class largeobject; +} // namespace pqxx + + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_largeobject : callgate +{ + friend class pqxx::blob; + friend class pqxx::largeobject; + + connection_largeobject(reference x) : super(x) {} + + pq::PGconn *raw_connection() const { return home().raw_connection(); } +}; + + +class PQXX_PRIVATE const_connection_largeobject : callgate +{ + friend class pqxx::blob; + friend class pqxx::largeobject; + + const_connection_largeobject(reference x) : super(x) {} + + std::string error_message() const { return home().err_msg(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-notification_receiver.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-notification_receiver.hxx new file mode 100644 index 000000000..0bcb2db17 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-notification_receiver.hxx @@ -0,0 +1,29 @@ +#include + +#include "pqxx/connection.hxx" + + +namespace pqxx +{ +class notification_receiver; +} + + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_notification_receiver : callgate +{ + friend class pqxx::notification_receiver; + + connection_notification_receiver(reference x) : super(x) {} + + void add_receiver(notification_receiver *receiver) + { + home().add_receiver(receiver); + } + void remove_receiver(notification_receiver *receiver) noexcept + { + home().remove_receiver(receiver); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-pipeline.hxx new file mode 100644 index 000000000..c6ae6e17a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-pipeline.hxx @@ -0,0 +1,23 @@ +#include "pqxx/internal/libpq-forward.hxx" +#include + +#include "pqxx/pipeline.hxx" + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_pipeline : callgate +{ + friend class pqxx::pipeline; + + connection_pipeline(reference x) : super(x) {} + + void start_exec(char const query[]) { home().start_exec(query); } + pqxx::internal::pq::PGresult *get_result() { return home().get_result(); } + void cancel_query() { home().cancel_query(); } + + bool consume_input() noexcept { return home().consume_input(); } + bool is_busy() const noexcept { return home().is_busy(); } + + int encoding_id() { return home().encoding_id(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-sql_cursor.hxx new file mode 100644 index 000000000..51a889844 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-sql_cursor.hxx @@ -0,0 +1,19 @@ +#include + +namespace pqxx::internal +{ +class sql_cursor; +} + + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_sql_cursor : callgate +{ + friend class pqxx::internal::sql_cursor; + + connection_sql_cursor(reference x) : super(x) {} + + result exec(char const query[]) { return home().exec(query); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_from.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_from.hxx new file mode 100644 index 000000000..8961e7146 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_from.hxx @@ -0,0 +1,15 @@ +#include + +#include "pqxx/connection.hxx" + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_stream_from : callgate +{ + friend class pqxx::stream_from; + + connection_stream_from(reference x) : super{x} {} + + auto read_copy_line() { return home().read_copy_line(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_to.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_to.hxx new file mode 100644 index 000000000..a6974fb21 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_to.hxx @@ -0,0 +1,17 @@ +#include + +#include "pqxx/stream_to.hxx" + + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_stream_to : callgate +{ + friend class pqxx::stream_to; + + connection_stream_to(reference x) : super(x) {} + + void write_copy_line(std::string_view line) { home().write_copy_line(line); } + void end_copy_write() { home().end_copy_write(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-transaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-transaction.hxx new file mode 100644 index 000000000..74d659253 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-transaction.hxx @@ -0,0 +1,44 @@ +#include + +namespace pqxx +{ +class connection; +} + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_transaction : callgate +{ + friend class pqxx::transaction_base; + + connection_transaction(reference x) : super(x) {} + + template result exec(STRING query, std::string_view desc) + { + return home().exec(query, desc); + } + + void register_transaction(transaction_base *t) + { + home().register_transaction(t); + } + void unregister_transaction(transaction_base *t) noexcept + { + home().unregister_transaction(t); + } + + auto read_copy_line() { return home().read_copy_line(); } + void write_copy_line(std::string_view line) { home().write_copy_line(line); } + void end_copy_write() { home().end_copy_write(); } + + result exec_prepared(zview statement, internal::c_params const &args) + { + return home().exec_prepared(statement, args); + } + + result exec_params(zview query, internal::c_params const &args) + { + return home().exec_params(query, args); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/errorhandler-connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/errorhandler-connection.hxx new file mode 100644 index 000000000..5560cedec --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/errorhandler-connection.hxx @@ -0,0 +1,13 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE errorhandler_connection : callgate +{ + friend class pqxx::connection; + + errorhandler_connection(reference x) : super(x) {} + + void unregister() noexcept { home().unregister(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx new file mode 100644 index 000000000..296d22145 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx @@ -0,0 +1,24 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE icursor_iterator_icursorstream : callgate +{ + friend class pqxx::icursorstream; + + icursor_iterator_icursorstream(reference x) : super(x) {} + + icursor_iterator::difference_type pos() const noexcept + { + return home().pos(); + } + + icursor_iterator *get_prev() { return home().m_prev; } + void set_prev(icursor_iterator *i) { home().m_prev = i; } + + icursor_iterator *get_next() { return home().m_next; } + void set_next(icursor_iterator *i) { home().m_next = i; } + + void fill(result const &r) { home().fill(r); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx new file mode 100644 index 000000000..56056d5ef --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx @@ -0,0 +1,32 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE icursorstream_icursor_iterator : callgate +{ + friend class pqxx::icursor_iterator; + + icursorstream_icursor_iterator(reference x) : super(x) {} + + void insert_iterator(icursor_iterator *i) noexcept + { + home().insert_iterator(i); + } + + void remove_iterator(icursor_iterator *i) const noexcept + { + home().remove_iterator(i); + } + + icursorstream::size_type forward() { return home().forward(); } + icursorstream::size_type forward(icursorstream::size_type n) + { + return home().forward(n); + } + + void service_iterators(icursorstream::difference_type p) + { + home().service_iterators(p); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-connection.hxx new file mode 100644 index 000000000..daa0808c0 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-connection.hxx @@ -0,0 +1,14 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE result_connection : callgate +{ + friend class pqxx::connection; + + result_connection(reference x) : super(x) {} + + operator bool() const { return bool(home()); } + bool operator!() const { return not home(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-creation.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-creation.hxx new file mode 100644 index 000000000..3d9205f2c --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-creation.hxx @@ -0,0 +1,24 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE result_creation : callgate +{ + friend class pqxx::connection; + friend class pqxx::pipeline; + + result_creation(reference x) : super(x) {} + + static result create( + internal::pq::PGresult *rhs, std::shared_ptr const &query, + encoding_group enc) + { + return result(rhs, query, enc); + } + + void check_status(std::string_view desc = ""sv) const + { + return home().check_status(desc); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-pipeline.hxx new file mode 100644 index 000000000..3ebe436d2 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-pipeline.hxx @@ -0,0 +1,16 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE result_pipeline : callgate +{ + friend class pqxx::pipeline; + + result_pipeline(reference x) : super(x) {} + + std::shared_ptr query_ptr() const + { + return home().query_ptr(); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-sql_cursor.hxx new file mode 100644 index 000000000..78b450739 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-sql_cursor.hxx @@ -0,0 +1,13 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE result_sql_cursor : callgate +{ + friend class pqxx::internal::sql_cursor; + + result_sql_cursor(reference x) : super(x) {} + + char const *cmd_status() const noexcept { return home().cmd_status(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-sql_cursor.hxx new file mode 100644 index 000000000..4ed78dc93 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-sql_cursor.hxx @@ -0,0 +1,10 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE transaction_sql_cursor : callgate +{ + friend class pqxx::internal::sql_cursor; + transaction_sql_cursor(reference x) : super(x) {} +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-transaction_focus.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-transaction_focus.hxx new file mode 100644 index 000000000..ca7939a99 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-transaction_focus.hxx @@ -0,0 +1,30 @@ +#include + +#include "pqxx/transaction_base.hxx" + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE transaction_transaction_focus : callgate +{ + friend class pqxx::transaction_focus; + + transaction_transaction_focus(reference x) : super(x) {} + + void register_focus(transaction_focus *focus) + { + home().register_focus(focus); + } + void unregister_focus(transaction_focus *focus) noexcept + { + home().unregister_focus(focus); + } + void register_pending_error(zview error) + { + home().register_pending_error(error); + } + void register_pending_error(std::string &&error) + { + home().register_pending_error(std::move(error)); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-post.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-post.hxx new file mode 100644 index 000000000..ff6bf8986 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-post.hxx @@ -0,0 +1,22 @@ +/* Compiler deficiency workarounds for compiling libpqxx headers. + * + * To be included at the end of each libpqxx header, in order to restore the + * client program's settings. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +// NO GUARDS HERE! This code should be executed every time! + +#if defined(_MSC_VER) +# pragma warning(pop) // Restore compiler's warning state +#endif + +#if !defined(PQXX_HEADER_PRE) +# error "Include pqxx/internal/header-post.hxx AFTER its 'pre' counterpart." +#endif + +#undef PQXX_HEADER_PRE diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-pre.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-pre.hxx new file mode 100644 index 000000000..abc1a398d --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-pre.hxx @@ -0,0 +1,169 @@ +/* Compiler settings for compiling libpqxx headers, and workarounds for all. + * + * Include this before including any other libpqxx headers from within libpqxx. + * And to balance it out, also include header-post.hxx at the end of the batch + * of headers. + * + * The public libpqxx headers (e.g. ``) include this already; + * there's no need to do this from within an application. + * + * Include this file at the highest aggregation level possible to avoid nesting + * and to keep things simple. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ + +// NO GUARD HERE! This part should be included every time this file is. +#if defined(_MSC_VER) + +// Save compiler's warning state, and set warning level 4 for maximum +// sensitivity to warnings. +# pragma warning(push, 4) + +// Visual C++ generates some entirely unreasonable warnings. Disable them. +// Copy constructor could not be generated. +# pragma warning(disable : 4511) +// Assignment operator could not be generated. +# pragma warning(disable : 4512) +// Can't expose outside classes without exporting them. Except the MSVC docs +// say please ignore the warning if it's a standard library class. +# pragma warning(disable : 4251) +// Can't derive library classes from outside classes without exporting them. +// Except the MSVC docs say please ignore the warning if the parent class is +// in the standard library. +# pragma warning(disable : 4275) +// Can't inherit from non-exported class. +# pragma warning(disable : 4275) + +#endif // _MSC_VER + + +#if defined(PQXX_HEADER_PRE) +# error "Avoid nesting #include of pqxx/internal/header-pre.hxx." +#endif + +#define PQXX_HEADER_PRE + + +// Workarounds & definitions that need to be included even in library's headers +#include "pqxx/config-public-compiler.h" + +// Enable ISO-646 alternative operaotr representations: "and" instead of "&&" +// etc. on older compilers. C++20 removes this header. +#if __has_include() +# include +#endif + + +#if defined(PQXX_HAVE_GCC_PURE) +/// Declare function "pure": no side effects, only reads globals and its args. +# define PQXX_PURE __attribute__((pure)) +#else +# define PQXX_PURE /* pure */ +#endif + + +#if defined(__GNUC__) +/// Tell the compiler to optimise a function for size, not speed. +# define PQXX_COLD __attribute__((cold)) +#else +# define PQXX_COLD /* cold */ +#endif + + +// Workarounds for Windows +#ifdef _WIN32 + +/* For now, export DLL symbols if _DLL is defined. This is done automatically + * by the compiler when linking to the dynamic version of the runtime library, + * according to "gzh" + */ +# if defined(PQXX_SHARED) && !defined(PQXX_LIBEXPORT) +# define PQXX_LIBEXPORT __declspec(dllimport) +# endif // PQXX_SHARED && !PQXX_LIBEXPORT + + +// Workarounds for Microsoft Visual C++ +# ifdef _MSC_VER + +// Suppress vtables on abstract classes. +# define PQXX_NOVTABLE __declspec(novtable) + +// Automatically link with the appropriate libpq (static or dynamic, debug or +// release). The default is to use the release DLL. Define PQXX_PQ_STATIC to +// link to a static version of libpq, and _DEBUG to link to a debug version. +// The two may be combined. +# if defined(PQXX_AUTOLINK) +# if defined(PQXX_PQ_STATIC) +# ifdef _DEBUG +# pragma comment(lib, "libpqd") +# else +# pragma comment(lib, "libpq") +# endif +# else +# ifdef _DEBUG +# pragma comment(lib, "libpqddll") +# else +# pragma comment(lib, "libpqdll") +# endif +# endif +# endif + +// If we're not compiling libpqxx itself, automatically link with the +// appropriate libpqxx library. To link with the libpqxx DLL, define +// PQXX_SHARED; the default is to link with the static library. A static link +// is the recommended practice. +// +// The preprocessor macro PQXX_INTERNAL is used to detect whether we +// are compiling the libpqxx library itself. When you compile the library +// yourself using your own project file, make sure to include this macro. +# if defined(PQXX_AUTOLINK) && !defined(PQXX_INTERNAL) +# ifdef PQXX_SHARED +# ifdef _DEBUG +# pragma comment(lib, "libpqxxD") +# else +# pragma comment(lib, "libpqxx") +# endif +# else // !PQXX_SHARED +# ifdef _DEBUG +# pragma comment(lib, "libpqxx_staticD") +# else +# pragma comment(lib, "libpqxx_static") +# endif +# endif +# endif + +# endif // _MSC_VER + +#elif defined(PQXX_HAVE_GCC_VISIBILITY) // !_WIN32 + +# define PQXX_LIBEXPORT __attribute__((visibility("default"))) +# define PQXX_PRIVATE __attribute__((visibility("hidden"))) + +#endif // PQXX_HAVE_GCC_VISIBILITY + + +#ifndef PQXX_LIBEXPORT +# define PQXX_LIBEXPORT /* libexport */ +#endif + +#ifndef PQXX_PRIVATE +# define PQXX_PRIVATE /* private */ +#endif + +#ifndef PQXX_NOVTABLE +# define PQXX_NOVTABLE /* novtable */ +#endif + +// C++20: Assume support. +#if defined(PQXX_HAVE_LIKELY) +# define PQXX_LIKELY [[likely]] +# define PQXX_UNLIKELY [[unlikely]] +#else +# define PQXX_LIKELY /* [[likely]] */ +# define PQXX_UNLIKELY /* [[unlikely]] */ +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-post.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-post.hxx new file mode 100644 index 000000000..cebcf0594 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-post.hxx @@ -0,0 +1,15 @@ +/// End a code block started by "ignore-deprecated-pre.hxx". + +#if !defined(PQXX_IGNORING_DEPRECATED) +# error "Ended an 'ignore-deprecated' block while none was active." +#endif + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif // __GNUC__ + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#undef PQXX_IGNORING_DEPRECATED diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-pre.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-pre.hxx new file mode 100644 index 000000000..8ac57afaa --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-pre.hxx @@ -0,0 +1,28 @@ +/** Start a block of deprecated code which may call other deprecated code. + * + * Most compilers will emit warnings when deprecated code is invoked from + * non-deprecated code. But some compilers (notably gcc) will always emit the + * warning even when the calling code is also deprecated. + * + * This header starts a block where those warnings are suppressed. It can be + * included inside a code block. + * + * Always match the #include with a closing #include of + * "ignore-deprecated-post.hxx". To avoid mistakes, keep the enclosed area as + * small as possible. + */ +#if defined(PQXX_IGNORING_DEPRECATED) +# error "Started an 'ignore-deprecated' block inside another." +#endif + +#define PQXX_IGNORING_DEPRECATED + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif // __GNUC__ + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4996) +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/libpq-forward.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/libpq-forward.hxx new file mode 100644 index 000000000..9e74f79ec --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/libpq-forward.hxx @@ -0,0 +1,31 @@ +/** Minimal forward declarations of libpq types needed in libpqxx headers. + * + * DO NOT INCLUDE THIS FILE when building client programs. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +extern "C" +{ + struct pg_conn; + struct pg_result; + struct pgNotify; +} + +/// Forward declarations of libpq types as needed in libpqxx headers. +namespace pqxx::internal::pq +{ +using PGconn = pg_conn; +using PGresult = pg_result; +using PGnotify = pgNotify; +using PQnoticeProcessor = void (*)(void *, char const *); +} // namespace pqxx::internal::pq + +namespace pqxx +{ +/// PostgreSQL database row identifier. +using oid = unsigned int; +} // namespace pqxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iter.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iter.hxx new file mode 100644 index 000000000..1fa1f7d8a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iter.hxx @@ -0,0 +1,124 @@ +/** Result loops. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_RESULT_ITER +#define PQXX_H_RESULT_ITER + +#include + +#include "pqxx/strconv.hxx" + +namespace pqxx +{ +class result; +} // namespace pqxx + + +namespace pqxx::internal +{ +// C++20: Replace with generator? +/// Iterator for looped unpacking of a result. +template class result_iter +{ +public: + using value_type = std::tuple; + + /// Construct an "end" iterator. + result_iter() = default; + + explicit result_iter(result const &home) : + m_home{&home}, m_size{std::size(home)} + { + if (not std::empty(home)) + read(); + } + result_iter(result_iter const &) = default; + + result_iter &operator++() + { + m_index++; + if (m_index >= m_size) + m_home = nullptr; + else + read(); + return *this; + } + + /// Comparison only works for comparing to end(). + bool operator==(result_iter const &rhs) const + { + return m_home == rhs.m_home; + } + bool operator!=(result_iter const &rhs) const { return not(*this == rhs); } + + value_type const &operator*() const { return m_value; } + +private: + void read() { (*m_home)[m_index].convert(m_value); } + + result const *m_home{nullptr}; + result::size_type m_index{0}; + result::size_type m_size; + value_type m_value; +}; + + +template class result_iteration +{ +public: + using iterator = result_iter; + explicit result_iteration(result const &home) : m_home{home} + { + constexpr auto tup_size{sizeof...(TYPE)}; + if (home.columns() != tup_size) + throw usage_error{internal::concat( + "Tried to extract ", to_string(tup_size), + " field(s) from a result with ", to_string(home.columns()), + " column(s).")}; + } + iterator begin() const + { + if (std::size(m_home) == 0) + return end(); + else + return iterator{m_home}; + } + iterator end() const { return {}; } + +private: + pqxx::result const &m_home; +}; +} // namespace pqxx::internal + + +template inline auto pqxx::result::iter() const +{ + return pqxx::internal::result_iteration{*this}; +} + + +template +inline void pqxx::result::for_each(CALLABLE &&func) const +{ + using args_tuple = internal::args_t; + constexpr auto sz{std::tuple_size_v}; + static_assert( + sz > 0, + "Callback for for_each must take parameters, one for each column in the " + "result."); + + auto const cols{this->columns()}; + if (sz != cols) + throw usage_error{internal::concat( + "Callback to for_each takes ", sz, "parameter", (sz == 1) ? "" : "s", + ", but result set has ", cols, "field", (cols == 1) ? "" : "s", ".")}; + + using pass_tuple = pqxx::internal::strip_types_t; + for (auto const r : *this) std::apply(func, r.as_tuple()); +} +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iterator.hxx new file mode 100644 index 000000000..3f27a1d3f --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iterator.hxx @@ -0,0 +1,389 @@ +/* Definitions for the pqxx::result class and support classes. + * + * pqxx::result represents the set of result rows from a database query. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_RESULT_ITERATOR +#define PQXX_H_RESULT_ITERATOR + +#include "pqxx/row.hxx" + + +/* Result iterator. + * + * Don't include this header from your own application; it is included for you + * by other libpqxx headers. + */ + +namespace pqxx +{ +/// Iterator for rows in a result. Use as result::const_iterator. +/** A result, once obtained, cannot be modified. Therefore there is no + * plain iterator type for result. However its const_iterator type can be + * used to inspect its rows without changing them. + */ +class PQXX_LIBEXPORT const_result_iterator : public row +{ +public: + using iterator_category = std::random_access_iterator_tag; + using value_type = row const; + using pointer = row const *; + using reference = row; + using size_type = result_size_type; + using difference_type = result_difference_type; + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + /// Create an iterator, but in an unusable state. + const_result_iterator() noexcept = default; + /// Copy an iterator. + const_result_iterator(const_result_iterator const &) noexcept = default; + /// Move an iterator. + const_result_iterator(const_result_iterator &&) noexcept = default; + + /// Begin iterating a @ref row. + const_result_iterator(row const &t) noexcept : row{t} {} +#include "pqxx/internal/ignore-deprecated-post.hxx" + + /** + * @name Dereferencing operators + * + * An iterator "points to" its own row, which is also itself. This makes it + * easy to address a @ref result as a two-dimensional container, without + * going through the intermediate step of dereferencing the iterator. It + * makes the interface similar to C pointer/array semantics. + * + * IIRC Alex Stepanov, the inventor of the STL, once remarked that having + * this as standard behaviour for pointers would be useful in some + * algorithms. So even if this makes me look foolish, I would seem to be in + * distinguished company. + */ + //@{ + /// Dereference the iterator. + [[nodiscard]] pointer operator->() const { return this; } + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + /// Dereference the iterator. + [[nodiscard]] reference operator*() const { return *this; } +#include "pqxx/internal/ignore-deprecated-post.hxx" + //@} + + /** + * @name Field access + */ + //@{ + using row::back; + using row::front; + using row::operator[]; + using row::at; + using row::rownumber; + //@} + + /** + * @name Manipulations + */ + //@{ + const_result_iterator &operator=(const_result_iterator const &rhs) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + row::operator=(rhs); +#include "pqxx/internal/ignore-deprecated-post.hxx" + return *this; + } + + const_result_iterator &operator=(const_result_iterator &&rhs) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + row::operator=(std::move(rhs)); +#include "pqxx/internal/ignore-deprecated-post.hxx" + return *this; + } + + const_result_iterator operator++(int); + const_result_iterator &operator++() + { + ++m_index; + return *this; + } + const_result_iterator operator--(int); + const_result_iterator &operator--() + { + --m_index; + return *this; + } + + const_result_iterator &operator+=(difference_type i) + { + m_index += i; + return *this; + } + const_result_iterator &operator-=(difference_type i) + { + m_index -= i; + return *this; + } + + /// Interchange two iterators in an exception-safe manner. + void swap(const_result_iterator &other) noexcept + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + row::swap(other); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + //@} + + /** + * @name Comparisons + */ + //@{ + [[nodiscard]] bool operator==(const_result_iterator const &i) const + { + return m_index == i.m_index; + } + [[nodiscard]] bool operator!=(const_result_iterator const &i) const + { + return m_index != i.m_index; + } + [[nodiscard]] bool operator<(const_result_iterator const &i) const + { + return m_index < i.m_index; + } + [[nodiscard]] bool operator<=(const_result_iterator const &i) const + { + return m_index <= i.m_index; + } + [[nodiscard]] bool operator>(const_result_iterator const &i) const + { + return m_index > i.m_index; + } + [[nodiscard]] bool operator>=(const_result_iterator const &i) const + { + return m_index >= i.m_index; + } + //@} + + /** + * @name Arithmetic operators + */ + //@{ + [[nodiscard]] inline const_result_iterator operator+(difference_type) const; + friend const_result_iterator + operator+(difference_type, const_result_iterator const &); + [[nodiscard]] inline const_result_iterator operator-(difference_type) const; + [[nodiscard]] inline difference_type + operator-(const_result_iterator const &) const; + //@} + +private: + friend class pqxx::result; + const_result_iterator(pqxx::result const *r, result_size_type i) noexcept : + row{*r, i, r->columns()} + {} +}; + + +/// Reverse iterator for result. Use as result::const_reverse_iterator. +class PQXX_LIBEXPORT const_reverse_result_iterator + : private const_result_iterator +{ +public: + using super = const_result_iterator; + using iterator_type = const_result_iterator; + using iterator_type::difference_type; + using iterator_type::iterator_category; + using iterator_type::pointer; + using value_type = iterator_type::value_type; + using reference = iterator_type::reference; + + /// Create an iterator, but in an unusable state. + const_reverse_result_iterator() = default; + /// Copy an iterator. + const_reverse_result_iterator(const_reverse_result_iterator const &rhs) = + default; + /// Copy a reverse iterator from a regular iterator. + explicit const_reverse_result_iterator(const_result_iterator const &rhs) : + const_result_iterator{rhs} + { + super::operator--(); + } + + /// Move a regular iterator into a reverse iterator. + explicit const_reverse_result_iterator(const_result_iterator const &&rhs) : + const_result_iterator{std::move(rhs)} + { + super::operator--(); + } + + /// Return the underlying "regular" iterator (as per standard library). + [[nodiscard]] PQXX_PURE const_result_iterator base() const noexcept; + + /** + * @name Dereferencing operators + */ + //@{ + /// Dereference iterator. + using const_result_iterator::operator->; + /// Dereference iterator. + using const_result_iterator::operator*; + //@} + + /** + * @name Field access + */ + //@{ + using const_result_iterator::back; + using const_result_iterator::front; + using const_result_iterator::operator[]; + using const_result_iterator::at; + using const_result_iterator::rownumber; + //@} + + /** + * @name Manipulations + */ + //@{ + const_reverse_result_iterator & + operator=(const_reverse_result_iterator const &r) + { + iterator_type::operator=(r); + return *this; + } + const_reverse_result_iterator &operator=(const_reverse_result_iterator &&r) + { + iterator_type::operator=(std::move(r)); + return *this; + } + const_reverse_result_iterator &operator++() + { + iterator_type::operator--(); + return *this; + } + const_reverse_result_iterator operator++(int); + const_reverse_result_iterator &operator--() + { + iterator_type::operator++(); + return *this; + } + const_reverse_result_iterator operator--(int); + const_reverse_result_iterator &operator+=(difference_type i) + { + iterator_type::operator-=(i); + return *this; + } + const_reverse_result_iterator &operator-=(difference_type i) + { + iterator_type::operator+=(i); + return *this; + } + + void swap(const_reverse_result_iterator &other) noexcept + { + const_result_iterator::swap(other); + } + //@} + + /** + * @name Arithmetic operators + */ + //@{ + [[nodiscard]] const_reverse_result_iterator + operator+(difference_type i) const + { + return const_reverse_result_iterator(base() - i); + } + [[nodiscard]] const_reverse_result_iterator operator-(difference_type i) + { + return const_reverse_result_iterator(base() + i); + } + [[nodiscard]] difference_type + operator-(const_reverse_result_iterator const &rhs) const + { + return rhs.const_result_iterator::operator-(*this); + } + //@} + + /** + * @name Comparisons + */ + //@{ + [[nodiscard]] bool + operator==(const_reverse_result_iterator const &rhs) const noexcept + { + return iterator_type::operator==(rhs); + } + [[nodiscard]] bool + operator!=(const_reverse_result_iterator const &rhs) const noexcept + { + return not operator==(rhs); + } + + [[nodiscard]] bool operator<(const_reverse_result_iterator const &rhs) const + { + return iterator_type::operator>(rhs); + } + [[nodiscard]] bool operator<=(const_reverse_result_iterator const &rhs) const + { + return iterator_type::operator>=(rhs); + } + [[nodiscard]] bool operator>(const_reverse_result_iterator const &rhs) const + { + return iterator_type::operator<(rhs); + } + [[nodiscard]] bool operator>=(const_reverse_result_iterator const &rhs) const + { + return iterator_type::operator<=(rhs); + } + //@} +}; + + +inline const_result_iterator +const_result_iterator::operator+(result::difference_type o) const +{ + return {&m_result, size_type(result::difference_type(m_index) + o)}; +} + +inline const_result_iterator +operator+(result::difference_type o, const_result_iterator const &i) +{ + return i + o; +} + +inline const_result_iterator +const_result_iterator::operator-(result::difference_type o) const +{ + return {&m_result, result_size_type(result::difference_type(m_index) - o)}; +} + +inline result::difference_type +const_result_iterator::operator-(const const_result_iterator &i) const +{ + return result::difference_type(num() - i.num()); +} + +inline const_result_iterator result::end() const noexcept +{ + return {this, size()}; +} + + +inline const_result_iterator result::cend() const noexcept +{ + return end(); +} + + +inline const_reverse_result_iterator +operator+(result::difference_type n, const_reverse_result_iterator const &i) +{ + return const_reverse_result_iterator{i.base() - n}; +} + +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/sql_cursor.hxx new file mode 100644 index 000000000..a26d06306 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/sql_cursor.hxx @@ -0,0 +1,118 @@ +/** Internal wrapper for SQL cursors. Supports higher-level cursor classes. + * + * DO NOT INCLUDE THIS FILE DIRECTLY. Other headers include it for you. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_SQL_CURSOR +#define PQXX_H_SQL_CURSOR + +namespace pqxx::internal +{ +/// Cursor with SQL positioning semantics. +/** Thin wrapper around an SQL cursor, with SQL's ideas of positioning. + * + * SQL cursors have pre-increment/pre-decrement semantics, with on either end + * of the result set a special position that does not repesent a row. This + * class models SQL cursors for the purpose of implementing more C++-like + * semantics on top. + * + * Positions of actual rows are numbered starting at 1. Position 0 exists but + * does not refer to a row. There is a similar non-row position at the end of + * the result set. + * + * Don't use this at home. You deserve better. Use the stateles_cursor + * instead. + */ +class PQXX_LIBEXPORT sql_cursor : public cursor_base +{ +public: + sql_cursor( + transaction_base &t, std::string_view query, std::string_view cname, + cursor_base::access_policy ap, cursor_base::update_policy up, + cursor_base::ownership_policy op, bool hold); + + sql_cursor( + transaction_base &t, std::string_view cname, + cursor_base::ownership_policy op); + + ~sql_cursor() noexcept { close(); } + + result fetch(difference_type rows, difference_type &displacement); + result fetch(difference_type rows) + { + difference_type d = 0; + return fetch(rows, d); + } + difference_type move(difference_type rows, difference_type &displacement); + difference_type move(difference_type rows) + { + difference_type d = 0; + return move(rows, d); + } + + /// Current position, or -1 for unknown + /** + * The starting position, just before the first row, counts as position zero. + * + * Position may be unknown if (and only if) this cursor was adopted, and has + * never hit its starting position (position zero). + */ + difference_type pos() const noexcept { return m_pos; } + + /// End position, or -1 for unknown + /** + * Returns the final position, just after the last row in the result set. The + * starting position, just before the first row, counts as position zero. + * + * End position is unknown until it is encountered during use. + */ + difference_type endpos() const noexcept { return m_endpos; } + + /// Return zero-row result for this cursor. + result const &empty_result() const noexcept { return m_empty_result; } + + void close() noexcept; + +private: + difference_type adjust(difference_type hoped, difference_type actual); + static std::string stridestring(difference_type); + /// Initialize cached empty result. Call only at beginning or end! + void init_empty_result(transaction_base &); + + /// Connection in which this cursor lives. + connection &m_home; + + /// Zero-row result from this cursor (or plain empty one if cursor is + /// adopted) + result m_empty_result; + + result m_cached_current_row; + + /// Is this cursor adopted (as opposed to created by this cursor object)? + bool m_adopted; + + /// Will this cursor object destroy its SQL cursor when it dies? + cursor_base::ownership_policy m_ownership; + + /// At starting position (-1), somewhere in the middle (0), or past end (1) + int m_at_end; + + /// Position, or -1 for unknown + difference_type m_pos; + + /// End position, or -1 for unknown + difference_type m_endpos = -1; +}; + + +PQXX_LIBEXPORT result_size_type obtain_stateless_cursor_size(sql_cursor &); +PQXX_LIBEXPORT result stateless_cursor_retrieve( + sql_cursor &, result::difference_type size, + result::difference_type begin_pos, result::difference_type end_pos); +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/statement_parameters.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/statement_parameters.hxx new file mode 100644 index 000000000..b078bf6e0 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/statement_parameters.hxx @@ -0,0 +1,131 @@ +/** Common implementation for statement parameter lists. + * + * These are used for both prepared statements and parameterized statements. + * + * DO NOT INCLUDE THIS FILE DIRECTLY. Other headers include it for you. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_STATEMENT_PARAMETER +#define PQXX_H_STATEMENT_PARAMETER + +#include +#include +#include +#include +#include + +#include "pqxx/binarystring.hxx" +#include "pqxx/strconv.hxx" +#include "pqxx/util.hxx" + + +namespace pqxx::internal +{ +template +constexpr inline auto const iterator_identity{ + [](decltype(*std::declval()) x) { return x; }}; + + +/// Marker type: pass a dynamically-determined number of statement parameters. +/** @deprecated Use @ref params instead. + * + * Normally when invoking a prepared or parameterised statement, the number + * of parameters is known at compile time. For instance, + * `t.exec_prepared("foo", 1, "x");` executes statement `foo` with two + * parameters, an `int` and a C string. + * + * But sometimes you may want to pass a number of parameters known only at run + * time. In those cases, a @ref dynamic_params encodes a dynamically + * determined number of parameters. You can mix these with regular, static + * parameter lists, and you can re-use them for multiple statement invocations. + * + * A dynamic_params object does not store copies of its parameters, so make + * sure they remain accessible until you've executed the statement. + * + * The ACCESSOR is an optional callable (such as a lambda). If you pass an + * accessor `a`, then each parameter `p` goes into your statement as `a(p)`. + */ +template)> +class dynamic_params +{ +public: + /// Wrap a sequence of pointers or iterators. + constexpr dynamic_params(IT begin, IT end) : + m_begin(begin), m_end(end), m_accessor(iterator_identity) + {} + + /// Wrap a sequence of pointers or iterators. + /** This version takes an accessor callable. If you pass an accessor `acc`, + * then any parameter `p` will go into the statement's parameter list as + * `acc(p)`. + */ + constexpr dynamic_params(IT begin, IT end, ACCESSOR &acc) : + m_begin(begin), m_end(end), m_accessor(acc) + {} + + /// Wrap a container. + template + explicit constexpr dynamic_params(C &container) : + dynamic_params(std::begin(container), std::end(container)) + {} + + /// Wrap a container. + /** This version takes an accessor callable. If you pass an accessor `acc`, + * then any parameter `p` will go into the statement's parameter list as + * `acc(p)`. + */ + template + explicit constexpr dynamic_params(C &container, ACCESSOR &acc) : + dynamic_params(std::begin(container), std::end(container), acc) + {} + + constexpr IT begin() const noexcept { return m_begin; } + constexpr IT end() const noexcept { return m_end; } + + constexpr auto access(decltype(*std::declval()) value) const + -> decltype(std::declval()(value)) + { + return m_accessor(value); + } + +private: + IT const m_begin, m_end; + ACCESSOR m_accessor = iterator_identity; +}; + + +/// Internal type: encode statement parameters. +/** Compiles arguments for prepared statements and parameterised queries into + * a format that can be passed into libpq. + * + * Objects of this type are meant to be short-lived: a `c_params` lives and + * dies entirely within the call to execute. So, for example, if you pass in a + * non-null pointer as a parameter, @ref params may simply use that pointer as + * a parameter value, without arranging longer-term storage for the data to + * which it points. All values referenced by parameters must remain "live" + * until the parameterised or prepared statement has been executed. + */ +struct PQXX_LIBEXPORT c_params +{ + c_params() = default; + /// Copying these objects is pointless and expensive. Don't do it. + c_params(c_params const &) = delete; + c_params(c_params &&) = default; + + /// Pre-allocate storage for `n` parameters. + void reserve(std::size_t n) &; + + /// As used by libpq: pointers to parameter values. + std::vector values; + /// As used by libpq: lengths of non-null arguments, in bytes. + std::vector lengths; + /// As used by libpq: effectively boolean "is this a binary parameter?" + std::vector formats; +}; +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/stream_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/stream_iterator.hxx new file mode 100644 index 000000000..f240dcfa7 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/stream_iterator.hxx @@ -0,0 +1,105 @@ +/** Stream iterators. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_STREAM_ITERATOR +#define PQXX_H_STREAM_ITERATOR + +#include + +namespace pqxx +{ +class stream_from; +} + + +namespace pqxx::internal +{ +// C++20: Replace with generator? +/// Input iterator for stream_from. +/** Just barely enough to support range-based "for" loops. Don't assume that + * any of the usual behaviour works beyond that. + */ +template class stream_input_iterator +{ +public: + using value_type = std::tuple; + + /// Construct an "end" iterator. + stream_input_iterator() = default; + + explicit stream_input_iterator(stream_from &home) : m_home(&home) + { + advance(); + } + stream_input_iterator(stream_input_iterator const &) = default; + + stream_input_iterator &operator++() + { + advance(); + return *this; + } + + value_type const &operator*() const { return m_value; } + + /// Comparison only works for comparing to end(). + bool operator==(stream_input_iterator const &rhs) const + { + return m_home == rhs.m_home; + } + /// Comparison only works for comparing to end(). + bool operator!=(stream_input_iterator const &rhs) const + { + return not(*this == rhs); + } + +private: + void advance() + { + if (m_home == nullptr) + throw usage_error{"Moving stream_from iterator beyond end()."}; + if (not((*m_home) >> m_value)) + m_home = nullptr; + } + + stream_from *m_home{nullptr}; + value_type m_value; +}; + + +// C++20: Replace with generator? +/// Iteration over a @ref stream_from. +template class stream_input_iteration +{ +public: + using iterator = stream_input_iterator; + explicit stream_input_iteration(stream_from &home) : m_home{home} {} + iterator begin() const { return iterator{m_home}; } + iterator end() const { return {}; } + +private: + stream_from &m_home; +}; + + +// C++20: Replace with generator? +/// Iteration over a @ref stream_from, deleting it once done. +template class owning_stream_input_iteration +{ +public: + using iterator = stream_input_iterator; + explicit owning_stream_input_iteration(std::unique_ptr &&home) : + m_home{std::move(home)} + {} + iterator begin() const { return iterator{*m_home.get()}; } + iterator end() const { return {}; } + +private: + std::unique_ptr m_home; +}; +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/wait.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/wait.hxx new file mode 100644 index 000000000..7a82e6553 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/wait.hxx @@ -0,0 +1,18 @@ +#if !defined(PQXX_WAIT_HXX) +# define PQXX_WAIT_HXX + +namespace pqxx::internal +{ +/// Wait. +/** This is normally `std::this_thread::sleep_for()`. But MinGW's `thread` + * header doesn't work, so we must be careful about including it. + */ +void PQXX_LIBEXPORT wait_for(unsigned int microseconds); + + +/// Wait for a socket to be ready for reading/writing, or timeout. +PQXX_LIBEXPORT void wait_fd( + int fd, bool for_read, bool for_write, unsigned seconds = 1, + unsigned microseconds = 0); +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation new file mode 100644 index 000000000..1b801329b --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation @@ -0,0 +1,8 @@ +/** Transaction isolation levels. + * + * Policies and traits describing SQL transaction isolation levels + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/isolation.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation.hxx new file mode 100644 index 000000000..0698c6ab4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation.hxx @@ -0,0 +1,75 @@ +/* Definitions for transaction isolation levels, and such. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/isolation instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ISOLATION +#define PQXX_H_ISOLATION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/util.hxx" + +namespace pqxx +{ +/// Should a transaction be read-only, or read-write? +/** No, this is not an isolation level. So it really doesn't belong here. + * But it's not really worth a separate header. + */ +enum class write_policy +{ + read_only, + read_write +}; + + +/// Transaction isolation levels. +/** These are as defined in the SQL standard. But there are a few notes + * specific to PostgreSQL. + * + * First, postgres does not support "read uncommitted." The lowest level you + * can get is "read committed," which is better. PostgreSQL is built on the + * MVCC paradigm, which guarantees "read committed" isolation without any + * additional performance overhead, so there was no point in providing the + * lower level. + * + * Second, "repeatable read" also makes more isolation guarantees than the + * standard requires. According to the standard, this level prevents "dirty + * reads" and "nonrepeatable reads," but not "phantom reads." In postgres, + * it actually prevents all three. + * + * Third, "serializable" is only properly supported starting at postgres 9.1. + * If you request "serializable" isolation on an older backend, you will get + * the same isolation as in "repeatable read." It's better than the + * "repeatable read" defined in the SQL standard, but not a complete + * implementation of the standard's "serializable" isolation level. + * + * In general, a lower isolation level will allow more surprising interactions + * between ongoing transactions, but improve performance. A higher level + * gives you more protection from subtle concurrency bugs, but sometimes it + * may not be possible to complete your transaction without avoiding paradoxes + * in the data. In that case a transaction may fail, and the application will + * have to re-do the whole thing based on the latest state of the database. + * (If you want to retry your code in that situation, have a look at the + * transactor framework.) + * + * Study the levels and design your application with the right level in mind. + */ +enum isolation_level +{ + // PostgreSQL only has the better isolation levels. + // read_uncommitted, + + read_committed, + repeatable_read, + serializable, +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject new file mode 100644 index 000000000..1f2f94790 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject @@ -0,0 +1,8 @@ +/** Large Objects interface. + * + * Supports direct access to large objects, as well as through I/O streams + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/largeobject.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject.hxx new file mode 100644 index 000000000..ebafc51d8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject.hxx @@ -0,0 +1,735 @@ +/* Large Objects interface. Deprecated; use blob instead. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/largeobject instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_LARGEOBJECT +#define PQXX_H_LARGEOBJECT + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#include "pqxx/dbtransaction.hxx" + + +namespace pqxx +{ +/// Identity of a large object. +/** @deprecated Use the @ref blob class instead. + * + * Encapsulates the identity of a large object. + * + * A largeobject must be accessed only from within a backend transaction, but + * the object's identity remains valid as long as the object exists. + */ +class PQXX_LIBEXPORT largeobject +{ +public: + using size_type = large_object_size_type; + + /// Refer to a nonexistent large object (similar to what a null pointer + /// does). + [[deprecated("Use blob instead.")]] largeobject() noexcept = default; + + /// Create new large object. + /** @param t Backend transaction in which the object is to be created. + */ + [[deprecated("Use blob instead.")]] explicit largeobject(dbtransaction &t); + + /// Wrap object with given oid. + /** Convert combination of a transaction and object identifier into a + * large object identity. Does not affect the database. + * @param o Object identifier for the given object. + */ + [[deprecated("Use blob instead.")]] explicit largeobject(oid o) noexcept : + m_id{o} + {} + + /// Import large object from a local file. + /** Creates a large object containing the data found in the given file. + * @param t Backend transaction in which the large object is to be created. + * @param file A filename on the client program's filesystem. + */ + [[deprecated("Use blob instead.")]] largeobject( + dbtransaction &t, std::string_view file); + + /// Take identity of an opened large object. + /** Copy identity of already opened large object. Note that this may be done + * as an implicit conversion. + * @param o Already opened large object to copy identity from. + */ + [[deprecated("Use blob instead.")]] largeobject( + largeobjectaccess const &o) noexcept; + + /// Object identifier. + /** The number returned by this function identifies the large object in the + * database we're connected to (or oid_none is returned if we refer to the + * null object). + */ + [[nodiscard]] oid id() const noexcept { return m_id; } + + /** + * @name Identity comparisons + * + * These operators compare the object identifiers of large objects. This has + * nothing to do with the objects' actual contents; use them only for keeping + * track of containers of references to large objects and such. + */ + //@{ + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator==(largeobject const &other) const + { + return m_id == other.m_id; + } + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator!=(largeobject const &other) const + { + return m_id != other.m_id; + } + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator<=(largeobject const &other) const + { + return m_id <= other.m_id; + } + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator>=(largeobject const &other) const + { + return m_id >= other.m_id; + } + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator<(largeobject const &other) const + { + return m_id < other.m_id; + } + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator>(largeobject const &other) const + { + return m_id > other.m_id; + } + //@} + + /// Export large object's contents to a local file + /** Writes the data stored in the large object to the given file. + * @param t Transaction in which the object is to be accessed + * @param file A filename on the client's filesystem + */ + void to_file(dbtransaction &t, std::string_view file) const; + + /// Delete large object from database + /** Unlike its low-level equivalent cunlink, this will throw an exception if + * deletion fails. + * @param t Transaction in which the object is to be deleted + */ + void remove(dbtransaction &t) const; + +protected: + PQXX_PURE static internal::pq::PGconn * + raw_connection(dbtransaction const &T); + + PQXX_PRIVATE std::string reason(connection const &, int err) const; + +private: + oid m_id = oid_none; +}; + + +/// Accessor for large object's contents. +/** @deprecated Use the `blob` class instead. + */ +class PQXX_LIBEXPORT largeobjectaccess : private largeobject +{ +public: + using largeobject::size_type; + using off_type = size_type; + using pos_type = size_type; + + /// Open mode: `in`, `out` (can be combined using "bitwise or"). + /** According to the C++ standard, these should be in `std::ios_base`. We + * take them from derived class `std::ios` instead, which is easier on the + * eyes. + * + * Historical note: taking it from std::ios was originally a workaround for a + * problem with gcc 2.95. + */ + using openmode = std::ios::openmode; + + /// Default open mode: in, out, binary. + static constexpr auto default_mode{ + std::ios::in | std::ios::out | std::ios::binary}; + + /// Seek direction: `beg`, `cur`, `end`. + using seekdir = std::ios::seekdir; + + /// Create new large object and open it. + /** + * @param t Backend transaction in which the object is to be created. + * @param mode Access mode, defaults to ios_base::in | ios_base::out | + * ios_base::binary. + */ + [[deprecated("Use blob instead.")]] explicit largeobjectaccess( + dbtransaction &t, openmode mode = default_mode); + + /// Open large object with given oid. + /** Convert combination of a transaction and object identifier into a + * large object identity. Does not affect the database. + * @param t Transaction in which the object is to be accessed. + * @param o Object identifier for the given object. + * @param mode Access mode, defaults to ios_base::in | ios_base::out | + * ios_base::binary. + */ + [[deprecated("Use blob instead.")]] largeobjectaccess( + dbtransaction &t, oid o, openmode mode = default_mode); + + /// Open given large object. + /** Open a large object with the given identity for reading and/or writing. + * @param t Transaction in which the object is to be accessed. + * @param o Identity for the large object to be accessed. + * @param mode Access mode, defaults to ios_base::in | ios_base::out | + * ios_base::binary. + */ + [[deprecated("Use blob instead.")]] largeobjectaccess( + dbtransaction &t, largeobject o, openmode mode = default_mode); + + /// Import large object from a local file and open it. + /** Creates a large object containing the data found in the given file. + * @param t Backend transaction in which the large object is to be created. + * @param file A filename on the client program's filesystem. + * @param mode Access mode, defaults to ios_base::in | ios_base::out. + */ + [[deprecated("Use blob instead.")]] largeobjectaccess( + dbtransaction &t, std::string_view file, openmode mode = default_mode); + + ~largeobjectaccess() noexcept { close(); } + + /// Object identifier. + /** The number returned by this function uniquely identifies the large object + * in the context of the database we're connected to. + */ + using largeobject::id; + + /// Export large object's contents to a local file. + /** Writes the data stored in the large object to the given file. + * @param file A filename on the client's filesystem. + */ + void to_file(std::string_view file) const + { + largeobject::to_file(m_trans, file); + } + + using largeobject::to_file; + + /** + * @name High-level access to object contents. + */ + //@{ + /// Write data to large object. + /** @warning The size of a write is currently limited to 2GB. + * + * @param buf Data to write. + * @param len Number of bytes from Buf to write. + */ + void write(char const buf[], std::size_t len); + + /// Write string to large object. + /** If not all bytes could be written, an exception is thrown. + * @param buf Data to write; no terminating zero is written. + */ + void write(std::string_view buf) { write(std::data(buf), std::size(buf)); } + + /// Read data from large object. + /** Throws an exception if an error occurs while reading. + * @param buf Location to store the read data in. + * @param len Number of bytes to try and read. + * @return Number of bytes read, which may be less than the number requested + * if the end of the large object is reached. + */ + size_type read(char buf[], std::size_t len); + + /// Seek in large object's data stream. + /** Throws an exception if an error occurs. + * @return The new position in the large object + */ + size_type seek(size_type dest, seekdir dir); + + /// Report current position in large object's data stream. + /** Throws an exception if an error occurs. + * @return The current position in the large object. + */ + [[nodiscard]] size_type tell() const; + //@} + + /** + * @name Low-level access to object contents. + * + * These functions provide a more "C-like" access interface, returning + * special values instead of throwing exceptions on error. These functions + * are generally best avoided in favour of the high-level access functions, + * which behave more like C++ functions should. + * + * Due to libpq's underlying API, some operations are limited to "int" + * sizes, typically 2 GB, even though a large object can grow much larger. + */ + //@{ + /// Seek in large object's data stream. + /** Does not throw exception in case of error; inspect return value and + * `errno` instead. + * @param dest Offset to go to. + * @param dir Origin to which dest is relative: ios_base::beg (from beginning + * of the object), ios_base::cur (from current access position), or + * ios_base;:end (from end of object). + * @return New position in large object, or -1 if an error occurred. + */ + pos_type cseek(off_type dest, seekdir dir) noexcept; + + /// Write to large object's data stream. + /** Does not throw exception in case of error; inspect return value and + * `errno` instead. + * @param buf Data to write. + * @param len Number of bytes to write. + * @return Number of bytes actually written, or -1 if an error occurred. + */ + off_type cwrite(char const buf[], std::size_t len) noexcept; + + /// Read from large object's data stream. + /** Does not throw exception in case of error; inspect return value and + * `errno` instead. + * @param buf Area where incoming bytes should be stored. + * @param len Number of bytes to read. + * @return Number of bytes actually read, or -1 if an error occurred.. + */ + off_type cread(char buf[], std::size_t len) noexcept; + + /// Report current position in large object's data stream. + /** Does not throw exception in case of error; inspect return value and + * `errno` instead. + * @return Current position in large object, of -1 if an error occurred. + */ + [[nodiscard]] pos_type ctell() const noexcept; + //@} + + /** + * @name Error/warning output + */ + //@{ + /// Issue message to transaction's notice processor. + void process_notice(zview) noexcept; + //@} + + using largeobject::remove; + + using largeobject::operator==; + using largeobject::operator!=; + using largeobject::operator<; + using largeobject::operator<=; + using largeobject::operator>; + using largeobject::operator>=; + + largeobjectaccess() = delete; + largeobjectaccess(largeobjectaccess const &) = delete; + largeobjectaccess operator=(largeobjectaccess const &) = delete; + +private: + PQXX_PRIVATE std::string reason(int err) const; + internal::pq::PGconn *raw_connection() const + { + return largeobject::raw_connection(m_trans); + } + + PQXX_PRIVATE void open(openmode mode); + void close() noexcept; + + dbtransaction &m_trans; + int m_fd = -1; +}; + + +/// Streambuf to use large objects in standard I/O streams. +/** @deprecated Access large objects directly using the @ref blob class. + * + * The standard streambuf classes provide uniform access to data storage such + * as files or string buffers, so they can be accessed using standard input or + * output streams. This streambuf implementation provided similar access to + * large objects, so they could be read and written using the same stream + * classes. + * + * This functionality was considered too fragile and complex, so it has been + * replaced with a single, much simpler class. + */ +template> +class largeobject_streambuf : public std::basic_streambuf +{ + using size_type = largeobject::size_type; + +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + using openmode = largeobjectaccess::openmode; + using seekdir = largeobjectaccess::seekdir; + + /// Default open mode: in, out, binary. + static constexpr auto default_mode{ + std::ios::in | std::ios::out | std::ios::binary}; + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + [[deprecated("Use blob instead.")]] largeobject_streambuf( + dbtransaction &t, largeobject o, openmode mode = default_mode, + size_type buf_size = 512) : + m_bufsize{buf_size}, m_obj{t, o, mode}, m_g{nullptr}, m_p{nullptr} + { + initialize(mode); + } +#include "pqxx/internal/ignore-deprecated-post.hxx" + + [[deprecated("Use blob instead.")]] largeobject_streambuf( + dbtransaction &t, oid o, openmode mode = default_mode, + size_type buf_size = 512) : + m_bufsize{buf_size}, m_obj{t, o, mode}, m_g{nullptr}, m_p{nullptr} + { + initialize(mode); + } + + virtual ~largeobject_streambuf() noexcept + { + delete[] m_p; + delete[] m_g; + } + + /// For use by large object stream classes. + void process_notice(zview const &s) { m_obj.process_notice(s); } + +protected: + virtual int sync() override + { + // setg() sets eback, gptr, egptr. + this->setg(this->eback(), this->eback(), this->egptr()); + return overflow(eof()); + } + + virtual pos_type seekoff(off_type offset, seekdir dir, openmode) override + { + return adjust_eof(m_obj.cseek(largeobjectaccess::off_type(offset), dir)); + } + + virtual pos_type seekpos(pos_type pos, openmode) override + { + largeobjectaccess::pos_type const newpos{ + m_obj.cseek(largeobjectaccess::off_type(pos), std::ios::beg)}; + return adjust_eof(newpos); + } + + virtual int_type overflow(int_type ch) override + { + auto *const pp{this->pptr()}; + if (pp == nullptr) + return eof(); + auto *const pb{this->pbase()}; + int_type res{0}; + + if (pp > pb) + { + auto const write_sz{pp - pb}; + auto const written_sz{ + m_obj.cwrite(pb, static_cast(pp - pb))}; + if (internal::cmp_less_equal(written_sz, 0)) + throw internal_error{ + "pqxx::largeobject: write failed " + "(is transaction still valid on write or flush?), " + "libpq reports error"}; + else if (write_sz != written_sz) + throw internal_error{ + "pqxx::largeobject: write failed " + "(is transaction still valid on write or flush?), " + + std::to_string(written_sz) + "/" + std::to_string(write_sz) + + " bytes written"}; + auto const out{adjust_eof(written_sz)}; + + if constexpr (std::is_arithmetic_v) + res = check_cast(out, "largeobject position"sv); + else + res = int_type(out); + } + this->setp(m_p, m_p + m_bufsize); + + // Write that one more character, if it's there. + if (ch != eof()) + { + *this->pptr() = static_cast(ch); + this->pbump(1); + } + return res; + } + + virtual int_type overflow() { return overflow(eof()); } + + virtual int_type underflow() override + { + if (this->gptr() == nullptr) + return eof(); + auto *const eb{this->eback()}; + auto const res{adjust_eof( + m_obj.cread(this->eback(), static_cast(m_bufsize)))}; + this->setg( + eb, eb, eb + (res == eof() ? 0 : static_cast(res))); + return (res == eof() or res == 0) ? eof() : traits_type::to_int_type(*eb); + } + +private: + /// Shortcut for traits_type::eof(). + static int_type eof() { return traits_type::eof(); } + + /// Helper: change error position of -1 to EOF (probably a no-op). + template static std::streampos adjust_eof(INTYPE pos) + { + bool const at_eof{pos == -1}; + if constexpr (std::is_arithmetic_v) + { + return check_cast( + (at_eof ? eof() : pos), "large object seek"sv); + } + else + { + return std::streampos(at_eof ? eof() : pos); + } + } + + void initialize(openmode mode) + { + if ((mode & std::ios::in) != 0) + { + m_g = new char_type[unsigned(m_bufsize)]; + this->setg(m_g, m_g, m_g); + } + if ((mode & std::ios::out) != 0) + { + m_p = new char_type[unsigned(m_bufsize)]; + this->setp(m_p, m_p + m_bufsize); + } + } + + size_type const m_bufsize; + largeobjectaccess m_obj; + + /// Get & put buffers. + char_type *m_g, *m_p; +}; + + +/// Input stream that gets its data from a large object. +/** @deprecated Access large objects directly using the @ref blob class. + * + * This class worked like any other istream, but to read data from a large + * object. It supported all formatting and streaming operations of + * `std::istream`. + * + * This functionality was considered too fragile and complex, so it has been + * replaced with a single, much simpler class. + */ +template> +class basic_ilostream : public std::basic_istream +{ + using super = std::basic_istream; + +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + /// Create a basic_ilostream. + /** + * @param t Transaction in which this stream is to exist. + * @param o Large object to access. + * @param buf_size Size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_ilostream( + dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{t, o, std::ios::in | std::ios::binary, buf_size} + { + super::init(&m_buf); + } +#include "pqxx/internal/ignore-deprecated-post.hxx" + + /// Create a basic_ilostream. + /** + * @param t Transaction in which this stream is to exist. + * @param o Identifier of a large object to access. + * @param buf_size Size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_ilostream( + dbtransaction &t, oid o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{t, o, std::ios::in | std::ios::binary, buf_size} + { + super::init(&m_buf); + } + +private: + largeobject_streambuf m_buf; +}; + +using ilostream = basic_ilostream; + + +/// Output stream that writes data back to a large object. +/** @deprecated Access large objects directly using the @ref blob class. + * + * This worked like any other ostream, but to write data to a large object. + * It supported all formatting and streaming operations of `std::ostream`. + * + * This functionality was considered too fragile and complex, so it has been + * replaced with a single, much simpler class. + */ +template> +class basic_olostream : public std::basic_ostream +{ + using super = std::basic_ostream; + +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + /// Create a basic_olostream. + /** + * @param t transaction in which this stream is to exist. + * @param o a large object to access. + * @param buf_size size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_olostream( + dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{t, o, std::ios::out | std::ios::binary, buf_size} + { + super::init(&m_buf); + } +#include "pqxx/internal/ignore-deprecated-post.hxx" + + /// Create a basic_olostream. + /** + * @param t transaction in which this stream is to exist. + * @param o a large object to access. + * @param buf_size size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_olostream( + dbtransaction &t, oid o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{t, o, std::ios::out | std::ios::binary, buf_size} + { + super::init(&m_buf); + } + + ~basic_olostream() + { + try + { + m_buf.pubsync(); + m_buf.pubsync(); + } + catch (std::exception const &e) + { + m_buf.process_notice(e.what()); + } + } + +private: + largeobject_streambuf m_buf; +}; + +using olostream = basic_olostream; + + +/// Stream that reads and writes a large object. +/** @deprecated Access large objects directly using the @ref blob class. + * + * This worked like a std::iostream, but to read data from, or write data to, a + * large object. It supported all formatting and streaming operations of + * `std::iostream`. + * + * This functionality was considered too fragile and complex, so it has been + * replaced with a single, much simpler class. + */ +template> +class basic_lostream : public std::basic_iostream +{ + using super = std::basic_iostream; + +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + + /// Create a basic_lostream. + /** + * @param t Transaction in which this stream is to exist. + * @param o Large object to access. + * @param buf_size Size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_lostream( + dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{ + t, o, std::ios::in | std::ios::out | std::ios::binary, buf_size} + { + super::init(&m_buf); + } + + /// Create a basic_lostream. + /** + * @param t Transaction in which this stream is to exist. + * @param o Large object to access. + * @param buf_size Size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_lostream( + dbtransaction &t, oid o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{ + t, o, std::ios::in | std::ios::out | std::ios::binary, buf_size} + { + super::init(&m_buf); + } + + ~basic_lostream() + { + try + { + m_buf.pubsync(); + m_buf.pubsync(); + } + catch (std::exception const &e) + { + m_buf.process_notice(e.what()); + } + } + +private: + largeobject_streambuf m_buf; +}; + +using lostream = basic_lostream; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction new file mode 100644 index 000000000..bb5b79724 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction @@ -0,0 +1,8 @@ +/** pqxx::nontransaction class. + * + * pqxx::nontransaction provides nontransactional database access. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/nontransaction.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction.hxx new file mode 100644 index 000000000..c50715594 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction.hxx @@ -0,0 +1,76 @@ +/* Definition of the pqxx::nontransaction class. + * + * pqxx::nontransaction provides nontransactional database access + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/nontransaction instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_NONTRANSACTION +#define PQXX_H_NONTRANSACTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/connection.hxx" +#include "pqxx/result.hxx" +#include "pqxx/transaction.hxx" + +namespace pqxx +{ +using namespace std::literals; + +/// Simple "transaction" class offering no transactional integrity. +/** + * @ingroup transactions + * + * nontransaction, like transaction or any other transaction_base-derived + * class, provides access to a database through a connection. Unlike its + * siblings, however, nontransaction does not maintain any kind of + * transactional integrity. This may be useful eg. for read-only access to the + * database that does not require a consistent, atomic view on its data; or for + * operations that are not allowed within a backend transaction, such as + * creating tables. + * + * For queries that update the database, however, a real transaction is likely + * to be faster unless the transaction consists of only a single record update. + * + * Also, you can keep a nontransaction open for as long as you like. Actual + * back-end transactions are limited in lifespan, and will sometimes fail just + * because they took too long to execute or were left idle for too long. This + * will not happen with a nontransaction (although the connection may still + * time out, e.g. when the network is unavailable for a very long time). + * + * Any query executed in a nontransaction is committed immediately, and neither + * commit() nor abort() has any effect. + * + * Database features that require a backend transaction, such as cursors or + * large objects, will not work in a nontransaction. + */ +class PQXX_LIBEXPORT nontransaction final : public transaction_base +{ +public: + /// Constructor. + /** Create a "dummy" transaction. + * @param c Connection in which this "transaction" will operate. + * @param tname Optional tname for the transaction, beginning with a letter + * and containing only letters and digits. + */ + nontransaction(connection &c, std::string_view tname = ""sv) : + transaction_base{c, tname, std::shared_ptr{}} + { + register_transaction(); + } + + virtual ~nontransaction() override { close(); } + +private: + virtual void do_commit() override {} +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification new file mode 100644 index 000000000..a0bd1c73e --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification @@ -0,0 +1,8 @@ +/** pqxx::notification_receiver functor interface. + * + * pqxx::notification_receiver handles incoming notifications. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/notification.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification.hxx new file mode 100644 index 000000000..b59b8567a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification.hxx @@ -0,0 +1,94 @@ +/* Definition of the pqxx::notification_receiver functor interface. + * + * pqxx::notification_receiver handles incoming notifications. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/notification instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_NOTIFICATION +#define PQXX_H_NOTIFICATION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#include "pqxx/types.hxx" + + +namespace pqxx +{ +/// "Observer" base class for notifications. +/** @addtogroup notification Notifications and Receivers + * + * To listen on a notification issued using the NOTIFY command, derive your own + * class from notification_receiver and define its function-call operator to + * perform whatever action you wish to take when the given notification + * arrives. Then create an object of that class and pass it to your connection. + * DO NOT use raw SQL to listen for notifications, or your attempts to listen + * won't be resumed when a connection fails--and you'll have no way to notice. + * + * Notifications never arrive inside a transaction, not even in a + * nontransaction. Therefore, you are free to open a transaction of your own + * inside your receiver's function invocation operator. + * + * Notifications you are listening for may arrive anywhere within libpqxx code, + * but be aware that **PostgreSQL defers notifications occurring inside + * transactions.** (This was done for excellent reasons; just think about what + * happens if the transaction where you happen to handle an incoming + * notification is later rolled back for other reasons). So if you're keeping + * a transaction open, don't expect any of your receivers on the same + * connection to be notified. + * + * (For very similar reasons, outgoing notifications are also not sent until + * the transaction that sends them commits.) + * + * Multiple receivers on the same connection may listen on a notification of + * the same name. An incoming notification is processed by invoking all + * receivers (zero or more) of the same name. + */ +class PQXX_LIBEXPORT PQXX_NOVTABLE notification_receiver +{ +public: + /// Register the receiver with a connection. + /** + * @param c Connnection to operate on. + * @param channel Name of the notification to listen for. + */ + notification_receiver(connection &c, std::string_view channel); + /// Register the receiver with a connection. + notification_receiver(notification_receiver const &) = delete; + /// Register the receiver with a connection. + notification_receiver &operator=(notification_receiver const &) = delete; + /// Deregister the receiver. + virtual ~notification_receiver(); + + /// The channel that this receiver listens on. + [[nodiscard]] std::string const &channel() const & { return m_channel; } + + // TODO: Change API to take payload as zview instead of string ref. + /// Overridable: action to invoke when notification arrives. + /** + * @param payload An optional string that may have been passed to the NOTIFY + * command. + * @param backend_pid Process ID of the database backend process that served + * our connection when the notification arrived. The actual process ID + * behind the connection may have changed by the time this method is called. + */ + virtual void operator()(std::string const &payload, int backend_pid) = 0; + +protected: + connection &conn() const noexcept { return m_conn; } + +private: + connection &m_conn; + std::string m_channel; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params new file mode 100644 index 000000000..4098782aa --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params @@ -0,0 +1,8 @@ +/** Helper classes for passing statement parameters. + * + * Use these for prepared statements and parameterised statements. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/params.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params.hxx new file mode 100644 index 000000000..2d29cdfed --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params.hxx @@ -0,0 +1,383 @@ +/* Helpers for prepared statements and parameterised statements. + * + * See the connection class for more about such statements. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_PARAMS +#define PQXX_H_PARAMS + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#include "pqxx/internal/concat.hxx" +#include "pqxx/internal/statement_parameters.hxx" +#include "pqxx/types.hxx" + + +/// @deprecated The new @ref params class replaces all of this. +namespace pqxx::prepare +{ +/// Pass a number of statement parameters only known at runtime. +/** @deprecated Use @ref params instead. + * + * When you call any of the `exec_params` functions, the number of arguments + * is normally known at compile time. This helper function supports the case + * where it is not. + * + * Use this function to pass a variable number of parameters, based on a + * sequence ranging from `begin` to `end` exclusively. + * + * The technique combines with the regular static parameters. You can use it + * to insert dynamic parameter lists in any place, or places, among the call's + * parameters. You can even insert multiple dynamic sequences. + * + * @param begin A pointer or iterator for iterating parameters. + * @param end A pointer or iterator for iterating parameters. + * @return An object representing the parameters. + */ +template +[[deprecated("Use the params class instead.")]] constexpr inline auto +make_dynamic_params(IT begin, IT end) +{ + return pqxx::internal::dynamic_params(begin, end); +} + + +/// Pass a number of statement parameters only known at runtime. +/** @deprecated Use @ref params instead. + * + * When you call any of the `exec_params` functions, the number of arguments + * is normally known at compile time. This helper function supports the case + * where it is not. + * + * Use this function to pass a variable number of parameters, based on a + * container of parameter values. + * + * The technique combines with the regular static parameters. You can use it + * to insert dynamic parameter lists in any place, or places, among the call's + * parameters. You can even insert multiple dynamic containers. + * + * @param container A container of parameter values. + * @return An object representing the parameters. + */ +template +[[deprecated("Use the params class instead.")]] constexpr inline auto +make_dynamic_params(C const &container) +{ + using IT = typename C::const_iterator; +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return pqxx::internal::dynamic_params{container}; +#include "pqxx/internal/ignore-deprecated-post.hxx" +} + + +/// Pass a number of statement parameters only known at runtime. +/** @deprecated Use @ref params instead. + * + * When you call any of the `exec_params` functions, the number of arguments + * is normally known at compile time. This helper function supports the case + * where it is not. + * + * Use this function to pass a variable number of parameters, based on a + * container of parameter values. + * + * The technique combines with the regular static parameters. You can use it + * to insert dynamic parameter lists in any place, or places, among the call's + * parameters. You can even insert multiple dynamic containers. + * + * @param container A container of parameter values. + * @param accessor For each parameter `p`, pass `accessor(p)`. + * @return An object representing the parameters. + */ +template +[[deprecated("Use the params class instead.")]] constexpr inline auto +make_dynamic_params(C &container, ACCESSOR accessor) +{ + using IT = decltype(std::begin(container)); +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return pqxx::internal::dynamic_params{container, accessor}; +#include "pqxx/internal/ignore-deprecated-post.hxx" +} +} // namespace pqxx::prepare + + +namespace pqxx +{ +/// Generate parameter placeholders for use in an SQL statement. +/** When you want to pass parameters to a prepared statement or a parameterised + * statement, you insert placeholders into the SQL. During invocation, the + * database replaces those with the respective parameter values you passed. + * + * The placeholders look like `$1` (for the first parameter value), `$2` (for + * the second), and so on. You can just write those directly in your + * statement. But for those rare cases where it becomes difficult to track + * which number a placeholder should have, you can use a `placeholders` object + * to count and generate them in order. + */ +template class placeholders +{ +public: + /// Maximum number of parameters we support. + static inline constexpr unsigned int max_params{ + (std::numeric_limits::max)()}; + + placeholders() + { + static constexpr auto initial{"$1\0"sv}; + initial.copy(std::data(m_buf), std::size(initial)); + } + + /// Read an ephemeral version of the current placeholder text. + /** @warning Changing the current placeholder number will overwrite this. + * Use the view immediately, or lose it. + */ + constexpr zview view() const &noexcept + { + return zview{std::data(m_buf), m_len}; + } + + /// Read the current placeholder text, as a `std::string`. + /** This will be slightly slower than converting to a `zview`. With most + * C++ implementations however, until you get into ridiculous numbers of + * parameters, the string will benefit from the Short String Optimization, or + * SSO. + */ + std::string get() const { return std::string(std::data(m_buf), m_len); } + + /// Move on to the next parameter. + void next() & + { + if (m_current >= max_params) + throw range_error{pqxx::internal::concat( + "Too many parameters in one statement: limit is ", max_params, ".")}; + ++m_current; + if (m_current % 10 == 0) + { + // Carry the 1. Don't get too clever for this relatively rare + // case, just rewrite the entire number. Leave the $ in place + // though. + char *const data{std::data(m_buf)}; + char *const end{string_traits::into_buf( + data + 1, data + std::size(m_buf), m_current)}; + // (Subtract because we don't include the trailing zero.) + m_len = check_cast(end - data, "placeholders counter") - 1; + } + else + { + PQXX_LIKELY + // Shortcut for the common case: just increment that last digit. + ++m_buf[m_len - 1]; + } + } + + /// Return the current placeholder number. The initial placeholder is 1. + COUNTER count() const noexcept { return m_current; } + +private: + /// Current placeholder number. Starts at 1. + COUNTER m_current = 1; + + /// Length of the current placeholder string, not including trailing zero. + COUNTER m_len = 2; + + /// Text buffer where we render the placeholders, with a trailing zero. + /** We keep reusing this for every subsequent placeholder, just because we + * don't like string allocations. + * + * Maximum length is the maximum base-10 digits that COUNTER can fully + * represent, plus 1 more for the extra digit that it can only partially + * fill up, plus room for the dollar sign and the trailing zero. + */ + std::array::digits10 + 3> m_buf; +}; + + +/// Build a parameter list for a parameterised or prepared statement. +/** When calling a parameterised statement or a prepared statement, you can + * pass parameters into the statement directly in the invocation, as + * additional arguments to `exec_prepared` or `exec_params`. But in + * complex cases, sometimes that's just not convenient. + * + * In those situations, you can create a `params` and append your parameters + * into that, one by one. Then you pass the `params` to `exec_prepared` or + * `exec_params`. + * + * Combinations also work: if you have a `params` containing a string + * parameter, and you call `exec_params` with an `int` argument followed by + * your `params`, you'll be passing the `int` as the first parameter and + * the string as the second. You can even insert a `params` in a `params`, + * or pass two `params` objects to a statement. + */ +class PQXX_LIBEXPORT params +{ +public: + params() = default; + + /// Pre-populate a `params` with `args`. Feel free to add more later. + template constexpr params(Args &&...args) + { + reserve(sizeof...(args)); + append_pack(std::forward(args)...); + } + + /// Pre-allocate room for at least `n` parameters. + /** This is not needed, but it may improve efficiency. + * + * Reserve space if you're going to add parameters individually, and you've + * got some idea of how many there are going to be. It may save some + * memory re-allocations. + */ + void reserve(std::size_t n) &; + + // C++20: constexpr. + /// Get the number of parameters currently in this `params`. + [[nodiscard]] auto size() const noexcept { return m_params.size(); } + + // C++20: Use the vector's ssize() directly and go noexcept+constexpr. + /// Get the number of parameters (signed). + /** Unlike `size()`, this is not yet `noexcept`. That's because C++17's + * `std::vector` does not have a `ssize()` member function. These member + * functions are `noexcept`, but `std::size()` and `std::ssize()` are + * not. + */ + [[nodiscard]] auto ssize() const { return pqxx::internal::ssize(m_params); } + + /// Append a null value. + void append() &; + + /// Append a non-null zview parameter. + /** The underlying data must stay valid for as long as the `params` + * remains active. + */ + void append(zview) &; + + /// Append a non-null string parameter. + /** Copies the underlying data into internal storage. For best efficiency, + * use the @ref zview variant if you can, or `std::move()` + */ + void append(std::string const &) &; + + /// Append a non-null string parameter. + void append(std::string &&) &; + + /// Append a non-null binary parameter. + /** The underlying data must stay valid for as long as the `params` + * remains active. + */ + void append(std::basic_string_view) &; + + /// Append a non-null binary parameter. + /** Copies the underlying data into internal storage. For best efficiency, + * use the `std::basic_string_view` variant if you can, or + * `std::move()`. + */ + void append(std::basic_string const &) &; + +#if defined(PQXX_HAVE_CONCEPTS) + /// Append a non-null binary parameter. + /** The `data` object must stay in place and unchanged, for as long as the + * `params` remains active. + */ + template void append(DATA const &data) & + { + append( + std::basic_string_view{std::data(data), std::size(data)}); + } +#endif // PQXX_HAVE_CONCEPTS + + /// Append a non-null binary parameter. + void append(std::basic_string &&) &; + + /// @deprecated Append binarystring parameter. + /** The binarystring must stay valid for as long as the `params` remains + * active. + */ + void append(binarystring const &value) &; + + /// Append all parameters from value. + template + void append(pqxx::internal::dynamic_params const &value) & + { + for (auto ¶m : value) append(value.access(param)); + } + + void append(params const &value) &; + + void append(params &&value) &; + + /// Append a non-null parameter, converting it to its string + /// representation. + template void append(TYPE const &value) & + { + // TODO: Pool storage for multiple string conversions in one buffer? + if constexpr (nullness>::always_null) + { + ignore_unused(value); + m_params.emplace_back(); + } + else if (is_null(value)) + { + m_params.emplace_back(); + } + else + { + m_params.emplace_back(entry{to_string(value)}); + } + } + + /// Append all elements of `range` as parameters. + template void append_multi(RANGE const &range) & + { +#if defined(PQXX_HAVE_CONCEPTS) + if constexpr (std::ranges::sized_range) + reserve(std::size(*this) + std::size(range)); +#endif + for (auto &value : range) append(value); + } + + /// For internal use: Generate a `params` object for use in calls. + /** The params object encapsulates the pointers which we will need to pass + * to libpq when calling a parameterised or prepared statement. + * + * The pointers in the params will refer to storage owned by either the + * params object, or the caller. This is not a problem because a + * `c_params` object is guaranteed to live only while the call is going on. + * As soon as we climb back out of that call tree, we're done with that + * data. + */ + pqxx::internal::c_params make_c_params() const; + +private: + /// Recursively append a pack of params. + template + void append_pack(Arg &&arg, More &&...args) + { + this->append(std::forward(arg)); + // Recurse for remaining args. + append_pack(std::forward(args)...); + } + + /// Terminating case: append an empty parameter pack. It's not hard BTW. + constexpr void append_pack() noexcept {} + + // The way we store a parameter depends on whether it's binary or text + // (most types are text), and whether we're responsible for storing the + // contents. + using entry = std::variant< + std::nullptr_t, zview, std::string, std::basic_string_view, + std::basic_string>; + std::vector m_params; + + static constexpr std::string_view s_overflow{ + "Statement parameter length overflow."sv}; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline new file mode 100644 index 000000000..bf828843a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline @@ -0,0 +1,8 @@ +/** pqxx::pipeline class. + * + * Throughput-optimized query interface. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/pipeline.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline.hxx new file mode 100644 index 000000000..049dcdd58 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline.hxx @@ -0,0 +1,237 @@ +/* Definition of the pqxx::pipeline class. + * + * Throughput-optimized mechanism for executing queries. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/pipeline instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_PIPELINE +#define PQXX_H_PIPELINE + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include + +#include "pqxx/transaction_base.hxx" + + +namespace pqxx +{ +// TODO: libpq 14 introduced a similar "pipeline mode." Can we use that? + +/// Processes several queries in FIFO manner, optimized for high throughput. +/** Use a pipeline if you want to keep doing useful work while your queries are + * executing. Result retrieval is decoupled from execution request; queries + * "go in at the front" and results "come out the back." + * + * Actually, you can retrieve the results in any order if you want, but it may + * lead to surprising "time travel" effects if any of the queries fails. In + * particular, syntax errors in the queries can confuse things and show up too + * early in the stream of results. + * + * Generally, if any of the queries fails, it will throw an exception at the + * point where you request its result. But it may happen earlier, especially + * if you request results out of chronological order. + * + * @warning While a pipeline is active, you cannot execute queries, open + * streams, etc. on the same transaction. A transaction can have at most one + * object of a type derived from @ref pqxx::transaction_focus active on it at a + * time. + */ +class PQXX_LIBEXPORT pipeline : public transaction_focus +{ +public: + /// Identifying numbers for queries. + using query_id = long; + + pipeline(pipeline const &) = delete; + pipeline &operator=(pipeline const &) = delete; + + /// Start a pipeline. + explicit pipeline(transaction_base &t) : transaction_focus{t, s_classname} + { + init(); + } + /// Start a pipeline. Assign it a name, for more helpful error messages. + pipeline(transaction_base &t, std::string_view tname) : + transaction_focus{t, s_classname, tname} + { + init(); + } + + /// Close the pipeline. + ~pipeline() noexcept; + + /// Add query to the pipeline. + /** Queries accumulate in the pipeline, which sends them to the backend in a + * batch separated by semicolons. The queries you insert must not use this + * trick themselves, or the pipeline will get hopelessly confused! + * + * @return Identifier for this query, unique only within this pipeline. + */ + query_id insert(std::string_view) &; + + /// Wait for all ongoing or pending operations to complete, and detach. + /** Detaches from the transaction when done. + * + * This does not produce the queries' results, so it may not report any + * errors which may have occurred in their execution. To be sure that your + * statements succeeded, call @ref retrieve until the pipeline is empty. + */ + void complete(); + + /// Forget all ongoing or pending operations and retrieved results. + /** Queries already sent to the backend may still be completed, depending + * on implementation and timing. + * + * Any error state (unless caused by an internal error) will also be cleared. + * This is mostly useful in a nontransaction, since a backend transaction is + * aborted automatically when an error occurs. + * + * Detaches from the transaction when done. + */ + void flush(); + + /// Cancel ongoing query, if any. + /** May cancel any or all of the queries that have been inserted at this + * point whose results have not yet been retrieved. If the pipeline lives in + * a backend transaction, that transaction may be left in a nonfunctional + * state in which it can only be aborted. + * + * Therefore, either use this function in a nontransaction, or abort the + * transaction after calling it. + */ + void cancel(); + + /// Is result for given query available? + [[nodiscard]] bool is_finished(query_id) const; + + /// Retrieve result for given query. + /** If the query failed for whatever reason, this will throw an exception. + * The function will block if the query has not finished yet. + * @warning If results are retrieved out-of-order, i.e. in a different order + * than the one in which their queries were inserted, errors may "propagate" + * to subsequent queries. + */ + result retrieve(query_id qid) + { + return retrieve(m_queries.find(qid)).second; + } + + /// Retrieve oldest unretrieved result (possibly wait for one). + /** @return The query's identifier and its result set. */ + std::pair retrieve(); + + [[nodiscard]] bool empty() const noexcept { return std::empty(m_queries); } + + /// Set maximum number of queries to retain before issuing them to the + /// backend. + /** The pipeline will perform better if multiple queries are issued at once, + * but retaining queries until the results are needed (as opposed to issuing + * them to the backend immediately) may negate any performance benefits the + * pipeline can offer. + * + * Recommended practice is to set this value no higher than the number of + * queries you intend to insert at a time. + * @param retain_max A nonnegative "retention capacity;" passing zero will + * cause queries to be issued immediately + * @return Old retention capacity + */ + int retain(int retain_max = 2) &; + + + /// Resume retained query emission. Harmless when not needed. + void resume() &; + +private: + struct PQXX_PRIVATE Query + { + explicit Query(std::string_view q) : + query{std::make_shared(q)} + {} + + std::shared_ptr query; + result res; + }; + + using QueryMap = std::map; + + void init(); + void attach(); + void detach(); + + /// Upper bound to query id's. + static constexpr query_id qid_limit() noexcept + { + // Parenthesise this to work around an eternal Visual C++ problem: + // Without the extra parentheses, unless NOMINMAX is defined, the + // preprocessor will mistake this "max" for its annoying built-in macro + // of the same name. + return (std::numeric_limits::max)(); + } + + /// Create new query_id. + PQXX_PRIVATE query_id generate_id(); + + bool have_pending() const noexcept + { + return m_issuedrange.second != m_issuedrange.first; + } + + PQXX_PRIVATE void issue(); + + /// The given query failed; never issue anything beyond that. + void set_error_at(query_id qid) noexcept + { + PQXX_UNLIKELY + if (qid < m_error) + m_error = qid; + } + + /// Throw pqxx::internal_error. + [[noreturn]] PQXX_PRIVATE void internal_error(std::string const &err); + + PQXX_PRIVATE bool obtain_result(bool expect_none = false); + + PQXX_PRIVATE void obtain_dummy(); + PQXX_PRIVATE void get_further_available_results(); + PQXX_PRIVATE void check_end_results(); + + /// Receive any results that happen to be available; it's not urgent. + PQXX_PRIVATE void receive_if_available(); + + /// Receive results, up to stop if possible. + PQXX_PRIVATE void receive(pipeline::QueryMap::const_iterator stop); + std::pair retrieve(pipeline::QueryMap::iterator); + + QueryMap m_queries; + std::pair m_issuedrange; + int m_retain = 0; + int m_num_waiting = 0; + query_id m_q_id = 0; + + /// Is there a "dummy query" pending? + bool m_dummy_pending = false; + + /// Point at which an error occurred; no results beyond it will be available + query_id m_error = qid_limit(); + + /// Encoding. + /** We store this in the object to avoid the risk of exceptions at awkward + * moments. + */ + internal::encoding_group m_encoding; + + static constexpr std::string_view s_classname{"pipeline"}; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pqxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pqxx new file mode 100644 index 000000000..17a8eaa9c --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pqxx @@ -0,0 +1,28 @@ +/// Convenience header: include all libpqxx definitions. +#include "pqxx/internal/header-pre.hxx" + +#include "pqxx/array.hxx" +#include "pqxx/binarystring.hxx" +#include "pqxx/blob.hxx" +#include "pqxx/connection.hxx" +#include "pqxx/cursor.hxx" +#include "pqxx/errorhandler.hxx" +#include "pqxx/except.hxx" +#include "pqxx/largeobject.hxx" +#include "pqxx/nontransaction.hxx" +#include "pqxx/notification.hxx" +#include "pqxx/params.hxx" +#include "pqxx/pipeline.hxx" +#include "pqxx/prepared_statement.hxx" +#include "pqxx/result.hxx" +#include "pqxx/internal/result_iterator.hxx" +#include "pqxx/internal/result_iter.hxx" +#include "pqxx/robusttransaction.hxx" +#include "pqxx/row.hxx" +#include "pqxx/stream_from.hxx" +#include "pqxx/stream_to.hxx" +#include "pqxx/subtransaction.hxx" +#include "pqxx/transaction.hxx" +#include "pqxx/transactor.hxx" + +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement new file mode 100644 index 000000000..674be7090 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement @@ -0,0 +1,3 @@ +/// @deprecated Include @c instead. + +#include "params.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement.hxx new file mode 100644 index 000000000..674be7090 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement.hxx @@ -0,0 +1,3 @@ +/// @deprecated Include @c instead. + +#include "params.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range new file mode 100644 index 000000000..11985eca4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range @@ -0,0 +1,6 @@ +/** Client-side support for SQL range types. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/range.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range.hxx new file mode 100644 index 000000000..dc480e4b7 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range.hxx @@ -0,0 +1,515 @@ +#ifndef PQXX_H_RANGE +#define PQXX_H_RANGE + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#include "pqxx/internal/array-composite.hxx" +#include "pqxx/internal/concat.hxx" + +namespace pqxx +{ +/// An _unlimited_ boundary value to a @ref pqxx::range. +/** Use this as a lower or upper bound for a range if the range should extend + * to infinity on that side. + * + * An unlimited boundary is always inclusive of "infinity" values, if the + * range's value type supports them. + */ +struct no_bound +{ + template constexpr bool extends_down_to(TYPE const &) const + { + return true; + } + template constexpr bool extends_up_to(TYPE const &) const + { + return true; + } +}; + + +/// An _inclusive_ boundary value to a @ref pqxx::range. +/** Use this as a lower or upper bound for a range if the range should include + * the value. + */ +template class inclusive_bound +{ +public: + inclusive_bound() = delete; + explicit inclusive_bound(TYPE const &value) : m_value{value} + { + if (is_null(value)) + throw argument_error{"Got null value as an inclusive range bound."}; + } + + [[nodiscard]] constexpr TYPE const &get() const &noexcept { return m_value; } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + /// Would this bound, as a lower bound, include value? + [[nodiscard]] bool extends_down_to(TYPE const &value) const + { + return not(value < m_value); + } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + /// Would this bound, as an upper bound, include value? + [[nodiscard]] bool extends_up_to(TYPE const &value) const + { + return not(m_value < value); + } + +private: + TYPE m_value; +}; + + +/// An _exclusive_ boundary value to a @ref pqxx::range. +/** Use this as a lower or upper bound for a range if the range should _not_ + * include the value. + */ +template class exclusive_bound +{ +public: + exclusive_bound() = delete; + explicit exclusive_bound(TYPE const &value) : m_value{value} + { + if (is_null(value)) + throw argument_error{"Got null value as an exclusive range bound."}; + } + + [[nodiscard]] constexpr TYPE const &get() const &noexcept { return m_value; } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + /// Would this bound, as a lower bound, include value? + [[nodiscard]] bool extends_down_to(TYPE const &value) const + { + return m_value < value; + } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + /// Would this bound, as an upper bound, include value? + [[nodiscard]] bool extends_up_to(TYPE const &value) const + { + return value < m_value; + } + +private: + TYPE m_value; +}; + + +/// A range boundary value. +/** A range bound is either no bound at all; or an inclusive bound; or an + * exclusive bound. Pass one of the three to the constructor. + */ +template class range_bound +{ +public: + range_bound() = delete; + // TODO: constexpr and/or noexcept if underlying constructor supports it. + range_bound(no_bound) : m_bound{} {} + // TODO: constexpr and/or noexcept if underlying constructor supports it. + range_bound(inclusive_bound const &bound) : m_bound{bound} {} + // TODO: constexpr and/or noexcept if underlying constructor supports it. + range_bound(exclusive_bound const &bound) : m_bound{bound} {} + // TODO: constexpr and/or noexcept if underlying constructor supports it. + range_bound(range_bound const &) = default; + // TODO: constexpr and/or noexcept if underlying constructor supports it. + range_bound(range_bound &&) = default; + + // TODO: constexpr and/or noexcept if underlying operators support it. + bool operator==(range_bound const &rhs) const + { + if (this->is_limited()) + return ( + rhs.is_limited() and (this->is_inclusive() == rhs.is_inclusive()) and + (*this->value() == *rhs.value())); + else + return not rhs.is_limited(); + } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + bool operator!=(range_bound const &rhs) const { return not(*this == rhs); } + range_bound &operator=(range_bound const &) = default; + range_bound &operator=(range_bound &&) = default; + + /// Is this a finite bound? + constexpr bool is_limited() const noexcept + { + return not std::holds_alternative(m_bound); + } + + /// Is this boundary an inclusive one? + constexpr bool is_inclusive() const noexcept + { + return std::holds_alternative>(m_bound); + } + + /// Is this boundary an exclusive one? + constexpr bool is_exclusive() const noexcept + { + return std::holds_alternative>(m_bound); + } + + // TODO: constexpr/noexcept if underlying function supports it. + /// Would this bound, as a lower bound, include `value`? + bool extends_down_to(TYPE const &value) const + { + return std::visit( + [&value](auto const &bound) { return bound.extends_down_to(value); }, + m_bound); + } + + // TODO: constexpr/noexcept if underlying function supports it. + /// Would this bound, as an upper bound, include `value`? + bool extends_up_to(TYPE const &value) const + { + return std::visit( + [&value](auto const &bound) { return bound.extends_up_to(value); }, + m_bound); + } + + /// Return bound value, or `nullptr` if it's not limited. + [[nodiscard]] constexpr TYPE const *value() const &noexcept + { + return std::visit( + [](auto const &bound) noexcept { + using bound_t = std::decay_t; + if constexpr (std::is_same_v) + return static_cast(nullptr); + else + return &bound.get(); + }, + m_bound); + } + +private: + std::variant, exclusive_bound> m_bound; +}; + + +// C++20: Concepts for comparisons, construction, etc. +/// A C++ equivalent to PostgreSQL's range types. +/** You can use this as a client-side representation of a "range" in SQL. + * + * PostgreSQL defines several range types, differing in the data type over + * which they range. You can also define your own range types. + * + * Usually you'll want the server to deal with ranges. But on occasions where + * you need to work with them client-side, you may want to use @ref + * pqxx::range. (In cases where all you do is pass them along to the server + * though, it's not worth the complexity. In that case you might as well treat + * ranges as just strings.) + * + * For documentation on PostgreSQL's range types, see: + * https://www.postgresql.org/docs/current/rangetypes.html + * + * The value type must be copyable and default-constructible, and support the + * less-than (`<`) and equals (`==`) comparisons. Value initialisation must + * produce a consistent value. + */ +template class range +{ +public: + /// Create a range. + /** For each of the two bounds, pass a @ref no_bound, @ref inclusive_bound, + * or + * @ref exclusive_bound. + */ + range(range_bound lower, range_bound upper) : + m_lower{lower}, m_upper{upper} + { + if ( + lower.is_limited() and upper.is_limited() and + (*upper.value() < *lower.value())) + throw range_error{internal::concat( + "Range's lower bound (", *lower.value(), + ") is greater than its upper bound (", *upper.value(), ").")}; + } + + // TODO: constexpr and/or noexcept if underlying constructor supports it. + /// Create an empty range. + /** SQL has a separate literal to denote an empty range, but any range which + * encompasses no values is an empty range. + */ + range() : + m_lower{exclusive_bound{TYPE{}}}, + m_upper{exclusive_bound{TYPE{}}} + {} + + // TODO: constexpr and/or noexcept if underlying operators support it. + bool operator==(range const &rhs) const + { + return (this->lower_bound() == rhs.lower_bound() and + this->upper_bound() == rhs.upper_bound()) or + (this->empty() and rhs.empty()); + } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + bool operator!=(range const &rhs) const { return !(*this == rhs); } + + range(range const &) = default; + range(range &&) = default; + range &operator=(range const &) = default; + range &operator=(range &&) = default; + + // TODO: constexpr and/or noexcept if underlying operator supports it. + /// Is this range clearly empty? + /** An empty range encompasses no values. + * + * It is possible to "fool" this. For example, if your range is of an + * integer type and has exclusive bounds of 0 and 1, it encompasses no values + * but its `empty()` will return false. The PostgreSQL implementation, by + * contrast, will notice that it is empty. Similar things can happen for + * floating-point types, but with more subtleties and edge cases. + */ + bool empty() const + { + return (m_lower.is_exclusive() or m_upper.is_exclusive()) and + m_lower.is_limited() and m_upper.is_limited() and + not(*m_lower.value() < *m_upper.value()); + } + + // TODO: constexpr and/or noexcept if underlying functions support it. + /// Does this range encompass `value`? + bool contains(TYPE value) const + { + return m_lower.extends_down_to(value) and m_upper.extends_up_to(value); + } + + // TODO: constexpr and/or noexcept if underlying operators support it. + /// Does this range encompass all of `other`? + /** This function is not particularly smart. It does not know, for example, + * that integer ranges `[0,9]` and `[0,10)` contain the same values. + */ + bool contains(range const &other) const + { + return (*this & other) == other; + } + + [[nodiscard]] constexpr range_bound const & + lower_bound() const &noexcept + { + return m_lower; + } + [[nodiscard]] constexpr range_bound const & + upper_bound() const &noexcept + { + return m_upper; + } + + // TODO: constexpr and/or noexcept if underlying operators support it. + /// Intersection of two ranges. + /** Returns a range describing those values which are in both ranges. + */ + range operator&(range const &other) const + { + range_bound lower{no_bound{}}; + if (not this->lower_bound().is_limited()) + lower = other.lower_bound(); + else if (not other.lower_bound().is_limited()) + lower = this->lower_bound(); + else if (*this->lower_bound().value() < *other.lower_bound().value()) + lower = other.lower_bound(); + else if (*other.lower_bound().value() < *this->lower_bound().value()) + lower = this->lower_bound(); + else if (this->lower_bound().is_exclusive()) + lower = this->lower_bound(); + else + lower = other.lower_bound(); + + range_bound upper{no_bound{}}; + if (not this->upper_bound().is_limited()) + upper = other.upper_bound(); + else if (not other.upper_bound().is_limited()) + upper = this->upper_bound(); + else if (*other.upper_bound().value() < *this->upper_bound().value()) + upper = other.upper_bound(); + else if (*this->upper_bound().value() < *other.upper_bound().value()) + upper = this->upper_bound(); + else if (this->upper_bound().is_exclusive()) + upper = this->upper_bound(); + else + upper = other.upper_bound(); + + if ( + lower.is_limited() and upper.is_limited() and + (*upper.value() < *lower.value())) + return {}; + else + return {lower, upper}; + } + + /// Convert to another base type. + template operator range() const + { + range_bound lower{no_bound{}}, upper{no_bound{}}; + if (lower_bound().is_inclusive()) + lower = inclusive_bound{*lower_bound().value()}; + else if (lower_bound().is_exclusive()) + lower = exclusive_bound{*lower_bound().value()}; + + if (upper_bound().is_inclusive()) + upper = inclusive_bound{*upper_bound().value()}; + else if (upper_bound().is_exclusive()) + upper = exclusive_bound{*upper_bound().value()}; + + return {lower, upper}; + } + +private: + range_bound m_lower, m_upper; +}; + + +/// String conversions for a @ref range type. +/** Conversion assumes that either your client encoding is UTF-8, or the values + * are pure ASCII. + */ +template struct string_traits> +{ + [[nodiscard]] static inline zview + to_buf(char *begin, char *end, range const &value) + { + return generic_to_buf(begin, end, value); + } + + static inline char * + into_buf(char *begin, char *end, range const &value) + { + if (value.empty()) + { + if ((end - begin) <= internal::ssize(s_empty)) + throw conversion_overrun{s_overrun.c_str()}; + char *here = begin + s_empty.copy(begin, std::size(s_empty)); + *here++ = '\0'; + return here; + } + else + { + if (end - begin < 4) + throw conversion_overrun{s_overrun.c_str()}; + char *here = begin; + *here++ = + (static_cast(value.lower_bound().is_inclusive() ? '[' : '(')); + TYPE const *lower{value.lower_bound().value()}; + // Convert bound (but go back to overwrite that trailing zero). + if (lower != nullptr) + here = string_traits::into_buf(here, end, *lower) - 1; + *here++ = ','; + TYPE const *upper{value.upper_bound().value()}; + // Convert bound (but go back to overwrite that trailing zero). + if (upper != nullptr) + here = string_traits::into_buf(here, end, *upper) - 1; + if ((end - here) < 2) + throw conversion_overrun{s_overrun.c_str()}; + *here++ = + static_cast(value.upper_bound().is_inclusive() ? ']' : ')'); + *here++ = '\0'; + return here; + } + } + + [[nodiscard]] static inline range from_string(std::string_view text) + { + if (std::size(text) < 3) + throw pqxx::conversion_error{err_bad_input(text)}; + bool left_inc{false}; + switch (text[0]) + { + case '[': left_inc = true; break; + + case '(': break; + + case 'e': + case 'E': + if ( + (std::size(text) != std::size(s_empty)) or + (text[1] != 'm' and text[1] != 'M') or + (text[2] != 'p' and text[2] != 'P') or + (text[3] != 't' and text[3] != 'T') or + (text[4] != 'y' and text[4] != 'Y')) + throw pqxx::conversion_error{err_bad_input(text)}; + return {}; + break; + + default: throw pqxx::conversion_error{err_bad_input(text)}; + } + + auto scan{internal::get_glyph_scanner(internal::encoding_group::UTF8)}; + // The field parser uses this to track which field it's parsing, and + // when not to expect a field separator. + std::size_t index{0}; + // The last field we expect to see. + static constexpr std::size_t last{1}; + // Current parsing position. We skip the opening parenthesis or bracket. + std::size_t pos{1}; + // The string may leave out either bound to indicate that it's unlimited. + std::optional lower, upper; + // We reuse the same field parser we use for composite values and arrays. + internal::parse_composite_field(index, text, pos, lower, scan, last); + internal::parse_composite_field(index, text, pos, upper, scan, last); + + // We need one more character: the closing parenthesis or bracket. + if (pos != std::size(text)) + throw pqxx::conversion_error{err_bad_input(text)}; + char const closing{text[pos - 1]}; + if (closing != ')' and closing != ']') + throw pqxx::conversion_error{err_bad_input(text)}; + bool const right_inc{closing == ']'}; + + range_bound lower_bound{no_bound{}}, upper_bound{no_bound{}}; + if (lower) + { + if (left_inc) + lower_bound = inclusive_bound{*lower}; + else + lower_bound = exclusive_bound{*lower}; + } + if (upper) + { + if (right_inc) + upper_bound = inclusive_bound{*upper}; + else + upper_bound = exclusive_bound{*upper}; + } + + return {lower_bound, upper_bound}; + } + + [[nodiscard]] static inline constexpr std::size_t + size_buffer(range const &value) noexcept + { + TYPE const *lower{value.lower_bound().value()}, + *upper{value.upper_bound().value()}; + std::size_t const lsz{ + lower == nullptr ? 0 : string_traits::size_buffer(*lower) - 1}, + usz{upper == nullptr ? 0 : string_traits::size_buffer(*upper) - 1}; + + if (value.empty()) + return std::size(s_empty) + 1; + else + return 1 + lsz + 1 + usz + 2; + } + +private: + static constexpr zview s_empty{"empty"_zv}; + static constexpr auto s_overrun{"Not enough space in buffer for range."_zv}; + + /// Compose error message for invalid range input. + static std::string err_bad_input(std::string_view text) + { + return internal::concat("Invalid range input: '", text, "'"); + } +}; + + +/// A range type does not have an innate null value. +template struct nullness> : no_null> +{}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result new file mode 100644 index 000000000..523394b72 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result @@ -0,0 +1,16 @@ +/** pqxx::result class and support classes. + * + * pqxx::result represents the set of result rows from a database query. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" + +#include "pqxx/result.hxx" + +// Now include some types which depend on result, but which the user will +// expect to see defined after including this header. +#include "pqxx/internal/result_iterator.hxx" +#include "pqxx/field.hxx" +#include "pqxx/internal/result_iter.hxx" + +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result.hxx new file mode 100644 index 000000000..6c41cc096 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result.hxx @@ -0,0 +1,335 @@ +/* Definitions for the pqxx::result class and support classes. + * + * pqxx::result represents the set of result rows from a database query. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_RESULT +#define PQXX_H_RESULT + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include +#include + +#include "pqxx/except.hxx" +#include "pqxx/types.hxx" +#include "pqxx/util.hxx" +#include "pqxx/zview.hxx" + +#include "pqxx/internal/encodings.hxx" + + +namespace pqxx::internal +{ +// TODO: Make noexcept (but breaks ABI). +PQXX_LIBEXPORT void clear_result(pq::PGresult const *); +} // namespace pqxx::internal + + +namespace pqxx::internal::gate +{ +class result_connection; +class result_creation; +class result_pipeline; +class result_row; +class result_sql_cursor; +} // namespace pqxx::internal::gate + + +namespace pqxx +{ +/// Result set containing data returned by a query or command. +/** This behaves as a container (as defined by the C++ standard library) and + * provides random access const iterators to iterate over its rows. You can + * also access a row by indexing a `result R` by the row's zero-based + * number: + * + * + * for (result::size_type i=0; i < std::size(R); ++i) Process(R[i]); + * + * + * Result sets in libpqxx are lightweight, reference-counted wrapper objects + * which are relatively small and cheap to copy. Think of a result object as + * a "smart pointer" to an underlying result set. + * + * @warning The result set that a result object points to is not thread-safe. + * If you copy a result object, it still refers to the same underlying result + * set. So never copy, destroy, query, or otherwise access a result while + * another thread may be copying, destroying, querying, or otherwise accessing + * the same result set--even if it is doing so through a different result + * object! + */ +class PQXX_LIBEXPORT result +{ +public: + using size_type = result_size_type; + using difference_type = result_difference_type; + using reference = row; + using const_iterator = const_result_iterator; + using pointer = const_iterator; + using iterator = const_iterator; + using const_reverse_iterator = const_reverse_result_iterator; + using reverse_iterator = const_reverse_iterator; + + result() noexcept : + m_data{make_data_pointer()}, + m_query{}, + m_encoding{internal::encoding_group::MONOBYTE} + {} + + result(result const &rhs) noexcept = default; + result(result &&rhs) noexcept = default; + + /// Assign one result to another. + /** Copying results is cheap: it copies only smart pointers, but the actual + * data stays in the same place. + */ + result &operator=(result const &rhs) noexcept = default; + + /// Assign one result to another, invaliding the old one. + result &operator=(result &&rhs) noexcept = default; + + /** + * @name Comparisons + * + * You can compare results for equality. Beware: this is a very strict, + * dumb comparison. The smallest difference between two results (such as a + * string "Foo" versus a string "foo") will make them unequal. + */ + //@{ + /// Compare two results for equality. + [[nodiscard]] bool operator==(result const &) const noexcept; + /// Compare two results for inequality. + [[nodiscard]] bool operator!=(result const &rhs) const noexcept + { + return not operator==(rhs); + } + //@} + + /// Iterate rows, reading them directly into a tuple of "TYPE...". + /** Converts the fields to values of the given respective types. + * + * Use this only with a ranged "for" loop. The iteration produces + * std::tuple which you can "unpack" to a series of `auto` + * variables. + */ + template auto iter() const; + + [[nodiscard]] const_reverse_iterator rbegin() const; + [[nodiscard]] const_reverse_iterator crbegin() const; + [[nodiscard]] const_reverse_iterator rend() const; + [[nodiscard]] const_reverse_iterator crend() const; + + [[nodiscard]] const_iterator begin() const noexcept; + [[nodiscard]] const_iterator cbegin() const noexcept; + [[nodiscard]] inline const_iterator end() const noexcept; + [[nodiscard]] inline const_iterator cend() const noexcept; + + [[nodiscard]] reference front() const noexcept; + [[nodiscard]] reference back() const noexcept; + + [[nodiscard]] PQXX_PURE size_type size() const noexcept; + [[nodiscard]] PQXX_PURE bool empty() const noexcept; + [[nodiscard]] size_type capacity() const noexcept { return size(); } + + /// Exchange two `result` values in an exception-safe manner. + /** If the swap fails, the two values will be exactly as they were before. + * + * The swap is not necessarily thread-safe. + */ + void swap(result &) noexcept; + + /// Index a row by number. + /** This returns a @ref row object. Generally you should not keep the row + * around as a variable, but if you do, make sure that your variable is a + * `row`, not a `row&`. + */ + [[nodiscard]] row operator[](size_type i) const noexcept; + +#if defined(PQXX_HAVE_MULTIDIMENSIONAL_SUBSCRIPT) + // TODO: If C++23 will let us, also accept string for the column. + [[nodiscard]] field + operator[](size_type row_num, row_size_type col_num) const noexcept; +#endif + + /// Index a row by number, but check that the row number is valid. + row at(size_type) const; + + /// Index a field by row number and column number. + field at(size_type, row_size_type) const; + + /// Let go of the result's data. + /** Use this if you need to deallocate the result data earlier than you can + * destroy the `result` object itself. + * + * Multiple `result` objects can refer to the same set of underlying data. + * The underlying data will be deallocated once all `result` objects that + * refer to it are cleared or destroyed. + */ + void clear() noexcept + { + m_data.reset(); + m_query = nullptr; + } + + /** + * @name Column information + */ + //@{ + /// Number of columns in result. + [[nodiscard]] PQXX_PURE row_size_type columns() const noexcept; + + /// Number of given column (throws exception if it doesn't exist). + [[nodiscard]] row_size_type column_number(zview name) const; + + /// Name of column with this number (throws exception if it doesn't exist) + [[nodiscard]] char const *column_name(row_size_type number) const &; + + /// Return column's type, as an OID from the system catalogue. + [[nodiscard]] oid column_type(row_size_type col_num) const; + + /// Return column's type, as an OID from the system catalogue. + [[nodiscard]] oid column_type(zview col_name) const + { + return column_type(column_number(col_name)); + } + + /// What table did this column come from? + [[nodiscard]] oid column_table(row_size_type col_num) const; + + /// What table did this column come from? + [[nodiscard]] oid column_table(zview col_name) const + { + return column_table(column_number(col_name)); + } + + /// What column in its table did this column come from? + [[nodiscard]] row_size_type table_column(row_size_type col_num) const; + + /// What column in its table did this column come from? + [[nodiscard]] row_size_type table_column(zview col_name) const + { + return table_column(column_number(col_name)); + } + //@} + + /// Query that produced this result, if available (empty string otherwise) + [[nodiscard]] PQXX_PURE std::string const &query() const &noexcept; + + /// If command was an `INSERT` of 1 row, return oid of the inserted row. + /** @return Identifier of inserted row if exactly one row was inserted, or + * @ref oid_none otherwise. + */ + [[nodiscard]] PQXX_PURE oid inserted_oid() const; + + /// If command was `INSERT`, `UPDATE`, or `DELETE`: number of affected rows. + /** @return Number of affected rows if last command was `INSERT`, `UPDATE`, + * or `DELETE`; zero for all other commands. + */ + [[nodiscard]] PQXX_PURE size_type affected_rows() const; + + // C++20: Concept like std::invocable, but without specifying param types. + /// Run `func` on each row, passing the row's fields as parameters. + /** Goes through the rows from first to last. You provide a callable `func`. + * + * For each row in the `result`, `for_each` will call `func`. It converts + * the row's fields to the types of `func`'s parameters, and pass them to + * `func`. + * + * (Therefore `func` must have a _single_ signature. It can't be a generic + * lambda, or an object of a class with multiple overloaded function call + * operators. Otherwise, `for_each` will have no way to detect a parameter + * list without ambiguity.) + * + * If any of your parameter types is `std::string_view`, it refers to the + * underlying storage of this `result`. + * + * If any of your parameter types is a reference type, its argument will + * refer to a temporary value which only lives for the duration of that + * single invocation to `func`. If the reference is an lvalue reference, it + * must be `const`. + * + * For example, this queries employee names and salaries from the database + * and prints how much each would like to earn instead: + * ```cxx + * tx.exec("SELECT name, salary FROM employee").for_each( + * [](std::string_view name, float salary){ + * std::cout << name << " would like " << salary * 2 << ".\n"; + * }) + * ``` + * + * If `func` throws an exception, processing stops at that point and + * propagates the exception. + * + * @throws usage_error if `func`'s number of parameters does not match the + * number of columns in this result. + */ + template inline void for_each(CALLABLE &&func) const; + +private: + using data_pointer = std::shared_ptr; + + /// Underlying libpq result set. + data_pointer m_data; + + /// Factory for data_pointer. + static data_pointer + make_data_pointer(internal::pq::PGresult const *res = nullptr) noexcept + { + return {res, internal::clear_result}; + } + + friend class pqxx::internal::gate::result_pipeline; + PQXX_PURE std::shared_ptr query_ptr() const noexcept + { + return m_query; + } + + /// Query string. + std::shared_ptr m_query; + + internal::encoding_group m_encoding; + + static std::string const s_empty_string; + + friend class pqxx::field; + // TODO: noexcept. Breaks ABI. + PQXX_PURE char const *get_value(size_type row, row_size_type col) const; + // TODO: noexcept. Breaks ABI. + PQXX_PURE bool get_is_null(size_type row, row_size_type col) const; + PQXX_PURE + field_size_type get_length(size_type, row_size_type) const noexcept; + + friend class pqxx::internal::gate::result_creation; + result( + internal::pq::PGresult *rhs, std::shared_ptr query, + internal::encoding_group enc); + + PQXX_PRIVATE void check_status(std::string_view desc = ""sv) const; + + friend class pqxx::internal::gate::result_connection; + friend class pqxx::internal::gate::result_row; + bool operator!() const noexcept { return m_data.get() == nullptr; } + operator bool() const noexcept { return m_data.get() != nullptr; } + + [[noreturn]] PQXX_PRIVATE void + throw_sql_error(std::string const &Err, std::string const &Query) const; + PQXX_PRIVATE PQXX_PURE int errorposition() const; + PQXX_PRIVATE std::string status_error() const; + + friend class pqxx::internal::gate::result_sql_cursor; + PQXX_PURE char const *cmd_status() const noexcept; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction new file mode 100644 index 000000000..04b71d7cc --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction @@ -0,0 +1,8 @@ +/** pqxx::robusttransaction class. + * + * pqxx::robusttransaction is a slower but safer transaction class. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/robusttransaction.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction.hxx new file mode 100644 index 000000000..faf6dbf5e --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction.hxx @@ -0,0 +1,120 @@ +/* Definition of the pqxx::robusttransaction class. + * + * pqxx::robusttransaction is a slower but safer transaction class. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/robusttransaction instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ROBUSTTRANSACTION +#define PQXX_H_ROBUSTTRANSACTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/dbtransaction.hxx" + +namespace pqxx::internal +{ +/// Helper base class for the @ref robusttransaction class template. +class PQXX_LIBEXPORT PQXX_NOVTABLE basic_robusttransaction + : public dbtransaction +{ +public: + virtual ~basic_robusttransaction() override = 0; + +protected: + basic_robusttransaction( + connection &c, zview begin_command, std::string_view tname); + basic_robusttransaction(connection &c, zview begin_command); + +private: + using IDType = unsigned long; + + std::string m_conn_string; + std::string m_xid; + int m_backendpid = -1; + + void init(zview begin_command); + + // @warning This function will become `final`. + virtual void do_commit() override; +}; +} // namespace pqxx::internal + + +namespace pqxx +{ +/** + * @ingroup transactions + * + * @{ + */ + +/// Slightly slower, better-fortified version of transaction. +/** Requires PostgreSQL 10 or better. + * + * robusttransaction is similar to transaction, but spends more time and effort + * to deal with the hopefully rare case that the connection to the backend is + * lost just while it's trying to commit. In such cases, the client does not + * know whether the backend (on the other side of the broken connection) + * managed to commit the transaction. + * + * When this happens, robusttransaction tries to reconnect to the database and + * figure out what happened. + * + * This service level was made optional since you may not want to pay the + * overhead where it is not necessary. Certainly the use of this class makes + * no sense for local connections, or for transactions that read the database + * but never modify it, or for noncritical database manipulations. + * + * Besides being slower, it's also more complex. Which means that in practice + * a robusttransaction could actually fail more instead of less often than a + * normal transaction. What robusttransaction tries to achieve is to give you + * certainty, not just be more successful per se. + */ +template +class robusttransaction final : public internal::basic_robusttransaction +{ +public: + /** Create robusttransaction of given name. + * @param c Connection inside which this robusttransaction should live. + * @param tname optional human-readable name for this transaction. + */ + robusttransaction(connection &c, std::string_view tname) : + internal::basic_robusttransaction{ + c, pqxx::internal::begin_cmd, + tname} + {} + + /** Create robusttransaction of given name. + * @param c Connection inside which this robusttransaction should live. + * @param tname optional human-readable name for this transaction. + */ + robusttransaction(connection &c, std::string &&tname) : + internal::basic_robusttransaction{ + c, pqxx::internal::begin_cmd, + std::move(tname)} + {} + + /** Create robusttransaction of given name. + * @param c Connection inside which this robusttransaction should live. + */ + explicit robusttransaction(connection &c) : + internal::basic_robusttransaction{ + c, pqxx::internal::begin_cmd} + {} + + virtual ~robusttransaction() noexcept override { close(); } +}; + +/** + * @} + */ +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row new file mode 100644 index 000000000..62a950ac8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row @@ -0,0 +1,11 @@ +/** pqxx::row class. + * + * pqxx::row refers to a row in a result. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" + +#include "pqxx/result.hxx" +#include "pqxx/row.hxx" + +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row.hxx new file mode 100644 index 000000000..5be5132e3 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row.hxx @@ -0,0 +1,561 @@ +/* Definitions for the pqxx::result class and support classes. + * + * pqxx::result represents the set of result rows from a database query. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ROW +#define PQXX_H_ROW + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/except.hxx" +#include "pqxx/field.hxx" +#include "pqxx/result.hxx" + +#include "pqxx/internal/concat.hxx" + +namespace pqxx::internal +{ +template class result_iter; +} // namespace pqxx::internal + + +namespace pqxx +{ +/// Reference to one row in a result. +/** A row represents one row (also called a row) in a query result set. + * It also acts as a container mapping column numbers or names to field + * values (see below): + * + * ```cxx + * cout << row["date"].c_str() << ": " << row["name"].c_str() << endl; + * ``` + * + * The row itself acts like a (non-modifyable) container, complete with its + * own const_iterator and const_reverse_iterator. + */ +class PQXX_LIBEXPORT row +{ +public: + using size_type = row_size_type; + using difference_type = row_difference_type; + using const_iterator = const_row_iterator; + using iterator = const_iterator; + using reference = field; + using pointer = const_row_iterator; + using const_reverse_iterator = const_reverse_row_iterator; + using reverse_iterator = const_reverse_iterator; + + row() noexcept = default; + row(row &&) noexcept = default; + row(row const &) noexcept = default; + row &operator=(row const &) noexcept = default; + row &operator=(row &&) noexcept = default; + + /** + * @name Comparison + */ + //@{ + [[nodiscard]] PQXX_PURE bool operator==(row const &) const noexcept; + [[nodiscard]] bool operator!=(row const &rhs) const noexcept + { + return not operator==(rhs); + } + //@} + + [[nodiscard]] const_iterator begin() const noexcept; + [[nodiscard]] const_iterator cbegin() const noexcept; + [[nodiscard]] const_iterator end() const noexcept; + [[nodiscard]] const_iterator cend() const noexcept; + + /** + * @name Field access + */ + //@{ + [[nodiscard]] reference front() const noexcept; + [[nodiscard]] reference back() const noexcept; + + // TODO: noexcept. Breaks ABI. + [[nodiscard]] const_reverse_row_iterator rbegin() const; + // TODO: noexcept. Breaks ABI. + [[nodiscard]] const_reverse_row_iterator crbegin() const; + // TODO: noexcept. Breaks ABI. + [[nodiscard]] const_reverse_row_iterator rend() const; + // TODO: noexcept. Breaks ABI. + [[nodiscard]] const_reverse_row_iterator crend() const; + + [[nodiscard]] reference operator[](size_type) const noexcept; + /** Address field by name. + * @warning This is much slower than indexing by number, or iterating. + */ + [[nodiscard]] reference operator[](zview col_name) const; + + reference at(size_type) const; + /** Address field by name. + * @warning This is much slower than indexing by number, or iterating. + */ + reference at(zview col_name) const; + + [[nodiscard]] constexpr size_type size() const noexcept + { + return m_end - m_begin; + } + + [[deprecated("Swap iterators, not rows.")]] void swap(row &) noexcept; + + /// Row number, assuming this is a real row and not end()/rend(). + [[nodiscard]] constexpr result::size_type rownumber() const noexcept + { + return m_index; + } + + /** + * @name Column information + */ + //@{ + /// Number of given column (throws exception if it doesn't exist). + [[nodiscard]] size_type column_number(zview col_name) const; + + /// Return a column's type. + [[nodiscard]] oid column_type(size_type) const; + + /// Return a column's type. + [[nodiscard]] oid column_type(zview col_name) const + { + return column_type(column_number(col_name)); + } + + /// What table did this column come from? + [[nodiscard]] oid column_table(size_type col_num) const; + + /// What table did this column come from? + [[nodiscard]] oid column_table(zview col_name) const + { + return column_table(column_number(col_name)); + } + + /// What column number in its table did this result column come from? + /** A meaningful answer can be given only if the column in question comes + * directly from a column in a table. If the column is computed in any + * other way, a logic_error will be thrown. + * + * @param col_num a zero-based column number in this result set + * @return a zero-based column number in originating table + */ + [[nodiscard]] size_type table_column(size_type) const; + + /// What column number in its table did this result column come from? + [[nodiscard]] size_type table_column(zview col_name) const + { + return table_column(column_number(col_name)); + } + //@} + + [[nodiscard]] constexpr result::size_type num() const noexcept + { + return rownumber(); + } + + /** Produce a slice of this row, containing the given range of columns. + * + * @deprecated I haven't heard of anyone caring about row slicing at all in + * at least the last 15 years. Yet it adds complexity, so unless anyone + * files a bug explaining why they really need this feature, I'm going to + * remove it. Even if they do, the feature may need an update. + * + * The slice runs from the range's starting column to the range's end + * column, exclusive. It looks just like a normal result row, except + * slices can be empty. + */ + [[deprecated("Row slicing is going away. File a bug if you need it.")]] row + slice(size_type sbegin, size_type send) const; + + /// Is this a row without fields? Can only happen to a slice. + [[nodiscard, deprecated("Row slicing is going away.")]] PQXX_PURE bool + empty() const noexcept; + + /// Extract entire row's values into a tuple. + /** Converts to the types of the tuple's respective fields. + */ + template void to(Tuple &t) const + { + check_size(std::tuple_size_v); + convert(t); + } + + template std::tuple as() const + { + check_size(sizeof...(TYPE)); + using seq = std::make_index_sequence; + return get_tuple>(seq{}); + } + +protected: + friend class const_row_iterator; + friend class result; + row(result const &r, result_size_type index, size_type cols) noexcept; + + /// Throw @ref usage_error if row size is not `expected`. + void check_size(size_type expected) const + { + if (size() != expected) + throw usage_error{internal::concat( + "Tried to extract ", expected, " field(s) from a row of ", size(), + ".")}; + } + + /// Convert to a given tuple of values, don't check sizes. + /** We need this for cases where we have a full tuple of field types, but + * not a parameter pack. + */ + template TUPLE as_tuple() const + { + using seq = std::make_index_sequence>; + return get_tuple(seq{}); + } + + template friend class pqxx::internal::result_iter; + /// Convert entire row to tuple fields, without checking row size. + template void convert(Tuple &t) const + { + extract_fields(t, std::make_index_sequence>{}); + } + + friend class field; + + /// Result set of which this is one row. + result m_result; + + /// Row number. + /** + * You'd expect this to be unsigned, but due to the way reverse iterators + * are related to regular iterators, it must be allowed to underflow to -1. + */ + result::size_type m_index = 0; + + // TODO: Remove m_begin and (if possible) m_end when we remove slice(). + /// First column in slice. This row ignores lower-numbered columns. + size_type m_begin = 0; + /// End column in slice. This row only sees lower-numbered columns. + size_type m_end = 0; + +private: + template + void extract_fields(Tuple &t, std::index_sequence) const + { + (extract_value(t), ...); + } + + template + void extract_value(Tuple &t) const; + + /// Convert row's values as a new tuple. + template + auto get_tuple(std::index_sequence) const + { + return std::make_tuple(get_field()...); + } + + /// Extract and convert a field. + template auto get_field() const + { + return (*this)[index].as>(); + } +}; + + +/// Iterator for fields in a row. Use as row::const_iterator. +class PQXX_LIBEXPORT const_row_iterator : public field +{ +public: + using iterator_category = std::random_access_iterator_tag; + using value_type = field const; + using pointer = field const *; + using size_type = row_size_type; + using difference_type = row_difference_type; + using reference = field; + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + const_row_iterator() = default; +#include "pqxx/internal/ignore-deprecated-post.hxx" + const_row_iterator(row const &t, row_size_type c) noexcept : + field{t.m_result, t.m_index, c} + {} + const_row_iterator(field const &F) noexcept : field{F} {} + const_row_iterator(const_row_iterator const &) noexcept = default; + const_row_iterator(const_row_iterator &&) noexcept = default; + + /** + * @name Dereferencing operators + */ + //@{ + [[nodiscard]] constexpr pointer operator->() const noexcept { return this; } + [[nodiscard]] reference operator*() const noexcept { return {*this}; } + //@} + + /** + * @name Manipulations + */ + //@{ + const_row_iterator &operator=(const_row_iterator const &) noexcept = default; + const_row_iterator &operator=(const_row_iterator &&) noexcept = default; + + // TODO: noexcept. Breaks ABI. + const_row_iterator operator++(int); + const_row_iterator &operator++() noexcept + { + ++m_col; + return *this; + } + // TODO: noexcept. Breaks ABI. + const_row_iterator operator--(int); + const_row_iterator &operator--() noexcept + { + --m_col; + return *this; + } + + const_row_iterator &operator+=(difference_type i) noexcept + { + m_col = size_type(difference_type(m_col) + i); + return *this; + } + const_row_iterator &operator-=(difference_type i) noexcept + { + m_col = size_type(difference_type(m_col) - i); + return *this; + } + //@} + + /** + * @name Comparisons + */ + //@{ + [[nodiscard]] constexpr bool + operator==(const_row_iterator const &i) const noexcept + { + return col() == i.col(); + } + [[nodiscard]] constexpr bool + operator!=(const_row_iterator const &i) const noexcept + { + return col() != i.col(); + } + [[nodiscard]] constexpr bool + operator<(const_row_iterator const &i) const noexcept + { + return col() < i.col(); + } + [[nodiscard]] constexpr bool + operator<=(const_row_iterator const &i) const noexcept + { + return col() <= i.col(); + } + [[nodiscard]] constexpr bool + operator>(const_row_iterator const &i) const noexcept + { + return col() > i.col(); + } + [[nodiscard]] constexpr bool + operator>=(const_row_iterator const &i) const noexcept + { + return col() >= i.col(); + } + //@} + + /** + * @name Arithmetic operators + */ + //@{ + [[nodiscard]] inline const_row_iterator + operator+(difference_type) const noexcept; + + friend const_row_iterator + operator+(difference_type, const_row_iterator const &) noexcept; + + [[nodiscard]] inline const_row_iterator + operator-(difference_type) const noexcept; + [[nodiscard]] inline difference_type + operator-(const_row_iterator const &) const noexcept; + //@} +}; + + +/// Reverse iterator for a row. Use as row::const_reverse_iterator. +class PQXX_LIBEXPORT const_reverse_row_iterator : private const_row_iterator +{ +public: + using super = const_row_iterator; + using iterator_type = const_row_iterator; + using iterator_type::difference_type; + using iterator_type::iterator_category; + using iterator_type::pointer; + using value_type = iterator_type::value_type; + using reference = iterator_type::reference; + + const_reverse_row_iterator() noexcept = default; + const_reverse_row_iterator(const_reverse_row_iterator const &) noexcept = + default; + const_reverse_row_iterator(const_reverse_row_iterator &&) noexcept = default; + + explicit const_reverse_row_iterator(super const &rhs) noexcept : + const_row_iterator{rhs} + { + super::operator--(); + } + + [[nodiscard]] PQXX_PURE iterator_type base() const noexcept; + + /** + * @name Dereferencing operators + */ + //@{ + using iterator_type::operator->; + using iterator_type::operator*; + //@} + + /** + * @name Manipulations + */ + //@{ + const_reverse_row_iterator & + operator=(const_reverse_row_iterator const &r) noexcept + { + iterator_type::operator=(r); + return *this; + } + const_reverse_row_iterator operator++() noexcept + { + iterator_type::operator--(); + return *this; + } + // TODO: noexcept. Breaks ABI. + const_reverse_row_iterator operator++(int); + const_reverse_row_iterator &operator--() noexcept + { + iterator_type::operator++(); + return *this; + } + const_reverse_row_iterator operator--(int); + // TODO: noexcept. Breaks ABI. + const_reverse_row_iterator &operator+=(difference_type i) noexcept + { + iterator_type::operator-=(i); + return *this; + } + const_reverse_row_iterator &operator-=(difference_type i) noexcept + { + iterator_type::operator+=(i); + return *this; + } + //@} + + /** + * @name Arithmetic operators + */ + //@{ + [[nodiscard]] const_reverse_row_iterator + operator+(difference_type i) const noexcept + { + return const_reverse_row_iterator{base() - i}; + } + [[nodiscard]] const_reverse_row_iterator + operator-(difference_type i) noexcept + { + return const_reverse_row_iterator{base() + i}; + } + [[nodiscard]] difference_type + operator-(const_reverse_row_iterator const &rhs) const noexcept + { + return rhs.const_row_iterator::operator-(*this); + } + //@} + + /** + * @name Comparisons + */ + //@{ + [[nodiscard]] bool + operator==(const_reverse_row_iterator const &rhs) const noexcept + { + return iterator_type::operator==(rhs); + } + [[nodiscard]] bool + operator!=(const_reverse_row_iterator const &rhs) const noexcept + { + return !operator==(rhs); + } + + [[nodiscard]] constexpr bool + operator<(const_reverse_row_iterator const &rhs) const noexcept + { + return iterator_type::operator>(rhs); + } + [[nodiscard]] constexpr bool + operator<=(const_reverse_row_iterator const &rhs) const noexcept + { + return iterator_type::operator>=(rhs); + } + [[nodiscard]] constexpr bool + operator>(const_reverse_row_iterator const &rhs) const noexcept + { + return iterator_type::operator<(rhs); + } + [[nodiscard]] constexpr bool + operator>=(const_reverse_row_iterator const &rhs) const noexcept + { + return iterator_type::operator<=(rhs); + } + //@} +}; + + +const_row_iterator +const_row_iterator::operator+(difference_type o) const noexcept +{ + // TODO:: More direct route to home().columns()? + return { + row{home(), idx(), home().columns()}, + size_type(difference_type(col()) + o)}; +} + +inline const_row_iterator operator+( + const_row_iterator::difference_type o, const_row_iterator const &i) noexcept +{ + return i + o; +} + +inline const_row_iterator +const_row_iterator::operator-(difference_type o) const noexcept +{ + // TODO:: More direct route to home().columns()? + return { + row{home(), idx(), home().columns()}, + size_type(difference_type(col()) - o)}; +} + +inline const_row_iterator::difference_type +const_row_iterator::operator-(const_row_iterator const &i) const noexcept +{ + return difference_type(num() - i.num()); +} + + +template +inline void row::extract_value(Tuple &t) const +{ + using field_type = strip_t(t))>; + field const f{m_result, m_index, index}; + std::get(t) = from_string(f); +} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list new file mode 100644 index 000000000..1bdf51c6a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list @@ -0,0 +1,6 @@ +/** Helper similar to Python's @c str.join(). + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/separated_list.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list.hxx new file mode 100644 index 000000000..d4230ea08 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list.hxx @@ -0,0 +1,142 @@ +/* Helper similar to Python's `str.join()`. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/separated_list instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_SEPARATED_LIST +#define PQXX_H_SEPARATED_LIST + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + +#include "pqxx/strconv.hxx" + +// C++20: Simplify using std::ranges::range. +// C++20: Optimise buffer allocation using random_access_range/iterator. +namespace pqxx +{ +/** + * @defgroup utility Utility functions + */ +//@{ + +/// Represent sequence of values as a string, joined by a given separator. +/** + * Use this to turn e.g. the numbers 1, 2, and 3 into a string "1, 2, 3". + * + * @param sep separator string (to be placed between items) + * @param begin beginning of items sequence + * @param end end of items sequence + * @param access functor defining how to dereference sequence elements + */ +template +[[nodiscard]] inline std::string +separated_list(std::string_view sep, ITER begin, ITER end, ACCESS access) +{ + if (end == begin) + return {}; + auto next{begin}; + ++next; + if (next == end) + return to_string(access(begin)); + + // From here on, we've got at least 2 elements -- meaning that we need sep. + using elt_type = strip_t; + using traits = string_traits; + + std::size_t budget{0}; + for (ITER cnt{begin}; cnt != end; ++cnt) + budget += traits::size_buffer(access(cnt)); + budget += + static_cast(std::distance(begin, end)) * std::size(sep); + + std::string result; + result.resize(budget); + + char *const data{result.data()}; + char *here{data}; + char *stop{data + budget}; + here = traits::into_buf(here, stop, access(begin)) - 1; + for (++begin; begin != end; ++begin) + { + here += sep.copy(here, std::size(sep)); + here = traits::into_buf(here, stop, access(begin)) - 1; + } + result.resize(static_cast(here - data)); + return result; +} + + +/// Render sequence as a string, using given separator between items. +template +[[nodiscard]] inline std::string +separated_list(std::string_view sep, ITER begin, ITER end) +{ + return separated_list(sep, begin, end, [](ITER i) { return *i; }); +} + + +/// Render items in a container as a string, using given separator. +template +[[nodiscard]] inline auto +separated_list(std::string_view sep, CONTAINER const &c) + /* + Always std::string; necessary because SFINAE doesn't work with the + contents of function bodies, so the check for iterability has to be in + the signature. + */ + -> typename std::enable_if< + (not std::is_void::value and + not std::is_void::value), + std::string>::type +{ + return separated_list(sep, std::begin(c), std::end(c)); +} + + +/// Render items in a tuple as a string, using given separator. +template< + typename TUPLE, std::size_t INDEX = 0, typename ACCESS, + typename std::enable_if< + (INDEX == std::tuple_size::value - 1), int>::type = 0> +[[nodiscard]] inline std::string separated_list( + std::string_view /* sep */, TUPLE const &t, ACCESS const &access) +{ + return to_string(access(&std::get(t))); +} + +template< + typename TUPLE, std::size_t INDEX = 0, typename ACCESS, + typename std::enable_if< + (INDEX < std::tuple_size::value - 1), int>::type = 0> +[[nodiscard]] inline std::string +separated_list(std::string_view sep, TUPLE const &t, ACCESS const &access) +{ + std::string out{to_string(access(&std::get(t)))}; + out.append(sep); + out.append(separated_list(sep, t, access)); + return out; +} + +template< + typename TUPLE, std::size_t INDEX = 0, + typename std::enable_if< + (INDEX <= std::tuple_size::value), int>::type = 0> +[[nodiscard]] inline std::string +separated_list(std::string_view sep, TUPLE const &t) +{ + // TODO: Optimise allocation. + return separated_list(sep, t, [](TUPLE const &tup) { return *tup; }); +} +//@} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv new file mode 100644 index 000000000..aa2c40ed5 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv @@ -0,0 +1,6 @@ +/** String conversion definitions. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/strconv.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv.hxx new file mode 100644 index 000000000..863711228 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv.hxx @@ -0,0 +1,468 @@ +/* String conversion definitions. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stringconv instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_STRCONV +#define PQXX_H_STRCONV + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include +#include +#include +#include + +#if __has_include() +# include +#endif + +#if defined(PQXX_HAVE_RANGES) && __has_include() +# include +#endif + +#include "pqxx/except.hxx" +#include "pqxx/util.hxx" +#include "pqxx/zview.hxx" + + +namespace pqxx::internal +{ +/// Attempt to demangle @c std::type_info::name() to something human-readable. +PQXX_LIBEXPORT std::string demangle_type_name(char const[]); +} // namespace pqxx::internal + + +namespace pqxx +{ +/** + * @defgroup stringconversion String conversion + * + * The PostgreSQL server accepts and represents data in string form. It has + * its own formats for various data types. The string conversions define how + * various C++ types translate to and from their respective PostgreSQL text + * representations. + * + * Each conversion is defined by a specialisations of @c string_traits. It + * gets complicated if you want top performance, but until you do, all you + * really need to care about when converting values between C++ in-memory + * representations such as @c int and the postgres string representations is + * the @c pqxx::to_string and @c pqxx::from_string functions. + * + * If you need to convert a type which is not supported out of the box, you'll + * need to define your own specialisations for these templates, similar to the + * ones defined here and in `pqxx/conversions.hxx`. Any conversion code which + * "sees" your specialisation will now support your conversion. In particular, + * you'll be able to read result fields into a variable of the new type. + * + * There is a macro to help you define conversions for individual enumeration + * types. The conversion will represent enumeration values as numeric strings. + */ +//@{ + +/// A human-readable name for a type, used in error messages and such. +/** Actually this may not always be very user-friendly. It uses + * @c std::type_info::name(). On gcc-like compilers we try to demangle its + * output. Visual Studio produces human-friendly names out of the box. + * + * This variable is not inline. Inlining it gives rise to "memory leak" + * warnings from asan, the address sanitizer, possibly from use of + * @c std::type_info::name. + */ +template +std::string const type_name{internal::demangle_type_name(typeid(TYPE).name())}; + + +/// Traits describing a type's "null value," if any. +/** Some C++ types have a special value or state which correspond directly to + * SQL's NULL. + * + * The @c nullness traits describe whether it exists, and whether a particular + * value is null. + */ +template struct nullness +{ + /// Does this type have a null value? + static bool has_null; + + /// Is this type always null? + static bool always_null; + + /// Is @c value a null? + static bool is_null(TYPE const &value); + + /// Return a null value. + /** Don't use this in generic code to compare a value and see whether it is + * null. Some types may have multiple null values which do not compare as + * equal, or may define a null value which is not equal to anything including + * itself, like in SQL. + */ + [[nodiscard]] static TYPE null(); +}; + + +/// Nullness traits describing a type which does not have a null value. +template struct no_null +{ + /// Does @c TYPE have a "built-in null value"? + /** For example, a pointer can equal @c nullptr, which makes a very natural + * representation of an SQL null value. For such types, the code sometimes + * needs to make special allowances. + * + * for most types, such as @c int or @c std::string, there is no built-in + * null. If you want to represent an SQL null value for such a type, you + * would have to wrap it in something that does have a null value. For + * example, you could use @c std::optional for "either an @c int or a + * null value." + */ + static constexpr bool has_null = false; + + /// Are all values of this type null? + /** There are a few special C++ types which are always null - mainly + * @c std::nullptr_t. + */ + static constexpr bool always_null = false; + + /// Does a given value correspond to an SQL null value? + /** Most C++ types, such as @c int or @c std::string, have no inherent null + * value. But some types such as C-style string pointers do have a natural + * equivalent to an SQL null. + */ + [[nodiscard]] static constexpr bool is_null(TYPE const &) noexcept + { + return false; + } +}; + + +/// Traits class for use in string conversions. +/** Specialize this template for a type for which you wish to add to_string + * and from_string support. + * + * String conversions are not meant to work for nulls. Check for null before + * converting a value of @c TYPE to a string, or vice versa. + */ +template struct string_traits +{ + /// Return a @c string_view representing value, plus terminating zero. + /** Produces a @c string_view containing the PostgreSQL string representation + * for @c value. + * + * Uses the space from @c begin to @c end as a buffer, if needed. The + * returned string may lie somewhere in that buffer, or it may be a + * compile-time constant, or it may be null if value was a null value. Even + * if the string is stored in the buffer, its @c begin() may or may not be + * the same as @c begin. + * + * The @c string_view is guaranteed to be valid as long as the buffer from + * @c begin to @c end remains accessible and unmodified. + * + * @throws pqxx::conversion_overrun if the provided buffer space may not be + * enough. For maximum performance, this is a conservative estimate. It may + * complain about a buffer which is actually large enough for your value, if + * an exact check gets too expensive. + */ + [[nodiscard]] static inline zview + to_buf(char *begin, char *end, TYPE const &value); + + /// Write value's string representation into buffer at @c begin. + /** Assumes that value is non-null. + * + * Writes value's string representation into the buffer, starting exactly at + * @c begin, and ensuring a trailing zero. Returns the address just beyond + * the trailing zero, so the caller could use it as the @c begin for another + * call to @c into_buf writing a next value. + */ + static inline char *into_buf(char *begin, char *end, TYPE const &value); + + /// Parse a string representation of a @c TYPE value. + /** Throws @c conversion_error if @c value does not meet the expected format + * for a value of this type. + */ + [[nodiscard]] static inline TYPE from_string(std::string_view text); + + // C++20: Can we make these all constexpr? + /// Estimate how much buffer space is needed to represent value. + /** The estimate may be a little pessimistic, if it saves time. + * + * The estimate includes the terminating zero. + */ + [[nodiscard]] static inline std::size_t + size_buffer(TYPE const &value) noexcept; +}; + + +/// Nullness: Enums do not have an inherent null value. +template +struct nullness>> : no_null +{}; +} // namespace pqxx + + +namespace pqxx::internal +{ +/// Helper class for defining enum conversions. +/** The conversion will convert enum values to numeric strings, and vice versa. + * + * To define a string conversion for an enum type, derive a @c string_traits + * specialisation for the enum from this struct. + * + * There's usually an easier way though: the @c PQXX_DECLARE_ENUM_CONVERSION + * macro. Use @c enum_traits manually only if you need to customise your + * traits type in more detail. + */ +template struct enum_traits +{ + using impl_type = std::underlying_type_t; + using impl_traits = string_traits; + + [[nodiscard]] static constexpr zview + to_buf(char *begin, char *end, ENUM const &value) + { + return impl_traits::to_buf(begin, end, to_underlying(value)); + } + + static constexpr char *into_buf(char *begin, char *end, ENUM const &value) + { + return impl_traits::into_buf(begin, end, to_underlying(value)); + } + + [[nodiscard]] static ENUM from_string(std::string_view text) + { + return static_cast(impl_traits::from_string(text)); + } + + [[nodiscard]] static std::size_t size_buffer(ENUM const &value) noexcept + { + return impl_traits::size_buffer(to_underlying(value)); + } + +private: + // C++23: Replace with std::to_underlying. + static constexpr impl_type to_underlying(ENUM const &value) noexcept + { + return static_cast(value); + } +}; +} // namespace pqxx::internal + + +/// Macro: Define a string conversion for an enum type. +/** This specialises the @c pqxx::string_traits template, so use it in the + * @c ::pqxx namespace. + * + * For example: + * + * #include + * #include + * enum X { xa, xb }; + * namespace pqxx { PQXX_DECLARE_ENUM_CONVERSION(x); } + * int main() { std::cout << pqxx::to_string(xa) << std::endl; } + */ +#define PQXX_DECLARE_ENUM_CONVERSION(ENUM) \ + template<> struct string_traits : pqxx::internal::enum_traits \ + {}; \ + template<> inline std::string const type_name { #ENUM } + + +namespace pqxx +{ +/// Parse a value in postgres' text format as a TYPE. +/** If the form of the value found in the string does not match the expected + * type, e.g. if a decimal point is found when converting to an integer type, + * the conversion fails. Overflows (e.g. converting "9999999999" to a 16-bit + * C++ type) are also treated as errors. If in some cases this behaviour + * should be inappropriate, convert to something bigger such as @c long @c int + * first and then truncate the resulting value. + * + * Only the simplest possible conversions are supported. Fancy features like + * hexadecimal or octal, spurious signs, or exponent notation won't work. + * Whitespace is not stripped away. Only the kinds of strings that come out of + * PostgreSQL and out of to_string() can be converted. + */ +template +[[nodiscard]] inline TYPE from_string(std::string_view text) +{ + return string_traits::from_string(text); +} + + +/// "Convert" a std::string_view to a std::string_view. +/** Just returns its input. + * + * @warning Of course the result is only valid for as long as the original + * string remains valid! Never access the string referenced by the return + * value after the original has been destroyed. + */ +template<> +[[nodiscard]] inline std::string_view from_string(std::string_view text) +{ + return text; +} + + +/// Attempt to convert postgres-generated string to given built-in object. +/** This is like the single-argument form of the function, except instead of + * returning the value, it sets @c value. + * + * You may find this more convenient in that it infers the type you want from + * the argument you pass. But there are disadvantages: it requires an + * assignment operator, and it may be less efficient. + */ +template inline void from_string(std::string_view text, T &value) +{ + value = from_string(text); +} + + +/// Convert a value to a readable string that PostgreSQL will understand. +/** The conversion does no special formatting, and ignores any locale settings. + * The resulting string will be human-readable and in a format suitable for use + * in SQL queries. It won't have niceties such as "thousands separators" + * though. + */ +template inline std::string to_string(TYPE const &value); + + +/// Convert multiple values to strings inside a single buffer. +/** There must be enough room for all values, or this will throw + * @c conversion_overrun. You can obtain a conservative estimate of the buffer + * space required by calling @c size_buffer() on the values. + * + * The @c std::string_view results may point into the buffer, so don't assume + * that they will remain valid after you destruct or move the buffer. + */ +template +[[nodiscard]] inline std::vector +to_buf(char *here, char const *end, TYPE... value) +{ + return {[&here, end](auto v) { + auto begin = here; + here = string_traits::into_buf(begin, end, v); + // Exclude the trailing zero out of the string_view. + auto len{static_cast(here - begin) - 1}; + return std::string_view{begin, len}; + }(value)...}; +} + +/// Convert a value to a readable string that PostgreSQL will understand. +/** This variant of to_string can sometimes save a bit of time in loops, by + * re-using a std::string for multiple conversions. + */ +template +inline void into_string(TYPE const &value, std::string &out); + + +/// Is @c value null? +template +[[nodiscard]] inline constexpr bool is_null(TYPE const &value) noexcept +{ + return nullness>::is_null(value); +} + + +/// Estimate how much buffer space is needed to represent values as a string. +/** The estimate may be a little pessimistic, if it saves time. It also + * includes room for a terminating zero after each value. + */ +template +[[nodiscard]] inline std::size_t size_buffer(TYPE const &...value) noexcept +{ + return (string_traits>::size_buffer(value) + ...); +} + + +/// Does this type translate to an SQL array? +/** Specialisations may override this to be true for container types. + * + * This may not always be a black-and-white choice. For instance, a + * @c std::string is a container, but normally it translates to an SQL string, + * not an SQL array. + */ +template inline constexpr bool is_sql_array{false}; + + +/// Can we use this type in arrays and composite types without quoting them? +/** Define this as @c true only if values of @c TYPE can never contain any + * special characters that might need escaping or confuse the parsing of array + * or composite * types, such as commas, quotes, parentheses, braces, newlines, + * and so on. + * + * When converting a value of such a type to a string in an array or a field in + * a composite type, we do not need to add quotes, nor escape any special + * characters. + * + * This is just an optimisation, so it defaults to @c false to err on the side + * of slow correctness. + */ +template inline constexpr bool is_unquoted_safe{false}; + + +/// Element separator between SQL array elements of this type. +template inline constexpr char array_separator{','}; + + +/// What's the preferred format for passing non-null parameters of this type? +/** This affects how we pass parameters of @c TYPE when calling parameterised + * statements or prepared statements. + * + * Generally we pass parameters in text format, but binary strings are the + * exception. We also pass nulls in binary format, so this function need not + * handle null values. + */ +template inline constexpr format param_format(TYPE const &) +{ + return format::text; +} + + +/// Implement @c string_traits::to_buf by calling @c into_buf. +/** When you specialise @c string_traits for a new type, most of the time its + * @c to_buf implementation has no special optimisation tricks and just writes + * its text into the buffer it receives from the caller, starting at the + * beginning. + * + * In that common situation, you can implement @c to_buf as just a call to + * @c generic_to_buf. It will call @c into_buf and return the right result for + * @c to_buf. + */ +template +inline zview generic_to_buf(char *begin, char *end, TYPE const &value) +{ + using traits = string_traits; + // The trailing zero does not count towards the zview's size, so subtract 1 + // from the result we get from into_buf(). + if (is_null(value)) + return {}; + else + return {begin, traits::into_buf(begin, end, value) - begin - 1}; +} + + +#if defined(PQXX_HAVE_CONCEPTS) +/// Concept: Binary string, akin to @c std::string for binary data. +/** Any type that satisfies this concept can represent an SQL BYTEA value. + * + * A @c binary has a @c begin(), @c end(), @c size(), and @data(). Each byte + * is a @c std::byte, and they must all be laid out contiguously in memory so + * we can reference them by a pointer. + */ +template +concept binary = std::ranges::contiguous_range and + std::is_same_v>, std::byte>; +#endif +//@} +} // namespace pqxx + + +#include "pqxx/internal/conversions.hxx" +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from new file mode 100644 index 000000000..972762443 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from @@ -0,0 +1,8 @@ +/** pqxx::stream_from class. + * + * pqxx::stream_from enables optimized batch reads from a database table. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/stream_from.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from.hxx new file mode 100644 index 000000000..ff4a93d2e --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from.hxx @@ -0,0 +1,361 @@ +/* Definition of the pqxx::stream_from class. + * + * pqxx::stream_from enables optimized batch reads from a database table. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stream_from instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_STREAM_FROM +#define PQXX_H_STREAM_FROM + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include + +#include "pqxx/connection.hxx" +#include "pqxx/except.hxx" +#include "pqxx/internal/concat.hxx" +#include "pqxx/internal/encoding_group.hxx" +#include "pqxx/internal/stream_iterator.hxx" +#include "pqxx/separated_list.hxx" +#include "pqxx/transaction_focus.hxx" + + +namespace pqxx +{ +class transaction_base; + + +/// Pass this to a `stream_from` constructor to stream table contents. +/** @deprecated Use @ref stream_from::table() instead. + */ +constexpr from_table_t from_table; +/// Pass this to a `stream_from` constructor to stream query results. +/** @deprecated Use stream_from::query() instead. + */ +constexpr from_query_t from_query; + + +/// Stream data from the database. +/** For larger data sets, retrieving data this way is likely to be faster than + * executing a query and then iterating and converting the rows fields. You + * will also be able to start processing before all of the data has come in. + * + * There are also downsides. Not all kinds of query will work in a stream. + * But straightforward `SELECT` and `UPDATE ... RETURNING` queries should work. + * This function makes use of @ref pqxx::stream_from, which in turn uses + * PostgreSQL's `COPY` command, so see the documentation for those to get the + * full details. + * + * There are other downsides. If there stream encounters an error, it may + * leave the entire connection in an unusable state, so you'll have to give the + * whole thing up. Finally, opening a stream puts the connection in a special + * state, so you won't be able to do many other things with the connection or + * the transaction while the stream is open. + * + * There are two ways of starting a stream: you stream either all rows in a + * table (using one of the factories, `table()` or `raw_table()`), or the + * results of a query (using the `query()` factory). + * + * Usually you'll want the `stream` convenience wrapper in + * @ref transaction_base, * so you don't need to deal with this class directly. + * + * @warning While a stream is active, you cannot execute queries, open a + * pipeline, etc. on the same transaction. A transaction can have at most one + * object of a type derived from @ref pqxx::transaction_focus active on it at a + * time. + */ +class PQXX_LIBEXPORT stream_from : transaction_focus +{ +public: + using raw_line = + std::pair>, std::size_t>; + + /// Factory: Execute query, and stream the results. + /** The query can be a SELECT query or a VALUES query; or it can be an + * UPDATE, INSERT, or DELETE with a RETURNING clause. + * + * The query is executed as part of a COPY statement, so there are additional + * restrictions on what kind of query you can use here. See the PostgreSQL + * documentation for the COPY command: + * + * https://www.postgresql.org/docs/current/sql-copy.html + */ + static stream_from query(transaction_base &tx, std::string_view q) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return {tx, from_query, q}; +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /** + * @name Streaming data from tables + * + * You can use `stream_from` to read a table's contents. This is a quick + * and easy way to read a table, but it comes with limitations. It cannot + * stream from a view, only from a table. It does not support conditions. + * And there are no guarantees about ordering. If you need any of those + * things, consider streaming from a query instead. + */ + //@{ + + /// Factory: Stream data from a pre-quoted table and columns. + /** Use this factory if you need to create multiple streams using the same + * table path and/or columns list, and you want to save a bit of work on + * composing the internal SQL statement for starting the stream. It lets you + * compose the string representations for the table path and the columns + * list, so you can compute these once and then re-use them later. + * + * @param tx The transaction within which the stream will operate. + * @param path Name or path for the table upon which the stream will + * operate. If any part of the table path may contain special + * characters or be case-sensitive, quote the path using + * pqxx::connection::quote_table(). + * @param columns Columns which the stream will read. They should be + * comma-separated and, if needed, quoted. You can produce the string + * using pqxx::connection::quote_columns(). If you omit this argument, + * the stream will read all columns in the table, in schema order. + */ + static stream_from raw_table( + transaction_base &tx, std::string_view path, + std::string_view columns = ""sv); + + /// Factory: Stream data from a given table. + /** This is the convenient way to stream from a table. + */ + static stream_from table( + transaction_base &tx, table_path path, + std::initializer_list columns = {}); + //@} + + /// Execute query, and stream over the results. + /** @deprecated Use factory function @ref query instead. + */ + [[deprecated("Use query() factory instead.")]] stream_from( + transaction_base &, from_query_t, std::string_view query); + + /// Stream all rows in table, all columns. + /** @deprecated Use factories @ref table or @ref raw_table instead. + */ + [[deprecated("Use table() or raw_table() factory instead.")]] stream_from( + transaction_base &, from_table_t, std::string_view table); + + /// Stream given columns from all rows in table. + /** @deprecated Use factories @ref table or @ref raw_table instead. + */ + template + [[deprecated("Use table() or raw_table() factory instead.")]] stream_from( + transaction_base &, from_table_t, std::string_view table, + Iter columns_begin, Iter columns_end); + + /// Stream given columns from all rows in table. + /** @deprecated Use factory function @ref query instead. + */ + template + [[deprecated("Use table() or raw_table() factory instead.")]] stream_from( + transaction_base &tx, from_table_t, std::string_view table, + Columns const &columns); + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + /// @deprecated Use factories @ref table or @ref raw_table instead. + [[deprecated("Use the from_table_t overload instead.")]] stream_from( + transaction_base &tx, std::string_view table) : + stream_from{tx, from_table, table} + {} +#include "pqxx/internal/ignore-deprecated-post.hxx" + + /// @deprecated Use factories @ref table or @ref raw_table instead. + template + [[deprecated("Use the from_table_t overload instead.")]] stream_from( + transaction_base &tx, std::string_view table, Columns const &columns) : + stream_from{tx, from_table, table, columns} + {} + + /// @deprecated Use factories @ref table or @ref raw_table instead. + template + [[deprecated("Use the from_table_t overload instead.")]] stream_from( + transaction_base &, std::string_view table, Iter columns_begin, + Iter columns_end); + + ~stream_from() noexcept; + + /// May this stream still produce more data? + [[nodiscard]] constexpr operator bool() const noexcept + { + return not m_finished; + } + /// Has this stream produced all the data it is going to produce? + [[nodiscard]] constexpr bool operator!() const noexcept + { + return m_finished; + } + + /// Finish this stream. Call this before continuing to use the connection. + /** Consumes all remaining lines, and closes the stream. + * + * This may take a while if you're abandoning the stream before it's done, so + * skip it in error scenarios where you're not planning to use the connection + * again afterwards. + */ + void complete(); + + /// Read one row into a tuple. + /** Converts the row's fields into the fields making up the tuple. + * + * For a column which can contain nulls, be sure to give the corresponding + * tuple field a type which can be null. For example, to read a field as + * `int` when it may contain nulls, read it as `std::optional`. + * Using `std::shared_ptr` or `std::unique_ptr` will also work. + */ + template stream_from &operator>>(Tuple &); + + /// Doing this with a `std::variant` is going to be horrifically borked. + template + stream_from &operator>>(std::variant &) = delete; + + /// Iterate over this stream. Supports range-based "for" loops. + /** Produces an input iterator over the stream. + * + * Do not call this yourself. Use it like "for (auto data : stream.iter())". + */ + template [[nodiscard]] auto iter() & + { + return pqxx::internal::stream_input_iteration{*this}; + } + + /// Read a row. Return fields as views, valid until you read the next row. + /** Returns `nullptr` when there are no more rows to read. Do not attempt + * to read any further rows after that. + * + * Do not access the vector, or the storage referenced by the views, after + * closing or completing the stream, or after attempting to read a next row. + * + * A @ref pqxx::zview is like a `std::string_view`, but with the added + * guarantee that if its data pointer is non-null, the string is followed by + * a terminating zero (which falls just outside the view itself). + * + * If any of the views' data pointer is null, that means that the + * corresponding SQL field is null. + * + * @warning The return type may change in the future, to support C++20 + * coroutine-based usage. + */ + std::vector const *read_row() &; + + /// Read a raw line of text from the COPY command. + /** @warning Do not use this unless you really know what you're doing. */ + raw_line get_raw_line(); + +private: + // TODO: Clean up this signature once we cull the deprecated constructors. + /// @deprecated + stream_from( + transaction_base &tx, std::string_view table, std::string_view columns, + from_table_t); + + // TODO: Clean up this signature once we cull the deprecated constructors. + /// @deprecated + stream_from( + transaction_base &, std::string_view unquoted_table, + std::string_view columns, from_table_t, int); + + template + void extract_fields(Tuple &t, std::index_sequence) const + { + (extract_value(t), ...); + } + + pqxx::internal::glyph_scanner_func *m_glyph_scanner; + + /// Current row's fields' text, combined into one reusable string. + std::string m_row; + + /// The current row's fields. + std::vector m_fields; + + bool m_finished = false; + + void close(); + + template + void extract_value(Tuple &) const; + + /// Read a line of COPY data, write `m_row` and `m_fields`. + void parse_line(); +}; + + +template +inline stream_from::stream_from( + transaction_base &tx, from_table_t, std::string_view table_name, + Columns const &columns) : + stream_from{ + tx, from_table, table_name, std::begin(columns), std::end(columns)} +{} + + +template +inline stream_from::stream_from( + transaction_base &tx, from_table_t, std::string_view table, + Iter columns_begin, Iter columns_end) : + stream_from{ + tx, table, separated_list(",", columns_begin, columns_end), + from_table, 1} +{} + + +template inline stream_from &stream_from::operator>>(Tuple &t) +{ + if (m_finished) + return *this; + static constexpr auto tup_size{std::tuple_size_v}; + m_fields.reserve(tup_size); + parse_line(); + if (m_finished) + return *this; + + if (std::size(m_fields) != tup_size) + throw usage_error{internal::concat( + "Tried to extract ", tup_size, " field(s) from a stream of ", + std::size(m_fields), ".")}; + + extract_fields(t, std::make_index_sequence{}); + return *this; +} + + +template +inline void stream_from::extract_value(Tuple &t) const +{ + using field_type = strip_t(t))>; + using nullity = nullness; + assert(index < std::size(m_fields)); + if constexpr (nullity::always_null) + { + if (std::data(m_fields[index]) != nullptr) + throw conversion_error{"Streaming non-null value into null field."}; + } + else if (std::data(m_fields[index]) == nullptr) + { + if constexpr (nullity::has_null) + std::get(t) = nullity::null(); + else + internal::throw_null_conversion(type_name); + } + else + { + // Don't ever try to convert a non-null value to nullptr_t! + std::get(t) = from_string(m_fields[index]); + } +} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to new file mode 100644 index 000000000..8760cf1f4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to @@ -0,0 +1,8 @@ +/** pqxx::stream_to class. + * + * pqxx::stream_to enables optimized batch updates to a database table. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/stream_to.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to.hxx new file mode 100644 index 000000000..2a49d8f85 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to.hxx @@ -0,0 +1,455 @@ +/* Definition of the pqxx::stream_to class. + * + * pqxx::stream_to enables optimized batch updates to a database table. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stream_to.hxx instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_STREAM_TO +#define PQXX_H_STREAM_TO + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/separated_list.hxx" +#include "pqxx/transaction_base.hxx" + + +namespace pqxx +{ +/// Efficiently write data directly to a database table. +/** If you wish to insert rows of data into a table, you can compose INSERT + * statements and execute them. But it's slow and tedious, and you need to + * worry about quoting and escaping the data. + * + * If you're just inserting a single row, it probably won't matter much. You + * can use prepared or parameterised statements to take care of the escaping + * for you. But if you're inserting large numbers of rows you will want + * something better. + * + * Inserting rows one by one using INSERT statements involves a lot of + * pointless overhead, especially when you are working with a remote database + * server over the network. You may end up sending each row over the network + * as a separate query, and waiting for a reply. Do it "in bulk" using + * `stream_to`, and you may find that it goes many times faster. Sometimes + * you gain orders of magnitude in speed. + * + * Here's how it works: you create a `stream_to` stream to start writing to + * your table. You will probably want to specify the columns. Then, you + * feed your data into the stream one row at a time. And finally, you call the + * stream's @ref complete function to tell it to finalise the operation, wait + * for completion, and check for errors. + * + * (You _must_ complete the stream before committing or aborting the + * transaction. The connection is in a special state while the stream is + * active, where it can't process commands, and can't commit or abort a + * transaction.) + * + * So how do you feed a row of data into the stream? There's several ways, but + * the preferred one is to call its @ref write_values. Pass the field values + * as arguments. Doesn't matter what type they are, as long as libpqxx knows + * how to convert them to PostgreSQL's text format: `int`, `std::string` or + * `std:string_view`, `float` and `double`, `bool`... lots of basic types + * are supported. If some of the values are null, feel free to use + * `std::optional`, `std::shared_ptr`, or `std::unique_ptr`. + * + * The arguments' types don't even have to match the fields' SQL types. If you + * want to insert an `int` into a `DECIMAL` column, that's your choice -- it + * will produce a `DECIMAL` value which happens to be integral. Insert a + * `float` into a `VARCHAR` column? That's fine, you'll get a string whose + * contents happen to read like a number. And so on. You can even insert + * different types of value in the same column on different rows. If you have + * a code path where a particular field is always null, just insert `nullptr`. + * + * There is another way to insert rows: the `<<` ("shift-left") operator. + * It's not as fast and it doesn't support variable arguments: each row must be + * either a `std::tuple` or something iterable, such as a `std::vector`, or + * anything else with a `begin()` and `end()`. + * + * @warning While a stream is active, you cannot execute queries, open a + * pipeline, etc. on the same transaction. A transaction can have at most one + * object of a type derived from @ref pqxx::transaction_focus active on it at a + * time. + */ +class PQXX_LIBEXPORT stream_to : transaction_focus +{ +public: + /// Stream data to a pre-quoted table and columns. + /** This factory can be useful when it's not convenient to provide the + * columns list in the form of a `std::initializer_list`, or when the list + * of columns is simply not known at compile time. + * + * Also use this if you need to create multiple streams using the same table + * path and/or columns list, and you want to save a bit of work on composing + * the internal SQL statement for starting the stream. It lets you compose + * the string representations for the table path and the columns list, so you + * can compute these once and then re-use them later. + * + * @param tx The transaction within which the stream will operate. + * @param path Name or path for the table upon which the stream will + * operate. If any part of the table path may contain special + * characters or be case-sensitive, quote the path using + * pqxx::connection::quote_table(). + * @param columns Columns to which the stream will write. They should be + * comma-separated and, if needed, quoted. You can produce the string + * using pqxx::connection::quote_columns(). If you omit this argument, + * the stream will write all columns in the table, in schema order. + */ + static stream_to raw_table( + transaction_base &tx, std::string_view path, std::string_view columns = "") + { + return {tx, path, columns}; + } + + /// Create a `stream_to` writing to a named table and columns. + /** Use this to stream data to a table, where the list of columns is known at + * compile time. + * + * @param tx The transaction within which the stream will operate. + * @param path A @ref table_path designating the target table. + * @param columns Optionally, the columns to which the stream should write. + * If you do not pass this, the stream will write to all columns in the + * table, in schema order. + */ + static stream_to table( + transaction_base &tx, table_path path, + std::initializer_list columns = {}) + { + auto const &conn{tx.conn()}; + return raw_table(tx, conn.quote_table(path), conn.quote_columns(columns)); + } + +#if defined(PQXX_HAVE_CONCEPTS) + /// Create a `stream_to` writing to a named table and columns. + /** Use this version to stream data to a table, when the list of columns is + * not known at compile time. + * + * @param tx The transaction within which the stream will operate. + * @param path A @ref table_path designating the target table. + * @param columns The columns to which the stream should write. + */ + template + static stream_to + table(transaction_base &tx, table_path path, COLUMNS const &columns) + { + auto const &conn{tx.conn()}; + return stream_to::raw_table( + tx, conn.quote_table(path), tx.conn().quote_columns(columns)); + } + + /// Create a `stream_to` writing to a named table and columns. + /** Use this version to stream data to a table, when the list of columns is + * not known at compile time. + * + * @param tx The transaction within which the stream will operate. + * @param path A @ref table_path designating the target table. + * @param columns The columns to which the stream should write. + */ + template + static stream_to + table(transaction_base &tx, std::string_view path, COLUMNS const &columns) + { + return stream_to::raw_table(tx, path, tx.conn().quote_columns(columns)); + } +#endif // PQXX_HAVE_CONCEPTS + + /// Create a stream, without specifying columns. + /** @deprecated Use @ref table or @ref raw_table as a factory. + * + * Fields will be inserted in whatever order the columns have in the + * database. + * + * You'll probably want to specify the columns, so that the mapping between + * your data fields and the table is explicit in your code, and not hidden + * in an "implicit contract" between your code and your schema. + */ + [[deprecated("Use table() or raw_table() factory.")]] stream_to( + transaction_base &tx, std::string_view table_name) : + stream_to{tx, table_name, ""sv} + {} + + /// Create a stream, specifying column names as a container of strings. + /** @deprecated Use @ref table or @ref raw_table as a factory. + */ + template + [[deprecated("Use table() or raw_table() factory.")]] stream_to( + transaction_base &, std::string_view table_name, Columns const &columns); + + /// Create a stream, specifying column names as a sequence of strings. + /** @deprecated Use @ref table or @ref raw_table as a factory. + */ + template + [[deprecated("Use table() or raw_table() factory.")]] stream_to( + transaction_base &, std::string_view table_name, Iter columns_begin, + Iter columns_end); + + ~stream_to() noexcept; + + /// Does this stream still need to @ref complete()? + [[nodiscard]] constexpr operator bool() const noexcept + { + return not m_finished; + } + /// Has this stream been through its concluding @c complete()? + [[nodiscard]] constexpr bool operator!() const noexcept + { + return m_finished; + } + + /// Complete the operation, and check for errors. + /** Always call this to close the stream in an orderly fashion, even after + * an error. (In the case of an error, abort the transaction afterwards.) + * + * The only circumstance where it's safe to skip this is after an error, if + * you're discarding the entire connection. + */ + void complete(); + + /// Insert a row of data. + /** Returns a reference to the stream, so you can chain the calls. + * + * The @c row can be a tuple, or any type that can be iterated. Each + * item becomes a field in the row, in the same order as the columns you + * specified when creating the stream. + * + * If you don't already happen to have your fields in the form of a tuple or + * container, prefer @c write_values. It's faster and more convenient. + */ + template stream_to &operator<<(Row const &row) + { + write_row(row); + return *this; + } + + /// Stream a `stream_from` straight into a `stream_to`. + /** This can be useful when copying between different databases. If the + * source and the destination are on the same database, you'll get better + * performance doing it all in a regular query. + */ + stream_to &operator<<(stream_from &); + + /// Insert a row of data, given in the form of a @c std::tuple or container. + /** The @c row can be a tuple, or any type that can be iterated. Each + * item becomes a field in the row, in the same order as the columns you + * specified when creating the stream. + * + * The preferred way to insert a row is @c write_values. + */ + template void write_row(Row const &row) + { + fill_buffer(row); + write_buffer(); + } + + /// Insert values as a row. + /** This is the recommended way of inserting data. Pass your field values, + * of any convertible type. + */ + template void write_values(Ts const &...fields) + { + fill_buffer(fields...); + write_buffer(); + } + +private: + /// Stream a pre-quoted table name and columns list. + stream_to( + transaction_base &tx, std::string_view path, std::string_view columns); + + bool m_finished = false; + + /// Reusable buffer for a row. Saves doing an allocation for each row. + std::string m_buffer; + + /// Reusable buffer for converting/escaping a field. + std::string m_field_buf; + + /// Glyph scanner, for parsing the client encoding. + internal::glyph_scanner_func *m_scanner; + + /// Write a row of raw text-format data into the destination table. + void write_raw_line(std::string_view); + + /// Write a row of data from @c m_buffer into the destination table. + /** Resets the buffer for the next row. + */ + void write_buffer(); + + /// COPY encoding for a null field, plus subsequent separator. + static constexpr std::string_view null_field{"\\N\t"}; + + /// Estimate buffer space needed for a field which is always null. + template + static std::enable_if_t::always_null, std::size_t> + estimate_buffer(T const &) + { + return std::size(null_field); + } + + /// Estimate buffer space needed for field f. + /** The estimate is not very precise. We don't actually know how much space + * we'll need once the escaping comes in. + */ + template + static std::enable_if_t::always_null, std::size_t> + estimate_buffer(T const &field) + { + return is_null(field) ? std::size(null_field) : size_buffer(field); + } + + /// Append escaped version of @c data to @c m_buffer, plus a tab. + void escape_field_to_buffer(std::string_view data); + + /// Append string representation for @c f to @c m_buffer. + /** This is for the general case, where the field may contain a value. + * + * Also appends a tab. The tab is meant to be a separator, not a terminator, + * so if you write any fields at all, you'll end up with one tab too many + * at the end of the buffer. + */ + template + std::enable_if_t::always_null> + append_to_buffer(Field const &f) + { + // We append each field, terminated by a tab. That will leave us with + // one tab too many, assuming we write any fields at all; we remove that + // at the end. + if (is_null(f)) + { + // Easy. Append null and tab in one go. + m_buffer.append(null_field); + } + else + { + // Convert f into m_buffer. + + using traits = string_traits; + auto const budget{estimate_buffer(f)}; + auto const offset{std::size(m_buffer)}; + + if constexpr (std::is_arithmetic_v) + { + // Specially optimised for "safe" types, which never need any + // escaping. Convert straight into m_buffer. + + // The budget we get from size_buffer() includes room for the trailing + // zero, which we must remove. But we're also inserting tabs between + // fields, so we re-purpose the extra byte for that. + auto const total{offset + budget}; + m_buffer.resize(total); + auto const data{m_buffer.data()}; + char *const end{traits::into_buf(data + offset, data + total, f)}; + *(end - 1) = '\t'; + // Shrink to fit. Keep the tab though. + m_buffer.resize(static_cast(end - data)); + } + else if constexpr ( + std::is_same_v or + std::is_same_v or + std::is_same_v) + { + // This string may need escaping. + m_field_buf.resize(budget); + escape_field_to_buffer(f); + } + else + { + // This field needs to be converted to a string, and after that, + // escaped as well. + m_field_buf.resize(budget); + auto const data{m_field_buf.data()}; + escape_field_to_buffer( + traits::to_buf(data, data + std::size(m_field_buf), f)); + } + } + } + + /// Append string representation for a null field to @c m_buffer. + /** This special case is for types which are always null. + * + * Also appends a tab. The tab is meant to be a separator, not a terminator, + * so if you write any fields at all, you'll end up with one tab too many + * at the end of the buffer. + */ + template + std::enable_if_t::always_null> + append_to_buffer(Field const &) + { + m_buffer.append(null_field); + } + + /// Write raw COPY line into @c m_buffer, based on a container of fields. + template + std::enable_if_t> + fill_buffer(Container const &c) + { + // To avoid unnecessary allocations and deallocations, we run through c + // twice: once to determine how much buffer space we may need, and once to + // actually write it into the buffer. + std::size_t budget{0}; + for (auto const &f : c) budget += estimate_buffer(f); + m_buffer.reserve(budget); + for (auto const &f : c) append_to_buffer(f); + } + + /// Estimate how many buffer bytes we need to write tuple. + template + static std::size_t + budget_tuple(Tuple const &t, std::index_sequence) + { + return (estimate_buffer(std::get(t)) + ...); + } + + /// Write tuple of fields to @c m_buffer. + template + void append_tuple(Tuple const &t, std::index_sequence) + { + (append_to_buffer(std::get(t)), ...); + } + + /// Write raw COPY line into @c m_buffer, based on a tuple of fields. + template void fill_buffer(std::tuple const &t) + { + using indexes = std::make_index_sequence; + + m_buffer.reserve(budget_tuple(t, indexes{})); + append_tuple(t, indexes{}); + } + + /// Write raw COPY line into @c m_buffer, based on varargs fields. + template void fill_buffer(const Ts &...fields) + { + (..., append_to_buffer(fields)); + } + + constexpr static std::string_view s_classname{"stream_to"}; +}; + + +template +inline stream_to::stream_to( + transaction_base &tx, std::string_view table_name, Columns const &columns) : + stream_to{tx, table_name, std::begin(columns), std::end(columns)} +{} + + +template +inline stream_to::stream_to( + transaction_base &tx, std::string_view table_name, Iter columns_begin, + Iter columns_end) : + stream_to{ + tx, + tx.quote_name( + table_name, + separated_list(",", columns_begin, columns_end, [&tx](auto col) { + return tx.quote_name(*col); + }))} +{} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction new file mode 100644 index 000000000..e0d154903 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction @@ -0,0 +1,8 @@ +/** pqxx::subtransaction class. + * + * pqxx::subtransaction is a nested transaction, i.e. one inside a transaction. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/subtransaction.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction.hxx new file mode 100644 index 000000000..e66b7a7a8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction.hxx @@ -0,0 +1,96 @@ +/* Definition of the pqxx::subtransaction class. + * + * pqxx::subtransaction is a nested transaction, i.e. one within a transaction. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/subtransaction instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_SUBTRANSACTION +#define PQXX_H_SUBTRANSACTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/dbtransaction.hxx" + +namespace pqxx +{ +/** + * @ingroup transactions + */ +/// "Transaction" nested within another transaction +/** A subtransaction can be executed inside a backend transaction, or inside + * another subtransaction. This can be useful when, for example, statements in + * a transaction may harmlessly fail and you don't want them to abort the + * entire transaction. Here's an example of how a temporary table may be + * dropped before re-creating it, without failing if the table did not exist: + * + * ```cxx + * void do_job(connection &C) + * { + * string const temptable = "fleetingtable"; + * + * work W(C, "do_job"); + * do_firstpart(W); + * + * // Attempt to delete our temporary table if it already existed. + * try + * { + * subtransaction S(W, "droptemp"); + * S.exec0("DROP TABLE " + temptable); + * S.commit(); + * } + * catch (undefined_table const &) + * { + * // Table did not exist. Which is what we were hoping to achieve anyway. + * // Carry on without regrets. + * } + * + * // S may have gone into a failed state and been destroyed, but the + * // upper-level transaction W is still fine. We can continue to use it. + * W.exec0("CREATE TEMP TABLE " + temptable + "(bar integer, splat + * varchar)"); + * + * do_lastpart(W); + * } + * ``` + * + * (This is just an example. If you really wanted to do drop a table without + * an error if it doesn't exist, you'd use DROP TABLE IF EXISTS.) + * + * There are no isolation levels inside a transaction. They are not needed + * because all actions within the same backend transaction are always performed + * sequentially anyway. + * + * @warning While the subtransaction is "live," you cannot execute queries or + * open streams etc. on its parent transaction. A transaction can have at most + * one object of a type derived from @ref pqxx::transaction_focus active on it + * at a time. + */ +class PQXX_LIBEXPORT subtransaction : public transaction_focus, + public dbtransaction +{ +public: + /// Nest a subtransaction nested in another transaction. + explicit subtransaction(dbtransaction &t, std::string_view tname = ""sv); + + /// Nest a subtransaction in another subtransaction. + explicit subtransaction(subtransaction &t, std::string_view name = ""sv); + + virtual ~subtransaction() noexcept override; + +private: + std::string quoted_name() const + { + return quote_name(transaction_focus::name()); + } + virtual void do_commit() override; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time new file mode 100644 index 000000000..85df05744 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time @@ -0,0 +1,6 @@ +/** Date/time string conversions. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/time.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time.hxx new file mode 100644 index 000000000..effed05e0 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time.hxx @@ -0,0 +1,88 @@ +/** Support for date/time values. + * + * At the moment this supports dates, but not times. + */ +#ifndef PQXX_H_TIME +#define PQXX_H_TIME + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + +#include "pqxx/internal/concat.hxx" +#include "pqxx/strconv.hxx" + + +#if defined(PQXX_HAVE_YEAR_MONTH_DAY) + +namespace pqxx +{ +using namespace std::literals; + +template<> +struct nullness + : no_null +{}; + + +/// String representation for a Gregorian date in ISO-8601 format. +/** @warning Experimental. There may still be design problems, particularly + * when it comes to BC years. + * + * PostgreSQL supports a choice of date formats, but libpqxx does not. The + * other formats in turn support a choice of "month before day" versus "day + * before month," meaning that it's not necessarily known which format a given + * date is supposed to be. So I repeat: ISO-8601-style format only! + * + * Invalid dates will not convert. This includes February 29 on non-leap + * years, which is why it matters that `year_month_day` represents a + * _Gregorian_ date. + * + * The range of years is limited. At the time of writing, PostgreSQL 14 + * supports years from 4713 BC to 294276 AD inclusive, and C++20 supports + * a range of 32767 BC to 32767 AD inclusive. So in practice, years must fall + * between 4713 BC and 32767 AD, inclusive. + * + * @warning Support for BC (or BCE) years is still experimental. I still need + * confirmation on this issue: it looks as if C++ years are astronomical years, + * which means they have a Year Zero. Regular BC/AD years do not have a year + * zero, so the year 1 AD follows directly after 1 BC. + * + * So, what to our calendars (and to PostgreSQL) is the year "0001 BC" seems to + * count as year "0" in a `std::chrono::year_month_day`. The year 0001 AD is + * still equal to 1 as you'd expect, and all AD years work normally, but all + * years before then are shifted by one. For instance, the year 543 BC would + * be -542 in C++. + */ +template<> struct PQXX_LIBEXPORT string_traits +{ + [[nodiscard]] static zview + to_buf(char *begin, char *end, std::chrono::year_month_day const &value) + { + return generic_to_buf(begin, end, value); + } + + static char * + into_buf(char *begin, char *end, std::chrono::year_month_day const &value); + + [[nodiscard]] static std::chrono::year_month_day + from_string(std::string_view text); + + [[nodiscard]] static std::size_t + size_buffer(std::chrono::year_month_day const &) noexcept + { + static_assert(int{(std::chrono::year::min)()} >= -99999); + static_assert(int{(std::chrono::year::max)()} <= 99999); + return 5 + 1 + 2 + 1 + 2 + std::size(s_bc) + 1; + } + +private: + /// The "BC" suffix for years before 1 AD. + static constexpr std::string_view s_bc{" BC"sv}; +}; +} // namespace pqxx +#endif // PQXX_HAVE_YEAR_MONTH_DAY +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction new file mode 100644 index 000000000..a7ae39d43 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction @@ -0,0 +1,8 @@ +/** pqxx::transaction class. + * + * pqxx::transaction represents a standard database transaction. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/transaction.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction.hxx new file mode 100644 index 000000000..e90917e38 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction.hxx @@ -0,0 +1,108 @@ +/* Definition of the pqxx::transaction class. + * pqxx::transaction represents a standard database transaction. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/transaction instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_TRANSACTION +#define PQXX_H_TRANSACTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/dbtransaction.hxx" + +namespace pqxx::internal +{ +/// Helper base class for the @ref transaction class template. +class PQXX_LIBEXPORT basic_transaction : public dbtransaction +{ +protected: + basic_transaction( + connection &c, zview begin_command, std::string_view tname); + basic_transaction(connection &c, zview begin_command, std::string &&tname); + basic_transaction(connection &c, zview begin_command); + + virtual ~basic_transaction() noexcept override = 0; + +private: + virtual void do_commit() override; +}; +} // namespace pqxx::internal + + +namespace pqxx +{ +/** + * @ingroup transactions + */ +//@{ + +/// Standard back-end transaction, templatised on isolation level. +/** This is the type you'll normally want to use to represent a transaction on + * the database. + * + * Usage example: double all wages. + * + * ```cxx + * extern connection C; + * work T(C); + * try + * { + * T.exec0("UPDATE employees SET wage=wage*2"); + * T.commit(); // NOTE: do this inside try block + * } + * catch (exception const &e) + * { + * cerr << e.what() << endl; + * T.abort(); // Usually not needed; same happens when T's life ends. + * } + * ``` + */ +template< + isolation_level ISOLATION = isolation_level::read_committed, + write_policy READWRITE = write_policy::read_write> +class transaction final : public internal::basic_transaction +{ +public: + /// Begin a transaction. + /** + * @param c Connection for this transaction to operate on. + * @param tname Optional name for transaction. Must begin with a letter and + * may contain letters and digits only. + */ + transaction(connection &c, std::string_view tname) : + internal::basic_transaction{ + c, internal::begin_cmd, tname} + {} + + /// Begin a transaction. + /** + * @param c Connection for this transaction to operate on. + * may contain letters and digits only. + */ + explicit transaction(connection &c) : + internal::basic_transaction{ + c, internal::begin_cmd} + {} + + virtual ~transaction() noexcept override { close(); } +}; + + +/// The default transaction type. +using work = transaction<>; + +/// Read-only transaction. +using read_transaction = + transaction; + +//@} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base new file mode 100644 index 000000000..c39219aac --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base @@ -0,0 +1,9 @@ +/** Base for the transaction classes. + * + * pqxx::transaction_base defines the interface for any abstract class that + * represents a database transaction. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/transaction_base.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base.hxx new file mode 100644 index 000000000..4363cc56a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base.hxx @@ -0,0 +1,810 @@ +/* Common code and definitions for the transaction classes. + * + * pqxx::transaction_base defines the interface for any abstract class that + * represents a database transaction. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/transaction_base instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_TRANSACTION_BASE +#define PQXX_H_TRANSACTION_BASE + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + +/* End-user programs need not include this file, unless they define their own + * transaction classes. This is not something the typical program should want + * to do. + * + * However, reading this file is worthwhile because it defines the public + * interface for the available transaction classes such as transaction and + * nontransaction. + */ + +#include "pqxx/connection.hxx" +#include "pqxx/internal/concat.hxx" +#include "pqxx/internal/encoding_group.hxx" +#include "pqxx/isolation.hxx" +#include "pqxx/result.hxx" +#include "pqxx/row.hxx" +#include "pqxx/stream_from.hxx" +#include "pqxx/util.hxx" + +namespace pqxx::internal::gate +{ +class transaction_subtransaction; +class transaction_sql_cursor; +class transaction_stream_to; +class transaction_transaction_focus; +} // namespace pqxx::internal::gate + + +namespace pqxx +{ +using namespace std::literals; + + +class transaction_focus; + + +/** + * @defgroup transactions Transaction classes + * + * All database access goes through instances of these classes. + * However, not all implementations of this interface need to provide full + * transactional integrity. + * + * Several implementations of this interface are shipped with libpqxx, + * including the plain transaction class, the entirely unprotected + * nontransaction, and the more cautious robusttransaction. + */ + +/// Interface definition (and common code) for "transaction" classes. +/** + * @ingroup transactions + * + * Abstract base class for all transaction types. + */ +class PQXX_LIBEXPORT PQXX_NOVTABLE transaction_base +{ +public: + transaction_base() = delete; + transaction_base(transaction_base const &) = delete; + transaction_base(transaction_base &&) = delete; + transaction_base &operator=(transaction_base const &) = delete; + transaction_base &operator=(transaction_base &&) = delete; + + virtual ~transaction_base() = 0; + + /// Commit the transaction. + /** Make the effects of this transaction definite. If you destroy a + * transaction without invoking its @ref commit() first, that will implicitly + * abort it. (For the @ref nontransaction class though, "commit" and "abort" + * really don't do anything, hence its name.) + * + * There is, however, a minute risk that you might lose your connection to + * the database at just the wrong moment here. In that case, libpqxx may be + * unable to determine whether the database was able to complete the + * transaction, or had to roll it back. In that scenario, @ref commit() will + * throw an in_doubt_error. There is a different transaction class called + * @ref robusttransaction which takes some special precautions to reduce this + * risk. + */ + void commit(); + + /// Abort the transaction. + /** No special effort is required to call this function; it will be called + * implicitly when the transaction is destructed. + */ + void abort(); + + /** + * @ingroup escaping-functions + * + * Use these when writing SQL queries that incorporate C++ values as SQL + * constants. + * + * The functions you see here are just convenience shortcuts to the same + * functions on the connection object. + */ + //@{ + /// Escape string for use as SQL string literal in this transaction. + template [[nodiscard]] auto esc(ARGS &&...args) const + { + return conn().esc(std::forward(args)...); + } + + /// Escape binary data for use as SQL string literal in this transaction. + /** Raw, binary data is treated differently from regular strings. Binary + * strings are never interpreted as text, so they may safely include byte + * values or byte sequences that don't happen to represent valid characters + * in the character encoding being used. + * + * The binary string does not stop at the first zero byte, as is the case + * with textual strings. Instead, it may contain zero bytes anywhere. If + * it happens to contain bytes that look like quote characters, or other + * things that can disrupt their use in SQL queries, they will be replaced + * with special escape sequences. + */ + template [[nodiscard]] auto esc_raw(ARGS &&...args) const + { + return conn().esc_raw(std::forward(args)...); + } + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string + unesc_raw(zview text) const + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return conn().unesc_raw(text); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard]] std::basic_string unesc_bin(zview text) + { + return conn().unesc_bin(text); + } + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string + unesc_raw(char const *text) const + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return conn().unesc_raw(text); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard]] std::basic_string unesc_bin(char const text[]) + { + return conn().unesc_bin(text); + } + + /// Represent object as SQL string, including quoting & escaping. + /** Nulls are recognized and represented as SQL nulls. */ + template [[nodiscard]] std::string quote(T const &t) const + { + return conn().quote(t); + } + + [[deprecated( + "Use std::basic_string instead of binarystring.")]] std::string + quote(binarystring const &t) const + { + return conn().quote(t.bytes_view()); + } + + /// Binary-escape and quote a binary string for use as an SQL constant. + [[deprecated("Use quote(std::basic_string_view).")]] std::string + quote_raw(unsigned char const bin[], std::size_t len) const + { + return quote(binary_cast(bin, len)); + } + + /// Binary-escape and quote a binary string for use as an SQL constant. + [[deprecated("Use quote(std::basic_string_view).")]] std::string + quote_raw(zview bin) const; + +#if defined(PQXX_HAVE_CONCEPTS) + /// Binary-escape and quote a binary string for use as an SQL constant. + /** For binary data you can also just use @ref quote(data). */ + template + [[nodiscard]] std::string quote_raw(DATA const &data) const + { + return conn().quote_raw(data); + } +#endif + + /// Escape an SQL identifier for use in a query. + [[nodiscard]] std::string quote_name(std::string_view identifier) const + { + return conn().quote_name(identifier); + } + + /// Escape string for literal LIKE match. + [[nodiscard]] std::string + esc_like(std::string_view bin, char escape_char = '\\') const + { + return conn().esc_like(bin, escape_char); + } + //@} + + /** + * @name Command execution + * + * There are many functions for executing (or "performing") a command (or + * "query"). This is the most fundamental thing you can do with the library, + * and you always do it from a transaction class. + * + * Command execution can throw many types of exception, including sql_error, + * broken_connection, and many sql_error subtypes such as + * feature_not_supported or insufficient_privilege. But any exception thrown + * by the C++ standard library may also occur here. All exceptions you will + * see libpqxx throw are derived from std::exception. + * + * One unusual feature in libpqxx is that you can give your query a name or + * description. This does not mean anything to the database, but sometimes + * it can help libpqxx produce more helpful error messages, making problems + * in your code easier to debug. + * + * Many of the execution functions used to accept a `desc` argument, a + * human-readable description of the statement for use in error messages. + * This could make failures easier to debug. Future versions will use + * C++20's `std::source_location` to identify the failing statement. + */ + //@{ + + /// Execute a command. + /** + * @param query Query or command to execute. + * @param desc Optional identifier for query, to help pinpoint SQL errors. + * @return A result set describing the query's or command's result. + */ + [[deprecated("The desc parameter is going away.")]] result + exec(std::string_view query, std::string_view desc); + + /// Execute a command. + /** + * @param query Query or command to execute. + * @return A result set describing the query's or command's result. + */ + result exec(std::string_view query) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return exec(query, std::string_view{}); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Execute a command. + /** + * @param query Query or command to execute. + * @param desc Optional identifier for query, to help pinpoint SQL errors. + * @return A result set describing the query's or command's result. + */ + [[deprecated( + "Pass your query as a std::string_view, not stringstream.")]] result + exec(std::stringstream const &query, std::string_view desc) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return exec(query.str(), desc); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Execute command, which should return zero rows of data. + /** Works like @ref exec, but fails if the result contains data. It still + * returns a result, however, which may contain useful metadata. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + [[deprecated("The desc parameter is going away.")]] result + exec0(zview query, std::string_view desc) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return exec_n(0, query, desc); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Execute command, which should return zero rows of data. + /** Works like @ref exec, but fails if the result contains data. It still + * returns a result, however, which may contain useful metadata. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + result exec0(zview query) { return exec_n(0, query); } + + /// Execute command returning a single row of data. + /** Works like @ref exec, but requires the result to contain exactly one row. + * The row can be addressed directly, without the need to find the first row + * in a result set. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + [[deprecated("The desc parameter is going away.")]] row + exec1(zview query, std::string_view desc) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return exec_n(1, query, desc).front(); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Execute command returning a single row of data. + /** Works like @ref exec, but requires the result to contain exactly one row. + * The row can be addressed directly, without the need to find the first row + * in a result set. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + row exec1(zview query) { return exec_n(1, query).front(); } + + /// Execute command, expect given number of rows. + /** Works like @ref exec, but checks that the result has exactly the expected + * number of rows. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + [[deprecated("The desc parameter is going away.")]] result + exec_n(result::size_type rows, zview query, std::string_view desc); + + /// Execute command, expect given number of rows. + /** Works like @ref exec, but checks that the result has exactly the expected + * number of rows. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + result exec_n(result::size_type rows, zview query) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return exec_n(rows, query, std::string_view{}); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Perform query, expecting exactly 1 row with 1 field, and convert it. + /** This is convenience shorthand for querying exactly one value from the + * database. It returns that value, converted to the type you specify. + */ + template + [[deprecated("The desc parameter is going away.")]] TYPE + query_value(zview query, std::string_view desc) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + row const r{exec1(query, desc)}; +#include "pqxx/internal/ignore-deprecated-post.hxx" + if (std::size(r) != 1) + throw usage_error{internal::concat( + "Queried single value from result with ", std::size(r), " columns.")}; + return r[0].as(); + } + + /// Perform query, expecting exactly 1 row with 1 field, and convert it. + /** This is convenience shorthand for querying exactly one value from the + * database. It returns that value, converted to the type you specify. + */ + template TYPE query_value(zview query) + { + row const r{exec1(query)}; + if (std::size(r) != 1) + throw usage_error{internal::concat( + "Queried single value from result with ", std::size(r), " columns.")}; + return r[0].as(); + } + + /// Execute a query, and loop over the results row by row. + /** Converts the rows to `std::tuple`, of the column types you specify. + * + * Use this with a range-based "for" loop. It executes the query, and + * directly maps the resulting rows onto a `std::tuple` of the types you + * specify. It starts before all the data from the server is in, so if your + * network connection to the server breaks while you're iterating, you'll get + * an exception partway through. + * + * The stream lives entirely within the lifetime of the transaction. Make + * sure you destroy the stream before you destroy the transaction. Either + * iterate the stream all the way to the end, or destroy first the stream + * and then the transaction without touching either in any other way. Until + * the stream has finished, the transaction is in a special state where it + * cannot execute queries. + * + * As a special case, tuple may contain `std::string_view` fields, but the + * strings to which they point will only remain valid until you extract the + * next row. After that, the memory holding the string may be overwritten or + * deallocated. + * + * If any of the columns can be null, and the C++ type to which it translates + * does not have a null value, wrap the type in `std::optional` (or if + * you prefer, `std::shared_ptr` or `std::unique_ptr)`. These templates + * do recognise null values, and libpqxx will know how to convert to them. + * + * The connection is in a special state until the iteration finishes. So if + * it does not finish due to a `break` or a `return` or an exception, then + * the entire connection becomes effectively unusable. + * + * Querying in this way is faster than the `exec()` methods for larger + * results (but slower for small ones). You can start processing rows before + * the full result is in. Also, `stream()` scales better in terms of memory + * usage. Where @ref exec() reads the entire result into memory at once, + * `stream()` will read and process one row at at a time. + * + * Your query executes as part of a COPY command, not as a stand-alone query, + * so there are limitations to what you can do in the query. It can be + * either a SELECT or VALUES query; or an INSERT, UPDATE, or DELETE with a + * RETURNING clause. See the documentation for PostgreSQL's COPY command for + * the details: + * + * https://www.postgresql.org/docs/current/sql-copy.html + * + * Iterating in this way does require each of the field types you pass to be + * default-constructible, copy-constructible, and assignable. These + * requirements may be loosened once libpqxx moves on to C++20. + */ + template + [[nodiscard]] auto stream(std::string_view query) & + { + // Tricky: std::make_unique() supports constructors but not RVO functions. + return pqxx::internal::owning_stream_input_iteration{ + std::unique_ptr{ + new stream_from{stream_from::query(*this, query)}}}; + } + + // C++20: Concept like std::invocable, but without specifying param types. + /// Perform a streaming query, and for each result row, call `func`. + /** Here, `func` can be a function, a `std::function`, a lambda, or an + * object that supports the function call operator. Of course `func` must + * have an unambiguous signature; it can't be overloaded or generic. + * + * The `for_each` function executes `query` in a stream using + * @ref pqxx::stream_from. Every time a row of data comes in from the + * server, it converts the row's fields to the types of `func`'s respective + * parameters, and calls `func` with those values. + * + * This will not work for all queries, but straightforward `SELECT` and + * `UPDATE ... RETURNING` queries should work. Consult the documentation for + * @ref pqxx::stream_from and PostgreSQL's underlying `COPY` command for the + * full details. + * + * Streaming a query like this is likely to be slower than the @ref exec() + * functions for small result sets, but faster for large result sets. So if + * performance matters, you'll want to use `for_each` if you query large + * amounts of data, but not if you do lots of queries with small outputs. + */ + template + inline auto for_each(std::string_view query, CALLABLE &&func) + { + using param_types = + pqxx::internal::strip_types_t>; + param_types const *const sample{nullptr}; + auto data_stream{stream_like(query, sample)}; + for (auto const &fields : data_stream) std::apply(func, fields); + } + + /** + * @name Parameterized statements + * + * You'll often need parameters in the queries you execute: "select the + * car with this licence plate." If the parameter is a string, you need to + * quote it and escape any special characters inside it, or it may become a + * target for an SQL injection attack. If it's an integer (for example), + * you need to convert it to a string, but in the database's format, without + * locale-specific niceties like "," separators between the thousands. + * + * Parameterised statements are an easier and safer way to do this. They're + * like prepared statements, but for a single use. You don't need to name + * them, and you don't need to prepare them first. + * + * Your query will include placeholders like `$1` and `$2` etc. in the places + * where you want the arguments to go. Then, you pass the argument values + * and the actual query is constructed for you. + * + * Pass the exact right number of parameters, and in the right order. The + * parameters in the query don't have to be neatly ordered from `$1` to + * `$2` to `$3` - but you must pass the argument for `$1` first, the one + * for `$2` second, etc. + * + * @warning Beware of "nul" bytes. Any string you pass as a parameter will + * end at the first char with value zero. If you pass a string that contains + * a zero byte, the last byte in the value will be the one just before the + * zero. + */ + //@{ + /// Execute an SQL statement with parameters. + template result exec_params(zview query, Args &&...args) + { + params pp(args...); + return internal_exec_params(query, pp.make_c_params()); + } + + // Execute parameterised statement, expect a single-row result. + /** @throw unexpected_rows if the result does not consist of exactly one row. + */ + template row exec_params1(zview query, Args &&...args) + { + return exec_params_n(1, query, std::forward(args)...).front(); + } + + // Execute parameterised statement, expect a result with zero rows. + /** @throw unexpected_rows if the result contains rows. + */ + template result exec_params0(zview query, Args &&...args) + { + return exec_params_n(0, query, std::forward(args)...); + } + + // Execute parameterised statement, expect exactly a given number of rows. + /** @throw unexpected_rows if the result contains the wrong number of rows. + */ + template + result exec_params_n(std::size_t rows, zview query, Args &&...args) + { + auto const r{exec_params(query, std::forward(args)...)}; + check_rowcount_params(rows, std::size(r)); + return r; + } + //@} + + /** + * @name Prepared statements + * + * These are very similar to parameterised statements. The difference is + * that you prepare them in advance, giving them identifying names. You can + * then call them by these names, passing in the argument values appropriate + * for that call. + * + * You prepare a statement on the connection, using + * @ref pqxx::connection::prepare(). But you then call the statement in a + * transaction, using the functions you see here. + * + * Never try to prepare, execute, or unprepare a prepared statement manually + * using direct SQL queries when you also use the libpqxx equivalents. For + * any given statement, either prepare, manage, and execute it through the + * dedicated libpqxx functions; or do it all directly in SQL. Don't mix the + * two, or the code may get confused. + * + * See \ref prepared for a full discussion. + * + * @warning Beware of "nul" bytes. Any string you pass as a parameter will + * end at the first char with value zero. If you pass a string that contains + * a zero byte, the last byte in the value will be the one just before the + * zero. If you need a zero byte, you're dealing with binary strings, not + * regular strings. Represent binary strings on the SQL side as `BYTEA` + * (or as large objects). On the C++ side, use types like + * `std::basic_string` or `std::basic_string_view` + * or (in C++20) `std::vector`. Also, consider large objects on + * the SQL side and @ref blob on the C++ side. + */ + //@{ + + /// Execute a prepared statement, with optional arguments. + template + result exec_prepared(zview statement, Args &&...args) + { + params pp(args...); + return internal_exec_prepared(statement, pp.make_c_params()); + } + + /// Execute a prepared statement, and expect a single-row result. + /** @throw pqxx::unexpected_rows if the result was not exactly 1 row. + */ + template + row exec_prepared1(zview statement, Args &&...args) + { + return exec_prepared_n(1, statement, std::forward(args)...).front(); + } + + /// Execute a prepared statement, and expect a result with zero rows. + /** @throw pqxx::unexpected_rows if the result contained rows. + */ + template + result exec_prepared0(zview statement, Args &&...args) + { + return exec_prepared_n(0, statement, std::forward(args)...); + } + + /// Execute a prepared statement, expect a result with given number of rows. + /** @throw pqxx::unexpected_rows if the result did not contain exactly the + * given number of rows. + */ + template + result + exec_prepared_n(result::size_type rows, zview statement, Args &&...args) + { + auto const r{exec_prepared(statement, std::forward(args)...)}; + check_rowcount_prepared(statement, rows, std::size(r)); + return r; + } + + //@} + + /** + * @name Error/warning output + */ + //@{ + /// Have connection process a warning message. + void process_notice(char const msg[]) const { m_conn.process_notice(msg); } + /// Have connection process a warning message. + void process_notice(zview msg) const { m_conn.process_notice(msg); } + //@} + + /// The connection in which this transaction lives. + [[nodiscard]] constexpr connection &conn() const noexcept { return m_conn; } + + /// Set session variable using SQL "SET" command. + /** @deprecated To set a transaction-local variable, execute an SQL `SET` + * command. To set a session variable, use the connection's + * @ref set_session_var function. + * + * @warning When setting a string value, you must make sure that the string + * is "safe." If you call @ref quote() on the string, it will return a + * safely escaped and quoted version for use as an SQL literal. + * + * @warning This function executes SQL. Do not try to set or get variables + * while a pipeline or table stream is active. + * + * @param var The variable to set. + * @param value The new value to store in the variable. This can be any SQL + * expression. + */ + [[deprecated( + "Set transaction-local variables using SQL SET statements.")]] void + set_variable(std::string_view var, std::string_view value); + + /// Read session variable using SQL "SHOW" command. + /** @warning This executes SQL. Do not try to set or get variables while a + * pipeline or table stream is active. + */ + [[deprecated("Read variables using SQL SHOW statements.")]] std::string + get_variable(std::string_view); + + // C++20: constexpr. + /// Transaction name, if you passed one to the constructor; or empty string. + [[nodiscard]] std::string_view name() const &noexcept { return m_name; } + +protected: + /// Create a transaction (to be called by implementation classes only). + /** The name, if nonempty, must begin with a letter and may contain letters + * and digits only. + */ + transaction_base( + connection &c, std::string_view tname, + std::shared_ptr rollback_cmd) : + m_conn{c}, m_name{tname}, m_rollback_cmd{rollback_cmd} + {} + + /// Create a transaction (to be called by implementation classes only). + /** Its rollback command will be "ROLLBACK". + * + * The name, if nonempty, must begin with a letter and may contain letters + * and digits only. + */ + transaction_base(connection &c, std::string_view tname); + + /// Create a transaction (to be called by implementation classes only). + explicit transaction_base(connection &c); + + /// Register this transaction with the connection. + void register_transaction(); + + /// End transaction. To be called by implementing class' destructor. + void close() noexcept; + + /// To be implemented by derived implementation class: commit transaction. + virtual void do_commit() = 0; + + /// Transaction type-specific way of aborting a transaction. + /** @warning This will become "final", since this function can be called + * from the implementing class destructor. + */ + virtual void do_abort(); + + /// Set the rollback command. + void set_rollback_cmd(std::shared_ptr cmd) + { + m_rollback_cmd = cmd; + } + + /// Execute query on connection directly. + result direct_exec(std::string_view, std::string_view desc = ""sv); + result + direct_exec(std::shared_ptr, std::string_view desc = ""sv); + +private: + enum class status + { + active, + aborted, + committed, + in_doubt + }; + + PQXX_PRIVATE void check_pending_error(); + + result + internal_exec_prepared(zview statement, internal::c_params const &args); + + result internal_exec_params(zview query, internal::c_params const &args); + + /// Throw unexpected_rows if prepared statement returned wrong no. of rows. + void check_rowcount_prepared( + zview statement, result::size_type expected_rows, + result::size_type actual_rows); + + /// Throw unexpected_rows if wrong row count from parameterised statement. + void + check_rowcount_params(std::size_t expected_rows, std::size_t actual_rows); + + /// Describe this transaction to humans, e.g. "transaction 'foo'". + [[nodiscard]] std::string description() const; + + friend class pqxx::internal::gate::transaction_transaction_focus; + PQXX_PRIVATE void register_focus(transaction_focus *); + PQXX_PRIVATE void unregister_focus(transaction_focus *) noexcept; + PQXX_PRIVATE void register_pending_error(zview) noexcept; + PQXX_PRIVATE void register_pending_error(std::string &&) noexcept; + + /// Like @ref stream(), but takes a tuple rather than a parameter pack. + template + auto stream_like(std::string_view query, std::tuple const *) + { + return stream(query); + } + + connection &m_conn; + + /// Current "focus": a pipeline, a nested transaction, a stream... + /** This pointer is used for only one purpose: sanity checks against mistakes + * such as opening one while another is still active. + */ + transaction_focus const *m_focus = nullptr; + + status m_status = status::active; + bool m_registered = false; + std::string m_name; + std::string m_pending_error; + + /// SQL command for aborting this type of transaction. + std::shared_ptr m_rollback_cmd; + + static constexpr std::string_view s_type_name{"transaction"sv}; +}; + + +// C++20: Can borrowed_range help? +/// Forbidden specialisation: underlying buffer immediately goes out of scope. +template<> +std::string_view transaction_base::query_value( + zview query, std::string_view desc) = delete; +/// Forbidden specialisation: underlying buffer immediately goes out of scope. +template<> +zview transaction_base::query_value( + zview query, std::string_view desc) = delete; + +} // namespace pqxx + + +namespace pqxx::internal +{ +/// The SQL command for starting a given type of transaction. +template +extern const zview begin_cmd; + +// These are not static members, so "constexpr" does not imply "inline". +template<> +inline constexpr zview begin_cmd{ + "BEGIN"_zv}; +template<> +inline constexpr zview begin_cmd{ + "BEGIN READ ONLY"_zv}; +template<> +inline constexpr zview begin_cmd{ + "BEGIN ISOLATION LEVEL REPEATABLE READ"_zv}; +template<> +inline constexpr zview begin_cmd{ + "BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY"_zv}; +template<> +inline constexpr zview begin_cmd{ + "BEGIN ISOLATION LEVEL SERIALIZABLE"_zv}; +template<> +inline constexpr zview begin_cmd{ + "BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY"_zv}; +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus new file mode 100644 index 000000000..fe78a9bcc --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus @@ -0,0 +1,7 @@ +/** + * Transaction focus: types which monopolise a transaction's attention. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/types.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus.hxx new file mode 100644 index 000000000..0707e3cc4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus.hxx @@ -0,0 +1,89 @@ +/** Transaction focus: types which monopolise a transaction's attention. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_TRANSACTION_FOCUS +#define PQXX_H_TRANSACTION_FOCUS + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/util.hxx" + +namespace pqxx +{ +/// Base class for things that monopolise a transaction's attention. +/** You probably won't need to use this class. But it can be useful to _know_ + * that a given libpqxx class is derived from it. + * + * Pipelines, SQL statements, and data streams are examples of classes derived + * from `transaction_focus`. For any given transaction, only one object of + * such a class can be active at any given time. + */ +class PQXX_LIBEXPORT transaction_focus +{ +public: + transaction_focus( + transaction_base &t, std::string_view cname, std::string_view oname) : + m_trans{t}, m_classname{cname}, m_name{oname} + {} + + transaction_focus( + transaction_base &t, std::string_view cname, std::string &&oname) : + m_trans{t}, m_classname{cname}, m_name{std::move(oname)} + {} + + transaction_focus(transaction_base &t, std::string_view cname) : + m_trans{t}, m_classname{cname} + {} + + transaction_focus() = delete; + transaction_focus(transaction_focus const &) = delete; + transaction_focus &operator=(transaction_focus const &) = delete; + + /// Class name, for human consumption. + [[nodiscard]] constexpr std::string_view classname() const noexcept + { + return m_classname; + } + + /// Name for this object, if the caller passed one; empty string otherwise. + [[nodiscard]] std::string_view name() const &noexcept { return m_name; } + + [[nodiscard]] std::string description() const + { + return pqxx::internal::describe_object(m_classname, m_name); + } + + /// Can't move a transaction_focus. + /** Moving the transaction_focus would break the transaction's reference back + * to the object. + */ + transaction_focus(transaction_focus &&) = delete; + + /// Can't move a transaction_focus. + /** Moving the transaction_focus would break the transaction's reference back + * to the object. + */ + transaction_focus &operator=(transaction_focus &&) = delete; + +protected: + void register_me(); + void unregister_me() noexcept; + void reg_pending_error(std::string const &) noexcept; + bool registered() const noexcept { return m_registered; } + + transaction_base &m_trans; + +private: + bool m_registered = false; + std::string_view m_classname; + std::string m_name; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor new file mode 100644 index 000000000..29d1b9640 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor @@ -0,0 +1,8 @@ +/** pqxx::transactor class. + * + * pqxx::transactor is a framework-style wrapper for safe transactions. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/transactor.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor.hxx new file mode 100644 index 000000000..eefd04ba1 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor.hxx @@ -0,0 +1,147 @@ +/* Transactor framework, a wrapper for safely retryable transactions. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/transactor instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_TRANSACTOR +#define PQXX_H_TRANSACTOR + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + +#include "pqxx/connection.hxx" +#include "pqxx/transaction.hxx" + +namespace pqxx +{ +/** + * @defgroup transactor Transactor framework + * + * Sometimes a transaction can fail for completely transient reasons, such as a + * conflict with another transaction in SERIALIZABLE isolation. The right way + * to handle those failures is often just to re-run the transaction from + * scratch. + * + * For example, your REST API might be handling each HTTP request in its own + * database transaction, and if this kind of transient failure happens, you + * simply want to "replay" the whole request, in a fresh transaction. + * + * You won't necessarily want to execute the exact same SQL commands with the + * exact same data. Some of your SQL statements may depend on state that can + * vary between retries. Data in the database may already have changed, for + * instance. So instead of dumbly replaying the SQL, you re-run the same + * application code that produced those SQL commands, from the start. + * + * The transactor framework makes it a little easier for you to do this safely, + * and avoid typical pitfalls. You encapsulate the work that you want to do + * into a callable that you pass to the @ref perform function. + * + * Here's how it works. You write your transaction code as a lambda or + * function, which creates its own transaction object, does its work, and + * commits at the end. You pass that callback to @ref pqxx::perform, which + * runs it for you. + * + * If there's a failure inside your callback, there will be an exception. Your + * transaction object goes out of scope and gets destroyed, so that it aborts + * implicitly. Seeing this, @ref perform tries running your callback again. It + * stops doing that when the callback succeeds, or when it has failed too many + * times, or when there's an error that leaves the database in an unknown + * state, such as a lost connection just while we're waiting for the database + * to confirm a commit. It all depends on the type of exception. + * + * The callback takes no arguments. If you're using lambdas, the easy way to + * pass arguments is for the lambda to "capture" them from your variables. Or, + * if you're using functions, you may want to use `std::bind`. + * + * Once your callback succeeds, it can return a result, and @ref perform will + * return that result back to you. + */ +//@{ + +/// Simple way to execute a transaction with automatic retry. +/** + * Executes your transaction code as a callback. Repeats it until it completes + * normally, or it throws an error other than the few libpqxx-generated + * exceptions that the framework understands, or after a given number of failed + * attempts, or if the transaction ends in an "in-doubt" state. + * + * (An in-doubt state is one where libpqxx cannot determine whether the server + * finally committed a transaction or not. This can happen if the network + * connection to the server is lost just while we're waiting for its reply to + * a "commit" statement. The server may have completed the commit, or not, but + * it can't tell you because there's no longer a connection. + * + * Using this still takes a bit of care. If your callback makes use of data + * from the database, you'll probably have to query that data within your + * callback. If the attempt to perform your callback fails, and the framework + * tries again, you'll be in a new transaction and the data in the database may + * have changed under your feet. + * + * Also be careful about changing variables or data structures from within + * your callback. The run may still fail, and perhaps get run again. The + * ideal way to do it (in most cases) is to return your result from your + * callback, and change your program's data state only after @ref perform + * completes successfully. + * + * @param callback Transaction code that can be called with no arguments. + * @param attempts Maximum number of times to attempt performing callback. + * Must be greater than zero. + * @return Whatever your callback returns. + */ +template +inline auto perform(TRANSACTION_CALLBACK &&callback, int attempts = 3) + -> std::invoke_result_t +{ + if (attempts <= 0) + throw std::invalid_argument{ + "Zero or negative number of attempts passed to pqxx::perform()."}; + + for (; attempts > 0; --attempts) + { + try + { + return std::invoke(callback); + } + catch (in_doubt_error const &) + { + // Not sure whether transaction went through or not. The last thing in + // the world that we should do now is try again! + throw; + } + catch (statement_completion_unknown const &) + { + // Not sure whether our last statement succeeded. Don't risk running it + // again. + throw; + } + catch (broken_connection const &) + { + // Connection failed. May be worth retrying, if the transactor opens its + // own connection. + if (attempts <= 1) + throw; + continue; + } + catch (transaction_rollback const &) + { + // Some error that may well be transient, such as serialization failure + // or deadlock. Worth retrying. + if (attempts <= 1) + throw; + continue; + } + } + throw pqxx::internal_error{"No outcome reached on perform()."}; +} +} // namespace pqxx +//@} +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types new file mode 100644 index 000000000..23a5caae1 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types @@ -0,0 +1,7 @@ +/** + * Basic typedefs and forward declarations. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/types.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types.hxx new file mode 100644 index 000000000..f95b598f8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types.hxx @@ -0,0 +1,173 @@ +/* Basic type aliases and forward declarations. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_TYPES +#define PQXX_H_TYPES + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include + +#if defined(PQXX_HAVE_CONCEPTS) && __has_include() +# include +#endif + + +namespace pqxx +{ +/// Number of rows in a result set. +using result_size_type = int; + +/// Difference between result sizes. +using result_difference_type = int; + +/// Number of fields in a row of database data. +using row_size_type = int; + +/// Difference between row sizes. +using row_difference_type = int; + +/// Number of bytes in a field of database data. +using field_size_type = std::size_t; + +/// Number of bytes in a large object. +using large_object_size_type = int64_t; + + +// Forward declarations, to help break compilation dependencies. +// These won't necessarily include all classes in libpqxx. +class binarystring; +class connection; +class const_result_iterator; +class const_reverse_result_iterator; +class const_reverse_row_iterator; +class const_row_iterator; +class dbtransaction; +class field; +class largeobjectaccess; +class notification_receiver; +struct range_error; +class result; +class row; +class stream_from; +class transaction_base; + +/// Marker for @ref stream_from constructors: "stream from table." +/** @deprecated Use @ref stream_from::table() instead. + */ +struct from_table_t +{}; + +/// Marker for @ref stream_from constructors: "stream from query." +/** @deprecated Use @ref stream_from::query() instead. + */ +struct from_query_t +{}; + + +/// Format code: is data text or binary? +/** Binary-compatible with libpq's format codes. + */ +enum class format : int +{ + text = 0, + binary = 1, +}; + + +/// Remove any constness, volatile, and reference-ness from a type. +/** @deprecated In C++20 we'll replace this with std::remove_cvref. + */ +template +using strip_t = std::remove_cv_t>; + + +#if defined(PQXX_HAVE_CONCEPTS) +/// The type of a container's elements. +/** At the time of writing there's a similar thing in `std::experimental`, + * which we may or may not end up using for this. + */ +template +using value_type = strip_t()))>; +#else // PQXX_HAVE_CONCEPTS +/// The type of a container's elements. +/** At the time of writing there's a similar thing in `std::experimental`, + * which we may or may not end up using for this. + */ +template +using value_type = strip_t()))>; +#endif // PQXX_HAVE_CONCEPTS + + +#if defined(PQXX_HAVE_CONCEPTS) +/// Concept: Any type that we can read as a string of `char`. +template +concept char_string = std::ranges::contiguous_range and + std::same_as < strip_t>, +char > ; + +/// Concept: Anything we can iterate to get things we can read as strings. +template +concept char_strings = + std::ranges::range and char_string>>; + +/// Concept: Anything we might want to treat as binary data. +template +concept potential_binary = std::ranges::contiguous_range and + (sizeof(value_type) == 1); +#endif // PQXX_HAVE_CONCEPTS + + +// C++20: Retire these compatibility definitions. +#if defined(PQXX_HAVE_CONCEPTS) + +/// Template argument type for a range. +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_RANGE_ARG std::ranges::range + +/// Template argument type for @ref char_string. +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_CHAR_STRING_ARG pqxx::char_string + +/// Template argument type for @ref char_strings +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_CHAR_STRINGS_ARG pqxx::char_strings + +#else // PQXX_HAVE_CONCEPTS + +/// Template argument type for a range. +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_RANGE_ARG typename + +/// Template argument type for @ref char_string. +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_CHAR_STRING_ARG typename + +/// Template argument type for @ref char_strings +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_CHAR_STRINGS_ARG typename + +#endif // PQXX_HAVE_CONCEPTS +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util new file mode 100644 index 000000000..6d85ab611 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util @@ -0,0 +1,6 @@ +/** Various utility definitions for libpqxx. + */ +// Actual definitions in .hxx file so editors and such recognize file type +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/util.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util.hxx new file mode 100644 index 000000000..4aa5ecf57 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util.hxx @@ -0,0 +1,521 @@ +/* Various utility definitions for libpqxx. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/util instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_UTIL +#define PQXX_H_UTIL + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __has_include() +# include +#endif + +#include "pqxx/except.hxx" +#include "pqxx/internal/encodings.hxx" +#include "pqxx/types.hxx" +#include "pqxx/version.hxx" + + +/// The home of all libpqxx classes, functions, templates, etc. +namespace pqxx +{} + +#include + + +/// Internal items for libpqxx' own use. Do not use these yourself. +namespace pqxx::internal +{ + +// C++20: Retire wrapper. +/// Same as `std::cmp_less`, or a workaround where that's not available. +template +inline constexpr bool cmp_less(LEFT lhs, RIGHT rhs) noexcept +{ +#if defined(PQXX_HAVE_CMP) + return std::cmp_less(lhs, rhs); +#else + // We need a variable just because lgtm.com gives off a false positive + // warning when we compare the values directly. It considers that a + // "self-comparison." + constexpr bool left_signed{std::is_signed_v}; + if constexpr (left_signed == std::is_signed_v) + return lhs < rhs; + else if constexpr (std::is_signed_v) + return (lhs <= 0) ? true : (std::make_unsigned_t(lhs) < rhs); + else + return (rhs <= 0) ? false : (lhs < std::make_unsigned_t(rhs)); +#endif +} + + +// C++20: Retire wrapper. +/// C++20 std::cmp_greater, or workaround if not available. +template +inline constexpr bool cmp_greater(LEFT lhs, RIGHT rhs) noexcept +{ +#if defined(PQXX_HAVE_CMP) + return std::cmp_greater(lhs, rhs); +#else + return cmp_less(rhs, lhs); +#endif +} + + +// C++20: Retire wrapper. +/// C++20 std::cmp_less_equal, or workaround if not available. +template +inline constexpr bool cmp_less_equal(LEFT lhs, RIGHT rhs) noexcept +{ +#if defined(PQXX_HAVE_CMP) + return std::cmp_less_equal(lhs, rhs); +#else + return not cmp_less(rhs, lhs); +#endif +} + + +// C++20: Retire wrapper. +/// C++20 std::cmp_greater_equal, or workaround if not available. +template +inline constexpr bool cmp_greater_equal(LEFT lhs, RIGHT rhs) noexcept +{ +#if defined(PQXX_HAVE_CMP) + return std::cmp_greater_equal(lhs, rhs); +#else + return not cmp_less(lhs, rhs); +#endif +} + + +/// Efficiently concatenate two strings. +/** This is a special case of concatenate(), needed because dependency + * management does not let us use that function here. + */ +[[nodiscard]] inline std::string cat2(std::string_view x, std::string_view y) +{ + std::string buf; + auto const xs{std::size(x)}, ys{std::size(y)}; + buf.resize(xs + ys); + x.copy(std::data(buf), xs); + y.copy(std::data(buf) + xs, ys); + return buf; +} +} // namespace pqxx::internal + + +namespace pqxx +{ +using namespace std::literals; + +/// Suppress compiler warning about an unused item. +template inline constexpr void ignore_unused(T &&...) noexcept +{} + + +/// Cast a numeric value to another type, or throw if it underflows/overflows. +/** Both types must be arithmetic types, and they must either be both integral + * or both floating-point types. + */ +template +inline TO check_cast(FROM value, std::string_view description) +{ + static_assert(std::is_arithmetic_v); + static_assert(std::is_arithmetic_v); + static_assert(std::is_integral_v == std::is_integral_v); + + // The rest of this code won't quite work for bool, but bool is trivially + // convertible to other arithmetic types as far as I can see. + if constexpr (std::is_same_v) + return static_cast(value); + + // Depending on our "if constexpr" conditions, this parameter may not be + // needed. Some compilers will warn. + ignore_unused(description); + + using from_limits = std::numeric_limits; + using to_limits = std::numeric_limits; + if constexpr (std::is_signed_v) + { + if constexpr (std::is_signed_v) + { + if (value < to_limits::lowest()) + throw range_error{internal::cat2("Cast underflow: "sv, description)}; + } + else + { + // FROM is signed, but TO is not. Treat this as a special case, because + // there may not be a good broader type in which the compiler can even + // perform our check. + if (value < 0) + throw range_error{internal::cat2( + "Casting negative value to unsigned type: "sv, description)}; + } + } + else + { + // No need to check: the value is unsigned so can't fall below the range + // of the TO type. + } + + if constexpr (std::is_integral_v) + { + using unsigned_from = std::make_unsigned_t; + using unsigned_to = std::make_unsigned_t; + constexpr auto from_max{static_cast((from_limits::max)())}; + constexpr auto to_max{static_cast((to_limits::max)())}; + if constexpr (from_max > to_max) + { + if (internal::cmp_greater(value, to_max)) + throw range_error{internal::cat2("Cast overflow: "sv, description)}; + } + } + else if constexpr ((from_limits::max)() > (to_limits::max)()) + { + if (value > (to_limits::max)()) + throw range_error{internal::cat2("Cast overflow: ", description)}; + } + + return static_cast(value); +} + + +/** Check library version at link time. + * + * Ensures a failure when linking an application against a radically + * different libpqxx version than the one against which it was compiled. + * + * Sometimes application builds fail in unclear ways because they compile + * using headers from libpqxx version X, but then link against libpqxx + * binary version Y. A typical scenario would be one where you're building + * against a libpqxx which you have built yourself, but a different version + * is installed on the system. + * + * The check_library_version template is declared for any library version, + * but only actually defined for the version of the libpqxx binary against + * which the code is linked. + * + * If the library binary is a different version than the one declared in + * these headers, then this call will fail to link: there will be no + * definition for the function with these exact template parameter values. + * There will be a definition, but the version in the parameter values will + * be different. + */ +inline PQXX_PRIVATE void check_version() noexcept +{ + // There is no particular reason to do this here in @ref connection, except + // to ensure that every meaningful libpqxx client will execute it. The call + // must be in the execution path somewhere or the compiler won't try to link + // it. We can't use it to initialise a global or class-static variable, + // because a smart compiler might resolve it at compile time. + // + // On the other hand, we don't want to make a useless function call too + // often for performance reasons. A local static variable is initialised + // only on the definition's first execution. Compilers will be well + // optimised for this behaviour, so there's a minimal one-time cost. + static auto const version_ok{internal::PQXX_VERSION_CHECK()}; + ignore_unused(version_ok); +} + + +/// Descriptor of library's thread-safety model. +/** This describes what the library knows about various risks to thread-safety. + */ +struct PQXX_LIBEXPORT thread_safety_model +{ + /// Is the underlying libpq build thread-safe? + bool safe_libpq = false; + + /// Is Kerberos thread-safe? + /** @warning Is currently always `false`. + * + * If your application uses Kerberos, all accesses to libpqxx or Kerberos + * must be serialized. Confine their use to a single thread, or protect it + * with a global lock. + */ + bool safe_kerberos = false; + + /// A human-readable description of any thread-safety issues. + std::string description; +}; + + +/// Describe thread safety available in this build. +[[nodiscard]] PQXX_LIBEXPORT thread_safety_model describe_thread_safety(); + + +#if defined(PQXX_HAVE_CONCEPTS) +# define PQXX_POTENTIAL_BINARY_ARG pqxx::potential_binary +#else +# define PQXX_POTENTIAL_BINARY_ARG typename +#endif + + +/// Cast binary data to a type that libpqxx will recognise as binary. +/** There are many different formats for storing binary data in memory. You + * may have yours as a `std::string`, or a `std::vector`, or one of + * many other types. + * + * But for libpqxx to recognise your data as binary, it needs to be a + * `std::basic_string`, or a `std::basic_string_view`; + * or in C++20 or better, any contiguous block of `std::byte`. + * + * Use `binary_cast` as a convenience helper to cast your data as a + * `std::basic_string_view`. + * + * @warning There are two things you should be aware of! First, the data must + * be contiguous in memory. In C++20 the compiler will enforce this, but in + * C++17 it's your own problem. Second, you must keep the object where you + * store the actual data alive for as long as you might use this function's + * return value. + */ +template +std::basic_string_view binary_cast(TYPE const &data) +{ + static_assert(sizeof(value_type) == 1); + return { + reinterpret_cast( + const_cast const *>( + std::data(data))), + std::size(data)}; +} + + +#if defined(PQXX_HAVE_CONCEPTS) +template +concept char_sized = (sizeof(CHAR) == 1); +# define PQXX_CHAR_SIZED_ARG char_sized +#else +# define PQXX_CHAR_SIZED_ARG typename +#endif + +/// Construct a type that libpqxx will recognise as binary. +/** Takes a data pointer and a size, without being too strict about their + * types, and constructs a `std::basic_string_view` pointing to + * the same data. + * + * This makes it a little easier to turn binary data, in whatever form you + * happen to have it, into binary data as libpqxx understands it. + */ +template +std::basic_string_view binary_cast(CHAR const *data, SIZE size) +{ + static_assert(sizeof(CHAR) == 1); + return { + reinterpret_cast(data), + check_cast(size, "binary data size")}; +} + + +/// The "null" oid. +constexpr oid oid_none{0}; +} // namespace pqxx + + +/// Private namespace for libpqxx's internal use; do not access. +/** This namespace hides definitions internal to libpqxx. These are not + * supposed to be used by client programs, and they may change at any time + * without notice. + * + * Conversely, if you find something in this namespace tremendously useful, by + * all means do lodge a request for its publication. + * + * @warning Here be dragons! + */ +namespace pqxx::internal +{ +using namespace std::literals; + + +/// A safer and more generic replacement for `std::isdigit`. +/** Turns out `std::isdigit` isn't as easy to use as it sounds. It takes an + * `int`, but requires it to be nonnegative. Which means it's an outright + * liability on systems where `char` is signed. + */ +template inline constexpr bool is_digit(CHAR c) noexcept +{ + return (c >= '0') and (c <= '9'); +} + + +/// Describe an object for humans, based on class name and optional name. +/** Interprets an empty name as "no name given." + */ +[[nodiscard]] std::string +describe_object(std::string_view class_name, std::string_view name); + + +/// Check validity of registering a new "guest" in a "host." +/** The host might be e.g. a connection, and the guest a transaction. The + * host can only have one guest at a time, so it is an error to register a new + * guest while the host already has a guest. + * + * If the new registration is an error, this function throws a descriptive + * exception. + * + * Pass the old guest (if any) and the new guest (if any), for both, a type + * name (at least if the guest is not null), and optionally an object name + * (but which may be omitted if the caller did not assign one). + */ +void check_unique_register( + void const *old_guest, std::string_view old_class, std::string_view old_name, + void const *new_guest, std::string_view new_class, + std::string_view new_name); + + +/// Like @ref check_unique_register, but for un-registering a guest. +/** Pass the guest which was registered, as well as the guest which is being + * unregistered, so that the function can check that they are the same one. + */ +void check_unique_unregister( + void const *old_guest, std::string_view old_class, std::string_view old_name, + void const *new_guest, std::string_view new_class, + std::string_view new_name); + + +/// Compute buffer size needed to escape binary data for use as a BYTEA. +/** This uses the hex-escaping format. The return value includes room for the + * "\x" prefix. + */ +inline constexpr std::size_t size_esc_bin(std::size_t binary_bytes) noexcept +{ + return 2 + (2 * binary_bytes) + 1; +} + + +/// Compute binary size from the size of its escaped version. +/** Do not include a terminating zero in `escaped_bytes`. + */ +inline constexpr std::size_t size_unesc_bin(std::size_t escaped_bytes) noexcept +{ + return (escaped_bytes - 2) / 2; +} + + +// TODO: Use actual binary type for "data". +/// Hex-escape binary data into a buffer. +/** The buffer must be able to accommodate + * `size_esc_bin(std::size(binary_data))` bytes, and the function will write + * exactly that number of bytes into the buffer. This includes a trailing + * zero. + */ +void PQXX_LIBEXPORT +esc_bin(std::basic_string_view binary_data, char buffer[]) noexcept; + + +/// Hex-escape binary data into a std::string. +std::string PQXX_LIBEXPORT +esc_bin(std::basic_string_view binary_data); + + +/// Reconstitute binary data from its escaped version. +void PQXX_LIBEXPORT +unesc_bin(std::string_view escaped_data, std::byte buffer[]); + + +/// Reconstitute binary data from its escaped version. +std::basic_string + PQXX_LIBEXPORT unesc_bin(std::string_view escaped_data); + + +/// Transitional: std::ssize(), or custom implementation if not available. +template auto ssize(T const &c) +{ +#if defined(__cpp_lib_ssize) && __cplusplus >= __cpp_lib_ssize + return std::ssize(c); +#else + using signed_t = std::make_signed_t; + return static_cast(std::size(c)); +#endif // __cpp_lib_ssize +} + + +/// Helper for determining a function's parameter types. +/** This function has no definition. It's not meant to be actually called. + * It's just there for pattern-matching in the compiler, so we can use its + * hypothetical return value. + */ +template +std::tuple args_f(RETURN (&func)(ARGS...)); + + +/// Helper for determining a `std::function`'s parameter types. +/** This function has no definition. It's not meant to be actually called. + * It's just there for pattern-matching in the compiler, so we can use its + * hypothetical return value. + */ +template +std::tuple args_f(std::function const &); + + +/// Helper for determining a member function's parameter types. +/** This function has no definition. It's not meant to be actually called. + * It's just there for pattern-matching in the compiler, so we can use its + * hypothetical return value. + */ +template +std::tuple member_args_f(RETURN (CLASS::*)(ARGS...)); + + +/// Helper for determining a const member function's parameter types. +/** This function has no definition. It's not meant to be actually called. + * It's just there for pattern-matching in the compiler, so we can use its + * hypothetical return value. + */ +template +std::tuple member_args_f(RETURN (CLASS::*)(ARGS...) const); + + +/// Helper for determining a callable type's parameter types. +/** This specialisation should work for lambdas. + * + * This function has no definition. It's not meant to be actually called. + * It's just there for pattern-matching in the compiler, so we can use its + * hypothetical return value. + */ +template +auto args_f(CALLABLE const &f) + -> decltype(member_args_f(&CALLABLE::operator())); + + +/// A callable's parameter types, as a tuple. +template +using args_t = decltype(args_f(std::declval())); + + +/// Helper: Apply `strip_t` to each of a tuple type's component types. +/** This function has no definition. It is not meant to be called, only to be + * used to deduce the right types. + */ +template +std::tuple...> strip_types(std::tuple const &); + + +/// Take a tuple type and apply @ref strip_t to its component types. +template +using strip_types_t = decltype(strip_types(std::declval())); +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version new file mode 100644 index 000000000..8dd5e48d4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version @@ -0,0 +1,7 @@ +/** libpqxx version info. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/version.hxx" +#include "pqxx/internal/header-post.hxx" + diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version.hxx new file mode 100644 index 000000000..a159f1bed --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version.hxx @@ -0,0 +1,55 @@ +/* Version info for libpqxx. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/version instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_VERSION + +# if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +# endif + +/// Full libpqxx version string. +# define PQXX_VERSION "7.7.3" +/// Library ABI version. +# define PQXX_ABI "7.7" + +/// Major version number. +# define PQXX_VERSION_MAJOR 7 +/// Minor version number. +# define PQXX_VERSION_MINOR 7 + +# define PQXX_VERSION_CHECK check_pqxx_version_7_7 + +namespace pqxx::internal +{ +/// Library version check stub. +/** Helps detect version mismatches between libpqxx headers and the libpqxx + * library binary. + * + * Sometimes users run into trouble linking their code against libpqxx because + * they build their own libpqxx, but the system also has a different version + * installed. The declarations in the headers against which they compile their + * code will differ from the ones used to build the libpqxx version they're + * using, leading to confusing link errors. The solution is to generate a link + * error when the libpqxx binary is not the same version as the libpqxx headers + * used to compile the code. + * + * This function's definition is in the libpqxx binary, so it's based on the + * version as found in the binary. The headers contain a call to the function, + * whose name contains the libpqxx version as found in the headers. (The + * library build process will use its own local headers even if another version + * of the headers is installed on the system.) + * + * If the libpqxx binary was compiled for a different version than the user's + * code, linking will fail with an error: `check_pqxx_version_*_*` will not + * exist for the given version number. + */ +PQXX_LIBEXPORT int PQXX_VERSION_CHECK() noexcept; +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview new file mode 100644 index 000000000..66ea2a625 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview @@ -0,0 +1,6 @@ +/** Zero-terminated string view class. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/zview.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview.hxx new file mode 100644 index 000000000..36a779f51 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview.hxx @@ -0,0 +1,163 @@ +/* Zero-terminated string view. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/zview instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ZVIEW +#define PQXX_H_ZVIEW + +#include +#include +#include + +#include "pqxx/types.hxx" + + +namespace pqxx +{ +/// Marker-type wrapper: zero-terminated `std::string_view`. +/** @warning Use this only if the underlying string is zero-terminated. + * + * When you construct a zview, you are promising that if the data pointer is + * non-null, the underlying string is zero-terminated. It otherwise behaves + * exactly like a std::string_view. + * + * The terminating zero is not "in" the string, so it does not count as part of + * the view's length. + * + * The added guarantee lets the view be used as a C-style string, which often + * matters since libpqxx builds on top of a C library. For this reason, zview + * also adds a @ref c_str method. + */ +class zview : public std::string_view +{ +public: + constexpr zview() noexcept = default; + + /// Convenience overload: construct using pointer and signed length. + constexpr zview(char const text[], std::ptrdiff_t len) : + std::string_view{text, static_cast(len)} + {} + + /// Convenience overload: construct using pointer and signed length. + constexpr zview(char text[], std::ptrdiff_t len) : + std::string_view{text, static_cast(len)} + {} + + /// Explicitly promote a `string_view` to a `zview`. + explicit constexpr zview(std::string_view other) noexcept : + std::string_view{other} + {} + + /// Construct from any initialiser you might use for `std::string_view`. + /** @warning Only do this if you are sure that the string is zero-terminated. + */ + template + explicit constexpr zview(Args &&...args) : + std::string_view(std::forward(args)...) + {} + + // C++20: constexpr. + /// @warning There's an implicit conversion from `std::string`. + zview(std::string const &str) noexcept : + std::string_view{str.c_str(), str.size()} + {} + + /// Construct a `zview` from a C-style string. + /** @warning This scans the string to discover its length. So if you need to + * do it many times, it's probably better to create the `zview` once and + * re-use it. + */ + constexpr zview(char const str[]) : std::string_view{str} {} + + /// Construct a `zview` from a string literal. + /** A C++ string literal ("foo") normally looks a lot like a pointer to + * char const, but that's not really true. It's actually an array of char, + * which _devolves_ to a pointer when you pass it. + * + * For the purpose of creating a `zview` there is one big difference: if we + * know the array's size, we don't need to scan through the string in order + * to find out its length. + */ + template + constexpr zview(char const (&literal)[size]) : zview(literal, size - 1) + {} + + /// Either a null pointer, or a zero-terminated text buffer. + [[nodiscard]] constexpr char const *c_str() const &noexcept + { + return data(); + } +}; + + +/// Support @ref zview literals. +/** You can "import" this selectively into your namespace, without pulling in + * all of the @ref pqxx namespace: + * + * ```cxx + * using pqxx::operator"" _zv; + * ``` + */ +constexpr zview operator"" _zv(char const str[], std::size_t len) noexcept +{ + return zview{str, len}; +} +} // namespace pqxx + + +#if defined(PQXX_HAVE_CONCEPTS) +/// A zview is a view. +template<> inline constexpr bool std::ranges::enable_view{true}; + + +/// A zview is a borrowed range. +template<> +inline constexpr bool std::ranges::enable_borrowed_range{true}; + +namespace pqxx::internal +{ +/// Concept: T is a known zero-terminated string type. +/** There's no unified API for these string types. It's just a check for some + * known types. Any code that makes use of the concept will still have to + * support each of these individually. + */ +template +concept ZString = std::is_convertible_v < strip_t, +char const * > or std::is_convertible_v, zview> or + std::is_convertible_v; +} // namespace pqxx::internal +#endif // PQXX_HAVE_CONCEPTS + + +namespace pqxx::internal +{ +/// Get a raw C string pointer. +inline constexpr char const *as_c_string(char const str[]) noexcept +{ + return str; +} +/// Get a raw C string pointer. +template +inline constexpr char const *as_c_string(char (&str)[N]) noexcept +{ + return str; +} +/// Get a raw C string pointer. +inline constexpr char const *as_c_string(pqxx::zview str) noexcept +{ + return str.c_str(); +} +// C++20: Make this constexpr. +/// Get a raw C string pointer. +inline char const *as_c_string(std::string const &str) noexcept +{ + return str.c_str(); +} +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config-version.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config-version.cmake new file mode 100644 index 000000000..c47d6956d --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config-version.cmake @@ -0,0 +1,70 @@ +# This is a basic version file for the Config-mode of find_package(). +# It is used by write_basic_package_version_file() as input file for configure_file() +# to create a version-file which can be installed along a config.cmake file. +# +# The created file sets PACKAGE_VERSION_EXACT if the current version string and +# the requested version string are exactly the same and it sets +# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version, +# but only if the requested major version is the same as the current one. +# The variable CVF_VERSION must be set before calling configure_file(). + + +set(PACKAGE_VERSION "7.7.3") + +if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + + if("7.7.3" MATCHES "^([0-9]+)\\.") + set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}") + if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}") + endif() + else() + set(CVF_VERSION_MAJOR "7.7.3") + endif() + + if(PACKAGE_FIND_VERSION_RANGE) + # both endpoints of the range must have the expected major version + math (EXPR CVF_VERSION_MAJOR_NEXT "${CVF_VERSION_MAJOR} + 1") + if (NOT PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX_MAJOR STREQUAL CVF_VERSION_MAJOR) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX VERSION_LESS_EQUAL CVF_VERSION_MAJOR_NEXT))) + set(PACKAGE_VERSION_COMPATIBLE FALSE) + elseif(PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + AND ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS_EQUAL PACKAGE_FIND_VERSION_MAX) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MAX))) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + else() + if(PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() + endif() +endif() + + +# if the installed project requested no architecture check, don't perform the check +if("FALSE") + return() +endif() + +# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "") + return() +endif() + +# check that the installed version has the same 32/64bit-ness as the one which is currently searching: +if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") + math(EXPR installedBits "8 * 8") + set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config.cmake new file mode 100644 index 000000000..cb25a05f2 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config.cmake @@ -0,0 +1,4 @@ +include(CMakeFindDependencyMacro) +find_dependency(PostgreSQL) + +include("${CMAKE_CURRENT_LIST_DIR}/libpqxx-targets.cmake") diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake new file mode 100644 index 000000000..980f46098 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake @@ -0,0 +1,19 @@ +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Import target "libpqxx::pqxx" for configuration "" +set_property(TARGET libpqxx::pqxx APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG) +set_target_properties(libpqxx::pqxx PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES_NOCONFIG "CXX" + IMPORTED_LOCATION_NOCONFIG "${_IMPORT_PREFIX}/lib/libpqxx-7.7.a" + ) + +list(APPEND _IMPORT_CHECK_TARGETS libpqxx::pqxx ) +list(APPEND _IMPORT_CHECK_FILES_FOR_libpqxx::pqxx "${_IMPORT_PREFIX}/lib/libpqxx-7.7.a" ) + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets.cmake new file mode 100644 index 000000000..c7b525b18 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets.cmake @@ -0,0 +1,99 @@ +# Generated by CMake + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.6) + message(FATAL_ERROR "CMake >= 2.6.0 required") +endif() +cmake_policy(PUSH) +cmake_policy(VERSION 2.6...3.20) +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Protect against multiple inclusion, which would fail when already imported targets are added once more. +set(_targetsDefined) +set(_targetsNotDefined) +set(_expectedTargets) +foreach(_expectedTarget libpqxx::pqxx) + list(APPEND _expectedTargets ${_expectedTarget}) + if(NOT TARGET ${_expectedTarget}) + list(APPEND _targetsNotDefined ${_expectedTarget}) + endif() + if(TARGET ${_expectedTarget}) + list(APPEND _targetsDefined ${_expectedTarget}) + endif() +endforeach() +if("${_targetsDefined}" STREQUAL "${_expectedTargets}") + unset(_targetsDefined) + unset(_targetsNotDefined) + unset(_expectedTargets) + set(CMAKE_IMPORT_FILE_VERSION) + cmake_policy(POP) + return() +endif() +if(NOT "${_targetsDefined}" STREQUAL "") + message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n") +endif() +unset(_targetsDefined) +unset(_targetsNotDefined) +unset(_expectedTargets) + + +# Compute the installation prefix relative to this file. +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +# Create imported target libpqxx::pqxx +add_library(libpqxx::pqxx STATIC IMPORTED) + +set_target_properties(libpqxx::pqxx PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" + INTERFACE_LINK_LIBRARIES "/usr/lib/aarch64-linux-gnu/libpq.so" +) + +if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "This file relies on consumers using CMake 2.8.12 or greater.") +endif() + +# Load information for each installed configuration. +get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +file(GLOB CONFIG_FILES "${_DIR}/libpqxx-targets-*.cmake") +foreach(f ${CONFIG_FILES}) + include(${f}) +endforeach() + +# Cleanup temporary variables. +set(_IMPORT_PREFIX) + +# Loop over all imported files and verify that they actually exist +foreach(target ${_IMPORT_CHECK_TARGETS} ) + foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} ) + if(NOT EXISTS "${file}" ) + message(FATAL_ERROR "The imported target \"${target}\" references the file + \"${file}\" +but this file does not exist. Possible reasons include: +* The file was deleted, renamed, or moved to another location. +* An install or uninstall procedure did not complete successfully. +* The installation package was faulty and contained + \"${CMAKE_CURRENT_LIST_FILE}\" +but not all the files it references. +") + endif() + endforeach() + unset(_IMPORT_CHECK_FILES_FOR_${target}) +endforeach() +unset(_IMPORT_CHECK_TARGETS) + +# This file does not depend on other imported targets which have +# been exported from the same project but in a separate export set. + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) +cmake_policy(POP) diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/libpqxx-7.7.a b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/libpqxx-7.7.a new file mode 100644 index 0000000000000000000000000000000000000000..2cb705a34c561d8f7fdf049f81c34e0d61b351e7 GIT binary patch literal 4997126 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

GHxEt7eC|`>T;TzHry*?zQ!rsq0 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

)<=8B7BHJG6hcC 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≥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