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 */ 26package org.apache.harmony.jpda.tests.framework.jdwp; 27 28 29import java.net.InetAddress; 30import java.net.ServerSocket; 31import java.net.Socket; 32import java.net.SocketTimeoutException; 33import java.io.InputStream; 34import java.io.InterruptedIOException; 35import java.io.OutputStream; 36import java.io.IOException; 37 38import org.apache.harmony.jpda.tests.framework.jdwp.Packet; 39 40/** 41 * This class provides TransportWrapper for row TCP/IP socket connection. 42 * 43 */ 44public 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 address to listen or null for default address 57 * @return string representation of listening address 58 */ 59 public String startListening(String address) throws IOException { 60 String hostName = null; 61 InetAddress hostAddr = null; 62 int port = 0; 63 if (address != null) { 64 String portName = null; 65 int i = address.indexOf(':'); 66 if (i < 0) { 67 portName = address; 68 } else { 69 hostName = address.substring(0, i); 70 portName = address.substring(i+1); 71 } 72 try { 73 port = Integer.parseInt(portName); 74 } catch (NumberFormatException e) { 75 throw new IOException("Illegal port number in socket address: " + address); 76 } 77 } 78 79 if (hostName != null) { 80 hostAddr = InetAddress.getByName(hostName); 81 serverSocket = new ServerSocket(port, 0, hostAddr); 82 } else { 83 serverSocket = new ServerSocket(port); 84 } 85 86 // use as workaround for unspecified behaviour of isAnyLocalAddress() 87 InetAddress iAddress = null; 88 if (hostName != null) { 89 iAddress = serverSocket.getInetAddress(); 90 } else { 91 iAddress = InetAddress.getLocalHost(); 92 } 93 94 address = iAddress.getHostName() + ":" + serverSocket.getLocalPort(); 95 return address; 96 } 97 98 /** 99 * Stops listening for connection on current address. 100 */ 101 public void stopListening() throws IOException { 102 if (serverSocket != null) { 103 serverSocket.close(); 104 } 105 } 106 107 /** 108 * Accepts transport connection for currently listened address and performs handshaking 109 * for specified timeout. 110 * 111 * @param acceptTimeout timeout for accepting in milliseconds 112 * @param handshakeTimeout timeout for handshaking in milliseconds 113 */ 114 public void accept(long acceptTimeout, long handshakeTimeout) throws IOException { 115 synchronized (serverSocket) { 116 serverSocket.setSoTimeout((int) acceptTimeout); 117 try { 118 transportSocket = serverSocket.accept(); 119 } finally { 120 serverSocket.setSoTimeout(0); 121 } 122 } 123 createStreams(); 124 handshake(handshakeTimeout); 125 } 126 127 /** 128 * Attaches transport connection to given address and performs handshaking 129 * for specified timeout. 130 * 131 * @param address address for attaching 132 * @param attachTimeout timeout for attaching in milliseconds 133 * @param handshakeTimeout timeout for handshaking in milliseconds 134 */ 135 public void attach(String address, long attachTimeout, long handshakeTimeout) throws IOException { 136 if (address == null) { 137 throw new IOException("Illegal socket address: " + address); 138 } 139 140 String hostName = null; 141 int port = 0; 142 { 143 String portName = null; 144 int i = address.indexOf(':'); 145 if (i < 0) { 146 throw new IOException("Illegal socket address: " + address); 147 } else { 148 hostName = address.substring(0, i); 149 portName = address.substring(i+1); 150 } 151 try { 152 port = Integer.parseInt(portName); 153 } catch (NumberFormatException e) { 154 throw new IOException("Illegal port number in socket address: " + address); 155 } 156 } 157 158 long finishTime = System.currentTimeMillis() + attachTimeout; 159 long sleepTime = 4 * 1000; // millesecinds 160 IOException exception = null; 161 try { 162 do { 163 try { 164 transportSocket = new Socket(hostName, port); 165 break; 166 } catch (IOException e) { 167 Thread.sleep(sleepTime); 168 } 169 } while (attachTimeout == 0 || System.currentTimeMillis() < finishTime); 170 } catch (InterruptedException e) { 171 throw new InterruptedIOException("Interruption in attaching to " + address); 172 } 173 174 if (transportSocket == null) { 175 if (exception != null) { 176 throw exception; 177 } else { 178 throw new SocketTimeoutException("Timeout exceeded in attaching to " + address); 179 } 180 } 181 182 createStreams(); 183 handshake(handshakeTimeout); 184 } 185 186 /** 187 * Closes transport connection. 188 */ 189 public void close() throws IOException { 190 if (input != null) { 191 input.close(); 192 } 193 if (output != null) { 194 output.close(); 195 } 196 197 if (transportSocket != null && input == null && output == null && !transportSocket.isClosed()) { 198 transportSocket.close(); 199 } 200 if (serverSocket != null) { 201 serverSocket.close(); 202 } 203 } 204 205 /** 206 * Checks if transport connection is open. 207 * 208 * @return true if transport connection is open 209 */ 210 public boolean isOpen() { 211 return (transportSocket != null 212 && transportSocket.isConnected() 213 && !transportSocket.isClosed()); 214 } 215 216 /** 217 * Reads packet bytes from transport connection. 218 * 219 * @return packet as byte array or null or empty packet if connection was closed 220 */ 221 public byte[] readPacket() throws IOException { 222 223 // read packet header 224 byte[] header = new byte[Packet.HEADER_SIZE]; 225 int off = 0; 226 227 while (off < Packet.HEADER_SIZE) { 228 try { 229 int bytesRead = input.read(header, off, Packet.HEADER_SIZE - off); 230 if (bytesRead < 0) { 231 break; 232 } 233 off += bytesRead; 234 } catch (IOException e) { 235 // workaround for "Socket Closed" exception if connection was closed 236 break; 237 } 238 } 239 240 if (off == 0) { 241 return null; 242 } 243 if (off < Packet.HEADER_SIZE) { 244 throw new IOException("Connection closed in reading packet header"); 245 } 246 247 // extract packet length 248 int len = Packet.getPacketLength(header); 249 if (len < Packet.HEADER_SIZE) { 250 throw new IOException("Wrong packet size detected: " + len); 251 } 252 253 // allocate packet bytes and store header there 254 byte[] bytes = new byte[len]; 255 System.arraycopy(header, 0, bytes, 0, Packet.HEADER_SIZE); 256 257 // read packet data 258 while (off < len) { 259 int bytesRead = input.read(bytes, off, len - off); 260 if (bytesRead < 0) { 261 break; 262 } 263 off += bytesRead; 264 } 265 if (off < len) { 266 throw new IOException("Connection closed in reading packet data"); 267 } 268 269 return bytes; 270 } 271 272 /** 273 * Writes packet bytes to transport connection. 274 * 275 * @param packet 276 * packet as byte array 277 */ 278 public void writePacket(byte[] packet) throws IOException { 279 output.write(packet); 280 output.flush(); 281 } 282 283 /** 284 * Performs handshaking for given timeout. 285 * 286 * @param handshakeTimeout timeout for handshaking in milliseconds 287 */ 288 protected void handshake(long handshakeTimeout) throws IOException { 289 transportSocket.setSoTimeout((int) handshakeTimeout); 290 291 try { 292 output.write(HANDSHAKE_STRING.getBytes()); 293 output.flush(); 294 int len = HANDSHAKE_STRING.length(); 295 byte[] bytes = new byte[len]; 296 int off = 0; 297 while (off < len) { 298 int bytesRead = input.read(bytes, off, len - off); 299 if (bytesRead < 0) { 300 break; 301 } 302 off += bytesRead; 303 } 304 String response = new String(bytes, 0, off); 305 if (!response.equals(HANDSHAKE_STRING)) { 306 throw new IOException("Unexpected handshake response: " + response); 307 } 308 } finally { 309 transportSocket.setSoTimeout(0); 310 } 311 } 312 313 /** 314 * Creates input/output streams for connection socket. 315 */ 316 protected void createStreams() throws IOException { 317 input = transportSocket.getInputStream(); 318 output = transportSocket.getOutputStream(); 319 } 320} 321