1 /*
2  * Copyright (C) 2016 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.googlecode.android_scripting;
18 
19 import com.google.common.collect.Lists;
20 
21 import org.json.JSONException;
22 import org.json.JSONObject;
23 
24 import java.io.BufferedReader;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.io.PrintWriter;
28 import java.net.BindException;
29 import java.net.Inet4Address;
30 import java.net.InetAddress;
31 import java.net.InetSocketAddress;
32 import java.net.NetworkInterface;
33 import java.net.ServerSocket;
34 import java.net.Socket;
35 import java.net.SocketException;
36 import java.net.UnknownHostException;
37 import java.util.Collections;
38 import java.util.Enumeration;
39 import java.util.List;
40 import java.util.concurrent.ConcurrentHashMap;
41 //import com.googlecode.android_scripting.jsonrpc.RpcReceiverManager;
42 
43 /**
44  * A simple server.
45  * @author Damon Kohler (damonkohler@gmail.com)
46  */
47 public abstract class SimpleServer {
48   private static int threadIndex = 0;
49   private final ConcurrentHashMap<Integer, ConnectionThread> mConnectionThreads =
50       new ConcurrentHashMap<Integer, ConnectionThread>();
51   private final List<SimpleServerObserver> mObservers = Lists.newArrayList();
52   private volatile boolean mStopServer = false;
53   private ServerSocket mServer;
54   private Thread mServerThread;
55 
56   public interface SimpleServerObserver {
onConnect()57     public void onConnect();
onDisconnect()58     public void onDisconnect();
59   }
60 
handleConnection(Socket socket)61   protected abstract void handleConnection(Socket socket) throws Exception;
handleRPCConnection(Socket socket, Integer UID, BufferedReader reader, PrintWriter writer)62   protected abstract void handleRPCConnection(Socket socket,
63                                               Integer UID,
64                                               BufferedReader reader,
65                                               PrintWriter writer) throws Exception;
66 
67   /** Adds an observer. */
addObserver(SimpleServerObserver observer)68   public void addObserver(SimpleServerObserver observer) {
69     mObservers.add(observer);
70   }
71 
72   /** Removes an observer. */
removeObserver(SimpleServerObserver observer)73   public void removeObserver(SimpleServerObserver observer) {
74     mObservers.remove(observer);
75   }
76 
notifyOnConnect()77   private void notifyOnConnect() {
78     for (SimpleServerObserver observer : mObservers) {
79       observer.onConnect();
80     }
81   }
82 
notifyOnDisconnect()83   private void notifyOnDisconnect() {
84     for (SimpleServerObserver observer : mObservers) {
85       observer.onDisconnect();
86     }
87   }
88 
89   private final class ConnectionThread extends Thread {
90     private final Socket mmSocket;
91     private final BufferedReader reader;
92     private final PrintWriter writer;
93     private final Integer UID;
94     private final boolean isRpc;
95 
ConnectionThread(Socket socket, boolean rpc, Integer uid, BufferedReader reader, PrintWriter writer)96     private ConnectionThread(Socket socket, boolean rpc, Integer uid, BufferedReader reader, PrintWriter writer) {
97       setName("SimpleServer ConnectionThread " + getId());
98       mmSocket = socket;
99       this.UID = uid;
100       this.reader = reader;
101       this.writer = writer;
102       this.isRpc = rpc;
103     }
104 
105     @Override
run()106     public void run() {
107       Log.v("Server thread " + getId() + " started.");
108       try {
109         if(isRpc) {
110           Log.d("Handling RPC connection in "+getId());
111           handleRPCConnection(mmSocket, UID, reader, writer);
112         }else{
113           Log.d("Handling Non-RPC connection in "+getId());
114           handleConnection(mmSocket);
115         }
116       } catch (Exception e) {
117         if (!mStopServer) {
118           Log.e("Server error.", e);
119         }
120       } finally {
121         close();
122         mConnectionThreads.remove(this.UID);
123         notifyOnDisconnect();
124         Log.v("Server thread " + getId() + " stopped.");
125       }
126     }
127 
close()128     private void close() {
129       if (mmSocket != null) {
130         try {
131           mmSocket.close();
132         } catch (IOException e) {
133           Log.e(e.getMessage(), e);
134         }
135       }
136     }
137   }
138 
139   /** Returns the number of active connections to this server. */
getNumberOfConnections()140   public int getNumberOfConnections() {
141     return mConnectionThreads.size();
142   }
143 
getPrivateInetAddress()144   public static InetAddress getPrivateInetAddress() throws UnknownHostException, SocketException {
145 
146     InetAddress candidate = null;
147     Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
148     for (NetworkInterface netint : Collections.list(nets)) {
149       if (!netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active
150         continue;
151       }
152       Enumeration<InetAddress> addresses = netint.getInetAddresses();
153       for (InetAddress address : Collections.list(addresses)) {
154         if (address instanceof Inet4Address) {
155           Log.d("local address " + address);
156           return address; // Prefer ipv4
157         }
158         candidate = address; // Probably an ipv6
159       }
160     }
161     if (candidate != null) {
162       return candidate; // return ipv6 address if no suitable ipv6
163     }
164     return InetAddress.getLocalHost(); // No damn matches. Give up, return local host.
165   }
166 
getPublicInetAddress()167   public static InetAddress getPublicInetAddress() throws UnknownHostException, SocketException {
168 
169     InetAddress candidate = null;
170     Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
171     for (NetworkInterface netint : Collections.list(nets)) {
172       if (netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active
173         continue;
174       }
175       Enumeration<InetAddress> addresses = netint.getInetAddresses();
176       for (InetAddress address : Collections.list(addresses)) {
177         if (address instanceof Inet4Address) {
178           return address; // Prefer ipv4
179         }
180         candidate = address; // Probably an ipv6
181       }
182     }
183     if (candidate != null) {
184       return candidate; // return ipv6 address if no suitable ipv6
185     }
186     return InetAddress.getLocalHost(); // No damn matches. Give up, return local host.
187   }
188 
189   /**
190    * Starts the RPC server bound to the localhost address.
191    *
192    * @param port
193    *          the port to bind to or 0 to pick any unused port
194    *
195    * @return the port that the server is bound to
196    * @throws IOException
197    */
startLocal(int port)198   public InetSocketAddress startLocal(int port) {
199     InetAddress address;
200     try {
201       // address = InetAddress.getLocalHost();
202       address = getPrivateInetAddress();
203       mServer = new ServerSocket(port, 5, address);
204     } catch (BindException e) {
205       Log.e("Port " + port + " already in use.");
206       try {
207         address = getPrivateInetAddress();
208         mServer = new ServerSocket(0, 5, address);
209       } catch (IOException e1) {
210         e1.printStackTrace();
211         return null;
212       }
213     } catch (Exception e) {
214       Log.e("Failed to start server.", e);
215       return null;
216     }
217     int boundPort = start();
218     return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(), boundPort);
219   }
220 
221   /**
222    * data Starts the RPC server bound to the public facing address.
223    *
224    * @param port
225    *          the port to bind to or 0 to pick any unused port
226    *
227    * @return the port that the server is bound to
228    */
startPublic(int port)229   public InetSocketAddress startPublic(int port) {
230     InetAddress address;
231     try {
232       // address = getPublicInetAddress();
233       address = null;
234       mServer = new ServerSocket(port, 5 /* backlog */, address);
235     } catch (Exception e) {
236       Log.e("Failed to start server.", e);
237       return null;
238     }
239     int boundPort = start();
240     return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(), boundPort);
241   }
242 
243   /**
244    * data Starts the RPC server bound to all interfaces
245    *
246    * @param port
247    *          the port to bind to or 0 to pick any unused port
248    *
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(), boundPort);
260   }
261 
start()262   private int start() {
263     mServerThread = new Thread() {
264       @Override
265       public void run() {
266         while (!mStopServer) {
267           try {
268             Socket sock = mServer.accept();
269             if (!mStopServer) {
270               startConnectionThread(sock);
271             } else {
272               sock.close();
273             }
274           } catch (IOException e) {
275             if (!mStopServer) {
276               Log.e("Failed to accept connection.", e);
277             }
278           } catch (JSONException e) {
279             if (!mStopServer) {
280               Log.e("Failed to parse request.", e);
281             }
282           }
283         }
284       }
285     };
286     mServerThread.start();
287     Log.v("Bound to " + mServer.getInetAddress());
288     return mServer.getLocalPort();
289   }
290 
startConnectionThread(final Socket sock)291   private void startConnectionThread(final Socket sock) throws IOException, JSONException {
292     BufferedReader reader =
293         new BufferedReader(new InputStreamReader(sock.getInputStream()), 8192);
294     PrintWriter writer = new PrintWriter(sock.getOutputStream(), true);
295     String data;
296     if((data = reader.readLine()) != null) {
297       Log.v("Received: " + data);
298       JSONObject request = new JSONObject(data);
299       if(request.has("cmd") && request.has("uid")) {
300         String cmd = request.getString("cmd");
301         int uid = request.getInt("uid");
302         JSONObject result = new JSONObject();
303         if(cmd.equals("initiate")) {
304           Log.d("Initiate a new session");
305           threadIndex += 1;
306           int mUID = threadIndex;
307           ConnectionThread networkThread = new ConnectionThread(sock,true,mUID,reader,writer);
308           mConnectionThreads.put(mUID, networkThread);
309           networkThread.start();
310           notifyOnConnect();
311           result.put("uid", mUID);
312           result.put("status",true);
313           result.put("error", null);
314         }else if(cmd.equals("continue")) {
315           Log.d("Continue an existing session");
316           Log.d("keys: "+mConnectionThreads.keySet().toString());
317           if(!mConnectionThreads.containsKey(uid)) {
318             result.put("uid", uid);
319             result.put("status",false);
320             result.put("error", "Session does not exist.");
321           }else{
322             ConnectionThread networkThread = new ConnectionThread(sock,true,uid,reader,writer);
323             mConnectionThreads.put(uid, networkThread);
324             networkThread.start();
325             notifyOnConnect();
326             result.put("uid", uid);
327             result.put("status",true);
328             result.put("error", null);
329           }
330         }else {
331           result.put("uid", uid);
332           result.put("status",false);
333           result.put("error", "Unrecognized command.");
334         }
335         writer.write(result + "\n");
336         writer.flush();
337         Log.v("Sent: " + result);
338       }else{
339         ConnectionThread networkThread = new ConnectionThread(sock,false,0,reader,writer);
340         mConnectionThreads.put(0, networkThread);
341         networkThread.start();
342         notifyOnConnect();
343       }
344     }
345   }
346 
shutdown()347   public void shutdown() {
348     // Stop listening on the server socket to ensure that
349     // beyond this point there are no incoming requests.
350     mStopServer = true;
351     try {
352       mServer.close();
353     } catch (IOException e) {
354       Log.e("Failed to close server socket.", e);
355     }
356     // Since the server is not running, the mNetworkThreads set can only
357     // shrink from this point onward. We can just stop all of the running helper
358     // threads. In the worst case, one of the running threads will already have
359     // shut down. Since this is a CopyOnWriteList, we don't have to worry about
360     // concurrency issues while iterating over the set of threads.
361     for (ConnectionThread connectionThread : mConnectionThreads.values()) {
362       connectionThread.close();
363     }
364     for (SimpleServerObserver observer : mObservers) {
365       removeObserver(observer);
366     }
367   }
368 }
369