/* * Copyright (c) 2002-2017 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * 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.neo4j.driver.internal.net; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import java.io.IOException; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.util.ArrayList; import java.util.List; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.exceptions.ServiceUnavailableException; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.neo4j.driver.internal.logging.DevNullLogger.DEV_NULL_LOGGER; import static org.neo4j.driver.internal.net.BoltServerAddress.LOCAL_DEFAULT; public class SocketClientTest { private static final int CONNECTION_TIMEOUT = 42; @Rule public ExpectedException exception = ExpectedException.none(); // TODO: This is not possible with blocking NIO channels, unless we use inputStreams, but then we can't use // off-heap buffers. We need to swap to use selectors, which would allow us to time out. @Test @Ignore public void testNetworkTimeout() throws Throwable { // Given a server that will never reply ServerSocket server = new ServerSocket( 0 ); BoltServerAddress address = new BoltServerAddress( "localhost", server.getLocalPort() ); SocketClient client = dummyClient( address ); // Expect exception.expect( ClientException.class ); exception.expectMessage( "database took longer than network timeout (100ms) to reply." ); // When client.start(); } @Test public void testConnectionTimeout() throws Throwable { BoltServerAddress address = new BoltServerAddress( "localhost", 1234 ); SocketClient client = dummyClient( address ); // Expect exception.expect( ServiceUnavailableException.class ); exception.expectMessage( "Unable to connect to localhost:1234, " + "ensure the database is running and that there is a working network connection to it." ); // When client.start(); } @Test public void testIOExceptionWhenFailedToEstablishConnection() throws Throwable { SocketClient client = dummyClient(); ByteChannel mockedChannel = mock( ByteChannel.class ); when( mockedChannel.write( any( ByteBuffer.class ) ) ) .thenThrow( new IOException( "Failed to connect to server due to IOException" ) ); client.setChannel( mockedChannel ); // Expect exception.expect( ServiceUnavailableException.class ); exception.expectMessage( "Unable to process request: Failed to connect to server due to IOException" ); // When client.start(); } private SocketClient dummyClient( BoltServerAddress address ) { return new SocketClient( address, SecurityPlan.insecure(), CONNECTION_TIMEOUT, DEV_NULL_LOGGER ); } private SocketClient dummyClient() { return dummyClient( LOCAL_DEFAULT ); } @Test public void shouldReadAllBytes() throws IOException { // Given ByteBuffer buffer = ByteBuffer.allocate( 4 ); ByteAtATimeChannel channel = new ByteAtATimeChannel( new byte[]{0, 1, 2, 3} ); SocketClient client = dummyClient(); // When client.setChannel( channel ); client.blockingRead( buffer ); buffer.flip(); // Then assertThat(buffer.get(), equalTo((byte) 0)); assertThat(buffer.get(), equalTo((byte) 1)); assertThat(buffer.get(), equalTo((byte) 2)); assertThat(buffer.get(), equalTo((byte) 3)); } @Test public void shouldFailIfConnectionFailsWhileReading() throws IOException { // Given ByteBuffer buffer = ByteBuffer.allocate( 4 ); ByteChannel channel = mock( ByteChannel.class ); when(channel.read( buffer )).thenReturn( -1 ); SocketClient client = dummyClient(); //Expect exception.expect( ServiceUnavailableException.class ); exception.expectMessage( "Expected 4 bytes, received none" ); // When client.setChannel( channel ); client.blockingRead( buffer ); } @Test public void shouldWriteAllBytes() throws IOException { // Given ByteBuffer buffer = ByteBuffer.wrap( new byte[]{0, 1, 2, 3}); ByteAtATimeChannel channel = new ByteAtATimeChannel( new byte[0] ); SocketClient client = dummyClient(); // When client.setChannel( channel ); client.blockingWrite( buffer ); // Then assertThat(channel.writtenBytes.get(0), equalTo((byte) 0)); assertThat(channel.writtenBytes.get(1), equalTo((byte) 1)); assertThat(channel.writtenBytes.get(2), equalTo((byte) 2)); assertThat(channel.writtenBytes.get(3), equalTo((byte) 3)); } @Test public void shouldFailIfConnectionFailsWhileWriting() throws IOException { // Given ByteBuffer buffer = ByteBuffer.allocate( 4 ); buffer.position( 1 ); ByteChannel channel = mock( ByteChannel.class ); when(channel.write( buffer )).thenReturn( -1 ); SocketClient client = dummyClient(); //Expect exception.expect( ServiceUnavailableException.class ); exception.expectMessage( "Expected 4 bytes, wrote 00" ); // When client.setChannel( channel ); client.blockingWrite( buffer ); } private static class ByteAtATimeChannel implements ByteChannel { private final byte[] bytes; private int index = 0; private List<Byte> writtenBytes = new ArrayList<>( ); private ByteAtATimeChannel( byte[] bytes ) { this.bytes = bytes; } @Override public int read( ByteBuffer dst ) throws IOException { if (index >= bytes.length) { return -1; } dst.put( bytes[index++]); return 1; } @Override public int write( ByteBuffer src ) throws IOException { writtenBytes.add( src.get() ); return 1; } @Override public boolean isOpen() { return true; } @Override public void close() throws IOException { } } }