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