/*
* JBoss, Home of Professional Open Source.
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates, and individual
* contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xnio.ssl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.xnio.ssl.mock.SSLEngineMock.CLOSE_MSG;
import static org.xnio.ssl.mock.SSLEngineMock.HANDSHAKE_MSG;
import static org.xnio.ssl.mock.SSLEngineMock.HandshakeAction.FINISH;
import static org.xnio.ssl.mock.SSLEngineMock.HandshakeAction.NEED_TASK;
import static org.xnio.ssl.mock.SSLEngineMock.HandshakeAction.NEED_UNWRAP;
import static org.xnio.ssl.mock.SSLEngineMock.HandshakeAction.NEED_WRAP;
import static org.xnio.ssl.mock.SSLEngineMock.HandshakeAction.PERFORM_REQUESTED_ACTION;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import org.jmock.integration.junit4.JUnitRuleMockery;
import org.jmock.lib.concurrent.Synchroniser;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.xnio.Buffers;
import org.xnio.ssl.mock.SSLEngineMock;
/**
* Test for concurrent read and write operations on {@link #AssembledConnectedSslStreamChannel}.
*
* @author <a href="mailto:frainone@redhat.com">Flavia Rainone</a>
*/
@Ignore // ignoring for now as these tests hang more consistently than they pass
public class ConnectedSslStreamChannelReadWriteTestCase extends AbstractConnectedSslStreamChannelTest{
@Rule
public final JUnitRuleMockery context = new JUnitRuleMockery() {{
setThreadingPolicy(new Synchroniser());
}};
private Object readWriteMonitor = new Object();
private boolean syncFlush() throws IOException {
synchronized (readWriteMonitor) {
return sslChannel.flush();
}
}
private void syncShutdownWrites() throws IOException {
synchronized (readWriteMonitor) {
sslChannel.shutdownWrites();
}
}
@Test
public void simpleReadAndWrite() throws Exception {
// no handshake actions for engineMock this time, meaning it will just wrap and unwrap without any handshake
// the message we want to write
conduitMock.setReadData("read data");
conduitMock.enableReads(true);
final Future<ByteBuffer> readFuture = triggerReadThread(9);
final Future<Void> writeFuture = triggerWriteThread("write data");
// wait for write thread to finish
writeFuture.get();
conduitMock.setReadData(CLOSE_MSG);
// wait for read thread to finish
final ByteBuffer readBuffer = readFuture.get();
sslChannel.shutdownWrites();
assertTrue(sslChannel.flush());
sslChannel.shutdownReads();
// close channel
sslChannel.close();
// data expected to have been copied to buffer by channel
assertReadMessage(readBuffer, "read data");
assertWrittenMessage("write data", CLOSE_MSG);
}
@Test
public void readAndWriteMappedWrap() throws Exception {
// map the wrap
engineMock.addWrapEntry("a very long message", "BLABLABLABLABLABLABLA");
engineMock.addWrapEntry("short msg", "MSG");
// no handshake actions for engineMock this time, meaning that it will just wrap and unwrap without any handshake
// the message we want to read
conduitMock.setReadData("BLABLABLABLABLABLABLA");
conduitMock.enableReads(true);
// attempt to read and write
final Future<Void> writeFuture = triggerWriteThread("short msg", "short msg");
final Future<ByteBuffer> readFuture = triggerReadThread(19);
// wait for write thread to finish
writeFuture.get();
conduitMock.setReadData(CLOSE_MSG);
// wait for read thread to finish
final ByteBuffer readBuffer = readFuture.get();
// channel should be able to shutdown reads and writes
sslChannel.shutdownReads();
sslChannel.shutdownWrites();
assertTrue(sslChannel.flush());
// close channel
sslChannel.close();
// data expected to have been read to 'buffer' by 'channel'
assertReadMessage(readBuffer, "a very long message");
assertWrittenMessage("MSG", "MSG", CLOSE_MSG);
}
@Test
public void readAndWriteWithSimpleHandshake() throws Exception {
// map all data to be read and written
engineMock.addWrapEntry(HANDSHAKE_MSG, "handshake");
engineMock.addWrapEntry("MockTest", "mock test works!");
engineMock.addWrapEntry(CLOSE_MSG, "channel closed");
// set the handshake actions that engineMock will emulate
engineMock.setHandshakeActions(NEED_WRAP, NEED_UNWRAP, NEED_TASK, FINISH);
// set ReadData on connectedChannelMock, including the wrapped version of message we want to read
conduitMock.setReadData("handshake", "mock test works!", "mock test works!");
conduitMock.enableReads(true);
// attempt to read and write
final Future<Void> writeFuture = triggerWriteThread("MockTest");
final Future<ByteBuffer> readFuture = triggerReadThread(16);
// wait for write thread to finish
writeFuture.get();
conduitMock.setReadData("channel closed");
// wait for read thread to finish
final ByteBuffer readBuffer = readFuture.get();
// channel should be able to shutdown reads and writes
sslChannel.shutdownReads();
sslChannel.shutdownWrites();
assertTrue(sslChannel.flush());
// close channel
sslChannel.close();
assertReadMessage(readBuffer, "MockTest", "MockTest");
// data expected to have been written to 'connectedChannelMock' by 'channel'
assertWrittenMessage("handshake", "mock test works!", "channel closed");
}
@Test
public void readAndWriteWithTasklessHandshake() throws Exception {
// map data to be read and written
engineMock.addWrapEntry("Mock Test", "{testReadWriteWithTasklessHandshake}");
engineMock.addWrapEntry(CLOSE_MSG, " _ ");
// set the handshake actions that engineMock will emulate
engineMock.setHandshakeActions(NEED_WRAP, NEED_UNWRAP, FINISH);
// set ReadData on connectedChannelMock
conduitMock.setReadData(SSLEngineMock.HANDSHAKE_MSG, "{testReadWriteWithTasklessHandshake}");
conduitMock.enableReads(true);
// attempt to read and write
final Future<Void> writeFuture = triggerWriteThread("Mock Test", "Mock Test", "Mock Test", "Mock Test");
final Future<ByteBuffer> readFuture = triggerReadThread(9);
// wait for write thread to finish
writeFuture.get();
conduitMock.setReadData(" _ ");
// wait for read thread to finish
final ByteBuffer readBuffer = readFuture.get();
// channel should be able to shutdown reads and writes
sslChannel.shutdownReads();
sslChannel.shutdownWrites();
assertTrue(sslChannel.flush());
// close channel
sslChannel.close();
// data expected to have been read from 'connectedChannelMock' by 'channel'
assertReadMessage(readBuffer, "Mock Test");
// data expected to have been written to 'connectedChannelMock' by 'channel'
assertWrittenMessage(HANDSHAKE_MSG, "{testReadWriteWithTasklessHandshake}",
"{testReadWriteWithTasklessHandshake}", "{testReadWriteWithTasklessHandshake}",
"{testReadWriteWithTasklessHandshake}", " _ ");
}
@Test
public void multipleFeedReadAndWriteWithSimpleHandshake() throws Exception {
// map data to be read and written
engineMock.addWrapEntry(HANDSHAKE_MSG, "{handshake data}");
engineMock.addWrapEntry("Mock Read/Write Test", "{data}");
engineMock.addWrapEntry("it works!", "{more data}");
engineMock.addWrapEntry(CLOSE_MSG, "{message closed}");
// set the handshake actions that engineMock will emulate
engineMock.setHandshakeActions(NEED_UNWRAP, NEED_WRAP, NEED_TASK, FINISH);
// set ReadData on connectedChannelMock
conduitMock.setReadData();
// enable read on connectedChannelMock, meaning that data above will be available to be read right away
conduitMock.enableReads(true);
// attempt to read and write
final Future<Void> writeFuture = triggerWriteThread("it works!", "Mock Read/Write Test", "it works!",
"Mock Read/Write Test", "it works!", "it works!", "Mock Read/Write Test");
final Future<ByteBuffer> readFuture = triggerReadThread(67);
Thread.sleep(20);
conduitMock.setReadData("{handshake data}");
Thread.sleep(10);
conduitMock.setReadData( "{data}", "{data}", "{more data}");
Thread.sleep(10);
conduitMock.setReadData("{more data}", "{more data}");
Thread.sleep(10);
// wait for write thread to finish
writeFuture.get();
conduitMock.setReadData("{message closed}");
// wait for read thread to finish
final ByteBuffer readBuffer = readFuture.get();
assertNotNull(readBuffer);
// channel should be able to shutdown reads and writes
sslChannel.shutdownReads();
sslChannel.shutdownWrites();
assertTrue(sslChannel.flush());
// close channel
sslChannel.close();
// data expected to have been read from 'connectedChannelMock' by 'channel' so far
assertReadMessage(readBuffer, "Mock Read/Write Test", "Mock Read/Write Test", "it works!", "it works!", "it works!");
// data expected to have been written to 'connectedChannelMock' by 'channel'
assertWrittenMessage("{handshake data}", "{more data}", "{data}", "{more data}", "{data}", "{more data}",
"{more data}", "{data}", "{message closed}");
}
@Test
public void readAndWriteWithConstantHandshake() throws Exception {
// set the handshake actions that engineMock will emulate
engineMock.setHandshakeActions(NEED_WRAP, NEED_UNWRAP, NEED_TASK, FINISH, PERFORM_REQUESTED_ACTION,
NEED_UNWRAP, NEED_WRAP, NEED_TASK, FINISH, PERFORM_REQUESTED_ACTION,
NEED_WRAP, NEED_WRAP, NEED_WRAP, NEED_UNWRAP, NEED_TASK, NEED_TASK, NEED_TASK, NEED_TASK, FINISH,
PERFORM_REQUESTED_ACTION, NEED_WRAP, NEED_UNWRAP, NEED_TASK, FINISH, PERFORM_REQUESTED_ACTION);
// set ReadData on connectedChannelMock
conduitMock.setReadData(HANDSHAKE_MSG, "read a lot");
// enable read on connectedChannelMock, meaning that data above will be available to be read right away
conduitMock.enableReads(true);
// attempt to read and write
final Future<Void> writeFuture = triggerMultipleWriteThread("write a lot", "write a lot", "write a lot",
"write it", "a lot", "write it down", "a lot");
final Future<ByteBuffer> readFuture = triggerReadThread(54);
Thread.sleep(20);
conduitMock.setReadData(HANDSHAKE_MSG, HANDSHAKE_MSG);
Thread.sleep(10);
conduitMock.setReadData( "this is a lot", "lot", "lot", "lot", "lot", "lot", "lot", "lot");
Thread.sleep(10);
conduitMock.setReadData("read a lot", HANDSHAKE_MSG);
Thread.sleep(10);
// wait for write thread to finish
writeFuture.get();
syncShutdownWrites();
assertFalse(syncFlush());
conduitMock.setReadData(CLOSE_MSG);
// wait for read thread to finish
final ByteBuffer readBuffer = readFuture.get();
assertNotNull(readBuffer);
// shutdown reads
sslChannel.shutdownReads();
sslChannel.shutdownWrites();
synchronized (readWriteMonitor) {
assertTrue(sslChannel.flush());
}
// close channel
sslChannel.close();
// data expected to have been read from 'connectedChannelMock' by 'channel' so far
assertReadMessage(readBuffer, "read a lot", "this is a lot", "lot", "lot", "lot" , "lot", "lot", "lot", "lot",
"read a lot");
// data expected to have been written to 'connectedChannelMock' by 'channel'
assertWrittenMessage(new String[]{HANDSHAKE_MSG, HANDSHAKE_MSG, HANDSHAKE_MSG, HANDSHAKE_MSG, HANDSHAKE_MSG,
HANDSHAKE_MSG, CLOSE_MSG}, new String[] {"write a lot", "write a lot", "write a lot", "write it",
"a lot", "write it down", "a lot"});
}
@Test
public void readAndWriteWithConstantHandshakeAndMappedData() throws Exception {
// map data to be read and written
engineMock.addWrapEntry(HANDSHAKE_MSG, "HANDSHAKE_MSG");
engineMock.addWrapEntry("MockTest1", "MOCK 1");
engineMock.addWrapEntry("MockTest2", "MOCK 2");
engineMock.addWrapEntry("MockTest3", "MOCK 3");
engineMock.addWrapEntry("MockTest4", "MOCK 4");
engineMock.addWrapEntry(CLOSE_MSG, "CLOSE_MSG");
// set the handshake actions that engineMock will emulate
engineMock.setHandshakeActions(NEED_WRAP, NEED_UNWRAP, NEED_TASK, FINISH, PERFORM_REQUESTED_ACTION,
NEED_UNWRAP, NEED_WRAP, NEED_TASK, FINISH, PERFORM_REQUESTED_ACTION,
NEED_WRAP, NEED_WRAP, NEED_WRAP, NEED_UNWRAP, NEED_TASK, NEED_TASK, NEED_TASK, NEED_TASK, FINISH,
PERFORM_REQUESTED_ACTION, NEED_WRAP, NEED_UNWRAP, NEED_TASK, FINISH, PERFORM_REQUESTED_ACTION,
NEED_UNWRAP, NEED_WRAP, NEED_TASK, FINISH, PERFORM_REQUESTED_ACTION, NEED_UNWRAP, NEED_WRAP,
NEED_TASK, FINISH, PERFORM_REQUESTED_ACTION, NEED_WRAP, NEED_UNWRAP, NEED_TASK, FINISH,
PERFORM_REQUESTED_ACTION);
// set ReadData on connectedChannelMock
conduitMock.setReadData("HANDSHAKE_MSG", "MOCK 3");
// enable read on connectedChannelMock, meaning that data above will be available to be read right away
conduitMock.enableReads(true);
// attempt to read and write
final Future<Void> writeFuture = triggerMultipleWriteThread("MockTest1", "MockTest2", "MockTest2", "MockTest1", "MockTest2", "MockTest3", "MockTest4");
final Future<ByteBuffer> readFuture = triggerMultipleReadThread(99);
Thread.sleep(40);
conduitMock.setReadData("HANDSHAKE_MSG", "HANDSHAKE_MSG", "MOCK 3", "HANDSHAKE_MSG");
Thread.sleep(10);
conduitMock.setReadData( "MOCK 2", "MOCK 2", "HANDSHAKE_MSG", "MOCK 4");
Thread.sleep(10);
conduitMock.setReadData("MOCK 4", "MOCK 1", "MOCK 3", "MOCK 2", "MOCK 4");
Thread.sleep(10);
conduitMock.setReadData("MOCK 1", "HANDSHAKE_MSG", "HANDSHAKE_MSG");
Thread.sleep(10);
// wait for write thread to finish
writeFuture.get();
syncShutdownWrites();
assertFalse(syncFlush());
conduitMock.setReadData("CLOSE_MSG");
// wait for read thread to finish
final ByteBuffer readBuffer = readFuture.get();
assertNotNull(readBuffer);
// shutdown reads
sslChannel.shutdownReads();
sslChannel.shutdownWrites();
assertTrue(sslChannel.flush());
// close channel
sslChannel.close();
// make sure that channel managed to do the WRAP and there is no more handshake actions left
assertSame(HandshakeStatus.NOT_HANDSHAKING, engineMock.getHandshakeStatus());
// data expected to have been read from 'connectedChannelMock' by 'channel' so far
assertReadMessage(readBuffer, "MockTest3", "MockTest3", "MockTest2", "MockTest2", "MockTest4", "MockTest4",
"MockTest1", "MockTest3", "MockTest2", "MockTest4", "MockTest1");
// data expected to have been written to 'connectedChannelMock' by 'channel'
assertWrittenMessage(new String[]{"HANDSHAKE_MSG", "HANDSHAKE_MSG", "HANDSHAKE_MSG", "HANDSHAKE_MSG",
"HANDSHAKE_MSG", "HANDSHAKE_MSG", "HANDSHAKE_MSG", "HANDSHAKE_MSG", "HANDSHAKE_MSG", "CLOSE_MSG"},
new String[] {"MOCK 1", "MOCK 2", "MOCK 2", "MOCK 1", "MOCK 2", "MOCK 3", "MOCK 4"});
}
@Test
public void readAndWriteWithIntercalatedHandshake() throws Exception {
// set the handshake actions that engineMock will emulate
engineMock.setHandshakeActions(NEED_WRAP, NEED_UNWRAP, NEED_TASK, FINISH, PERFORM_REQUESTED_ACTION,
NEED_WRAP, NEED_UNWRAP, PERFORM_REQUESTED_ACTION, NEED_UNWRAP, PERFORM_REQUESTED_ACTION, FINISH);
// set ReadData on connectedChannelMock
conduitMock.setReadData(HANDSHAKE_MSG, "read this", "read this", "read this", "read this",
HANDSHAKE_MSG, "read this", HANDSHAKE_MSG, "read this");
// attempt to read and write
final Future<Void> writeFuture = triggerMultipleWriteThread("write this", "write this", "write this", "write this",
"write this");
final Future<ByteBuffer> readFuture = triggerReadThread(54);
// enable read on connectedChannelMock, meaning that data above will be available to be read right away
conduitMock.enableReads(true);
// wait for write thread to finish
writeFuture.get();
syncShutdownWrites();
assertFalse(syncFlush());
conduitMock.setReadData(CLOSE_MSG);
// wait for read thread to finish
final ByteBuffer readBuffer = readFuture.get();
assertNotNull(readBuffer);
// shutdown reads
sslChannel.shutdownReads();
sslChannel.shutdownWrites();
assertTrue(sslChannel.flush());
// close channel
sslChannel.close();
// make sure that channel managed to do the WRAP and there is no more handshake actions left
assertSame(HandshakeStatus.NOT_HANDSHAKING, engineMock.getHandshakeStatus());
// data expected to have been read from 'connectedChannelMock' by 'channel' so far
assertReadMessage(readBuffer, "read this", "read this", "read this", "read this", "read this", "read this");
// data expected to have been written to 'connectedChannelMock' by 'channel'
assertWrittenMessage(new String[]{HANDSHAKE_MSG, HANDSHAKE_MSG, CLOSE_MSG},
new String[] {"write this", "write this", "write this", "write this", "write this"});
}
@Test
public void readAndWriteWithIntercalatedHandshakeAndMappedData() throws Exception {
// map all data to be read and written
engineMock.addWrapEntry(HANDSHAKE_MSG, "[!@#$%^&*()_]");
engineMock.addWrapEntry("this", "read this");
engineMock.addWrapEntry("write this", "this");
engineMock.addWrapEntry(CLOSE_MSG, "[_)(*&^%$#@!]");
// set the handshake actions that engineMock will emulate
engineMock.setHandshakeActions(NEED_WRAP, NEED_UNWRAP, NEED_TASK, FINISH, PERFORM_REQUESTED_ACTION,
NEED_WRAP, NEED_UNWRAP, PERFORM_REQUESTED_ACTION, NEED_UNWRAP, PERFORM_REQUESTED_ACTION, FINISH);
// set ReadData on connectedChannelMock
conduitMock.setReadData("[!@#$%^&*()_]", "read this", "read this", "read this", "read this",
"[!@#$%^&*()_]", "read this", "[!@#$%^&*()_]", "read this");
// enable read on connectedChannelMock, meaning that data above will be available to be read right away
conduitMock.enableReads(true);
// attempt to read and write
final Future<Void> writeFuture = triggerMultipleWriteThread("write this", "write this", "write this", "write this",
"write this");
final Future<ByteBuffer> readFuture = triggerMultipleReadThread(24);
// wait for write thread to finish
writeFuture.get();
syncShutdownWrites();
assertFalse(syncFlush());
conduitMock.setReadData("[_)(*&^%$#@!]");
// wait for read thread to finish
final ByteBuffer readBuffer = readFuture.get();
assertNotNull(readBuffer);
// shutdown reads
sslChannel.shutdownReads();
sslChannel.shutdownWrites();
assertTrue(sslChannel.flush());
// close channel
sslChannel.close();
// make sure that channel managed to do the WRAP and there is no more handshake actions left
assertSame(HandshakeStatus.NOT_HANDSHAKING, engineMock.getHandshakeStatus());
// data expected to have been read from 'connectedChannelMock' by 'channel' so far
assertReadMessage(readBuffer, "this", "this", "this", "this", "this", "this");
// data expected to have been written to 'connectedChannelMock' by 'channel'
assertWrittenMessage(new String[]{"[!@#$%^&*()_]", "[!@#$%^&*()_]", "[_)(*&^%$#@!]"},
new String[] {"this", "this", "this", "this", "this"});
}
private Future<ByteBuffer> triggerReadThread(int expectedReadLength) {
ReadRunnable readRunnable = new ReadRunnable(expectedReadLength);
Thread newThread = new Thread(readRunnable);
newThread.start();
return readRunnable.getResultFuture();
}
private Future<Void> triggerWriteThread(String... text) {
WriteRunnable writeRunnable = new WriteRunnable(text);
Thread newThread = new Thread(writeRunnable);
newThread.start();
return writeRunnable.getResultFuture();
}
private Future<ByteBuffer> triggerMultipleReadThread(int expectedReadLength) {
MultipleReadRunnable readRunnable = new MultipleReadRunnable(expectedReadLength);
Thread newThread = new Thread(readRunnable);
newThread.start();
return readRunnable.getResultFuture();
}
private Future<Void> triggerMultipleWriteThread(String... text) {
MultipleWriteRunnable writeRunnable = new MultipleWriteRunnable(text);
Thread newThread = new Thread(writeRunnable);
newThread.start();
return writeRunnable.getResultFuture();
}
private class ReadRunnable implements Runnable {
private ResultFuture<ByteBuffer> resultFuture;
private int expectedReadLength;
public ReadRunnable(int expectedReadLength) {
this.expectedReadLength = expectedReadLength;
this.resultFuture = new ResultFuture<ByteBuffer>();
}
public Future<ByteBuffer> getResultFuture() {
return resultFuture;
}
@Override
public void run() {
final ByteBuffer buffer = ByteBuffer.allocate(100);
final ByteBuffer[] buffers = new ByteBuffer[10];
for (int i = 0; i < 10; i++) {
buffers[i] = ByteBuffer.allocate(10);
}
// attempt to read... channel is expected to read the entire message without any issues
try {
int totalLength = 0;
long length = 0;
do {
totalLength += length;
// keep the synchronized block inside the while, even though it looks unelegant, this way we will
// achieve a finer granularity
synchronized (readWriteMonitor) {
length = sslChannel.read(buffers);
}
} while (length >= 0);
synchronized (readWriteMonitor) {
assertEquals(-1, sslChannel.read(buffers, 0, 10));
assertEquals(-1, sslChannel.read(buffers, 0, 10));
assertEquals(-1, sslChannel.read(buffers, 0, 10));
}
for (ByteBuffer b: buffers) {
b.flip();
}
Buffers.copy(buffer, buffers, 0, 10);
buffer.flip();
assertEquals("This is what we read '" + Buffers.getModifiedUtf8(buffer) + "'", expectedReadLength, totalLength);
} catch (IOException e) {
throw new RuntimeException("Unexpected IOException while reading", e);
}
resultFuture.setResult(buffer);
}
}
private class WriteRunnable implements Runnable {
private ResultFuture<Void> resultFuture;
private String[] text;
public WriteRunnable(String... text) {
this.text = text;
this.resultFuture = new ResultFuture<Void>();
}
public Future<Void> getResultFuture() {
return resultFuture;
}
@Override
public void run() {
final ByteBuffer[] buffer = new ByteBuffer[text.length];
int totalBytes = 0;
try {
for (int i = 0; i < text.length; i++) {
// attempt to write... channel is expected to write the entire message without any issues
buffer[i] = ByteBuffer.allocate(50);
buffer[i].put(text[i].getBytes("UTF-8")).flip();
totalBytes += text[i].length();
}
final int attemptsLimit = 10000;
int attempts = 0;
long bytes = 0;
do {
synchronized (readWriteMonitor) {
bytes += sslChannel.write(buffer);
}
}
while (bytes < totalBytes && (++ attempts) < attemptsLimit);
assertEquals(totalBytes, bytes);
} catch (IOException e) {
throw new RuntimeException("Unexpected exception while writing", e);
}
resultFuture.setResult(null);
}
}
private class MultipleReadRunnable implements Runnable {
private ResultFuture<ByteBuffer> resultFuture;
private int expectedReadLength;
public MultipleReadRunnable(int expectedReadLength) {
this.expectedReadLength = expectedReadLength;
this.resultFuture = new ResultFuture<ByteBuffer>();
}
public Future<ByteBuffer> getResultFuture() {
return resultFuture;
}
@Override
public void run() {
final ByteBuffer buffer = ByteBuffer.allocate(100);
// attempt to read... channel is expected to read the entire message without any issues
try {
int totalLength = 0;
int length = 0;
do {
totalLength += length;
synchronized (readWriteMonitor) {
length = sslChannel.read(buffer);
}
} while(length >= 0);
synchronized (readWriteMonitor) {
assertEquals(-1, sslChannel.read(buffer));
assertEquals(-1, sslChannel.read(buffer));
assertEquals(-1, sslChannel.read(buffer));
}
buffer.flip();
assertEquals("This is what we read '" + Buffers.getModifiedUtf8(buffer) + "'", expectedReadLength, totalLength);
} catch (IOException e) {
throw new RuntimeException("Unexpected IOException while reading", e);
}
resultFuture.setResult(buffer);
}
}
private class MultipleWriteRunnable implements Runnable {
private ResultFuture<Void> resultFuture;
private String[] text;
public MultipleWriteRunnable(String... text) {
this.text = text;
this.resultFuture = new ResultFuture<Void>();
}
public Future<Void> getResultFuture() {
return resultFuture;
}
@Override
public void run() {
final ByteBuffer buffer = ByteBuffer.allocate(100);
try {
// attempt to write... channel is expected to write the entire message without any issues
for (String textStr: text) {
buffer.put(textStr.getBytes("UTF-8")).flip();
int bytes = 0;
while (bytes == 0) {
synchronized (readWriteMonitor) {
bytes = sslChannel.write(buffer);
}
}
assertEquals(textStr.length(), bytes);
buffer.compact();
}
} catch (IOException e) {
throw new RuntimeException("Unexpected exception while writing", e);
}
resultFuture.setResult(null);
}
}
private static class ResultFuture<T> implements Future<T> {
private T result;
private final CountDownLatch countDownLatch = new CountDownLatch(1);
private final long delay;
private final TimeUnit delayTimeUnit;
ResultFuture() {
delay = 500000L;
delayTimeUnit = TimeUnit.MILLISECONDS;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return result != null;
}
@Override
public T get() throws InterruptedException, ExecutionException {
try {
return get(delay, delayTimeUnit);
} catch (TimeoutException e) {
throw new RuntimeException("Could not get start exception in " + delay + " " + delayTimeUnit + " timeout.");
}
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
countDownLatch.await(timeout, unit);
return result;
}
private void setResult(final T result) {
this.result = result;
countDownLatch.countDown();
}
}
}