/* * Copyright (c) 2013-2019 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include #include #include #include #include #include #include using namespace Swift; JingleFileTransfer::JingleFileTransfer( std::shared_ptr session, const JID& target, FileTransferTransporterFactory* transporterFactory) : session(session), target(target), transporterFactory(transporterFactory), transporter(nullptr), ourCandidateSelectFinished(false), theirCandidateSelectFinished(false) { session->addListener(this); } JingleFileTransfer::~JingleFileTransfer() { session->removeListener(this); } void JingleFileTransfer::fillCandidateMap(CandidateMap& map, const std::vector& candidates) { map.clear(); for (auto&& candidate : candidates) { map[candidate.cid] = candidate; } } /* std::string JingleFileTransfer::getS5BDstAddr(const JID& requester, const JID& target) const { return Hexify::hexify(crypto->getSHA1Hash( createSafeByteArray(s5bSessionID + requester.toString() + target.toString()))); } */ const JID& JingleFileTransfer::getInitiator() const { return session->getInitiator(); } const JID& JingleFileTransfer::getResponder() const { return target; } FileTransfer::State::Type JingleFileTransfer::getExternalFinishedState(JinglePayload::Reason::Type reason) { if (reason == JinglePayload::Reason::Cancel || reason == JinglePayload::Reason::Decline) { return FileTransfer::State::Canceled; } else if (reason == JinglePayload::Reason::Success) { return FileTransfer::State::Finished; } else { return FileTransfer::State::Failed; } } boost::optional JingleFileTransfer::getFileTransferError(JinglePayload::Reason::Type reason) { if (reason == JinglePayload::Reason::Success) { return boost::optional(); } else { return boost::optional(FileTransferError::UnknownError); } } void JingleFileTransfer::handleRemoteTransportCandidateSelectFinished( const std::string& s5bSessionID, const boost::optional& candidate) { SWIFT_LOG(debug); ourCandidateChoice = candidate; ourCandidateSelectFinished = true; JingleS5BTransportPayload::ref s5bPayload = std::make_shared(); s5bPayload->setSessionID(s5bSessionID); if (candidate) { s5bPayload->setCandidateUsed(candidate->cid); } else { s5bPayload->setCandidateError(true); } candidateSelectRequestID = session->sendTransportInfo(getContentID(), s5bPayload); decideOnCandidates(); } // decide on candidates according to http://xmpp.org/extensions/xep-0260.html#complete void JingleFileTransfer::decideOnCandidates() { SWIFT_LOG(debug); if (!ourCandidateSelectFinished || !theirCandidateSelectFinished) { SWIFT_LOG(debug) << "Can't make a decision yet!"; return; } if (!ourCandidateChoice && !theirCandidateChoice) { SWIFT_LOG(debug) << "No candidates succeeded."; fallback(); } else if (ourCandidateChoice && !theirCandidateChoice) { SWIFT_LOG(debug) << "Start transfer using remote candidate: " << ourCandidateChoice.get().cid << "."; startTransferViaRemoteCandidate(); } else if (theirCandidateChoice && !ourCandidateChoice) { SWIFT_LOG(debug) << "Start transfer using local candidate: " << theirCandidateChoice.get().cid << "."; startTransferViaLocalCandidate(); } else { SWIFT_LOG(debug) << "Choosing between candidates " << ourCandidateChoice->cid << "(" << ourCandidateChoice->priority << ")" << " and " << theirCandidateChoice->cid << "(" << theirCandidateChoice->priority << ")"; if (ourCandidateChoice->priority > theirCandidateChoice->priority) { SWIFT_LOG(debug) << "Start transfer using remote candidate: " << ourCandidateChoice.get().cid << "."; startTransferViaRemoteCandidate(); } else if (ourCandidateChoice->priority < theirCandidateChoice->priority) { SWIFT_LOG(debug) << "Start transfer using local candidate:" << theirCandidateChoice.get().cid << "."; startTransferViaLocalCandidate(); } else { if (hasPriorityOnCandidateTie()) { SWIFT_LOG(debug) << "Start transfer using remote candidate: " << ourCandidateChoice.get().cid; startTransferViaRemoteCandidate(); } else { SWIFT_LOG(debug) << "Start transfer using local candidate: " << theirCandidateChoice.get().cid; startTransferViaLocalCandidate(); } } } } void JingleFileTransfer::handleProxyActivateFinished( const std::string& s5bSessionID, ErrorPayload::ref error) { SWIFT_LOG(debug); if (!isWaitingForLocalProxyActivate()) { SWIFT_LOG(warning) << "Incorrect state"; return; } if (error) { SWIFT_LOG(debug) << "Error activating proxy"; JingleS5BTransportPayload::ref proxyError = std::make_shared(); proxyError->setSessionID(s5bSessionID); proxyError->setProxyError(true); session->sendTransportInfo(getContentID(), proxyError); fallback(); } else { JingleS5BTransportPayload::ref proxyActivate = std::make_shared(); proxyActivate->setSessionID(s5bSessionID); proxyActivate->setActivated(theirCandidateChoice->cid); session->sendTransportInfo(getContentID(), proxyActivate); startTransferring(createLocalCandidateSession()); } } void JingleFileTransfer::handleTransportInfoReceived( const JingleContentID& /* contentID */, JingleTransportPayload::ref transport) { SWIFT_LOG(debug); if (JingleS5BTransportPayload::ref s5bPayload = std::dynamic_pointer_cast(transport)) { if (s5bPayload->hasCandidateError() || !s5bPayload->getCandidateUsed().empty()) { SWIFT_LOG(debug) << "Received candidate decision from peer"; if (!isTryingCandidates()) { SWIFT_LOG(warning) << "Incorrect state"; return; } theirCandidateSelectFinished = true; if (!s5bPayload->hasCandidateError()) { auto theirCandidate = localCandidates.find(s5bPayload->getCandidateUsed()); if (theirCandidate == localCandidates.end()) { SWIFT_LOG(warning) << "Got invalid candidate"; terminate(JinglePayload::Reason::GeneralError); return; } theirCandidateChoice = theirCandidate->second; } decideOnCandidates(); } else if (!s5bPayload->getActivated().empty()) { SWIFT_LOG(debug) << "Received peer activate from peer"; if (!isWaitingForPeerProxyActivate()) { SWIFT_LOG(warning) << "Incorrect state"; return; } if (ourCandidateChoice->cid == s5bPayload->getActivated()) { startTransferring(createRemoteCandidateSession()); } else { SWIFT_LOG(warning) << "ourCandidateChoice doesn't match activated proxy candidate!"; terminate(JinglePayload::Reason::GeneralError); } } else if (s5bPayload->hasProxyError()) { SWIFT_LOG(debug) << "Received proxy error. Trying to fall back to IBB."; fallback(); } else { SWIFT_LOG(debug) << "Ignoring unknown info"; } } else { SWIFT_LOG(debug) << "Ignoring unknown info"; } } void JingleFileTransfer::setTransporter(FileTransferTransporter* transporter) { SWIFT_LOG_ASSERT(!this->transporter, error); this->transporter = transporter; localTransportCandidatesGeneratedConnection = transporter->onLocalCandidatesGenerated.connect( boost::bind(&JingleFileTransfer::handleLocalTransportCandidatesGenerated, this, _1, _2, _3)); remoteTransportCandidateSelectFinishedConnection = transporter->onRemoteCandidateSelectFinished.connect( boost::bind(&JingleFileTransfer::handleRemoteTransportCandidateSelectFinished, this, _1, _2)); proxyActivatedConnection = transporter->onProxyActivated.connect( boost::bind(&JingleFileTransfer::handleProxyActivateFinished, this, _1, _2)); } void JingleFileTransfer::removeTransporter() { if (transporter) { localTransportCandidatesGeneratedConnection.release(); remoteTransportCandidateSelectFinishedConnection.release(); proxyActivatedConnection.release(); delete transporter; transporter = nullptr; } }