/* * Copyright (c) 2011 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swiften/FileTransfer/IncomingJingleFileTransfer.h> #include <boost/bind.hpp> #include <boost/smart_ptr/make_shared.hpp> #include <Swiften/Base/Log.h> #include <Swiften/Base/foreach.h> #include <Swiften/Elements/JingleIBBTransportPayload.h> #include <Swiften/Elements/JingleS5BTransportPayload.h> #include <Swiften/Elements/JingleFileTransferHash.h> #include <Swiften/Elements/S5BProxyRequest.h> #include <Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h> #include <Swiften/FileTransfer/JingleIncomingIBBTransport.h> #include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> #include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> #include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> #include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> #include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> #include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> #include <Swiften/Network/TimerFactory.h> #include <Swiften/Queries/GenericRequest.h> namespace Swift { IncomingJingleFileTransfer::IncomingJingleFileTransfer( const JID& ourJID, JingleSession::ref session, JingleContentPayload::ref content, RemoteJingleTransportCandidateSelectorFactory* candidateSelectorFactory, LocalJingleTransportCandidateGeneratorFactory* candidateGeneratorFactory, IQRouter* router, SOCKS5BytestreamRegistry* registry, SOCKS5BytestreamProxy* proxy, TimerFactory* timerFactory) : ourJID(ourJID), session(session), router(router), initialContent(content), state(Initial), receivedBytes(0), s5bRegistry(registry), s5bProxy(proxy), remoteTransportCandidateSelectFinished(false), localTransportCandidateSelectFinished(false), serverSession(0) { candidateSelector = candidateSelectorFactory->createCandidateSelector(); candidateSelector->onRemoteTransportCandidateSelectFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished, this, _1)); candidateGenerator = candidateGeneratorFactory->createCandidateGenerator(); candidateGenerator->onLocalTransportCandidatesGenerated.connect(boost::bind(&IncomingJingleFileTransfer::handleLocalTransportCandidatesGenerated, this, _1)); session->onTransportInfoReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleTransportInfoReceived, this, _1, _2)); session->onTransportReplaceReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleTransportReplaceReceived, this, _1, _2)); session->onSessionTerminateReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleSessionTerminateReceived, this, _1)); session->onSessionInfoReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleSessionInfoReceived, this, _1)); description = initialContent->getDescription<JingleFileTransferDescription>(); assert(description); assert(description->getOffers().size() == 1); StreamInitiationFileInfo fileInfo = description->getOffers().front(); fileSizeInBytes = fileInfo.getSize(); filename = fileInfo.getName(); hash = fileInfo.getHash(); algo = fileInfo.getAlgo(); waitOnHashTimer = timerFactory->createTimer(5000); waitOnHashTimer->onTick.connect(boost::bind(&IncomingJingleFileTransfer::finishOffTransfer, this)); } IncomingJingleFileTransfer::~IncomingJingleFileTransfer() { stream->onWrite.disconnect(boost::bind(&IncrementalBytestreamHashCalculator::feedData, hashCalculator, _1)); delete hashCalculator; session->onSessionTerminateReceived.disconnect(boost::bind(&IncomingJingleFileTransfer::handleSessionTerminateReceived, this, _1)); session->onTransportReplaceReceived.disconnect(boost::bind(&IncomingJingleFileTransfer::handleTransportReplaceReceived, this, _1, _2)); session->onTransportInfoReceived.disconnect(boost::bind(&IncomingJingleFileTransfer::handleTransportInfoReceived, this, _1, _2)); candidateGenerator->onLocalTransportCandidatesGenerated.disconnect(boost::bind(&IncomingJingleFileTransfer::handleLocalTransportCandidatesGenerated, this, _1)); delete candidateGenerator; candidateSelector->onRemoteTransportCandidateSelectFinished.disconnect(boost::bind(&IncomingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished, this, _1)); delete candidateSelector; } void IncomingJingleFileTransfer::accept(WriteBytestream::ref stream) { assert(!this->stream); this->stream = stream; hashCalculator = new IncrementalBytestreamHashCalculator( algo == "md5" || hash.empty() , algo == "sha-1" || hash.empty() ); stream->onWrite.connect(boost::bind(&IncrementalBytestreamHashCalculator::feedData, hashCalculator, _1)); stream->onWrite.connect(boost::bind(&IncomingJingleFileTransfer::handleWriteStreamDataReceived, this, _1)); onStateChange(FileTransfer::State(FileTransfer::State::Negotiating)); if (JingleIBBTransportPayload::ref ibbTransport = initialContent->getTransport<JingleIBBTransportPayload>()) { SWIFT_LOG(debug) << "Got IBB transport payload!" << std::endl; setActiveTransport(createIBBTransport(ibbTransport)); session->sendAccept(getContentID(), initialContent->getDescriptions()[0], ibbTransport); } else if (JingleS5BTransportPayload::ref s5bTransport = initialContent->getTransport<JingleS5BTransportPayload>()) { SWIFT_LOG(debug) << "Got S5B transport payload!" << std::endl; state = CreatingInitialTransports; s5bSessionID = s5bTransport->getSessionID().empty() ? idGenerator.generateID() : s5bTransport->getSessionID(); s5bDestination = SOCKS5BytestreamRegistry::getHostname(s5bSessionID, ourJID, session->getInitiator()); s5bRegistry->addWriteBytestream(s5bDestination, stream); fillCandidateMap(theirCandidates, s5bTransport); candidateSelector->addRemoteTransportCandidates(s5bTransport); candidateSelector->setRequesterTargtet(session->getInitiator(), ourJID); s5bTransport->setSessionID(s5bSessionID); candidateGenerator->generateLocalTransportCandidates(s5bTransport); } else { assert(false); } } const JID& IncomingJingleFileTransfer::getSender() const { return session->getInitiator(); } const JID& IncomingJingleFileTransfer::getRecipient() const { return ourJID; } void IncomingJingleFileTransfer::cancel() { session->sendTerminate(JinglePayload::Reason::Cancel); if (activeTransport) activeTransport->stop(); if (serverSession) serverSession->stop(); if (clientSession) clientSession->stop(); onStateChange(FileTransfer::State(FileTransfer::State::Canceled)); } void IncomingJingleFileTransfer::handleLocalTransportCandidatesGenerated(JingleTransportPayload::ref candidates) { if (state == CreatingInitialTransports) { if (JingleS5BTransportPayload::ref s5bCandidates = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(candidates)) { //localTransportCandidateSelectFinished = true; //JingleS5BTransportPayload::ref emptyCandidates = boost::make_shared<JingleS5BTransportPayload>(); //emptyCandidates->setSessionID(s5bCandidates->getSessionID()); fillCandidateMap(ourCandidates, s5bCandidates); session->sendAccept(getContentID(), initialContent->getDescriptions()[0], s5bCandidates); state = NegotiatingTransport; candidateSelector->selectCandidate(); } } else { SWIFT_LOG(debug) << "Unhandled state!" << std::endl; } } void IncomingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished(JingleTransportPayload::ref transport) { SWIFT_LOG(debug) << std::endl; if (state == Terminated) { return; } if (JingleS5BTransportPayload::ref s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transport)) { //remoteTransportCandidateSelectFinished = true; //selectedRemoteTransportCandidate = transport; ourCandidate = s5bPayload; //checkCandidateSelected(); decideOnUsedTransport(); session->sendTransportInfo(getContentID(), s5bPayload); } else { SWIFT_LOG(debug) << "Expected something different here." << std::endl; } } void IncomingJingleFileTransfer::checkCandidateSelected() { assert(false); if (localTransportCandidateSelectFinished && remoteTransportCandidateSelectFinished) { if (candidateGenerator->isActualCandidate(selectedLocalTransportCandidate) && candidateSelector->isActualCandidate(selectedRemoteTransportCandidate)) { if (candidateGenerator->getPriority(selectedLocalTransportCandidate) > candidateSelector->getPriority(selectedRemoteTransportCandidate)) { setActiveTransport(candidateGenerator->selectTransport(selectedLocalTransportCandidate)); } else { setActiveTransport(candidateSelector->selectTransport(selectedRemoteTransportCandidate)); } } else if (candidateSelector->isActualCandidate(selectedRemoteTransportCandidate)) { setActiveTransport(candidateSelector->selectTransport(selectedRemoteTransportCandidate)); } else if (candidateGenerator->isActualCandidate(selectedLocalTransportCandidate)) { setActiveTransport(candidateGenerator->selectTransport(selectedLocalTransportCandidate)); } else { state = WaitingForFallbackOrTerminate; } } } void IncomingJingleFileTransfer::setActiveTransport(JingleTransport::ref transport) { state = Transferring; onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); activeTransport = transport; activeTransport->onDataReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleTransportDataReceived, this, _1)); activeTransport->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); activeTransport->start(); } bool IncomingJingleFileTransfer::verifyReceviedData() { if (algo.empty() || hash.empty()) { SWIFT_LOG(debug) << "no verification possible, skipping" << std::endl; return true; } else { if (algo == "sha-1") { SWIFT_LOG(debug) << "verify data via SHA-1 hash: " << (hash == hashCalculator->getSHA1String()) << std::endl; return hash == hashCalculator->getSHA1String(); } else if (algo == "md5") { SWIFT_LOG(debug) << "verify data via MD5 hash: " << (hash == hashCalculator->getMD5String()) << std::endl; return hash == hashCalculator->getMD5String(); } else { SWIFT_LOG(debug) << "no verification possible, skipping" << std::endl; return true; } } } void IncomingJingleFileTransfer::finishOffTransfer() { if (verifyReceviedData()) { onStateChange(FileTransfer::State(FileTransfer::State::Finished)); session->sendTerminate(JinglePayload::Reason::Success); } else { onStateChange(FileTransfer::State(FileTransfer::State::Failed, "Verification failed.")); session->sendTerminate(JinglePayload::Reason::MediaError); } state = Terminated; waitOnHashTimer->stop(); } void IncomingJingleFileTransfer::handleSessionInfoReceived(JinglePayload::ref jinglePayload) { if (state == Terminated) { return; } JingleFileTransferHash::ref transferHash = jinglePayload->getPayload<JingleFileTransferHash>(); if (transferHash) { SWIFT_LOG(debug) << "Recevied hash information." << std::endl; if (transferHash->getHashes().find("sha-1") != transferHash->getHashes().end()) { algo = "sha-1"; hash = transferHash->getHashes().find("sha-1")->second; } else if (transferHash->getHashes().find("md5") != transferHash->getHashes().end()) { algo = "md5"; hash = transferHash->getHashes().find("md5")->second; } checkIfAllDataReceived(); } } void IncomingJingleFileTransfer::handleSessionTerminateReceived(boost::optional<JinglePayload::Reason> reason) { SWIFT_LOG(debug) << "session terminate received" << std::endl; if (activeTransport) activeTransport->stop(); if (reason && reason.get().type == JinglePayload::Reason::Cancel) { onStateChange(FileTransfer::State(FileTransfer::State::Canceled, "Other user canceled the transfer.")); } else if (reason && reason.get().type == JinglePayload::Reason::Success) { /*if (verifyReceviedData()) { onStateChange(FileTransfer::State(FileTransfer::State::Finished)); } else { onStateChange(FileTransfer::State(FileTransfer::State::Failed, "Verification failed.")); }*/ } state = Terminated; } void IncomingJingleFileTransfer::checkIfAllDataReceived() { if (receivedBytes == fileSizeInBytes) { SWIFT_LOG(debug) << "All data received." << std::endl; if (hash.empty()) { SWIFT_LOG(debug) << "No hash information yet. Waiting 5 seconds on hash info." << std::endl; waitOnHashTimer->start(); } else { SWIFT_LOG(debug) << "We already have hash info using " << algo << " algorithm. Finishing off transfer." << std::endl; finishOffTransfer(); } } else if (receivedBytes > fileSizeInBytes) { SWIFT_LOG(debug) << "We got more than we could handle!" << std::endl; } } void IncomingJingleFileTransfer::handleTransportDataReceived(const std::vector<unsigned char>& data) { SWIFT_LOG(debug) << data.size() << " bytes received" << std::endl; onProcessedBytes(data.size()); stream->write(data); receivedBytes += data.size(); checkIfAllDataReceived(); } void IncomingJingleFileTransfer::handleWriteStreamDataReceived(const std::vector<unsigned char>& data) { receivedBytes += data.size(); checkIfAllDataReceived(); } void IncomingJingleFileTransfer::useOurCandidateChoiceForTransfer(JingleS5BTransportPayload::Candidate candidate) { SWIFT_LOG(debug) << std::endl; if (candidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { // get proxy client session from remoteCandidateSelector clientSession = candidateSelector->getS5BSession(); // wait on <activated/> transport-info } else { // ask s5b client clientSession = candidateSelector->getS5BSession(); if (clientSession) { state = Transferring; SWIFT_LOG(debug) << clientSession->getAddressPort().toString() << std::endl; clientSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); clientSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); clientSession->startReceiving(stream); } else { SWIFT_LOG(debug) << "No S5B client session found!!!" << std::endl; } } } void IncomingJingleFileTransfer::useTheirCandidateChoiceForTransfer(JingleS5BTransportPayload::Candidate candidate) { SWIFT_LOG(debug) << std::endl; if (candidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { // get proxy client session from s5bRegistry clientSession = s5bProxy->createSOCKS5BytestreamClientSession(candidate.hostPort, SOCKS5BytestreamRegistry::getHostname(s5bSessionID, ourJID, session->getInitiator())); clientSession->onSessionReady.connect(boost::bind(&IncomingJingleFileTransfer::proxySessionReady, this, candidate.jid, _1)); clientSession->start(); // on reply send activate } else { // ask s5b server serverSession = s5bRegistry->getConnectedSession(s5bDestination); if (serverSession) { state = Transferring; serverSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); serverSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); serverSession->startTransfer(); } else { SWIFT_LOG(debug) << "No S5B server session found!!!" << std::endl; } } } void IncomingJingleFileTransfer::fillCandidateMap(CandidateMap& map, JingleS5BTransportPayload::ref s5bPayload) { map.clear(); foreach (JingleS5BTransportPayload::Candidate candidate, s5bPayload->getCandidates()) { map[candidate.cid] = candidate; } } void IncomingJingleFileTransfer::decideOnUsedTransport() { if (ourCandidate && theirCandidate) { if (ourCandidate->hasCandidateError() && theirCandidate->hasCandidateError()) { state = WaitingForFallbackOrTerminate; return; } std::string our_cid = ourCandidate->getCandidateUsed(); std::string their_cid = theirCandidate->getCandidateUsed(); if (ourCandidate->hasCandidateError() && !their_cid.empty()) { useTheirCandidateChoiceForTransfer(ourCandidates[their_cid]); onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); } else if (theirCandidate->hasCandidateError() && !our_cid.empty()) { useOurCandidateChoiceForTransfer(theirCandidates[our_cid]); onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); } else if (!our_cid.empty() && !their_cid.empty()) { // compare priorites, if same they win if (ourCandidates.find(their_cid) == ourCandidates.end() || theirCandidates.find(our_cid) == theirCandidates.end()) { SWIFT_LOG(debug) << "Didn't recognize candidate IDs!" << std::endl; session->sendTerminate(JinglePayload::Reason::FailedTransport); onStateChange(FileTransfer::State(FileTransfer::State::Canceled, "Failed to negotiate candidate.")); onFinished(FileTransferError(FileTransferError::PeerError)); return; } JingleS5BTransportPayload::Candidate our_candidate = theirCandidates[our_cid]; JingleS5BTransportPayload::Candidate their_candidate = ourCandidates[their_cid]; if (our_candidate.priority > their_candidate.priority) { useOurCandidateChoiceForTransfer(our_candidate); } else if (our_candidate.priority < their_candidate.priority) { useTheirCandidateChoiceForTransfer(their_candidate); } else { useTheirCandidateChoiceForTransfer(their_candidate); } onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); } else { assert(false); } } else { SWIFT_LOG(debug) << "Can't make a transport decision yet." << std::endl; } } void IncomingJingleFileTransfer::proxySessionReady(const JID& proxy, bool error) { if (error) { // indicate proxy error } else { // activate proxy activateProxySession(proxy); } } void IncomingJingleFileTransfer::activateProxySession(const JID &proxy) { S5BProxyRequest::ref proxyRequest = boost::make_shared<S5BProxyRequest>(); proxyRequest->setSID(s5bSessionID); proxyRequest->setActivate(session->getInitiator()); boost::shared_ptr<GenericRequest<S5BProxyRequest> > request = boost::make_shared<GenericRequest<S5BProxyRequest> >(IQ::Set, proxy, proxyRequest, router); request->onResponse.connect(boost::bind(&IncomingJingleFileTransfer::handleActivateProxySessionResult, this, _1, _2)); request->send(); } void IncomingJingleFileTransfer::handleActivateProxySessionResult(boost::shared_ptr<S5BProxyRequest> /*request*/, ErrorPayload::ref error) { SWIFT_LOG(debug) << std::endl; if (error) { SWIFT_LOG(debug) << "ERROR" << std::endl; } else { // send activated to other jingle party JingleS5BTransportPayload::ref proxyActivate = boost::make_shared<JingleS5BTransportPayload>(); proxyActivate->setActivated(theirCandidate->getCandidateUsed()); session->sendTransportInfo(getContentID(), proxyActivate); // start transferring clientSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); clientSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); clientSession->startReceiving(stream); onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); } } void IncomingJingleFileTransfer::handleTransportInfoReceived(const JingleContentID&, JingleTransportPayload::ref transport) { SWIFT_LOG(debug) << "transport info received" << std::endl; if (state == Terminated) { return; } if (JingleS5BTransportPayload::ref s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transport)) { if (!s5bPayload->getActivated().empty()) { if (ourCandidate->getCandidateUsed() == s5bPayload->getActivated()) { clientSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); clientSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); clientSession->startReceiving(stream); onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); } else { SWIFT_LOG(debug) << "ourCandidateChoice doesn't match activated proxy candidate!" << std::endl; JingleS5BTransportPayload::ref proxyError = boost::make_shared<JingleS5BTransportPayload>(); proxyError->setProxyError(true); proxyError->setSessionID(s5bSessionID); session->sendTransportInfo(getContentID(), proxyError); } } else { theirCandidate = s5bPayload; decideOnUsedTransport(); } } else { SWIFT_LOG(debug) << "Expected something different here." << std::endl; } /*localTransportCandidateSelectFinished = true; selectedLocalTransportCandidate = transport; if (candidateGenerator->isActualCandidate(transport)) { candidateSelector->setMinimumPriority(candidateGenerator->getPriority(transport)); }*/ //checkCandidateSelected(); } void IncomingJingleFileTransfer::handleTransportReplaceReceived(const JingleContentID& content, JingleTransportPayload::ref transport) { if (state == Terminated) { return; } if (JingleIBBTransportPayload::ref ibbTransport = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transport)) { SWIFT_LOG(debug) << "transport replaced with IBB" << std::endl; setActiveTransport(createIBBTransport(ibbTransport)); session->sendTransportAccept(content, ibbTransport); } else { SWIFT_LOG(debug) << "transport replaced failed" << std::endl; session->sendTransportReject(content, transport); } } void IncomingJingleFileTransfer::stopActiveTransport() { if (activeTransport) { activeTransport->stop(); activeTransport->onDataReceived.disconnect(boost::bind(&IncomingJingleFileTransfer::handleTransportDataReceived, this, _1)); } } JingleIncomingIBBTransport::ref IncomingJingleFileTransfer::createIBBTransport(JingleIBBTransportPayload::ref ibbTransport) { // TODO: getOffer() -> getOffers correction return boost::make_shared<JingleIncomingIBBTransport>(session->getInitiator(), getRecipient(), ibbTransport->getSessionID(), description->getOffers()[0].getSize(), router); } JingleContentID IncomingJingleFileTransfer::getContentID() const { return JingleContentID(initialContent->getName(), initialContent->getCreator()); } void IncomingJingleFileTransfer::handleTransferFinished(boost::optional<FileTransferError> error) { if (state == Terminated) { return; } if (error) { session->sendTerminate(JinglePayload::Reason::ConnectivityError); onStateChange(FileTransfer::State(FileTransfer::State::Failed)); onFinished(error); } // } }