From 2ebf488dfee7156fbbe0b3d3eccebe13d86a8634 Mon Sep 17 00:00:00 2001 From: Alex Clayton Date: Mon, 8 Feb 2016 12:30:12 +0000 Subject: Add the FileTransfer tests Add the missing FileTransfer tests to stroke. When porting the tests I found some of them were failing and required changes to the classes being tested to fix. Had to add a DummyNetworkEnvironment as well for the OutgoingJingleFileTransferTest. Test-information: All unit tests pass. Change-Id: Id511a556ef3a5d66e0e107f36f736db3bbb3a437 diff --git a/PortingProgress.txt b/PortingProgress.txt index 97c4ac9..d8dee4f 100644 --- a/PortingProgress.txt +++ b/PortingProgress.txt @@ -94,14 +94,15 @@ All files ported to 6ca201d0b48f4273e24dd7bff17c4a46eeaddf39. ----- FileTransfer: -All files ported to 6ca201d0b48f4273e24dd7bff17c4a46eeaddf39 except for: - -Tests not yet ported. +All files ported to 6ca201d0b48f4273e24dd7bff17c4a46eeaddf39 Individual Comments: Incoming and Outgoing Jingle File Transfer have been implemented slightly differently to swiften due to multipe inheritance. +IncomingJingleFileTransferTest -- test_AcceptFailingS5BFallsBackToIBB test commented +out in Swiften code this test is not run as it does not work in Swiften. + ----- IDN diff --git a/src/com/isode/stroke/base/ByteArray.java b/src/com/isode/stroke/base/ByteArray.java index 997cc6e..f1181f5 100644 --- a/src/com/isode/stroke/base/ByteArray.java +++ b/src/com/isode/stroke/base/ByteArray.java @@ -3,7 +3,7 @@ * All rights reserved. */ /* - * Copyright (c) 2010-2015, Isode Limited, London, England. + * Copyright (c) 2010-2016, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.base; @@ -61,6 +61,14 @@ public class ByteArray { public ByteArray(ByteArray b) { append(b); } + + /** + * Constructs a new {@link ByteArray} containing the user supplied byte. + * @param b a byte. + */ + public ByteArray(byte b) { + append(b); + } /*public ByteArray(char[] c, int n) { for (int i = 0; i < n; i++) { diff --git a/src/com/isode/stroke/base/SafeByteArray.java b/src/com/isode/stroke/base/SafeByteArray.java index 437346f..f07cbe1 100644 --- a/src/com/isode/stroke/base/SafeByteArray.java +++ b/src/com/isode/stroke/base/SafeByteArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2015, Isode Limited, London, England. + * Copyright (c) 2011-2016, Isode Limited, London, England. * All rights reserved. */ package com.isode.stroke.base; @@ -34,6 +34,14 @@ public class SafeByteArray extends ByteArray { public SafeByteArray(byte[] c) { super(c); } + + /** + * Constructs a new {@link SafeByteArray} containing the user supplied byte. + * @param b a byte. + */ + public SafeByteArray(byte b) { + super(b); + } /** * Creates a new SafeByteArray object containing all diff --git a/src/com/isode/stroke/filetransfer/ByteArrayReadBytestream.java b/src/com/isode/stroke/filetransfer/ByteArrayReadBytestream.java index 41528fa..5b3542f 100644 --- a/src/com/isode/stroke/filetransfer/ByteArrayReadBytestream.java +++ b/src/com/isode/stroke/filetransfer/ByteArrayReadBytestream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -11,6 +11,8 @@ package com.isode.stroke.filetransfer; +import java.util.Arrays; + import com.isode.stroke.base.ByteArray; public class ByteArrayReadBytestream extends ReadBytestream { @@ -30,9 +32,9 @@ public class ByteArrayReadBytestream extends ReadBytestream { if (position + readSize > data.getSize()) { readSize = data.getSize() - position; } - String s = data.toString(); - s = s.substring(position, position+readSize); - ByteArray result = new ByteArray(s); + byte[] rawBytes = data.getData(); + byte[] resultRawBytes = Arrays.copyOfRange(rawBytes, position, position+readSize); + ByteArray result = new ByteArray(resultRawBytes); onRead.emit(result); position += readSize; diff --git a/src/com/isode/stroke/filetransfer/OutgoingJingleFileTransfer.java b/src/com/isode/stroke/filetransfer/OutgoingJingleFileTransfer.java index 10760a1..c5a1f11 100644 --- a/src/com/isode/stroke/filetransfer/OutgoingJingleFileTransfer.java +++ b/src/com/isode/stroke/filetransfer/OutgoingJingleFileTransfer.java @@ -4,7 +4,7 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ /* - * Copyright (c) 2013-2015 Isode Limited. + * Copyright (c) 2013-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -100,6 +100,7 @@ public class OutgoingJingleFileTransfer extends JingleFileTransfer implements Ou this.idGenerator = idGenerator; this.stream = stream; this.fileInfo = fileInfo; + this.options = options; this.session = session; this.contentID = new JingleContentID(idGenerator.generateID(), JingleContentPayload.Creator.InitiatorCreator); this.state = State.Initial; diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java index 3e51b5f..18e4484 100644 --- a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java +++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java @@ -55,7 +55,7 @@ public class SOCKS5BytestreamClientSession extends SOCKS5AbstractBytestreamSessi private State state; - private ByteArray unprocessedData; + private final ByteArray unprocessedData = new ByteArray(); private ByteArray authenticateAddress; private int chunkSize; @@ -227,12 +227,9 @@ public class SOCKS5BytestreamClientSession extends SOCKS5AbstractBytestreamSessi 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); + SafeByteArray message = new SafeByteArray(new byte[]{0x05, 0x01, 0x00, 0x03}); + message.append((byte)destination.length()); + message.append(destination); message.append(new SafeByteArray(new byte[]{0x00, 0x00})); // 2 byte for port connection.write(message); state = State.Authenticating; @@ -324,7 +321,9 @@ public class SOCKS5BytestreamClientSession extends SOCKS5AbstractBytestreamSessi private void closeConnection() { connectFinishedConnection.disconnect(); - dataWrittenConnection.disconnect(); + if (dataWrittenConnection != null) { + dataWrittenConnection.disconnect(); + } dataReadConnection.disconnect(); disconnectedConnection.disconnect(); connection.disconnect(); diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerSession.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerSession.java index 8452e36..8facca0 100644 --- a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerSession.java +++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamServerSession.java @@ -15,6 +15,7 @@ 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.base.StartStoppable; import com.isode.stroke.signals.SignalConnection; import com.isode.stroke.signals.Signal1; import com.isode.stroke.signals.Signal; @@ -23,11 +24,11 @@ import com.isode.stroke.signals.Slot1; import java.util.logging.Logger; -public class SOCKS5BytestreamServerSession extends SOCKS5AbstractBytestreamSession { +public class SOCKS5BytestreamServerSession extends SOCKS5AbstractBytestreamSession implements StartStoppable { private Connection connection; private SOCKS5BytestreamRegistry bytestreams; - private ByteArray unprocessedData; + private ByteArray unprocessedData = new ByteArray(); private State state; private int chunkSize; private String streamID = ""; @@ -131,8 +132,12 @@ public class SOCKS5BytestreamServerSession extends SOCKS5AbstractBytestreamSess disconnectedConnection.disconnect(); dataReadConnection.disconnect(); - dataWrittenConnection.disconnect(); - dataAvailableConnection.disconnect(); + if (dataWrittenConnection != null) { + dataWrittenConnection.disconnect(); + } + if (dataAvailableConnection != null) { + dataAvailableConnection.disconnect(); + } readBytestream = null; state = State.Finished; if (error) { @@ -180,10 +185,10 @@ public class SOCKS5BytestreamServerSession extends SOCKS5AbstractBytestreamSess unprocessedData.clear(); streamID = requestID.toString(); boolean hasBytestream = bytestreams.hasBytestream(streamID); - SafeByteArray result = new SafeByteArray("0x05"); + SafeByteArray result = new SafeByteArray((byte)0x05); result.append(hasBytestream ? (byte)0x0 : (byte)0x4); result.append(new ByteArray(new byte[]{0x00, 0x03})); - result.append(Integer.toString(requestID.getSize())); + result.append((byte)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"); diff --git a/src/com/isode/stroke/network/DummyNetworkEnvironment.java b/src/com/isode/stroke/network/DummyNetworkEnvironment.java new file mode 100644 index 0000000..32524d9 --- /dev/null +++ b/src/com/isode/stroke/network/DummyNetworkEnvironment.java @@ -0,0 +1,32 @@ +/* Copyright (c) 2016, Isode Limited, London, England. + * All rights reserved. + * + * Acquisition and use of this software and related materials for any + * purpose requires a written license agreement from Isode Limited, + * or a written license from an organisation licensed by Isode Limited + * to grant such a license. + * + */ +package com.isode.stroke.network; + +import java.util.Vector; + +/** + * Dummy {@link NetworkEnvironment} for testing, returns an empty vector + * of {@link NetworkInterface} + */ +public class DummyNetworkEnvironment extends NetworkEnvironment { + + /** + * Constructor + */ + public DummyNetworkEnvironment() { + // Empty Constructor + } + + @Override + public Vector getNetworkInterfaces() { + return new Vector(); + } + +} diff --git a/test/com/isode/stroke/filetransfer/IBBReceiveSessionTest.java b/test/com/isode/stroke/filetransfer/IBBReceiveSessionTest.java new file mode 100644 index 0000000..bb6ac7a --- /dev/null +++ b/test/com/isode/stroke/filetransfer/IBBReceiveSessionTest.java @@ -0,0 +1,238 @@ +/* Copyright (c) 2016, Isode Limited, London, England. + * All rights reserved. + * + * Acquisition and use of this software and related materials for any + * purpose requires a written license agreement from Isode Limited, + * or a written license from an organisation licensed by Isode Limited + * to grant such a license. + * + */ +package com.isode.stroke.filetransfer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.client.DummyStanzaChannel; +import com.isode.stroke.elements.IBB; +import com.isode.stroke.elements.IBB.Action; +import com.isode.stroke.elements.IQ; +import com.isode.stroke.elements.IQ.Type; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.signals.Slot1; + +/** + * Tests for {@link IBBReceiveSession} + */ +public class IBBReceiveSessionTest { + + private final DummyStanzaChannel stanzaChannel = new DummyStanzaChannel(); + private final IQRouter iqRouter = new IQRouter(stanzaChannel); + private boolean finished = false; + private final ByteArrayWriteBytestream bytestream = new ByteArrayWriteBytestream(); + private FileTransferError error = null; + + @Test + public void testOpen() { + IBBReceiveSession testling = createSession("foo@bar.com/baz", "mysession"); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBBRequest(IBB.createIBBOpen("mysession", 0x10), + new JID("foo@bar.com/baz"), "id-open")); + assertTrue(stanzaChannel.isResultAtIndex(0, "id-open")); + assertFalse(finished); + testling.stop(); + } + + @Test + public void testReceiveData() { + IBBReceiveSession testling = createSession("foo@bar.com/baz", "mysession"); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBOpen("mysession", 0x10), + new JID("foo@bar.com/baz"), + "id-open")); + + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBData("mysession", 0, new ByteArray("abc")), + new JID("foo@bar.com/baz"), + "id-a")); + + assertTrue(stanzaChannel.isResultAtIndex(1, "id-a")); + assertEquals(new ByteArray("abc"),bytestream.getData()); + assertFalse(finished); + + testling.stop(); + } + + @Test + public void testReceiveMultipleData() { + IBBReceiveSession testling = createSession("foo@bar.com/baz", "mysession"); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBOpen("mysession", 0x10), + new JID("foo@bar.com/baz"), + "id-open")); + + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBData("mysession", 0, new ByteArray("abc")), + new JID("foo@bar.com/baz"), + "id-a")); + + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBData("mysession", 1, new ByteArray("def")), + new JID("foo@bar.com/baz"), + "id-b")); + + assertTrue(stanzaChannel.isResultAtIndex(2, "id-b")); + assertEquals(new ByteArray("abcdef"),bytestream.getData()); + assertFalse(finished); + + testling.stop(); + } + + @Test + public void testReceiveDataForOtherSession() { + IBBReceiveSession testling = createSession("foo@bar.com/baz", "mysession"); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBOpen("mysession", 0x10), + new JID("foo@bar.com/baz"), + "id-open")); + + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBData("othersession", 0, new ByteArray("abc")), + new JID("foo@bar.com/baz"), + "id-a")); + + assertTrue(stanzaChannel.isErrorAtIndex(1, "id-a")); + + testling.stop(); + } + + @Test + public void testReceiveDataOutOfOrder() { + IBBReceiveSession testling = createSession("foo@bar.com/baz", "mysession"); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBOpen("mysession", 0x10), + new JID("foo@bar.com/baz"), + "id-open")); + + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBData("mysession", 0, new ByteArray("abc")), + new JID("foo@bar.com/baz"), + "id-a")); + + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBData("mysession", 0, new ByteArray("def")), + new JID("foo@bar.com/baz"), + "id-b")); + + assertTrue(stanzaChannel.isErrorAtIndex(2, "id-b")); + assertTrue(finished); + assertNotNull(error); + + testling.stop(); + } + + @Test + public void testReceiveLastData() { + IBBReceiveSession testling = createSession("foo@bar.com/baz", "mysession", 6); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBOpen("mysession", 0x10), + new JID("foo@bar.com/baz"), + "id-open")); + + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBData("mysession", 0, new ByteArray("abc")), + new JID("foo@bar.com/baz"), + "id-a")); + + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBData("mysession", 1, new ByteArray("def")), + new JID("foo@bar.com/baz"), + "id-b")); + + assertTrue(stanzaChannel.isResultAtIndex(2, "id-b")); + assertEquals(new ByteArray("abcdef"),bytestream.getData()); + assertTrue(finished); + assertNull(error); + + testling.stop(); + } + + @Test + public void testReceiveClose() { + IBBReceiveSession testling = createSession("foo@bar.com/baz", "mysession", 6); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBOpen("mysession", 0x10), + new JID("foo@bar.com/baz"), + "id-open")); + + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBClose("mysession"), + new JID("foo@bar.com/baz"), + "id-close")); + + assertTrue(finished); + assertNotNull(error); + + testling.stop(); + } + + @Test + public void testStopWhileActive() { + IBBReceiveSession testling = createSession("foo@bar.com/baz", "mysession", 6); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBOpen("mysession", 0x10), + new JID("foo@bar.com/baz"), + "id-open")); + + testling.stop(); + + assertTrue(stanzaChannel.isRequestAtIndex(1, new JID("foo@bar.com/baz"), + Type.Set, new IBB())); + IBB ibb = stanzaChannel.sentStanzas.get(1).getPayload(new IBB()); + assertEquals(Action.Close,ibb.getAction()); + assertTrue(finished); + assertNull(error); + } + + private IQ createIBBRequest(IBB ibb,JID from,String id) { + IQ request = IQ.createRequest(Type.Set, new JID("baz@fum.com/dum"), id, ibb); + request.setFrom(from); + return request; + } + + private IBBReceiveSession createSession(String from,String id) { + return createSession(from, id, 0x1000); + } + + private IBBReceiveSession createSession(String from,String id,int size) { + IBBReceiveSession session = + new IBBReceiveSession(id, new JID(from), new JID(), size, bytestream, iqRouter); + session.onFinished.connect(new Slot1() { + + @Override + public void call(FileTransferError error) { + handleFinished(error); + } + }); + return session; + } + + private void handleFinished(FileTransferError error) { + finished = true; + this.error = error; + } + +} diff --git a/test/com/isode/stroke/filetransfer/IBBSendSessionTest.java b/test/com/isode/stroke/filetransfer/IBBSendSessionTest.java new file mode 100644 index 0000000..dd39d57 --- /dev/null +++ b/test/com/isode/stroke/filetransfer/IBBSendSessionTest.java @@ -0,0 +1,220 @@ +/* Copyright (c) 2016, Isode Limited, London, England. + * All rights reserved. + * + * Acquisition and use of this software and related materials for any + * purpose requires a written license agreement from Isode Limited, + * or a written license from an organisation licensed by Isode Limited + * to grant such a license. + * + */ +package com.isode.stroke.filetransfer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.client.DummyStanzaChannel; +import com.isode.stroke.elements.IBB; +import com.isode.stroke.elements.IBB.Action; +import com.isode.stroke.elements.IQ; +import com.isode.stroke.elements.IQ.Type; +import com.isode.stroke.elements.Stanza; +import com.isode.stroke.jid.JID; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.signals.Slot1; + +/** + * Tests for {@link IBBSendSession} + */ +public class IBBSendSessionTest { + + private final DummyStanzaChannel stanzaChannel = new DummyStanzaChannel(); + private final IQRouter iqRouter = new IQRouter(stanzaChannel); + private boolean finished; + private FileTransferError error; + private final ByteArrayReadBytestream bytestream = new ByteArrayReadBytestream(new ByteArray("abcdefg")); + + @Test + public void testStart() { + IBBSendSession testling = createSession("foo@bar.com/baz"); + testling.setBlockSize(1234); + testling.start(); + assertEquals(1,stanzaChannel.sentStanzas.size()); + assertTrue(stanzaChannel.isRequestAtIndex(0, new JID("foo@bar.com/baz"), Type.Set, new IBB())); + IBB ibb = stanzaChannel.sentStanzas.get(0).getPayload(new IBB()); + assertEquals(Action.Open,ibb.getAction()); + assertEquals(1234,ibb.getBlockSize()); + assertEquals("myid",ibb.getStreamID()); + } + + @Test + public void testStart_ResponseStartsSending() { + IBBSendSession testling = createSession("foo@bar.com/baz"); + testling.setBlockSize(3); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBResult()); + assertEquals(2, stanzaChannel.sentStanzas.size()); + assertTrue(stanzaChannel.isRequestAtIndex(1, new JID("foo@bar.com/baz"), Type.Set, new IBB())); + IBB ibb = stanzaChannel.sentStanzas.get(1).getPayload(new IBB()); + assertEquals(Action.Data,ibb.getAction()); + assertEquals(new ByteArray("abc"),ibb.getData()); + assertEquals(0,ibb.getSequenceNumber()); + assertEquals("myid",ibb.getStreamID()); + } + + @Test + public void testResponseContinuesSending() { + IBBSendSession testling = createSession("foo@bar.com/baz"); + testling.setBlockSize(3); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + assertEquals(3,stanzaChannel.sentStanzas.size()); + assertTrue(stanzaChannel.isRequestAtIndex(2, new JID("foo@bar.com/baz"), + Type.Set, new IBB())); + IBB ibb = stanzaChannel.sentStanzas.get(2).getPayload(new IBB()); + assertEquals(Action.Data,ibb.getAction()); + assertEquals(new ByteArray("def"),ibb.getData()); + assertEquals(1,ibb.getSequenceNumber()); + assertEquals("myid",ibb.getStreamID()); + } + + @Test + public void testResponsdToAllFinishes() { + IBBSendSession testling = createSession("foo@bar.com/baz"); + testling.setBlockSize(3); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + assertTrue(finished); + assertNull(error); + } + + @Test + public void testErrorResponseFinishesWithError() { + IBBSendSession testling = createSession("foo@bar.com/baz"); + testling.setBlockSize(3); + testling.start(); + Stanza sentStanza = stanzaChannel.sentStanzas.get(0); + IQ errorIQ = + IQ.createError(new JID("baz@fum.com/foo"), sentStanza.getTo(), sentStanza.getID()); + stanzaChannel.onIQReceived.emit(errorIQ); + assertTrue(finished); + assertNotNull(error); + } + + @Test + public void testStopDuringSessionCloses() { + IBBSendSession testling = createSession("foo@bar.com/baz"); + testling.setBlockSize(3); + testling.start(); + testling.stop(); + + assertEquals(2,stanzaChannel.sentStanzas.size()); + assertTrue(stanzaChannel.isRequestAtIndex(1, new JID("foo@bar.com/baz"), + Type.Set, new IBB())); + IBB ibb = stanzaChannel.sentStanzas.get(1).getPayload(new IBB()); + assertEquals(Action.Close,ibb.getAction()); + assertEquals("myid",ibb.getStreamID()); + assertTrue(finished); + assertNull(error); + } + + @Test + public void testStopAfterFinishedDoesNotClose() { + IBBSendSession testling = createSession("foo@bar.com/baz"); + testling.setBlockSize(16); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + assertTrue(finished); + testling.stop(); + assertEquals(2,stanzaChannel.sentStanzas.size()); + } + + @Test + public void testDataStreamPauseStopsSendingData() { + IBBSendSession testling = createSession("foo@bar.com/baz"); + bytestream.setDataComplete(false); + testling.setBlockSize(3); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + assertFalse(finished); + assertNull(error); + } + + @Test + public void testDataStreamResumeAfterPauseSendsData() { + IBBSendSession testling = createSession("foo@bar.com/baz"); + bytestream.setDataComplete(false); + testling.setBlockSize(3); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + bytestream.addData(new ByteArray("xyz")); + assertEquals(5,stanzaChannel.sentStanzas.size()); + } + + @Test + public void testDataStreamResumeBeforePauseDoesNotSendData() { + IBBSendSession testling = createSession("foo@bar.com/baz"); + bytestream.setDataComplete(false); + testling.setBlockSize(3); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBResult()); + bytestream.addData(new ByteArray("xyz")); + assertEquals(2,stanzaChannel.sentStanzas.size()); + } + + @Test + public void testDataStreamResumeAfterResumeDoesNotSendData() { + IBBSendSession testling = createSession("foo@bar.com/baz"); + bytestream.setDataComplete(false); + testling.setBlockSize(3); + testling.start(); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + stanzaChannel.onIQReceived.emit(createIBResult()); + bytestream.addData(new ByteArray("xyz")); + bytestream.addData(new ByteArray("xuv")); + assertEquals(5,stanzaChannel.sentStanzas.size()); + } + + private IQ createIBResult() { + Stanza lastStanza = stanzaChannel.sentStanzas.lastElement(); + return IQ.createResult(new JID("baz@fum.com/dum"), lastStanza.getTo(), + lastStanza.getID(), new IBB()); + } + + private IBBSendSession createSession(String to) { + IBBSendSession session = + new IBBSendSession("myid", new JID(), new JID(to), bytestream, iqRouter); + session.onFinished.connect(new Slot1() { + + @Override + public void call(FileTransferError error) { + handleFinished(error); + } + + }); + return session; + } + + private void handleFinished(FileTransferError error) { + finished = true; + this.error = error; + } +} diff --git a/test/com/isode/stroke/filetransfer/IncomingJingleFileTransferTest.java b/test/com/isode/stroke/filetransfer/IncomingJingleFileTransferTest.java new file mode 100644 index 0000000..87dbaa4 --- /dev/null +++ b/test/com/isode/stroke/filetransfer/IncomingJingleFileTransferTest.java @@ -0,0 +1,224 @@ +/* Copyright (c) 2016, Isode Limited, London, England. + * All rights reserved. + * + * Acquisition and use of this software and related materials for any + * purpose requires a written license agreement from Isode Limited, + * or a written license from an organisation licensed by Isode Limited + * to grant such a license. + * + */ +package com.isode.stroke.filetransfer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Test; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.base.IDGenerator; +import com.isode.stroke.base.SimpleIDGenerator; +import com.isode.stroke.client.DummyStanzaChannel; +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.crypto.JavaCryptoProvider; +import com.isode.stroke.elements.IBB; +import com.isode.stroke.elements.IQ; +import com.isode.stroke.elements.IQ.Type; +import com.isode.stroke.elements.JingleContentPayload; +import com.isode.stroke.elements.JingleFileTransferDescription; +import com.isode.stroke.elements.JingleFileTransferFileInfo; +import com.isode.stroke.elements.JingleIBBTransportPayload; +import com.isode.stroke.elements.JingleS5BTransportPayload; +import com.isode.stroke.eventloop.DummyEventLoop; +import com.isode.stroke.eventloop.EventLoop; +import com.isode.stroke.jid.JID; +import com.isode.stroke.jingle.FakeJingleSession; +import com.isode.stroke.jingle.JingleContentID; +import com.isode.stroke.network.DomainNameResolver; +import com.isode.stroke.network.DummyConnectionFactory; +import com.isode.stroke.network.DummyConnectionServerFactory; +import com.isode.stroke.network.DummyNetworkEnvironment; +import com.isode.stroke.network.DummyTimerFactory; +import com.isode.stroke.network.NATTraverser; +import com.isode.stroke.network.NetworkEnvironment; +import com.isode.stroke.network.NullNATTraverser; +import com.isode.stroke.network.StaticDomainNameResolver; +import com.isode.stroke.queries.IQRouter; + +/** + * Tests for {@link IncomingJingleFileTransfer} + */ +public class IncomingJingleFileTransferTest { + + private final CryptoProvider crypto = new JavaCryptoProvider(); + private final EventLoop eventLoop = new DummyEventLoop(); + private final DomainNameResolver resolver = new StaticDomainNameResolver(eventLoop); + private final FakeJingleSession session = + new FakeJingleSession(new JID("foo@bar.com/baz"),"mysession"); + private final JingleContentPayload jingleContentPayload = new JingleContentPayload(); + private final DummyStanzaChannel stanzaChannel = new DummyStanzaChannel(); + private final DummyConnectionFactory connectionFactory = + new DummyConnectionFactory(eventLoop); + private final DummyConnectionServerFactory serverConnectionFactory = + new DummyConnectionServerFactory(eventLoop); + private final IQRouter iqRouter = new IQRouter(stanzaChannel); + private final SOCKS5BytestreamRegistry bytestreamRegistry = new SOCKS5BytestreamRegistry(); + private final NetworkEnvironment networkEnvironment = new DummyNetworkEnvironment(); + private final NATTraverser natTraverser = new NullNATTraverser(eventLoop); + private final SOCKS5BytestreamServerManager bytestreamServerManager = + new SOCKS5BytestreamServerManager(bytestreamRegistry, serverConnectionFactory, + networkEnvironment, natTraverser); + private final IDGenerator idGenerator = new SimpleIDGenerator(); + private final DummyTimerFactory timerFactory = new DummyTimerFactory(); + private final SOCKS5BytestreamProxiesManager bytestreamProxy = + new SOCKS5BytestreamProxiesManager(connectionFactory, timerFactory, resolver, + iqRouter, new JID("bar.com")); + private final FileTransferTransporterFactory ftTransporterFactory = + new DefaultFileTransferTransporterFactory(bytestreamRegistry, bytestreamServerManager, + bytestreamProxy, idGenerator, connectionFactory, timerFactory, crypto, iqRouter); + + + @Test + public void test_AcceptOnyIBBSendsSessionAccept() { + // Tests whether IncomingJingleFileTransfer would accept a IBB only file transfer. + // 1 Create your test incoming file transfer + JingleFileTransferDescription desc = new JingleFileTransferDescription(); + desc.setFileInfo(new JingleFileTransferFileInfo("foo.tx", "", 10)); + jingleContentPayload.addDescription(desc); + JingleIBBTransportPayload tpRef = new JingleIBBTransportPayload(); + tpRef.setSessionID("mysession"); + jingleContentPayload.addTransport(tpRef); + + IncomingJingleFileTransfer fileTransfer = createTestling(); + + // 2 Do 'accept' on a dummy writebytestream (you'll have to look if there already is one) + ByteArrayWriteBytestream byteStream = new ByteArrayWriteBytestream(); + fileTransfer.accept(byteStream);; + + // 3 Check whether accept has been called + getCall(FakeJingleSession.AcceptCall.class,0); + } + + @Test + public void test_OnlyIBBTransferReceiveWorks() { + // 1 Create your test incoming file transfer + JingleFileTransferDescription desc = new JingleFileTransferDescription(); + desc.setFileInfo(new JingleFileTransferFileInfo("foo.tx", "", 10)); + jingleContentPayload.addDescription(desc); + JingleIBBTransportPayload tpRef = new JingleIBBTransportPayload(); + tpRef.setSessionID("mysession"); + jingleContentPayload.addTransport(tpRef); + + IncomingJingleFileTransfer fileTransfer = createTestling(); + + // 2 Do 'accept' on a dummy writebytestream (you'll have to look if there already is one) + ByteArrayWriteBytestream byteStream = new ByteArrayWriteBytestream(); + fileTransfer.accept(byteStream);; + + // 3 Check whether accept has been called + getCall(FakeJingleSession.AcceptCall.class,0); + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBOpen("myession", 10), + new JID("foo@bar.com/baz"), "id-open")); + stanzaChannel.onIQReceived.emit(createIBBRequest( + IBB.createIBBData("mysession", 0, new ByteArray("abc")), + new JID("foo@bar.com/baz"), "id-open")); + assertEquals(new ByteArray("abc"),byteStream.getData()); + } + +// This test is not run in the Swiften code (If it is run it fails there too) +// +// public void test_AcceptFailingS5BFallsBackToIBB() { +// // 1 Create your test incoming file transfer +// addFileTransferDescription(); +// +// // add SOCKS5BytestreamTransportPayload +// JingleS5BTransportPayload payload = addJingleS5BPayload(); +// +// IncomingJingleFileTransfer fileTransfer = createTestling(); +// +// // 2 Do 'accept' on a dummy writebytestream (you'll have to look if there already is one) +// ByteArrayWriteBytestream byteStream = new ByteArrayWriteBytestream(); +// fileTransfer.accept(byteStream);; +// +// // Candidates are gathered +// +// // Check whether accept has been called +// FakeJingleSession.AcceptCall acceptCall = getCall(FakeJingleSession.AcceptCall.class, 0); +// assertEquals(payload.getSessionID(),acceptCall.payload.getSessionID()); +// +// // Check for candiate error +// FakeJingleSession.InfoTransportCall infoTransportCall = getCall(FakeJingleSession.InfoTransportCall.class,1); +// JingleS5BTransportPayload s5bPayload = null; +// if (infoTransportCall.payload instanceof JingleS5BTransportPayload) { +// s5bPayload = (JingleS5BTransportPayload) infoTransportCall.payload; +// } +// assertNotNull(s5bPayload); +// assertTrue(s5bPayload.hasCandidateError()); +// +// // Indicate transport replace (Romeo) +// session.handleTransportReplaceReceived(getContentID(), addJingleIBBPayload()); +// +// FakeJingleSession.AcceptTransportCall acceptTranpsportCall = getCall(FakeJingleSession.AcceptTransportCall.class,2); +// +// // Send a bit of data +// stanzaChannel.onIQReceived.emit(createIBBRequest(IBB.createIBBOpen("mysession", 10), new JID("foo@bar.com/baz"), "id-open")); +// stanzaChannel.onIQReceived.emit(createIBBRequest(IBB.createIBBData("mysession", 0, new ByteArray("abc")), new JID("foo@bar.com/baz"), "id-a")); +// assertEquals(new ByteArray("abc"),byteStream.getData()); +// +// } + + private IncomingJingleFileTransfer createTestling() { + JID ourJID = new JID(""); + return new IncomingJingleFileTransfer(ourJID, session, jingleContentPayload, + ftTransporterFactory, timerFactory, crypto); + } + + private IQ createIBBRequest(IBB payload,JID from,String id) { + IQ request = IQ.createRequest(Type.Set, new JID("foo@bar.com/baz"), id, payload); + request.setFrom(from); + return request; + } + + private void addFileTransferDescription() { + JingleFileTransferDescription desc = new JingleFileTransferDescription(); + desc.setFileInfo(new JingleFileTransferFileInfo("file.txt", "", 10)); + jingleContentPayload.addDescription(desc); + } + + private JingleS5BTransportPayload addJingleS5BPayload() { + JingleS5BTransportPayload payLoad = new JingleS5BTransportPayload(); + payLoad.setSessionID("mysession"); + jingleContentPayload.addTransport(payLoad); + return payLoad; + } + + private JingleIBBTransportPayload addJingleIBBPayload() { + JingleIBBTransportPayload payLoad = new JingleIBBTransportPayload(); + payLoad.setSessionID("mysession"); + jingleContentPayload.addTransport(payLoad); + return payLoad; + } + + private JingleContentID getContentID() { + return new JingleContentID(jingleContentPayload.getName(), + jingleContentPayload.getCreator()); + } + + private T getCall(Class target,int i) { + assertTrue("Index "+i+" is not less then session.calledCommands.size() = " + +session.calledCommands.size(), + i < session.calledCommands.size()); + Object rawObject = session.calledCommands.get(i); + try { + return target.cast(rawObject); + } + catch (ClassCastException e) { + fail("Item could not be cast to type "+e.getMessage()); + } + // Should not get here + return null; + } + + +} diff --git a/test/com/isode/stroke/filetransfer/OutgoingJingleFileTransferTest.java b/test/com/isode/stroke/filetransfer/OutgoingJingleFileTransferTest.java new file mode 100644 index 0000000..4532efb --- /dev/null +++ b/test/com/isode/stroke/filetransfer/OutgoingJingleFileTransferTest.java @@ -0,0 +1,269 @@ +/* Copyright (c) 2016, Isode Limited, London, England. + * All rights reserved. + * + * Acquisition and use of this software and related materials for any + * purpose requires a written license agreement from Isode Limited, + * or a written license from an organisation licensed by Isode Limited + * to grant such a license. + * + */ +package com.isode.stroke.filetransfer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.base.IDGenerator; +import com.isode.stroke.client.DummyStanzaChannel; +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.crypto.JavaCryptoProvider; +import com.isode.stroke.elements.HashElement; +import com.isode.stroke.elements.IBB; +import com.isode.stroke.elements.IQ; +import com.isode.stroke.elements.JingleContentPayload; +import com.isode.stroke.elements.JingleFileTransferDescription; +import com.isode.stroke.elements.JingleFileTransferFileInfo; +import com.isode.stroke.elements.JingleIBBTransportPayload; +import com.isode.stroke.elements.JingleS5BTransportPayload; +import com.isode.stroke.elements.JinglePayload.Reason; +import com.isode.stroke.eventloop.DummyEventLoop; +import com.isode.stroke.eventloop.EventLoop; +import com.isode.stroke.filetransfer.FileTransfer.State; +import com.isode.stroke.filetransfer.FileTransferError.Type; +import com.isode.stroke.jid.JID; +import com.isode.stroke.jingle.FakeJingleSession; +import com.isode.stroke.jingle.JingleContentID; +import com.isode.stroke.network.DomainNameResolver; +import com.isode.stroke.network.DummyConnectionFactory; +import com.isode.stroke.network.DummyConnectionServerFactory; +import com.isode.stroke.network.DummyNetworkEnvironment; +import com.isode.stroke.network.DummyTimerFactory; +import com.isode.stroke.network.NATTraverser; +import com.isode.stroke.network.NetworkEnvironment; +import com.isode.stroke.network.NullNATTraverser; +import com.isode.stroke.network.StaticDomainNameResolver; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.signals.Slot1; + +/** + * Tests for {@link OutgoingJingleFileTransfer} + * + */ +public class OutgoingJingleFileTransferTest { + + private final CryptoProvider crypto = new JavaCryptoProvider(); + private final FakeJingleSession fakeJingleSession = + new FakeJingleSession(new JID("foo@bar.com/baz"), "mysession"); + private final JingleContentPayload jingleContentPayload = new JingleContentPayload(); + private final DummyStanzaChannel stanzaChannel = new DummyStanzaChannel(); + private final IQRouter iqRouter = new IQRouter(stanzaChannel); + private final EventLoop eventLoop = new DummyEventLoop(); + private final DummyTimerFactory timeFactory = new DummyTimerFactory(); + private final DummyConnectionFactory connectionFactory = new DummyConnectionFactory(eventLoop); + private final DummyConnectionServerFactory serverConnectionFactory = new DummyConnectionServerFactory(eventLoop); + private final SOCKS5BytestreamRegistry s5bRegistry = new SOCKS5BytestreamRegistry(); + private final NetworkEnvironment networkEnviroment = new DummyNetworkEnvironment(); + private final NATTraverser natTraverser = new NullNATTraverser(eventLoop); + private final SOCKS5BytestreamServerManager bytestreamServerManager = + new SOCKS5BytestreamServerManager(s5bRegistry, serverConnectionFactory, networkEnviroment, natTraverser); + private final ByteArray data = new ByteArray(); + { + for (int n = 0; n < (1024 * 1024); ++n) { + data.append((byte)34); + } + } + private final ByteArrayReadBytestream stream = new ByteArrayReadBytestream(data); + private final IDGenerator idGen = new IDGenerator(); + private final DomainNameResolver resolver = new StaticDomainNameResolver(eventLoop); + private final SOCKS5BytestreamProxiesManager s5bProxy = + new SOCKS5BytestreamProxiesManager(connectionFactory, timeFactory, resolver, + iqRouter, new JID("bar.com")); + private final FileTransferTransporterFactory ftTransporterFactory = + new DummyFileTransferTransporterFactory(s5bRegistry, bytestreamServerManager, s5bProxy, idGen, connectionFactory, timeFactory, crypto, iqRouter); + + @Test + public void test_SendSessionInitiateOnStart() { + OutgoingJingleFileTransfer transfer = createTestling(); + transfer.start(); + + FakeJingleSession.InitiateCall call = getCall(FakeJingleSession.InitiateCall.class,0); + JingleFileTransferDescription description = null; + if (call.description instanceof JingleFileTransferDescription) { + description = (JingleFileTransferDescription) call.description; + } + assertNotNull(description); + assertEquals(1048576,description.getFileInfo().getSize()); + + JingleS5BTransportPayload transport = null; + if (call.payload instanceof JingleS5BTransportPayload) { + transport = (JingleS5BTransportPayload) call.payload; + } + assertNotNull(transport); + } + + @Test + public void test_FallbackToIBBAfterFailingS5b() { + OutgoingJingleFileTransfer transfer = createTestling(); + transfer.start(); + + FakeJingleSession.InitiateCall call = getCall(FakeJingleSession.InitiateCall.class,0); + + fakeJingleSession.handleSessionAcceptReceived(call.id, call.description, call.payload); + + // Send candidate failure + JingleS5BTransportPayload candiateFailurePayload = new JingleS5BTransportPayload(); + candiateFailurePayload.setCandidateError(true); + candiateFailurePayload.setSessionID(call.payload.getSessionID()); + fakeJingleSession.handleTransportInfoReceived(call.id, candiateFailurePayload); + + // no S5B candidates -> fall back to IBB + // call at position 1 is the candidate our candidate error + FakeJingleSession.ReplaceTransportCall replaceCall = + getCall(FakeJingleSession.ReplaceTransportCall.class,2); + + // accept transport replace + fakeJingleSession.handleTransportAcceptReceived(replaceCall.id, replaceCall.payload); + + IQ iqOpenStanza = stanzaChannel.getStanzaAtIndex(new IQ(), 0); + assertNotNull(iqOpenStanza); + IBB ibbOpen = iqOpenStanza.getPayload(new IBB()); + assertNotNull(ibbOpen); + assertEquals(IBB.Action.Open,ibbOpen.getAction()); + + } + + @Test + public void test_ReceiveSessionTerminateAfterSessionInitiate() { + OutgoingJingleFileTransfer transfer = createTestling(); + transfer.start(); + + getCall(FakeJingleSession.InitiateCall.class,0); + + final FTStatusHelper helper = new FTStatusHelper(); + helper.finishedCalled = false; + transfer.onFinished.connect(new Slot1() { + + @Override + public void call(FileTransferError error) { + helper.handleFileTransferFinished(error); + } + + }); + fakeJingleSession.handleSessionTerminateReceived(new Reason(Reason.Type.Busy)); + assertTrue(helper.finishedCalled); + assertEquals(FileTransferError.Type.PeerError,helper.errorType); + } + + @Test + public void test_DeclineEmitsFinishedStateCanceled() { + OutgoingJingleFileTransfer transfer = createTestling(); + transfer.start(); + + getCall(FakeJingleSession.InitiateCall.class,0); + + final FTStatusHelper helper = new FTStatusHelper(); + helper.finishedCalled = false; + transfer.onFinished.connect(new Slot1() { + + @Override + public void call(FileTransferError error) { + helper.handleFileTransferFinished(error); + } + + }); + transfer.onStateChanged.connect(new Slot1() { + + @Override + public void call(State newState) { + helper.handleFileTransferStatusChanged(newState); + } + + }); + fakeJingleSession.handleSessionTerminateReceived(new Reason(Reason.Type.Decline)); + assertTrue(helper.finishedCalled); + assertEquals(FileTransferError.Type.UnknownError, helper.errorType); + assertEquals(State.Type.Canceled,helper.state.type); + } + + private static class FTStatusHelper { + + public FTStatusHelper() { + // Empty Constructor + } + + public void handleFileTransferFinished(FileTransferError error) { + finishedCalled = true; + if (error != null) { + errorType = error.getType(); + } + } + + public void handleFileTransferStatusChanged(State fileTransferState) { + state = fileTransferState; + } + private boolean finishedCalled = false; + private Type errorType = Type.UnknownError; + private State state = null; + } + + private OutgoingJingleFileTransfer createTestling() { + JID to = new JID("test@foo.com/bla"); + JingleFileTransferFileInfo fileInfo = new JingleFileTransferFileInfo(); + fileInfo.setDescription("some file"); + fileInfo.setName("test.bin"); + fileInfo.addHash(new HashElement("sha-1", new ByteArray())); + fileInfo.setSize(1024 * 1024); + FileTransferOptions options = (new FileTransferOptions()).withAssistedAllowed(false).withDirectAllowed(false).withProxiedAllowed(false); + return new OutgoingJingleFileTransfer(to, fakeJingleSession, stream, + ftTransporterFactory, timeFactory, idGen, fileInfo, options, crypto); + } + + private IQ createIBBRequest(IBB ibb,JID from,String id) { + IQ request = IQ.createRequest(IQ.Type.Set, new JID("foo@bar.com/baz"), id, ibb); + request.setFrom(from); + return request; + } + + private void addFileTransferDescription() { + JingleFileTransferDescription desc = new JingleFileTransferDescription(); + desc.setFileInfo(new JingleFileTransferFileInfo()); + jingleContentPayload.addDescription(desc); + } + + private JingleS5BTransportPayload addJingleS5BPayload() { + JingleS5BTransportPayload payLoad = new JingleS5BTransportPayload(); + payLoad.setSessionID("mysession"); + jingleContentPayload.addTransport(payLoad); + return payLoad; + } + + private JingleIBBTransportPayload addJingleIBBPayload() { + JingleIBBTransportPayload payLoad = new JingleIBBTransportPayload(); + payLoad.setSessionID("mysession"); + jingleContentPayload.addTransport(payLoad); + return payLoad; + } + + private JingleContentID getContentID() { + return new JingleContentID(jingleContentPayload.getName(), + jingleContentPayload.getCreator()); + } + + private T getCall(Class target,int i) { + assertTrue(i < fakeJingleSession.calledCommands.size()); + Object rawObject = fakeJingleSession.calledCommands.get(i); + try { + return target.cast(rawObject); + } + catch (ClassCastException e) { + fail("Item could not be cast to type "+e.getMessage()); + } + // Should not get here + return null; + } + +} diff --git a/test/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSessionTest.java b/test/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSessionTest.java new file mode 100644 index 0000000..ba696b7 --- /dev/null +++ b/test/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSessionTest.java @@ -0,0 +1,427 @@ +/* Copyright (c) 2016, Isode Limited, London, England. + * All rights reserved. + * + * Acquisition and use of this software and related materials for any + * purpose requires a written license agreement from Isode Limited, + * or a written license from an organisation licensed by Isode Limited + * to grant such a license. + * + */ +package com.isode.stroke.filetransfer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Random; + +import org.junit.Before; +import org.junit.Test; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.base.SafeByteArray; +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.crypto.JavaCryptoProvider; +import com.isode.stroke.eventloop.DummyEventLoop; +import com.isode.stroke.eventloop.Event.Callback; +import com.isode.stroke.eventloop.EventLoop; +import com.isode.stroke.eventloop.EventOwner; +import com.isode.stroke.network.Connection; +import com.isode.stroke.network.DummyTimerFactory; +import com.isode.stroke.network.HostAddress; +import com.isode.stroke.network.HostAddressPort; +import com.isode.stroke.signals.Signal1; +import com.isode.stroke.signals.Slot1; + +/** + * Tests for {@link SOCKS5BytestreamClientSession} + */ +public class SOCKS5BytestreamClientSessionTest { + + private static final Random rng = new Random(); + + private final HostAddressPort destinationAddressPort = new HostAddressPort(new HostAddress("127.0.0.1"), 8888); + private final CryptoProvider crypto = new JavaCryptoProvider(); + private final String destination = "092a44d859d19c9eed676b551ee80025903351c2"; + private final DummyEventLoop eventLoop = new DummyEventLoop(); + private final DummyTimerFactory timerFactory = new DummyTimerFactory(); + private final List failingPorts = new ArrayList(); + private final MockeryConnection connection = + new MockeryConnection(failingPorts, true, eventLoop); + + @Before + public void setUp() { + rng.setSeed(System.currentTimeMillis()); + } + + @Test + public void testForSessionReady() { + final TestHelper helper = new TestHelper(); + connection.onDataSent.connect(new Slot1() { + + @Override + public void call(SafeByteArray data) { + helper.handleConnectionDataWritten(data); + } + + }); + + SOCKS5BytestreamClientSession clientSession = new SOCKS5BytestreamClientSession(connection, destinationAddressPort, destination, timerFactory); + clientSession.onSessionReady.connect(new Slot1() { + + @Override + public void call(Boolean hasError) { + helper.handleSessionRead(hasError.booleanValue()); + } + + }); + + clientSession.start(); + eventLoop.processEvents(); + assertEquals(new ByteArray(new byte[] {0x05,0x01,0x00}),helper.unprocessedInput); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop.processEvents(); + ByteArray expected = new ByteArray(new byte[] {0x05,0x01,0x00,0x03}); + expected.append((byte)destination.length()); + expected.append(destination); + expected.append((byte)0x00); + ByteArray results = getSubArray(helper.unprocessedInput, expected.getSize()); + assertEquals(expected,results); + + helper.unprocessedInput.clear(); + serverRespondRequestOK(); + eventLoop.processEvents(); + assertTrue(helper.sessionReadyCalled); + assertFalse(helper.sessionReadyError); + } + + @Test + public void testErrorHandlingHello() { + final TestHelper helper = new TestHelper(); + connection.onDataSent.connect(new Slot1() { + + @Override + public void call(SafeByteArray data) { + helper.handleConnectionDataWritten(data); + } + + }); + + SOCKS5BytestreamClientSession clientSession = new SOCKS5BytestreamClientSession(connection, destinationAddressPort, destination, timerFactory); + clientSession.onSessionReady.connect(new Slot1() { + + @Override + public void call(Boolean hasError) { + helper.handleSessionRead(hasError.booleanValue()); + } + + }); + + clientSession.start(); + eventLoop.processEvents(); + assertEquals(new ByteArray(new byte[] {0x05,0x01,0x00}),helper.unprocessedInput); + + helper.unprocessedInput.clear(); + serverRespondHelloAuthFail(); + eventLoop.processEvents(); + + assertTrue(helper.sessionReadyCalled); + assertTrue(helper.sessionReadyError); + assertTrue(connection.disconnectCalled); + } + + @Test + public void testErrorHandlingRequest() { + final TestHelper helper = new TestHelper(); + connection.onDataSent.connect(new Slot1() { + + @Override + public void call(SafeByteArray data) { + helper.handleConnectionDataWritten(data); + } + + }); + + SOCKS5BytestreamClientSession clientSession = new SOCKS5BytestreamClientSession(connection, destinationAddressPort, destination, timerFactory); + clientSession.onSessionReady.connect(new Slot1() { + + @Override + public void call(Boolean hasError) { + helper.handleSessionRead(hasError.booleanValue()); + } + + }); + + clientSession.start(); + eventLoop.processEvents(); + assertEquals(new ByteArray(new byte[] {0x05,0x01,0x00}),helper.unprocessedInput); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop.processEvents(); + ByteArray expected = new ByteArray(new byte[] {0x05,0x01,0x00,0x03}); + expected.append((byte)destination.length()); + expected.append(destination); + expected.append((byte)0x00); + ByteArray results = getSubArray(helper.unprocessedInput, expected.getSize()); + assertEquals(expected,results); + + helper.unprocessedInput.clear(); + serverRespondRequestFail(); + eventLoop.processEvents(); + assertTrue(helper.sessionReadyCalled); + assertTrue(helper.sessionReadyError); + assertTrue(connection.disconnectCalled); + } + + @Test + public void testWriteBytestream() { + final TestHelper helper = new TestHelper(); + connection.onDataSent.connect(new Slot1() { + + @Override + public void call(SafeByteArray data) { + helper.handleConnectionDataWritten(data); + } + + }); + + SOCKS5BytestreamClientSession clientSession = new SOCKS5BytestreamClientSession(connection, destinationAddressPort, destination, timerFactory); + clientSession.onSessionReady.connect(new Slot1() { + + @Override + public void call(Boolean hasError) { + helper.handleSessionRead(hasError.booleanValue()); + } + + }); + + clientSession.start(); + eventLoop.processEvents(); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop.processEvents(); + + helper.unprocessedInput.clear(); + serverRespondRequestOK(); + eventLoop.processEvents(); + assertTrue(helper.sessionReadyCalled); + assertFalse(helper.sessionReadyError); + + ByteArrayWriteBytestream output = new ByteArrayWriteBytestream(); + clientSession.startReceiving(output); + + ByteArray transferData = generateRandomByteArray(1024); + connection.onDataRead.emit(new SafeByteArray(transferData)); + assertEquals(transferData,output.getData()); + } + + @Test + public void testReadBytestream() { + final TestHelper helper = new TestHelper(); + connection.onDataSent.connect(new Slot1() { + + @Override + public void call(SafeByteArray data) { + helper.handleConnectionDataWritten(data); + } + + }); + + SOCKS5BytestreamClientSession clientSession = new SOCKS5BytestreamClientSession(connection, destinationAddressPort, destination, timerFactory); + clientSession.onSessionReady.connect(new Slot1() { + + @Override + public void call(Boolean hasError) { + helper.handleSessionRead(hasError.booleanValue()); + } + + }); + + clientSession.start(); + eventLoop.processEvents(); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop.processEvents(); + + helper.unprocessedInput.clear(); + serverRespondRequestOK(); + eventLoop.processEvents(); + assertTrue(helper.sessionReadyCalled); + assertFalse(helper.sessionReadyError); + + helper.unprocessedInput.clear(); + ByteArray transferData = generateRandomByteArray(1024); + ByteArrayReadBytestream input = new ByteArrayReadBytestream(transferData); + clientSession.startSending(input); + eventLoop.processEvents(); + + assertEquals(transferData,helper.unprocessedInput); + } + + private static ByteArray generateRandomByteArray(int len) { + byte[] randomBytes = new byte[len]; + rng.nextBytes(randomBytes); + return new ByteArray(randomBytes); + } + + private void serverRespondHelloOK() { + connection.onDataRead.emit(new SafeByteArray(new byte[] {0x05,0x00})); + } + + private void serverRespondHelloAuthFail() { + connection.onDataRead.emit(new SafeByteArray(new byte[] {0x05,(byte) 0xFF})); + } + + private void serverRespondRequestOK() { + SafeByteArray dataToSend = new SafeByteArray(new byte[] {0x05,0x00,0x00,0x03}); + dataToSend.append((byte)destination.length()); + dataToSend.append(destination); + dataToSend.append((byte)0x00); + connection.onDataRead.emit(dataToSend); + } + + private void serverRespondRequestFail() { + SafeByteArray correctData = new SafeByteArray(new byte[] {0x05,0x00,0x00,0x03}); + correctData.append((byte)destination.length()); + correctData.append(destination); + correctData.append((byte)0x00); + SafeByteArray dataToSend; + do { + ByteArray rndArray = generateRandomByteArray(correctData.getSize()); + dataToSend = new SafeByteArray(rndArray); + } while (dataToSend.equals(correctData)); + connection.onDataRead.emit(dataToSend); + } + + /** + * Gets the sub {@link ByteArray} consisting of the first n bytes of + * a given {@link ByteArray} + * @param array A {@link ByteArray} should not be {@code null} and should + * be at least n characters long. + * @param n the number of bytes of the {@link ByteArray} to return as a new + * {@link ByteArray} + * @return The first n characters of the given {@link ByteArray} as a new + * {@link ByteArray}. Will not be {@code null} + */ + private ByteArray getSubArray(ByteArray array,int n) { + byte[] arrayData = array.getData(); + byte[] newArrayData = Arrays.copyOfRange(arrayData, 0, n); + return new ByteArray(newArrayData); + } + + private static final class TestHelper { + + private ByteArray unprocessedInput = new ByteArray(); + private boolean sessionReadyCalled = false; + private boolean sessionReadyError = false; + + public TestHelper() { + // Empty Constructor + } + + public void handleConnectionDataWritten(SafeByteArray data) { + unprocessedInput.append(data); + } + + public void handleSessionRead(boolean error) { + sessionReadyCalled = true; + sessionReadyError = error; + } + + } + + + private static final class MockeryConnection extends Connection implements EventOwner { + + private EventLoop eventLoop; + private HostAddressPort hostAddressPort; + private final List failingPorts; + private boolean isResponsive; + private boolean disconnectCalled; + + private final Signal1 onDataSent = new Signal1(); + + public MockeryConnection(Collection failingPorts, + boolean isResponsive,EventLoop eventLoop) { + this.eventLoop = eventLoop; + this.failingPorts = new ArrayList(failingPorts); + this.isResponsive = isResponsive; + this.disconnectCalled = false; + } + + @Override + public void listen() { + fail(); + } + + /* (non-Javadoc) + * @see com.isode.stroke.network.Connection#connect(com.isode.stroke.network.HostAddressPort) + */ + @Override + public void connect(HostAddressPort address) { + hostAddressPort = address; + if (isResponsive) { + final boolean fail = failingPorts.contains(address); + eventLoop.postEvent(new Callback() { + + @Override + public void run() { + onConnectFinished.emit(fail); + } + + }); + } + } + + /* (non-Javadoc) + * @see com.isode.stroke.network.Connection#disconnect() + */ + @Override + public void disconnect() { + disconnectCalled = true; + } + + /* (non-Javadoc) + * @see com.isode.stroke.network.Connection#write(com.isode.stroke.base.SafeByteArray) + */ + @Override + public void write(SafeByteArray data) { + eventLoop.postEvent(new Callback() { + + @Override + public void run() { + onDataWritten.emit(); + } + + }); + onDataSent.emit(data); + } + + + + /* (non-Javadoc) + * @see com.isode.stroke.network.Connection#getLocalAddress() + */ + @Override + public HostAddressPort getLocalAddress() { + return new HostAddressPort(); + } + + public HostAddressPort getRemoteAddress() { + return new HostAddressPort(); + } + + } + +} diff --git a/test/com/isode/stroke/filetransfer/SOCKS5BytestreamServerSessionTest.java b/test/com/isode/stroke/filetransfer/SOCKS5BytestreamServerSessionTest.java new file mode 100644 index 0000000..d5dc3da --- /dev/null +++ b/test/com/isode/stroke/filetransfer/SOCKS5BytestreamServerSessionTest.java @@ -0,0 +1,256 @@ +/* Copyright (c) 2016, Isode Limited, London, England. + * All rights reserved. + * + * Acquisition and use of this software and related materials for any + * purpose requires a written license agreement from Isode Limited, + * or a written license from an organisation licensed by Isode Limited + * to grant such a license. + * + */ +package com.isode.stroke.filetransfer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +import java.util.Arrays; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.base.SafeByteArray; +import com.isode.stroke.base.StartStopper; +import com.isode.stroke.eventloop.DummyEventLoop; +import com.isode.stroke.network.DummyConnection; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot1; + +/** + * Tests for {@link SOCKS5BytestreamServerSession} + */ +public class SOCKS5BytestreamServerSessionTest { + + private final DummyEventLoop eventLoop = new DummyEventLoop(); + private final SOCKS5BytestreamRegistry bytestreams = new SOCKS5BytestreamRegistry(); + private final DummyConnection connection = new DummyConnection(eventLoop); + private final ByteArray receivedData = new ByteArray(); + private int receivedDataChunks = 0; + private final ByteArrayReadBytestream stream1 = + new ByteArrayReadBytestream(new ByteArray("abcdefg")); + private boolean finished = false; + private FileTransferError error = null; + private SignalConnection onDataSentConnection; + + + @Before + public void setUp() { + onDataSentConnection = connection.onDataSent.connect(new Slot1() { + + @Override + public void call(SafeByteArray data) { + handleDataWritten(data); + } + + }); + } + + @After + public void tearDown() { + onDataSentConnection.disconnect(); + } + + @Test + public void testAuthenticate() { + SOCKS5BytestreamServerSession testling = createSession(); + StartStopper stopper = + new StartStopper(testling); + receive(new SafeByteArray(new byte[] {0x05,0x02,0x01,0x02})); + SafeByteArray expected = new SafeByteArray(new byte[] {0x05,0x00}); + assertEquals(expected,receivedData); + } + + + @Test + public void testAuthenticate_Chunked() { + SOCKS5BytestreamServerSession testling = createSession(); + StartStopper stopper = + new StartStopper(testling); + receive(new SafeByteArray(new byte[] {0x05,0x02,0x01})); + assertEquals(0,receivedData.getSize()); + receive(new SafeByteArray(new byte[] {0x02})); + SafeByteArray expected = new SafeByteArray(new byte[] {0x05,0x00}); + assertEquals(expected,receivedData); + } + + @Test + public void testRequest() { + SOCKS5BytestreamServerSession testling = createSession(); + StartStopper stopper = + new StartStopper(testling); + bytestreams.setHasBytestream("abcdef", true); + authenticate(); + + ByteArray hostname = new ByteArray("abcdef"); + SafeByteArray data = new SafeByteArray(); + data.append(new byte[] {0x05,0x01,0x00,0x03}); + data.append((byte)hostname.getSize()); + data.append(hostname); + data.append(new byte[] {0x00,0x00}); + receive(data); + // Compare first 13 bytes of received data with what we expect + ByteArray expectedData = + new ByteArray(new byte[] {0x05,0x00,0x00,0x03,0x06,0x61,0x62, + 0x63,0x64,0x65,0x66,0x00,0x00}); + assertEquals(expectedData,receivedData); + + } + + @Test + public void testRequest_UnknownBytestream() { + SOCKS5BytestreamServerSession testling = createSession(); + StartStopper stopper = + new StartStopper(testling); + authenticate(); + ByteArray hostname = new ByteArray("abcdef"); + SafeByteArray data = new SafeByteArray(); + data.append(new byte[]{0x05,0x01,0x00,0x03}); + data.append((byte)hostname.getSize()); + data.append(hostname); + data.append(new byte[] {0x00,0x00}); + receive(data); + + ByteArray expected = + new ByteArray(new byte[] {0x05,0x04,0x00,0x03,0x06,0x61,0x62, + 0x63,0x64,0x65,0x66,0x00,0x00}); + + assertEquals(expected,receivedData); + } + + @Test + public void testReceiveData() { + SOCKS5BytestreamServerSession testling = createSession(); + StartStopper stopper = + new StartStopper(testling); + bytestreams.setHasBytestream("abcdef", true); + authenticate(); + request("abcdef"); + eventLoop.processEvents(); + testling.startSending(stream1); + skipHeader("abcdef"); + eventLoop.processEvents(); + assertEquals(new ByteArray("abcdefg"),receivedData); + assertEquals(2,receivedDataChunks); + } + + @Test + public void testReceiveData_Chunked() { + SOCKS5BytestreamServerSession testling = createSession(); + testling.setChunkSize(3); + StartStopper stopper = + new StartStopper(testling); + bytestreams.setHasBytestream("abcdef", true); + authenticate(); + request("abcdef"); + eventLoop.processEvents(); + testling.startSending(stream1); + eventLoop.processEvents(); + skipHeader("abcdef"); + assertEquals(new ByteArray("abcdefg"),receivedData); + assertEquals(4,receivedDataChunks); + } + + @Test + public void testDataStreamPauseStopsSendingData() { + SOCKS5BytestreamServerSession testling = createSession(); + testling.setChunkSize(3); + stream1.setDataComplete(false); + StartStopper stopper = + new StartStopper(testling); + bytestreams.setHasBytestream("abcdef", true); + authenticate(); + request("abcdef"); + eventLoop.processEvents(); + testling.startSending(stream1); + eventLoop.processEvents(); + skipHeader("abcdef"); + assertEquals(new ByteArray("abcdefg"),receivedData); + assertEquals(4,receivedDataChunks); + assertFalse(finished); + assertNull(error); + } + + @Test + public void testDataStreamResumeAfterPauseSendsData() { + SOCKS5BytestreamServerSession testling = createSession(); + testling.setChunkSize(3); + stream1.setDataComplete(false); + StartStopper stopper = + new StartStopper(testling); + bytestreams.setHasBytestream("abcdef", true); + authenticate(); + request("abcdef"); + eventLoop.processEvents(); + testling.startSending(stream1); + eventLoop.processEvents(); + skipHeader("abcdef"); + stream1.addData(new ByteArray("xyz")); + eventLoop.processEvents(); + assertEquals(new ByteArray("abcdefgxyz"),receivedData); + assertFalse(finished); + assertNull(error); + } + + private void receive(SafeByteArray data) { + connection.receive(data); + eventLoop.processEvents(); + } + + private void authenticate() { + receive(new SafeByteArray(new byte[] {0x05,0x02,0x01,0x02})); + receivedData.clear(); + receivedDataChunks = 0; + } + + private void request(String hostname) { + SafeByteArray results = new SafeByteArray(); + results.append(new byte[] {0x05,0x01,0x00,0x03}); + results.append((byte) hostname.length()); + results.append(hostname); + results.append(new byte[] {0x00,0x00}); + receive(results); + } + + private void skipHeader(String hostname) { + int headerSize = 7 + hostname.length(); + byte[] currentReceivedData = receivedData.getData(); + byte[] newContents = Arrays.copyOfRange(currentReceivedData, headerSize, currentReceivedData.length); + receivedData.clear(); + receivedData.append(newContents); + } + + private void handleDataWritten(SafeByteArray data) { + receivedData.append(data); + receivedDataChunks++; + } + + private SOCKS5BytestreamServerSession createSession() { + SOCKS5BytestreamServerSession session = new SOCKS5BytestreamServerSession(connection, bytestreams); + session.onFinished.connect(new Slot1() { + + @Override + public void call(FileTransferError error) { + handleFinished(error); + } + + }); + return session; + } + + private void handleFinished(FileTransferError error) { + finished = true; + this.error = error; + } + +} -- cgit v0.10.2-6-g49f6