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 com.googlecode.android_scripting;
18 
19 import com.google.common.collect.Lists;
20 
21 import java.io.IOException;
22 import java.net.BindException;
23 import java.net.Inet4Address;
24 import java.net.InetAddress;
25 import java.net.InetSocketAddress;
26 import java.net.NetworkInterface;
27 import java.net.ServerSocket;
28 import java.net.Socket;
29 import java.net.SocketException;
30 import java.net.UnknownHostException;
31 import java.util.Collections;
32 import java.util.Enumeration;
33 import java.util.List;
34 import java.util.concurrent.CopyOnWriteArrayList;
35 
36 /**
37  * A simple server.
38  */
39 public abstract class SimpleServer {
40     private final CopyOnWriteArrayList<ConnectionThread> mConnectionThreads =
41             new CopyOnWriteArrayList<>();
42     private final List<SimpleServerObserver> mObservers = Lists.newArrayList();
43     private volatile boolean mStopServer = false;
44     private ServerSocket mServer;
45     private Thread mServerThread;
46 
47     /**
48      * An interface for accessing SimpleServer events.
49      */
50     public interface SimpleServerObserver {
51         /** The function to be called when a ConnectionThread is established.*/
onConnect()52         void onConnect();
53         /** The function to be called when a ConnectionThread disconnects.*/
onDisconnect()54         void onDisconnect();
55     }
56 
57     /** An abstract method for handling non-RPC connections. */
handleConnection(Socket socket)58     protected abstract void handleConnection(Socket socket) throws Exception;
59 
60     /**
61      * Adds an observer.
62      */
addObserver(SimpleServerObserver observer)63     public void addObserver(SimpleServerObserver observer) {
64         mObservers.add(observer);
65     }
66 
67     /**
68      * Removes an observer.
69      */
removeObserver(SimpleServerObserver observer)70     public void removeObserver(SimpleServerObserver observer) {
71         mObservers.remove(observer);
72     }
73 
74     /** Notifies all subscribers when a new ConnectionThread has been created.
75      *
76      * This applies to both newly instantiated sessions and continued sessions.
77      */
notifyOnConnect()78     private void notifyOnConnect() {
79         for (SimpleServerObserver observer : mObservers) {
80             observer.onConnect();
81         }
82     }
83 
84     /** Notifies all subscribers when a ConnectionThread has been terminated. */
notifyOnDisconnect()85     private void notifyOnDisconnect() {
86         for (SimpleServerObserver observer : mObservers) {
87             observer.onDisconnect();
88         }
89     }
90 
91     /** An implementation of a thread that holds data about its server connection status. */
92     private final class ConnectionThread extends Thread {
93         /** The socket used for communication. */
94         private final Socket mmSocket;
95 
ConnectionThread(Socket socket)96         private ConnectionThread(Socket socket) {
97             setName("SimpleServer ConnectionThread " + getId());
98             mmSocket = socket;
99         }
100 
101         @Override
run()102         public void run() {
103             Log.v("Server thread " + getId() + " started.");
104             try {
105                 handleConnection(mmSocket);
106             } catch (Exception e) {
107                 if (!mStopServer) {
108                     Log.e("Server error.", e);
109                 }
110             } finally {
111                 close();
112                 mConnectionThreads.remove(this);
113                 notifyOnDisconnect();
114                 Log.v("Server thread " + getId() + " stopped.");
115             }
116         }
117 
close()118         private void close() {
119             if (mmSocket != null) {
120                 try {
121                     mmSocket.close();
122                 } catch (IOException e) {
123                     Log.e(e.getMessage(), e);
124                 }
125             }
126         }
127     }
128 
129     /**
130      * Returns the number of active connections to this server.
131      */
getNumberOfConnections()132     public int getNumberOfConnections() {
133         return mConnectionThreads.size();
134     }
135 
136     /**
137      * Returns the private InetAddress
138      * @return the private InetAddress
139      * @throws UnknownHostException If unable to resolve localhost during fallback.
140      * @throws SocketException if an IOError occurs while querying the network interfaces.
141      */
getPrivateInetAddress()142     public static InetAddress getPrivateInetAddress() throws UnknownHostException, SocketException {
143         InetAddress candidate = null;
144         Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
145         for (NetworkInterface netint : Collections.list(nets)) {
146             if (!netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active
147                 continue;
148             }
149             Enumeration<InetAddress> addresses = netint.getInetAddresses();
150             for (InetAddress address : Collections.list(addresses)) {
151                 if (address instanceof Inet4Address) {
152                     Log.d("local address " + address);
153                     return address; // Prefer ipv4
154                 }
155                 candidate = address; // Probably an ipv6
156             }
157         }
158         if (candidate != null) {
159             return candidate; // return ipv6 address if no suitable ipv6
160         }
161         return InetAddress.getLocalHost(); // No damn matches. Give up, return local host.
162     }
163 
164     /**
165      * Returns the public InetAddress
166      * @return the private InetAddress
167      * @throws UnknownHostException If unable to resolve localhost during fallback.
168      * @throws SocketException if an IOError occurs while querying the network interfaces.
169      */
getPublicInetAddress()170     public static InetAddress getPublicInetAddress() throws UnknownHostException, SocketException {
171         InetAddress candidate = null;
172         Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
173         for (NetworkInterface netint : Collections.list(nets)) {
174             // TODO(markdr): The only diff between this and above fn is the ! on the line below.
175             //               Merge these two functions.
176             if (netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active
177                 continue;
178             }
179             Enumeration<InetAddress> addresses = netint.getInetAddresses();
180             for (InetAddress address : Collections.list(addresses)) {
181                 if (address instanceof Inet4Address) {
182                     return address; // Prefer ipv4
183                 }
184                 candidate = address; // Probably an ipv6
185             }
186         }
187         if (candidate != null) {
188             return candidate; // return ipv6 address if no suitable ipv6
189         }
190         return InetAddress.getLocalHost(); // No damn matches. Give up, return local host.
191     }
192 
193     /**
194      * Starts the RPC server bound to the localhost address.
195      *
196      * @param port the port to bind to or 0 to pick any unused port
197      * @return the port that the server is bound to
198      * @throws IOException
199      */
startLocal(int port)200     public InetSocketAddress startLocal(int port) {
201         InetAddress address;
202         try {
203             // address = InetAddress.getLocalHost();
204             address = getPrivateInetAddress();
205             mServer = new ServerSocket(port, 5, address);
206         } catch (BindException e) {
207             Log.e("Port " + port + " already in use.");
208             try {
209                 address = getPrivateInetAddress();
210                 mServer = new ServerSocket(0, 5, address);
211             } catch (IOException e1) {
212                 e1.printStackTrace();
213                 return null;
214             }
215         } catch (Exception e) {
216             Log.e("Failed to start server.", e);
217             return null;
218         }
219         int boundPort = start();
220         return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(),
221                 boundPort);
222     }
223 
224     /**
225      * Starts the RPC server bound to the public facing address.
226      *
227      * @param port the port to bind to or 0 to pick any unused port
228      * @return the port that the server is bound to
229      */
startPublic(int port)230     public InetSocketAddress startPublic(int port) {
231         InetAddress address;
232         try {
233             // address = getPublicInetAddress();
234             address = null;
235             mServer = new ServerSocket(port, 5 /* backlog */, address);
236         } catch (Exception e) {
237             Log.e("Failed to start server.", e);
238             return null;
239         }
240         int boundPort = start();
241         return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(),
242                 boundPort);
243     }
244 
245     /**
246      * Starts the RPC server bound to all interfaces.
247      *
248      * @param port the port to bind to or 0 to pick any unused port
249      * @return the port that the server is bound to
250      */
startAllInterfaces(int port)251     public InetSocketAddress startAllInterfaces(int port) {
252         try {
253             mServer = new ServerSocket(port, 5 /* backlog */);
254         } catch (Exception e) {
255             Log.e("Failed to start server.", e);
256             return null;
257         }
258         int boundPort = start();
259         return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(),
260                 boundPort);
261     }
262 
start()263     private int start() {
264         mServerThread = new Thread(() -> {
265             while (!mStopServer) {
266                 try {
267                     Socket sock = mServer.accept();
268                     if (!mStopServer) {
269                         startConnectionThread(sock);
270                     } else {
271                         sock.close();
272                     }
273                 } catch (IOException e) {
274                     if (!mStopServer) {
275                         Log.e("Failed to accept connection.", e);
276                     }
277                 }
278             }
279         });
280         mServerThread.start();
281         Log.v("Bound to " + mServer.getInetAddress());
282         return mServer.getLocalPort();
283     }
284 
startConnectionThread(final Socket sock)285     protected void startConnectionThread(final Socket sock) {
286         ConnectionThread networkThread = new ConnectionThread(sock);
287         mConnectionThreads.add(networkThread);
288         networkThread.start();
289         notifyOnConnect();
290     }
291 
292     /** Closes the server, preventing new connections from being added. */
shutdown()293     public void shutdown() {
294         // Stop listening on the server socket to ensure that
295         // beyond this point there are no incoming requests.
296         mStopServer = true;
297         try {
298             mServer.close();
299         } catch (IOException e) {
300             Log.e("Failed to close server socket.", e);
301         }
302         // Since the server is not running, the mNetworkThreads set can only
303         // shrink from this point onward. We can just stop all of the running helper
304         // threads. In the worst case, one of the running threads will already have
305         // shut down. Since this is a CopyOnWriteList, we don't have to worry about
306         // concurrency issues while iterating over the set of threads.
307         for (ConnectionThread connectionThread : mConnectionThreads) {
308             connectionThread.close();
309         }
310         for (SimpleServerObserver observer : mObservers) {
311             removeObserver(observer);
312         }
313     }
314 }
315