diff options
Diffstat (limited to 'src/com/isode/stroke/filetransfer/IncomingJingleFileTransfer.java')
-rw-r--r-- | src/com/isode/stroke/filetransfer/IncomingJingleFileTransfer.java | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/src/com/isode/stroke/filetransfer/IncomingJingleFileTransfer.java b/src/com/isode/stroke/filetransfer/IncomingJingleFileTransfer.java new file mode 100644 index 0000000..c5001e0 --- /dev/null +++ b/src/com/isode/stroke/filetransfer/IncomingJingleFileTransfer.java @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2010-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.filetransfer; + +import com.isode.stroke.jid.JID; +import com.isode.stroke.jingle.JingleSession; +import com.isode.stroke.network.TimerFactory; +import com.isode.stroke.network.Timer; +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.base.IDGenerator; +import com.isode.stroke.base. ByteArray; +import com.isode.stroke.elements.JingleFileTransferFileInfo; +import com.isode.stroke.elements.JingleDescription; +import com.isode.stroke.elements.JingleTransportPayload; +import com.isode.stroke.elements.JingleIBBTransportPayload; +import com.isode.stroke.elements.JingleS5BTransportPayload; +import com.isode.stroke.elements.JinglePayload; +import com.isode.stroke.elements.JingleContentPayload; +import com.isode.stroke.elements.JingleFileTransferDescription; +import com.isode.stroke.elements.HashElement; +import com.isode.stroke.elements.JingleFileTransferHash; +import com.isode.stroke.jingle.JingleContentID; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot1; +import com.isode.stroke.signals.Slot; +import java.util.logging.Logger; +import java.util.Vector; +import java.util.Map; +import java.util.HashMap; + +public class IncomingJingleFileTransfer extends JingleFileTransfer implements IncomingFileTransfer { + + private long fileSizeInBytes = 0; //FileTransferVariables + private String filename = ""; //FileTransferVariables + + /** + * FileTransferMethod. + */ + @Override + public String getFileName() { + return filename; + } + + /** + * FileTransferMethod. + */ + @Override + public long getFileSizeInBytes() { + return fileSizeInBytes; + } + + /** + * FileTransferMethod. + */ + @Override + public void setFileInfo(final String name, long size) { + this.filename = name; + this.fileSizeInBytes = size; + } + + private JingleContentPayload initialContent; + private CryptoProvider crypto; + private State state; + private JingleFileTransferDescription description; + private WriteBytestream stream; + private long receivedBytes; + private IncrementalBytestreamHashCalculator hashCalculator; + private Timer waitOnHashTimer; + private Map<String, ByteArray> hashes = new HashMap<String, ByteArray>(); + private FileTransferOptions options; + + private SignalConnection writeStreamDataReceivedConnection; + private SignalConnection waitOnHashTimerTickedConnection; + private SignalConnection transferFinishedConnection; + private Logger logger_ = Logger.getLogger(this.getClass().getName()); + + public IncomingJingleFileTransfer( + final JID toJID, + JingleSession session, + JingleContentPayload content, + FileTransferTransporterFactory transporterFactory, + TimerFactory timerFactory, + CryptoProvider crypto) { + super(session, toJID, transporterFactory); + this.initialContent = content; + this.crypto = crypto; + this.state = State.Initial; + this. receivedBytes = 0; + this.hashCalculator = null; + this.description = initialContent.getDescription(new JingleFileTransferDescription()); + assert(description != null); + JingleFileTransferFileInfo fileInfo = description.getFileInfo(); + setFileInfo(fileInfo.getName(), fileInfo.getSize()); + hashes = fileInfo.getHashes(); + + waitOnHashTimer = timerFactory.createTimer(5000); + waitOnHashTimerTickedConnection = waitOnHashTimer.onTick.connect(new Slot() { + @Override + public void call() { + handleWaitOnHashTimerTicked(); + } + }); + } + + /** + * IncomingFileTransferMethod. + */ + @Override + public void accept(WriteBytestream stream) { + accept(stream, new FileTransferOptions()); + } + + /** + * IncomingFileTransferMethod. + */ + @Override + public void accept(WriteBytestream stream, final FileTransferOptions options) { + logger_.fine("\n"); + if (!State.Initial.equals(state)) { logger_.warning("Incorrect state \n"); return; } + + assert(this.stream == null); + this.stream = stream; + this.options = options; + + assert(hashCalculator == null); + + hashCalculator = new IncrementalBytestreamHashCalculator(hashes.containsKey("md5"), hashes.containsKey("sha-1"), crypto); + + writeStreamDataReceivedConnection = stream.onWrite.connect(new Slot1<ByteArray>() { + @Override + public void call(ByteArray b) { + handleWriteStreamDataReceived(b); + } + }); + + if (initialContent.getTransport(new JingleS5BTransportPayload()) != null) { + JingleS5BTransportPayload s5bTransport = initialContent.getTransport(new JingleS5BTransportPayload()); + logger_.fine("Got S5B transport as initial payload.\n"); + setTransporter(transporterFactory.createResponderTransporter(getInitiator(), getResponder(), s5bTransport.getSessionID(), options)); + transporter.addRemoteCandidates(s5bTransport.getCandidates(), s5bTransport.getDstAddr()); + setState(State.GeneratingInitialLocalCandidates); + transporter.startGeneratingLocalCandidates(); + } + else if(initialContent.getTransport(new JingleIBBTransportPayload()) != null) { + JingleIBBTransportPayload ibbTransport = initialContent.getTransport(new JingleIBBTransportPayload()); + logger_.fine("Got IBB transport as initial payload.\n"); + setTransporter(transporterFactory.createResponderTransporter(getInitiator(), getResponder(), ibbTransport.getSessionID(), options)); + + startTransferring(transporter.createIBBReceiveSession(ibbTransport.getSessionID(), (int)description.getFileInfo().getSize(), stream)); + + session.sendAccept(getContentID(), initialContent.getDescriptions().get(0), ibbTransport); + } + else { + // Can't happen, because the transfer would have been rejected automatically + assert(false); + } + } + + /** + * IncomingFileTransferMethod. + */ + @Override + public JID getSender() { + return getInitiator(); + } + + /** + * IncomingFileTransferMethod. + */ + @Override + public JID getRecipient() { + return getResponder(); + } + + /** + * JingleFileTransferMethod. + */ + @Override + public void cancel() { + logger_.fine("\n"); + terminate(State.Initial.equals(state) ? JinglePayload.Reason.Type.Decline : JinglePayload.Reason.Type.Cancel); + } + + protected void startTransferViaRemoteCandidate() { + logger_.fine("\n"); + + if (JingleS5BTransportPayload.Candidate.Type.ProxyType.equals(ourCandidateChoice.type)) { + setState(State.WaitingForPeerProxyActivate); + } + else { + startTransferring(createRemoteCandidateSession()); + } + } + + protected void startTransferViaLocalCandidate() { + logger_.fine("\n"); + + if (JingleS5BTransportPayload.Candidate.Type.ProxyType.equals(theirCandidateChoice.type)) { + setState(State.WaitingForLocalProxyActivate); + transporter.startActivatingProxy(theirCandidateChoice.jid); + } + else { + startTransferring(createLocalCandidateSession()); + } + } + + protected void checkHashAndTerminate() { + if (verifyData()) { + terminate(JinglePayload.Reason.Type.Success); + } + else { + logger_.warning("Hash verification failed\n"); + terminate(JinglePayload.Reason.Type.MediaError); + } + } + + protected void stopAll() { + if (!State.Initial.equals(state)) { + writeStreamDataReceivedConnection.disconnect(); + hashCalculator = null; + } + 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 != null); + transferFinishedConnection.disconnect(); + transportSession.stop(); + transportSession = null; + break; + case Finished: logger_.warning("Already finished\n"); break; + } + if (!State.Initial.equals(state)) { + removeTransporter(); + } + } + + protected void setState(State state) { + logger_.fine(state + "\n"); + this.state = state; + onStateChanged.emit(new FileTransfer.State(getExternalState(state))); + } + + protected void setFinishedState(FileTransfer.State.Type type, final FileTransferError error) { + logger_.fine("\n"); + this.state = State.Finished; + onStateChanged.emit(new FileTransfer.State(type)); + onFinished.emit(error); + } + + protected static FileTransfer.State.Type getExternalState(State state) { + switch (state) { + case Initial: return FileTransfer.State.Type.Initial; + case GeneratingInitialLocalCandidates: return FileTransfer.State.Type.WaitingForStart; + case TryingCandidates: return FileTransfer.State.Type.Negotiating; + case WaitingForPeerProxyActivate: return FileTransfer.State.Type.Negotiating; + case WaitingForLocalProxyActivate: return FileTransfer.State.Type.Negotiating; + case WaitingForFallbackOrTerminate: return FileTransfer.State.Type.Negotiating; + case Transferring: return FileTransfer.State.Type.Transferring; + case WaitingForHash: return FileTransfer.State.Type.Transferring; + case Finished: return FileTransfer.State.Type.Finished; + } + assert(false); + return FileTransfer.State.Type.Initial; + } + + protected boolean hasPriorityOnCandidateTie() { + return false; + } + + protected void fallback() { + setState(State.WaitingForFallbackOrTerminate); + } + + protected void startTransferring(TransportSession transportSession) { + logger_.fine("\n"); + + this.transportSession = transportSession; + transferFinishedConnection = transportSession.onFinished.connect(new Slot1<FileTransferError>() { + @Override + public void call(FileTransferError e) { + handleTransferFinished(e); + } + }); + setState(State.Transferring); + transportSession.start(); + } + + protected boolean isWaitingForPeerProxyActivate() { + return State.WaitingForPeerProxyActivate.equals(state); + } + + protected boolean isWaitingForLocalProxyActivate() { + return State.WaitingForLocalProxyActivate.equals(state); + } + + protected boolean isTryingCandidates() { + return State.TryingCandidates.equals(state); + } + + protected TransportSession createLocalCandidateSession() { + return transporter.createLocalCandidateSession(stream, theirCandidateChoice); + } + + protected TransportSession createRemoteCandidateSession() { + return transporter.createRemoteCandidateSession(stream, ourCandidateChoice); + } + + protected void terminate(JinglePayload.Reason.Type reason) { + logger_.fine(reason + "\n"); + + if (!State.Finished.equals(state)) { + session.sendTerminate(reason); + } + stopAll(); + setFinishedState(getExternalFinishedState(reason), getFileTransferError(reason)); + } + + private enum State { + Initial, + GeneratingInitialLocalCandidates, + TryingCandidates, + WaitingForPeerProxyActivate, + WaitingForLocalProxyActivate, + WaitingForFallbackOrTerminate, + Transferring, + WaitingForHash, + Finished + }; + + public void handleSessionTerminateReceived(JinglePayload.Reason reason) { + logger_.fine("\n"); + if (State.Finished.equals(state)) { logger_.warning("Incorrect state\n"); return; } + + if (State.Finished.equals(state)) { + logger_.fine("Already terminated\n"); + return; + } + + stopAll(); + if (reason != null && JinglePayload.Reason.Type.Cancel.equals(reason.type)) { + setFinishedState(FileTransfer.State.Type.Canceled, new FileTransferError(FileTransferError.Type.PeerError)); + } + else if (reason != null && JinglePayload.Reason.Type.Success.equals(reason.type)) { + setFinishedState(FileTransfer.State.Type.Finished, null); + } + else { + setFinishedState(FileTransfer.State.Type.Failed, new FileTransferError(FileTransferError.Type.PeerError)); + } + } + + public void handleSessionInfoReceived(JinglePayload jinglePayload) { + logger_.fine("\n"); + + JingleFileTransferHash transferHash = jinglePayload.getPayload(new JingleFileTransferHash()); + if (transferHash != null) { + logger_.fine("Received hash information.\n"); + waitOnHashTimer.stop(); + if (transferHash.getFileInfo().getHashes().containsKey("sha-1")) { + hashes.put("sha-1", transferHash.getFileInfo().getHash("sha-1")); + } + if (transferHash.getFileInfo().getHashes().containsKey("md5")) { + hashes.put("md5", transferHash.getFileInfo().getHash("md5")); + } + if (State.WaitingForHash.equals(state)) { + checkHashAndTerminate(); + } + } + else { + logger_.fine("Ignoring unknown session info\n"); + } + } + + public void handleTransportReplaceReceived(final JingleContentID content, JingleTransportPayload transport) { + logger_.fine("\n"); + if (!State.WaitingForFallbackOrTerminate.equals(state)) { + logger_.warning("Incorrect state\n"); + return; + } + + JingleIBBTransportPayload ibbTransport; + if (options.isInBandAllowed() && transport instanceof JingleIBBTransportPayload) { + ibbTransport = (JingleIBBTransportPayload)transport; + logger_.fine("transport replaced with IBB\n"); + + startTransferring(transporter.createIBBReceiveSession(ibbTransport.getSessionID(), (int)description.getFileInfo().getSize(), stream)); + session.sendTransportAccept(content, ibbTransport); + } + else { + logger_.fine("Unknown replace transport\n"); + session.sendTransportReject(content, transport); + } + } + + protected void handleLocalTransportCandidatesGenerated(final String s5bSessionID, final Vector<JingleS5BTransportPayload.Candidate> candidates, final String dstAddr) { + logger_.fine("\n"); + if (!State.GeneratingInitialLocalCandidates.equals(state)) { logger_.warning("Incorrect state\n"); return; } + + fillCandidateMap(localCandidates, candidates); + + JingleS5BTransportPayload transport = new JingleS5BTransportPayload(); + transport.setSessionID(s5bSessionID); + transport.setMode(JingleS5BTransportPayload.Mode.TCPMode); + transport.setDstAddr(dstAddr); + for(JingleS5BTransportPayload.Candidate candidate : candidates) { + transport.addCandidate(candidate); + } + session.sendAccept(getContentID(), initialContent.getDescriptions().get(0), transport); + + setState(State.TryingCandidates); + transporter.startTryingRemoteCandidates(); + } + + private void handleWriteStreamDataReceived(final ByteArray data) { + hashCalculator.feedData(data); + receivedBytes += data.getSize(); + onProcessedBytes.emit(data.getSize()); + checkIfAllDataReceived(); + } + + private void stopActiveTransport() { + + } + + private void checkCandidateSelected() { + + } + + protected JingleContentID getContentID() { + return new JingleContentID(initialContent.getName(), initialContent.getCreator()); + } + + private void checkIfAllDataReceived() { + if (receivedBytes == getFileSizeInBytes()) { + logger_.fine("All data received.\n"); + boolean hashInfoAvailable = false; + for(final Map.Entry<String, ByteArray> hashElement : hashes.entrySet()) { + hashInfoAvailable |= !(hashElement.getValue().isEmpty()); + } + + if (!hashInfoAvailable) { + logger_.fine("No hash information yet. Waiting a while on hash info.\n"); + setState(State.WaitingForHash); + waitOnHashTimer.start(); + } + else { + checkHashAndTerminate(); + } + } + else if (receivedBytes > getFileSizeInBytes()) { + logger_.fine("We got more than we could handle!\n"); + terminate(JinglePayload.Reason.Type.MediaError); + } + } + + private boolean verifyData() { + if (hashes.isEmpty()) { + logger_.fine("no verification possible, skipping\n"); + return true; + } + if (hashes.containsKey("sha-1")) { + logger_.fine("Verify SHA-1 hash: " + (hashes.get("sha-1").equals(hashCalculator.getSHA1Hash())) + "\n"); + return hashes.get("sha-1").equals(hashCalculator.getSHA1Hash()); + } + else if (hashes.containsKey("md5")) { + logger_.fine("Verify MD5 hash: " + (hashes.get("md5").equals(hashCalculator.getMD5Hash())) + "\n"); + return hashes.get("md5").equals(hashCalculator.getMD5Hash()); + } + else { + logger_.fine("Unknown hash, skipping\n"); + return true; + } + } + + private void handleWaitOnHashTimerTicked() { + logger_.fine("\n"); + waitOnHashTimer.stop(); + terminate(JinglePayload.Reason.Type.Success); + } + + private void handleTransferFinished(FileTransferError error) { + if (error != null && !State.WaitingForHash.equals(state)) { + terminate(JinglePayload.Reason.Type.MediaError); + } + } +}
\ No newline at end of file |