/* * 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_FAULTY_TASK; 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.EOFException; import java.io.IOException; import java.nio.ByteBuffer; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import org.jmock.integration.junit4.JUnitRuleMockery; import org.junit.Rule; import org.junit.Test; import org.xnio.ssl.mock.SSLEngineMock; /** * Test write operations on {@link JsseSslStreamSinkConduit}. * * @author <a href="mailto:frainone@redhat.com">Flavia Rainone</a> */ public class JsseSslStreamSinkConduitTestCase extends AbstractSslConnectionTest { @Rule public final JUnitRuleMockery context = new JUnitRuleMockery(); @Test public void writeWithoutHandshake() throws IOException { // no handshake actions for engineMock this time, meaning that it will just wrap and unwrap without any handshake // the message we want to write final ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("MockTest".getBytes("UTF-8")).flip(); // attempt to write... conduit is expected to write the entire message without any issues assertEquals(8, sinkConduit.write(buffer)); assertFalse(buffer.hasRemaining()); // conduit should not be able to shutdown writes... for that, it must receive a close message sinkConduit.terminateWrites(); assertFalse(sinkConduit.flush()); // send the close message conduitMock.setReadData(CLOSE_MSG); conduitMock.enableReads(true); sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); // close connection sourceConduit.terminateReads(); // data expected to have been written to 'conduitMock' by 'sinkConduit' assertWrittenMessage("MockTest", CLOSE_MSG); } @Test public void writeWithoutHandshakeMappedWrap() throws IOException { // map the wrap engineMock.addWrapEntry("MockTest", "WRAPPED_MOCK_TEST"); // no handshake actions for engineMock this time, meaning that it will just wrap and unwrap without any handshake // the message we want to write final ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("MockTest".getBytes("UTF-8")).flip(); // attempt to write... conduit is expected to write the entire message without any issues assertEquals(8, sinkConduit.write(buffer)); assertFalse(buffer.hasRemaining()); // conduit should not be able to shutdown writes... for that, it must receive a close message sinkConduit.terminateWrites(); assertFalse(sinkConduit.flush()); // send the close message conduitMock.setReadData(CLOSE_MSG); conduitMock.enableReads(true); sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); // close connection sourceConduit.terminateReads(); // data expected to have bee written to 'conduitMock' by 'sinkConduit' assertWrittenMessage("WRAPPED_MOCK_TEST", CLOSE_MSG); } @Test public void writeWithSimpleHandshake() throws IOException { // 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 conduitMock conduitMock.setReadData("handshake", "channel closed"); conduitMock.enableFlush(false); // message we want to write final ByteBuffer buffer = ByteBuffer.allocate(100); final ByteBuffer[] buffers = new ByteBuffer[]{buffer}; buffer.put("MockTest".getBytes("UTF-8")).flip(); // attempt to write... conduit is expected to stop on NEED_WRAP, as flush is disabled on conduitMock assertEquals(0, sinkConduit.write(buffers, 0, 1)); assertFalse(conduitMock.isFlushed()); conduitMock.enableFlush(true); assertSame(HandshakeStatus.NEED_UNWRAP, engineMock.getHandshakeStatus()); // attempt to write... conduit is expected to stop on NEED_UNWRAP, as read on conduitMock is not available assertEquals(0, sinkConduit.write(buffers, 0, 1)); assertSame(HandshakeStatus.NEED_UNWRAP, engineMock.getHandshakeStatus()); assertTrue(conduitMock.isFlushed()); // enable read, now read data will be available to the source conduit conduitMock.enableReads(true); // conduit is expected to write all data from buffer assertEquals(8, sinkConduit.write(new ByteBuffer[]{buffer, ByteBuffer.allocate(0)}, 0, 2)); assertFalse(buffer.hasRemaining()); // for coverage purposes, attempt to write empty buffers assertEquals(0, sinkConduit.write(new ByteBuffer[]{buffer, ByteBuffer.allocate(0)}, 0, 2)); // conduit should be able to terminate writes sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); // close connection sourceConduit.terminateReads(); // data expected to have been written to 'conduitMock' by 'sinkConduit' assertWrittenMessage("handshake", "mock test works!", "channel closed"); } @Test public void writeWithTasklessHandshake() throws IOException { // map data to be read and written engineMock.addWrapEntry("MockTest", "{testWriteWithTasklessHandshake}"); engineMock.addWrapEntry(CLOSE_MSG, " _ "); // set the handshake actions that engineMock will emulate engineMock.setHandshakeActions(NEED_WRAP, NEED_UNWRAP, FINISH); // set ReadData on conduitMock conduitMock.setReadData(SSLEngineMock.HANDSHAKE_MSG, " _ "); // message we want to write final ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("MockTest".getBytes("UTF-8")).flip(); // attempt to write... conduit is expected to stop on NEED_UNWRAP, as read on conduitMock is not available assertEquals(0, sinkConduit.write(buffer)); assertSame(HandshakeStatus.NEED_UNWRAP, engineMock.getHandshakeStatus()); // enable read, now read data will be available to the source conduit conduitMock.enableReads(true); // conduit is expected to write all data from buffer assertEquals(8, sinkConduit.write(buffer)); assertFalse(buffer.hasRemaining()); // conduit should be able to shutdown writes sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); // close connection sourceConduit.terminateReads(); // data expected to have been written to 'conduitMock' by 'sinkConduit' assertWrittenMessage(HANDSHAKE_MSG, "{testWriteWithTasklessHandshake}", " _ "); } @Test public void multipleWritesWithSimpleHandshake() throws IOException { // map data to be read and written engineMock.addWrapEntry(HANDSHAKE_MSG, "{handshake data}"); engineMock.addWrapEntry("MockTest", "{data}"); engineMock.addWrapEntry(CLOSE_MSG, "{message closed}"); // set the handshake actions that engineMock will emulate engineMock.setHandshakeActions(NEED_WRAP, NEED_UNWRAP, NEED_TASK, FINISH); // set ReadData on conduitMock conduitMock.setReadData("{handshake data}"); // enable read on conduitMock, meaning that data above will be available to be read right away conduitMock.enableReads(true); // message we want to write final ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("MockTest".getBytes("UTF-8")).flip(); // attempt to write... conduit is expected to write all messages without any issues for (int i = 0; i < 10; i++) { while(buffer.hasRemaining()) { assertEquals(8, sinkConduit.write(buffer)); assertFalse(buffer.hasRemaining()); } buffer.flip(); } // conduit should not be able to terminate writes... for that, it must receive a close message sinkConduit.terminateWrites(); assertFalse(sinkConduit.flush()); // send the close message conduitMock.setReadData("{message closed}"); conduitMock.enableReads(true); sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); // close connection sourceConduit.terminateReads(); // data expected to have been written to 'conduitMock' by 'sinkConduit' assertWrittenMessage("{handshake data}", "{data}", "{data}", "{data}", "{data}", "{data}", "{data}", "{data}", "{data}", "{data}", "{data}", "{message closed}"); } @Test public void writeWithConnectionWithoutReadData() throws IOException { // set the handshake actions that engineMock will emulate engineMock.setHandshakeActions(NEED_WRAP, NEED_UNWRAP, NEED_TASK, FINISH); // message we want to write final ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("MockTest".getBytes("UTF-8")).flip(); // attempt to write several times... conduit is expected to stop on NEED_UNWRAP, as read on conduitMock is disabled assertEquals(0, sinkConduit.write(buffer)); assertEquals(0, sinkConduit.write(buffer)); assertEquals(0, sinkConduit.write(buffer)); // make sure that conduit managed to do the WRAP and got stalled on NEED_UNWRAP handshake status assertSame(HandshakeStatus.NEED_UNWRAP, engineMock.getHandshakeStatus()); // conduit should not be able to shutdown writes... for that, it must receive a close message sinkConduit.terminateWrites(); assertFalse(sinkConduit.flush()); conduitMock.setReadData(HANDSHAKE_MSG); conduitMock.enableReads(true); // close connection sourceConduit.terminateReads(); // data expected to have been written to 'conduitMock' by 'sinkConduit' assertWrittenMessage(HANDSHAKE_MSG, CLOSE_MSG); } @Test public void writeWithConstantHandshake() throws IOException { // 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 conduitMock conduitMock.setReadData(HANDSHAKE_MSG, HANDSHAKE_MSG, HANDSHAKE_MSG, HANDSHAKE_MSG, CLOSE_MSG); // enable read on conduitMock, meaning that data above will be available to be read right away conduitMock.enableReads(true); // messages we plan to write final ByteBuffer buffer1 = ByteBuffer.allocate(10); buffer1.put("MockTest1".getBytes("UTF-8")).flip(); final ByteBuffer buffer2 = ByteBuffer.allocate(10); buffer2.put("MockTest2".getBytes("UTF-8")).flip(); final ByteBuffer buffer3 = ByteBuffer.allocate(10); buffer3.put("MockTest3".getBytes("UTF-8")).flip(); final ByteBuffer buffer4 = ByteBuffer.allocate(10); buffer4.put("MockTest4".getBytes("UTF-8")).flip(); // attempt to write... conduit is expected to write all messages without any issues despite the constant handshaking action assertEquals(9, sinkConduit.write(buffer1)); assertFalse(buffer1.hasRemaining()); assertSame(HandshakeStatus.NEED_UNWRAP, engineMock.getHandshakeStatus()); // next handshake action is NEED_UNWRAP assertEquals(9, sinkConduit.write(buffer2)); assertFalse(buffer2.hasRemaining()); assertSame(HandshakeStatus.NEED_WRAP, engineMock.getHandshakeStatus()); // next handshake action is NEED_WRAP assertEquals(9, sinkConduit.write(buffer3)); assertFalse(buffer3.hasRemaining()); assertSame(HandshakeStatus.NEED_WRAP, engineMock.getHandshakeStatus()); // next handshake action is NEED_WRAP assertEquals(9, sinkConduit.write(buffer4)); assertFalse(buffer4.hasRemaining()); // make sure that conduit managed to do the WRAP and there is no more handshake actions left assertSame(HandshakeStatus.NOT_HANDSHAKING, engineMock.getHandshakeStatus()); // conduit should be able to shutdown writes sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); // close connection sourceConduit.terminateReads(); // data expected to have been written to 'conduitMock' by 'sinkConduit' assertWrittenMessage(HANDSHAKE_MSG, "MockTest1", HANDSHAKE_MSG, "MockTest2", HANDSHAKE_MSG, HANDSHAKE_MSG, HANDSHAKE_MSG, "MockTest3", HANDSHAKE_MSG, "MockTest4", CLOSE_MSG); } @Test public void writeWithConstantHandshakeAndMappedData() throws IOException { // 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); // set ReadData on conduitMock conduitMock.setReadData("HANDSHAKE_MSG", "HANDSHAKE_MSG", "HANDSHAKE_MSG", "HANDSHAKE_MSG", "CLOSE_MSG"); // enable read on conduitMock, meaning that data above will be available to be read right away conduitMock.enableReads(true); // messages we plan to write final ByteBuffer buffer1 = ByteBuffer.allocate(10); buffer1.put("MockTest1".getBytes("UTF-8")).flip(); final ByteBuffer buffer2 = ByteBuffer.allocate(10); buffer2.put("MockTest2".getBytes("UTF-8")).flip(); final ByteBuffer buffer3 = ByteBuffer.allocate(10); buffer3.put("MockTest3".getBytes("UTF-8")).flip(); final ByteBuffer buffer4 = ByteBuffer.allocate(10); buffer4.put("MockTest4".getBytes("UTF-8")).flip(); // attempt to write... conduit is expected to write all messages without any issues despite the constant handshaking action assertEquals(9, sinkConduit.write(buffer1)); assertFalse(buffer1.hasRemaining()); assertSame(HandshakeStatus.NEED_UNWRAP, engineMock.getHandshakeStatus()); // next handshake action is NEED_UNWRAP assertEquals(9, sinkConduit.write(buffer2)); assertFalse(buffer2.hasRemaining()); assertSame(HandshakeStatus.NEED_WRAP, engineMock.getHandshakeStatus()); // next handshake action is NEED_WRAP assertEquals(9, sinkConduit.write(buffer3)); assertFalse(buffer3.hasRemaining()); assertSame(HandshakeStatus.NEED_WRAP, engineMock.getHandshakeStatus()); // next handshake action is NEED_WRAP assertEquals(9, sinkConduit.write(buffer4)); assertFalse(buffer4.hasRemaining()); // make sure that conduit managed to do the WRAP and there is no more handshake actions left assertSame(HandshakeStatus.NOT_HANDSHAKING, engineMock.getHandshakeStatus()); // conduit should be able to terminate writes sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); // close connection sourceConduit.terminateReads(); // data expected to have been written to 'conduitMock' by 'sinkConduit' assertWrittenMessage("HANDSHAKE_MSG", "MOCK 1", "HANDSHAKE_MSG", "MOCK 2", "HANDSHAKE_MSG", "HANDSHAKE_MSG", "HANDSHAKE_MSG", "MOCK 3", "HANDSHAKE_MSG", "MOCK 4", "CLOSE_MSG"); } @Test public void writeWithIntercalatedHandshake() throws IOException { // set the handshake actions that engineMock will emulate engineMock.setHandshakeActions(NEED_WRAP, NEED_UNWRAP, NEED_TASK, FINISH, PERFORM_REQUESTED_ACTION, NEED_WRAP, PERFORM_REQUESTED_ACTION, NEED_WRAP, PERFORM_REQUESTED_ACTION, NEED_WRAP, PERFORM_REQUESTED_ACTION, NEED_UNWRAP, PERFORM_REQUESTED_ACTION, PERFORM_REQUESTED_ACTION, NEED_UNWRAP, PERFORM_REQUESTED_ACTION, NEED_TASK, PERFORM_REQUESTED_ACTION, PERFORM_REQUESTED_ACTION, FINISH); // set ReadData on conduitMock conduitMock.setReadData(HANDSHAKE_MSG, HANDSHAKE_MSG, HANDSHAKE_MSG, CLOSE_MSG); // enable read on conduitMock, meaning that data above will be available to be read right away conduitMock.enableReads(true); // message we plan to write final ByteBuffer buffer = ByteBuffer.allocate(10); buffer.put("write this".getBytes("UTF-8")).flip(); // attempt to write... conduit is expected to write all messages without any issues despite the constant handshaking action for (int i = 0; i < 10; i++) { assertEquals("Failed at attempt to write number " + i, 10, sinkConduit.write(buffer)); assertFalse(buffer.hasRemaining()); buffer.flip(); } // make sure that conduit managed to do the WRAP and there is no more handshake actions left assertSame(HandshakeStatus.NOT_HANDSHAKING, engineMock.getHandshakeStatus()); // conduit should be able to terminate writes sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); // close connection sourceConduit.terminateReads(); // data expected to have been written to 'conduitMock' by 'sinkConduit' assertWrittenMessage(HANDSHAKE_MSG, "write this", HANDSHAKE_MSG, "write this", HANDSHAKE_MSG, "write this", HANDSHAKE_MSG, "write this", "write this", "write this", "write this", "write this", "write this", "write this", CLOSE_MSG); } @Test public void writeWithIntercalatedHandshakeAndMappedData() throws IOException { // map all data to be read and written engineMock.addWrapEntry(HANDSHAKE_MSG, "[!@#$%^&*()_]"); 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, PERFORM_REQUESTED_ACTION, NEED_WRAP, PERFORM_REQUESTED_ACTION, NEED_WRAP, PERFORM_REQUESTED_ACTION, NEED_UNWRAP, PERFORM_REQUESTED_ACTION, PERFORM_REQUESTED_ACTION, NEED_UNWRAP, PERFORM_REQUESTED_ACTION, NEED_TASK, PERFORM_REQUESTED_ACTION, PERFORM_REQUESTED_ACTION, FINISH); // set ReadData on conduitMock conduitMock.setReadData("[!@#$%^&*()_]", "[!@#$%^&*()_]", "[!@#$%^&*()_]", "[_)(*&^%$#@!]"); // enable read on conduitMock, meaning that data above will be available to be read right away conduitMock.enableReads(true); // message we plan to write final ByteBuffer buffer = ByteBuffer.allocate(10); buffer.put("write this".getBytes("UTF-8")).flip(); // attempt to write... conduit is expected to write all messages without any issues despite the constant handshaking action for (int i = 0; i < 10; i++) { assertEquals("Failed at attempt to write number " + i, 10, sinkConduit.write(buffer)); assertFalse(buffer.hasRemaining()); buffer.flip(); } // make sure that conduit managed to do the WRAP and there is no more handshake actions left assertSame(HandshakeStatus.NOT_HANDSHAKING, engineMock.getHandshakeStatus()); // conduit should be able to terminate writes sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); // close connection sourceConduit.terminateReads(); // data expected to have been written to 'conduitMock' by 'sinkConduit' assertWrittenMessage("[!@#$%^&*()_]", "this", "[!@#$%^&*()_]", "this", "[!@#$%^&*()_]", "this", "[!@#$%^&*()_]", "this", "this", "this", "this", "this", "this", "this", "[_)(*&^%$#@!]"); } @Test public void attemptToWriteWithFaultyTask() throws IOException { // set the handshake actions that engineMock will emulate engineMock.setHandshakeActions(NEED_FAULTY_TASK); // message we plan to write final ByteBuffer buffer = ByteBuffer.allocate(21); buffer.put("write this if you can".getBytes("UTF-8")).flip(); // try to write a bunch of times, we will get an IOException at all of the times for (int i = 0; i < 10; i ++) { boolean failed = false; try { sinkConduit.write(buffer); } catch (IOException expected) { failed = true; } assertTrue(failed); } } @Test public void closeWithoutFlushing() throws IOException { ByteBuffer buffer = ByteBuffer.allocate(10); buffer.put("abc".getBytes("UTF-8")).flip(); sinkConduit.write(buffer); sourceConduit.terminateReads(); assertWrittenMessage("abc", CLOSE_MSG); } }