• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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