diff options
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 |