diff options
author | Tarun Gupta <tarun1995gupta@gmail.com> | 2015-08-03 12:08:37 (GMT) |
---|---|---|
committer | Alex Clayton <alex.clayton@isode.com> | 2016-01-21 10:47:51 (GMT) |
commit | 97a085f7e2c9b7820000eaace97dc0ab6392cb0d (patch) | |
tree | d3df191a053a69bc52238b76b8e9e42af043302c | |
parent | fa1633e3b4d75a8217459cdc5fe64e9ee5ace65a (diff) | |
download | stroke-97a085f7e2c9b7820000eaace97dc0ab6392cb0d.zip stroke-97a085f7e2c9b7820000eaace97dc0ab6392cb0d.tar.bz2 |
Completes FileTransfer according to Swiften.
S5BTransport Session still needs generic T.
FileTransfer, OutgoingFileTransfer and IncomingFileTransfer are made an interface due to the need of multiple inheritance in
IncomingJingleFileTransfer and OutgoingJingleFileTransfer. Corresponding documentation has been updated.
License:
This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.
Test-Information:
None.
Change-Id: If44cf387767865c37492d871c12d623f94ebaa3a
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 |