summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/isode/stroke/filetransfer/JingleFileTransfer.java')
-rw-r--r--src/com/isode/stroke/filetransfer/JingleFileTransfer.java284
1 files changed, 284 insertions, 0 deletions
diff --git a/src/com/isode/stroke/filetransfer/JingleFileTransfer.java b/src/com/isode/stroke/filetransfer/JingleFileTransfer.java
new file mode 100644
index 0000000..b70c11a
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/JingleFileTransfer.java
@@ -0,0 +1,284 @@
+/*
+ * 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.elements.JingleS5BTransportPayload;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.JingleTransportPayload;
+import com.isode.stroke.elements.JinglePayload;
+import com.isode.stroke.jingle.JingleContentID;
+import com.isode.stroke.jingle.AbstractJingleSessionListener;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot3;
+import com.isode.stroke.signals.Slot2;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.logging.Logger;
+
+public abstract class JingleFileTransfer extends AbstractJingleSessionListener {
+
+ protected JingleSession session;
+ protected JID target;
+ protected FileTransferTransporterFactory transporterFactory;
+ protected FileTransferTransporter transporter;
+
+ protected String candidateSelectRequestID;
+ protected boolean ourCandidateSelectFinished;
+ protected JingleS5BTransportPayload.Candidate ourCandidateChoice;
+ protected boolean theirCandidateSelectFinished;
+ protected JingleS5BTransportPayload.Candidate theirCandidateChoice;
+ protected Map<String, JingleS5BTransportPayload.Candidate> localCandidates = new HashMap<String, JingleS5BTransportPayload.Candidate>();
+
+ protected TransportSession transportSession;
+
+ protected SignalConnection localTransportCandidatesGeneratedConnection;
+ protected SignalConnection remoteTransportCandidateSelectFinishedConnection;
+ protected SignalConnection proxyActivatedConnection;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public JingleFileTransfer(
+ JingleSession session,
+ final JID target,
+ FileTransferTransporterFactory transporterFactory) {
+ this.session = session;
+ this.target = target;
+ this.transporterFactory = transporterFactory;
+ this.transporter = null;
+ this.ourCandidateSelectFinished = false;
+ this.theirCandidateSelectFinished = false;
+ session.addListener(this);
+ }
+
+ public void handleTransportInfoReceived(final JingleContentID contentID, JingleTransportPayload transport) {
+ logger_.fine("\n");
+ if(transport instanceof JingleS5BTransportPayload) {
+ JingleS5BTransportPayload s5bPayload = (JingleS5BTransportPayload)transport;
+ if (s5bPayload.hasCandidateError() || !s5bPayload.getCandidateUsed().isEmpty()) {
+ logger_.fine("Received candidate decision from peer\n");
+ if (!isTryingCandidates()) {logger_.warning("Incorrect state\n"); return; }
+
+ theirCandidateSelectFinished = true;
+ if (!s5bPayload.hasCandidateError()) {
+ if(!(localCandidates.containsKey(s5bPayload.getCandidateUsed()))) {
+ logger_.warning("Got invalid candidate\n");
+ terminate(JinglePayload.Reason.Type.GeneralError);
+ return;
+ }
+ theirCandidateChoice = localCandidates.get(s5bPayload.getCandidateUsed());
+ }
+ decideOnCandidates();
+ }
+ else if (!s5bPayload.getActivated().isEmpty()) {
+ logger_.fine("Received peer activate from peer\n");
+ if (!isWaitingForPeerProxyActivate()) { logger_.warning("Incorrect state\n"); return; }
+
+ if (ourCandidateChoice.cid.equals(s5bPayload.getActivated())) {
+ startTransferring(createRemoteCandidateSession());
+ }
+ else {
+ logger_.warning("ourCandidateChoice doesn't match activated proxy candidate!\n");
+ terminate(JinglePayload.Reason.Type.GeneralError);
+ }
+ }
+ else if (s5bPayload.hasProxyError()) {
+ logger_.fine("Received proxy error. Trying to fall back to IBB.\n");
+ fallback();
+ }
+ else {
+ logger_.fine("Ignoring unknown info\n");
+ }
+ }
+ else {
+ logger_.fine("Ignoring unknown info\n");
+ }
+ }
+
+ protected abstract void handleLocalTransportCandidatesGenerated(
+ final String s5bSessionID,
+ final Vector<JingleS5BTransportPayload.Candidate> candidates,
+ final String dstAddr);
+
+ protected void handleProxyActivateFinished(final String s5bSessionID, ErrorPayload error) {
+ logger_.fine("\n");
+ if (!isWaitingForLocalProxyActivate()) { logger_.warning("Incorrect state\n"); return; }
+
+ if (error != null) {
+ logger_.fine("Error activating proxy\n");
+ JingleS5BTransportPayload proxyError = new JingleS5BTransportPayload();
+ proxyError.setSessionID(s5bSessionID);
+ proxyError.setProxyError(true);
+ session.sendTransportInfo(getContentID(), proxyError);
+ fallback();
+ }
+ else {
+ JingleS5BTransportPayload proxyActivate = new JingleS5BTransportPayload();
+ proxyActivate.setSessionID(s5bSessionID);
+ proxyActivate.setActivated(theirCandidateChoice.cid);
+ session.sendTransportInfo(getContentID(), proxyActivate);
+ startTransferring(createLocalCandidateSession());
+ }
+ }
+
+ protected void decideOnCandidates() {
+ logger_.fine("\n");
+ if (!ourCandidateSelectFinished || !theirCandidateSelectFinished) {
+ logger_.fine("Can't make a decision yet!\n");
+ return;
+ }
+ if (ourCandidateChoice == null && theirCandidateChoice == null) {
+ logger_.fine("No candidates succeeded.\n");
+ fallback();
+ }
+ else if (ourCandidateChoice != null && theirCandidateChoice == null) {
+ logger_.fine("Start transfer using remote candidate: " + ourCandidateChoice.cid + ".\n");
+ startTransferViaRemoteCandidate();
+ }
+ else if (theirCandidateChoice != null && ourCandidateChoice == null) {
+ logger_.fine("Start transfer using local candidate: " + theirCandidateChoice.cid + ".\n");
+ startTransferViaLocalCandidate();
+ }
+ else {
+ logger_.fine("Choosing between candidates "
+ + ourCandidateChoice.cid + "(" + ourCandidateChoice.priority + ")" + " and "
+ + theirCandidateChoice.cid + "(" + theirCandidateChoice.priority + ")\n");
+ if (ourCandidateChoice.priority > theirCandidateChoice.priority) {
+ logger_.fine("Start transfer using remote candidate: " + ourCandidateChoice.cid + ".\n");
+ startTransferViaRemoteCandidate();
+ }
+ else if (ourCandidateChoice.priority < theirCandidateChoice.priority) {
+ logger_.fine("Start transfer using local candidate:" + theirCandidateChoice.cid + ".\n");
+ startTransferViaLocalCandidate();
+ }
+ else {
+ if (hasPriorityOnCandidateTie()) {
+ logger_.fine("Start transfer using remote candidate: " + ourCandidateChoice.cid + "\n");
+ startTransferViaRemoteCandidate();
+ }
+ else {
+ logger_.fine("Start transfer using local candidate: " + theirCandidateChoice.cid + "\n");
+ startTransferViaLocalCandidate();
+ }
+ }
+ }
+ }
+
+ protected void handleRemoteTransportCandidateSelectFinished(final String s5bSessionID, final JingleS5BTransportPayload.Candidate candidate) {
+ logger_.fine("\n");
+
+ ourCandidateChoice = candidate;
+ ourCandidateSelectFinished = true;
+
+ JingleS5BTransportPayload s5bPayload = new JingleS5BTransportPayload();
+ s5bPayload.setSessionID(s5bSessionID);
+ if (candidate != null) {
+ s5bPayload.setCandidateUsed(candidate.cid);
+ }
+ else {
+ s5bPayload.setCandidateError(true);
+ }
+ candidateSelectRequestID = session.sendTransportInfo(getContentID(), s5bPayload);
+
+ decideOnCandidates();
+ }
+
+ protected abstract JingleContentID getContentID();
+ protected abstract void startTransferring(TransportSession session);
+ protected abstract void terminate(JinglePayload.Reason.Type reason);
+ protected abstract void fallback();
+ protected abstract boolean hasPriorityOnCandidateTie();
+ protected abstract boolean isWaitingForPeerProxyActivate();
+ protected abstract boolean isWaitingForLocalProxyActivate();
+ protected abstract boolean isTryingCandidates();
+ protected abstract TransportSession createLocalCandidateSession();
+ protected abstract TransportSession createRemoteCandidateSession();
+ protected abstract void startTransferViaLocalCandidate();
+ protected abstract void startTransferViaRemoteCandidate();
+
+
+ protected void setTransporter(FileTransferTransporter transporter) {
+ //SWIFT_LOG_ASSERT(!this.transporter, error);
+ this.transporter = transporter;
+ localTransportCandidatesGeneratedConnection = transporter.onLocalCandidatesGenerated.connect(new Slot3<String, Vector<JingleS5BTransportPayload.Candidate>, String>() {
+ @Override
+ public void call(String s, Vector<JingleS5BTransportPayload.Candidate> v, String t) {
+ handleLocalTransportCandidatesGenerated(s, v, t);
+ }
+ });
+ remoteTransportCandidateSelectFinishedConnection = transporter.onRemoteCandidateSelectFinished.connect(new Slot2<String, JingleS5BTransportPayload.Candidate>() {
+ @Override
+ public void call(String s, JingleS5BTransportPayload.Candidate c) {
+ handleRemoteTransportCandidateSelectFinished(s, c);
+ }
+ });
+ proxyActivatedConnection = transporter.onProxyActivated.connect(new Slot2<String, ErrorPayload>() {
+ @Override
+ public void call(String s, ErrorPayload e) {
+ handleProxyActivateFinished(s, e);
+ }
+ });
+ }
+
+ protected void removeTransporter() {
+ if (transporter != null) {
+ localTransportCandidatesGeneratedConnection.disconnect();
+ remoteTransportCandidateSelectFinishedConnection.disconnect();
+ proxyActivatedConnection.disconnect();
+ transporter = null;
+ }
+ }
+
+ protected void fillCandidateMap(Map<String, JingleS5BTransportPayload.Candidate> map, final Vector<JingleS5BTransportPayload.Candidate> candidates) {
+ map.clear();
+ for (JingleS5BTransportPayload.Candidate candidate : candidates) {
+ map.put(candidate.cid, candidate);
+ }
+ }
+
+ /*
+ std.string JingleFileTransfer.getS5BDstAddr(const JID& requester, const JID& target) const {
+ return Hexify.hexify(crypto.getSHA1Hash(
+ createSafeByteArray(s5bSessionID + requester.toString() + target.toString())));
+ }
+ */
+
+ protected JID getInitiator() {
+ return session.getInitiator();
+ }
+
+ protected JID getResponder() {
+ return target;
+ }
+
+ protected static FileTransfer.State.Type getExternalFinishedState(JinglePayload.Reason.Type reason) {
+ if (reason.equals(JinglePayload.Reason.Type.Cancel) || reason.equals(JinglePayload.Reason.Type.Decline)) {
+ return FileTransfer.State.Type.Canceled;
+ }
+ else if (reason.equals(JinglePayload.Reason.Type.Success)) {
+ return FileTransfer.State.Type.Finished;
+ }
+ else {
+ return FileTransfer.State.Type.Failed;
+ }
+ }
+
+ protected static FileTransferError getFileTransferError(JinglePayload.Reason.Type reason) {
+ if (reason.equals(JinglePayload.Reason.Type.Success)) {
+ return null;
+ }
+ else {
+ return new FileTransferError(FileTransferError.Type.UnknownError);
+ }
+ }
+} \ No newline at end of file