Home | History | Annotate | Download | only in jdwp
      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
     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
    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
    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
    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
    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
    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
    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
    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      */
    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      */
    331     protected void createStreams() throws IOException {
    332         input = transportSocket.getInputStream();
    333         output = transportSocket.getOutputStream();
    334     }
    335 }
    336