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