1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package libcore.java.net; 18 19 import org.junit.Test; 20 21 import java.io.Closeable; 22 import java.io.IOException; 23 import java.net.DatagramPacket; 24 import java.net.DatagramSocket; 25 import java.net.InetSocketAddress; 26 import java.net.ServerSocket; 27 import java.net.Socket; 28 import java.net.SocketTimeoutException; 29 import java.nio.channels.ServerSocketChannel; 30 import java.util.concurrent.CountDownLatch; 31 import java.util.concurrent.TimeUnit; 32 33 import libcore.util.EmptyArray; 34 35 import static org.junit.Assert.assertFalse; 36 import static org.junit.Assert.assertTrue; 37 import static org.junit.Assert.fail; 38 39 /** 40 * Tests socket timeout behavior for various different socket types. 41 */ 42 public class SocketTimeoutTest { 43 44 private static final int TIMEOUT_MILLIS = 500; 45 46 private static final InetSocketAddress UNREACHABLE_ADDRESS 47 = new InetSocketAddress("192.0.2.0", 0); // RFC 5737 48 49 @FunctionalInterface 50 private interface SocketOperation<T> { operate(T s)51 void operate(T s) throws IOException; 52 } 53 54 @FunctionalInterface 55 private interface SocketConstructor<T> { get()56 T get() throws IOException; 57 } 58 checkOperationTimesOut(SocketConstructor<T> construct, SocketOperation<T> op)59 private static <T extends Closeable> void checkOperationTimesOut(SocketConstructor<T> construct, 60 SocketOperation<T> op) throws Exception { 61 try (T socket = construct.get()) { 62 long startingTime = System.currentTimeMillis(); 63 try { 64 op.operate(socket); 65 fail(); 66 } catch (SocketTimeoutException timeoutException) { 67 long timeElapsed = System.currentTimeMillis() - startingTime; 68 assertTrue( 69 Math.abs(((float) timeElapsed / TIMEOUT_MILLIS) - 1) 70 < 0.2f); // Allow some error. 71 } 72 } 73 } 74 75 @Test testSocketConnectTimeout()76 public void testSocketConnectTimeout() throws Exception { 77 // #connect(SocketAddress endpoint, int timeout) 78 checkOperationTimesOut(() -> new Socket(), s -> s.connect(UNREACHABLE_ADDRESS, 79 TIMEOUT_MILLIS)); 80 81 // Setting SO_TIMEOUT should not affect connect timeout. 82 checkOperationTimesOut(() -> new Socket(), 83 s -> { 84 s.setSoTimeout(TIMEOUT_MILLIS / 2); 85 s.connect(UNREACHABLE_ADDRESS, TIMEOUT_MILLIS); 86 }); 87 } 88 89 @Test testSocketReadTimeout()90 public void testSocketReadTimeout() throws Exception { 91 // #read() 92 try (ServerSocket ss = new ServerSocket(0)) { 93 // The server socket will accept the connection without explicitly calling accept() due 94 // to TCP backlog. 95 96 checkOperationTimesOut(() -> new Socket(), s -> { 97 s.connect(ss.getLocalSocketAddress()); 98 s.setSoTimeout(TIMEOUT_MILLIS); 99 s.getInputStream().read(); 100 }); 101 } 102 } 103 104 @Test testSocketWriteNeverTimeouts()105 public void testSocketWriteNeverTimeouts() throws Exception { 106 // #write() should block if the buffers are full, and does not drop packets or throw 107 // SocketTimeoutException. 108 try (Socket sock = new Socket(); 109 ServerSocket serverSocket = new ServerSocket(0)) { 110 // Setting this option should not affect behaviour, as specified by the spec. 111 sock.setSoTimeout(TIMEOUT_MILLIS); 112 113 // Set SO_SNDBUF and SO_RCVBUF to minimum value allowed by kernel. 114 sock.setSendBufferSize(1); 115 serverSocket.setReceiveBufferSize(1); 116 int actualSize = sock.getSendBufferSize() + serverSocket.getReceiveBufferSize(); 117 118 sock.connect(serverSocket.getLocalSocketAddress()); 119 120 CountDownLatch threadStarted = new CountDownLatch(1); 121 CountDownLatch writeCompleted = new CountDownLatch(1); 122 Thread thread = new Thread(() -> { 123 threadStarted.countDown(); 124 try { 125 // Should block 126 sock.getOutputStream().write(new byte[actualSize + 1]); 127 writeCompleted.countDown(); 128 } catch (IOException ignored) { 129 } finally { 130 writeCompleted.countDown(); 131 } 132 }); 133 134 thread.start(); 135 136 // Wait for the thread to start. 137 assertTrue(threadStarted.await(500, TimeUnit.MILLISECONDS)); 138 139 // Wait for TIMEOUT_MILLIS + slop. If write does not complete by then, we assume it has 140 // blocked. 141 boolean blocked = 142 !writeCompleted.await(TIMEOUT_MILLIS * 2, TimeUnit.MILLISECONDS); 143 assertTrue(blocked); 144 145 // Make sure the writing thread completes after the socket is closed. 146 sock.close(); 147 assertTrue(writeCompleted.await(5000, TimeUnit.MILLISECONDS)); 148 } 149 } 150 151 @Test testServerSocketAcceptTimeout()152 public void testServerSocketAcceptTimeout() throws Exception { 153 // #accept() 154 checkOperationTimesOut(() -> new ServerSocket(0), 155 s -> { 156 s.setSoTimeout(TIMEOUT_MILLIS); 157 s.accept(); 158 }); 159 } 160 161 @Test testServerSocketChannelAcceptTimeout()162 public void testServerSocketChannelAcceptTimeout() throws Exception { 163 // #accept() 164 checkOperationTimesOut(() -> ServerSocketChannel.open(), 165 s -> { 166 s.bind(null, 0); 167 s.socket().setSoTimeout(TIMEOUT_MILLIS); 168 s.socket().accept(); 169 }); 170 } 171 172 @Test testDatagramSocketReceive()173 public void testDatagramSocketReceive() throws Exception { 174 checkOperationTimesOut(() -> new DatagramSocket(), s -> { 175 s.setSoTimeout(TIMEOUT_MILLIS); 176 s.receive(new DatagramPacket(EmptyArray.BYTE, 0)); 177 }); 178 } 179 180 // TODO(yikong), http://b/35867657: 181 // Add tests for SocksSocketImpl once a mock Socks server is implemented. 182 } 183