--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jdi/share/classes/com/sun/tools/jdi/SocketTransportService.java Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,539 @@
+/*
+ * Copyright (c) 1998, 2003, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.tools.jdi;
+
+import com.sun.jdi.*;
+import com.sun.jdi.connect.*;
+import com.sun.jdi.connect.spi.*;
+import java.net.*;
+import java.io.*;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+/*
+ * A transport service based on a TCP connection between the
+ * debugger and debugee.
+ */
+
+public class SocketTransportService extends TransportService {
+ private ResourceBundle messages = null;
+
+ /**
+ * The listener returned by startListening encapsulates
+ * the ServerSocket.
+ */
+ static class SocketListenKey extends ListenKey {
+ ServerSocket ss;
+
+ SocketListenKey(ServerSocket ss) {
+ this.ss = ss;
+ }
+
+ ServerSocket socket() {
+ return ss;
+ }
+
+ /*
+ * Returns the string representation of the address that this
+ * listen key represents.
+ */
+ public String address() {
+ InetAddress address = ss.getInetAddress();
+
+ /*
+ * If bound to the wildcard address then use current local
+ * hostname. In the event that we don't know our own hostname
+ * then assume that host supports IPv4 and return something to
+ * represent the loopback address.
+ */
+ if (address.isAnyLocalAddress()) {
+ try {
+ address = InetAddress.getLocalHost();
+ } catch (UnknownHostException uhe) {
+ byte[] loopback = {0x7f,0x00,0x00,0x01};
+ try {
+ address = InetAddress.getByAddress("127.0.0.1", loopback);
+ } catch (UnknownHostException x) {
+ throw new InternalError("unable to get local hostname");
+ }
+ }
+ }
+
+ /*
+ * Now decide if we return a hostname or IP address. Where possible
+ * return a hostname but in the case that we are bound to an
+ * address that isn't registered in the name service then we
+ * return an address.
+ */
+ String result;
+ String hostname = address.getHostName();
+ String hostaddr = address.getHostAddress();
+ if (hostname.equals(hostaddr)) {
+ if (address instanceof Inet6Address) {
+ result = "[" + hostaddr + "]";
+ } else {
+ result = hostaddr;
+ }
+ } else {
+ result = hostname;
+ }
+
+ /*
+ * Finally return "hostname:port", "ipv4-address:port" or
+ * "[ipv6-address]:port".
+ */
+ return result + ":" + ss.getLocalPort();
+ }
+
+ public String toString() {
+ return address();
+ }
+ }
+
+ /**
+ * Handshake with the debuggee
+ */
+ void handshake(Socket s, long timeout) throws IOException {
+ s.setSoTimeout((int)timeout);
+
+ byte[] hello = "JDWP-Handshake".getBytes("UTF-8");
+ s.getOutputStream().write(hello);
+
+ byte[] b = new byte[hello.length];
+ int received = 0;
+ while (received < hello.length) {
+ int n;
+ try {
+ n = s.getInputStream().read(b, received, hello.length-received);
+ } catch (SocketTimeoutException x) {
+ throw new IOException("handshake timeout");
+ }
+ if (n < 0) {
+ s.close();
+ throw new IOException("handshake failed - connection prematurally closed");
+ }
+ received += n;
+ }
+ for (int i=0; i<hello.length; i++) {
+ if (b[i] != hello[i]) {
+ throw new IOException("handshake failed - unrecognized message from target VM");
+ }
+ }
+
+ // disable read timeout
+ s.setSoTimeout(0);
+ }
+
+ /**
+ * No-arg constructor
+ */
+ public SocketTransportService() {
+ }
+
+ /**
+ * The name of this transport service
+ */
+ public String name() {
+ return "Socket";
+ }
+
+ /**
+ * Return localized description of this transport service
+ */
+ public String description() {
+ synchronized (this) {
+ if (messages == null) {
+ messages = ResourceBundle.getBundle("com.sun.tools.jdi.resources.jdi");
+ }
+ }
+ return messages.getString("socket_transportservice.description");
+ }
+
+ /**
+ * Return the capabilities of this transport service
+ */
+ public Capabilities capabilities() {
+ return new SocketTransportServiceCapabilities();
+ }
+
+
+ /**
+ * Attach to the specified address with optional attach and handshake
+ * timeout.
+ */
+ public Connection attach(String address, long attachTimeout, long handshakeTimeout)
+ throws IOException {
+
+ if (address == null) {
+ throw new NullPointerException("address is null");
+ }
+ if (attachTimeout < 0 || handshakeTimeout < 0) {
+ throw new IllegalArgumentException("timeout is negative");
+ }
+
+ int splitIndex = address.indexOf(':');
+ String host;
+ String portStr;
+ if (splitIndex < 0) {
+ host = "localhost";
+ portStr = address;
+ } else {
+ host = address.substring(0, splitIndex);
+ portStr = address.substring(splitIndex+1);
+ }
+
+ if (host.equals("*")) {
+ host = InetAddress.getLocalHost().getHostName();
+ }
+
+ int port;
+ try {
+ port = Integer.decode(portStr).intValue();
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ "unable to parse port number in address");
+ }
+
+
+ // open TCP connection to VM
+ InetSocketAddress sa = new InetSocketAddress(host, port);
+ Socket s = new Socket();
+ try {
+ s.connect(sa, (int)attachTimeout);
+ } catch (SocketTimeoutException exc) {
+ try {
+ s.close();
+ } catch (IOException x) { }
+ throw new TransportTimeoutException("timed out trying to establish connection");
+ }
+
+ // handshake with the target VM
+ try {
+ handshake(s, handshakeTimeout);
+ } catch (IOException exc) {
+ try {
+ s.close();
+ } catch (IOException x) { }
+ throw exc;
+ }
+
+ return new SocketConnection(s);
+ }
+
+ /*
+ * Listen on the specified address and port. Return a listener
+ * that encapsulates the ServerSocket.
+ */
+ ListenKey startListening(String localaddress, int port) throws IOException {
+ InetSocketAddress sa;
+ if (localaddress == null) {
+ sa = new InetSocketAddress(port);
+ } else {
+ sa = new InetSocketAddress(localaddress, port);
+ }
+ ServerSocket ss = new ServerSocket();
+ ss.bind(sa);
+ return new SocketListenKey(ss);
+ }
+
+ /**
+ * Listen on the specified address
+ */
+ public ListenKey startListening(String address) throws IOException {
+ // use ephemeral port if address isn't specified.
+ if (address == null || address.length() == 0) {
+ address = "0";
+ }
+
+ int splitIndex = address.indexOf(':');
+ String localaddr = null;
+ if (splitIndex >= 0) {
+ localaddr = address.substring(0, splitIndex);
+ address = address.substring(splitIndex+1);
+ }
+
+ int port;
+ try {
+ port = Integer.decode(address).intValue();
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ "unable to parse port number in address");
+ }
+
+ return startListening(localaddr, port);
+ }
+
+ /**
+ * Listen on the default address
+ */
+ public ListenKey startListening() throws IOException {
+ return startListening(null, 0);
+ }
+
+ /**
+ * Stop the listener
+ */
+ public void stopListening(ListenKey listener) throws IOException {
+ if (!(listener instanceof SocketListenKey)) {
+ throw new IllegalArgumentException("Invalid listener");
+ }
+
+ synchronized (listener) {
+ ServerSocket ss = ((SocketListenKey)listener).socket();
+
+ // if the ServerSocket has been closed it means
+ // the listener is invalid
+ if (ss.isClosed()) {
+ throw new IllegalArgumentException("Invalid listener");
+ }
+ ss.close();
+ }
+ }
+
+ /**
+ * Accept a connection from a debuggee and handshake with it.
+ */
+ public Connection accept(ListenKey listener, long acceptTimeout, long handshakeTimeout) throws IOException {
+ if (acceptTimeout < 0 || handshakeTimeout < 0) {
+ throw new IllegalArgumentException("timeout is negative");
+ }
+ if (!(listener instanceof SocketListenKey)) {
+ throw new IllegalArgumentException("Invalid listener");
+ }
+ ServerSocket ss;
+
+ // obtain the ServerSocket from the listener - if the
+ // socket is closed it means the listener is invalid
+ synchronized (listener) {
+ ss = ((SocketListenKey)listener).socket();
+ if (ss.isClosed()) {
+ throw new IllegalArgumentException("Invalid listener");
+ }
+ }
+
+ // from here onwards it's possible that the ServerSocket
+ // may be closed by a call to stopListening - that's okay
+ // because the ServerSocket methods will throw an
+ // IOException indicating the socket is closed.
+ //
+ // Additionally, it's possible that another thread calls accept
+ // with a different accept timeout - that creates a same race
+ // condition between setting the timeout and calling accept.
+ // As it is such an unlikely scenario (requires both threads
+ // to be using the same listener we've chosen to ignore the issue).
+
+ ss.setSoTimeout((int)acceptTimeout);
+ Socket s;
+ try {
+ s = ss.accept();
+ } catch (SocketTimeoutException x) {
+ throw new TransportTimeoutException("timeout waiting for connection");
+ }
+
+ // handshake here
+ handshake(s, handshakeTimeout);
+
+ return new SocketConnection(s);
+ }
+
+ public String toString() {
+ return name();
+ }
+}
+
+
+/*
+ * The Connection returned by attach and accept is one of these
+ */
+class SocketConnection extends Connection {
+ private Socket socket;
+ private boolean closed = false;
+ private OutputStream socketOutput;
+ private InputStream socketInput;
+ private Object receiveLock = new Object();
+ private Object sendLock = new Object();
+ private Object closeLock = new Object();
+
+ SocketConnection(Socket socket) throws IOException {
+ this.socket = socket;
+ socket.setTcpNoDelay(true);
+ socketInput = socket.getInputStream();
+ socketOutput = socket.getOutputStream();
+ }
+
+ public void close() throws IOException {
+ synchronized (closeLock) {
+ if (closed) {
+ return;
+ }
+ socketOutput.close();
+ socketInput.close();
+ socket.close();
+ closed = true;
+ }
+ }
+
+ public boolean isOpen() {
+ synchronized (closeLock) {
+ return !closed;
+ }
+ }
+
+ public byte[] readPacket() throws IOException {
+ if (!isOpen()) {
+ throw new ClosedConnectionException("connection is closed");
+ }
+ synchronized (receiveLock) {
+ int b1,b2,b3,b4;
+
+ // length
+ try {
+ b1 = socketInput.read();
+ b2 = socketInput.read();
+ b3 = socketInput.read();
+ b4 = socketInput.read();
+ } catch (IOException ioe) {
+ if (!isOpen()) {
+ throw new ClosedConnectionException("connection is closed");
+ } else {
+ throw ioe;
+ }
+ }
+
+ // EOF
+ if (b1<0) {
+ return new byte[0];
+ }
+
+ if (b2<0 || b3<0 || b4<0) {
+ throw new IOException("protocol error - premature EOF");
+ }
+
+ int len = ((b1 << 24) | (b2 << 16) | (b3 << 8) | (b4 << 0));
+
+ if (len < 0) {
+ throw new IOException("protocol error - invalid length");
+ }
+
+ byte b[] = new byte[len];
+ b[0] = (byte)b1;
+ b[1] = (byte)b2;
+ b[2] = (byte)b3;
+ b[3] = (byte)b4;
+
+ int off = 4;
+ len -= off;
+
+ while (len > 0) {
+ int count;
+ try {
+ count = socketInput.read(b, off, len);
+ } catch (IOException ioe) {
+ if (!isOpen()) {
+ throw new ClosedConnectionException("connection is closed");
+ } else {
+ throw ioe;
+ }
+ }
+ if (count < 0) {
+ throw new IOException("protocol error - premature EOF");
+ }
+ len -= count;
+ off += count;
+ }
+
+ return b;
+ }
+ }
+
+ public void writePacket(byte b[]) throws IOException {
+ if (!isOpen()) {
+ throw new ClosedConnectionException("connection is closed");
+ }
+
+ /*
+ * Check the packet size
+ */
+ if (b.length < 11) {
+ throw new IllegalArgumentException("packet is insufficient size");
+ }
+ int b0 = b[0] & 0xff;
+ int b1 = b[1] & 0xff;
+ int b2 = b[2] & 0xff;
+ int b3 = b[3] & 0xff;
+ int len = ((b0 << 24) | (b1 << 16) | (b2 << 8) | (b3 << 0));
+ if (len < 11) {
+ throw new IllegalArgumentException("packet is insufficient size");
+ }
+
+ /*
+ * Check that the byte array contains the complete packet
+ */
+ if (len > b.length) {
+ throw new IllegalArgumentException("length mis-match");
+ }
+
+ synchronized (sendLock) {
+ try {
+ /*
+ * Send the packet (ignoring any bytes that follow
+ * the packet in the byte array).
+ */
+ socketOutput.write(b, 0, len);
+ } catch (IOException ioe) {
+ if (!isOpen()) {
+ throw new ClosedConnectionException("connection is closed");
+ } else {
+ throw ioe;
+ }
+ }
+ }
+ }
+}
+
+
+/*
+ * The capabilities of the socket transport service
+ */
+class SocketTransportServiceCapabilities extends TransportService.Capabilities {
+
+ public boolean supportsMultipleConnections() {
+ return true;
+ }
+
+ public boolean supportsAttachTimeout() {
+ return true;
+ }
+
+ public boolean supportsAcceptTimeout() {
+ return true;
+ }
+
+ public boolean supportsHandshakeTimeout() {
+ return true;
+ }
+
+}