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