Home | History | Annotate | Download | only in android_scripting
      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 {
     56     public void onConnect();
     57     public void onDisconnect();
     58   }
     59 
     60   protected abstract void handleConnection(Socket socket) throws Exception;
     61   protected abstract void handleRPCConnection(Socket socket,
     62                                               Integer UID,
     63                                               BufferedReader reader,
     64                                               PrintWriter writer) throws Exception;
     65 
     66   /** Adds an observer. */
     67   public void addObserver(SimpleServerObserver observer) {
     68     mObservers.add(observer);
     69   }
     70 
     71   /** Removes an observer. */
     72   public void removeObserver(SimpleServerObserver observer) {
     73     mObservers.remove(observer);
     74   }
     75 
     76   private void notifyOnConnect() {
     77     for (SimpleServerObserver observer : mObservers) {
     78       observer.onConnect();
     79     }
     80   }
     81 
     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 
     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
    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 
    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. */
    139   public int getNumberOfConnections() {
    140     return mConnectionThreads.size();
    141   }
    142 
    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 
    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    */
    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    */
    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    */
    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 
    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 
    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 
    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