diff --git a/components/net/sf/briar/transport/ConnectionWriterImpl.java b/components/net/sf/briar/transport/ConnectionWriterImpl.java index 495d60a0937a1016e4f17ddcb416e8b4fbf82140..c4b52cfe4d1f93d48458c2d48b5fdb815a262098 100644 --- a/components/net/sf/briar/transport/ConnectionWriterImpl.java +++ b/components/net/sf/briar/transport/ConnectionWriterImpl.java @@ -2,7 +2,6 @@ package net.sf.briar.transport; import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH; import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH; -import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; import java.io.IOException; import java.io.OutputStream; @@ -22,7 +21,6 @@ class ConnectionWriterImpl extends OutputStream implements ConnectionWriter { private final int frameLength; private int length = 0; - private long frameNumber = 0L; ConnectionWriterImpl(FrameWriter out, int frameLength) { this.out = out; @@ -80,9 +78,7 @@ class ConnectionWriterImpl extends OutputStream implements ConnectionWriter { } private void writeFrame(boolean finalFrame) throws IOException { - if(frameNumber > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); out.writeFrame(frame, length, finalFrame); length = 0; - frameNumber++; } } diff --git a/components/net/sf/briar/transport/OutgoingEncryptionLayer.java b/components/net/sf/briar/transport/OutgoingEncryptionLayer.java index 5e8ec05db996ca1ed0e737bcb4abdb9907664ebf..d691bd39bccccf836674835964af43c97162d2bd 100644 --- a/components/net/sf/briar/transport/OutgoingEncryptionLayer.java +++ b/components/net/sf/briar/transport/OutgoingEncryptionLayer.java @@ -69,6 +69,7 @@ class OutgoingEncryptionLayer implements FrameWriter { public void writeFrame(byte[] frame, int payloadLength, boolean finalFrame) throws IOException { + if(frameNumber > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); // If the initiator's side of the connection is closed without writing // any data, don't write anything to the underlying transport if(writeTag && finalFrame && payloadLength == 0) return; diff --git a/test/net/sf/briar/transport/ConnectionReaderImplTest.java b/test/net/sf/briar/transport/ConnectionReaderImplTest.java index f00b611ba90d7263ce420b6a55c81c963a68aaca..4bec12fc63964be0720a2a6dfcab4fed5439c2ca 100644 --- a/test/net/sf/briar/transport/ConnectionReaderImplTest.java +++ b/test/net/sf/briar/transport/ConnectionReaderImplTest.java @@ -33,6 +33,7 @@ public class ConnectionReaderImplTest extends BriarTestCase { assertEquals(0, c.read()); // Read another byte assertEquals(-1, c.read()); // Skip the second empty frame, reach EOF assertEquals(-1, c.read()); // Still at EOF + context.assertIsSatisfied(); } @Test @@ -57,6 +58,7 @@ public class ConnectionReaderImplTest extends BriarTestCase { assertEquals(-1, c.read(buf)); // Still at EOF assertEquals(-1, c.read(buf)); + context.assertIsSatisfied(); } @Test @@ -77,6 +79,7 @@ public class ConnectionReaderImplTest extends BriarTestCase { assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf)); // Reach EOF assertEquals(-1, c.read(buf, 0, buf.length)); + context.assertIsSatisfied(); } @Test @@ -99,5 +102,6 @@ public class ConnectionReaderImplTest extends BriarTestCase { MAX_PAYLOAD_LENGTH / 2)); // Reach EOF assertEquals(-1, c.read(buf, 0, buf.length)); + context.assertIsSatisfied(); } } diff --git a/test/net/sf/briar/transport/ConnectionWriterImplTest.java b/test/net/sf/briar/transport/ConnectionWriterImplTest.java index 21ac3c7e1b048e69ef256f676aef017484ee012a..c0c7437816fb9d5aa24300777b1bb36189107727 100644 --- a/test/net/sf/briar/transport/ConnectionWriterImplTest.java +++ b/test/net/sf/briar/transport/ConnectionWriterImplTest.java @@ -1,13 +1,123 @@ package net.sf.briar.transport; -import org.junit.Test; - +import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH; +import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH; import net.sf.briar.BriarTestCase; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.junit.Test; + public class ConnectionWriterImplTest extends BriarTestCase { - // FIXME: This is an arbitrary change to test the synchronization view + private static final int FRAME_LENGTH = 1024; + private static final int MAX_PAYLOAD_LENGTH = + FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH; + + @Test + public void testCloseWithoutWritingWritesFinalFrame() throws Exception { + Mockery context = new Mockery(); + final FrameWriter writer = context.mock(FrameWriter.class); + context.checking(new Expectations() {{ + // Write an empty final frame + oneOf(writer).writeFrame(with(any(byte[].class)), with(0), + with(true)); + // Flush the stream + oneOf(writer).flush(); + }}); + ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); + c.close(); + context.assertIsSatisfied(); + } + + @Test + public void testFlushWithoutBufferedDataWritesFrame() throws Exception { + Mockery context = new Mockery(); + final FrameWriter writer = context.mock(FrameWriter.class); + ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); + context.checking(new Expectations() {{ + // Flush the stream + oneOf(writer).flush(); + }}); + c.flush(); + context.assertIsSatisfied(); + } + + @Test + public void testFlushWithBufferedDataWritesFrameAndFlushes() + throws Exception { + Mockery context = new Mockery(); + final FrameWriter writer = context.mock(FrameWriter.class); + ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); + context.checking(new Expectations() {{ + // Write a non-final frame with one payload byte + oneOf(writer).writeFrame(with(any(byte[].class)), with(1), + with(false)); + // Flush the stream + oneOf(writer).flush(); + }}); + c.write(0); + c.flush(); + context.assertIsSatisfied(); + } + + @Test + public void testSingleByteWritesWriteFullFrame() throws Exception { + Mockery context = new Mockery(); + final FrameWriter writer = context.mock(FrameWriter.class); + ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); + context.checking(new Expectations() {{ + // Write a full non-final frame + oneOf(writer).writeFrame(with(any(byte[].class)), + with(MAX_PAYLOAD_LENGTH), with(false)); + }}); + for(int i = 0; i < MAX_PAYLOAD_LENGTH; i++) { + c.write(0); + } + context.assertIsSatisfied(); + } + + @Test + public void testMultiByteWritesWriteFullFrames() throws Exception { + Mockery context = new Mockery(); + final FrameWriter writer = context.mock(FrameWriter.class); + ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); + context.checking(new Expectations() {{ + // Write two full non-final frames + exactly(2).of(writer).writeFrame(with(any(byte[].class)), + with(MAX_PAYLOAD_LENGTH), with(false)); + }}); + // Sanity check + assertEquals(0, MAX_PAYLOAD_LENGTH % 2); + // Write two full payloads using four multi-byte writes + byte[] b = new byte[MAX_PAYLOAD_LENGTH / 2]; + c.write(b); + c.write(b); + c.write(b); + c.write(b); + context.assertIsSatisfied(); + } @Test - public void testNothing() {} + public void testLargeMultiByteWriteWritesFullFrames() throws Exception { + Mockery context = new Mockery(); + final FrameWriter writer = context.mock(FrameWriter.class); + ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH); + context.checking(new Expectations() {{ + // Write two full non-final frames + exactly(2).of(writer).writeFrame(with(any(byte[].class)), + with(MAX_PAYLOAD_LENGTH), with(false)); + // Write a final frame with a one-byte payload + oneOf(writer).writeFrame(with(any(byte[].class)), with(1), + with(true)); + // Flush the stream + oneOf(writer).flush(); + }}); + // Write two full payloads using one large multi-byte write + byte[] b = new byte[MAX_PAYLOAD_LENGTH * 2 + 1]; + c.write(b); + // There should be one byte left in the buffer + c.close(); + context.assertIsSatisfied(); + } }