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/OutgoingJingleFileTransfer.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/OutgoingJingleFileTransfer.java')
-rw-r--r--src/com/isode/stroke/filetransfer/OutgoingJingleFileTransfer.java446
1 files changed, 446 insertions, 0 deletions
diff --git a/src/com/isode/stroke/filetransfer/OutgoingJingleFileTransfer.java b/src/com/isode/stroke/filetransfer/OutgoingJingleFileTransfer.java
new file mode 100644
index 0000000..10760a1
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/OutgoingJingleFileTransfer.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+/*
+ * Copyright (c) 2013-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;
+
+public class OutgoingJingleFileTransfer extends JingleFileTransfer implements OutgoingFileTransfer {
+
+ 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;
+ }
+
+ public static final int DEFAULT_BLOCK_SIZE = 4096;
+ private IDGenerator idGenerator;
+ private ReadBytestream stream;
+ private JingleFileTransferFileInfo fileInfo;
+ private FileTransferOptions options;
+ private JingleContentID contentID;
+ private IncrementalBytestreamHashCalculator hashCalculator;
+ private State state;
+ private boolean candidateAcknowledged;
+
+ private Timer waitForRemoteTermination;
+
+ private SignalConnection processedBytesConnection;
+ private SignalConnection transferFinishedConnection;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public OutgoingJingleFileTransfer(
+ final JID toJID,
+ JingleSession session,
+ ReadBytestream stream,
+ FileTransferTransporterFactory transporterFactory,
+ TimerFactory timerFactory,
+ IDGenerator idGenerator,
+ final JingleFileTransferFileInfo fileInfo,
+ final FileTransferOptions options,
+ CryptoProvider crypto) {
+ super(session, toJID, transporterFactory);
+ this.idGenerator = idGenerator;
+ this.stream = stream;
+ this.fileInfo = fileInfo;
+ this.session = session;
+ this.contentID = new JingleContentID(idGenerator.generateID(), JingleContentPayload.Creator.InitiatorCreator);
+ this.state = State.Initial;
+ this.candidateAcknowledged = false;
+ setFileInfo(fileInfo.getName(), fileInfo.getSize());
+
+ // calculate both, MD5 and SHA-1 since we don't know which one the other side supports
+ hashCalculator = new IncrementalBytestreamHashCalculator(true, true, crypto);
+ stream.onRead.connect(new Slot1<ByteArray>() {
+ @Override
+ public void call(ByteArray b) {
+ hashCalculator.feedData(b);
+ }
+ });
+ waitForRemoteTermination = timerFactory.createTimer(5000);
+ waitForRemoteTermination.onTick.connect(new Slot() {
+ @Override
+ public void call() {
+ handleWaitForRemoteTerminationTimeout();
+ }
+ });
+ }
+
+ /**
+ * OutgoingFileTransferMethod.
+ */
+ @Override
+ public void start() {
+ logger_.fine("\n");
+ if (!State.Initial.equals(state)) {
+ logger_.warning("Incorrect state\n");
+ return;
+ }
+
+ setTransporter(transporterFactory.createInitiatorTransporter(getInitiator(), getResponder(), options));
+ setState(State.GeneratingInitialLocalCandidates);
+ transporter.startGeneratingLocalCandidates();
+ }
+
+ /**
+ * JingleFileTransferMethod.
+ */
+ @Override
+ public void cancel() {
+ terminate(JinglePayload.Reason.Type.Cancel);
+ }
+
+ private enum State {
+ Initial,
+ GeneratingInitialLocalCandidates,
+ WaitingForAccept,
+ TryingCandidates,
+ WaitingForPeerProxyActivate,
+ WaitingForLocalProxyActivate,
+ WaitingForCandidateAcknowledge,
+ FallbackRequested,
+ Transferring,
+ WaitForTermination,
+ Finished
+ };
+
+ public void handleSessionAcceptReceived(final JingleContentID contentID, JingleDescription description, JingleTransportPayload transportPayload) {
+ logger_.fine("\n");
+ if (!State.WaitingForAccept.equals(state)) { logger_.warning("Incorrect state\n"); return; }
+
+ if (transportPayload instanceof JingleS5BTransportPayload) {
+ JingleS5BTransportPayload s5bPayload = (JingleS5BTransportPayload)transportPayload;
+ transporter.addRemoteCandidates(s5bPayload.getCandidates(), s5bPayload.getDstAddr());
+ setState(State.TryingCandidates);
+ transporter.startTryingRemoteCandidates();
+ }
+ else {
+ logger_.fine("Unknown transport payload. Falling back.\n");
+ fallback();
+ }
+ }
+
+ public void handleSessionTerminateReceived(JinglePayload.Reason reason) {
+ logger_.fine("\n");
+ if (State.Finished.equals(state)) { logger_.warning("Incorrect state: " + state + "\n"); return; }
+
+ stopAll();
+ if (State.WaitForTermination.equals(state)) {
+ waitForRemoteTermination.stop();
+ }
+ 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.Decline.equals(reason.type)) {
+ setFinishedState(FileTransfer.State.Type.Canceled, null);
+ }
+ 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 handleTransportAcceptReceived(final JingleContentID contentID, JingleTransportPayload transport) {
+ logger_.fine("\n");
+ if (!State.FallbackRequested.equals(state)) { logger_.warning("Incorrect state\n"); return; }
+
+ if (transport instanceof JingleIBBTransportPayload) {
+ JingleIBBTransportPayload ibbPayload = (JingleIBBTransportPayload)transport;
+ startTransferring(transporter.createIBBSendSession(ibbPayload.getSessionID(), ( ibbPayload.getBlockSize() != null ? ibbPayload.getBlockSize() : DEFAULT_BLOCK_SIZE), stream));
+ }
+ else {
+ logger_.fine("Unknown transport replacement\n");
+ terminate(JinglePayload.Reason.Type.FailedTransport);
+ }
+ }
+
+ public void handleTransportRejectReceived(final JingleContentID contentID, JingleTransportPayload transport) {
+ logger_.fine("\n");
+
+ terminate(JinglePayload.Reason.Type.UnsupportedTransports);
+ }
+
+ protected void startTransferViaRemoteCandidate() {
+ logger_.fine("\n");
+
+ if (JingleS5BTransportPayload.Candidate.Type.ProxyType.equals(ourCandidateChoice.type)) {
+ setState(State.WaitingForPeerProxyActivate);
+ }
+ else {
+ transportSession = createRemoteCandidateSession();
+ startTransferringIfCandidateAcknowledged();
+ }
+ }
+
+ protected void startTransferViaLocalCandidate() {
+ logger_.fine("\n");
+
+ if (JingleS5BTransportPayload.Candidate.Type.ProxyType.equals(theirCandidateChoice.type)) {
+ setState(State.WaitingForLocalProxyActivate);
+ transporter.startActivatingProxy(theirCandidateChoice.jid);
+ }
+ else {
+ transportSession = createLocalCandidateSession();
+ startTransferringIfCandidateAcknowledged();
+ }
+ }
+
+ private void startTransferringIfCandidateAcknowledged() {
+ if (candidateAcknowledged) {
+ startTransferring(transportSession);
+ }
+ else {
+ setState(State.WaitingForCandidateAcknowledge);
+ }
+ }
+
+ 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);
+
+ JingleFileTransferDescription description = new JingleFileTransferDescription();
+ fileInfo.addHash(new HashElement("sha-1", new ByteArray()));
+ fileInfo.addHash(new HashElement("md5", new ByteArray()));
+ description.setFileInfo(fileInfo);
+
+ JingleS5BTransportPayload transport = new JingleS5BTransportPayload();
+ transport.setSessionID(s5bSessionID);
+ transport.setMode(JingleS5BTransportPayload.Mode.TCPMode);
+ transport.setDstAddr(dstAddr);
+ for(JingleS5BTransportPayload.Candidate candidate : candidates) {
+ transport.addCandidate(candidate);
+ logger_.fine("\t" + "S5B candidate: " + candidate.hostPort.toString() + "\n");
+ }
+ setState(State.WaitingForAccept);
+ session.sendInitiate(contentID, description, transport);
+ }
+
+ public void handleTransportInfoAcknowledged(final String id) {
+ if (id.equals(candidateSelectRequestID)) {
+ candidateAcknowledged = true;
+ }
+ if (State.WaitingForCandidateAcknowledge.equals(state)) {
+ startTransferring(transportSession);
+ }
+ }
+
+ protected JingleContentID getContentID() {
+ return contentID;
+ }
+
+ protected void terminate(JinglePayload.Reason.Type reason) {
+ logger_.fine(reason + "\n");
+
+ if (!State.Initial.equals(state) && !State.GeneratingInitialLocalCandidates.equals(state) && !State.Finished.equals(state)) {
+ session.sendTerminate(reason);
+ }
+ stopAll();
+ setFinishedState(getExternalFinishedState(reason), getFileTransferError(reason));
+ }
+
+ protected void fallback() {
+ if (options.isInBandAllowed()) {
+ logger_.fine("Trying to fallback to IBB transport.\n");
+ JingleIBBTransportPayload ibbTransport = new JingleIBBTransportPayload();
+ ibbTransport.setBlockSize(DEFAULT_BLOCK_SIZE);
+ ibbTransport.setSessionID(idGenerator.generateID());
+ setState(State.FallbackRequested);
+ session.sendTransportReplace(contentID, ibbTransport);
+ }
+ else {
+ logger_.fine("Fallback to IBB transport not allowed.\n");
+ terminate(JinglePayload.Reason.Type.ConnectivityError);
+ }
+ }
+
+ private void handleTransferFinished(FileTransferError error) {
+ logger_.fine("\n");
+ if (!State.Transferring.equals(state)) { logger_.warning("Incorrect state: " + state + "\n"); return; }
+
+ if (error != null) {
+ terminate(JinglePayload.Reason.Type.ConnectivityError);
+ }
+ else {
+ sendSessionInfoHash();
+
+ // wait for other party to terminate session after they have verified the hash
+ setState(State.WaitForTermination);
+ waitForRemoteTermination.start();
+ }
+ }
+
+ private void sendSessionInfoHash() {
+ logger_.fine("\n");
+
+ JingleFileTransferHash hashElement = new JingleFileTransferHash();
+ hashElement.getFileInfo().addHash(new HashElement("sha-1", hashCalculator.getSHA1Hash()));
+ hashElement.getFileInfo().addHash(new HashElement("md5", hashCalculator.getMD5Hash()));
+ session.sendInfo(hashElement);
+ }
+
+ protected void startTransferring(TransportSession transportSession) {
+ logger_.fine("\n");
+
+ this.transportSession = transportSession;
+ processedBytesConnection = transportSession.onBytesSent.connect(onProcessedBytes);
+ transferFinishedConnection = transportSession.onFinished.connect(new Slot1<FileTransferError>() {
+ @Override
+ public void call(FileTransferError e) {
+ handleTransferFinished(e);
+ }
+ });
+ setState(State.Transferring);
+ transportSession.start();
+ }
+
+ protected boolean hasPriorityOnCandidateTie() {
+ return true;
+ }
+
+ 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);
+ }
+
+ private void handleWaitForRemoteTerminationTimeout() {
+ assert(state.equals(State.WaitForTermination));
+ logger_.warning("Other party did not terminate session. Terminate it now.\n");
+ waitForRemoteTermination.stop();
+ terminate(JinglePayload.Reason.Type.MediaError);
+ }
+
+ private void stopAll() {
+ logger_.fine(state + "\n");
+ switch (state) {
+ case Initial: logger_.warning("Not yet started\n"); break;
+ case GeneratingInitialLocalCandidates: transporter.stopGeneratingLocalCandidates(); break;
+ case WaitingForAccept: break;
+ case TryingCandidates: transporter.stopTryingRemoteCandidates(); break;
+ case FallbackRequested: break;
+ case WaitingForPeerProxyActivate: break;
+ case WaitingForLocalProxyActivate: transporter.stopActivatingProxy(); break;
+ case WaitingForCandidateAcknowledge: // Fallthrough
+ case Transferring:
+ assert(transportSession != null);
+ processedBytesConnection.disconnect();
+ transferFinishedConnection.disconnect();
+ transportSession.stop();
+ transportSession = null;
+ break;
+ case WaitForTermination:
+ break;
+ case Finished: logger_.warning("Already finished\n"); break;
+ }
+ if (!State.Initial.equals(state)) {
+ removeTransporter();
+ }
+ }
+
+ private void setState(State state) {
+ logger_.fine(state + "\n");
+ this.state = state;
+ onStateChanged.emit(new FileTransfer.State(getExternalState(state)));
+ }
+
+ private 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);
+ }
+
+ private static FileTransfer.State.Type getExternalState(State state) {
+ switch (state) {
+ case Initial: return FileTransfer.State.Type.Initial;
+ case GeneratingInitialLocalCandidates: return FileTransfer.State.Type.WaitingForStart;
+ case WaitingForAccept: return FileTransfer.State.Type.WaitingForAccept;
+ case TryingCandidates: return FileTransfer.State.Type.Negotiating;
+ case WaitingForPeerProxyActivate: return FileTransfer.State.Type.Negotiating;
+ case WaitingForLocalProxyActivate: return FileTransfer.State.Type.Negotiating;
+ case WaitingForCandidateAcknowledge: return FileTransfer.State.Type.Negotiating;
+ case FallbackRequested: return FileTransfer.State.Type.Negotiating;
+ case Transferring: return FileTransfer.State.Type.Transferring;
+ case WaitForTermination: return FileTransfer.State.Type.Transferring;
+ case Finished: return FileTransfer.State.Type.Finished;
+ }
+ assert(false);
+ return FileTransfer.State.Type.Initial;
+ }
+
+} \ No newline at end of file