Home | History | Annotate | Download | only in net
      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> {
     51         void operate(T s) throws IOException;
     52     }
     53 
     54     @FunctionalInterface
     55     private interface SocketConstructor<T> {
     56         T get() throws IOException;
     57     }
     58 
     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
     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
     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
    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
    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
    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
    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