summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/isode/stroke/base/SafeByteArray.java7
-rw-r--r--src/com/isode/stroke/client/Client.java36
-rw-r--r--src/com/isode/stroke/disco/FeatureOracle.java9
-rw-r--r--src/com/isode/stroke/elements/JingleS5BTransportPayload.java18
-rw-r--r--src/com/isode/stroke/elements/S5BProxyRequest.java85
-rw-r--r--src/com/isode/stroke/filetransfer/ByteArrayReadBytestream.java54
-rw-r--r--src/com/isode/stroke/filetransfer/ByteArrayWriteBytestream.java31
-rw-r--r--src/com/isode/stroke/filetransfer/BytestreamException.java19
-rw-r--r--src/com/isode/stroke/filetransfer/BytestreamsRequest.java37
-rw-r--r--src/com/isode/stroke/filetransfer/DefaultFileTransferTransporter.java317
-rw-r--r--src/com/isode/stroke/filetransfer/DefaultFileTransferTransporterFactory.java101
-rw-r--r--src/com/isode/stroke/filetransfer/DummyFileTransferManager.java48
-rw-r--r--src/com/isode/stroke/filetransfer/DummyFileTransferTransporterFactory.java203
-rw-r--r--src/com/isode/stroke/filetransfer/FailingTransportSession.java23
-rw-r--r--src/com/isode/stroke/filetransfer/FileReadBytestream.java58
-rw-r--r--src/com/isode/stroke/filetransfer/FileTransfer.java67
-rw-r--r--src/com/isode/stroke/filetransfer/FileTransferError.java36
-rw-r--r--src/com/isode/stroke/filetransfer/FileTransferManager.java52
-rw-r--r--src/com/isode/stroke/filetransfer/FileTransferManagerImpl.java189
-rw-r--r--src/com/isode/stroke/filetransfer/FileTransferOptions.java63
-rw-r--r--src/com/isode/stroke/filetransfer/FileTransferTransporter.java55
-rw-r--r--src/com/isode/stroke/filetransfer/FileTransferTransporterFactory.java27
-rw-r--r--src/com/isode/stroke/filetransfer/IBBReceiveSession.java131
-rw-r--r--src/com/isode/stroke/filetransfer/IBBReceiveTransportSession.java34
-rw-r--r--src/com/isode/stroke/filetransfer/IBBRequest.java29
-rw-r--r--src/com/isode/stroke/filetransfer/IBBSendSession.java135
-rw-r--r--src/com/isode/stroke/filetransfer/IBBSendTransportSession.java35
-rw-r--r--src/com/isode/stroke/filetransfer/IncomingFileTransfer.java31
-rw-r--r--src/com/isode/stroke/filetransfer/IncomingFileTransferManager.java82
-rw-r--r--src/com/isode/stroke/filetransfer/IncomingJingleFileTransfer.java500
-rw-r--r--src/com/isode/stroke/filetransfer/IncrementalBytestreamHashCalculator.java79
-rw-r--r--src/com/isode/stroke/filetransfer/JingleFileTransfer.java284
-rw-r--r--src/com/isode/stroke/filetransfer/LocalJingleTransportCandidateGenerator.java215
-rw-r--r--src/com/isode/stroke/filetransfer/OutgoingFileTransfer.java25
-rw-r--r--src/com/isode/stroke/filetransfer/OutgoingFileTransferManager.java79
-rw-r--r--src/com/isode/stroke/filetransfer/OutgoingJingleFileTransfer.java446
-rw-r--r--src/com/isode/stroke/filetransfer/OutgoingSIFileTransfer.java149
-rw-r--r--src/com/isode/stroke/filetransfer/ReadBytestream.java31
-rw-r--r--src/com/isode/stroke/filetransfer/RemoteJingleTransportCandidateSelector.java111
-rw-r--r--src/com/isode/stroke/filetransfer/S5BTransportSession.java59
-rw-r--r--src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java336
-rw-r--r--src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxiesManager.java249
-rw-r--r--src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxyFinder.java99
-rw-r--r--src/com/isode/stroke/filetransfer/SOCKS5BytestreamRegistry.java46
-rw-r--r--src/com/isode/stroke/filetransfer/SOCKS5BytestreamServer.java86
-rw-r--r--src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerManager.java308
-rw-r--r--src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerPortForwardingUser.java68
-rw-r--r--src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerResourceUser.java62
-rw-r--r--src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerSession.java246
-rw-r--r--src/com/isode/stroke/filetransfer/StreamInitiationRequest.java37
-rw-r--r--src/com/isode/stroke/filetransfer/TransportSession.java23
-rw-r--r--src/com/isode/stroke/filetransfer/WriteBytestream.java22
-rw-r--r--src/com/isode/stroke/jingle/Jingle.java4
-rw-r--r--src/com/isode/stroke/queries/GenericRequest.java19
-rw-r--r--test/com/isode/stroke/filetransfer/DummyFileTransferManager.java48
-rw-r--r--test/com/isode/stroke/filetransfer/DummyFileTransferTransporterFactory.java203
56 files changed, 5817 insertions, 29 deletions
diff --git a/src/com/isode/stroke/base/SafeByteArray.java b/src/com/isode/stroke/base/SafeByteArray.java
index e193299..437346f 100644
--- a/src/com/isode/stroke/base/SafeByteArray.java
+++ b/src/com/isode/stroke/base/SafeByteArray.java
@@ -61,6 +61,11 @@ public class SafeByteArray extends ByteArray {
return this;
}
+ public SafeByteArray append(SafeByteArray b) {
+ append(b.getData());
+ return this;
+ }
+
/**
* Updates the SafeByteArray by adding all the bytes
* in a byte[] to the end of the array (mutable add).
@@ -102,4 +107,4 @@ public class SafeByteArray extends ByteArray {
super.append(s);
return this;
}
-} \ No newline at end of file
+}
diff --git a/src/com/isode/stroke/client/Client.java b/src/com/isode/stroke/client/Client.java
index 464e062..07ac885 100644
--- a/src/com/isode/stroke/client/Client.java
+++ b/src/com/isode/stroke/client/Client.java
@@ -30,6 +30,10 @@ import com.isode.stroke.signals.Signal1;
import com.isode.stroke.vcards.VCardManager;
import com.isode.stroke.base.SafeByteArray;
import com.isode.stroke.tls.BlindCertificateTrustChecker;
+import com.isode.stroke.filetransfer.FileTransferManager;
+import com.isode.stroke.filetransfer.FileTransferManagerImpl;
+import com.isode.stroke.jingle.JingleSessionManager;
+import com.isode.stroke.filetransfer.DummyFileTransferManager;
/**
* Provides the core functionality for writing XMPP client software.
@@ -59,8 +63,8 @@ public class Client extends CoreClient {
private final SubscriptionManager subscriptionManager;
private final ClientDiscoManager discoManager;
private final AvatarManager avatarManager;
- //private final JingleSessionManager jingleSessionManager;
- //private final FileTransferManager fileTransferManager;
+ private final JingleSessionManager jingleSessionManager;
+ private final FileTransferManager fileTransferManager;
private final BlindCertificateTrustChecker blindCertificateTrustChecker;
//private final WhiteboardSessionManager whiteboardSessionManager;
private final ClientBlockListManager blockListManager;
@@ -117,8 +121,7 @@ public class Client extends CoreClient {
blindCertificateTrustChecker = new BlindCertificateTrustChecker();
- //TO PORT
- //jingleSessionManager = new JingleSessionManager(getIQRouter());
+ jingleSessionManager = new JingleSessionManager(getIQRouter());
blockListManager = new ClientBlockListManager(getIQRouter());
/*whiteboardSessionManager = NULL;
@@ -135,16 +138,16 @@ public class Client extends CoreClient {
getIQRouter(),
getEntityCapsProvider(),
presenceOracle,
- getNetworkFactories()->getConnectionFactory(),
- getNetworkFactories()->getConnectionServerFactory(),
- getNetworkFactories()->getTimerFactory(),
- getNetworkFactories()->getDomainNameResolver(),
- getNetworkFactories()->getNetworkEnvironment(),
- getNetworkFactories()->getNATTraverser(),
- getNetworkFactories()->getCryptoProvider());
- #else
- fileTransferManager = new DummyFileTransferManager();
- #endif*/
+ getNetworkFactories().getConnectionFactory(),
+ getNetworkFactories().getConnectionServerFactory(),
+ getNetworkFactories().getTimerFactory(),
+ getNetworkFactories().getDomainNameResolver(),
+ getNetworkFactories().getNetworkEnvironment(),
+ getNetworkFactories().getNATTraverser(),
+ getNetworkFactories().getCryptoProvider());
+ #else*/
+ fileTransferManager = new DummyFileTransferManager();
+ //#endif
}
/**
@@ -266,10 +269,9 @@ public class Client extends CoreClient {
*
* WARNING: File transfer will only work if Swiften is built in 'experimental' mode.
*/
- //TO PORT
- /*public FileTransferManager getFileTransferManager() {
+ public FileTransferManager getFileTransferManager() {
return fileTransferManager;
- }*/
+ }
/**
* Configures the client to always trust a non-validating
diff --git a/src/com/isode/stroke/disco/FeatureOracle.java b/src/com/isode/stroke/disco/FeatureOracle.java
index e01b2ab..12a45b4 100644
--- a/src/com/isode/stroke/disco/FeatureOracle.java
+++ b/src/com/isode/stroke/disco/FeatureOracle.java
@@ -17,7 +17,7 @@ import com.isode.stroke.elements.Presence;
import com.isode.stroke.jid.JID;
import com.isode.stroke.disco.EntityCapsProvider;
import com.isode.stroke.presence.PresenceOracle;
-//import com.isode.stroke.filetransfer.FileTransferManager;
+import com.isode.stroke.filetransfer.FileTransferManager;
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
@@ -32,10 +32,7 @@ public class FeatureOracle {
this.presenceOracle_ = presenceOracle;
}
- /**
- * To PORT : FileTransfer.
- */
- /*public Tristate isFileTransferSupported(JID jid) {
+ public Tristate isFileTransferSupported(JID jid) {
DiscoInfo discoInfo = getDiscoResultForJID(jid);
if (discoInfo != null) {
return FileTransferManager.isSupportedBy(discoInfo) ? Tristate.Yes : Tristate.No;
@@ -43,7 +40,7 @@ public class FeatureOracle {
else {
return Tristate.Maybe;
}
- }*/
+ }
public Tristate isMessageReceiptsSupported(JID jid) {
return isFeatureSupported(jid, DiscoInfo.MessageDeliveryReceiptsFeature);
diff --git a/src/com/isode/stroke/elements/JingleS5BTransportPayload.java b/src/com/isode/stroke/elements/JingleS5BTransportPayload.java
index e9ee67f..546a41a 100644
--- a/src/com/isode/stroke/elements/JingleS5BTransportPayload.java
+++ b/src/com/isode/stroke/elements/JingleS5BTransportPayload.java
@@ -17,6 +17,7 @@ import com.isode.stroke.network.HostAddressPort;
import com.isode.stroke.jid.JID;
import com.isode.stroke.base.NotNull;
import java.util.Vector;
+import java.util.Comparator;
public class JingleS5BTransportPayload extends JingleTransportPayload {
@@ -46,9 +47,20 @@ public class JingleS5BTransportPayload extends JingleTransportPayload {
}
}
- public class CompareCandidate {
- public boolean compareTo(JingleS5BTransportPayload.Candidate c1, JingleS5BTransportPayload.Candidate c2) {
- return (c1.priority < c2.priority);
+ public static class CompareCandidate implements Comparator<JingleS5BTransportPayload.Candidate> {
+ public int compare(JingleS5BTransportPayload.Candidate c1, JingleS5BTransportPayload.Candidate c2) {
+ if (c1.priority == c2.priority) { return 0; }
+ else if (c1.priority < c2.priority) { return -1; }
+ else { return 1; }
+ }
+
+ public boolean equals(Object c) {
+ if(!(c instanceof JingleS5BTransportPayload.Candidate)) {
+ return false;
+ }
+ else {
+ return this.equals(c);
+ }
}
}
diff --git a/src/com/isode/stroke/elements/S5BProxyRequest.java b/src/com/isode/stroke/elements/S5BProxyRequest.java
new file mode 100644
index 0000000..9d0c651
--- /dev/null
+++ b/src/com/isode/stroke/elements/S5BProxyRequest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+/*
+ * Copyright (c) 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.elements;
+
+import com.isode.stroke.elements.Payload;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.base.NotNull;
+import com.isode.stroke.elements.Bytestreams;
+
+public class S5BProxyRequest extends Payload {
+
+ private StreamHost streamHost;
+ private String sid = "";
+ private JID activate;
+
+ public static class StreamHost {
+ public String host = "";
+ public int port;
+ public JID jid = new JID();
+ };
+
+ /**
+ * Default Constructor.
+ */
+ public S5BProxyRequest() {
+
+ }
+
+ /**
+ * @return streamHost.
+ */
+ public StreamHost getStreamHost() {
+ return streamHost;
+ }
+
+ /**
+ * @param streamHost.
+ */
+ public void setStreamHost(StreamHost streamHost) {
+ this.streamHost = streamHost;
+ }
+
+ /**
+ * @return sid, Not Null.
+ */
+ public String getSID() {
+ return sid;
+ }
+
+ /**
+ * @param sid, Not Null.
+ */
+ public void setSID(String sid) {
+ NotNull.exceptIfNull(sid, "sid");
+ this.sid = sid;
+ }
+
+ /**
+ * @return activate.
+ */
+ public JID getActivate() {
+ return activate;
+ }
+
+ /**
+ * @param activate.
+ */
+ public void setActivate(JID activate) {
+ this.activate = activate;
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/ByteArrayReadBytestream.java b/src/com/isode/stroke/filetransfer/ByteArrayReadBytestream.java
new file mode 100644
index 0000000..4c3d47d
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/ByteArrayReadBytestream.java
@@ -0,0 +1,54 @@
+/*
+ * 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.base.ByteArray;
+
+public class ByteArrayReadBytestream extends ReadBytestream {
+
+ private ByteArray data;
+ private int position;
+ private boolean dataComplete;
+
+ public ByteArrayReadBytestream(final ByteArray data) {
+ this.data = data;
+ this.position = 0;
+ this.dataComplete = true;
+ }
+
+ public ByteArray read(int size) {
+ int readSize = size;
+ if (position + readSize > data.getSize()) {
+ readSize = data.getSize() - position;
+ }
+ String s = new String(data.getData());
+ s = s.substring(position, position+readSize);
+ ByteArray result = new ByteArray(s);
+
+ onRead.emit(result);
+ position += readSize;
+ return result;
+ }
+
+ public boolean isFinished() {
+ return position >= data.getSize() && dataComplete;
+ }
+
+ public void setDataComplete(boolean b) {
+ dataComplete = b;
+ }
+
+ public void addData(final ByteArray moreData) {
+ data.append(moreData);
+ onDataAvailable.emit();
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/ByteArrayWriteBytestream.java b/src/com/isode/stroke/filetransfer/ByteArrayWriteBytestream.java
new file mode 100644
index 0000000..eb3a30f
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/ByteArrayWriteBytestream.java
@@ -0,0 +1,31 @@
+/*
+ * 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.base.ByteArray;
+
+public class ByteArrayWriteBytestream extends WriteBytestream {
+
+ private ByteArray data = new ByteArray();
+
+ public ByteArrayWriteBytestream() {
+ }
+
+ public void write(final ByteArray bytes) {
+ data.append(bytes);
+ onWrite.emit(bytes);
+ }
+
+ public ByteArray getData() {
+ return data;
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/BytestreamException.java b/src/com/isode/stroke/filetransfer/BytestreamException.java
new file mode 100644
index 0000000..addac30
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/BytestreamException.java
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+public class BytestreamException extends Exception {
+
+ public BytestreamException(String s) {
+ super(s);
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/BytestreamsRequest.java b/src/com/isode/stroke/filetransfer/BytestreamsRequest.java
new file mode 100644
index 0000000..a975b3d
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/BytestreamsRequest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.queries.GenericRequest;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.elements.Bytestreams;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.jid.JID;
+
+public class BytestreamsRequest extends GenericRequest<Bytestreams> {
+
+ public static BytestreamsRequest create(final JID jid, Bytestreams payload, IQRouter router) {
+ return new BytestreamsRequest(jid, payload, router);
+ }
+
+ public static BytestreamsRequest create(final JID from, final JID to, Bytestreams payload, IQRouter router) {
+ return new BytestreamsRequest(from, to, payload, router);
+ }
+
+ private BytestreamsRequest(final JID jid, Bytestreams payload, IQRouter router) {
+ super(IQ.Type.Set, jid, payload, router);
+ }
+
+ private BytestreamsRequest(final JID from, final JID to, Bytestreams payload, IQRouter router) {
+ super(IQ.Type.Set, from, to, payload, router);
+ }
+}
diff --git a/src/com/isode/stroke/filetransfer/DefaultFileTransferTransporter.java b/src/com/isode/stroke/filetransfer/DefaultFileTransferTransporter.java
new file mode 100644
index 0000000..f06407e
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/DefaultFileTransferTransporter.java
@@ -0,0 +1,317 @@
+/*
+ * 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.elements.JingleS5BTransportPayload;
+import com.isode.stroke.signals.Signal2;
+import com.isode.stroke.signals.Signal3;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+import java.util.logging.Logger;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Slot2;
+import com.isode.stroke.base.IDGenerator;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.network.ConnectionFactory;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.elements.S5BProxyRequest;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.stringcodecs.Hexify;
+
+public class DefaultFileTransferTransporter extends FileTransferTransporter {
+
+ private JID initiator;
+ private JID responder;
+ private Role role;
+ private SOCKS5BytestreamRegistry s5bRegistry;
+ private SOCKS5BytestreamServerManager s5bServerManager;
+ private SOCKS5BytestreamProxiesManager s5bProxy;
+ private CryptoProvider crypto;
+ private IQRouter router;
+ private LocalJingleTransportCandidateGenerator localCandidateGenerator;
+ private RemoteJingleTransportCandidateSelector remoteCandidateSelector;
+ private String s5bSessionID;
+ private SOCKS5BytestreamClientSession remoteS5BClientSession;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public enum Role {
+ Initiator,
+ Responder
+ };
+
+ public DefaultFileTransferTransporter(
+ final JID initiator,
+ final JID responder,
+ Role role,
+ SOCKS5BytestreamRegistry s5bRegistry,
+ SOCKS5BytestreamServerManager s5bServerManager,
+ SOCKS5BytestreamProxiesManager s5bProxy,
+ IDGenerator idGenerator,
+ ConnectionFactory connectionFactory,
+ TimerFactory timerFactory,
+ CryptoProvider crypto,
+ IQRouter router,
+ final FileTransferOptions options) {
+ this.initiator = initiator;
+ this.responder = responder;
+ this.role = role;
+ this.s5bRegistry = s5bRegistry;
+ this.s5bProxy = s5bProxy;
+ this.crypto = crypto;
+ this.router = router;
+ localCandidateGenerator = new LocalJingleTransportCandidateGenerator(
+ s5bServerManager,
+ s5bProxy,
+ (Role.Initiator.equals(role) ? initiator : responder),
+ idGenerator,
+ options);
+ localCandidateGenerator.onLocalTransportCandidatesGenerated.connect(new Slot1<Vector<JingleS5BTransportPayload.Candidate>>() {
+ @Override
+ public void call(Vector<JingleS5BTransportPayload.Candidate> e) {
+ handleLocalCandidatesGenerated(e);
+ }
+ });
+
+ remoteCandidateSelector = new RemoteJingleTransportCandidateSelector(
+ connectionFactory,
+ timerFactory,
+ options);
+ remoteCandidateSelector.onCandidateSelectFinished.connect(new Slot2<JingleS5BTransportPayload.Candidate, SOCKS5BytestreamClientSession>() {
+ @Override
+ public void call(JingleS5BTransportPayload.Candidate c, SOCKS5BytestreamClientSession s) {
+ handleRemoteCandidateSelectFinished(c, s);
+ }
+ });
+ }
+
+ public void initialize() {
+ s5bSessionID = s5bRegistry.generateSessionID();
+ }
+
+ public void initialize(final String s5bSessionID) {
+ this.s5bSessionID = s5bSessionID;
+ }
+
+ public void startGeneratingLocalCandidates() {
+ localCandidateGenerator.start();
+ }
+
+ public void stopGeneratingLocalCandidates() {
+ localCandidateGenerator.stop();
+ }
+
+ public void addRemoteCandidates(
+ final Vector<JingleS5BTransportPayload.Candidate> candidates, final String dstAddr) {
+ remoteCandidateSelector.setSOCKS5DstAddr(dstAddr.isEmpty() ? getRemoteCandidateSOCKS5DstAddr() : dstAddr);
+ remoteCandidateSelector.addCandidates(candidates);
+ }
+
+ public void startTryingRemoteCandidates() {
+ remoteCandidateSelector.startSelectingCandidate();
+ }
+
+ public void stopTryingRemoteCandidates() {
+ remoteCandidateSelector.stopSelectingCandidate();
+ }
+
+ public void startActivatingProxy(final JID proxyServiceJID) {
+ // activate proxy
+ logger_.fine("Start activating proxy " + proxyServiceJID.toString() + " with sid = " + s5bSessionID + ".\n");
+ S5BProxyRequest proxyRequest = new S5BProxyRequest();
+ proxyRequest.setSID(s5bSessionID);
+ proxyRequest.setActivate(Role.Initiator.equals(role) ? responder : initiator);
+
+ GenericRequest<S5BProxyRequest> request = new GenericRequest<S5BProxyRequest>(IQ.Type.Set, proxyServiceJID, proxyRequest, router);
+ request.onResponse.connect(new Slot2<S5BProxyRequest, ErrorPayload>() {
+ @Override
+ public void call(S5BProxyRequest s, ErrorPayload e) {
+ handleActivateProxySessionResult(s5bSessionID, e);
+ }
+ });
+ request.send();
+ }
+
+ public void stopActivatingProxy() {
+ // TODO
+ assert(false);
+ }
+
+ public TransportSession createIBBSendSession(
+ final String sessionID, int blockSize, ReadBytestream stream) {
+ if (s5bServerManager.getServer() != null) {
+ closeLocalSession();
+ }
+ closeRemoteSession();
+ IBBSendSession ibbSession = new IBBSendSession(
+ sessionID, initiator, responder, stream, router);
+ ibbSession.setBlockSize(blockSize);
+ return new IBBSendTransportSession(ibbSession);
+ }
+
+ public TransportSession createIBBReceiveSession(
+ final String sessionID, int size, WriteBytestream stream) {
+ if (s5bServerManager.getServer() != null) {
+ closeLocalSession();
+ }
+ closeRemoteSession();
+ IBBReceiveSession ibbSession = new IBBReceiveSession(
+ sessionID, initiator, responder, size, stream, router);
+ return new IBBReceiveTransportSession(ibbSession);
+ }
+
+ public TransportSession createRemoteCandidateSession(
+ ReadBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ closeLocalSession();
+ return new S5BTransportSession<SOCKS5BytestreamClientSession>(
+ remoteS5BClientSession, stream);
+ }
+
+ public TransportSession createRemoteCandidateSession(
+ WriteBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ closeLocalSession();
+ return new S5BTransportSession<SOCKS5BytestreamClientSession>(
+ remoteS5BClientSession, stream);
+ }
+
+ public TransportSession createLocalCandidateSession(
+ ReadBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ closeRemoteSession();
+ TransportSession transportSession = null;
+ if (JingleS5BTransportPayload.Candidate.Type.ProxyType.equals(candidate.type)) {
+ SOCKS5BytestreamClientSession proxySession = s5bProxy.getProxySessionAndCloseOthers(candidate.jid, getLocalCandidateSOCKS5DstAddr());
+ assert(proxySession != null);
+ transportSession = new S5BTransportSession<SOCKS5BytestreamClientSession>(proxySession, stream);
+ }
+
+ if (transportSession == null) {
+ SOCKS5BytestreamServerSession serverSession = getServerSession();
+ if (serverSession != null) {
+ transportSession = new S5BTransportSession<SOCKS5BytestreamServerSession>(serverSession, stream);
+ }
+ }
+
+ if (transportSession == null) {
+ transportSession = new FailingTransportSession();
+ }
+ return transportSession;
+ }
+
+ public TransportSession createLocalCandidateSession(
+ WriteBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ closeRemoteSession();
+ TransportSession transportSession = null;
+ if (JingleS5BTransportPayload.Candidate.Type.ProxyType.equals(candidate.type)) {
+ SOCKS5BytestreamClientSession proxySession = s5bProxy.getProxySessionAndCloseOthers(candidate.jid, getLocalCandidateSOCKS5DstAddr());
+ assert(proxySession != null);
+ transportSession = new S5BTransportSession<SOCKS5BytestreamClientSession>(proxySession, stream);
+ }
+
+ if (transportSession == null) {
+ SOCKS5BytestreamServerSession serverSession = getServerSession();
+ if (serverSession != null) {
+ transportSession = new S5BTransportSession<SOCKS5BytestreamServerSession>(serverSession, stream);
+ }
+ }
+
+ if (transportSession == null) {
+ transportSession = new FailingTransportSession();
+ }
+ return transportSession;
+ }
+
+ private void handleLocalCandidatesGenerated(final Vector<JingleS5BTransportPayload.Candidate> candidates) {
+ s5bRegistry.setHasBytestream(getSOCKS5DstAddr(), true);
+ s5bProxy.connectToProxies(getSOCKS5DstAddr());
+ onLocalCandidatesGenerated.emit(s5bSessionID, candidates, getSOCKS5DstAddr());
+ }
+
+ private void handleRemoteCandidateSelectFinished(
+ final JingleS5BTransportPayload.Candidate candidate,
+ SOCKS5BytestreamClientSession session) {
+ remoteS5BClientSession = session;
+ onRemoteCandidateSelectFinished.emit(s5bSessionID, candidate);
+ }
+
+ private void handleActivateProxySessionResult(final String sessionID, ErrorPayload error) {
+ onProxyActivated.emit(sessionID, error);
+ }
+
+ private void closeLocalSession() {
+ s5bRegistry.setHasBytestream(getSOCKS5DstAddr(), false);
+ if (s5bServerManager.getServer() != null) {
+ Vector<SOCKS5BytestreamServerSession> serverSessions = s5bServerManager.getServer().getSessions(getSOCKS5DstAddr());
+ for(SOCKS5BytestreamServerSession session : serverSessions) {
+ session.stop();
+ }
+ }
+ }
+ private void closeRemoteSession() {
+ if (remoteS5BClientSession != null) {
+ remoteS5BClientSession.stop();
+ remoteS5BClientSession = null;
+ }
+ }
+
+ private SOCKS5BytestreamServerSession getServerSession() {
+ s5bRegistry.setHasBytestream(getSOCKS5DstAddr(), false);
+ Vector<SOCKS5BytestreamServerSession> serverSessions = s5bServerManager.getServer().getSessions(getSOCKS5DstAddr());
+ while (serverSessions.size() > 1) {
+ SOCKS5BytestreamServerSession session = serverSessions.lastElement();
+ serverSessions.remove(serverSessions.lastElement());
+ session.stop();
+ }
+ return !serverSessions.isEmpty() ? serverSessions.get(0) : null;
+ }
+
+ private String getSOCKS5DstAddr() {
+ String result = "";
+ if (Role.Initiator.equals(role)) {
+ result = getInitiatorCandidateSOCKS5DstAddr();
+ logger_.fine("Initiator S5B DST.ADDR = " + s5bSessionID + " + " + initiator.toString() + " + " + responder.toString() + " : " + result + "\n");
+ }
+ else {
+ result = getResponderCandidateSOCKS5DstAddr();
+ logger_.fine("Responder S5B DST.ADDR = " + s5bSessionID + " + " + responder.toString() + " + " + initiator.toString() + " : " + result + "\n");
+ }
+ return result;
+ }
+
+ private String getInitiatorCandidateSOCKS5DstAddr() {
+ return Hexify.hexify(crypto.getSHA1Hash(new SafeByteArray(s5bSessionID + initiator.toString() + responder.toString())));
+ }
+
+ private String getResponderCandidateSOCKS5DstAddr() {
+ return Hexify.hexify(crypto.getSHA1Hash(new SafeByteArray(s5bSessionID + responder.toString() + initiator.toString())));
+ }
+
+ private String getRemoteCandidateSOCKS5DstAddr() {
+ if (Role.Initiator.equals(role)) {
+ return getResponderCandidateSOCKS5DstAddr();
+ }
+ else {
+ return getInitiatorCandidateSOCKS5DstAddr();
+ }
+ }
+
+ private String getLocalCandidateSOCKS5DstAddr() {
+ if (Role.Responder.equals(role)) {
+ return getResponderCandidateSOCKS5DstAddr();
+ }
+ else {
+ return getInitiatorCandidateSOCKS5DstAddr();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/DefaultFileTransferTransporterFactory.java b/src/com/isode/stroke/filetransfer/DefaultFileTransferTransporterFactory.java
new file mode 100644
index 0000000..3f15cc3
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/DefaultFileTransferTransporterFactory.java
@@ -0,0 +1,101 @@
+/*
+ * 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.elements.JingleS5BTransportPayload;
+import com.isode.stroke.signals.Signal2;
+import com.isode.stroke.signals.Signal3;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+import java.util.logging.Logger;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Slot2;
+import com.isode.stroke.base.IDGenerator;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.network.ConnectionFactory;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.elements.S5BProxyRequest;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.stringcodecs.Hexify;
+
+public class DefaultFileTransferTransporterFactory implements FileTransferTransporterFactory {
+
+ private SOCKS5BytestreamRegistry s5bRegistry;
+ private SOCKS5BytestreamServerManager s5bServerManager;
+ private SOCKS5BytestreamProxiesManager s5bProxiesManager;
+ private IDGenerator idGenerator;
+ private ConnectionFactory connectionFactory;
+ private TimerFactory timerFactory;
+ private CryptoProvider cryptoProvider;
+ private IQRouter iqRouter;
+
+ DefaultFileTransferTransporterFactory(
+ SOCKS5BytestreamRegistry s5bRegistry,
+ SOCKS5BytestreamServerManager s5bServerManager,
+ SOCKS5BytestreamProxiesManager s5bProxy,
+ IDGenerator idGenerator,
+ ConnectionFactory connectionFactory,
+ TimerFactory timerFactory,
+ CryptoProvider cryptoProvider,
+ IQRouter iqRouter) {
+ this.s5bRegistry = s5bRegistry;
+ this.s5bProxiesManager = s5bProxy;
+ this.s5bServerManager = s5bServerManager;
+ this.idGenerator = idGenerator;
+ this.connectionFactory = connectionFactory;
+ this.timerFactory = timerFactory;
+ this.cryptoProvider = cryptoProvider;
+ this.iqRouter = iqRouter;
+ }
+
+ public FileTransferTransporter createInitiatorTransporter(
+ final JID initiator, final JID responder, final FileTransferOptions options) {
+ DefaultFileTransferTransporter transporter = new DefaultFileTransferTransporter(
+ initiator,
+ responder,
+ DefaultFileTransferTransporter.Role.Initiator,
+ s5bRegistry,
+ s5bServerManager,
+ s5bProxiesManager,
+ idGenerator,
+ connectionFactory,
+ timerFactory,
+ cryptoProvider,
+ iqRouter,
+ options);
+ transporter.initialize();
+ return transporter;
+ }
+
+ public FileTransferTransporter createResponderTransporter(
+ final JID initiator, final JID responder, final String s5bSessionID, final FileTransferOptions options) {
+ DefaultFileTransferTransporter transporter = new DefaultFileTransferTransporter(
+ initiator,
+ responder,
+ DefaultFileTransferTransporter.Role.Responder,
+ s5bRegistry,
+ s5bServerManager,
+ s5bProxiesManager,
+ idGenerator,
+ connectionFactory,
+ timerFactory,
+ cryptoProvider,
+ iqRouter,
+ options);
+ transporter.initialize(s5bSessionID);
+ return transporter;
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/DummyFileTransferManager.java b/src/com/isode/stroke/filetransfer/DummyFileTransferManager.java
new file mode 100644
index 0000000..d45d9e6
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/DummyFileTransferManager.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt 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.signals.Signal1;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.S5BProxyRequest;
+import java.util.Date;
+
+public class DummyFileTransferManager extends FileTransferManager {
+
+ public DummyFileTransferManager() {
+ super();
+ }
+
+ public OutgoingFileTransfer createOutgoingFileTransfer(
+ final JID to,
+ final String filepath,
+ final String description,
+ ReadBytestream bytestream,
+ final FileTransferOptions op) {
+ return null;
+ }
+
+ public OutgoingFileTransfer createOutgoingFileTransfer(
+ final JID to,
+ final String filename,
+ final String description,
+ final long sizeInBytes,
+ final Date lastModified,
+ ReadBytestream bytestream,
+ final FileTransferOptions op) {
+ return null;
+ }
+
+ public void addS5BProxy(S5BProxyRequest p) {
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/DummyFileTransferTransporterFactory.java b/src/com/isode/stroke/filetransfer/DummyFileTransferTransporterFactory.java
new file mode 100644
index 0000000..10b630d
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/DummyFileTransferTransporterFactory.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 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.elements.JingleS5BTransportPayload;
+import com.isode.stroke.signals.Signal2;
+import com.isode.stroke.signals.Signal3;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+import java.util.logging.Logger;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Slot2;
+import com.isode.stroke.base.IDGenerator;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.network.ConnectionFactory;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.elements.S5BProxyRequest;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.stringcodecs.Hexify;
+import com.isode.stroke.elements.S5BProxyRequest;
+
+class DummyFileTransferTransporter extends FileTransferTransporter {
+
+ public enum Role {
+ Initiator,
+ Responder
+ };
+
+ public DummyFileTransferTransporter(
+ final JID initiator,
+ final JID responder,
+ Role role,
+ SOCKS5BytestreamRegistry s5bRegistry,
+ SOCKS5BytestreamServerManager s5bServerManager,
+ SOCKS5BytestreamProxiesManager s5bProxy,
+ IDGenerator idGenerator,
+ ConnectionFactory connectionFactory,
+ TimerFactory timer,
+ CryptoProvider cryptoProvider,
+ IQRouter iqRouter,
+ final FileTransferOptions option) {
+ initiator_ = initiator;
+ responder_ = responder;
+ role_ = role;
+ s5bRegistry_ = s5bRegistry;
+ crypto_ = cryptoProvider;
+ iqRouter_ = iqRouter;
+ }
+
+ public void initialize() {
+ s5bSessionID_ = s5bRegistry_.generateSessionID();
+ }
+
+ public void startGeneratingLocalCandidates() {
+ Vector<JingleS5BTransportPayload.Candidate> candidates = new Vector<JingleS5BTransportPayload.Candidate>();
+ onLocalCandidatesGenerated.emit(s5bSessionID_, candidates, getSOCKS5DstAddr());
+ }
+
+ public void stopGeneratingLocalCandidates() {
+ }
+
+ public void addRemoteCandidates(final Vector<JingleS5BTransportPayload.Candidate> candidates, final String d) {
+ }
+
+ public void startTryingRemoteCandidates() {
+ onRemoteCandidateSelectFinished.emit(s5bSessionID_, null);
+ }
+
+ public void stopTryingRemoteCandidates() {
+ }
+
+ public void startActivatingProxy(final JID proxy) {
+ }
+
+ public void stopActivatingProxy() {
+ }
+
+ public TransportSession createIBBSendSession(final String sessionID, int blockSize, ReadBytestream stream) {
+ IBBSendSession ibbSession =new IBBSendSession(
+ sessionID, initiator_, responder_, stream, iqRouter_);
+ ibbSession.setBlockSize(blockSize);
+ return new IBBSendTransportSession(ibbSession);
+ }
+
+ public TransportSession createIBBReceiveSession(final String sessionID, int size, WriteBytestream stream) {
+ IBBReceiveSession ibbSession = new IBBReceiveSession(
+ sessionID, initiator_, responder_, size, stream, iqRouter_);
+ return new IBBReceiveTransportSession(ibbSession);
+ }
+
+ public TransportSession createRemoteCandidateSession(
+ ReadBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ return null;
+ }
+
+ public TransportSession createRemoteCandidateSession(
+ WriteBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ return null;
+ }
+
+ public TransportSession createLocalCandidateSession(
+ ReadBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ return null;
+ }
+
+ public TransportSession createLocalCandidateSession(
+ WriteBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ return null;
+ }
+
+ private String getSOCKS5DstAddr() {
+ String result = "";
+ if (Role.Initiator.equals(role_)) {
+ result = getInitiatorCandidateSOCKS5DstAddr();
+ }
+ else {
+ result = getResponderCandidateSOCKS5DstAddr();
+ }
+ return result;
+ }
+
+ private String getInitiatorCandidateSOCKS5DstAddr() {
+ return Hexify.hexify(crypto_.getSHA1Hash(new SafeByteArray(s5bSessionID_ + initiator_.toString() + responder_.toString())));
+ }
+
+ private String getResponderCandidateSOCKS5DstAddr() {
+ return Hexify.hexify(crypto_.getSHA1Hash(new SafeByteArray(s5bSessionID_ + responder_.toString() + initiator_.toString())));
+ }
+
+ private JID initiator_;
+ private JID responder_;
+ private Role role_;
+ private SOCKS5BytestreamRegistry s5bRegistry_;
+ private CryptoProvider crypto_;
+ private String s5bSessionID_;
+ private IQRouter iqRouter_;
+};
+
+public class DummyFileTransferTransporterFactory implements FileTransferTransporterFactory {
+
+ public DummyFileTransferTransporterFactory(
+ SOCKS5BytestreamRegistry s5bRegistry,
+ SOCKS5BytestreamServerManager s5bServerManager,
+ SOCKS5BytestreamProxiesManager s5bProxy,
+ IDGenerator idGenerator,
+ ConnectionFactory connectionFactory,
+ TimerFactory timerFactory,
+ CryptoProvider cryptoProvider,
+ IQRouter iqRouter) {
+ s5bRegistry_ = s5bRegistry;
+ s5bServerManager_ = s5bServerManager;
+ s5bProxy_ = s5bProxy;
+ idGenerator_ = idGenerator;
+ connectionFactory_ = connectionFactory;
+ timerFactory_ = timerFactory;
+ cryptoProvider_ = cryptoProvider;
+ iqRouter_ = iqRouter;
+ }
+
+ public FileTransferTransporter createInitiatorTransporter(final JID initiator, final JID responder, final FileTransferOptions options) {
+ DummyFileTransferTransporter transporter = new DummyFileTransferTransporter(
+ initiator,
+ responder,
+ DummyFileTransferTransporter.Role.Initiator,
+ s5bRegistry_,
+ s5bServerManager_,
+ s5bProxy_,
+ idGenerator_,
+ connectionFactory_,
+ timerFactory_,
+ cryptoProvider_,
+ iqRouter_,
+ options);
+ transporter.initialize();
+ return transporter;
+ }
+
+ public FileTransferTransporter createResponderTransporter(final JID initiator, final JID responder, final String s5bSessionID, final FileTransferOptions options) {
+ return null;
+ }
+
+ private SOCKS5BytestreamRegistry s5bRegistry_;
+ private SOCKS5BytestreamServerManager s5bServerManager_;
+ private SOCKS5BytestreamProxiesManager s5bProxy_;
+ private IDGenerator idGenerator_;
+ private ConnectionFactory connectionFactory_;
+ private TimerFactory timerFactory_;
+ private CryptoProvider cryptoProvider_;
+ private IQRouter iqRouter_;
+}; \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/FailingTransportSession.java b/src/com/isode/stroke/filetransfer/FailingTransportSession.java
new file mode 100644
index 0000000..b3cac2a
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/FailingTransportSession.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 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;
+
+public class FailingTransportSession extends TransportSession {
+
+ public void start() {
+ assert(false);
+ onFinished.emit(new FileTransferError(FileTransferError.Type.PeerError));
+ }
+
+ public void stop() {
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/FileReadBytestream.java b/src/com/isode/stroke/filetransfer/FileReadBytestream.java
new file mode 100644
index 0000000..d349c17
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/FileReadBytestream.java
@@ -0,0 +1,58 @@
+/*
+ * 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 java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import com.isode.stroke.base.ByteArray;
+
+public class FileReadBytestream extends ReadBytestream {
+
+ private String file;
+ private FileInputStream stream;
+
+ public FileReadBytestream(final String file) {
+ this.file = file;
+ this.stream = null;
+ }
+
+ public ByteArray read(int size) {
+ try {
+ if (stream == null) {
+ stream = new FileInputStream(file);
+ }
+ ByteArray result = new ByteArray();
+ //assert(stream.good());
+ stream.read(result.getData(), 0, size);
+ onRead.emit(result);
+ return result;
+ }
+ catch (FileNotFoundException e) {
+ return null;
+ }
+ catch (IOException e) {
+ return null;
+ } finally {
+ try {
+ if(stream != null) stream.close();
+ }
+ catch (IOException e) {
+ // Needs a catch clause
+ }
+ }
+ }
+
+ public boolean isFinished() {
+ return stream != null;
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/FileTransfer.java b/src/com/isode/stroke/filetransfer/FileTransfer.java
new file mode 100644
index 0000000..8572d93
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/FileTransfer.java
@@ -0,0 +1,67 @@
+/*
+ * 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.signals.Signal1;
+
+/**
+ * Because of the lack of multiple inheritance in Java, this has to be done
+ * slightly differently from Swiften. What happens is that the methods in Swiften
+ * are provided abstract here. Any class implementing this interface directly/indirectly (through other interface) has to implement these methods.
+ * OutgoingJingleFileTransfer implements this interface indirectly through OutgoingFileTransfer.
+ * IncomingJingleFileTransfer implements this interface indirectly through IncomingFileTransfer.
+ * OutgoingSIFileTransfer implements this interface indirectly through OutgoingFileTransfer.
+ */
+public interface FileTransfer {
+
+ public static class State {
+ public enum Type {
+ Initial,
+ WaitingForStart,
+ Negotiating,
+ WaitingForAccept,
+ Transferring,
+ Canceled,
+ Failed,
+ Finished
+ };
+
+ public State(Type type) {
+ this(type, "");
+ }
+
+ public State(Type type, final String message) {
+ this.type = type;
+ this.message = message;
+ }
+
+ public Type type;
+ public String message = "";
+ };
+
+ public final Signal1<Integer /* proccessedBytes */> onProcessedBytes = new Signal1<Integer>();
+ public final Signal1<State> onStateChanged = new Signal1<State>();
+ public final Signal1<FileTransferError> onFinished = new Signal1<FileTransferError>();
+
+ public void cancel();
+
+ public String getFileName();
+
+ public long getFileSizeInBytes();
+
+ public void setFileInfo(final String name, long size);
+}
diff --git a/src/com/isode/stroke/filetransfer/FileTransferError.java b/src/com/isode/stroke/filetransfer/FileTransferError.java
new file mode 100644
index 0000000..ac135d6
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/FileTransferError.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+public class FileTransferError {
+
+ private Type type;
+
+ public enum Type {
+ UnknownError,
+ PeerError,
+ ReadError,
+ ClosedError
+ };
+
+ public FileTransferError() {
+ this.type = Type.UnknownError;
+ }
+
+ public FileTransferError(Type type) {
+ this.type = type;
+ }
+
+ public Type getType() {
+ return type;
+ }
+}
diff --git a/src/com/isode/stroke/filetransfer/FileTransferManager.java b/src/com/isode/stroke/filetransfer/FileTransferManager.java
new file mode 100644
index 0000000..037514a
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/FileTransferManager.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+/*
+ * Copyright (c) 2013 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.signals.Signal1;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.elements.DiscoInfo;
+import java.util.Date;
+
+public abstract class FileTransferManager {
+
+ public abstract OutgoingFileTransfer createOutgoingFileTransfer(
+ final JID to,
+ final String filepath,
+ final String description,
+ ReadBytestream bytestream,
+ final FileTransferOptions op);
+
+ public abstract OutgoingFileTransfer createOutgoingFileTransfer(
+ final JID to,
+ final String filename,
+ final String description,
+ final long sizeInBytes,
+ final Date lastModified,
+ ReadBytestream bytestream,
+ final FileTransferOptions op);
+
+ public static boolean isSupportedBy(final DiscoInfo info) {
+ if (info != null) {
+ return info.hasFeature(DiscoInfo.JingleFeature)
+ && info.hasFeature(DiscoInfo.JingleFTFeature)
+ && (info.hasFeature(DiscoInfo.JingleTransportsIBBFeature) || info.hasFeature(DiscoInfo.JingleTransportsS5BFeature));
+ }
+ return false;
+ }
+
+ public final Signal1<IncomingFileTransfer> onIncomingFileTransfer = new Signal1<IncomingFileTransfer>();
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/FileTransferManagerImpl.java b/src/com/isode/stroke/filetransfer/FileTransferManagerImpl.java
new file mode 100644
index 0000000..58ea74e
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/FileTransferManagerImpl.java
@@ -0,0 +1,189 @@
+/*
+ * 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.network.ConnectionFactory;
+import com.isode.stroke.network.ConnectionServerFactory;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.network.DomainNameResolver;
+import com.isode.stroke.disco.EntityCapsProvider;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.jingle.JingleSessionManager;
+import com.isode.stroke.network.NATTraverser;
+import com.isode.stroke.network.NetworkEnvironment;
+import com.isode.stroke.presence.PresenceOracle;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.base.IDGenerator;
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.Presence;
+import com.isode.stroke.elements.JingleFileTransferFileInfo;
+import java.io.File;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.Vector;
+import java.util.Collection;
+
+public class FileTransferManagerImpl extends FileTransferManager {
+
+ private OutgoingFileTransferManager outgoingFTManager;
+ private IncomingFileTransferManager incomingFTManager;
+ private FileTransferTransporterFactory transporterFactory;
+ private IQRouter iqRouter;
+ private EntityCapsProvider capsProvider;
+ private PresenceOracle presenceOracle;
+ private IDGenerator idGenerator;
+ private SOCKS5BytestreamRegistry bytestreamRegistry;
+ private SOCKS5BytestreamProxiesManager bytestreamProxy;
+ private SOCKS5BytestreamServerManager s5bServerManager;
+
+ public FileTransferManagerImpl(
+ final JID ownJID,
+ JingleSessionManager jingleSessionManager,
+ IQRouter router,
+ EntityCapsProvider capsProvider,
+ PresenceOracle presOracle,
+ ConnectionFactory connectionFactory,
+ ConnectionServerFactory connectionServerFactory,
+ TimerFactory timerFactory,
+ DomainNameResolver domainNameResolver,
+ NetworkEnvironment networkEnvironment,
+ NATTraverser natTraverser,
+ CryptoProvider crypto) {
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ this.iqRouter = router;
+ this.capsProvider = capsProvider;
+ this.presenceOracle = presOracle;
+ bytestreamRegistry = new SOCKS5BytestreamRegistry();
+ s5bServerManager = new SOCKS5BytestreamServerManager(bytestreamRegistry, connectionServerFactory, networkEnvironment, natTraverser);
+ bytestreamProxy = new SOCKS5BytestreamProxiesManager(connectionFactory, timerFactory, domainNameResolver, iqRouter, new JID(ownJID.getDomain()));
+
+ transporterFactory = new DefaultFileTransferTransporterFactory(
+ bytestreamRegistry,
+ s5bServerManager,
+ bytestreamProxy,
+ idGenerator,
+ connectionFactory,
+ timerFactory,
+ crypto,
+ iqRouter);
+ outgoingFTManager = new OutgoingFileTransferManager(
+ jingleSessionManager,
+ iqRouter,
+ transporterFactory,
+ timerFactory,
+ crypto);
+ incomingFTManager = new IncomingFileTransferManager(
+ jingleSessionManager,
+ iqRouter,
+ transporterFactory,
+ timerFactory,
+ crypto);
+ incomingFTManager.onIncomingFileTransfer.connect(onIncomingFileTransfer);
+ }
+
+ public OutgoingFileTransfer createOutgoingFileTransfer(
+ final JID to,
+ final String filepath,
+ final String description,
+ ReadBytestream bytestream) {
+ return createOutgoingFileTransfer(to, filepath, description, bytestream, new FileTransferOptions());
+ }
+
+ public OutgoingFileTransfer createOutgoingFileTransfer(
+ final JID to,
+ final String filepath,
+ final String description,
+ ReadBytestream bytestream,
+ final FileTransferOptions config) {
+ File file = new File(filepath);
+ String filename = file.getName();
+ long sizeInBytes = file.length();
+ Date lastModified = new Date(file.lastModified());
+ return createOutgoingFileTransfer(to, filename, description, sizeInBytes, lastModified, bytestream, config);
+ }
+
+ public OutgoingFileTransfer createOutgoingFileTransfer(
+ final JID to,
+ final String filename,
+ final String description,
+ final long sizeInBytes,
+ final Date lastModified,
+ ReadBytestream bytestream) {
+ return createOutgoingFileTransfer(to, filename, description, sizeInBytes, lastModified, bytestream, new FileTransferOptions());
+ }
+
+ public OutgoingFileTransfer createOutgoingFileTransfer(
+ final JID to,
+ final String filename,
+ final String description,
+ final long sizeInBytes,
+ final Date lastModified,
+ ReadBytestream bytestream,
+ final FileTransferOptions config) {
+ JingleFileTransferFileInfo fileInfo = new JingleFileTransferFileInfo();
+ fileInfo.setDate(lastModified);
+ fileInfo.setSize(sizeInBytes);
+ fileInfo.setName(filename);
+ fileInfo.setDescription(description);
+
+ JID receipient = to;
+
+ if(receipient.isBare()) {
+ JID fullJID = highestPriorityJIDSupportingFileTransfer(receipient);
+ if (fullJID != null) {
+ receipient = fullJID;
+ } else {
+ return null;
+ }
+ }
+
+ assert(!iqRouter.getJID().isBare());
+
+ return outgoingFTManager.createOutgoingFileTransfer(iqRouter.getJID(), receipient, bytestream, fileInfo, config);
+ }
+
+ public void start() {
+
+ }
+
+ public void stop() {
+ s5bServerManager.stop();
+ }
+
+ private JID highestPriorityJIDSupportingFileTransfer(final JID bareJID) {
+ JID fullReceipientJID = new JID();
+ int priority = -2147483648;
+
+ //getAllPresence(bareJID) gives you all presences for the bare JID (i.e. all resources) Isode Limited. @ 11:11
+ Collection<Presence> presences = presenceOracle.getAllPresence(bareJID);
+
+ //iterate over them
+ for(Presence pres : presences) {
+ if (pres.getPriority() > priority) {
+ // look up caps from the jid
+ DiscoInfo info = capsProvider.getCaps(pres.getFrom());
+ if (isSupportedBy(info)) {
+ priority = pres.getPriority();
+ fullReceipientJID = pres.getFrom();
+ }
+ }
+ }
+
+ return fullReceipientJID.isValid() ? fullReceipientJID : null;
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/FileTransferOptions.java b/src/com/isode/stroke/filetransfer/FileTransferOptions.java
new file mode 100644
index 0000000..fd529e9
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/FileTransferOptions.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+public class FileTransferOptions {
+
+ private boolean allowInBand_;
+ private boolean allowAssisted_;
+ private boolean allowProxied_;
+ private boolean allowDirect_;
+
+ public FileTransferOptions() {
+ allowInBand_ = true;
+ allowAssisted_ = true;
+ allowProxied_ = true;
+ allowDirect_ = true;
+ }
+
+ public FileTransferOptions withInBandAllowed(boolean b) {
+ allowInBand_ = b;
+ return this;
+ }
+
+ public boolean isInBandAllowed() {
+ return allowInBand_;
+ }
+
+ public FileTransferOptions withAssistedAllowed(boolean b) {
+ allowAssisted_ = b;
+ return this;
+ }
+
+ public boolean isAssistedAllowed() {
+ return allowAssisted_;
+ }
+
+ public FileTransferOptions withProxiedAllowed(boolean b) {
+ allowProxied_ = b;
+ return this;
+ }
+
+ public boolean isProxiedAllowed() {
+ return allowProxied_;
+ }
+
+ public FileTransferOptions withDirectAllowed(boolean b) {
+ allowDirect_ = b;
+ return this;
+ }
+
+ public boolean isDirectAllowed() {
+ return allowDirect_;
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/FileTransferTransporter.java b/src/com/isode/stroke/filetransfer/FileTransferTransporter.java
new file mode 100644
index 0000000..d20550d
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/FileTransferTransporter.java
@@ -0,0 +1,55 @@
+/*
+ * 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.elements.JingleS5BTransportPayload;
+import com.isode.stroke.signals.Signal2;
+import com.isode.stroke.signals.Signal3;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+
+/**
+ * @brief The FileTransferTransporter class is an abstract factory definition
+ * to generate SOCKS5 bytestream transports or IBB bytestreams for use in file
+ * transfers.
+ */
+public abstract class FileTransferTransporter {
+
+ public abstract void startGeneratingLocalCandidates();
+ public abstract void stopGeneratingLocalCandidates();
+
+ public abstract void addRemoteCandidates(
+ final Vector<JingleS5BTransportPayload.Candidate> c, final String s);
+ public abstract void startTryingRemoteCandidates();
+ public abstract void stopTryingRemoteCandidates();
+
+ public abstract void startActivatingProxy(final JID proxy);
+ public abstract void stopActivatingProxy();
+
+ public abstract TransportSession createIBBSendSession(
+ final String sessionID, int blockSize, ReadBytestream r);
+ public abstract TransportSession createIBBReceiveSession(
+ final String sessionID, int size, WriteBytestream w);
+ public abstract TransportSession createRemoteCandidateSession(
+ ReadBytestream r, final JingleS5BTransportPayload.Candidate candidate);
+ public abstract TransportSession createRemoteCandidateSession(
+ WriteBytestream w, final JingleS5BTransportPayload.Candidate candidate);
+ public abstract TransportSession createLocalCandidateSession(
+ ReadBytestream r, final JingleS5BTransportPayload.Candidate candidate);
+ public abstract TransportSession createLocalCandidateSession(
+ WriteBytestream w, final JingleS5BTransportPayload.Candidate candidate);
+
+ public final Signal3<String /* sessionID */, Vector<JingleS5BTransportPayload.Candidate>, String /* dstAddr */> onLocalCandidatesGenerated = new Signal3<String, Vector<JingleS5BTransportPayload.Candidate>, String>();
+ public final Signal2<String /* sessionID */, JingleS5BTransportPayload.Candidate> onRemoteCandidateSelectFinished = new Signal2<String, JingleS5BTransportPayload.Candidate>();
+ public final Signal2<String /* sessionID */, ErrorPayload> onProxyActivated = new Signal2<String, ErrorPayload>();
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/FileTransferTransporterFactory.java b/src/com/isode/stroke/filetransfer/FileTransferTransporterFactory.java
new file mode 100644
index 0000000..5eb3286
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/FileTransferTransporterFactory.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+public interface FileTransferTransporterFactory {
+
+ public FileTransferTransporter createInitiatorTransporter(
+ final JID initiator,
+ final JID responder,
+ final FileTransferOptions options);
+ public FileTransferTransporter createResponderTransporter(
+ final JID initiator,
+ final JID responder,
+ final String s5bSessionID,
+ final FileTransferOptions options);
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/IBBReceiveSession.java b/src/com/isode/stroke/filetransfer/IBBReceiveSession.java
new file mode 100644
index 0000000..d5e831a
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/IBBReceiveSession.java
@@ -0,0 +1,131 @@
+/*
+ * 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.queries.GenericRequest;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.queries.SetResponder;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.IBB;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.signals.Signal1;
+import java.util.logging.Logger;
+
+public class IBBReceiveSession {
+
+ class IBBResponder extends SetResponder<IBB> {
+
+ public IBBResponder(IBBReceiveSession session, IQRouter router) {
+ super(new IBB(), router);
+ this.session = session;
+ this.sequenceNumber = 0;
+ this.receivedSize = 0;
+ setFinal(false);
+ }
+
+ public boolean handleSetRequest(final JID from, final JID to, final String id, IBB ibb) {
+ if (from.equals(session.from) && ibb.getStreamID().equals(session.id)) {
+ if (IBB.Action.Data.equals(ibb.getAction())) {
+ if (sequenceNumber == ibb.getSequenceNumber()) {
+ session.bytestream.write(ibb.getData());
+ receivedSize += ibb.getData().getSize();
+ sequenceNumber++;
+ sendResponse(from, id, null);
+ if (receivedSize >= session.size) {
+ if (receivedSize > session.size) {
+ logger_.warning("Received more data than expected");
+ }
+ session.finish(null);
+ }
+ }
+ else {
+ logger_.warning("Received data out of order");
+ sendError(from, id, ErrorPayload.Condition.NotAcceptable, ErrorPayload.Type.Cancel);
+ session.finish(new FileTransferError(FileTransferError.Type.ClosedError));
+ }
+ }
+ else if (IBB.Action.Open.equals(ibb.getAction())) {
+ logger_.fine("IBB open received");
+ sendResponse(from, id, null);
+ }
+ else if (IBB.Action.Close.equals(ibb.getAction())) {
+ logger_.fine("IBB close received");
+ sendResponse(from, id, null);
+ session.finish(new FileTransferError(FileTransferError.Type.ClosedError));
+ }
+ return true;
+ }
+ logger_.fine("wrong from/sessionID: " + from + " == " + session.from + " / " + ibb.getStreamID() + " == " + session.id);
+ return false;
+ }
+
+ private IBBReceiveSession session;
+ private int sequenceNumber;
+ private long receivedSize;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+ };
+
+ private String id = "";
+ private JID from = new JID();
+ private JID to = new JID();
+ private long size;
+ private WriteBytestream bytestream;
+ private IQRouter router;
+ private IBBResponder responder;
+ private boolean active;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public IBBReceiveSession(final String id, final JID from, final JID to, long size, WriteBytestream bytestream, IQRouter router) {
+ this.id = id;
+ this.from = from;
+ this.to = to;
+ this.size = size;
+ this.bytestream = bytestream;
+ this.router = router;
+ this.active = false;
+ assert(!id.isEmpty());
+ assert(from.isValid());
+ responder = new IBBResponder(this, router);
+ }
+
+ public void start() {
+ logger_.fine("receive session started");
+ active = true;
+ responder.start();
+ }
+
+ public void stop() {
+ logger_.fine("receive session stopped");
+ responder.stop();
+ if (active) {
+ if (router.isAvailable()) {
+ IBBRequest.create(to, from, IBB.createIBBClose(id), router).send();
+ }
+ finish(null);
+ }
+ }
+
+ public JID getSender() {
+ return from;
+ }
+
+ public JID getReceiver() {
+ return to;
+ }
+
+ public final Signal1<FileTransferError> onFinished = new Signal1<FileTransferError>();
+
+ private void finish(FileTransferError error) {
+ active = false;
+ onFinished.emit(error);
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/IBBReceiveTransportSession.java b/src/com/isode/stroke/filetransfer/IBBReceiveTransportSession.java
new file mode 100644
index 0000000..9a494a4
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/IBBReceiveTransportSession.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 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.signals.SignalConnection;
+
+public class IBBReceiveTransportSession extends TransportSession {
+
+ private IBBReceiveSession session;
+ private SignalConnection finishedConnection;
+ private SignalConnection bytesSentConnection;
+
+ public IBBReceiveTransportSession(IBBReceiveSession session) {
+ this.session = session;
+ finishedConnection = session.onFinished.connect(onFinished);
+ }
+
+ public void start() {
+ session.start();
+ }
+
+ public void stop() {
+ session.stop();
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/IBBRequest.java b/src/com/isode/stroke/filetransfer/IBBRequest.java
new file mode 100644
index 0000000..54fbbd6
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/IBBRequest.java
@@ -0,0 +1,29 @@
+/*
+ * 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.queries.GenericRequest;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.elements.IBB;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.jid.JID;
+
+public class IBBRequest extends GenericRequest<IBB> {
+
+ public static IBBRequest create(final JID from, final JID to, IBB payload, IQRouter router) {
+ return new IBBRequest(from, to, payload, router);
+ }
+
+ private IBBRequest(final JID from, final JID to, IBB payload, IQRouter router) {
+ super(IQ.Type.Set, from, to, payload, router);
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/IBBSendSession.java b/src/com/isode/stroke/filetransfer/IBBSendSession.java
new file mode 100644
index 0000000..5a812ff
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/IBBSendSession.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2010-2013 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.signals.Signal1;
+import com.isode.stroke.signals.Slot2;
+import com.isode.stroke.signals.Slot;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.elements.IBB;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.base.ByteArray;
+
+public class IBBSendSession {
+
+ private String id = "";
+ private JID from = new JID();
+ private JID to = new JID();
+ private ReadBytestream bytestream;
+ private IQRouter router;
+ private int blockSize;
+ private int sequenceNumber;
+ private boolean active;
+ private boolean waitingForData;
+
+ public IBBSendSession(final String id, final JID from, final JID to, ReadBytestream bytestream, IQRouter router) {
+ this.id = id;
+ this.from = from;
+ this.to = to;
+ this.bytestream = bytestream;
+ this.router = router;
+ this.blockSize = 4096;
+ this.sequenceNumber = 0;
+ this.active = false;
+ this.waitingForData = false;
+ bytestream.onDataAvailable.connect(new Slot() {
+ @Override
+ public void call() {
+ handleDataAvailable();
+ }
+ });
+ }
+
+ public void start() {
+ IBBRequest request = IBBRequest.create(from, to, IBB.createIBBOpen(id, (int)(blockSize)), router);
+ request.onResponse.connect(new Slot2<IBB, ErrorPayload>() {
+ @Override
+ public void call(IBB b, ErrorPayload e) {
+ handleIBBResponse(b, e);
+ }
+ });
+ active = true;
+ request.send();
+ }
+
+ public void stop() {
+ if (active && router.isAvailable()) {
+ IBBRequest.create(from, to, IBB.createIBBClose(id), router).send();
+ }
+ finish(null);
+ }
+
+ public JID getSender() {
+ return from;
+ }
+
+ public JID getReceiver() {
+ return to;
+ }
+
+ public void setBlockSize(int blockSize) {
+ this.blockSize = blockSize;
+ }
+
+ public final Signal1<FileTransferError> onFinished = new Signal1<FileTransferError>();
+ public final Signal1<Integer> onBytesSent = new Signal1<Integer>();
+
+ private void handleIBBResponse(IBB ibb, ErrorPayload error) {
+ if (error == null && active) {
+ if (!bytestream.isFinished()) {
+ sendMoreData();
+ }
+ else {
+ finish(null);
+ }
+ }
+ else {
+ finish(new FileTransferError(FileTransferError.Type.PeerError));
+ }
+ }
+ private void finish(FileTransferError error) {
+ active = false;
+ onFinished.emit(error);
+ }
+
+ private void sendMoreData() {
+ //try {
+ ByteArray data = bytestream.read(blockSize);
+ if (!data.isEmpty()) {
+ waitingForData = false;
+ IBBRequest request = IBBRequest.create(from, to, IBB.createIBBData(id, sequenceNumber, data), router);
+ sequenceNumber++;
+ request.onResponse.connect(new Slot2<IBB, ErrorPayload>() {
+ @Override
+ public void call(IBB b, ErrorPayload e) {
+ handleIBBResponse(b, e);
+ }
+ });
+ request.send();
+ onBytesSent.emit(data.getSize());
+ }
+ else {
+ waitingForData = true;
+ }
+ //}
+ //catch (BytestreamException e) {
+ // finish(new FileTransferError(FileTransferError.Type.ReadError));
+ //}
+ }
+
+ private void handleDataAvailable() {
+ if (waitingForData) {
+ sendMoreData();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/IBBSendTransportSession.java b/src/com/isode/stroke/filetransfer/IBBSendTransportSession.java
new file mode 100644
index 0000000..a37ce38
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/IBBSendTransportSession.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 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.signals.SignalConnection;
+
+public class IBBSendTransportSession extends TransportSession {
+
+ private IBBSendSession session;
+ private SignalConnection finishedConnection;
+ private SignalConnection bytesSentConnection;
+
+ public IBBSendTransportSession(IBBSendSession session) {
+ this.session = session;
+ finishedConnection = session.onFinished.connect(onFinished);
+ bytesSentConnection = session.onBytesSent.connect(onBytesSent);
+ }
+
+ public void start() {
+ session.start();
+ }
+
+ public void stop() {
+ session.stop();
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/IncomingFileTransfer.java b/src/com/isode/stroke/filetransfer/IncomingFileTransfer.java
new file mode 100644
index 0000000..53c29a4
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/IncomingFileTransfer.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Because of the lack of multiple inheritance in Java, this has to be done
+ * slightly differently from Swiften. What happens is that the methods in Swiften
+ * are provided abstract here. Any class implementing this interface directly/indirectly (through other interface) has to implement these methods.
+ * IncomingJingleFileTransfer implements this interface and will also need to implement methods from FileTransfer (which is an interface).
+ * @brief The IncomingFileTransfer interface is the general interface in Swiften
+ * for incoming file transfers.
+ */
+public interface IncomingFileTransfer extends FileTransfer {
+
+ public void accept(WriteBytestream w);
+ public void accept(WriteBytestream w, final FileTransferOptions options);
+
+ public JID getSender();
+ public JID getRecipient();
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/IncomingFileTransferManager.java b/src/com/isode/stroke/filetransfer/IncomingFileTransferManager.java
new file mode 100644
index 0000000..ffdf86e
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/IncomingFileTransferManager.java
@@ -0,0 +1,82 @@
+/*
+ * 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.signals.Signal1;
+import com.isode.stroke.jingle.IncomingJingleSessionHandler;
+import com.isode.stroke.jingle.JingleSessionManager;
+import com.isode.stroke.jingle.JingleSession;
+import com.isode.stroke.elements.JinglePayload;
+import com.isode.stroke.elements.JingleContentPayload;
+import com.isode.stroke.elements.JingleFileTransferDescription;
+import com.isode.stroke.elements.JingleS5BTransportPayload;
+import com.isode.stroke.jingle.Jingle;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.jid.JID;
+import java.util.logging.Logger;
+import java.util.Vector;
+
+public class IncomingFileTransferManager implements IncomingJingleSessionHandler {
+
+ private JingleSessionManager jingleSessionManager;
+ private IQRouter router;
+ private FileTransferTransporterFactory transporterFactory;
+ private TimerFactory timerFactory;
+ private CryptoProvider crypto;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public IncomingFileTransferManager(
+ JingleSessionManager jingleSessionManager,
+ IQRouter router,
+ FileTransferTransporterFactory transporterFactory,
+ TimerFactory timerFactory,
+ CryptoProvider crypto) {
+ this.jingleSessionManager = jingleSessionManager;
+ this.router = router;
+ this.transporterFactory = transporterFactory;
+ this.timerFactory = timerFactory;
+ this.crypto = crypto;
+ jingleSessionManager.addIncomingSessionHandler(this);
+ }
+
+ public Signal1<IncomingFileTransfer> onIncomingFileTransfer = new Signal1<IncomingFileTransfer>();
+
+ public boolean handleIncomingJingleSession(
+ JingleSession session,
+ final Vector<JingleContentPayload> contents,
+ final JID recipient) {
+ if (Jingle.getContentWithDescription(contents, new JingleFileTransferDescription()) != null) {
+ JingleContentPayload content = Jingle.getContentWithDescription(contents, new JingleFileTransferDescription());
+ if (content.getTransport(new JingleS5BTransportPayload()) != null) {
+ JingleFileTransferDescription description = content.getDescription(new JingleFileTransferDescription());
+ if (description != null) {
+ IncomingJingleFileTransfer transfer = new IncomingJingleFileTransfer(
+ recipient, session, content, transporterFactory, timerFactory, crypto);
+ onIncomingFileTransfer.emit(transfer);
+ }
+ else {
+ logger_.warning("Received a file-transfer request with no file description.");
+ session.sendTerminate(JinglePayload.Reason.Type.FailedApplication);
+ }
+ }
+ else {
+ session.sendTerminate(JinglePayload.Reason.Type.UnsupportedTransports);
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+} \ No newline at end of file
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
diff --git a/src/com/isode/stroke/filetransfer/IncrementalBytestreamHashCalculator.java b/src/com/isode/stroke/filetransfer/IncrementalBytestreamHashCalculator.java
new file mode 100644
index 0000000..e79a7a5
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/IncrementalBytestreamHashCalculator.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+/*
+ * Copyright (c) 2013-2014 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.crypto.Hash;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.stringcodecs.Hexify;
+
+public class IncrementalBytestreamHashCalculator {
+
+ private Hash md5Hasher;
+ private Hash sha1Hasher;
+ private ByteArray md5Hash;
+ private ByteArray sha1Hash;
+
+ public IncrementalBytestreamHashCalculator(boolean doMD5, boolean doSHA1, CryptoProvider crypto) {
+ md5Hasher = doMD5 ? crypto.createMD5() : null;
+ sha1Hasher = doSHA1 ? crypto.createSHA1() : null;
+ }
+
+ public void feedData(final ByteArray data) {
+ if (md5Hasher != null) {
+ md5Hasher.update(data);
+ }
+ if (sha1Hasher != null) {
+ sha1Hasher.update(data);
+ }
+ }
+
+ /*void feedData(const SafeByteArray& data) {
+ if (md5Hasher) {
+ md5Hasher.update(createByteArray(data.data(), data.size()));
+ }
+ if (sha1Hasher) {
+ sha1Hasher.update(createByteArray(data.data(), data.size()));
+ }
+ }*/
+
+ public ByteArray getSHA1Hash() {
+ assert(sha1Hasher != null);
+ if (sha1Hash == null) {
+ sha1Hash = sha1Hasher.getHash();
+ }
+ return sha1Hash;
+ }
+
+ public ByteArray getMD5Hash() {
+ assert(md5Hasher != null);
+ if (md5Hash == null) {
+ md5Hash = md5Hasher.getHash();
+ }
+ return md5Hash;
+ }
+
+ public String getSHA1String() {
+ assert(sha1Hasher != null);
+ return Hexify.hexify(getSHA1Hash());
+ }
+
+ public String getMD5String() {
+ assert(md5Hasher != null);
+ return Hexify.hexify(getMD5Hash());
+ }
+} \ No newline at end of file
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
diff --git a/src/com/isode/stroke/filetransfer/LocalJingleTransportCandidateGenerator.java b/src/com/isode/stroke/filetransfer/LocalJingleTransportCandidateGenerator.java
new file mode 100644
index 0000000..1cacda7
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/LocalJingleTransportCandidateGenerator.java
@@ -0,0 +1,215 @@
+/*
+ * 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.base.IDGenerator;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Slot;
+import com.isode.stroke.signals.Signal1;
+import java.util.Vector;
+import java.util.logging.Logger;
+import com.isode.stroke.network.HostAddressPort;
+import com.isode.stroke.network.HostAddress;
+import com.isode.stroke.elements.JingleS5BTransportPayload;
+import com.isode.stroke.elements.S5BProxyRequest;
+
+public class LocalJingleTransportCandidateGenerator {
+
+ public static final int LOCAL_PREFERENCE = 0;
+ private SOCKS5BytestreamServerManager s5bServerManager;
+ private SOCKS5BytestreamProxiesManager s5bProxy;
+ private JID ownJID;
+ private IDGenerator idGenerator;
+ //private SOCKS5BytestreamServerInitializeRequest s5bServerInitializeRequest;
+ private SOCKS5BytestreamServerResourceUser s5bServerResourceUser_;
+ private SOCKS5BytestreamServerPortForwardingUser s5bServerPortForwardingUser_;
+ private boolean triedServerInit_;
+ private boolean triedForwarding_;
+ private boolean triedProxyDiscovery_;
+ private FileTransferOptions options_;
+ private SignalConnection onDiscoveredProxiesChangedConnection;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public LocalJingleTransportCandidateGenerator(
+ SOCKS5BytestreamServerManager s5bServerManager,
+ SOCKS5BytestreamProxiesManager s5bProxy,
+ final JID ownJID,
+ IDGenerator idGenerator,
+ final FileTransferOptions options) {
+ this.s5bServerManager = s5bServerManager;
+ this.s5bProxy = s5bProxy;
+ this.ownJID = ownJID;
+ this.idGenerator = idGenerator;
+ triedProxyDiscovery_ = false;
+ triedServerInit_ = false;
+ triedForwarding_ = false;
+ this.options_ = options;
+ }
+
+ public void start() {
+ //assert(s5bServerInitializeRequest == null);
+ if (options_.isDirectAllowed() || options_.isAssistedAllowed()) {
+ s5bServerResourceUser_ = s5bServerManager.aquireResourceUser();
+ if (s5bServerResourceUser_.isInitialized()) {
+ handleS5BServerInitialized(true);
+ }
+ else {
+ s5bServerResourceUser_.onSuccessfulInitialized.connect(new Slot1<Boolean>() {
+ @Override
+ public void call(Boolean b) {
+ handleS5BServerInitialized(b);
+ }
+ });
+ }
+ } else {
+ handleS5BServerInitialized(false);
+ }
+
+ if (options_.isProxiedAllowed()) {
+ onDiscoveredProxiesChangedConnection = s5bProxy.onDiscoveredProxiesChanged.connect(new Slot() {
+ @Override
+ public void call() {
+ handleDiscoveredProxiesChanged();
+ }
+ });
+ if (s5bProxy.getOrDiscoverS5BProxies() != null) {
+ handleDiscoveredProxiesChanged();
+ }
+ }
+ }
+
+ public void stop() {
+ onDiscoveredProxiesChangedConnection.disconnect();
+ if (s5bServerPortForwardingUser_ != null) {
+ s5bServerPortForwardingUser_.onSetup.disconnectAll();
+ s5bServerPortForwardingUser_ = null;
+ }
+ if (s5bServerResourceUser_ != null) {
+ s5bServerResourceUser_.onSuccessfulInitialized.disconnectAll();
+ s5bServerResourceUser_ = null;
+ }
+ }
+
+ public Signal1<Vector<JingleS5BTransportPayload.Candidate>> onLocalTransportCandidatesGenerated = new Signal1<Vector<JingleS5BTransportPayload.Candidate>>();
+
+ private void handleS5BServerInitialized(boolean success) {
+ if (s5bServerResourceUser_ != null) {
+ s5bServerResourceUser_.onSuccessfulInitialized.disconnectAll();
+ }
+ triedServerInit_ = true;
+ if (success) {
+ if (options_.isAssistedAllowed()) {
+ // try to setup port forwarding
+ s5bServerPortForwardingUser_ = s5bServerManager.aquirePortForwardingUser();
+ s5bServerPortForwardingUser_.onSetup.connect(new Slot1<Boolean>() {
+ @Override
+ public void call(Boolean b) {
+ handlePortForwardingSetup(b);
+ }
+ });
+ if (s5bServerPortForwardingUser_.isForwardingSetup()) {
+ handlePortForwardingSetup(true);
+ }
+ }
+ }
+ else {
+ logger_.warning("Unable to start SOCKS5 server\n");
+ if (s5bServerResourceUser_ != null) {
+ s5bServerResourceUser_.onSuccessfulInitialized.disconnectAll();
+ }
+ s5bServerResourceUser_ = null;
+ handlePortForwardingSetup(false);
+ }
+ checkS5BCandidatesReady();
+ }
+
+ private void handlePortForwardingSetup(boolean success) {
+ if (s5bServerPortForwardingUser_ != null) {
+ s5bServerPortForwardingUser_.onSetup.disconnectAll();
+ }
+ triedForwarding_ = true;
+ checkS5BCandidatesReady();
+ }
+
+ private void handleDiscoveredProxiesChanged() {
+ if (s5bProxy != null) {
+ s5bProxy.onDiscoveredProxiesChanged.disconnectAll();
+ }
+ triedProxyDiscovery_ = true;
+ checkS5BCandidatesReady();
+ }
+
+ private void checkS5BCandidatesReady() {
+ if ((!options_.isDirectAllowed() || (options_.isDirectAllowed() && triedServerInit_)) &&
+ (!options_.isProxiedAllowed() || (options_.isProxiedAllowed() && triedProxyDiscovery_)) &&
+ (!options_.isAssistedAllowed() || (options_.isAssistedAllowed() && triedForwarding_))) {
+ emitOnLocalTransportCandidatesGenerated();
+ }
+ }
+
+ private void emitOnLocalTransportCandidatesGenerated() {
+ Vector<JingleS5BTransportPayload.Candidate> candidates = new Vector<JingleS5BTransportPayload.Candidate>();
+
+ if (options_.isDirectAllowed()) {
+ // get direct candidates
+ Vector<HostAddressPort> directCandidates = s5bServerManager.getHostAddressPorts();
+ for(HostAddressPort addressPort : directCandidates) {
+ JingleS5BTransportPayload.Candidate candidate = new JingleS5BTransportPayload.Candidate();
+ candidate.type = JingleS5BTransportPayload.Candidate.Type.DirectType;
+ candidate.jid = ownJID;
+ candidate.hostPort = addressPort;
+ candidate.priority = 65536 * 126 + LOCAL_PREFERENCE;
+ candidate.cid = idGenerator.generateID();
+ candidates.add(candidate);
+ }
+ }
+
+ if (options_.isAssistedAllowed()) {
+ // get assissted candidates
+ Vector<HostAddressPort> assisstedCandidates = s5bServerManager.getAssistedHostAddressPorts();
+ for(HostAddressPort addressPort : assisstedCandidates) {
+ JingleS5BTransportPayload.Candidate candidate = new JingleS5BTransportPayload.Candidate();
+ candidate.type = JingleS5BTransportPayload.Candidate.Type.AssistedType;
+ candidate.jid = ownJID;
+ candidate.hostPort = addressPort;
+ candidate.priority = 65536 * 120 + LOCAL_PREFERENCE;
+ candidate.cid = idGenerator.generateID();
+ candidates.add(candidate);
+ }
+ }
+
+ if (options_.isProxiedAllowed() && s5bProxy.getOrDiscoverS5BProxies() != null) {
+ for(S5BProxyRequest proxy : s5bProxy.getOrDiscoverS5BProxies()) {
+ if (proxy.getStreamHost() != null) { // FIXME: Added this test, because there were cases where this wasn't initialized. Investigate this. (Remko)
+ JingleS5BTransportPayload.Candidate candidate = new JingleS5BTransportPayload.Candidate();
+ candidate.type = JingleS5BTransportPayload.Candidate.Type.ProxyType;
+ candidate.jid = (proxy.getStreamHost()).jid;
+ HostAddress address = new HostAddress((proxy.getStreamHost()).host);
+ assert(address.isValid());
+ candidate.hostPort = new HostAddressPort(address, (proxy.getStreamHost()).port);
+ candidate.priority = 65536 * 10 + LOCAL_PREFERENCE;
+ candidate.cid = idGenerator.generateID();
+ candidates.add(candidate);
+ }
+ }
+ }
+
+ onLocalTransportCandidatesGenerated.emit(candidates);
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/OutgoingFileTransfer.java b/src/com/isode/stroke/filetransfer/OutgoingFileTransfer.java
new file mode 100644
index 0000000..52aa378
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/OutgoingFileTransfer.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+/**
+ * Because of the lack of multiple inheritance in Java, this has to be done
+ * slightly differently from Swiften. What happens is that the methods in Swiften
+ * are provided abstract here. Any class implementing this interface directly/indirectly (through other interface) has to implement these methods.
+ * OutgoingJingleFileTransfer and OutgoingSIFileTransfer implements this interface and will also need to implement methods from FileTransfer (which is an interface).
+ */
+public interface OutgoingFileTransfer extends FileTransfer {
+
+ public void start();
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/OutgoingFileTransferManager.java b/src/com/isode/stroke/filetransfer/OutgoingFileTransferManager.java
new file mode 100644
index 0000000..ce848ee
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/OutgoingFileTransferManager.java
@@ -0,0 +1,79 @@
+/*
+ * 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.signals.Signal1;
+import com.isode.stroke.jingle.IncomingJingleSessionHandler;
+import com.isode.stroke.jingle.JingleSessionManager;
+import com.isode.stroke.jingle.JingleSession;
+import com.isode.stroke.elements.JinglePayload;
+import com.isode.stroke.elements.JingleContentPayload;
+import com.isode.stroke.elements.JingleFileTransferDescription;
+import com.isode.stroke.elements.JingleFileTransferFileInfo;
+import com.isode.stroke.jingle.JingleSessionImpl;
+import com.isode.stroke.elements.JingleS5BTransportPayload;
+import com.isode.stroke.jingle.Jingle;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.base.IDGenerator;
+
+public class OutgoingFileTransferManager {
+
+ private JingleSessionManager jingleSessionManager;
+ private IQRouter iqRouter;
+ private FileTransferTransporterFactory transporterFactory;
+ private TimerFactory timerFactory;
+ private IDGenerator idGenerator;
+ private CryptoProvider crypto;
+
+ public OutgoingFileTransferManager(
+ JingleSessionManager jingleSessionManager,
+ IQRouter router,
+ FileTransferTransporterFactory transporterFactory,
+ TimerFactory timerFactory,
+ CryptoProvider crypto) {
+ this.jingleSessionManager = jingleSessionManager;
+ this.iqRouter = router;
+ this.transporterFactory = transporterFactory;
+ this.timerFactory = timerFactory;
+ this.idGenerator = idGenerator;
+ this.crypto = crypto;
+ idGenerator = new IDGenerator();
+ }
+
+ public OutgoingFileTransfer createOutgoingFileTransfer(
+ final JID from,
+ final JID recipient,
+ ReadBytestream readBytestream,
+ final JingleFileTransferFileInfo fileInfo,
+ final FileTransferOptions config) {
+ JingleSessionImpl jingleSession = new JingleSessionImpl(from, recipient, idGenerator.generateID(), iqRouter);
+ jingleSessionManager.registerOutgoingSession(from, jingleSession);
+ return new OutgoingJingleFileTransfer(
+ recipient,
+ jingleSession,
+ readBytestream,
+ transporterFactory,
+ timerFactory,
+ idGenerator,
+ fileInfo,
+ config,
+ crypto);
+ }
+} \ No newline at end of file
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
diff --git a/src/com/isode/stroke/filetransfer/OutgoingSIFileTransfer.java b/src/com/isode/stroke/filetransfer/OutgoingSIFileTransfer.java
new file mode 100644
index 0000000..4ba17de
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/OutgoingSIFileTransfer.java
@@ -0,0 +1,149 @@
+/*
+ * 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.queries.IQRouter;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.StreamInitiation;
+import com.isode.stroke.elements.Bytestreams;
+
+public class OutgoingSIFileTransfer 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;
+ }
+
+ private String id = "";
+ private JID from;
+ private JID to;
+ private String name = "";
+ private long size;
+ private String description = "";
+ private ReadBytestream bytestream;
+ private IQRouter iqRouter;
+ private SOCKS5BytestreamServer socksServer;
+ private IBBSendSession ibbSession;
+
+ public OutgoingSIFileTransfer(final String id, final JID from, final JID to, final String name, long size, final String description, ReadBytestream bytestream, IQRouter iqRouter, SOCKS5BytestreamServer socksServer) {
+ this.id = id;
+ this.from = from;
+ this.to = to;
+ this.name = name;
+ this.size = size;
+ this.description = description;
+ this.bytestream = bytestream;
+ this.iqRouter = iqRouter;
+ this.socksServer = socksServer;
+ this.ibbSession = ibbSession;
+ }
+
+ /**
+ * OutgoingFileTransferMethod.
+ */
+ @Override
+ public void start() {
+ /*
+ StreamInitiation::ref streamInitiation(new StreamInitiation());
+ streamInitiation.setID(id);
+ streamInitiation.setFileInfo(StreamInitiationFileInfo(name, description, size));
+ //streamInitiation.addProvidedMethod("http://jabber.org/protocol/bytestreams");
+ streamInitiation.addProvidedMethod("http://jabber.org/protocol/ibb");
+ StreamInitiationRequest::ref request = StreamInitiationRequest::create(to, streamInitiation, iqRouter);
+ request.onResponse.connect(boost::bind(&OutgoingSIFileTransfer::handleStreamInitiationRequestResponse, this, _1, _2));
+ request.send();
+ */
+ }
+
+ public void stop() {
+ }
+
+ public final Signal1<FileTransferError> onFinished = new Signal1<FileTransferError>();
+
+ private void handleStreamInitiationRequestResponse(StreamInitiation stream, ErrorPayload error) {
+ /*
+ if (error) {
+ finish(FileTransferError());
+ }
+ else {
+ if (response->getRequestedMethod() == "http://jabber.org/protocol/bytestreams") {
+ socksServer->addReadBytestream(id, from, to, bytestream);
+ Bytestreams::ref bytestreams(new Bytestreams());
+ bytestreams->setStreamID(id);
+ HostAddressPort addressPort = socksServer->getAddressPort();
+ bytestreams->addStreamHost(Bytestreams::StreamHost(addressPort.getAddress().toString(), from, addressPort.getPort()));
+ BytestreamsRequest::ref request = BytestreamsRequest::create(to, bytestreams, iqRouter);
+ request->onResponse.connect(boost::bind(&OutgoingSIFileTransfer::handleBytestreamsRequestResponse, this, _1, _2));
+ request->send();
+ }
+ else if (response->getRequestedMethod() == "http://jabber.org/protocol/ibb") {
+ ibbSession = boost::make_shared<IBBSendSession>(id, from, to, bytestream, iqRouter);
+ ibbSession->onFinished.connect(boost::bind(&OutgoingSIFileTransfer::handleIBBSessionFinished, this, _1));
+ ibbSession->start();
+ }
+ }
+ */
+ }
+
+ private void handleBytestreamsRequestResponse(Bytestreams stream, ErrorPayload error) {
+ /*
+ if (error) {
+ finish(FileTransferError());
+ }
+ */
+ //socksServer->onTransferFinished.connect();
+ }
+
+ private void finish(FileTransferError error) {
+ /*
+ if (ibbSession) {
+ ibbSession->onFinished.disconnect(boost::bind(&OutgoingSIFileTransfer::handleIBBSessionFinished, this, _1));
+ ibbSession.reset();
+ }
+ socksServer->removeReadBytestream(id, from, to);
+ onFinished(error);
+ */
+ }
+
+ private void handleIBBSessionFinished(FileTransferError error) {
+ //finish(error);
+ }
+
+ public void cancel() {
+
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/ReadBytestream.java b/src/com/isode/stroke/filetransfer/ReadBytestream.java
new file mode 100644
index 0000000..a247653
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/ReadBytestream.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010 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.signals.Signal1;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.base.ByteArray;
+
+public abstract class ReadBytestream {
+
+ public final Signal onDataAvailable = new Signal();
+ public final Signal1<ByteArray> onRead = new Signal1<ByteArray>();
+
+ /**
+ * Return an empty vector if no more data is available.
+ * Use onDataAvailable signal for signaling there is data available again.
+ */
+ public abstract ByteArray read(int size);
+
+ public abstract boolean isFinished();
+
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/RemoteJingleTransportCandidateSelector.java b/src/com/isode/stroke/filetransfer/RemoteJingleTransportCandidateSelector.java
new file mode 100644
index 0000000..1d1c822
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/RemoteJingleTransportCandidateSelector.java
@@ -0,0 +1,111 @@
+/*
+ * 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 java.util.PriorityQueue;
+import com.isode.stroke.network.ConnectionFactory;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.network.Connection;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Signal2;
+import com.isode.stroke.elements.JingleS5BTransportPayload;
+import java.util.logging.Logger;
+import java.util.Vector;
+
+public class RemoteJingleTransportCandidateSelector {
+
+ private ConnectionFactory connectionFactory;
+ private TimerFactory timerFactory;
+
+ private PriorityQueue<JingleS5BTransportPayload.Candidate> candidates = new PriorityQueue<JingleS5BTransportPayload.Candidate>(25, new JingleS5BTransportPayload.CompareCandidate());
+ private SOCKS5BytestreamClientSession s5bSession;
+ private SignalConnection sessionReadyConnection;
+ private JingleS5BTransportPayload.Candidate lastCandidate;
+ private String socks5DstAddr;
+ private FileTransferOptions options;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public RemoteJingleTransportCandidateSelector(ConnectionFactory connectionFactory, TimerFactory timerFactory, final FileTransferOptions options) {
+ this.connectionFactory = connectionFactory;
+ this.timerFactory = timerFactory;
+ this.options = options;
+ }
+
+ public void addCandidates(final Vector<JingleS5BTransportPayload.Candidate> candidates) {
+ for(JingleS5BTransportPayload.Candidate c : candidates) {
+ this.candidates.add(c);
+ }
+ }
+
+ public void setSOCKS5DstAddr(final String socks5DstAddr) {
+ this.socks5DstAddr = socks5DstAddr;
+ }
+
+ public void startSelectingCandidate() {
+ tryNextCandidate();
+ }
+
+ public void stopSelectingCandidate() {
+ if (s5bSession != null) {
+ sessionReadyConnection.disconnect();
+ s5bSession.stop();
+ }
+ }
+
+ public Signal2<JingleS5BTransportPayload.Candidate, SOCKS5BytestreamClientSession> onCandidateSelectFinished = new Signal2<JingleS5BTransportPayload.Candidate, SOCKS5BytestreamClientSession>();
+
+ private void tryNextCandidate() {
+ if (candidates.isEmpty()) {
+ logger_.fine("No more candidates\n");
+ onCandidateSelectFinished.emit(null, null);
+ }
+ else {
+ lastCandidate = candidates.peek();
+ candidates.poll();
+ logger_.fine("Trying candidate " + lastCandidate.cid + "\n");
+ if ((lastCandidate.type.equals(JingleS5BTransportPayload.Candidate.Type.DirectType) && options.isDirectAllowed()) ||
+ (lastCandidate.type.equals(JingleS5BTransportPayload.Candidate.Type.AssistedType) && options.isAssistedAllowed()) ||
+ (lastCandidate.type.equals(JingleS5BTransportPayload.Candidate.Type.ProxyType) && options.isProxiedAllowed())) {
+ Connection connection = connectionFactory.createConnection();
+ s5bSession = new SOCKS5BytestreamClientSession(connection, lastCandidate.hostPort, socks5DstAddr, timerFactory);
+ sessionReadyConnection = s5bSession.onSessionReady.connect(new Slot1<Boolean>() {
+ @Override
+ public void call(Boolean b) {
+ handleSessionReady(b);
+ }
+ });
+ s5bSession.start();
+ }
+ else {
+ logger_.fine("Can't handle this type of candidate\n");
+ tryNextCandidate();
+ }
+ }
+ }
+
+ private void handleSessionReady(boolean error) {
+ sessionReadyConnection.disconnect();
+ if (error) {
+ s5bSession = null;
+ tryNextCandidate();
+ }
+ else {
+ onCandidateSelectFinished.emit(lastCandidate, s5bSession);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/S5BTransportSession.java b/src/com/isode/stroke/filetransfer/S5BTransportSession.java
new file mode 100644
index 0000000..768354f
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/S5BTransportSession.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 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.signals.Signal1;
+import com.isode.stroke.signals.SignalConnection;
+
+public class S5BTransportSession<T> extends TransportSession {
+
+ public S5BTransportSession(
+ T session,
+ ReadBytestream readStream) {
+ this.session = session;
+ this.readStream = readStream;
+ initialize();
+ }
+
+ public S5BTransportSession(
+ T session,
+ WriteBytestream writeStream) {
+ this.session = session;
+ this.writeStream = writeStream;
+ initialize();
+ }
+
+ public void start() {
+ if (readStream != null) {
+ //session.startSending(readStream);
+ }
+ else {
+ //session.startReceiving(writeStream);
+ }
+ }
+
+ public void stop() {
+ //session.stop();
+ }
+
+ private void initialize() {
+ //finishedConnection = session.onFinished.connect(onFinished);
+ //bytesSentConnection = session.onBytesSent.connect(onBytesSent);
+ }
+
+ private T session;
+ private ReadBytestream readStream;
+ private WriteBytestream writeStream;
+
+ private SignalConnection finishedConnection;
+ private SignalConnection bytesSentConnection;
+}
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java
new file mode 100644
index 0000000..25aadc4
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+/*
+ * Copyright (c) 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.base.ByteArray;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.network.Connection;
+import com.isode.stroke.network.HostAddressPort;
+import com.isode.stroke.network.Timer;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.Slot;
+import com.isode.stroke.stringcodecs.Hexify;
+import java.util.logging.Logger;
+
+/**
+ * A session which has been connected to a SOCKS5 server (requester).
+ *
+ */
+public class SOCKS5BytestreamClientSession {
+
+ public enum State {
+ Initial(0),
+ Hello(1),
+ Authenticating(2),
+ Ready(3),
+ Writing(4),
+ Reading(5),
+ Finished(6);
+ private State(int x) {
+ description = x;
+ }
+ public int description;
+ };
+
+ private Connection connection;
+ private HostAddressPort addressPort;
+ private String destination; // hexify(SHA1(sessionID + requester + target))
+
+ private State state;
+
+ private ByteArray unprocessedData;
+ private ByteArray authenticateAddress;
+
+ private int chunkSize;
+ private WriteBytestream writeBytestream;
+ private ReadBytestream readBytestream;
+
+ private Timer weFailedTimeout;
+
+ private SignalConnection connectFinishedConnection;
+ private SignalConnection dataWrittenConnection;
+ private SignalConnection dataReadConnection;
+ private SignalConnection disconnectedConnection;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public SOCKS5BytestreamClientSession(Connection connection, final HostAddressPort addressPort, final String destination, TimerFactory timerFactory) {
+ this.connection = connection;
+ this.addressPort = addressPort;
+ this.destination = destination;
+ this.state = State.Initial;
+ this.chunkSize = 131072;
+ weFailedTimeout = timerFactory.createTimer(3000);
+ weFailedTimeout.onTick.connect(new Slot() {
+ @Override
+ public void call() {
+ handleWeFailedTimeout();
+ }
+ });
+ }
+
+ public void start() {
+ assert(state == State.Initial);
+ logger_.fine("Trying to connect via TCP to " + addressPort.toString() + ".\n");
+ weFailedTimeout.start();
+ connectFinishedConnection = connection.onConnectFinished.connect(new Slot1<Boolean>() {
+ @Override
+ public void call(Boolean b) {
+ handleConnectFinished(b);
+ }
+ });
+ connection.connect(addressPort);
+ }
+
+ public void stop() {
+ logger_.fine("\n");
+ if (state.description < State.Ready.description) {
+ weFailedTimeout.stop();
+ }
+ if (state == State.Finished) {
+ return;
+ }
+ closeConnection();
+ readBytestream = null;
+ state = State.Finished;
+ }
+
+ public void startReceiving(WriteBytestream writeStream) {
+ if (state == State.Ready) {
+ state = State.Reading;
+ writeBytestream = writeStream;
+ writeBytestream.write(unprocessedData);
+ //onBytesReceived(unprocessedData.size());
+ unprocessedData.clear();
+ } else {
+ logger_.fine("Session isn't ready for transfer yet!\n");
+ }
+ }
+
+ public void startSending(ReadBytestream readStream) {
+ if (state == State.Ready) {
+ state = State.Writing;
+ readBytestream = readStream;
+ dataWrittenConnection = connection.onDataWritten.connect(new Slot() {
+ @Override
+ public void call() {
+ sendData();
+ }
+ });
+ sendData();
+ } else {
+ logger_.fine("Session isn't ready for transfer yet!\n");
+ }
+ }
+
+ public HostAddressPort getAddressPort() {
+ return addressPort;
+ }
+
+ public Signal1<Boolean /*error*/> onSessionReady = new Signal1<Boolean>();
+
+ public Signal1<FileTransferError> onFinished = new Signal1<FileTransferError>();
+ public Signal1<Integer> onBytesSent = new Signal1<Integer>();
+ //public boost::signal<void (size_t)> onBytesReceived;
+
+ private void process() {
+ logger_.fine("unprocessedData.size(): " + unprocessedData.getSize() + "\n");
+ ByteArray bndAddress = new ByteArray();
+ switch(state) {
+ case Initial:
+ hello();
+ break;
+ case Hello:
+ if (unprocessedData.getSize() > 1) {
+ char version = (char)unprocessedData.getData()[0];
+ char authMethod = (char)unprocessedData.getData()[1];
+ if (version != 5 || authMethod != 0) {
+ // signal failure to upper level
+ finish(true);
+ return;
+ }
+ unprocessedData.clear();
+ authenticate();
+ }
+ break;
+ case Authenticating:
+ if (unprocessedData.getSize() < 5) {
+ // need more data to start progressing
+ break;
+ }
+ if (unprocessedData.getData()[0] != (byte)0x05) {
+ // wrong version
+ // disconnect & signal failure
+ finish(true);
+ break;
+ }
+ if (unprocessedData.getData()[1] != (byte)0x00) {
+ // no success
+ // disconnect & signal failure
+ finish(true);
+ break;
+ }
+ if (unprocessedData.getData()[3] != (byte)0x03) {
+ // we expect x'03' = DOMAINNAME here
+ // disconnect & signal failure
+ finish(true);
+ break;
+ }
+ if (((int)unprocessedData.getData()[4]) + 1 > unprocessedData.getSize() + 5) {
+ // complete domainname and port not available yet
+ break;
+ }
+ //-----bndAddress = new ByteArray(&vecptr(unprocessedData)[5], unprocessedData[4]);
+ if (unprocessedData.getData()[unprocessedData.getData()[4] + 5] != 0 && new ByteArray(destination).equals(bndAddress)) {
+ // we expect a 0 as port
+ // disconnect and fail
+ finish(true);
+ }
+ unprocessedData.clear();
+ state = State.Ready;
+ logger_.fine("session ready\n");
+ // issue ready signal so the bytestream can be used for reading or writing
+ weFailedTimeout.stop();
+ onSessionReady.emit(false);
+ break;
+ case Ready:
+ logger_.fine("Received further data in Ready state.\n");
+ break;
+ case Reading:
+ case Writing:
+ case Finished:
+ logger_.fine("Unexpected receive of data. Current state: " + state + "\n");
+ logger_.fine("Data: " + Hexify.hexify(unprocessedData) + "\n");
+ unprocessedData.clear();
+ //assert(false);
+ }
+ }
+
+ private void hello() {
+ // Version 5, 1 auth method, No authentication
+ final SafeByteArray hello = new SafeByteArray(new byte[]{0x05, 0x01, 0x00});
+ connection.write(hello);
+ state = State.Hello;
+ }
+
+ private void authenticate() {
+ logger_.fine("\n");
+ SafeByteArray header = new SafeByteArray(new byte[]{0x05, 0x01, 0x00, 0x03});
+ SafeByteArray message = header;
+ String destinationlength = Integer.toString(destination.length());
+ message.append(new SafeByteArray(destinationlength));
+ authenticateAddress = new ByteArray(destination);
+ message.append(authenticateAddress);
+ message.append(new SafeByteArray(new byte[]{0x00, 0x00})); // 2 byte for port
+ connection.write(message);
+ state = State.Authenticating;
+ }
+
+ private void handleConnectFinished(boolean error) {
+ connectFinishedConnection.disconnect();
+ if (error) {
+ logger_.fine("Failed to connect via TCP to " + addressPort.toString() + "." + "\n");
+ finish(true);
+ } else {
+ logger_.fine("Successfully connected via TCP" + addressPort.toString() + "." + "\n");
+ disconnectedConnection = connection.onDisconnected.connect(new Slot1<Connection.Error>() {
+ @Override
+ public void call(Connection.Error e) {
+ handleDisconnected(e);
+ }
+ });
+ dataReadConnection = connection.onDataRead.connect(new Slot1<SafeByteArray>() {
+ @Override
+ public void call(SafeByteArray b) {
+ handleDataRead(b);
+ }
+ });
+ weFailedTimeout.stop();
+ weFailedTimeout.start();
+ process();
+ }
+ }
+
+ private void handleDataRead(SafeByteArray data) {
+ logger_.fine("state: " + state + " data.size() = " + data.getSize() + "\n");
+ if (state != State.Reading) {
+ unprocessedData.append(data);
+ process();
+ }
+ else {
+ //---------writeBytestream.write(new ByteArray(vecptr(*data), data.size()));
+ //onBytesReceived(data.size());
+ }
+ }
+
+ private void handleDisconnected(final Connection.Error error) {
+ logger_.fine((error != null ? (error == Connection.Error.ReadError ? "Read Error" : "Write Error") : "No Error") + "\n");
+ if (error != null) {
+ finish(true);
+ }
+ }
+
+ private void handleWeFailedTimeout() {
+ logger_.fine("Failed due to timeout!\n");
+ finish(true);
+ }
+
+ private void finish(boolean error) {
+ logger_.fine("\n");
+ if (state.description < State.Ready.description) {
+ weFailedTimeout.stop(); }
+ closeConnection();
+ readBytestream = null;
+ if (State.Initial.equals(state) || State.Hello.equals(state) || State.Authenticating.equals(state)) {
+ onSessionReady.emit(true);
+ }
+ else {
+ state = State.Finished;
+ if (error) {
+ onFinished.emit(new FileTransferError(FileTransferError.Type.ReadError));
+ } else {
+ onFinished.emit(null);
+ }
+ }
+ }
+
+ private void sendData() {
+ if (!readBytestream.isFinished()) {
+ //try {
+ ByteArray dataToSend = readBytestream.read((int)(chunkSize));
+ connection.write(new SafeByteArray(dataToSend));
+ onBytesSent.emit(dataToSend.getSize());
+ //}
+ //catch (BytestreamException e) {
+ // finish(true);
+ //}
+ }
+ else {
+ finish(false);
+ }
+ }
+
+ private void closeConnection() {
+ connectFinishedConnection.disconnect();
+ dataWrittenConnection.disconnect();
+ dataReadConnection.disconnect();
+ disconnectedConnection.disconnect();
+ connection.disconnect();
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxiesManager.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxiesManager.java
new file mode 100644
index 0000000..14c7ea3
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxiesManager.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+/*
+ * Copyright (c) 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.network.Connection;
+import com.isode.stroke.network.ConnectionFactory;
+import com.isode.stroke.network.DomainNameResolver;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.network.HostAddressPort;
+import com.isode.stroke.network.DomainNameResolveError;
+import com.isode.stroke.network.DomainNameAddressQuery;
+import com.isode.stroke.network.HostAddress;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Slot2;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.elements.S5BProxyRequest;
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+import java.util.Collection;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.logging.Logger;
+
+/**
+ * - manages list of working S5B proxies
+ * - creates initial connections (for the candidates you provide)
+ */
+public class SOCKS5BytestreamProxiesManager {
+
+ private ConnectionFactory connectionFactory_;
+ private TimerFactory timerFactory_;
+ private DomainNameResolver resolver_;
+ private IQRouter iqRouter_;
+ private JID serviceRoot_;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+ private SignalConnection onSessionReadyConnection;
+ private SignalConnection onFinishedConnection;
+
+ private static class Pair {
+ public JID jid;
+ public SOCKS5BytestreamClientSession sock5;
+
+ public Pair(JID j, SOCKS5BytestreamClientSession c) {this.jid = j; this.sock5 = c; }
+ }
+
+ private Map<String, Collection<Pair> > proxySessions_ = new HashMap<String, Collection<Pair> >();
+
+ private SOCKS5BytestreamProxyFinder proxyFinder_;
+
+ private Collection<S5BProxyRequest> localS5BProxies_;
+
+ public SOCKS5BytestreamProxiesManager(ConnectionFactory connFactory, TimerFactory timeFactory, DomainNameResolver resolver, IQRouter iqRouter, final JID serviceRoot) {
+ connectionFactory_ = connFactory;
+ timerFactory_ = timeFactory;
+ resolver_ = resolver;
+ iqRouter_ = iqRouter;
+ serviceRoot_ = serviceRoot;
+ }
+
+ public void addS5BProxy(S5BProxyRequest proxy) {
+ if (proxy != null) {
+ //SWIFT_LOG_ASSERT(HostAddress(proxy.getStreamHost().get().host).isValid(), warning) << std::endl;
+ if (localS5BProxies_ == null) {
+ localS5BProxies_ = new Vector<S5BProxyRequest>();
+ }
+ localS5BProxies_.add(proxy);
+ }
+ }
+
+ /*
+ * Returns a list of external S5B proxies. If the optinal return value is not initialized a discovery process has been started and
+ * onDiscoveredProxiesChanged signal will be emitted when it is finished.
+ */
+ public Collection<S5BProxyRequest> getOrDiscoverS5BProxies() {
+ if (localS5BProxies_ == null && proxyFinder_ == null) {
+ queryForProxies();
+ }
+ return localS5BProxies_;
+ }
+
+ public void connectToProxies(final String sessionID) {
+ logger_.fine("session ID: " + sessionID + "\n");
+ Collection<Pair> clientSessions = new Vector<Pair>();
+
+ if (localS5BProxies_ != null) {
+ for(S5BProxyRequest proxy : localS5BProxies_) {
+ Connection conn = connectionFactory_.createConnection();
+
+ HostAddressPort addressPort = new HostAddressPort(new HostAddress(proxy.getStreamHost().host), proxy.getStreamHost().port);
+ //SWIFT_LOG_ASSERT(addressPort.isValid(), warning) << std::endl;
+ final SOCKS5BytestreamClientSession session = new SOCKS5BytestreamClientSession(conn, addressPort, sessionID, timerFactory_);
+ final JID proxyJid = proxy.getStreamHost().jid;
+ clientSessions.add(new Pair(proxyJid, session));
+ onSessionReadyConnection = session.onSessionReady.connect(new Slot1<Boolean>() {
+ @Override
+ public void call(Boolean b) {
+ handleProxySessionReady(sessionID, proxyJid, session, b);
+ }
+ });
+ onFinishedConnection = session.onFinished.connect(new Slot1<FileTransferError>() {
+ @Override
+ public void call(FileTransferError e) {
+ handleProxySessionFinished(sessionID, proxyJid, session, e);
+ }
+ });
+ session.start();
+ }
+ }
+
+ proxySessions_.put(sessionID, clientSessions);
+ }
+
+ public SOCKS5BytestreamClientSession getProxySessionAndCloseOthers(final JID proxyJID, final String sessionID) {
+ // checking parameters
+ if (!proxySessions_.containsKey(sessionID)) {
+ return null;
+ }
+
+ // get active session
+ SOCKS5BytestreamClientSession activeSession = null;
+ for(Pair i : proxySessions_.get(sessionID)) {
+ i.sock5.onSessionReady.disconnectAll();
+ i.sock5.onFinished.disconnectAll();
+ if (i.jid.equals(proxyJID) && activeSession == null) {
+ activeSession = i.sock5;
+ }
+ else {
+ i.sock5.stop();
+ }
+ }
+
+ proxySessions_.remove(sessionID);
+
+ return activeSession;
+ }
+
+ public SOCKS5BytestreamClientSession createSOCKS5BytestreamClientSession(HostAddressPort addressPort, final String destAddr) {
+ SOCKS5BytestreamClientSession connection = new SOCKS5BytestreamClientSession(connectionFactory_.createConnection(), addressPort, destAddr, timerFactory_);
+ return connection;
+ }
+
+ public final Signal onDiscoveredProxiesChanged = new Signal();
+
+ private void handleProxyFound(final S5BProxyRequest proxy) {
+ if (proxy != null) {
+ if (new HostAddress(proxy.getStreamHost().host).isValid()) {
+ addS5BProxy(proxy);
+ onDiscoveredProxiesChanged.emit();
+ }
+ else {
+ DomainNameAddressQuery resolveRequest = resolver_.createAddressQuery(proxy.getStreamHost().host);
+ resolveRequest.onResult.connect(new Slot2<Collection<HostAddress>, DomainNameResolveError>() {
+ @Override
+ public void call(Collection<HostAddress> c, DomainNameResolveError d) {
+ handleNameLookupResult(c, d, proxy);
+ }
+ });
+ resolveRequest.run();
+ }
+ }
+ else {
+ onDiscoveredProxiesChanged.emit();
+ }
+ proxyFinder_.stop();
+ proxyFinder_ = null;
+ }
+
+ private void handleNameLookupResult(final Collection<HostAddress> addresses, DomainNameResolveError error, S5BProxyRequest proxy) {
+ if (error != null) {
+ onDiscoveredProxiesChanged.emit();
+ }
+ else {
+ if (addresses.isEmpty()) {
+ logger_.warning("S5B proxy hostname does not resolve.\n");
+ onDiscoveredProxiesChanged.emit();
+ }
+ else {
+ // generate proxy per returned address
+ for (final HostAddress address : addresses) {
+ S5BProxyRequest.StreamHost streamHost = proxy.getStreamHost();
+ S5BProxyRequest proxyForAddress = proxy;
+ streamHost.host = address.toString();
+ proxyForAddress.setStreamHost(streamHost);
+ addS5BProxy(proxyForAddress);
+ }
+ onDiscoveredProxiesChanged.emit();
+ }
+ }
+ }
+
+ private void queryForProxies() {
+ proxyFinder_ = new SOCKS5BytestreamProxyFinder(serviceRoot_, iqRouter_);
+
+ proxyFinder_.onProxyFound.connect(new Slot1<S5BProxyRequest>() {
+ @Override
+ public void call(S5BProxyRequest s) {
+ handleProxyFound(s);
+ }
+ });
+ proxyFinder_.start();
+ }
+
+ private void handleProxySessionReady(final String sessionID, final JID jid, SOCKS5BytestreamClientSession session, boolean error) {
+ onSessionReadyConnection.disconnect();
+ if (!error) {
+ // The SOCKS5 bytestream session to the proxy succeeded; stop and remove other sessions.
+ if (proxySessions_.containsKey(sessionID)) {
+ for(Pair i : proxySessions_.get(sessionID)) {
+ if ((i.jid.equals(jid)) && (!i.sock5.equals(session))) {
+ i.sock5.stop();
+ proxySessions_.get(sessionID).remove(i); //Swiften assigns i, so that iterator points to the next element.
+ }
+ }
+ }
+ }
+ }
+
+ private void handleProxySessionFinished(final String sessionID, final JID jid, SOCKS5BytestreamClientSession session, FileTransferError error) {
+ onFinishedConnection.disconnect();
+ if (error != null) {
+ // The SOCKS5 bytestream session to the proxy failed; remove it.
+ if (proxySessions_.containsKey(sessionID)) {
+ for(Pair i : proxySessions_.get(sessionID)) {
+ if ((i.jid.equals(jid)) && (i.sock5.equals(session))) {
+ i.sock5.stop();
+ proxySessions_.get(sessionID).remove(i); //Swiften assigns i, so that iterator points to the next element.
+ break;
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxyFinder.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxyFinder.java
new file mode 100644
index 0000000..d856a2f
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxyFinder.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+/*
+ * Copyright (c) 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.network.HostAddressPort;
+import com.isode.stroke.elements.S5BProxyRequest;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.disco.DiscoServiceWalker;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot2;
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+import java.util.logging.Logger;
+
+/*
+ * This class is designed to find possible SOCKS5 bytestream proxies which are used for peer-to-peer data transfers in
+ * restrictive environments.
+ */
+public class SOCKS5BytestreamProxyFinder {
+
+ private JID service;
+ private IQRouter iqRouter;
+ private DiscoServiceWalker serviceWalker;
+ private Vector<GenericRequest<S5BProxyRequest> > requests = new Vector<GenericRequest<S5BProxyRequest>>();
+ private SignalConnection onServiceFoundConnection;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public SOCKS5BytestreamProxyFinder(final JID service, IQRouter iqRouter) {
+ this.service = service;
+ this.iqRouter = iqRouter;
+ }
+
+ public void start() {
+ serviceWalker = new DiscoServiceWalker(service, iqRouter);
+ onServiceFoundConnection = serviceWalker.onServiceFound.connect(new Slot2<JID, DiscoInfo>() {
+ @Override
+ public void call(JID j, DiscoInfo d) {
+ handleServiceFound(j, d);
+ }
+ });
+ serviceWalker.beginWalk();
+ }
+
+ public void stop() {
+ serviceWalker.endWalk();
+ onServiceFoundConnection.disconnect();
+ serviceWalker = null;
+ }
+
+ public final Signal1<S5BProxyRequest> onProxyFound = new Signal1<S5BProxyRequest>();
+
+ private void sendBytestreamQuery(final JID jid) {
+ S5BProxyRequest proxyRequest = new S5BProxyRequest();
+ GenericRequest<S5BProxyRequest> request = new GenericRequest<S5BProxyRequest>(IQ.Type.Get, jid, proxyRequest, iqRouter);
+ request.onResponse.connect(new Slot2<S5BProxyRequest, ErrorPayload>() {
+ @Override
+ public void call(S5BProxyRequest s, ErrorPayload e) {
+ handleProxyResponse(s, e);
+ }
+ });
+ request.send();
+ }
+
+ private void handleServiceFound(final JID jid, DiscoInfo discoInfo) {
+ if (discoInfo.hasFeature(DiscoInfo.Bytestream)) {
+ sendBytestreamQuery(jid);
+ }
+ }
+ private void handleProxyResponse(S5BProxyRequest request, ErrorPayload error) {
+ if (error != null) {
+ logger_.fine("ERROR\n");
+ } else {
+ if (request != null) {
+ onProxyFound.emit(request);
+ } else {
+ //assert(false);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamRegistry.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamRegistry.java
new file mode 100644
index 0000000..7f131e5
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamRegistry.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010-2013 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.base.IDGenerator;
+import java.util.Set;
+import java.util.HashSet;
+
+public class SOCKS5BytestreamRegistry {
+
+ private Set<String> availableBytestreams = new HashSet<String>();
+ private IDGenerator idGenerator = new IDGenerator();
+
+ public SOCKS5BytestreamRegistry() {
+
+ }
+
+ public void setHasBytestream(final String destination, boolean b) {
+ if (b) {
+ availableBytestreams.add(destination);
+ }
+ else {
+ availableBytestreams.remove(destination);
+ }
+ }
+
+ public boolean hasBytestream(final String destination) {
+ return availableBytestreams.contains(destination);
+ }
+
+ /**
+ * Generate a new session ID to use for new S5B streams.
+ */
+ public String generateSessionID() {
+ return idGenerator.generateID();
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServer.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServer.java
new file mode 100644
index 0000000..3a745e2
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServer.java
@@ -0,0 +1,86 @@
+/*
+ * 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.network.ConnectionServer;
+import com.isode.stroke.network.HostAddressPort;
+import com.isode.stroke.network.Connection;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Slot;
+import java.util.Vector;
+
+public class SOCKS5BytestreamServer {
+
+ private ConnectionServer connectionServer;
+ private SOCKS5BytestreamRegistry registry;
+ private Vector<SOCKS5BytestreamServerSession> sessions = new Vector<SOCKS5BytestreamServerSession>();
+ private SignalConnection onNewConnectionConn;
+ private SignalConnection onFinishedConnection;
+
+ public SOCKS5BytestreamServer(ConnectionServer connectionServer, SOCKS5BytestreamRegistry registry) {
+ this.connectionServer = connectionServer;
+ this.registry = registry;
+ }
+
+ public HostAddressPort getAddressPort() {
+ return connectionServer.getAddressPort();
+ }
+
+ public void start() {
+ onNewConnectionConn = connectionServer.onNewConnection.connect(new Slot1<Connection>() {
+ @Override
+ public void call(Connection c) {
+ handleNewConnection(c);
+ }
+ });
+ }
+
+ public void stop() {
+ onNewConnectionConn.disconnect();
+ for (SOCKS5BytestreamServerSession session : sessions) {
+ session.onFinished.disconnectAll();
+ session.stop();
+ }
+ sessions.clear();
+ }
+
+ public Vector<SOCKS5BytestreamServerSession> getSessions(final String streamID) {
+ Vector<SOCKS5BytestreamServerSession> result = new Vector<SOCKS5BytestreamServerSession>();
+ for (SOCKS5BytestreamServerSession session : sessions) {
+ if (session.getStreamID().equals(streamID)) {
+ result.add(session);
+ }
+ }
+ return result;
+ }
+
+ private void handleNewConnection(Connection connection) {
+ final SOCKS5BytestreamServerSession session = new SOCKS5BytestreamServerSession(connection, registry);
+ onFinishedConnection = session.onFinished.connect(new Slot1<FileTransferError>() {
+ @Override
+ public void call(FileTransferError e) {
+ handleSessionFinished(session);
+ }
+ });
+ sessions.add(session);
+ session.start();
+ }
+
+ private void handleSessionFinished(SOCKS5BytestreamServerSession session) {
+ while(sessions.contains(session)) {
+ sessions.remove(session);
+ }
+ onFinishedConnection.disconnect();
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerManager.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerManager.java
new file mode 100644
index 0000000..729193a
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerManager.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2012-2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt 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.signals.Slot1;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.network.ConnectionServer;
+import com.isode.stroke.network.ConnectionServerFactory;
+import com.isode.stroke.network.NATTraverser;
+import com.isode.stroke.network.NetworkInterface;
+import com.isode.stroke.network.NATPortMapping;
+import com.isode.stroke.network.NetworkEnvironment;
+import com.isode.stroke.network.NATTraversalGetPublicIPRequest;
+import com.isode.stroke.network.NATTraversalForwardPortRequest;
+import com.isode.stroke.network.NATTraversalRemovePortForwardingRequest;
+import com.isode.stroke.network.HostAddress;
+import com.isode.stroke.network.HostAddressPort;
+import java.util.Vector;
+import java.util.logging.Logger;
+
+public class SOCKS5BytestreamServerManager {
+
+ public static final int LISTEN_PORTS_BEGIN = 10000;
+ public static final int LISTEN_PORTS_END = 11000;
+
+ private SOCKS5BytestreamRegistry bytestreamRegistry;
+ private ConnectionServerFactory connectionServerFactory;
+ private NetworkEnvironment networkEnvironment;
+ private NATTraverser natTraverser;
+ private enum State { Start, Initializing, Initialized };
+ private State state;
+ private SOCKS5BytestreamServer server;
+ private ConnectionServer connectionServer;
+ private int connectionServerPort;
+
+ private NATTraversalGetPublicIPRequest getPublicIPRequest;
+ private NATTraversalForwardPortRequest forwardPortRequest;
+ private NATTraversalRemovePortForwardingRequest unforwardPortRequest;
+ private HostAddress publicAddress;
+ private NATPortMapping portMapping;
+ private boolean attemptedPortMapping_;
+
+ private SOCKS5BytestreamServerResourceUser s5bServerResourceUser_;
+ private SOCKS5BytestreamServerPortForwardingUser s5bServerPortForwardingUser_;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public SOCKS5BytestreamServerManager(SOCKS5BytestreamRegistry bytestreamRegistry, ConnectionServerFactory connectionServerFactory, NetworkEnvironment networkEnvironment, NATTraverser natTraverser) {
+ this.bytestreamRegistry = bytestreamRegistry;
+ this.connectionServerFactory = connectionServerFactory;
+ this.networkEnvironment = networkEnvironment;
+ this.natTraverser = natTraverser;
+ this.state = State.Start;
+ this.server = null;
+ this.attemptedPortMapping_ = false;
+ }
+
+ /**
+ * User should call delete to free the resources.
+ */
+ public void delete() {
+ //SWIFT_LOG_ASSERT(!connectionServer, warning) << std::endl;
+ //SWIFT_LOG_ASSERT(!getPublicIPRequest, warning) << std::endl;
+ //SWIFT_LOG_ASSERT(!forwardPortRequest, warning) << std::endl;
+ //SWIFT_LOG_ASSERT(state == Start, warning) << std::endl;
+ if (portMapping != null && unforwardPortRequest == null) {
+ //SWIFT_LOG(warning) << "Port forwarding still alive. Trying to remove it now." << std::endl;
+ unforwardPortRequest = natTraverser.createRemovePortForwardingRequest(portMapping.getLocalPort(), portMapping.getPublicPort());
+ unforwardPortRequest.start();
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ delete();
+ }
+ finally {
+ super.finalize();
+ }
+ }
+
+ public SOCKS5BytestreamServerResourceUser aquireResourceUser() {
+ SOCKS5BytestreamServerResourceUser resourceUser = null;
+ if (s5bServerResourceUser_ == null) {
+ resourceUser = new SOCKS5BytestreamServerResourceUser(this);
+ s5bServerResourceUser_ = resourceUser;
+ }
+ else {
+ resourceUser = s5bServerResourceUser_;
+ }
+ return resourceUser;
+ }
+
+ public SOCKS5BytestreamServerPortForwardingUser aquirePortForwardingUser() {
+ SOCKS5BytestreamServerPortForwardingUser portForwardingUser = null;
+ if (s5bServerPortForwardingUser_ == null) {
+ portForwardingUser = new SOCKS5BytestreamServerPortForwardingUser(this);
+ s5bServerPortForwardingUser_ = portForwardingUser;
+ }
+ else {
+ portForwardingUser = s5bServerPortForwardingUser_;
+ }
+ return portForwardingUser;
+ }
+
+ public void stop() {
+ if (getPublicIPRequest != null) {
+ getPublicIPRequest.stop();
+ getPublicIPRequest = null;
+ }
+ if (forwardPortRequest != null) {
+ forwardPortRequest.stop();
+ forwardPortRequest = null;
+ }
+ if (server != null) {
+ server.stop();
+ server = null;
+ }
+ if (connectionServer != null) {
+ connectionServer.stop();
+ connectionServer = null;
+ }
+
+ state = State.Start;
+ }
+
+ public Vector<HostAddressPort> getHostAddressPorts() {
+ Vector<HostAddressPort> result = new Vector<HostAddressPort>();
+ if (connectionServer != null) {
+ Vector<NetworkInterface> networkInterfaces = networkEnvironment.getNetworkInterfaces();
+ for (final NetworkInterface networkInterface : networkInterfaces) {
+ for (final HostAddress address : networkInterface.getAddresses()) {
+ result.add(new HostAddressPort(address, connectionServerPort));
+ }
+ }
+ }
+ return result;
+ }
+
+ public Vector<HostAddressPort> getAssistedHostAddressPorts() {
+ Vector<HostAddressPort> result = new Vector<HostAddressPort>();
+ if (publicAddress != null && portMapping != null) {
+ result.add(new HostAddressPort(publicAddress, portMapping.getPublicPort()));
+ }
+ return result;
+ }
+
+ public SOCKS5BytestreamServer getServer() {
+ return server;
+ }
+
+ boolean isInitialized() {
+ return State.Initialized.equals(state);
+ }
+
+ void initialize() {
+ if (State.Start.equals(state)) {
+ state = State.Initializing;
+
+ // Find a port to listen on
+ assert(connectionServer == null);
+ int port;
+ for (port = LISTEN_PORTS_BEGIN; port < LISTEN_PORTS_END; ++port) {
+ logger_.fine("Trying to start server on port " + port + "\n");
+ connectionServer = connectionServerFactory.createConnectionServer(new HostAddress("0.0.0.0"), port);
+ ConnectionServer.Error error = connectionServer.tryStart();
+ if (error == null) {
+ break;
+ }
+ else if (!ConnectionServer.Error.Conflict.equals(error)) {
+ logger_.fine("Error starting server\n");
+ onInitialized.emit(false);
+ return;
+ }
+ connectionServer = null;
+ }
+ if (connectionServer == null) {
+ logger_.fine("Unable to find an open port\n");
+ onInitialized.emit(false);
+ return;
+ }
+ logger_.fine("Server started succesfully\n");
+ connectionServerPort = port;
+
+ // Start bytestream server. Should actually happen before the connectionserver is started
+ // but that doesn't really matter here.
+ assert(server == null);
+ server = new SOCKS5BytestreamServer(connectionServer, bytestreamRegistry);
+ server.start();
+ checkInitializeFinished();
+ }
+ }
+
+ boolean isPortForwardingReady() {
+ return attemptedPortMapping_ && getPublicIPRequest == null && forwardPortRequest == null;
+ }
+
+ void setupPortForwarding() {
+ assert(server != null);
+ attemptedPortMapping_ = true;
+
+ // Retrieve public addresses
+ assert(getPublicIPRequest == null);
+ publicAddress = null;
+ if ((natTraverser.createGetPublicIPRequest() != null)) {
+ getPublicIPRequest = natTraverser.createGetPublicIPRequest();
+ getPublicIPRequest.onResult.connect(new Slot1<HostAddress>() {
+ @Override
+ public void call(HostAddress a) {
+ handleGetPublicIPResult(a);
+ }
+ });
+ getPublicIPRequest.start();
+ }
+
+ // Forward ports
+ int port = server.getAddressPort().getPort();
+ assert(forwardPortRequest == null);
+ portMapping = null;
+ if ((natTraverser.createForwardPortRequest(port, port) != null)) {
+ forwardPortRequest = natTraverser.createForwardPortRequest(port, port);
+ forwardPortRequest.onResult.connect(new Slot1<NATPortMapping>() {
+ @Override
+ public void call(NATPortMapping n) {
+ handleForwardPortResult(n);
+ }
+ });
+ forwardPortRequest.start();
+ }
+ }
+
+ void removePortForwarding() {
+ // remove port forwards
+ if (portMapping != null) {
+ unforwardPortRequest = natTraverser.createRemovePortForwardingRequest(portMapping.getLocalPort(), portMapping.getPublicPort());
+ unforwardPortRequest.onResult.connect(new Slot1<Boolean>() {
+ @Override
+ public void call(Boolean b) {
+ handleUnforwardPortResult(b);
+ }
+ });
+ unforwardPortRequest.start();
+ }
+ }
+
+ void checkInitializeFinished() {
+ assert(State.Initializing.equals(state));
+ state = State.Initialized;
+ onInitialized.emit(true);
+ }
+
+ void handleGetPublicIPResult(HostAddress address) {
+ if (address != null) {
+ logger_.fine("Public IP discovered as " + address.toString() + ".\n");
+ }
+ else {
+ logger_.fine("No public IP discoverable.\n");
+ }
+
+ publicAddress = address;
+
+ getPublicIPRequest.stop();
+ getPublicIPRequest = null;
+ }
+
+ void handleForwardPortResult(NATPortMapping mapping) {
+ if (mapping != null) {
+ logger_.fine("Mapping port was successful.\n");
+ }
+ else {
+ logger_.fine("Mapping port has failed.\n");
+ }
+
+ portMapping = mapping;
+ onPortForwardingSetup.emit(mapping != null);
+
+ forwardPortRequest.stop();
+ forwardPortRequest = null;
+ }
+
+ void handleUnforwardPortResult(Boolean result) {
+ if (result != null && result) {
+ portMapping = null;
+ }
+ else {
+ logger_.warning("Failed to remove port forwarding.\n");
+ }
+ attemptedPortMapping_ = false;
+ unforwardPortRequest = null;
+ }
+
+ Signal1<Boolean/* success */> onInitialized = new Signal1<Boolean>();
+ Signal1<Boolean/* success */> onPortForwardingSetup = new Signal1<Boolean>();
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerPortForwardingUser.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerPortForwardingUser.java
new file mode 100644
index 0000000..dc3c3b7
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerPortForwardingUser.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 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.signals.Slot1;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.SignalConnection;
+
+public class SOCKS5BytestreamServerPortForwardingUser {
+
+ private SOCKS5BytestreamServerManager s5bServerManager_;
+ private SignalConnection onPortForwardingSetupConnection_;
+
+ public SOCKS5BytestreamServerPortForwardingUser(SOCKS5BytestreamServerManager s5bServerManager) {
+ this.s5bServerManager_ = s5bServerManager;
+ // the server should be initialized, so we know what port to setup a forward for
+ assert(s5bServerManager != null);
+ if (s5bServerManager_.isPortForwardingReady()) {
+ onSetup.emit(!s5bServerManager_.getAssistedHostAddressPorts().isEmpty());
+ }
+ else {
+ onPortForwardingSetupConnection_ = s5bServerManager_.onPortForwardingSetup.connect(new Slot1<Boolean>() {
+ @Override
+ public void call(Boolean s) {
+ handleServerManagerPortForwardingSetup(s);
+ }
+ });
+ s5bServerManager_.setupPortForwarding();
+ }
+ }
+
+ /**
+ * User should call delete to free the resources.
+ */
+ public void delete() {
+ if (s5bServerManager_.isPortForwardingReady()) {
+ s5bServerManager_.removePortForwarding();
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ delete();
+ }
+ finally {
+ super.finalize();
+ }
+ }
+
+ public boolean isForwardingSetup() {
+ return s5bServerManager_.isPortForwardingReady();
+ }
+
+ public Signal1<Boolean /* success */> onSetup = new Signal1<Boolean>();
+
+ private void handleServerManagerPortForwardingSetup(boolean successful) {
+ onSetup.emit(successful);
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerResourceUser.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerResourceUser.java
new file mode 100644
index 0000000..acb8af1
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerResourceUser.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 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.signals.Slot1;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.SignalConnection;
+
+public class SOCKS5BytestreamServerResourceUser {
+
+ private SOCKS5BytestreamServerManager s5bServerManager_;
+ private SignalConnection onInitializedConnection_;
+
+ public SOCKS5BytestreamServerResourceUser(SOCKS5BytestreamServerManager s5bServerManager) {
+ this.s5bServerManager_ = s5bServerManager;
+ assert(s5bServerManager_ == null);
+ onInitializedConnection_ = s5bServerManager_.onInitialized.connect(new Slot1<Boolean>() {
+ @Override
+ public void call(Boolean b) {
+ handleServerManagerInitialized(b);
+ }
+ });
+ s5bServerManager_.initialize();
+ }
+
+ /**
+ * User should call delete to free the resources.
+ */
+ public void delete() {
+ if (s5bServerManager_ != null) {
+ s5bServerManager_.stop();
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ delete();
+ }
+ finally {
+ super.finalize();
+ }
+ }
+
+ public boolean isInitialized() {
+ return s5bServerManager_.isInitialized();
+ }
+
+ public Signal1<Boolean /* success */> onSuccessfulInitialized = new Signal1<Boolean>();
+
+ private void handleServerManagerInitialized(boolean successfulInitialize) {
+ onSuccessfulInitialized.emit(successfulInitialize);
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerSession.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerSession.java
new file mode 100644
index 0000000..cb09ad9
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerSession.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2010 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.network.Connection;
+import com.isode.stroke.network.HostAddressPort;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.signals.Slot;
+import com.isode.stroke.signals.Slot1;
+import java.util.logging.Logger;
+
+public class SOCKS5BytestreamServerSession {
+
+ private Connection connection;
+ private SOCKS5BytestreamRegistry bytestreams;
+ private ByteArray unprocessedData;
+ private State state;
+ private int chunkSize;
+ private String streamID = "";
+ private ReadBytestream readBytestream;
+ private WriteBytestream writeBytestream;
+ private boolean waitingForData;
+
+ private SignalConnection disconnectedConnection;
+ private SignalConnection dataReadConnection;
+ private SignalConnection dataWrittenConnection;
+ private SignalConnection dataAvailableConnection;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public enum State {
+ Initial,
+ WaitingForAuthentication,
+ WaitingForRequest,
+ ReadyForTransfer,
+ ReadingData,
+ WritingData,
+ Finished
+ };
+
+ public SOCKS5BytestreamServerSession(Connection connection, SOCKS5BytestreamRegistry bytestreams) {
+ this.connection = connection;
+ this.bytestreams = bytestreams;
+ this.state = State.Initial;
+ this.chunkSize = 131072;
+ this.waitingForData = false;
+ disconnectedConnection = connection.onDisconnected.connect(new Slot1<Connection.Error>() {
+ @Override
+ public void call(Connection.Error e) {
+ handleDisconnected(e);
+ }
+ });
+ }
+
+ public void setChunkSize(int chunkSize) {
+ this.chunkSize = chunkSize;
+ }
+
+ public void start() {
+ logger_.fine("\n");
+ dataReadConnection = connection.onDataRead.connect(new Slot1<SafeByteArray>() {
+ @Override
+ public void call(SafeByteArray s) {
+ handleDataRead(s);
+ }
+ });
+ state = State.WaitingForAuthentication;
+ }
+
+ public void stop() {
+ finish(false);
+ }
+
+ public void startSending(ReadBytestream stream) {
+ if (!State.ReadyForTransfer.equals(state)) { logger_.fine("Not ready for transfer!\n"); return; }
+ readBytestream = stream;
+ state = State.WritingData;
+ dataAvailableConnection = readBytestream.onDataAvailable.connect(new Slot() {
+ @Override
+ public void call() {
+ handleDataAvailable();
+ }
+ });
+ dataWrittenConnection = connection.onDataWritten.connect(new Slot() {
+ @Override
+ public void call() {
+ sendData();
+ }
+ });
+ sendData();
+ }
+
+ public void startReceiving(WriteBytestream stream) {
+ if (!State.ReadyForTransfer.equals(state)) { logger_.fine("Not ready for transfer!\n"); return; }
+
+ writeBytestream = stream;
+ state = State.ReadingData;
+ writeBytestream.write(unprocessedData);
+ // onBytesReceived(unprocessedData.getSize());
+ unprocessedData.clear();
+ }
+
+ public HostAddressPort getAddressPort() {
+ return connection.getLocalAddress();
+ }
+
+ public Signal1<FileTransferError> onFinished = new Signal1<FileTransferError>();
+ public Signal1<Long> onBytesSent = new Signal1<Long>();
+ // boost::signal<void (unsigned long long)> onBytesReceived;
+
+ public String getStreamID() {
+ return streamID;
+ }
+
+ private void finish(boolean error) {
+ logger_.fine(error + " " + state + "\n");
+ if (State.Finished.equals(state)) {
+ return;
+ }
+
+ disconnectedConnection.disconnect();
+ dataReadConnection.disconnect();
+ dataWrittenConnection.disconnect();
+ dataAvailableConnection.disconnect();
+ readBytestream = null;
+ state = State.Finished;
+ if (error) {
+ onFinished.emit(new FileTransferError(FileTransferError.Type.PeerError));
+ } else {
+ onFinished.emit(null);
+ }
+ }
+
+ private void process() {
+ if (State.WaitingForAuthentication.equals(state)) {
+ if (unprocessedData.getSize() >= 2) {
+ int authCount = unprocessedData.getData()[1];
+ int i = 2;
+ while (i < 2 + authCount && i < unprocessedData.getSize()) {
+ // Skip authentication mechanism
+ ++i;
+ }
+ if (i == 2 + authCount) {
+ // Authentication message is complete
+ if (i != unprocessedData.getSize()) {
+ logger_.fine("Junk after authentication mechanism\n");
+ }
+ unprocessedData.clear();
+ connection.write(new SafeByteArray(new byte[]{0x05, 0x00}));
+ state = State.WaitingForRequest;
+ }
+ }
+ }
+ else if (State.WaitingForRequest.equals(state)) {
+ if (unprocessedData.getSize() >= 5) {
+ ByteArray requestID = new ByteArray();
+ int i = 5;
+ int hostnameSize = unprocessedData.getData()[4];
+ while (i < 5 + hostnameSize && i < unprocessedData.getSize()) {
+ requestID.append(unprocessedData.getData()[i]);
+ ++i;
+ }
+ // Skip the port: 2 byte large, one already skipped. Add one for comparison with size
+ i += 2;
+ if (i <= unprocessedData.getSize()) {
+ if (i != unprocessedData.getSize()) {
+ logger_.fine("Junk after authentication mechanism\n");
+ }
+ unprocessedData.clear();
+ streamID = requestID.toString();
+ boolean hasBytestream = bytestreams.hasBytestream(streamID);
+ SafeByteArray result = new SafeByteArray("0x05");
+ result.append(hasBytestream ? (byte)0x0 : (byte)0x4);
+ result.append(new ByteArray(new byte[]{0x00, 0x03}));
+ result.append(Integer.toString(requestID.getSize()));
+ result.append(requestID.append(new ByteArray(new byte[]{0x00, 0x00})));
+ if (!hasBytestream) {
+ logger_.fine("Readstream or Wrtiestream with ID " + streamID + " not found!\n");
+ connection.write(result);
+ finish(true);
+ }
+ else {
+ logger_.fine("Found stream. Sent OK.\n");
+ connection.write(result);
+ state = State.ReadyForTransfer;
+ }
+ }
+ }
+ }
+ }
+
+ private void handleDataRead(SafeByteArray data) {
+ if (!State.ReadingData.equals(state)) {
+ unprocessedData.append(data);
+ process();
+ } else {
+ writeBytestream.write(new ByteArray(data));
+ // onBytesReceived(data.size());
+ }
+ }
+
+ private void handleDisconnected(final Connection.Error error) {
+ logger_.fine((error != null ? (error.equals(Connection.Error.ReadError) ? "Read Error" : "Write Error") : "No Error") + "\n");
+ finish(error != null ? true : false);
+ }
+
+ private void handleDataAvailable() {
+ if (waitingForData) {
+ sendData();
+ }
+ }
+
+ private void sendData() {
+ if (!readBytestream.isFinished()) {
+ //try {
+ SafeByteArray dataToSend = new SafeByteArray(readBytestream.read((chunkSize)));
+ if (!dataToSend.isEmpty()) {
+ connection.write(dataToSend);
+ onBytesSent.emit((long)dataToSend.getSize());
+ waitingForData = false;
+ }
+ else {
+ waitingForData = true;
+ }
+ //}
+ //catch (BytestreamException e) {
+ // finish(true);
+ //}
+ }
+ else {
+ finish(false);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/StreamInitiationRequest.java b/src/com/isode/stroke/filetransfer/StreamInitiationRequest.java
new file mode 100644
index 0000000..8ea9f14
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/StreamInitiationRequest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.queries.GenericRequest;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.elements.StreamInitiation;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.jid.JID;
+
+public class StreamInitiationRequest extends GenericRequest<StreamInitiation> {
+
+ public static StreamInitiationRequest create(final JID jid, StreamInitiation payload, IQRouter router) {
+ return new StreamInitiationRequest(jid, payload, router);
+ }
+
+ public static StreamInitiationRequest create(final JID from, final JID to, StreamInitiation payload, IQRouter router) {
+ return new StreamInitiationRequest(from, to, payload, router);
+ }
+
+ private StreamInitiationRequest(final JID jid, StreamInitiation payload, IQRouter router) {
+ super(IQ.Type.Set, jid, payload, router);
+ }
+
+ private StreamInitiationRequest(final JID from, final JID to, StreamInitiation payload, IQRouter router) {
+ super(IQ.Type.Set, from, to, payload, router);
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/TransportSession.java b/src/com/isode/stroke/filetransfer/TransportSession.java
new file mode 100644
index 0000000..b0e101d
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/TransportSession.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2013 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.signals.Signal1;
+
+public abstract class TransportSession {
+
+ public abstract void start();
+ public abstract void stop();
+
+ public final Signal1<Integer> onBytesSent = new Signal1<Integer>();
+ public final Signal1<FileTransferError> onFinished = new Signal1<FileTransferError>();
+}
diff --git a/src/com/isode/stroke/filetransfer/WriteBytestream.java b/src/com/isode/stroke/filetransfer/WriteBytestream.java
new file mode 100644
index 0000000..c243bdc
--- /dev/null
+++ b/src/com/isode/stroke/filetransfer/WriteBytestream.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010 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.base.ByteArray;
+import com.isode.stroke.signals.Signal1;
+
+public abstract class WriteBytestream {
+
+ public abstract void write(final ByteArray b);
+
+ public final Signal1<ByteArray> onWrite = new Signal1<ByteArray>();
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/jingle/Jingle.java b/src/com/isode/stroke/jingle/Jingle.java
index db041b8..8e78789 100644
--- a/src/com/isode/stroke/jingle/Jingle.java
+++ b/src/com/isode/stroke/jingle/Jingle.java
@@ -16,8 +16,8 @@ import com.isode.stroke.elements.Payload;
import java.util.Vector;
public class Jingle {
-
- public <T extends Payload> JingleContentPayload getContentWithDescription(final Vector<JingleContentPayload> contents, T payload) {
+
+ public static <T extends Payload> JingleContentPayload getContentWithDescription(final Vector<JingleContentPayload> contents, T payload) {
for (JingleContentPayload jingleContentPayload : contents) {
if (jingleContentPayload.getDescription(payload) != null) {
return jingleContentPayload;
diff --git a/src/com/isode/stroke/queries/GenericRequest.java b/src/com/isode/stroke/queries/GenericRequest.java
index c35b369..c9ae4a6 100644
--- a/src/com/isode/stroke/queries/GenericRequest.java
+++ b/src/com/isode/stroke/queries/GenericRequest.java
@@ -22,10 +22,29 @@ public class GenericRequest<T extends Payload> extends Request {
public Signal2<T, ErrorPayload> onResponse = new Signal2<T, ErrorPayload>();
+ /**
+ * Create a request suitable for client use.
+ * @param type Iq type - Get or Set.
+ * @param receiver JID to send request to.
+ * @param payload Payload to send in stanza.
+ * @param router IQRouter instance for current connection.
+ */
public GenericRequest(IQ.Type type, JID receiver, Payload payload, IQRouter router) {
super(type, receiver, payload, router);
}
+ /**
+ * Create a request suitable for component or server use. As a client, use the other constructor instead.
+ * @param type Iq type - Get or Set.
+ * @param sender JID to use in "from" of stanza.
+ * @param receiver JID to send request to.
+ * @param payload Payload to send in stanza.
+ * @param router IQRouter instance for current connection.
+ */
+ public GenericRequest(IQ.Type type, final JID sender, final JID receiver, Payload payload, IQRouter router) {
+ super(type, sender, receiver, payload, router);
+ }
+
@Override
public void handleResponse(Payload payload, ErrorPayload error) {
T genericPayload = null;
diff --git a/test/com/isode/stroke/filetransfer/DummyFileTransferManager.java b/test/com/isode/stroke/filetransfer/DummyFileTransferManager.java
new file mode 100644
index 0000000..d45d9e6
--- /dev/null
+++ b/test/com/isode/stroke/filetransfer/DummyFileTransferManager.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt 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.signals.Signal1;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.S5BProxyRequest;
+import java.util.Date;
+
+public class DummyFileTransferManager extends FileTransferManager {
+
+ public DummyFileTransferManager() {
+ super();
+ }
+
+ public OutgoingFileTransfer createOutgoingFileTransfer(
+ final JID to,
+ final String filepath,
+ final String description,
+ ReadBytestream bytestream,
+ final FileTransferOptions op) {
+ return null;
+ }
+
+ public OutgoingFileTransfer createOutgoingFileTransfer(
+ final JID to,
+ final String filename,
+ final String description,
+ final long sizeInBytes,
+ final Date lastModified,
+ ReadBytestream bytestream,
+ final FileTransferOptions op) {
+ return null;
+ }
+
+ public void addS5BProxy(S5BProxyRequest p) {
+ }
+} \ No newline at end of file
diff --git a/test/com/isode/stroke/filetransfer/DummyFileTransferTransporterFactory.java b/test/com/isode/stroke/filetransfer/DummyFileTransferTransporterFactory.java
new file mode 100644
index 0000000..10b630d
--- /dev/null
+++ b/test/com/isode/stroke/filetransfer/DummyFileTransferTransporterFactory.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 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.elements.JingleS5BTransportPayload;
+import com.isode.stroke.signals.Signal2;
+import com.isode.stroke.signals.Signal3;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+import java.util.logging.Logger;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Slot2;
+import com.isode.stroke.base.IDGenerator;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.network.ConnectionFactory;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.elements.S5BProxyRequest;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.stringcodecs.Hexify;
+import com.isode.stroke.elements.S5BProxyRequest;
+
+class DummyFileTransferTransporter extends FileTransferTransporter {
+
+ public enum Role {
+ Initiator,
+ Responder
+ };
+
+ public DummyFileTransferTransporter(
+ final JID initiator,
+ final JID responder,
+ Role role,
+ SOCKS5BytestreamRegistry s5bRegistry,
+ SOCKS5BytestreamServerManager s5bServerManager,
+ SOCKS5BytestreamProxiesManager s5bProxy,
+ IDGenerator idGenerator,
+ ConnectionFactory connectionFactory,
+ TimerFactory timer,
+ CryptoProvider cryptoProvider,
+ IQRouter iqRouter,
+ final FileTransferOptions option) {
+ initiator_ = initiator;
+ responder_ = responder;
+ role_ = role;
+ s5bRegistry_ = s5bRegistry;
+ crypto_ = cryptoProvider;
+ iqRouter_ = iqRouter;
+ }
+
+ public void initialize() {
+ s5bSessionID_ = s5bRegistry_.generateSessionID();
+ }
+
+ public void startGeneratingLocalCandidates() {
+ Vector<JingleS5BTransportPayload.Candidate> candidates = new Vector<JingleS5BTransportPayload.Candidate>();
+ onLocalCandidatesGenerated.emit(s5bSessionID_, candidates, getSOCKS5DstAddr());
+ }
+
+ public void stopGeneratingLocalCandidates() {
+ }
+
+ public void addRemoteCandidates(final Vector<JingleS5BTransportPayload.Candidate> candidates, final String d) {
+ }
+
+ public void startTryingRemoteCandidates() {
+ onRemoteCandidateSelectFinished.emit(s5bSessionID_, null);
+ }
+
+ public void stopTryingRemoteCandidates() {
+ }
+
+ public void startActivatingProxy(final JID proxy) {
+ }
+
+ public void stopActivatingProxy() {
+ }
+
+ public TransportSession createIBBSendSession(final String sessionID, int blockSize, ReadBytestream stream) {
+ IBBSendSession ibbSession =new IBBSendSession(
+ sessionID, initiator_, responder_, stream, iqRouter_);
+ ibbSession.setBlockSize(blockSize);
+ return new IBBSendTransportSession(ibbSession);
+ }
+
+ public TransportSession createIBBReceiveSession(final String sessionID, int size, WriteBytestream stream) {
+ IBBReceiveSession ibbSession = new IBBReceiveSession(
+ sessionID, initiator_, responder_, size, stream, iqRouter_);
+ return new IBBReceiveTransportSession(ibbSession);
+ }
+
+ public TransportSession createRemoteCandidateSession(
+ ReadBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ return null;
+ }
+
+ public TransportSession createRemoteCandidateSession(
+ WriteBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ return null;
+ }
+
+ public TransportSession createLocalCandidateSession(
+ ReadBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ return null;
+ }
+
+ public TransportSession createLocalCandidateSession(
+ WriteBytestream stream, final JingleS5BTransportPayload.Candidate candidate) {
+ return null;
+ }
+
+ private String getSOCKS5DstAddr() {
+ String result = "";
+ if (Role.Initiator.equals(role_)) {
+ result = getInitiatorCandidateSOCKS5DstAddr();
+ }
+ else {
+ result = getResponderCandidateSOCKS5DstAddr();
+ }
+ return result;
+ }
+
+ private String getInitiatorCandidateSOCKS5DstAddr() {
+ return Hexify.hexify(crypto_.getSHA1Hash(new SafeByteArray(s5bSessionID_ + initiator_.toString() + responder_.toString())));
+ }
+
+ private String getResponderCandidateSOCKS5DstAddr() {
+ return Hexify.hexify(crypto_.getSHA1Hash(new SafeByteArray(s5bSessionID_ + responder_.toString() + initiator_.toString())));
+ }
+
+ private JID initiator_;
+ private JID responder_;
+ private Role role_;
+ private SOCKS5BytestreamRegistry s5bRegistry_;
+ private CryptoProvider crypto_;
+ private String s5bSessionID_;
+ private IQRouter iqRouter_;
+};
+
+public class DummyFileTransferTransporterFactory implements FileTransferTransporterFactory {
+
+ public DummyFileTransferTransporterFactory(
+ SOCKS5BytestreamRegistry s5bRegistry,
+ SOCKS5BytestreamServerManager s5bServerManager,
+ SOCKS5BytestreamProxiesManager s5bProxy,
+ IDGenerator idGenerator,
+ ConnectionFactory connectionFactory,
+ TimerFactory timerFactory,
+ CryptoProvider cryptoProvider,
+ IQRouter iqRouter) {
+ s5bRegistry_ = s5bRegistry;
+ s5bServerManager_ = s5bServerManager;
+ s5bProxy_ = s5bProxy;
+ idGenerator_ = idGenerator;
+ connectionFactory_ = connectionFactory;
+ timerFactory_ = timerFactory;
+ cryptoProvider_ = cryptoProvider;
+ iqRouter_ = iqRouter;
+ }
+
+ public FileTransferTransporter createInitiatorTransporter(final JID initiator, final JID responder, final FileTransferOptions options) {
+ DummyFileTransferTransporter transporter = new DummyFileTransferTransporter(
+ initiator,
+ responder,
+ DummyFileTransferTransporter.Role.Initiator,
+ s5bRegistry_,
+ s5bServerManager_,
+ s5bProxy_,
+ idGenerator_,
+ connectionFactory_,
+ timerFactory_,
+ cryptoProvider_,
+ iqRouter_,
+ options);
+ transporter.initialize();
+ return transporter;
+ }
+
+ public FileTransferTransporter createResponderTransporter(final JID initiator, final JID responder, final String s5bSessionID, final FileTransferOptions options) {
+ return null;
+ }
+
+ private SOCKS5BytestreamRegistry s5bRegistry_;
+ private SOCKS5BytestreamServerManager s5bServerManager_;
+ private SOCKS5BytestreamProxiesManager s5bProxy_;
+ private IDGenerator idGenerator_;
+ private ConnectionFactory connectionFactory_;
+ private TimerFactory timerFactory_;
+ private CryptoProvider cryptoProvider_;
+ private IQRouter iqRouter_;
+}; \ No newline at end of file