summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTarun Gupta <tarun1995gupta@gmail.com>2015-08-03 12:08:37 (GMT)
committerAlex Clayton <alex.clayton@isode.com>2016-01-21 10:47:51 (GMT)
commit97a085f7e2c9b7820000eaace97dc0ab6392cb0d (patch)
treed3df191a053a69bc52238b76b8e9e42af043302c /src/com/isode/stroke/filetransfer/IncomingJingleFileTransfer.java
parentfa1633e3b4d75a8217459cdc5fe64e9ee5ace65a (diff)
downloadstroke-97a085f7e2c9b7820000eaace97dc0ab6392cb0d.zip
stroke-97a085f7e2c9b7820000eaace97dc0ab6392cb0d.tar.bz2
Completes FileTransfer according to Swiften.
S5BTransport Session still needs generic T. FileTransfer, OutgoingFileTransfer and IncomingFileTransfer are made an interface due to the need of multiple inheritance in IncomingJingleFileTransfer and OutgoingJingleFileTransfer. Corresponding documentation has been updated. License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details. Test-Information: None. Change-Id: If44cf387767865c37492d871c12d623f94ebaa3a
Diffstat (limited to 'src/com/isode/stroke/filetransfer/IncomingJingleFileTransfer.java')
-rw-r--r--src/com/isode/stroke/filetransfer/IncomingJingleFileTransfer.java500
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