blob: 6f2dfacf5b00ca37be38189aa0bed4b53565f320 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1998-2003 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.tools.jdi;
27
28import com.sun.jdi.*;
29import com.sun.jdi.connect.*;
30import com.sun.jdi.connect.spi.*;
31import java.net.*;
32import java.io.*;
33import java.util.Map;
34import java.util.ResourceBundle;
35
36/*
37 * A transport service based on a TCP connection between the
38 * debugger and debugee.
39 */
40
41public class SocketTransportService extends TransportService {
42 private ResourceBundle messages = null;
43
44 /**
45 * The listener returned by startListening encapsulates
46 * the ServerSocket.
47 */
48 static class SocketListenKey extends ListenKey {
49 ServerSocket ss;
50
51 SocketListenKey(ServerSocket ss) {
52 this.ss = ss;
53 }
54
55 ServerSocket socket() {
56 return ss;
57 }
58
59 /*
60 * Returns the string representation of the address that this
61 * listen key represents.
62 */
63 public String address() {
64 InetAddress address = ss.getInetAddress();
65
66 /*
67 * If bound to the wildcard address then use current local
68 * hostname. In the event that we don't know our own hostname
69 * then assume that host supports IPv4 and return something to
70 * represent the loopback address.
71 */
72 if (address.isAnyLocalAddress()) {
73 try {
74 address = InetAddress.getLocalHost();
75 } catch (UnknownHostException uhe) {
76 byte[] loopback = {0x7f,0x00,0x00,0x01};
77 try {
78 address = InetAddress.getByAddress("127.0.0.1", loopback);
79 } catch (UnknownHostException x) {
80 throw new InternalError("unable to get local hostname");
81 }
82 }
83 }
84
85 /*
86 * Now decide if we return a hostname or IP address. Where possible
87 * return a hostname but in the case that we are bound to an
88 * address that isn't registered in the name service then we
89 * return an address.
90 */
91 String result;
92 String hostname = address.getHostName();
93 String hostaddr = address.getHostAddress();
94 if (hostname.equals(hostaddr)) {
95 if (address instanceof Inet6Address) {
96 result = "[" + hostaddr + "]";
97 } else {
98 result = hostaddr;
99 }
100 } else {
101 result = hostname;
102 }
103
104 /*
105 * Finally return "hostname:port", "ipv4-address:port" or
106 * "[ipv6-address]:port".
107 */
108 return result + ":" + ss.getLocalPort();
109 }
110
111 public String toString() {
112 return address();
113 }
114 }
115
116 /**
117 * Handshake with the debuggee
118 */
119 void handshake(Socket s, long timeout) throws IOException {
120 s.setSoTimeout((int)timeout);
121
122 byte[] hello = "JDWP-Handshake".getBytes("UTF-8");
123 s.getOutputStream().write(hello);
124
125 byte[] b = new byte[hello.length];
126 int received = 0;
127 while (received < hello.length) {
128 int n;
129 try {
130 n = s.getInputStream().read(b, received, hello.length-received);
131 } catch (SocketTimeoutException x) {
132 throw new IOException("handshake timeout");
133 }
134 if (n < 0) {
135 s.close();
136 throw new IOException("handshake failed - connection prematurally closed");
137 }
138 received += n;
139 }
140 for (int i=0; i<hello.length; i++) {
141 if (b[i] != hello[i]) {
142 throw new IOException("handshake failed - unrecognized message from target VM");
143 }
144 }
145
146 // disable read timeout
147 s.setSoTimeout(0);
148 }
149
150 /**
151 * No-arg constructor
152 */
153 public SocketTransportService() {
154 }
155
156 /**
157 * The name of this transport service
158 */
159 public String name() {
160 return "Socket";
161 }
162
163 /**
164 * Return localized description of this transport service
165 */
166 public String description() {
167 synchronized (this) {
168 if (messages == null) {
169 messages = ResourceBundle.getBundle("com.sun.tools.jdi.resources.jdi");
170 }
171 }
172 return messages.getString("socket_transportservice.description");
173 }
174
175 /**
176 * Return the capabilities of this transport service
177 */
178 public Capabilities capabilities() {
179 return new SocketTransportServiceCapabilities();
180 }
181
182
183 /**
184 * Attach to the specified address with optional attach and handshake
185 * timeout.
186 */
187 public Connection attach(String address, long attachTimeout, long handshakeTimeout)
188 throws IOException {
189
190 if (address == null) {
191 throw new NullPointerException("address is null");
192 }
193 if (attachTimeout < 0 || handshakeTimeout < 0) {
194 throw new IllegalArgumentException("timeout is negative");
195 }
196
197 int splitIndex = address.indexOf(':');
198 String host;
199 String portStr;
200 if (splitIndex < 0) {
201 host = InetAddress.getLocalHost().getHostName();
202 portStr = address;
203 } else {
204 host = address.substring(0, splitIndex);
205 portStr = address.substring(splitIndex+1);
206 }
207
208 int port;
209 try {
210 port = Integer.decode(portStr).intValue();
211 } catch (NumberFormatException e) {
212 throw new IllegalArgumentException(
213 "unable to parse port number in address");
214 }
215
216
217 // open TCP connection to VM
218
219 InetSocketAddress sa = new InetSocketAddress(host, port);
220 Socket s = new Socket();
221 try {
222 s.connect(sa, (int)attachTimeout);
223 } catch (SocketTimeoutException exc) {
224 try {
225 s.close();
226 } catch (IOException x) { }
227 throw new TransportTimeoutException("timed out trying to establish connection");
228 }
229
230 // handshake with the target VM
231 try {
232 handshake(s, handshakeTimeout);
233 } catch (IOException exc) {
234 try {
235 s.close();
236 } catch (IOException x) { }
237 throw exc;
238 }
239
240 return new SocketConnection(s);
241 }
242
243 /*
244 * Listen on the specified address and port. Return a listener
245 * that encapsulates the ServerSocket.
246 */
247 ListenKey startListening(String localaddress, int port) throws IOException {
248 InetSocketAddress sa;
249 if (localaddress == null) {
250 sa = new InetSocketAddress(port);
251 } else {
252 sa = new InetSocketAddress(localaddress, port);
253 }
254 ServerSocket ss = new ServerSocket();
255 ss.bind(sa);
256 return new SocketListenKey(ss);
257 }
258
259 /**
260 * Listen on the specified address
261 */
262 public ListenKey startListening(String address) throws IOException {
263 // use ephemeral port if address isn't specified.
264 if (address == null || address.length() == 0) {
265 address = "0";
266 }
267
268 int splitIndex = address.indexOf(':');
269 String localaddr = null;
270 if (splitIndex >= 0) {
271 localaddr = address.substring(0, splitIndex);
272 address = address.substring(splitIndex+1);
273 }
274
275 int port;
276 try {
277 port = Integer.decode(address).intValue();
278 } catch (NumberFormatException e) {
279 throw new IllegalArgumentException(
280 "unable to parse port number in address");
281 }
282
283 return startListening(localaddr, port);
284 }
285
286 /**
287 * Listen on the default address
288 */
289 public ListenKey startListening() throws IOException {
290 return startListening(null, 0);
291 }
292
293 /**
294 * Stop the listener
295 */
296 public void stopListening(ListenKey listener) throws IOException {
297 if (!(listener instanceof SocketListenKey)) {
298 throw new IllegalArgumentException("Invalid listener");
299 }
300
301 synchronized (listener) {
302 ServerSocket ss = ((SocketListenKey)listener).socket();
303
304 // if the ServerSocket has been closed it means
305 // the listener is invalid
306 if (ss.isClosed()) {
307 throw new IllegalArgumentException("Invalid listener");
308 }
309 ss.close();
310 }
311 }
312
313 /**
314 * Accept a connection from a debuggee and handshake with it.
315 */
316 public Connection accept(ListenKey listener, long acceptTimeout, long handshakeTimeout) throws IOException {
317 if (acceptTimeout < 0 || handshakeTimeout < 0) {
318 throw new IllegalArgumentException("timeout is negative");
319 }
320 if (!(listener instanceof SocketListenKey)) {
321 throw new IllegalArgumentException("Invalid listener");
322 }
323 ServerSocket ss;
324
325 // obtain the ServerSocket from the listener - if the
326 // socket is closed it means the listener is invalid
327 synchronized (listener) {
328 ss = ((SocketListenKey)listener).socket();
329 if (ss.isClosed()) {
330 throw new IllegalArgumentException("Invalid listener");
331 }
332 }
333
334 // from here onwards it's possible that the ServerSocket
335 // may be closed by a call to stopListening - that's okay
336 // because the ServerSocket methods will throw an
337 // IOException indicating the socket is closed.
338 //
339 // Additionally, it's possible that another thread calls accept
340 // with a different accept timeout - that creates a same race
341 // condition between setting the timeout and calling accept.
342 // As it is such an unlikely scenario (requires both threads
343 // to be using the same listener we've chosen to ignore the issue).
344
345 ss.setSoTimeout((int)acceptTimeout);
346 Socket s;
347 try {
348 s = ss.accept();
349 } catch (SocketTimeoutException x) {
350 throw new TransportTimeoutException("timeout waiting for connection");
351 }
352
353 // handshake here
354 handshake(s, handshakeTimeout);
355
356 return new SocketConnection(s);
357 }
358
359 public String toString() {
360 return name();
361 }
362}
363
364
365/*
366 * The Connection returned by attach and accept is one of these
367 */
368class SocketConnection extends Connection {
369 private Socket socket;
370 private boolean closed = false;
371 private OutputStream socketOutput;
372 private InputStream socketInput;
373 private Object receiveLock = new Object();
374 private Object sendLock = new Object();
375 private Object closeLock = new Object();
376
377 SocketConnection(Socket socket) throws IOException {
378 this.socket = socket;
379 socket.setTcpNoDelay(true);
380 socketInput = socket.getInputStream();
381 socketOutput = socket.getOutputStream();
382 }
383
384 public void close() throws IOException {
385 synchronized (closeLock) {
386 if (closed) {
387 return;
388 }
389 socketOutput.close();
390 socketInput.close();
391 socket.close();
392 closed = true;
393 }
394 }
395
396 public boolean isOpen() {
397 synchronized (closeLock) {
398 return !closed;
399 }
400 }
401
402 public byte[] readPacket() throws IOException {
403 if (!isOpen()) {
404 throw new ClosedConnectionException("connection is closed");
405 }
406 synchronized (receiveLock) {
407 int b1,b2,b3,b4;
408
409 // length
410 try {
411 b1 = socketInput.read();
412 b2 = socketInput.read();
413 b3 = socketInput.read();
414 b4 = socketInput.read();
415 } catch (IOException ioe) {
416 if (!isOpen()) {
417 throw new ClosedConnectionException("connection is closed");
418 } else {
419 throw ioe;
420 }
421 }
422
423 // EOF
424 if (b1<0) {
425 return new byte[0];
426 }
427
428 if (b2<0 || b3<0 || b4<0) {
429 throw new IOException("protocol error - premature EOF");
430 }
431
432 int len = ((b1 << 24) | (b2 << 16) | (b3 << 8) | (b4 << 0));
433
434 if (len < 0) {
435 throw new IOException("protocol error - invalid length");
436 }
437
438 byte b[] = new byte[len];
439 b[0] = (byte)b1;
440 b[1] = (byte)b2;
441 b[2] = (byte)b3;
442 b[3] = (byte)b4;
443
444 int off = 4;
445 len -= off;
446
447 while (len > 0) {
448 int count;
449 try {
450 count = socketInput.read(b, off, len);
451 } catch (IOException ioe) {
452 if (!isOpen()) {
453 throw new ClosedConnectionException("connection is closed");
454 } else {
455 throw ioe;
456 }
457 }
458 if (count < 0) {
459 throw new IOException("protocol error - premature EOF");
460 }
461 len -= count;
462 off += count;
463 }
464
465 return b;
466 }
467 }
468
469 public void writePacket(byte b[]) throws IOException {
470 if (!isOpen()) {
471 throw new ClosedConnectionException("connection is closed");
472 }
473
474 /*
475 * Check the packet size
476 */
477 if (b.length < 11) {
478 throw new IllegalArgumentException("packet is insufficient size");
479 }
480 int b0 = b[0] & 0xff;
481 int b1 = b[1] & 0xff;
482 int b2 = b[2] & 0xff;
483 int b3 = b[3] & 0xff;
484 int len = ((b0 << 24) | (b1 << 16) | (b2 << 8) | (b3 << 0));
485 if (len < 11) {
486 throw new IllegalArgumentException("packet is insufficient size");
487 }
488
489 /*
490 * Check that the byte array contains the complete packet
491 */
492 if (len > b.length) {
493 throw new IllegalArgumentException("length mis-match");
494 }
495
496 synchronized (sendLock) {
497 try {
498 /*
499 * Send the packet (ignoring any bytes that follow
500 * the packet in the byte array).
501 */
502 socketOutput.write(b, 0, len);
503 } catch (IOException ioe) {
504 if (!isOpen()) {
505 throw new ClosedConnectionException("connection is closed");
506 } else {
507 throw ioe;
508 }
509 }
510 }
511 }
512}
513
514
515/*
516 * The capabilities of the socket transport service
517 */
518class SocketTransportServiceCapabilities extends TransportService.Capabilities {
519
520 public boolean supportsMultipleConnections() {
521 return true;
522 }
523
524 public boolean supportsAttachTimeout() {
525 return true;
526 }
527
528 public boolean supportsAcceptTimeout() {
529 return true;
530 }
531
532 public boolean supportsHandshakeTimeout() {
533 return true;
534 }
535
536}