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
17package com.googlecode.android_scripting;
18
19import com.google.common.collect.Lists;
20
21import org.json.JSONException;
22import org.json.JSONObject;
23
24import java.io.BufferedReader;
25import java.io.IOException;
26import java.io.InputStreamReader;
27import java.io.PrintWriter;
28import java.net.BindException;
29import java.net.Inet4Address;
30import java.net.InetAddress;
31import java.net.InetSocketAddress;
32import java.net.NetworkInterface;
33import java.net.ServerSocket;
34import java.net.Socket;
35import java.net.SocketException;
36import java.net.UnknownHostException;
37import java.util.Collections;
38import java.util.Enumeration;
39import java.util.List;
40import java.util.concurrent.ConcurrentHashMap;
41//import com.googlecode.android_scripting.jsonrpc.RpcReceiverManager;
42
43/**
44 * A simple server.
45 */
46public 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