From 442fdabff04ae1b8b35b251dfcb43d25d0544e0f Mon Sep 17 00:00:00 2001 From: Javier Navarro Date: Tue, 12 Nov 2024 16:59:45 +0100 Subject: [PATCH 01/15] SDK-4563: Add Objective-C++ binding for enableRequestStatusMonitor --- bindings/ios/MEGASdk.mm | 14 ++++++++++++++ bindings/ios/include/MEGASdk.h | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/bindings/ios/MEGASdk.mm b/bindings/ios/MEGASdk.mm index 7c524cb475..d3098de1b1 100644 --- a/bindings/ios/MEGASdk.mm +++ b/bindings/ios/MEGASdk.mm @@ -4063,6 +4063,20 @@ - (void)queryAds:(AdsFlag)adFlags publicHandle:(MEGAHandle)publicHandle delegate } } +- (void)enableRequestStatusMonitor:(BOOL)enable { + if (self.megaApi) { + self.megaApi->enableRequestStatusMonitor(enable); + } +} + +- (BOOL)isRequestStatusMonitorEnabled { + if (self.megaApi) { + return self.megaApi->requestStatusMonitorEnabled(); + } else { + return NO; + } +} + #pragma mark - VPN - (void)getVpnRegionsWithDelegate:(id)delegate { diff --git a/bindings/ios/include/MEGASdk.h b/bindings/ios/include/MEGASdk.h index be865d2436..c4c01d73a5 100644 --- a/bindings/ios/include/MEGASdk.h +++ b/bindings/ios/include/MEGASdk.h @@ -9948,6 +9948,20 @@ typedef NS_ENUM(NSInteger, MEGAClientType) { */ - (void)queryAds:(AdsFlag)adFlags publicHandle:(MEGAHandle)publicHandle delegate:(id)delegate; +/// Enable or disable the request status monitor +/// +/// - Note: When it's enabled, the request status monitor generates events of type +/// `EventReqStatProgress` with the per mille progress in +/// the field [MEGAEvent number], or -1 if there isn't any operation in progress. +/// +/// - Parameters: +/// - enable: YES to enable the request status monitor, or No to disable it +- (void)enableRequestStatusMonitor:(BOOL)enable; + +/// Get the status of the request status monitor +/// - Returns: YES when the request status monitor is enabled, or NO if it's disabled +@property (readonly, nonatomic, getter=isRequestStatusMonitorEnabled) BOOL requestStatusMonitorEnabled; + #pragma mark - VPN /** From d248be777c2fba6f31a8d9a273fed408c9b9e7bf Mon Sep 17 00:00:00 2001 From: Jeff Ruan Date: Mon, 18 Nov 2024 11:54:48 +1300 Subject: [PATCH 02/15] Revert "Remove getMEGADNSservers" This reverts commit 735f842d3f1ae535d8fa648cc6442a8aac8605f7. --- include/mega/http.h | 8 +++++ include/mega/posix/meganet.h | 1 + src/http.cpp | 65 ++++++++++++++++++++++++++++++++++++ src/posix/net.cpp | 7 ++++ 4 files changed, 81 insertions(+) diff --git a/include/mega/http.h b/include/mega/http.h index 7b1af79452..43400a2f7d 100644 --- a/include/mega/http.h +++ b/include/mega/http.h @@ -174,6 +174,11 @@ namespace mega { #define SFUSTATSSSLEXPONENTSIZE "\x03" #define SFUSTATSSSLEXPONENT "\x01\x00\x01" +#define MEGA_DNS_SERVERS "2001:678:25c:2215::554,89.44.169.136," \ + "2001:678:25c:2215::559,89.44.169.141," \ + "2a0b:e40:3::14,66.203.127.16," \ + "2a0b:e40:3::16,66.203.127.14" + class MEGA_API SpeedController { public: @@ -306,6 +311,9 @@ struct MEGA_API HttpIO : public EventTrigger // get DNS servers as configured in the system void getDNSserversFromIos(string &dnsServers); + + // get alternative DNS servers + void getMEGADNSservers(string* dnsservers, bool getfromnetwork); // set max download speed virtual bool setmaxdownloadspeed(m_off_t bpslimit); diff --git a/include/mega/posix/meganet.h b/include/mega/posix/meganet.h index 6a746bf6f2..14009c7ce5 100644 --- a/include/mega/posix/meganet.h +++ b/include/mega/posix/meganet.h @@ -171,6 +171,7 @@ class CurlHttpIO: public HttpIO bool curlipv6; bool reset; bool statechange; + bool dnsok; #ifdef MEGA_USE_C_ARES string dnsservers; #endif diff --git a/src/http.cpp b/src/http.cpp index df961be0ea..c43b211128 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -303,6 +303,71 @@ void HttpIO::getDNSserversFromIos(string& dnsServers) #endif } +void HttpIO::getMEGADNSservers(string* dnsservers, bool getfromnetwork) +{ + if (!dnsservers) + { + return; + } + + dnsservers->clear(); + if (getfromnetwork) + { + struct addrinfo* aiList = NULL; + struct addrinfo* hp; + + struct addrinfo hints = {}; + hints.ai_family = AF_UNSPEC; + +#ifndef __MINGW32__ + hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; +#endif + + if (!getaddrinfo("ns.mega.co.nz", NULL, &hints, &aiList)) + { + hp = aiList; + while (hp) + { + char straddr[INET6_ADDRSTRLEN]; + straddr[0] = 0; + + if (hp->ai_family == AF_INET) + { + sockaddr_in* addr = (sockaddr_in*)hp->ai_addr; + mega_inet_ntop(hp->ai_family, &addr->sin_addr, straddr, sizeof(straddr)); + } + else if (hp->ai_family == AF_INET6) + { + sockaddr_in6* addr = (sockaddr_in6*)hp->ai_addr; + mega_inet_ntop(hp->ai_family, &addr->sin6_addr, straddr, sizeof(straddr)); + } + + if (straddr[0]) + { + if (dnsservers->size()) + { + dnsservers->append(","); + } + dnsservers->append(straddr); + } + + hp = hp->ai_next; + } + freeaddrinfo(aiList); + } + } + + if (!getfromnetwork || !dnsservers->size()) + { + *dnsservers = MEGA_DNS_SERVERS; + LOG_info << "Using hardcoded MEGA DNS servers: " << *dnsservers; + } + else + { + LOG_info << "Using current MEGA DNS servers: " << *dnsservers; + } +} + bool HttpIO::setmaxdownloadspeed(m_off_t) { return false; diff --git a/src/posix/net.cpp b/src/posix/net.cpp index 00f4c65786..d13e364601 100644 --- a/src/posix/net.cpp +++ b/src/posix/net.cpp @@ -259,6 +259,7 @@ CurlHttpIO::CurlHttpIO() curlipv6 = data->features & CURL_VERSION_IPV6; LOG_debug << "IPv6 enabled: " << curlipv6; + dnsok = false; reset = false; statechange = false; disconnecting = false; @@ -1940,6 +1941,11 @@ void CurlHttpIO::post(HttpReq* req, const char* data, unsigned len) LOG_info << "Using custom DNS servers: " << dnsservers; ares_set_servers_csv(ares, dnsservers.c_str()); } + else if (!dnsok) + { + getMEGADNSservers(&dnsservers, false); + ares_set_servers_csv(ares, dnsservers.c_str()); + } if (proxyurl.size() && !proxyip.size()) { @@ -2361,6 +2367,7 @@ bool CurlHttpIO::multidoio(CURLM *curlmhandle) if (req->status == REQ_SUCCESS) { + dnsok = true; lastdata = Waiter::ds; req->lastdata = Waiter::ds; } From b9c6cc4f4c1e2e66bdde5ea7a88211f3449f9a35 Mon Sep 17 00:00:00 2001 From: Jeff Ruan Date: Mon, 18 Nov 2024 12:30:02 +1300 Subject: [PATCH 03/15] Use public DNS servers --- include/mega/http.h | 10 ++++++---- src/http.cpp | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/mega/http.h b/include/mega/http.h index 43400a2f7d..a97326b611 100644 --- a/include/mega/http.h +++ b/include/mega/http.h @@ -174,10 +174,12 @@ namespace mega { #define SFUSTATSSSLEXPONENTSIZE "\x03" #define SFUSTATSSSLEXPONENT "\x01\x00\x01" -#define MEGA_DNS_SERVERS "2001:678:25c:2215::554,89.44.169.136," \ - "2001:678:25c:2215::559,89.44.169.141," \ - "2a0b:e40:3::14,66.203.127.16," \ - "2a0b:e40:3::16,66.203.127.14" + +#define DNS_SERVERS "2001:4860:4860::8888,8.8.8.8," \ + "2001:4860:4860::8844,8.8.4.4," \ + "2606:4700:4700::1111,1.1.1.1," \ + "2606:4700:4700::1001,1.0.0.1," \ + "2620:fe::fe,9.9.9.9" class MEGA_API SpeedController { diff --git a/src/http.cpp b/src/http.cpp index c43b211128..ec3c99d84c 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -359,7 +359,7 @@ void HttpIO::getMEGADNSservers(string* dnsservers, bool getfromnetwork) if (!getfromnetwork || !dnsservers->size()) { - *dnsservers = MEGA_DNS_SERVERS; + *dnsservers = DNS_SERVERS; LOG_info << "Using hardcoded MEGA DNS servers: " << *dnsservers; } else From 92732571884f0c88f7a63a482d9b8877c3a92c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C4=83t=C4=83lin=20R=C4=83ceanu?= Date: Tue, 12 Nov 2024 11:40:16 +0200 Subject: [PATCH 04/15] Add hashcash support --- include/mega/http.h | 4 ++ include/mega/megaclient.h | 5 +++ src/megaclient.cpp | 25 ++++++++++++ src/posix/net.cpp | 85 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+) diff --git a/include/mega/http.h b/include/mega/http.h index a97326b611..4e3cc5792b 100644 --- a/include/mega/http.h +++ b/include/mega/http.h @@ -383,6 +383,10 @@ struct MEGA_API HttpReq // Content-Type of the response string contenttype; + // Hashcash of a response + string hashcash; + uint8_t hashcashEasyness{}; + // HttpIO implementation-specific identifier for this connection void* httpiohandle; diff --git a/include/mega/megaclient.h b/include/mega/megaclient.h index 658d223c97..f90cfacea6 100644 --- a/include/mega/megaclient.h +++ b/include/mega/megaclient.h @@ -1716,6 +1716,11 @@ class MEGA_API MegaClient // reqs[r^1] is being processed on the API server HttpReq* pendingcs; + // When triggering an API Hashcash challenge, the HTTP response will contain + // X-Hashcash header, with relevant data to be saved and used for the next retry. + string reqHashcash; + uint8_t reqHashcashEasyness{}; + // Only queue the "Server busy" event once, until the current cs completes, otherwise we may DDOS // ourselves in cases where many clients get 500s for a while and then recover at the same time bool pendingcs_serverBusySent = false; diff --git a/src/megaclient.cpp b/src/megaclient.cpp index adcce2f408..4957873052 100644 --- a/src/megaclient.cpp +++ b/src/megaclient.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -2569,6 +2570,27 @@ void MegaClient::exec() // fall through case REQ_FAILURE: + if (pendingcs->httpstatus == 402) + { + // get X-Hashcash header + if (pendingcs->hashcash.empty()) + { + LOG_err << "X-Hashcash header missing for HTTP status " + << pendingcs->httpstatus; + } + else if (pendingcs->contentlength != 0) + { + LOG_err << "Content-Length not 0, as it should be when X-Hashcash " + "header was received"; + } + else + { + reqHashcash = std::move(pendingcs->hashcash); + pendingcs->hashcash.clear(); // just to be sure + reqHashcashEasyness = pendingcs->hashcashEasyness; + } + } + if (!reason && pendingcs->httpstatus != 200) { if (pendingcs->httpstatus == 500) @@ -2679,6 +2701,9 @@ void MegaClient::exec() pendingcs->mChunked = !isClientType(ClientType::VPN); } + pendingcs->hashcash = std::move(reqHashcash); + reqHashcash.clear(); + pendingcs->hashcashEasyness = reqHashcashEasyness; performanceStats.csRequestWaitTime.start(); pendingcs->post(this); continue; diff --git a/src/posix/net.cpp b/src/posix/net.cpp index d13e364601..1c44387520 100644 --- a/src/posix/net.cpp +++ b/src/posix/net.cpp @@ -1452,6 +1452,50 @@ struct curl_slist* CurlHttpIO::clone_curl_slist(struct curl_slist* inlist) return outlist; } +// Generate cash function +std::string gencash(const string& token, uint8_t easiness) +{ + // Calculate threshold + uint32_t threshold = + static_cast((((easiness & 63) << 1) + 1) << ((easiness >> 6) * 7 + 3)); + + string tokenBinary = Base64::atob(token); + LOG_debug << "Token in B64: " << token << " (size: " << token.size() << " chars, " + << tokenBinary.size() << " bytes)"; + + // Buffer to hold 4-byte prefix + 262144 * 48 bytes of the token + std::vector buffer(4 + 262144 * 48); // total size = 179919652 + for (auto i = 0; i < 262144; ++i) + { + std::copy(tokenBinary.begin(), tokenBinary.end(), buffer.begin() + 4 + i * 48); + } + + while (1) + { + // Increment prefix + uint32_t& prefixToIncrement = *reinterpret_cast(buffer.data()); + ++prefixToIncrement; + + // Save prefix + std::string prefixToReturn(buffer.begin(), buffer.begin() + 4); + + // SHA-256 hash + HashSHA256 hasher; + hasher.add((const byte*)buffer.data(), static_cast(buffer.size())); + + string hash; + hasher.get(&hash); + + // Check if hash meets threshold + uint32_t prefixOfDigest = + static_cast((hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3]); + if (prefixOfDigest <= threshold) + { + return Base64::btoa(prefixToReturn); + } + } +} + void CurlHttpIO::send_request(CurlHttpContext* httpctx) { CurlHttpIO* httpio = httpctx->httpio; @@ -1484,6 +1528,16 @@ void CurlHttpIO::send_request(CurlHttpContext* httpctx) httpctx->headers = clone_curl_slist(req->type == REQ_JSON ? httpio->contenttypejson : httpio->contenttypebinary); httpctx->posturl = req->posturl; + if (!req->hashcash.empty()) + { + string nextValue = gencash(req->hashcash, req->hashcashEasyness); + string xHashcashHeader{"X-Hashcash: 1:" + req->hashcash + ":" + std::move(nextValue)}; + httpctx->headers = curl_slist_append(httpctx->headers, xHashcashHeader.c_str()); + LOG_warn << "X-Hashcash computed: " << xHashcashHeader; + req->hashcash.clear(); + req->hashcashEasyness = 0; + } + #ifdef MEGA_USE_C_ARES if(httpio->proxyip.size()) { @@ -2721,6 +2775,37 @@ size_t CurlHttpIO::check_header(void* ptr, size_t size, size_t nmemb, void* targ { req->contenttype.assign((char *)ptr + 13, len - 15); } + else if (len >= (11 + 7) && !memcmp(ptr, "X-Hashcash:", 11)) // "X-Hashcash: 1:A:B:C" + { + string buffer{(char*)ptr + 11, len - 11}; + LOG_warn << "X-Hashcash received: " << buffer; + + // Example of hashcash header + // 1:100:1731410499:RUvIePV2PNO8ofg8xp1aT5ugBcKSEzwKoLBw9o4E6F_fmn44eC3oMpv388UtFl2K + // ::: + + std::stringstream ss(buffer); + vector hc; + for (size_t i = 0; i < 4; i++) + { + string buf; + if (!getline(ss, buf, ':')) + break; + hc.push_back(move(buf)); + } + if (hc.size() != 4 // incomplete data + || stoi(hc[0]) != 1 // not required to process + || stoi(hc[1]) < 0 || stoi(hc[1]) > 255) // invalid easiness [0, 255] + { + req->hashcash.clear(); + req->hashcashEasyness = 0; + } + else + { + req->hashcash = hc[3].substr(0, 64); + req->hashcashEasyness = static_cast(stoi(hc[1])); + } + } else { return len; From 1651009d73507d97e2197e11da64ba77d0ebdd74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Hern=C3=A1ndez?= Date: Tue, 12 Nov 2024 12:03:55 +0100 Subject: [PATCH 05/15] Test hashcash from megacli --- examples/megacli.cpp | 61 +++++++++++++++++++++++++++++++++----------- examples/megacli.h | 3 ++- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/examples/megacli.cpp b/examples/megacli.cpp index f78ed07aeb..2796ce8cce 100644 --- a/examples/megacli.cpp +++ b/examples/megacli.cpp @@ -5257,6 +5257,8 @@ autocomplete::ACN autocompleteSyntax() p->Add(exec_collectAndPrintTransferStats, sequence(text("getTransferStats"), opt(either(flag("-uploads"), flag("-downloads"))))); + + p->Add(exec_hashcash, sequence(text("hashcash"), opt(either(flag("-on"), flag("-off"))))); return autocompleteTemplate = std::move(p); } @@ -6668,21 +6670,21 @@ void exec_open(autocomplete::ACState& s) // create a new MegaClient with a different MegaApp to process callbacks // from the client logged into a folder. Reuse the waiter and httpio - clientFolder = new MegaClient(new DemoAppFolder, - client->waiter, - client->httpio, - #ifdef DBACCESS_CLASS - new DBACCESS_CLASS(*startDir), - #else - NULL, - #endif - gfx, - "Gk8DyQBS", - "megacli_folder/" TOSTRING(MEGA_MAJOR_VERSION) - "." TOSTRING(MEGA_MINOR_VERSION) - "." TOSTRING(MEGA_MICRO_VERSION), - 2, - client->getClientType()); + clientFolder = + new MegaClient(new DemoAppFolder, + client->waiter, + client->httpio, +#ifdef DBACCESS_CLASS + new DBACCESS_CLASS(*startDir), +#else + NULL, +#endif + gfx, + "HashcashDemo Gk8DyQBS", + "megacli_folder/" TOSTRING(MEGA_MAJOR_VERSION) "." TOSTRING( + MEGA_MINOR_VERSION) "." TOSTRING(MEGA_MICRO_VERSION), + 2, + client->getClientType()); } else { @@ -13494,3 +13496,32 @@ void exec_collectAndPrintTransferStats(autocomplete::ACState& state) collectAndPrintTransfersMetricsFromType(GET); } } + +void exec_hashcash(autocomplete::ACState& s) +{ + const static string originalUserAgent = client->useragent; + const static string hashcashUserAgent = "HashcashDemo"; + + if (s.words.size() == 1) + { + cout << "Hashcash demo is " + << ((client->useragent == hashcashUserAgent) ? "enabled" : "disabled") << endl; + return; + } + + if (s.extractflag("-on")) + { + g_APIURL_default = "https://staging.api.mega.co.nz/"; + client->useragent = hashcashUserAgent; + } + else if (s.extractflag("-off")) + { + g_APIURL_default = "https://g.api.mega.co.nz/"; + client->useragent = originalUserAgent; + } + + client->httpio->APIURL = g_APIURL_default; + string tempUserAgent = client->useragent; + client->httpio->setuseragent(&tempUserAgent); + client->disconnect(); +} diff --git a/examples/megacli.h b/examples/megacli.h index 87707fecc0..c71ffeb107 100644 --- a/examples/megacli.h +++ b/examples/megacli.h @@ -449,4 +449,5 @@ void exec_generatepassword(autocomplete::ACState&); void exec_importpasswordsfromgooglefile(autocomplete::ACState&); void exec_getpricing(autocomplete::ACState&); -void exec_collectAndPrintTransferStats(autocomplete::ACState&); \ No newline at end of file +void exec_collectAndPrintTransferStats(autocomplete::ACState&); +void exec_hashcash(autocomplete::ACState&); From fc77bc69106df276609ec45a8b9df0049841435b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C4=83t=C4=83lin=20R=C4=83ceanu?= Date: Wed, 13 Nov 2024 11:32:13 +0200 Subject: [PATCH 06/15] Add hashcash.gencash unit test --- tests/unit/CMakeLists.txt | 1 + tests/unit/hashcash_test.cpp | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/unit/hashcash_test.cpp diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 59d40c5f48..4e10961913 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -21,6 +21,7 @@ target_sources(test_unit FileFingerprint_test.cpp File_test.cpp FsNode.cpp + hashcash_test.cpp Logging_test.cpp MediaProperties_test.cpp MegaApi_test.cpp diff --git a/tests/unit/hashcash_test.cpp b/tests/unit/hashcash_test.cpp new file mode 100644 index 0000000000..b27944deff --- /dev/null +++ b/tests/unit/hashcash_test.cpp @@ -0,0 +1,34 @@ +/** + * (c) 2024 by Mega Limited, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +#include + +namespace mega +{ +std::string gencash(const std::string& token, uint8_t easiness); +} + +TEST(hashcash, gencash) +{ + static const std::string token = + "wFqIT_wY3tYKcrm5zqwaUoWym3ZCz32cCsrJOgYBgihtpaWUhGyWJ--EY-zfwI-i"; + static const uint8_t easiness = 180; + static const std::string prefix = ::mega::gencash(token, easiness); + + ASSERT_EQ(prefix, "owAAAA"); +} From 7c360a34396fe1b1fdc9f4479ded735c8303a01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Hern=C3=A1ndez?= Date: Wed, 13 Nov 2024 16:39:50 +0100 Subject: [PATCH 07/15] Add sanity check for size of token + minor refactors. - Remove `HashcashDemo` from the `appKey` passed to the ctor of `DemoApp` in `megacli`. - Clean new variable members upon logout (when requests are discarded). - Rename new variable members to include the `m` prefix. - Add comments. - Remove unnecessary logs. --- examples/megacli.cpp | 2 +- include/mega/http.h | 6 +++--- include/mega/megaclient.h | 4 ++-- src/megaclient.cpp | 17 ++++++++++------- src/posix/net.cpp | 32 +++++++++++++++++--------------- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/examples/megacli.cpp b/examples/megacli.cpp index 2796ce8cce..692b14b82b 100644 --- a/examples/megacli.cpp +++ b/examples/megacli.cpp @@ -6680,7 +6680,7 @@ void exec_open(autocomplete::ACState& s) NULL, #endif gfx, - "HashcashDemo Gk8DyQBS", + "Gk8DyQBS", "megacli_folder/" TOSTRING(MEGA_MAJOR_VERSION) "." TOSTRING( MEGA_MINOR_VERSION) "." TOSTRING(MEGA_MICRO_VERSION), 2, diff --git a/include/mega/http.h b/include/mega/http.h index 4e3cc5792b..d76ad970e6 100644 --- a/include/mega/http.h +++ b/include/mega/http.h @@ -383,9 +383,9 @@ struct MEGA_API HttpReq // Content-Type of the response string contenttype; - // Hashcash of a response - string hashcash; - uint8_t hashcashEasyness{}; + // Hashcash data extracted from X-Hashcash header of cs response, if any + string mHashcashToken; + uint8_t mHashcashEasyness{}; // HttpIO implementation-specific identifier for this connection void* httpiohandle; diff --git a/include/mega/megaclient.h b/include/mega/megaclient.h index f90cfacea6..cc8c062200 100644 --- a/include/mega/megaclient.h +++ b/include/mega/megaclient.h @@ -1718,8 +1718,8 @@ class MEGA_API MegaClient // When triggering an API Hashcash challenge, the HTTP response will contain // X-Hashcash header, with relevant data to be saved and used for the next retry. - string reqHashcash; - uint8_t reqHashcashEasyness{}; + string mReqHashcashToken; + uint8_t mReqHashcashEasyness{}; // Only queue the "Server busy" event once, until the current cs completes, otherwise we may DDOS // ourselves in cases where many clients get 500s for a while and then recover at the same time diff --git a/src/megaclient.cpp b/src/megaclient.cpp index 4957873052..b9855a1def 100644 --- a/src/megaclient.cpp +++ b/src/megaclient.cpp @@ -2573,7 +2573,7 @@ void MegaClient::exec() if (pendingcs->httpstatus == 402) { // get X-Hashcash header - if (pendingcs->hashcash.empty()) + if (pendingcs->mHashcashToken.empty()) { LOG_err << "X-Hashcash header missing for HTTP status " << pendingcs->httpstatus; @@ -2585,9 +2585,9 @@ void MegaClient::exec() } else { - reqHashcash = std::move(pendingcs->hashcash); - pendingcs->hashcash.clear(); // just to be sure - reqHashcashEasyness = pendingcs->hashcashEasyness; + mReqHashcashToken = std::move(pendingcs->mHashcashToken); + pendingcs->mHashcashToken.clear(); // just to be sure + mReqHashcashEasyness = pendingcs->mHashcashEasyness; } } @@ -2701,9 +2701,9 @@ void MegaClient::exec() pendingcs->mChunked = !isClientType(ClientType::VPN); } - pendingcs->hashcash = std::move(reqHashcash); - reqHashcash.clear(); - pendingcs->hashcashEasyness = reqHashcashEasyness; + pendingcs->mHashcashToken = std::move(mReqHashcashToken); + mReqHashcashToken.clear(); + pendingcs->mHashcashEasyness = mReqHashcashEasyness; performanceStats.csRequestWaitTime.start(); pendingcs->post(this); continue; @@ -4760,6 +4760,9 @@ void MegaClient::locallogout(bool removecaches, bool keepSyncsConfigFile) mKeyManager.reset(); mLastErrorDetected = REASON_ERROR_NO_ERROR; + + mReqHashcashEasyness = 0; + mReqHashcashToken.clear(); } void MegaClient::removeCaches() diff --git a/src/posix/net.cpp b/src/posix/net.cpp index 1c44387520..fe80a57896 100644 --- a/src/posix/net.cpp +++ b/src/posix/net.cpp @@ -1455,13 +1455,14 @@ struct curl_slist* CurlHttpIO::clone_curl_slist(struct curl_slist* inlist) // Generate cash function std::string gencash(const string& token, uint8_t easiness) { - // Calculate threshold + // Calculate threshold from easiness + // easiness: encoded threshold (maximum acceptable value in the first 32 byte of the + // hash (little endian) - the lower, the harder to solve) uint32_t threshold = static_cast((((easiness & 63) << 1) + 1) << ((easiness >> 6) * 7 + 3)); - + + // Token is 64 chars in B64, we need the 48 bytes in binary string tokenBinary = Base64::atob(token); - LOG_debug << "Token in B64: " << token << " (size: " << token.size() << " chars, " - << tokenBinary.size() << " bytes)"; // Buffer to hold 4-byte prefix + 262144 * 48 bytes of the token std::vector buffer(4 + 262144 * 48); // total size = 179919652 @@ -1528,14 +1529,14 @@ void CurlHttpIO::send_request(CurlHttpContext* httpctx) httpctx->headers = clone_curl_slist(req->type == REQ_JSON ? httpio->contenttypejson : httpio->contenttypebinary); httpctx->posturl = req->posturl; - if (!req->hashcash.empty()) + if (!req->mHashcashToken.empty()) { - string nextValue = gencash(req->hashcash, req->hashcashEasyness); - string xHashcashHeader{"X-Hashcash: 1:" + req->hashcash + ":" + std::move(nextValue)}; + string nextValue = gencash(req->mHashcashToken, req->mHashcashEasyness); + string xHashcashHeader{"X-Hashcash: 1:" + req->mHashcashToken + ":" + std::move(nextValue)}; httpctx->headers = curl_slist_append(httpctx->headers, xHashcashHeader.c_str()); LOG_warn << "X-Hashcash computed: " << xHashcashHeader; - req->hashcash.clear(); - req->hashcashEasyness = 0; + req->mHashcashToken.clear(); + req->mHashcashEasyness = 0; } #ifdef MEGA_USE_C_ARES @@ -2775,7 +2776,7 @@ size_t CurlHttpIO::check_header(void* ptr, size_t size, size_t nmemb, void* targ { req->contenttype.assign((char *)ptr + 13, len - 15); } - else if (len >= (11 + 7) && !memcmp(ptr, "X-Hashcash:", 11)) // "X-Hashcash: 1:A:B:C" + else if (len >= (11 + 7) && !memcmp(ptr, "X-Hashcash:", 11)) { string buffer{(char*)ptr + 11, len - 11}; LOG_warn << "X-Hashcash received: " << buffer; @@ -2795,15 +2796,16 @@ size_t CurlHttpIO::check_header(void* ptr, size_t size, size_t nmemb, void* targ } if (hc.size() != 4 // incomplete data || stoi(hc[0]) != 1 // not required to process - || stoi(hc[1]) < 0 || stoi(hc[1]) > 255) // invalid easiness [0, 255] + || stoi(hc[1]) < 0 || stoi(hc[1]) > 255 // invalid easiness [0, 255] + || hc[3].size() != 64) // token is 64 chars in B64 { - req->hashcash.clear(); - req->hashcashEasyness = 0; + req->mHashcashToken.clear(); + req->mHashcashEasyness = 0; } else { - req->hashcash = hc[3].substr(0, 64); - req->hashcashEasyness = static_cast(stoi(hc[1])); + req->mHashcashToken = hc[3].substr(0, 64); + req->mHashcashEasyness = static_cast(stoi(hc[1])); } } else From 5a6455a141c74cb6e12b322f4edefb2c83345d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Hern=C3=A1ndez?= Date: Thu, 14 Nov 2024 10:19:39 +0100 Subject: [PATCH 08/15] Fix typo on easiness --- include/mega/http.h | 2 +- include/mega/megaclient.h | 2 +- src/megaclient.cpp | 6 +++--- src/posix/net.cpp | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/mega/http.h b/include/mega/http.h index d76ad970e6..e7538f0ec3 100644 --- a/include/mega/http.h +++ b/include/mega/http.h @@ -385,7 +385,7 @@ struct MEGA_API HttpReq // Hashcash data extracted from X-Hashcash header of cs response, if any string mHashcashToken; - uint8_t mHashcashEasyness{}; + uint8_t mHashcashEasiness{}; // HttpIO implementation-specific identifier for this connection void* httpiohandle; diff --git a/include/mega/megaclient.h b/include/mega/megaclient.h index cc8c062200..172ff2ab3d 100644 --- a/include/mega/megaclient.h +++ b/include/mega/megaclient.h @@ -1719,7 +1719,7 @@ class MEGA_API MegaClient // When triggering an API Hashcash challenge, the HTTP response will contain // X-Hashcash header, with relevant data to be saved and used for the next retry. string mReqHashcashToken; - uint8_t mReqHashcashEasyness{}; + uint8_t mReqHashcashEasiness{}; // Only queue the "Server busy" event once, until the current cs completes, otherwise we may DDOS // ourselves in cases where many clients get 500s for a while and then recover at the same time diff --git a/src/megaclient.cpp b/src/megaclient.cpp index b9855a1def..f54181336c 100644 --- a/src/megaclient.cpp +++ b/src/megaclient.cpp @@ -2587,7 +2587,7 @@ void MegaClient::exec() { mReqHashcashToken = std::move(pendingcs->mHashcashToken); pendingcs->mHashcashToken.clear(); // just to be sure - mReqHashcashEasyness = pendingcs->mHashcashEasyness; + mReqHashcashEasiness = pendingcs->mHashcashEasiness; } } @@ -2703,7 +2703,7 @@ void MegaClient::exec() pendingcs->mHashcashToken = std::move(mReqHashcashToken); mReqHashcashToken.clear(); - pendingcs->mHashcashEasyness = mReqHashcashEasyness; + pendingcs->mHashcashEasiness = mReqHashcashEasiness; performanceStats.csRequestWaitTime.start(); pendingcs->post(this); continue; @@ -4761,7 +4761,7 @@ void MegaClient::locallogout(bool removecaches, bool keepSyncsConfigFile) mLastErrorDetected = REASON_ERROR_NO_ERROR; - mReqHashcashEasyness = 0; + mReqHashcashEasiness = 0; mReqHashcashToken.clear(); } diff --git a/src/posix/net.cpp b/src/posix/net.cpp index fe80a57896..1be375db81 100644 --- a/src/posix/net.cpp +++ b/src/posix/net.cpp @@ -1465,7 +1465,7 @@ std::string gencash(const string& token, uint8_t easiness) string tokenBinary = Base64::atob(token); // Buffer to hold 4-byte prefix + 262144 * 48 bytes of the token - std::vector buffer(4 + 262144 * 48); // total size = 179919652 + std::vector buffer(4 + 262144 * 48); // total size = 12582916 for (auto i = 0; i < 262144; ++i) { std::copy(tokenBinary.begin(), tokenBinary.end(), buffer.begin() + 4 + i * 48); @@ -1531,12 +1531,12 @@ void CurlHttpIO::send_request(CurlHttpContext* httpctx) if (!req->mHashcashToken.empty()) { - string nextValue = gencash(req->mHashcashToken, req->mHashcashEasyness); + string nextValue = gencash(req->mHashcashToken, req->mHashcashEasiness); string xHashcashHeader{"X-Hashcash: 1:" + req->mHashcashToken + ":" + std::move(nextValue)}; httpctx->headers = curl_slist_append(httpctx->headers, xHashcashHeader.c_str()); LOG_warn << "X-Hashcash computed: " << xHashcashHeader; req->mHashcashToken.clear(); - req->mHashcashEasyness = 0; + req->mHashcashEasiness = 0; } #ifdef MEGA_USE_C_ARES @@ -2800,12 +2800,12 @@ size_t CurlHttpIO::check_header(void* ptr, size_t size, size_t nmemb, void* targ || hc[3].size() != 64) // token is 64 chars in B64 { req->mHashcashToken.clear(); - req->mHashcashEasyness = 0; + req->mHashcashEasiness = 0; } else { req->mHashcashToken = hc[3].substr(0, 64); - req->mHashcashEasyness = static_cast(stoi(hc[1])); + req->mHashcashEasiness = static_cast(stoi(hc[1])); } } else From 6bf7a66fd54c2a44f4d8ccdd8946debba2c0f738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Hern=C3=A1ndez?= Date: Thu, 14 Nov 2024 10:43:26 +0100 Subject: [PATCH 09/15] Fix sanity check of size of token And remove pre-saving the prefix to return --- src/posix/net.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/posix/net.cpp b/src/posix/net.cpp index 1be375db81..bd52f7ea66 100644 --- a/src/posix/net.cpp +++ b/src/posix/net.cpp @@ -1477,9 +1477,6 @@ std::string gencash(const string& token, uint8_t easiness) uint32_t& prefixToIncrement = *reinterpret_cast(buffer.data()); ++prefixToIncrement; - // Save prefix - std::string prefixToReturn(buffer.begin(), buffer.begin() + 4); - // SHA-256 hash HashSHA256 hasher; hasher.add((const byte*)buffer.data(), static_cast(buffer.size())); @@ -1492,6 +1489,7 @@ std::string gencash(const string& token, uint8_t easiness) static_cast((hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3]); if (prefixOfDigest <= threshold) { + string prefixToReturn(buffer.begin(), buffer.begin() + 4); return Base64::btoa(prefixToReturn); } } @@ -2797,7 +2795,7 @@ size_t CurlHttpIO::check_header(void* ptr, size_t size, size_t nmemb, void* targ if (hc.size() != 4 // incomplete data || stoi(hc[0]) != 1 // not required to process || stoi(hc[1]) < 0 || stoi(hc[1]) > 255 // invalid easiness [0, 255] - || hc[3].size() != 64) // token is 64 chars in B64 + || hc[3].size() < 64) // token is 64 chars in B64 (may have ending \r\n { req->mHashcashToken.clear(); req->mHashcashEasiness = 0; From 4138c1ddb510fd69ba2c9a46c73b89a1199549ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C4=83t=C4=83lin=20R=C4=83ceanu?= Date: Thu, 14 Nov 2024 17:56:36 +0200 Subject: [PATCH 10/15] Set and read prefix from buffer in reversed byte-order --- src/posix/net.cpp | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/posix/net.cpp b/src/posix/net.cpp index bd52f7ea66..e82476cf13 100644 --- a/src/posix/net.cpp +++ b/src/posix/net.cpp @@ -1471,11 +1471,16 @@ std::string gencash(const string& token, uint8_t easiness) std::copy(tokenBinary.begin(), tokenBinary.end(), buffer.begin() + 4 + i * 48); } - while (1) + // Define a 4-byte unsigned counter, to be set as the prefix of the data to be hashed. + for (uint32_t i = 1; i < std::numeric_limits::max(); ++i) { - // Increment prefix - uint32_t& prefixToIncrement = *reinterpret_cast(buffer.data()); - ++prefixToIncrement; + // Write the prefix in front of the data in reversed byte-order + for (auto j = 0; j < 4; ++j) + { + uint8_t& src = *(reinterpret_cast(&i) + j); + uint8_t& dst = *(reinterpret_cast(buffer.data()) + 3 - j); + dst = src; + } // SHA-256 hash HashSHA256 hasher; @@ -1484,15 +1489,25 @@ std::string gencash(const string& token, uint8_t easiness) string hash; hasher.get(&hash); - // Check if hash meets threshold - uint32_t prefixOfDigest = - static_cast((hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3]); + // Get the new prefix after the hasher function has modified it along with the original + // data. + // Get the value converting it from reversed byte-order. + uint32_t prefixOfDigest = (uint16_t)hash[0]; + prefixOfDigest <<= 8; + prefixOfDigest += (uint16_t)hash[1]; + prefixOfDigest <<= 8; + prefixOfDigest += (uint16_t)hash[2]; + prefixOfDigest <<= 8; + prefixOfDigest += (uint16_t)hash[3]; + if (prefixOfDigest <= threshold) { string prefixToReturn(buffer.begin(), buffer.begin() + 4); return Base64::btoa(prefixToReturn); } } + + return {}; } void CurlHttpIO::send_request(CurlHttpContext* httpctx) From b48fd9005dd97a3ea7140d8c863b66f03c3da9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C4=83t=C4=83lin=20R=C4=83ceanu?= Date: Thu, 14 Nov 2024 18:02:05 +0200 Subject: [PATCH 11/15] Update hashcash.gencash test for the last changes and data sets --- tests/unit/hashcash_test.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/unit/hashcash_test.cpp b/tests/unit/hashcash_test.cpp index b27944deff..6770b8cfea 100644 --- a/tests/unit/hashcash_test.cpp +++ b/tests/unit/hashcash_test.cpp @@ -25,10 +25,18 @@ std::string gencash(const std::string& token, uint8_t easiness); TEST(hashcash, gencash) { - static const std::string token = - "wFqIT_wY3tYKcrm5zqwaUoWym3ZCz32cCsrJOgYBgihtpaWUhGyWJ--EY-zfwI-i"; - static const uint8_t easiness = 180; - static const std::string prefix = ::mega::gencash(token, easiness); + std::vector> hashcash{ + //{"wFqIT_wY3tYKcrm5zqwaUoWym3ZCz32cCsrJOgYBgihtpaWUhGyWJ--EY-zfwI-i", 180, "owAAAA"}, + //{"HuTZK3FtxCopSWiLkgNz2_f0rlL8SIEtogZEkeCG63Q_tAcuMo_uccuQD4VVZTPx", 180, "9QYAAA"}, + //{"3NIjq_fgu6bTyepwHuKiaB8a1YRjISBhktWK1fjhRx86RhOqKZNAcOZht0wJvmhQ", 180, "AQAAAA"}, + {"Fhhm_MO7cOf5UhE4jT5roHS4Tb61SU_QcFkcx6u1KZh5IqSA5NhCIZMViPuT7Exu", 180, "AAAARw"}, + {"l1_AfTmRRI05pmtF2Uf-wch8a8atZnrOcptIn0D79WIEfJo_qaFBZDqVuL3OOGPY", 180, "AAAAsA"}, + {"PVWJ-N_pGddp240_lya9XqrxodPPa4tmmxSeZost5MkWdj6vg42E2O7VpbY6ibNs", 180, "AAABag"}, + }; - ASSERT_EQ(prefix, "owAAAA"); + for (const auto& t : hashcash) + { + ASSERT_EQ(::mega::gencash(std::get<0>(t), std::get<1>(t)), std::get<2>(t)); + std::cout << "found " << std::get<2>(t) << std::endl; + } } From 769199ff6cf2c67608b635528b8221574ca9bcd7 Mon Sep 17 00:00:00 2001 From: Mathias Ortmann Date: Sat, 16 Nov 2024 19:14:55 +1300 Subject: [PATCH 12/15] Fix incorrect signed arithmetic, trim header string, handle curl return codes better --- src/megaclient.cpp | 2 +- src/posix/net.cpp | 49 +++++++++++++++++----------------------------- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/megaclient.cpp b/src/megaclient.cpp index f54181336c..e234ab0f54 100644 --- a/src/megaclient.cpp +++ b/src/megaclient.cpp @@ -2578,7 +2578,7 @@ void MegaClient::exec() LOG_err << "X-Hashcash header missing for HTTP status " << pendingcs->httpstatus; } - else if (pendingcs->contentlength != 0) + else if (pendingcs->contentlength > 0) { LOG_err << "Content-Length not 0, as it should be when X-Hashcash " "header was received"; diff --git a/src/posix/net.cpp b/src/posix/net.cpp index e82476cf13..e18308a580 100644 --- a/src/posix/net.cpp +++ b/src/posix/net.cpp @@ -1453,6 +1453,7 @@ struct curl_slist* CurlHttpIO::clone_curl_slist(struct curl_slist* inlist) } // Generate cash function +// FIXME: make async/make multithreaded std::string gencash(const string& token, uint8_t easiness) { // Calculate threshold from easiness @@ -1471,16 +1472,13 @@ std::string gencash(const string& token, uint8_t easiness) std::copy(tokenBinary.begin(), tokenBinary.end(), buffer.begin() + 4 + i * 48); } - // Define a 4-byte unsigned counter, to be set as the prefix of the data to be hashed. - for (uint32_t i = 1; i < std::numeric_limits::max(); ++i) + uint32_t* prefixptr = reinterpret_cast(buffer.data()); + + for (;;) { - // Write the prefix in front of the data in reversed byte-order - for (auto j = 0; j < 4; ++j) - { - uint8_t& src = *(reinterpret_cast(&i) + j); - uint8_t& dst = *(reinterpret_cast(buffer.data()) + 3 - j); - dst = src; - } + // increment prefix (the final result, but not its correctness, will depend on the CPU's endianness) + // we do not have an explicit abort condition (the actual easiness will be lenient enough) + (*prefixptr)++; // SHA-256 hash HashSHA256 hasher; @@ -1489,25 +1487,13 @@ std::string gencash(const string& token, uint8_t easiness) string hash; hasher.get(&hash); - // Get the new prefix after the hasher function has modified it along with the original - // data. - // Get the value converting it from reversed byte-order. - uint32_t prefixOfDigest = (uint16_t)hash[0]; - prefixOfDigest <<= 8; - prefixOfDigest += (uint16_t)hash[1]; - prefixOfDigest <<= 8; - prefixOfDigest += (uint16_t)hash[2]; - prefixOfDigest <<= 8; - prefixOfDigest += (uint16_t)hash[3]; - - if (prefixOfDigest <= threshold) + if (htonl(*(reinterpret_cast(hash.data()))) <= threshold) { + // success - return the prefix string prefixToReturn(buffer.begin(), buffer.begin() + 4); return Base64::btoa(prefixToReturn); } } - - return {}; } void CurlHttpIO::send_request(CurlHttpContext* httpctx) @@ -1549,7 +1535,6 @@ void CurlHttpIO::send_request(CurlHttpContext* httpctx) httpctx->headers = curl_slist_append(httpctx->headers, xHashcashHeader.c_str()); LOG_warn << "X-Hashcash computed: " << xHashcashHeader; req->mHashcashToken.clear(); - req->mHashcashEasiness = 0; } #ifdef MEGA_USE_C_ARES @@ -2302,7 +2287,7 @@ bool CurlHttpIO::multidoio(CURLM *curlmhandle) measureLatency(msg->easy_handle, req); CURLcode errorCode = msg->data.result; - if (errorCode != CURLE_OK) + if (errorCode != CURLE_OK && errorCode != CURLE_HTTP_RETURNED_ERROR && errorCode != CURLE_WRITE_ERROR) { LOG_debug << req->logname << "CURLMSG_DONE with error " << errorCode << ": " << curl_easy_strerror(errorCode); @@ -2791,12 +2776,15 @@ size_t CurlHttpIO::check_header(void* ptr, size_t size, size_t nmemb, void* targ } else if (len >= (11 + 7) && !memcmp(ptr, "X-Hashcash:", 11)) { - string buffer{(char*)ptr + 11, len - 11}; - LOG_warn << "X-Hashcash received: " << buffer; + // trim trailing CRLF + while (len > 11 && static_cast(ptr)[len] < ' ') len--; + + string buffer{(char*)ptr + 11, len - 10}; + LOG_warn << "X-Hashcash received:" << buffer; // Example of hashcash header // 1:100:1731410499:RUvIePV2PNO8ofg8xp1aT5ugBcKSEzwKoLBw9o4E6F_fmn44eC3oMpv388UtFl2K - // ::: + // ::: std::stringstream ss(buffer); vector hc; @@ -2808,12 +2796,11 @@ size_t CurlHttpIO::check_header(void* ptr, size_t size, size_t nmemb, void* targ hc.push_back(move(buf)); } if (hc.size() != 4 // incomplete data - || stoi(hc[0]) != 1 // not required to process + || stoi(hc[0]) != 1 // header version || stoi(hc[1]) < 0 || stoi(hc[1]) > 255 // invalid easiness [0, 255] - || hc[3].size() < 64) // token is 64 chars in B64 (may have ending \r\n + || hc[3].size() != 64) // token is 64 chars in B64 { req->mHashcashToken.clear(); - req->mHashcashEasiness = 0; } else { From 99e91864d6d88bb4d425fcc79552e30a7a989686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Hern=C3=A1ndez?= Date: Mon, 18 Nov 2024 14:43:57 +0100 Subject: [PATCH 13/15] Avoid to access out of the boundary of the buffer In first iteration, accessing to the index `len` will read further than expected. If the byte there were < ' ', then it won't trim the CR and LF. --- src/posix/net.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/posix/net.cpp b/src/posix/net.cpp index e18308a580..99b1f5b2f1 100644 --- a/src/posix/net.cpp +++ b/src/posix/net.cpp @@ -2777,9 +2777,9 @@ size_t CurlHttpIO::check_header(void* ptr, size_t size, size_t nmemb, void* targ else if (len >= (11 + 7) && !memcmp(ptr, "X-Hashcash:", 11)) { // trim trailing CRLF - while (len > 11 && static_cast(ptr)[len] < ' ') len--; + while (len > 11 && static_cast(ptr)[len - 1] < ' ') len--; - string buffer{(char*)ptr + 11, len - 10}; + string buffer{(char*)ptr + 11, len - 11}; LOG_warn << "X-Hashcash received:" << buffer; // Example of hashcash header From e2a329716ef2f8f7b15d78e43a8a99f602da8d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Hern=C3=A1ndez?= Date: Mon, 18 Nov 2024 15:19:42 +0100 Subject: [PATCH 14/15] Fix unit test --- tests/unit/hashcash_test.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/unit/hashcash_test.cpp b/tests/unit/hashcash_test.cpp index 6770b8cfea..09d3f82694 100644 --- a/tests/unit/hashcash_test.cpp +++ b/tests/unit/hashcash_test.cpp @@ -26,17 +26,12 @@ std::string gencash(const std::string& token, uint8_t easiness); TEST(hashcash, gencash) { std::vector> hashcash{ - //{"wFqIT_wY3tYKcrm5zqwaUoWym3ZCz32cCsrJOgYBgihtpaWUhGyWJ--EY-zfwI-i", 180, "owAAAA"}, - //{"HuTZK3FtxCopSWiLkgNz2_f0rlL8SIEtogZEkeCG63Q_tAcuMo_uccuQD4VVZTPx", 180, "9QYAAA"}, - //{"3NIjq_fgu6bTyepwHuKiaB8a1YRjISBhktWK1fjhRx86RhOqKZNAcOZht0wJvmhQ", 180, "AQAAAA"}, - {"Fhhm_MO7cOf5UhE4jT5roHS4Tb61SU_QcFkcx6u1KZh5IqSA5NhCIZMViPuT7Exu", 180, "AAAARw"}, - {"l1_AfTmRRI05pmtF2Uf-wch8a8atZnrOcptIn0D79WIEfJo_qaFBZDqVuL3OOGPY", 180, "AAAAsA"}, - {"PVWJ-N_pGddp240_lya9XqrxodPPa4tmmxSeZost5MkWdj6vg42E2O7VpbY6ibNs", 180, "AAABag"}, + {"wFqIT_wY3tYKcrm5zqwaUoWym3ZCz32cCsrJOgYBgihtpaWUhGyWJ--EY-zfwI-i", 180, "owAAAA"}, + {"3NIjq_fgu6bTyepwHuKiaB8a1YRjISBhktWK1fjhRx86RhOqKZNAcOZht0wJvmhQ", 180, "AQAAAA"}, }; for (const auto& t : hashcash) { ASSERT_EQ(::mega::gencash(std::get<0>(t), std::get<1>(t)), std::get<2>(t)); - std::cout << "found " << std::get<2>(t) << std::endl; } } From 672b44e6848beb45c77ddfd65634035e62af561e Mon Sep 17 00:00:00 2001 From: Jeff Ruan Date: Tue, 19 Nov 2024 10:38:25 +1300 Subject: [PATCH 15/15] Replace GetOverlappedResultEx --- include/mega/win32/gfx/worker/comms.h | 10 +++- src/gfx/worker/client.cpp | 1 - src/win32/gfx/worker/comms.cpp | 69 +++++++++++++++++++-------- src/win32/gfx/worker/comms_client.cpp | 2 - tools/gfxworker/src/win32/server.cpp | 51 +++++++++----------- tools/gfxworker/src/win32/server.h | 2 +- 6 files changed, 79 insertions(+), 56 deletions(-) diff --git a/include/mega/win32/gfx/worker/comms.h b/include/mega/win32/gfx/worker/comms.h index 148e6d58e6..59e5fad6a0 100644 --- a/include/mega/win32/gfx/worker/comms.h +++ b/include/mega/win32/gfx/worker/comms.h @@ -63,11 +63,17 @@ class WinOverlapped final OVERLAPPED* data(); - bool isValid() const { return mOverlapped.hEvent != NULL; }; + bool isValid() const + { + return mOverlapped.hEvent != NULL; + }; + + // Return an error code and error string on error + std::pair waitForCompletion(DWORD mWaitMs); private: OVERLAPPED mOverlapped; }; } // namespace -} \ No newline at end of file +} diff --git a/src/gfx/worker/client.cpp b/src/gfx/worker/client.cpp index 1548cc4b60..80ecd2a0e2 100644 --- a/src/gfx/worker/client.cpp +++ b/src/gfx/worker/client.cpp @@ -36,7 +36,6 @@ bool GfxClient::runHello(const std::string& text) auto response = sendAndReceive(endpoint.get(), command); if (response) { - LOG_verbose << "GfxClient gets hello response: " << response->Text; return true; } else diff --git a/src/win32/gfx/worker/comms.cpp b/src/win32/gfx/worker/comms.cpp index 4e96403df2..c06565b830 100644 --- a/src/win32/gfx/worker/comms.cpp +++ b/src/win32/gfx/worker/comms.cpp @@ -24,11 +24,10 @@ WinOverlapped::WinOverlapped() { mOverlapped.Offset = 0; mOverlapped.OffsetHigh = 0; - mOverlapped.hEvent = CreateEvent( - NULL, // default security attribute - TRUE, // manual-reset event - TRUE, // initial state = signaled - NULL); // unnamed event object + mOverlapped.hEvent = CreateEvent(NULL, // default security attribute + TRUE, // manual-reset event + FALSE, // initial state = non signaled + NULL); // unnamed event object if (mOverlapped.hEvent == NULL) { @@ -49,6 +48,30 @@ OVERLAPPED* WinOverlapped::data() return &mOverlapped; } +std::pair WinOverlapped::waitForCompletion(DWORD waitMs) +{ + const auto waitResult = WaitForSingleObject(mOverlapped.hEvent, waitMs); + switch (waitResult) + { + case WAIT_OBJECT_0: + return {std::error_code{}, ""}; + case WAIT_TIMEOUT: + return {std::make_error_code(std::errc::timed_out), + "wait timeout: " + std::to_string(waitMs)}; + case WAIT_ABANDONED: + return {std::make_error_code(std::errc::not_connected), "wait abandoned"}; + case WAIT_FAILED: + { + const auto lastError = GetLastError(); + return {std::make_error_code(std::errc::not_connected), + "wait failed error " + std::to_string(lastError) + " " + + mega::winErrorMessage(lastError)}; + } + default: + return {std::make_error_code(std::errc::not_connected), "wait error"}; + } +} + NamedPipe::NamedPipe(NamedPipe&& other) { this->mPipeHandle = other.mPipeHandle; @@ -61,7 +84,6 @@ NamedPipe::~NamedPipe() if (mPipeHandle != INVALID_HANDLE_VALUE) { CloseHandle(mPipeHandle); - LOG_verbose << "endpoint " << mName << "_" << mPipeHandle << " closed"; } } @@ -108,7 +130,7 @@ bool NamedPipe::doOverlappedOperation(std::function op, return false; } - WinOverlapped overlapped; + WinOverlapped overlapped{}; if (!overlapped.isValid()) { return false; @@ -117,34 +139,39 @@ bool NamedPipe::doOverlappedOperation(std::function op, // Call Op. if (op(overlapped.data())) { - return true; + return true; // Completed } - - // Error - auto lastError = GetLastError(); - if (lastError!= ERROR_IO_PENDING) + else if (const auto lastError = GetLastError(); + lastError != ERROR_IO_PENDING) // Fail with other errors { - LOG_err << mName << ": " << opStr << " pipe failed. error=" << lastError << " " << mega::winErrorMessage(lastError); + LOG_err << mName << ": " << opStr << " pipe failed. error=" << lastError << " " + << mega::winErrorMessage(lastError); return false; } - // Wait op to complete. Negative timeout is infinite + // Wait DWORD waitTimeout = timeout.count() < 0 ? INFINITE : static_cast(timeout.count()); + if (const auto& [error, errorText] = overlapped.waitForCompletion(waitTimeout); error) + { + LOG_verbose << mName << ": " << opStr << " " << errorText; + return false; + } + + // Get result DWORD numberOfBytesTransferred = 0; - bool success = GetOverlappedResultEx(mPipeHandle, - overlapped.data(), - &numberOfBytesTransferred, - waitTimeout, - false); + bool success = GetOverlappedResult(mPipeHandle, + overlapped.data(), + &numberOfBytesTransferred, + false /*bWait*/); if (!success) { - lastError = GetLastError(); + const auto lastError = GetLastError(); LOG_err << mName << ": " << opStr << " pipe fail to complete error=" << lastError << " " << mega::winErrorMessage(lastError); return false; } - // IO completed + // Completed return true; } diff --git a/src/win32/gfx/worker/comms_client.cpp b/src/win32/gfx/worker/comms_client.cpp index eb3289a509..c495c2c3bc 100644 --- a/src/win32/gfx/worker/comms_client.cpp +++ b/src/win32/gfx/worker/comms_client.cpp @@ -61,8 +61,6 @@ std::pair GfxCommunicationsClient::doConnect(LPCTSTR pipeName } } - LOG_verbose << "Connected Handle:" << hPipe << " error: " << static_cast(error); - return {error, hPipe}; } diff --git a/tools/gfxworker/src/win32/server.cpp b/tools/gfxworker/src/win32/server.cpp index c68033895a..1f49194fd7 100644 --- a/tools/gfxworker/src/win32/server.cpp +++ b/tools/gfxworker/src/win32/server.cpp @@ -13,9 +13,7 @@ ServerNamedPipe::~ServerNamedPipe() { if (isValid()) { - LOG_verbose << mName << "Endpoint server flush"; FlushFileBuffers(mPipeHandle); - LOG_verbose << mName << "Endpoint server disconnect"; DisconnectNamedPipe(mPipeHandle); } } @@ -25,58 +23,53 @@ void ServerWin32::operator()() serverListeningLoop(); } -std::error_code ServerWin32::waitForClient(HANDLE hPipe, OVERLAPPED* overlapped) +std::error_code ServerWin32::waitForClient(HANDLE hPipe, WinOverlapped& overlapped) { assert(hPipe != INVALID_HANDLE_VALUE); - assert(overlapped); + assert(overlapped.isValid()); // Wait for the client to connect asynchronous; if it succeeds, // the function returns a nonzero value. // If the function returns zero, - // GetLastError returns ERROR_IO_PENDING, the IO is connected - // GetLastError returns ERROR_PIPE_CONNECTED, the IO is pending - bool success = ConnectNamedPipe(hPipe, overlapped); + // GetLastError returns ERROR_PIPE_CONNECTED, the IO is connected + // GetLastError returns ERROR_IO_PENDING, the IO is pending + bool success = ConnectNamedPipe(hPipe, overlapped.data()); if (success) { LOG_verbose << "Client connected"; return OK; } - if (!success && GetLastError() == ERROR_PIPE_CONNECTED) + if (GetLastError() == ERROR_PIPE_CONNECTED) { LOG_verbose << "Client connected"; return OK; } - if (!success && GetLastError() != ERROR_IO_PENDING) + if (GetLastError() != ERROR_IO_PENDING) { LOG_verbose << "Client couldn't connect, error=" << GetLastError() << " " << mega::winErrorMessage(GetLastError()); return std::make_error_code(std::errc::not_connected); } - // IO_PENDING + // Wait + if (auto [error, errorText] = overlapped.waitForCompletion(mWaitMs); error) + { + LOG_verbose << "Client " << errorText; + return error; + } + + // Get result DWORD numberOfBytesTransferred = 0; - if (GetOverlappedResultEx( - hPipe, - overlapped, - &numberOfBytesTransferred, - mWaitMs, - false)) + if (GetOverlappedResult(hPipe, overlapped.data(), &numberOfBytesTransferred, false /*bWait*/)) { LOG_verbose << "Client connected"; return OK; } - if (GetLastError() == WAIT_TIMEOUT) - { - LOG_verbose << "Wait client connecting Timeout"; - return std::make_error_code(std::errc::timed_out); - } - else - { - LOG_verbose << "Client couldn't connect, error=" << GetLastError() << " " << mega::winErrorMessage(GetLastError()); - return std::make_error_code(std::errc::not_connected); - } + LOG_verbose << "Client couldn't connect, error=" << GetLastError() << " " + << mega::winErrorMessage(GetLastError()); + return std::make_error_code(std::errc::not_connected); } void ServerWin32::serverListeningLoop() @@ -87,6 +80,8 @@ void ServerWin32::serverListeningLoop() return; } + LOG_verbose << "server awaiting client connection"; + const auto fullPipeName = win_utils::toFullPipeName(mPipeName); // first instance to prevent two processes create the same pipe @@ -94,8 +89,6 @@ void ServerWin32::serverListeningLoop() const DWORD BUFSIZE = 512; for (;;) { - LOG_verbose << "server awaiting client connection"; - auto hPipe = CreateNamedPipe( fullPipeName.c_str(), // pipe name PIPE_ACCESS_DUPLEX | // read/write access @@ -120,7 +113,7 @@ void ServerWin32::serverListeningLoop() firstInstance = 0; bool stopRunning = false; - auto err_code = waitForClient(hPipe, overlapped.data()); + auto err_code = waitForClient(hPipe, overlapped); if (err_code) { // if has timeout and expires, we'll stop running diff --git a/tools/gfxworker/src/win32/server.h b/tools/gfxworker/src/win32/server.h index 2a089cbb66..7494707d65 100644 --- a/tools/gfxworker/src/win32/server.h +++ b/tools/gfxworker/src/win32/server.h @@ -49,7 +49,7 @@ class ServerWin32 void serverListeningLoop(); - std::error_code waitForClient(HANDLE hPipe, OVERLAPPED* overlapped); + std::error_code waitForClient(HANDLE hPipe, WinOverlapped& overlapped); std::unique_ptr mRequestProcessor;