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