// file transfer state machine void TransferSlot::doio(MegaClient* client) { if (!fa) { // this is a pending completion, retry every 200 ms by default retrybt.backoff(2); retrying = true; return transfer->complete(); } retrying = false; if (!tempurl.size()) { return; } dstime backoff = 0; m_off_t p = 0; if (errorcount > 4) { return transfer->failed(API_EFAILED); } for (int i = connections; i--; ) { if (reqs[i]) { switch (reqs[i]->status) { case REQ_INFLIGHT: p += reqs[i]->transferred(client); break; case REQ_SUCCESS: lastdata = Waiter::ds; progresscompleted += reqs[i]->size; if (transfer->type == PUT) { errorcount = 0; transfer->failcount = 0; // completed put transfers are signalled through the // return of the upload token if (reqs[i]->in.size()) { if (reqs[i]->in.size() == NewNode::UPLOADTOKENLEN * 4 / 3) { if (Base64::atob(reqs[i]->in.data(), transfer->ultoken, NewNode::UPLOADTOKENLEN + 1) == NewNode::UPLOADTOKENLEN) { memcpy(transfer->filekey, transfer->key.key, sizeof transfer->key.key); ((int64_t*)transfer->filekey)[2] = transfer->ctriv; ((int64_t*)transfer->filekey)[3] = macsmac(&transfer->chunkmacs); SymmCipher::xorblock(transfer->filekey + SymmCipher::KEYLENGTH, transfer->filekey); return transfer->complete(); } } progresscompleted -= reqs[i]->size; // fail with returned error return transfer->failed((error)atoi(reqs[i]->in.c_str())); } } else { if (reqs[i]->size == reqs[i]->bufpos) { errorcount = 0; transfer->failcount = 0; reqs[i]->finalize(fa, &transfer->key, &transfer->chunkmacs, transfer->ctriv, 0, -1); if (progresscompleted == transfer->size) { // verify meta MAC if (!progresscompleted || (macsmac(&transfer->chunkmacs) == transfer->metamac)) { return transfer->complete(); } else { progresscompleted -= reqs[i]->size; return transfer->failed(API_EKEY); } } } else { progresscompleted -= reqs[i]->size; errorcount++; reqs[i]->status = REQ_PREPARED; break; } } reqs[i]->status = REQ_READY; break; case REQ_FAILURE: if (reqs[i]->httpstatus == 509) { if (reqs[i]->timeleft < 0) { int creqtag = client->reqtag; client->reqtag = 0; client->sendevent(99408, "Overquota without timeleft"); client->reqtag = creqtag; } LOG_warn << "Bandwidth overquota from storage server"; if (reqs[i]->timeleft > 0) { backoff = reqs[i]->timeleft * 10; } else { // default retry intervals backoff = MegaClient::DEFAULT_BW_OVERQUOTA_BACKOFF_SECS * 10; } return transfer->failed(API_EOVERQUOTA, backoff); } else { if (!failure) { failure = true; bool changeport = false; if (transfer->type == GET && client->autodownport && !memcmp(tempurl.c_str(), "http:", 5)) { LOG_debug << "Automatically changing download port"; client->usealtdownport = !client->usealtdownport; changeport = true; } else if (transfer->type == PUT && client->autoupport && !memcmp(tempurl.c_str(), "http:", 5)) { LOG_debug << "Automatically changing upload port"; client->usealtupport = !client->usealtupport; changeport = true; } client->app->transfer_failed(transfer, API_EFAILED); client->setchunkfailed(&reqs[i]->posturl); if (changeport) { toggleport(reqs[i]); } } } reqs[i]->status = REQ_PREPARED; default: ; } } if (!failure) { if (!reqs[i] || (reqs[i]->status == REQ_READY)) { m_off_t npos = ChunkedHash::chunkceil(transfer->pos); if (npos > transfer->size) { npos = transfer->size; } if ((npos > transfer->pos) || !transfer->size) { if (!reqs[i]) { reqs[i] = transfer->type == PUT ? (HttpReqXfer*)new HttpReqUL() : (HttpReqXfer*)new HttpReqDL(); } string finaltempurl = tempurl; if (transfer->type == GET && client->usealtdownport && !memcmp(tempurl.c_str(), "http:", 5)) { size_t index = tempurl.find("/", 8); if(index != string::npos && tempurl.find(":", 8) == string::npos) { finaltempurl.insert(index, ":8080"); } } if (transfer->type == PUT && client->usealtupport && !memcmp(tempurl.c_str(), "http:", 5)) { size_t index = tempurl.find("/", 8); if(index != string::npos && tempurl.find(":", 8) == string::npos) { finaltempurl.insert(index, ":8080"); } } if (reqs[i]->prepare(fa, finaltempurl.c_str(), &transfer->key, &transfer->chunkmacs, transfer->ctriv, transfer->pos, npos)) { reqs[i]->status = REQ_PREPARED; transfer->pos = npos; } else { LOG_warn << "Error preparing transfer: " << fa->retry; if (!fa->retry) { return transfer->failed(API_EREAD); } // retry the read shortly backoff = 2; } } else if (reqs[i]) { reqs[i]->status = REQ_DONE; } } if (reqs[i] && (reqs[i]->status == REQ_PREPARED)) { reqs[i]->post(client); } } } p += progresscompleted; if (p != progressreported) { progressreported = p; lastdata = Waiter::ds; progress(); } if (Waiter::ds - lastdata >= XFERTIMEOUT && !failure) { failure = true; bool changeport = false; if (transfer->type == GET && client->autodownport && !memcmp(tempurl.c_str(), "http:", 5)) { LOG_debug << "Automatically changing download port due to a timeout"; client->usealtdownport = !client->usealtdownport; changeport = true; } else if (transfer->type == PUT && client->autoupport && !memcmp(tempurl.c_str(), "http:", 5)) { LOG_debug << "Automatically changing upload port due to a timeout"; client->usealtupport = !client->usealtupport; changeport = true; } client->app->transfer_failed(transfer, API_EFAILED); for (int i = connections; i--; ) { if (reqs[i] && reqs[i]->status == REQ_INFLIGHT) { client->setchunkfailed(&reqs[i]->posturl); reqs[i]->disconnect(); if (changeport) { toggleport(reqs[i]); } reqs[i]->status = REQ_PREPARED; } } } if (!failure) { if (!backoff && (Waiter::ds - lastdata) < XFERTIMEOUT) { // no other backoff: check again at XFERMAXFAIL backoff = XFERTIMEOUT - (Waiter::ds - lastdata); } retrybt.backoff(backoff); } }
// file transfer state machine void TransferSlot::doio(MegaClient* client) { if (!fa) { // this is a pending completion, retry every 200 ms by default retrybt.backoff(2); retrying = true; return transfer->complete(); } retrying = false; if (!tempurl.size()) { return; } time_t backoff = 0; m_off_t p = 0; if (errorcount > 4) { return transfer->failed(API_EFAILED); } for (int i = connections; i--; ) { if (reqs[i]) { switch (reqs[i]->status) { case REQ_INFLIGHT: p += reqs[i]->transferred(client); break; case REQ_SUCCESS: lastdata = Waiter::ds; progresscompleted += reqs[i]->size; if (transfer->type == PUT) { errorcount = 0; // completed put transfers are signalled through the // return of the upload token if (reqs[i]->in.size()) { if (reqs[i]->in.size() == NewNode::UPLOADTOKENLEN * 4 / 3) { if (Base64::atob(reqs[i]->in.data(), transfer->ultoken, NewNode::UPLOADTOKENLEN + 1) == NewNode::UPLOADTOKENLEN) { memcpy(transfer->filekey, transfer->key.key, sizeof transfer->key.key); ((int64_t*)transfer->filekey)[2] = transfer->ctriv; ((int64_t*)transfer->filekey)[3] = macsmac(&transfer->chunkmacs); SymmCipher::xorblock(transfer->filekey + SymmCipher::KEYLENGTH, transfer->filekey); return transfer->complete(); } } // fail with returned error return transfer->failed((error)atoi(reqs[i]->in.c_str())); } } else { if (reqs[i]->size == reqs[i]->bufpos) { errorcount = 0; reqs[i]->finalize(fa, &transfer->key, &transfer->chunkmacs, transfer->ctriv, 0, -1); if (progresscompleted == transfer->size) { // verify meta MAC if (!progresscompleted || (macsmac(&transfer->chunkmacs) == transfer->metamac)) { return transfer->complete(); } else { return transfer->failed(API_EKEY); } } } else { errorcount++; reqs[i]->status = REQ_PREPARED; break; } } reqs[i]->status = REQ_READY; break; case REQ_FAILURE: if (reqs[i]->httpstatus == 509) { client->app->transfer_limit(transfer); // fixed ten-minute retry intervals backoff = 6000; } else { if (!failure) { failure = true; client->setchunkfailed(&reqs[i]->posturl); } reqs[i]->status = REQ_PREPARED; } default: ; } } if (!failure) { if (!reqs[i] || (reqs[i]->status == REQ_READY)) { m_off_t npos = ChunkedHash::chunkceil(transfer->pos); if (npos > transfer->size) { npos = transfer->size; } if ((npos > transfer->pos) || !transfer->size) { if (!reqs[i]) { reqs[i] = transfer->type == PUT ? (HttpReqXfer*)new HttpReqUL() : (HttpReqXfer*)new HttpReqDL(); } if (reqs[i]->prepare(fa, tempurl.c_str(), &transfer->key, &transfer->chunkmacs, transfer->ctriv, transfer->pos, npos)) { reqs[i]->status = REQ_PREPARED; transfer->pos = npos; } else { if (!fa->retry) { return transfer->failed(API_EREAD); } // retry the read shortly backoff = 2; } } else if (reqs[i]) { reqs[i]->status = REQ_DONE; } } if (reqs[i] && (reqs[i]->status == REQ_PREPARED)) { reqs[i]->post(client); } } } p += progresscompleted; if (p != progressreported) { progressreported = p; lastdata = Waiter::ds; progress(); } if (Waiter::ds - lastdata >= XFERTIMEOUT && !failure) { failure = true; client->app->transfer_failed(transfer, API_EFAILED); for (int i = connections; i--; ) { if (reqs[i]) { client->setchunkfailed(&reqs[i]->posturl); reqs[i]->disconnect(); reqs[i]->status = REQ_PREPARED; } } } if (!failure) { if (!backoff && (Waiter::ds - lastdata) < XFERTIMEOUT) { // no other backoff: check again at XFERMAXFAIL backoff = XFERTIMEOUT - (Waiter::ds - lastdata); } retrybt.backoff(backoff); } }