1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 /** 20 * @author Ivan G. Popov 21 */ 22 23 /** 24 * Created on 05.23.2004 25 */ 26 package org.apache.harmony.jpda.tests.framework.jdwp; 27 28 29 import java.net.InetAddress; 30 import java.net.ServerSocket; 31 import java.net.Socket; 32 import java.net.SocketTimeoutException; 33 import java.io.InputStream; 34 import java.io.InterruptedIOException; 35 import java.io.OutputStream; 36 import java.io.IOException; 37 38 import org.apache.harmony.jpda.tests.framework.jdwp.Packet; 39 40 /** 41 * This class provides TransportWrapper for row TCP/IP socket connection. 42 * 43 */ 44 public class SocketTransportWrapper implements TransportWrapper { 45 46 public static final String HANDSHAKE_STRING = "JDWP-Handshake"; 47 48 private ServerSocket serverSocket; 49 private Socket transportSocket; 50 private InputStream input; 51 private OutputStream output; 52 53 /** 54 * Starts listening for connection on given or default address. 55 * 56 * @param address 57 * address to listen to or null for default address, 58 * parsed as "hostname:port" or "port", if it contains 59 * no semi-colon. 60 * @return string representation of listening address 61 */ 62 @Override startListening(String address)63 public String startListening(String address) throws IOException { 64 String hostName = null; 65 InetAddress hostAddr = null; 66 int port = 0; 67 if (address != null) { 68 String portName = null; 69 int i = address.indexOf(':'); 70 if (i < 0) { 71 portName = address; 72 } else { 73 hostName = address.substring(0, i); 74 portName = address.substring(i+1); 75 } 76 try { 77 port = Integer.parseInt(portName); 78 } catch (NumberFormatException e) { 79 throw new IOException("Illegal port number in socket address: " + address); 80 } 81 } 82 83 if (hostName != null) { 84 hostAddr = InetAddress.getByName(hostName); 85 serverSocket = new ServerSocket(port, 0, hostAddr); 86 } else { 87 serverSocket = new ServerSocket(port); 88 } 89 90 // use as workaround for unspecified behaviour of isAnyLocalAddress() 91 InetAddress iAddress = null; 92 if (hostName != null) { 93 iAddress = serverSocket.getInetAddress(); 94 } else { 95 iAddress = InetAddress.getLocalHost(); 96 } 97 98 // Older Android runtimes may fail to resolve 'localhost' on a host machine. The workaround 99 // is to use the address instead of the hostname. 100 String hostNameOrAddress = 101 iAddress.isLoopbackAddress() ? iAddress.getHostAddress() : iAddress.getHostName(); 102 address = hostNameOrAddress + ":" + serverSocket.getLocalPort(); 103 return address; 104 } 105 106 /** 107 * Stops listening for connection on current address. 108 */ 109 @Override stopListening()110 public void stopListening() throws IOException { 111 if (serverSocket != null) { 112 serverSocket.close(); 113 } 114 } 115 116 /** 117 * Accepts transport connection for currently listened address and performs handshaking 118 * for specified timeout. 119 * 120 * @param acceptTimeout timeout for accepting in milliseconds 121 * @param handshakeTimeout timeout for handshaking in milliseconds 122 */ 123 @Override accept(long acceptTimeout, long handshakeTimeout)124 public void accept(long acceptTimeout, long handshakeTimeout) throws IOException { 125 synchronized (serverSocket) { 126 serverSocket.setSoTimeout((int) acceptTimeout); 127 try { 128 transportSocket = serverSocket.accept(); 129 } finally { 130 serverSocket.setSoTimeout(0); 131 } 132 } 133 createStreams(); 134 handshake(handshakeTimeout); 135 } 136 137 /** 138 * Attaches transport connection to given address and performs handshaking 139 * for specified timeout. 140 * 141 * @param address address for attaching 142 * @param attachTimeout timeout for attaching in milliseconds 143 * @param handshakeTimeout timeout for handshaking in milliseconds 144 */ 145 @Override attach(String address, long attachTimeout, long handshakeTimeout)146 public void attach(String address, long attachTimeout, long handshakeTimeout) throws IOException { 147 if (address == null) { 148 throw new IOException("Illegal socket address: " + address); 149 } 150 151 String hostName = null; 152 int port = 0; 153 { 154 String portName = null; 155 int i = address.indexOf(':'); 156 if (i < 0) { 157 throw new IOException("Illegal socket address: " + address); 158 } else { 159 hostName = address.substring(0, i); 160 portName = address.substring(i+1); 161 } 162 try { 163 port = Integer.parseInt(portName); 164 } catch (NumberFormatException e) { 165 throw new IOException("Illegal port number in socket address: " + address); 166 } 167 } 168 169 long finishTime = System.currentTimeMillis() + attachTimeout; 170 long sleepTime = 4 * 1000; // milliseconds 171 IOException exception = null; 172 try { 173 do { 174 try { 175 transportSocket = new Socket(hostName, port); 176 break; 177 } catch (IOException e) { 178 Thread.sleep(sleepTime); 179 } 180 } while (attachTimeout == 0 || System.currentTimeMillis() < finishTime); 181 } catch (InterruptedException e) { 182 throw new InterruptedIOException("Interruption in attaching to " + address); 183 } 184 185 if (transportSocket == null) { 186 if (exception != null) { 187 throw exception; 188 } else { 189 throw new SocketTimeoutException("Timeout exceeded in attaching to " + address); 190 } 191 } 192 193 createStreams(); 194 handshake(handshakeTimeout); 195 } 196 197 /** 198 * Closes transport connection. 199 */ 200 @Override close()201 public void close() throws IOException { 202 if (input != null) { 203 input.close(); 204 } 205 if (output != null) { 206 output.close(); 207 } 208 209 if (transportSocket != null && input == null && output == null && !transportSocket.isClosed()) { 210 transportSocket.close(); 211 } 212 if (serverSocket != null) { 213 serverSocket.close(); 214 } 215 } 216 217 /** 218 * Checks if transport connection is open. 219 * 220 * @return true if transport connection is open 221 */ 222 @Override isOpen()223 public boolean isOpen() { 224 return (transportSocket != null 225 && transportSocket.isConnected() 226 && !transportSocket.isClosed()); 227 } 228 229 /** 230 * Reads packet bytes from transport connection. 231 * 232 * @return packet as byte array or null or empty packet if connection was closed 233 */ 234 @Override readPacket()235 public byte[] readPacket() throws IOException { 236 237 // read packet header 238 byte[] header = new byte[Packet.HEADER_SIZE]; 239 int off = 0; 240 241 while (off < Packet.HEADER_SIZE) { 242 try { 243 int bytesRead = input.read(header, off, Packet.HEADER_SIZE - off); 244 if (bytesRead < 0) { 245 break; 246 } 247 off += bytesRead; 248 } catch (IOException e) { 249 // workaround for "Socket Closed" exception if connection was closed 250 break; 251 } 252 } 253 254 if (off == 0) { 255 return null; 256 } 257 if (off < Packet.HEADER_SIZE) { 258 throw new IOException("Connection closed in reading packet header"); 259 } 260 261 // extract packet length 262 int len = Packet.getPacketLength(header); 263 if (len < Packet.HEADER_SIZE) { 264 throw new IOException("Wrong packet size detected: " + len); 265 } 266 267 // allocate packet bytes and store header there 268 byte[] bytes = new byte[len]; 269 System.arraycopy(header, 0, bytes, 0, Packet.HEADER_SIZE); 270 271 // read packet data 272 while (off < len) { 273 int bytesRead = input.read(bytes, off, len - off); 274 if (bytesRead < 0) { 275 break; 276 } 277 off += bytesRead; 278 } 279 if (off < len) { 280 throw new IOException("Connection closed in reading packet data"); 281 } 282 283 return bytes; 284 } 285 286 /** 287 * Writes packet bytes to transport connection. 288 * 289 * @param packet 290 * packet as byte array 291 */ 292 @Override writePacket(byte[] packet)293 public void writePacket(byte[] packet) throws IOException { 294 output.write(packet); 295 output.flush(); 296 } 297 298 /** 299 * Performs handshaking for given timeout. 300 * 301 * @param handshakeTimeout timeout for handshaking in milliseconds 302 */ handshake(long handshakeTimeout)303 protected void handshake(long handshakeTimeout) throws IOException { 304 transportSocket.setSoTimeout((int) handshakeTimeout); 305 306 try { 307 output.write(HANDSHAKE_STRING.getBytes()); 308 output.flush(); 309 int len = HANDSHAKE_STRING.length(); 310 byte[] bytes = new byte[len]; 311 int off = 0; 312 while (off < len) { 313 int bytesRead = input.read(bytes, off, len - off); 314 if (bytesRead < 0) { 315 break; 316 } 317 off += bytesRead; 318 } 319 String response = new String(bytes, 0, off); 320 if (!response.equals(HANDSHAKE_STRING)) { 321 throw new IOException("Unexpected handshake response: " + response); 322 } 323 } finally { 324 transportSocket.setSoTimeout(0); 325 } 326 } 327 328 /** 329 * Creates input/output streams for connection socket. 330 */ createStreams()331 protected void createStreams() throws IOException { 332 input = transportSocket.getInputStream(); 333 output = transportSocket.getOutputStream(); 334 } 335 } 336