/* * Copyright (c) 2011-2013 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/Jingle/JingleSession.h> #include <Swiften/Elements/JingleIBBTransportPayload.h> #include <Swiften/Elements/JingleS5BTransportPayload.h> #include <Swiften/Elements/JingleFileTransferHash.h> #include <Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h> #include <Swiften/FileTransfer/FileTransferTransporter.h> #include <Swiften/FileTransfer/FileTransferTransporterFactory.h> #include <Swiften/FileTransfer/WriteBytestream.h> #include <Swiften/Elements/JingleFileTransferDescription.h> #include <Swiften/Network/TimerFactory.h> #include <Swiften/Queries/GenericRequest.h> #include <Swiften/FileTransfer/TransportSession.h> using namespace Swift; // TODO: ALlow terminate when already terminated. IncomingJingleFileTransfer::IncomingJingleFileTransfer( const JID& toJID, JingleSession::ref session, JingleContentPayload::ref content, FileTransferTransporterFactory* transporterFactory, TimerFactory* timerFactory, CryptoProvider* crypto) : JingleFileTransfer(session, toJID, transporterFactory), initialContent(content), crypto(crypto), state(Initial), receivedBytes(0), hashCalculator(NULL) { description = initialContent->getDescription<JingleFileTransferDescription>(); assert(description); assert(description->getOffers().size() == 1); StreamInitiationFileInfo fileInfo = description->getOffers().front(); setFileInfo(fileInfo.getName(), fileInfo.getSize()); hash = fileInfo.getHash(); hashAlgorithm = fileInfo.getAlgo(); waitOnHashTimer = timerFactory->createTimer(5000); waitOnHashTimerTickedConnection = waitOnHashTimer->onTick.connect( boost::bind(&IncomingJingleFileTransfer::handleWaitOnHashTimerTicked, this)); } IncomingJingleFileTransfer::~IncomingJingleFileTransfer() { } void IncomingJingleFileTransfer::accept( boost::shared_ptr<WriteBytestream> stream, const FileTransferOptions& options) { SWIFT_LOG(debug) << std::endl; if (state != Initial) { SWIFT_LOG(warning) << "Incorrect state" << std::endl; return; } assert(!this->stream); this->stream = stream; this->options = options; assert(!hashCalculator); hashCalculator = new IncrementalBytestreamHashCalculator( hashAlgorithm == "md5" || hash.empty(), hashAlgorithm == "sha-1" || hash.empty(), crypto); writeStreamDataReceivedConnection = stream->onWrite.connect( boost::bind(&IncomingJingleFileTransfer::handleWriteStreamDataReceived, this, _1)); if (JingleS5BTransportPayload::ref s5bTransport = initialContent->getTransport<JingleS5BTransportPayload>()) { SWIFT_LOG(debug) << "Got S5B transport payload!" << std::endl; setTransporter(transporterFactory->createResponderTransporter( getInitiator(), getResponder(), s5bTransport->getSessionID())); transporter->addRemoteCandidates(s5bTransport->getCandidates()); setState(GeneratingInitialLocalCandidates); transporter->startGeneratingLocalCandidates(); } else { // Can't happen, because the transfer would have been rejected automatically assert(false); } } void IncomingJingleFileTransfer::cancel() { SWIFT_LOG(debug) << std::endl; terminate(state == Initial ? JinglePayload::Reason::Decline : JinglePayload::Reason::Cancel); } void IncomingJingleFileTransfer::handleLocalTransportCandidatesGenerated( const std::string& s5bSessionID, const std::vector<JingleS5BTransportPayload::Candidate>& candidates) { SWIFT_LOG(debug) << std::endl; if (state != GeneratingInitialLocalCandidates) { SWIFT_LOG(warning) << "Incorrect state" << std::endl; return; } fillCandidateMap(localCandidates, candidates); JingleS5BTransportPayload::ref transport = boost::make_shared<JingleS5BTransportPayload>(); transport->setSessionID(s5bSessionID); transport->setMode(JingleS5BTransportPayload::TCPMode); foreach(JingleS5BTransportPayload::Candidate candidate, candidates) { transport->addCandidate(candidate); } session->sendAccept(getContentID(), initialContent->getDescriptions()[0], transport); setState(TryingCandidates); transporter->startTryingRemoteCandidates(); } void IncomingJingleFileTransfer::handleSessionInfoReceived(JinglePayload::ref jinglePayload) { SWIFT_LOG(debug) << std::endl; JingleFileTransferHash::ref transferHash = jinglePayload->getPayload<JingleFileTransferHash>(); if (transferHash) { SWIFT_LOG(debug) << "Received hash information." << std::endl; waitOnHashTimer->stop(); if (transferHash->getHashes().find("sha-1") != transferHash->getHashes().end()) { hashAlgorithm = "sha-1"; hash = transferHash->getHashes().find("sha-1")->second; } else if (transferHash->getHashes().find("md5") != transferHash->getHashes().end()) { hashAlgorithm = "md5"; hash = transferHash->getHashes().find("md5")->second; } if (state == WaitingForHash) { checkHashAndTerminate(); } } else { SWIFT_LOG(debug) << "Ignoring unknown session info" << std::endl; } } void IncomingJingleFileTransfer::handleSessionTerminateReceived(boost::optional<JinglePayload::Reason> reason) { SWIFT_LOG(debug) << std::endl; if (state == Finished) { SWIFT_LOG(warning) << "Incorrect state" << std::endl; return; } if (state == Finished) { SWIFT_LOG(debug) << "Already terminated" << std::endl; return; } stopAll(); if (reason && reason->type == JinglePayload::Reason::Cancel) { setFinishedState(FileTransfer::State::Canceled, FileTransferError(FileTransferError::PeerError)); } else if (reason && reason->type == JinglePayload::Reason::Success) { setFinishedState(FileTransfer::State::Finished, boost::optional<FileTransferError>()); } else { setFinishedState(FileTransfer::State::Failed, FileTransferError(FileTransferError::PeerError)); } } void IncomingJingleFileTransfer::checkHashAndTerminate() { if (verifyData()) { terminate(JinglePayload::Reason::Success); } else { SWIFT_LOG(warning) << "Hash verification failed" << std::endl; terminate(JinglePayload::Reason::MediaError); } } void IncomingJingleFileTransfer::checkIfAllDataReceived() { if (receivedBytes == getFileSizeInBytes()) { SWIFT_LOG(debug) << "All data received." << std::endl; if (hash.empty()) { SWIFT_LOG(debug) << "No hash information yet. Waiting a while on hash info." << std::endl; setState(WaitingForHash); waitOnHashTimer->start(); } else { checkHashAndTerminate(); } } else if (receivedBytes > getFileSizeInBytes()) { SWIFT_LOG(debug) << "We got more than we could handle!" << std::endl; terminate(JinglePayload::Reason::MediaError); } } void IncomingJingleFileTransfer::handleWriteStreamDataReceived( const std::vector<unsigned char>& data) { hashCalculator->feedData(data); receivedBytes += data.size(); checkIfAllDataReceived(); } void IncomingJingleFileTransfer::handleTransportReplaceReceived( const JingleContentID& content, JingleTransportPayload::ref transport) { SWIFT_LOG(debug) << std::endl; if (state != WaitingForFallbackOrTerminate) { SWIFT_LOG(warning) << "Incorrect state" << std::endl; return; } if (JingleIBBTransportPayload::ref ibbTransport = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transport)) { SWIFT_LOG(debug) << "transport replaced with IBB" << std::endl; startTransferring(transporter->createIBBReceiveSession( ibbTransport->getSessionID(), description->getOffers()[0].getSize(), stream)); session->sendTransportAccept(content, ibbTransport); } else { SWIFT_LOG(debug) << "Unknown replace transport" << std::endl; session->sendTransportReject(content, transport); } } JingleContentID IncomingJingleFileTransfer::getContentID() const { return JingleContentID(initialContent->getName(), initialContent->getCreator()); } bool IncomingJingleFileTransfer::verifyData() { if (hashAlgorithm.empty() || hash.empty()) { SWIFT_LOG(debug) << "no verification possible, skipping" << std::endl; return true; } if (hashAlgorithm == "sha-1") { SWIFT_LOG(debug) << "Verify SHA-1 hash: " << (hash == hashCalculator->getSHA1String()) << std::endl; return hash == hashCalculator->getSHA1String(); } else if (hashAlgorithm == "md5") { SWIFT_LOG(debug) << "Verify MD5 hash: " << (hash == hashCalculator->getMD5String()) << std::endl; return hash == hashCalculator->getMD5String(); } else { SWIFT_LOG(debug) << "Unknown hash, skipping" << std::endl; return true; } } void IncomingJingleFileTransfer::handleWaitOnHashTimerTicked() { SWIFT_LOG(debug) << std::endl; waitOnHashTimer->stop(); terminate(JinglePayload::Reason::Success); } const JID& IncomingJingleFileTransfer::getSender() const { return getInitiator(); } const JID& IncomingJingleFileTransfer::getRecipient() const { return getResponder(); } void IncomingJingleFileTransfer::setState(State state) { SWIFT_LOG(debug) << state << std::endl; this->state = state; onStateChanged(FileTransfer::State(getExternalState(state))); } void IncomingJingleFileTransfer::setFinishedState( FileTransfer::State::Type type, const boost::optional<FileTransferError>& error) { SWIFT_LOG(debug) << std::endl; this->state = Finished; onStateChanged(type); onFinished(error); } void IncomingJingleFileTransfer::handleTransferFinished(boost::optional<FileTransferError> error) { if (error && state != WaitingForHash) { terminate(JinglePayload::Reason::MediaError); } } FileTransfer::State::Type IncomingJingleFileTransfer::getExternalState(State state) { switch (state) { case Initial: return FileTransfer::State::Initial; case GeneratingInitialLocalCandidates: return FileTransfer::State::WaitingForStart; case TryingCandidates: return FileTransfer::State::Negotiating; case WaitingForPeerProxyActivate: return FileTransfer::State::Negotiating; case WaitingForLocalProxyActivate: return FileTransfer::State::Negotiating; case WaitingForFallbackOrTerminate: return FileTransfer::State::Negotiating; case Transferring: return FileTransfer::State::Transferring; case WaitingForHash: return FileTransfer::State::Transferring; case Finished: return FileTransfer::State::Finished; } assert(false); return FileTransfer::State::Initial; } void IncomingJingleFileTransfer::stopAll() { if (state != Initial) { writeStreamDataReceivedConnection.disconnect(); delete hashCalculator; } switch (state) { case Initial: break; case GeneratingInitialLocalCandidates: transporter->stopGeneratingLocalCandidates(); break; case TryingCandidates: transporter->stopTryingRemoteCandidates(); break; case WaitingForFallbackOrTerminate: break; case WaitingForPeerProxyActivate: break; case WaitingForLocalProxyActivate: transporter->stopActivatingProxy(); break; case WaitingForHash: // Fallthrough case Transferring: assert(transportSession); transferFinishedConnection.disconnect(); transportSession->stop(); transportSession.reset(); break; case Finished: SWIFT_LOG(warning) << "Already finished" << std::endl; break; } if (state != Initial) { delete transporter; } } bool IncomingJingleFileTransfer::hasPriorityOnCandidateTie() const { return false; } void IncomingJingleFileTransfer::fallback() { if (options.isInBandAllowed()) { setState(WaitingForFallbackOrTerminate); } else { terminate(JinglePayload::Reason::ConnectivityError); } } void IncomingJingleFileTransfer::startTransferViaRemoteCandidate() { SWIFT_LOG(debug) << std::endl; if (ourCandidateChoice->type == JingleS5BTransportPayload::Candidate::ProxyType) { setState(WaitingForPeerProxyActivate); } else { startTransferring(createRemoteCandidateSession()); } } void IncomingJingleFileTransfer::startTransferViaLocalCandidate() { SWIFT_LOG(debug) << std::endl; if (theirCandidateChoice->type == JingleS5BTransportPayload::Candidate::ProxyType) { setState(WaitingForLocalProxyActivate); transporter->startActivatingProxy(theirCandidateChoice->jid); } else { startTransferring(createLocalCandidateSession()); } } void IncomingJingleFileTransfer::startTransferring(boost::shared_ptr<TransportSession> transportSession) { SWIFT_LOG(debug) << std::endl; this->transportSession = transportSession; transferFinishedConnection = transportSession->onFinished.connect( boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); setState(Transferring); transportSession->start(); } bool IncomingJingleFileTransfer::isWaitingForPeerProxyActivate() const { return state == WaitingForPeerProxyActivate; } bool IncomingJingleFileTransfer::isWaitingForLocalProxyActivate() const { return state == WaitingForLocalProxyActivate; } bool IncomingJingleFileTransfer::isTryingCandidates() const { return state == TryingCandidates; } boost::shared_ptr<TransportSession> IncomingJingleFileTransfer::createLocalCandidateSession() { return transporter->createLocalCandidateSession(stream); } boost::shared_ptr<TransportSession> IncomingJingleFileTransfer::createRemoteCandidateSession() { return transporter->createRemoteCandidateSession(stream); } void IncomingJingleFileTransfer::terminate(JinglePayload::Reason::Type reason) { SWIFT_LOG(debug) << reason << std::endl; if (state != Finished) { session->sendTerminate(reason); } stopAll(); setFinishedState(getExternalFinishedState(reason), getFileTransferError(reason)); }