1/*
2 * Copyright (C) 2016 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
17package android.net.util;
18
19import android.annotation.Nullable;
20import android.system.ErrnoException;
21import android.system.Os;
22import android.system.OsConstants;
23
24import libcore.io.IoBridge;
25
26import java.io.FileDescriptor;
27import java.io.InterruptedIOException;
28import java.io.IOException;
29
30
31/**
32 * A thread that reads from a socket and passes the received packets to a
33 * subclass's handlePacket() method.  The packet receive buffer is recycled
34 * on every read call, so subclasses should make any copies they would like
35 * inside their handlePacket() implementation.
36 *
37 * All public methods may be called from any thread.
38 *
39 * @hide
40 */
41public abstract class BlockingSocketReader {
42    public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024;
43
44    private final byte[] mPacket;
45    private final Thread mThread;
46    private volatile FileDescriptor mSocket;
47    private volatile boolean mRunning;
48    private volatile long mPacketsReceived;
49
50    // Make it slightly easier for subclasses to properly close a socket
51    // without having to know this incantation.
52    public static final void closeSocket(@Nullable FileDescriptor fd) {
53        try {
54            IoBridge.closeAndSignalBlockedThreads(fd);
55        } catch (IOException ignored) {}
56    }
57
58    protected BlockingSocketReader() {
59        this(DEFAULT_RECV_BUF_SIZE);
60    }
61
62    protected BlockingSocketReader(int recvbufsize) {
63        if (recvbufsize < DEFAULT_RECV_BUF_SIZE) {
64            recvbufsize = DEFAULT_RECV_BUF_SIZE;
65        }
66        mPacket = new byte[recvbufsize];
67        mThread = new Thread(() -> { mainLoop(); });
68    }
69
70    public final boolean start() {
71        if (mSocket != null) return false;
72
73        try {
74            mSocket = createSocket();
75        } catch (Exception e) {
76            logError("Failed to create socket: ", e);
77            return false;
78        }
79
80        if (mSocket == null) return false;
81
82        mRunning = true;
83        mThread.start();
84        return true;
85    }
86
87    public final void stop() {
88        mRunning = false;
89        closeSocket(mSocket);
90        mSocket = null;
91    }
92
93    public final boolean isRunning() { return mRunning; }
94
95    public final long numPacketsReceived() { return mPacketsReceived; }
96
97    /**
98     * Subclasses MUST create the listening socket here, including setting
99     * all desired socket options, interface or address/port binding, etc.
100     */
101    protected abstract FileDescriptor createSocket();
102
103    /**
104     * Called by the main loop for every packet.  Any desired copies of
105     * |recvbuf| should be made in here, and the underlying byte array is
106     * reused across all reads.
107     */
108    protected void handlePacket(byte[] recvbuf, int length) {}
109
110    /**
111     * Called by the main loop to log errors.  In some cases |e| may be null.
112     */
113    protected void logError(String msg, Exception e) {}
114
115    /**
116     * Called by the main loop just prior to exiting.
117     */
118    protected void onExit() {}
119
120    private final void mainLoop() {
121        while (isRunning()) {
122            final int bytesRead;
123
124            try {
125                // Blocking read.
126                // TODO: See if this can be converted to recvfrom.
127                bytesRead = Os.read(mSocket, mPacket, 0, mPacket.length);
128                if (bytesRead < 1) {
129                    if (isRunning()) logError("Socket closed, exiting", null);
130                    break;
131                }
132                mPacketsReceived++;
133            } catch (ErrnoException e) {
134                if (e.errno != OsConstants.EINTR) {
135                    if (isRunning()) logError("read error: ", e);
136                    break;
137                }
138                continue;
139            } catch (IOException ioe) {
140                if (isRunning()) logError("read error: ", ioe);
141                continue;
142            }
143
144            try {
145                handlePacket(mPacket, bytesRead);
146            } catch (Exception e) {
147                logError("Unexpected exception: ", e);
148                break;
149            }
150        }
151
152        stop();
153        onExit();
154    }
155}
156