1 /*
2  * Copyright (C) 2018 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.facade.net;
18 
19 import android.net.NetworkUtils;
20 import android.system.ErrnoException;
21 import android.system.Os;
22 
23 import com.googlecode.android_scripting.Log;
24 import com.googlecode.android_scripting.facade.FacadeManager;
25 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
26 import com.googlecode.android_scripting.rpc.Rpc;
27 
28 import java.io.FileDescriptor;
29 import java.io.IOException;
30 import java.io.InterruptedIOException;
31 import java.io.ObjectInputStream;
32 import java.io.ObjectOutputStream;
33 import java.net.DatagramPacket;
34 import java.net.DatagramSocket;
35 import java.net.InetAddress;
36 import java.net.ServerSocket;
37 import java.net.Socket;
38 import java.net.SocketException;
39 import java.nio.charset.StandardCharsets;
40 import java.util.HashMap;
41 
42 /**
43  * This class has APIs for various socket types
44  * android.system.Os socket used for UDP and TCP sockets
45  * java.net.DatagramSocket for UDP sockets
46  * java.net.Socket and java.net.ServerSocket for TCP sockets
47  */
48 public class SocketFacade extends RpcReceiver {
49 
50     public static HashMap<String, DatagramSocket> sDatagramSocketHashMap =
51             new HashMap<String, DatagramSocket>();
52     private static HashMap<String, Socket> sSocketHashMap =
53             new HashMap<String, Socket>();
54     private static HashMap<String, ServerSocket> sServerSocketHashMap =
55             new HashMap<String, ServerSocket>();
56     private static HashMap<String, FileDescriptor> sFileDescriptorHashMap =
57             new HashMap<String, FileDescriptor>();
58     public static int MAX_BUF_SZ = 2048;
59     public static int SOCK_TIMEOUT = 1500;
60 
SocketFacade(FacadeManager manager)61     public SocketFacade(FacadeManager manager) {
62         super(manager);
63     }
64 
65     /**
66      * Method to return hash value of a DatagramSocket object
67      * Assumes that a unique hashCode is generated for each object
68      * and odds of equal hashCode for different sockets is negligible
69      */
getDatagramSocketId(DatagramSocket socket)70     private String getDatagramSocketId(DatagramSocket socket) {
71         return "DATAGRAMSOCKET:" + socket.hashCode();
72     }
73 
74     /**
75      * Method to return hash value of a Socket object
76      * Assumes that a unique hashCode is generated for each object
77      * and odds of equal hashCode for different sockets is negligible
78      */
getSocketId(Socket socket)79     private String getSocketId(Socket socket) {
80         return "SOCKET:" + socket.hashCode();
81     }
82 
83     /**
84      * Method to return hash value of a ServerSocket object
85      * Assumes that a unique hashCode is generated for each object
86      * and odds of equal hashCode for different sockets is negligible
87      */
getServerSocketId(ServerSocket socket)88     private String getServerSocketId(ServerSocket socket) {
89         return "SERVERSOCKET:" + socket.hashCode();
90     }
91 
92     /**
93      * Method to return hash value of a FileDescriptor object
94      * Assumes that a unique hashCode is generated for each object
95      * and odds of equal hashCode for different sockets is negligible
96      */
getFileDescriptorId(FileDescriptor fd)97     private String getFileDescriptorId(FileDescriptor fd) {
98         return "FILEDESCRIPTOR:" + fd.hashCode();
99     }
100 
101     /**
102      * Method to retrieve FileDescriptor from hash key
103      * @param id : Hash key in String
104      * @return FileDescriptor
105      */
getFileDescriptor(String id)106     public static FileDescriptor getFileDescriptor(String id) {
107         return sFileDescriptorHashMap.get(id);
108     }
109 
110     /**
111      * Method to retrieve DatagramSocket from hash key
112      * @param id : Hash key in String
113      * @return DatagramSocket
114      */
getDatagramSocket(String id)115     public static DatagramSocket getDatagramSocket(String id) {
116         return sDatagramSocketHashMap.get(id);
117     }
118 
119     /**
120      * Method to retrieve Socket from hash key
121      * @param id : Hash key in String
122      * @return Socket
123      */
getSocket(String id)124     public static Socket getSocket(String id) {
125         return sSocketHashMap.get(id);
126     }
127 
128     /**
129      * Method to retrieve ServerSocket from hash key
130      * @param id : Hash key in String
131      * @return ServerSocket
132      */
getServerSocket(String id)133     public static ServerSocket getServerSocket(String id) {
134         return sServerSocketHashMap.get(id);
135     }
136 
137     /*
138      * The following APIs for open, close, send and recv over Stream sockets
139      * This uses java.net.Socket and java.net.ServerSocket and applies only for TCP traffic
140      */
141 
142     /**
143      * Open TCP client socket and connect to server using java.net.Socket
144      * @param remote : IP addr of the server
145      * @param remotePort : Port of the server's socket
146      * @param local : IP addr of the client
147      * @param localPort : Port the client's socket
148      * @return Hash key of client Socket
149      */
150     @Rpc(description = "Open TCP socket & connect to server")
openTcpSocket( String remote, Integer remotePort, String local, Integer localPort)151     public String openTcpSocket(
152             String remote,
153             Integer remotePort,
154             String local,
155             Integer localPort) {
156         try {
157             InetAddress remoteAddr = NetworkUtils.numericToInetAddress(remote);
158             InetAddress localAddr = NetworkUtils.numericToInetAddress(local);
159             Socket socket = new Socket(remoteAddr, remotePort.intValue(), localAddr,
160                     localPort.intValue());
161             String id = getSocketId(socket);
162             sSocketHashMap.put(id, socket);
163             return id;
164         } catch (IOException e) {
165             Log.e("Socket: Failed to open TCP client socket " + e.toString());
166         }
167         return null;
168     }
169 
170     /**
171      * Close socket of java.net.Socket class
172      * @param id : Hash key of Socket object
173      * @return True if closing socket is successful
174      */
175     @Rpc(description = "Close TCP client socket")
closeTcpSocket(String id)176     public Boolean closeTcpSocket(String id) {
177         Socket socket = sSocketHashMap.get(id);
178         if (socket == null) {
179             Log.e("Socket: Socket does not exist for the requested id");
180             return false;
181         }
182         try {
183             socket.close();
184             sSocketHashMap.remove(id);
185             return true;
186         } catch (IOException e) {
187             Log.e("Socket: Failed to close TCP client socket " + e.toString());
188         }
189         return false;
190     }
191 
192     /**
193      * Open TCP server socket using java.net.ServerSocket
194      * @param addr : IP addr of the server
195      * @param port : Port of the server's socket
196      * @return String of ServerSocket from successful accept
197      */
198     @Rpc(description = "Open TCP server socket and accept connection")
openTcpServerSocket(String addr, Integer port)199     public String openTcpServerSocket(String addr, Integer port) {
200         try {
201             InetAddress localAddr = NetworkUtils.numericToInetAddress(addr);
202             ServerSocket serverSocket = new ServerSocket(port.intValue(), 10, localAddr);
203             String id = getServerSocketId(serverSocket);
204             sServerSocketHashMap.put(id, serverSocket);
205             return id;
206         } catch (IOException e) {
207             Log.e("Socket: Failed to open TCP server socket " + e.toString());
208         }
209         return null;
210     }
211 
212     /**
213      * Close TCP server socket
214      * @param id : Hash key of ServerSocket
215      * @return True if server socket is closed
216      */
217     @Rpc(description = "Close TCP server socket")
closeTcpServerSocket(String id)218     public Boolean closeTcpServerSocket(String id) {
219         ServerSocket socket = sServerSocketHashMap.get(id);
220         if (socket == null) {
221             Log.e("Socket: Server socket does not exist for the requested id");
222             return false;
223         }
224         try {
225             socket.close();
226             sServerSocketHashMap.remove(id);
227             return true;
228         } catch (IOException e) {
229             Log.e("Socket: Failed to close TCP server socket " + e.toString());
230         }
231         return false;
232     }
233 
234     /**
235      * Get the local port on which the ServerSocket is listening. Useful when the server socket
236      * is initialized with 0 port (i.e. selects an available port)
237      *
238      * @param id : Hash key of ServerSocket (returned by
239      * {@link #openTcpServerSocket(String, Integer)}.
240      * @return An integer - the port number (0 in case of an error).
241      */
242     @Rpc(description = "Get the TCP Server socket port number")
getTcpServerSocketPort(String id)243     public Integer getTcpServerSocketPort(String id) {
244         ServerSocket socket = sServerSocketHashMap.get(id);
245         if (socket == null) {
246             Log.e("Socket: Server socket does not exist for the requested id");
247             return 0;
248         }
249         return socket.getLocalPort();
250     }
251 
252     /**
253      * Accept TCP connection
254      * @param id : Hash key of ServerSocket
255      * @return Hash key of Socket returned by accept()
256      */
257     @Rpc(description = "Accept connection")
acceptTcpSocket(String id)258     public String acceptTcpSocket(String id) {
259         try {
260             ServerSocket serverSocket = sServerSocketHashMap.get(id);
261             Socket socket = serverSocket.accept();
262             String sockId = getSocketId(socket);
263             sSocketHashMap.put(sockId, socket);
264             return sockId;
265         } catch (IOException e) {
266             Log.e("Socket: Failed to accept connection " + e.toString());
267         }
268         return null;
269     }
270 
271     /**
272      * Send data to server - only ASCII/UTF characters
273      * @param id : Hash key of client Socket
274      * @param message : Data to send to in String
275      * @return Hash key of client Socket
276      */
277     @Rpc(description = "Send data from client")
sendDataOverTcpSocket(String id, String message)278     public Boolean sendDataOverTcpSocket(String id, String message) {
279         Socket socket = sSocketHashMap.get(id);
280         if (socket == null) {
281             Log.e("Socket: Socket does not exist for the requested id");
282             return null;
283         }
284         try {
285             ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
286             oos.writeObject(message);
287             return true;
288         } catch (IOException | SecurityException | IllegalArgumentException e) {
289             Log.e("Socket: Failed to send data from socket " + e.toString());
290         }
291         return false;
292     }
293 
294     /**
295      * Receive data on ServerSocket - only ASCII/UTF characters
296      * @param id : Hash key of ServerSocket
297      * @return Received data in String
298      */
299     @Rpc(description = "Recv data from client")
recvDataOverTcpSocket(String id)300     public String recvDataOverTcpSocket(String id) {
301         Socket socket = sSocketHashMap.get(id);
302         if (socket == null) {
303             Log.e("Socket: Socket object does not exist");
304             return null;
305         }
306         try {
307             ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
308             String message = (String) ois.readObject();
309             Log.d("Socket: Received " + message);
310             return message;
311         } catch (IOException | SecurityException | IllegalArgumentException
312                 | ClassNotFoundException e) {
313             Log.e("Socket: Failed to read and write on socket " + e.toString());
314         }
315         return null;
316     }
317 
318     /*
319      * The following APIs are used to open, close, send and recv data over DatagramSocket
320      * This uses java.net.DatagramSocket class and only applies for UDP
321      */
322 
323     /**
324      * Open datagram socket using java.net.DatagramSocket
325      * @param addr : IP addr to use
326      * @param port : port to open socket on
327      * @return Hash key of DatargramSocket
328      */
329     @Rpc(description = "Open datagram socket")
openDatagramSocket(String addr, Integer port)330     public String openDatagramSocket(String addr, Integer port) {
331         InetAddress localAddr = NetworkUtils.numericToInetAddress(addr);
332         try {
333             DatagramSocket socket = new DatagramSocket(port.intValue(), localAddr);
334             socket.setSoTimeout(SOCK_TIMEOUT);
335             String id = getDatagramSocketId(socket);
336             sDatagramSocketHashMap.put(id, socket);
337             return id;
338         } catch (SocketException e) {
339             Log.e("Socket: Failed to open datagram socket");
340         }
341         return null;
342     }
343 
344     /**
345      * Close datagram socket
346      * @param id : Hash key of DatagramSocket
347      * @return True if close is successful
348      */
349     @Rpc(description = "Close datagram socket")
closeDatagramSocket(String id)350     public Boolean closeDatagramSocket(String id) {
351         DatagramSocket socket = sDatagramSocketHashMap.get(id);
352         if (socket == null) {
353             Log.e("Socket: Datagram socket does not exist for the requested id");
354             return false;
355         }
356         socket.close();
357         sDatagramSocketHashMap.remove(id);
358         return true;
359     }
360 
361     /**
362      * Send data from datagram socket to server.
363      * @param id : Hash key of local DatagramSocket
364      * @param message : data to send in String
365      * @param addr : IP addr to send the data to
366      * @param port : port of the socket to send the data to
367      * @return True if sending data is successful
368      */
369     @Rpc(description = "Send data over socket", returns = "True if sending data successful")
sendDataOverDatagramSocket(String id, String message, String addr, Integer port)370     public Boolean sendDataOverDatagramSocket(String id, String message, String addr,
371             Integer port) {
372         byte[] buf = message.getBytes();
373         try {
374             InetAddress remoteAddr = NetworkUtils.numericToInetAddress(addr);
375             DatagramSocket socket = sDatagramSocketHashMap.get(id);
376             DatagramPacket pkt = new DatagramPacket(buf, buf.length, remoteAddr, port.intValue());
377             socket.send(pkt);
378             return true;
379         } catch (IOException e) {
380             Log.e("Socket: Failed to send data over datagram socket");
381         }
382         return false;
383     }
384 
385     /**
386      * Receive data on the datagram socket
387      * @param id : Hash key of DatagramSocket
388      * @return Received data in String format
389      */
390     @Rpc(description = "Receive data over socket", returns = "Received data in String")
recvDataOverDatagramSocket(String id)391     public String recvDataOverDatagramSocket(String id) {
392         byte[] buf = new byte[MAX_BUF_SZ];
393         try {
394             DatagramSocket socket = sDatagramSocketHashMap.get(id);
395             DatagramPacket dgramPacket = new DatagramPacket(buf, MAX_BUF_SZ);
396             socket.receive(dgramPacket);
397             return new String(dgramPacket.getData(), 0, dgramPacket.getLength());
398         } catch (IOException e) {
399             Log.e("Socket: Failed to recv data over datagram socket");
400         }
401         return null;
402     }
403 
404     /*
405      * The following APIs are used to open, close, send and receive data over Os.socket
406      * This uses android.system.Os class and can be used for UDP and TCP traffic
407      */
408 
409     /**
410      * Open socket using android.system.Os class.
411      * @param domain : protocol family. Ex: IPv4 or IPv6
412      * @param type : socket type. Ex: DGRAM or STREAM
413      * @param addr : IP addr to use
414      * @param port : port to open socket on
415      * @return Hash key of socket FileDescriptor
416      */
417     @Rpc(description = "Open socket")
openSocket(Integer domain, Integer type, String addr, Integer port)418     public String openSocket(Integer domain, Integer type, String addr, Integer port) {
419         try {
420             FileDescriptor fd = Os.socket(domain, type, 0);
421             InetAddress localAddr = NetworkUtils.numericToInetAddress(addr);
422             Os.bind(fd, localAddr, port.intValue());
423             String id = getFileDescriptorId(fd);
424             sFileDescriptorHashMap.put(id, fd);
425             return id;
426         } catch (SocketException | ErrnoException e) {
427             Log.e("IpSec: Failed to open socket " + e.toString());
428         }
429         return null;
430     }
431 
432     /**
433      * Close socket of android.system.Os class
434      * @param id : Hash key of socket FileDescriptor
435      * @return True if connect successful
436      */
437     @Rpc(description = "Close socket")
closeSocket(String id)438     public Boolean closeSocket(String id) {
439         FileDescriptor fd = sFileDescriptorHashMap.get(id);
440         try {
441             Os.close(fd);
442             return true;
443         } catch (ErrnoException e) {
444             Log.e("IpSec: Failed to close socket " + e.toString());
445         }
446         return false;
447     }
448 
449     /**
450      * Send data from the socket
451      * @param remoteAddr : IP addr to send the data to
452      * @param remotePort : Port of the socket to send the data to
453      * @param message : data to send in String
454      * @param id : Hash key of socket FileDescriptor to send the data from
455      * @return True if connect successful
456      */
457     @Rpc(description = "Send data to server")
sendDataOverSocket( String remoteAddr, Integer remotePort, String message, String id)458     public Boolean sendDataOverSocket(
459             String remoteAddr, Integer remotePort, String message, String id) {
460         FileDescriptor fd = sFileDescriptorHashMap.get(id);
461         InetAddress remote = NetworkUtils.numericToInetAddress(remoteAddr);
462         try {
463             byte [] data = new String(message).getBytes(StandardCharsets.UTF_8);
464             int bytes = Os.sendto(fd, data, 0, data.length, 0, remote, remotePort.intValue());
465             Log.d("IpSec: Sent " + String.valueOf(bytes) + " bytes");
466             return true;
467         } catch (ErrnoException | SocketException e) {
468             Log.e("IpSec: Sending data over socket failed " + e.toString());
469         }
470         return false;
471     }
472 
473     /**
474      * Receive data on the socket.
475      * @param id : Hash key of the socket FileDescriptor
476      * @return Received data in String format
477      */
478     @Rpc(description = "Recv data on server")
recvDataOverSocket(String id)479     public String recvDataOverSocket(String id) {
480         byte[] data = new byte[MAX_BUF_SZ];
481         FileDescriptor fd = sFileDescriptorHashMap.get(id);
482         try {
483             Os.read(fd, data, 0, data.length);
484             return new String(data, StandardCharsets.UTF_8);
485         } catch (ErrnoException | InterruptedIOException e) {
486             Log.e("IpSec: Receiving data over socket failed " + e.toString());
487         }
488         return null;
489     }
490 
491     /**
492      * TCP connect to server from client.
493      * @param id : Hash key of socket FileDescriptor to listen
494      * @return True if listen successful
495      */
496     @Rpc(description = "Listen for connection on server")
listenSocket(String id)497     public Boolean listenSocket(String id) {
498         FileDescriptor fd = sFileDescriptorHashMap.get(id);
499         try {
500             // Start listening with buffer of size 10
501             Os.listen(fd, 10);
502             return true;
503         } catch (ErrnoException e) {
504             Log.e("IpSec: Failed to listen on socket " + e.toString());
505         }
506         return false;
507     }
508 
509     /**
510      * TCP connect to server from client.
511      * @param id : client FileDescriptor key
512      * @param addr : IP addr in string of server
513      * @param port : server's port to connect to
514      * @return True if connect successful
515      */
516     @Rpc(description = "Connect to server")
connectSocket(String id, String addr, Integer port)517     public Boolean connectSocket(String id, String addr, Integer port) {
518         FileDescriptor fd = sFileDescriptorHashMap.get(id);
519         try {
520             InetAddress remoteAddr = NetworkUtils.numericToInetAddress(addr);
521             Os.connect(fd, remoteAddr, port.intValue());
522             return true;
523         } catch (SocketException | ErrnoException e) {
524             Log.e("IpSec: Failed to connect socket " + e.toString());
525         }
526         return false;
527     }
528 
529     /**
530      * Accept TCP connection from the client to server.
531      * @param id : server FileDescriptor key
532      * @return Hash key of FileDescriptor returned by successful accept()
533      */
534     @Rpc(description = "Accept connection on server")
acceptSocket(String id)535     public String acceptSocket(String id) {
536         FileDescriptor fd = sFileDescriptorHashMap.get(id);
537         try {
538             FileDescriptor socket = Os.accept(fd, null);
539             String socketId = getFileDescriptorId(socket);
540             sFileDescriptorHashMap.put(socketId, socket);
541             return socketId;
542         } catch (SocketException | ErrnoException e) {
543             Log.e("IpSec: Failed to accept on socket " + e.toString());
544         }
545         return null;
546     }
547 
548     @Override
shutdown()549     public void shutdown() {}
550 }
551