diff --git a/com.microsoft.java.debug.core/pom.xml b/com.microsoft.java.debug.core/pom.xml
index 275da47c4..e42117c09 100644
--- a/com.microsoft.java.debug.core/pom.xml
+++ b/com.microsoft.java.debug.core/pom.xml
@@ -62,7 +62,7 @@
commons-io
commons-io
- 2.11.0
+ 2.14.0
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java
index 3551ab2e6..2b2a8c661 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java
@@ -25,7 +25,6 @@
import org.eclipse.jdi.internal.VirtualMachineImpl;
import org.eclipse.jdi.internal.VirtualMachineManagerImpl;
import org.eclipse.jdi.internal.connect.SocketLaunchingConnectorImpl;
-import org.eclipse.jdi.internal.connect.SocketListeningConnectorImpl;
import com.microsoft.java.debug.core.DebugUtility;
import com.microsoft.java.debug.core.LaunchException;
@@ -82,7 +81,7 @@ public VirtualMachine launch(Map connectionArgs)
// do nothing.
}
- SocketListeningConnectorImpl listenConnector = new SocketListeningConnectorImpl(
+ LocalhostSocketListeningConnector listenConnector = new LocalhostSocketListeningConnector(
virtualMachineManager());
Map args = listenConnector.defaultArguments();
((Connector.IntegerArgument) args.get("timeout")).setValue(ACCEPT_TIMEOUT); //$NON-NLS-1$
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedListeningConnector.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedListeningConnector.java
new file mode 100644
index 000000000..2b31c53f4
--- /dev/null
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedListeningConnector.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2017-2021 Microsoft Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Microsoft Corporation - initial API and implementation
+*******************************************************************************/
+
+package com.microsoft.java.debug.plugin.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Map;
+
+import org.eclipse.jdi.internal.VirtualMachineManagerImpl;
+import org.eclipse.jdi.internal.connect.ConnectMessages;
+import org.eclipse.jdi.internal.connect.SocketListeningConnectorImpl;
+import org.eclipse.jdi.internal.connect.SocketTransportImpl;
+
+import com.sun.jdi.VirtualMachine;
+import com.sun.jdi.connect.Connector;
+import com.sun.jdi.connect.IllegalConnectorArgumentsException;
+import com.sun.jdi.connect.ListeningConnector;
+
+/**
+ * An advanced launching connector that supports cwd and enviroment variables.
+ *
+ */
+public class AdvancedListeningConnector extends SocketListeningConnectorImpl implements ListeningConnector {
+ private static final int ACCEPT_TIMEOUT = 10 * 1000;
+
+ public AdvancedListeningConnector(VirtualMachineManagerImpl virtualMachineManager) {
+ super(virtualMachineManager);
+
+ // Create communication protocol specific transport.
+ this.fTransport = new LocalhostSocketTransport();
+ }
+
+ @Override
+ public String name() {
+ return "com.microsoft.java.debug.AdvancedListeningConnector";
+ }
+
+ /**
+ * Retrieves connection port.
+ */
+ private int getConnectionPort(Map connectionArgs) throws IllegalConnectorArgumentsException {
+ String attribute = "port"; //$NON-NLS-1$
+ try {
+ // If listening port is not specified, use port 0
+ IntegerArgument argument = (IntegerArgument) connectionArgs
+ .get(attribute);
+ if (argument != null && argument.value() != null) {
+ return argument.intValue();
+ } else {
+ return 0;
+ }
+ } catch (ClassCastException e) {
+ throw new IllegalConnectorArgumentsException(
+ ConnectMessages.SocketListeningConnectorImpl_Connection_argument_is_not_of_the_right_type_6,
+ attribute);
+ } catch (NullPointerException e) {
+ throw new IllegalConnectorArgumentsException(
+ ConnectMessages.SocketListeningConnectorImpl_Necessary_connection_argument_is_null_7,
+ attribute);
+ } catch (NumberFormatException e) {
+ throw new IllegalConnectorArgumentsException(
+ ConnectMessages.SocketListeningConnectorImpl_Connection_argument_is_not_a_number_8,
+ attribute);
+ }
+ }
+
+ /**
+ * Listens for one or more connections initiated by target VMs.
+ *
+ * @return Returns the address at which the connector is listening for a
+ * connection.
+ */
+ @Override
+ public String startListening(Map connectionArgs) throws IOException, IllegalConnectorArgumentsException {
+ int port = getConnectionPort(connectionArgs);
+ String result = null;
+ try {
+ result = ((LocalhostSocketTransport) fTransport).startListening(port);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalConnectorArgumentsException(
+ ConnectMessages.SocketListeningConnectorImpl_ListeningConnector_Socket_Port,
+ "port"); //$NON-NLS-1$
+ }
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see com.sun.jdi.connect.ListeningConnector#stopListening(java.util.Map)
+ */
+ @Override
+ public void stopListening(Map connectionArgs) throws IOException {
+ ((LocalhostSocketTransport) fTransport).stopListening();
+ }
+
+ /**
+ * Waits for a target VM to attach to this connector.
+ *
+ * @return Returns a connected Virtual Machine.
+ */
+ @Override
+ public VirtualMachine accept(Map connectionArgs) throws IOException, IllegalConnectorArgumentsException {
+ LocalhostSocketConnection connection = (LocalhostSocketConnection) ((LocalhostSocketTransport) fTransport)
+ .accept(ACCEPT_TIMEOUT, 0);
+ return establishedConnection(connection);
+ }
+}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedVirtualMachineManager.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedVirtualMachineManager.java
index a77bdda62..fae24b85e 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedVirtualMachineManager.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedVirtualMachineManager.java
@@ -21,6 +21,7 @@
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.LaunchingConnector;
+import com.sun.jdi.connect.ListeningConnector;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
@@ -46,6 +47,14 @@ public List launchingConnectors() {
return connectors;
}
+ @Override
+ public List listeningConnectors() {
+ List connectors = new ArrayList<>();
+ connectors.add(new AdvancedListeningConnector(this));
+ connectors.addAll(super.listeningConnectors());
+ return connectors;
+ }
+
@Override
public void update(DebugSettings oldSettings, DebugSettings newSettings) {
int currentTimeout = getGlobalRequestTimeout();
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java
index 32ab195b7..12c2955c3 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java
@@ -12,8 +12,10 @@
package com.microsoft.java.debug.plugin.internal;
import java.io.IOException;
+import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
+import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
@@ -33,8 +35,33 @@ public class JavaDebugServer implements IDebugServer {
private ExecutorService executor = null;
private JavaDebugServer() {
+ int port = 0;
+ InetAddress bindAddr = null;
+ String serverAddress = System.getProperty("com.microsoft.java.debug.serverAddress");
+ if (serverAddress != null) {
+ int portIndex = serverAddress.lastIndexOf(':');
+ if (portIndex == -1) {
+ logger.log(Level.SEVERE, String.format("Malformed server address \"%s\": missing port", serverAddress));
+ return;
+ }
+ try {
+ port = Integer.parseInt(serverAddress.substring(portIndex + 1));
+ } catch (NumberFormatException e) {
+ logger.log(Level.SEVERE, String.format("Malformed server address \"%s\": %s", serverAddress, e.toString()), e);
+ return;
+ }
+
+ if (portIndex > 0) {
+ try {
+ bindAddr = InetAddress.getByName(serverAddress.substring(0, portIndex));
+ } catch (UnknownHostException e) {
+ logger.log(Level.SEVERE, String.format("Invalid server address \"%s\": %s", serverAddress, e.toString()), e);
+ return;
+ }
+ }
+ }
try {
- this.serverSocket = new ServerSocket(0, 1);
+ this.serverSocket = new ServerSocket(port, 1, bindAddr);
} catch (IOException e) {
logger.log(Level.SEVERE, String.format("Failed to create Java Debug Server: %s", e.toString()), e);
}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketConnection.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketConnection.java
new file mode 100644
index 000000000..d1a1b5b49
--- /dev/null
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketConnection.java
@@ -0,0 +1,156 @@
+/**
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Google Inc - add support for accepting multiple connections
+ *******************************************************************************/
+package com.microsoft.java.debug.plugin.internal;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+import org.eclipse.jdi.internal.connect.SocketConnection;
+
+import com.sun.jdi.connect.spi.ClosedConnectionException;
+import com.sun.jdi.connect.spi.Connection;
+
+public class LocalhostSocketConnection extends Connection {
+ // for attaching connector
+ private Socket fSocket;
+
+ private InputStream fInput;
+
+ private OutputStream fOutput;
+
+ LocalhostSocketConnection(Socket socket, InputStream in, OutputStream out) {
+ fSocket = socket;
+ fInput = in;
+ fOutput = out;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sun.jdi.connect.spi.Connection#close()
+ */
+ @Override
+ public synchronized void close() throws IOException {
+ if (fSocket == null)
+ return;
+
+ fSocket.close();
+ fSocket = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sun.jdi.connect.spi.Connection#isOpen()
+ */
+ @Override
+ public synchronized boolean isOpen() {
+ return fSocket != null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sun.jdi.connect.spi.Connection#readPacket()
+ */
+ @Override
+ public byte[] readPacket() throws IOException {
+ DataInputStream stream;
+ synchronized (this) {
+ if (!isOpen()) {
+ throw new ClosedConnectionException();
+ }
+ stream = new DataInputStream(fInput);
+ }
+ synchronized (stream) {
+ int packetLength = 0;
+ try {
+ packetLength = stream.readInt();
+ } catch (IOException e) {
+ throw new ClosedConnectionException();
+ }
+
+ if (packetLength < 11) {
+ throw new IOException("JDWP Packet under 11 bytes"); //$NON-NLS-1$
+ }
+
+ byte[] packet = new byte[packetLength];
+ packet[0] = (byte) ((packetLength >>> 24) & 0xFF);
+ packet[1] = (byte) ((packetLength >>> 16) & 0xFF);
+ packet[2] = (byte) ((packetLength >>> 8) & 0xFF);
+ packet[3] = (byte) ((packetLength >>> 0) & 0xFF);
+
+ stream.readFully(packet, 4, packetLength - 4);
+ return packet;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sun.jdi.connect.spi.Connection#writePacket(byte[])
+ */
+ @Override
+ public void writePacket(byte[] packet) throws IOException {
+ if (!isOpen()) {
+ throw new ClosedConnectionException();
+ }
+ if (packet == null) {
+ throw new IllegalArgumentException(
+ "Invalid JDWP Packet, packet cannot be null"); //$NON-NLS-1$
+ }
+ if (packet.length < 11) {
+ throw new IllegalArgumentException(
+ "Invalid JDWP Packet, must be at least 11 bytes. PacketSize:" + packet.length); //$NON-NLS-1$
+ }
+
+ int packetSize = getPacketLength(packet);
+ if (packetSize < 11) {
+ throw new IllegalArgumentException(
+ "Invalid JDWP Packet, must be at least 11 bytes. PacketSize:" + packetSize); //$NON-NLS-1$
+ }
+
+ if (packetSize > packet.length) {
+ throw new IllegalArgumentException(
+ "Invalid JDWP packet: Specified length is greater than actual length"); //$NON-NLS-1$
+ }
+
+ OutputStream stream = null;
+ synchronized (this) {
+ if (!isOpen()) {
+ throw new ClosedConnectionException();
+ }
+ stream = fOutput;
+ }
+
+ synchronized (stream) {
+ // packet.length can be > packetSize. Sending too much will cause
+ // errors on the other side
+ stream.write(packet, 0, packetSize);
+ }
+ }
+
+ private int getPacketLength(byte[] packet) {
+ int len = 0;
+ if (packet.length >= 4) {
+ len = (((packet[0] & 0xFF) << 24) + ((packet[1] & 0xFF) << 16)
+ + ((packet[2] & 0xFF) << 8) + ((packet[3] & 0xFF) << 0));
+ }
+ return len;
+ }
+}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketListeningConnector.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketListeningConnector.java
new file mode 100644
index 000000000..e117f8201
--- /dev/null
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketListeningConnector.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Ivan Popov - Bug 184211: JDI connectors throw NullPointerException if used separately
+ * from Eclipse
+ * Google Inc - add support for accepting multiple connections
+ *******************************************************************************/
+
+package com.microsoft.java.debug.plugin.internal;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdi.internal.VirtualMachineManagerImpl;
+import org.eclipse.jdi.internal.connect.SocketListeningConnectorImpl;
+import org.eclipse.jdi.internal.connect.ConnectMessages;
+
+import com.sun.jdi.VirtualMachine;
+import com.sun.jdi.connect.Connector;
+import com.sun.jdi.connect.IllegalConnectorArgumentsException;
+import com.sun.jdi.connect.ListeningConnector;
+
+public class LocalhostSocketListeningConnector extends SocketListeningConnectorImpl implements ListeningConnector {
+ public LocalhostSocketListeningConnector(
+ VirtualMachineManagerImpl virtualMachineManager) {
+ super(virtualMachineManager);
+
+ // Create communication protocol specific transport.
+ this.fTransport = new LocalhostSocketTransport();
+ }
+
+ /**
+ * Retrieves connection port.
+ */
+ private int getConnectionPort(Map connectionArgs) throws IllegalConnectorArgumentsException {
+ String attribute = "port"; //$NON-NLS-1$
+ try {
+ // If listening port is not specified, use port 0
+ IntegerArgument argument = (IntegerArgument) connectionArgs
+ .get(attribute);
+ if (argument != null && argument.value() != null) {
+ return argument.intValue();
+ } else {
+ return 0;
+ }
+ } catch (ClassCastException e) {
+ throw new IllegalConnectorArgumentsException(
+ ConnectMessages.SocketListeningConnectorImpl_Connection_argument_is_not_of_the_right_type_6,
+ attribute);
+ } catch (NullPointerException e) {
+ throw new IllegalConnectorArgumentsException(
+ ConnectMessages.SocketListeningConnectorImpl_Necessary_connection_argument_is_null_7,
+ attribute);
+ } catch (NumberFormatException e) {
+ throw new IllegalConnectorArgumentsException(
+ ConnectMessages.SocketListeningConnectorImpl_Connection_argument_is_not_a_number_8,
+ attribute);
+ }
+ }
+
+ /**
+ * Listens for one or more connections initiated by target VMs.
+ *
+ * @return Returns the address at which the connector is listening for a
+ * connection.
+ */
+ @Override
+ public String startListening(Map connectionArgs) throws IOException, IllegalConnectorArgumentsException {
+ int port = getConnectionPort(connectionArgs);
+ String result = null;
+ try {
+ result = ((LocalhostSocketTransport) fTransport).startListening(port);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalConnectorArgumentsException(
+ ConnectMessages.SocketListeningConnectorImpl_ListeningConnector_Socket_Port,
+ "port"); //$NON-NLS-1$
+ }
+ return result;
+ }
+}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketTransport.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketTransport.java
new file mode 100644
index 000000000..79864612c
--- /dev/null
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketTransport.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package com.microsoft.java.debug.plugin.internal;
+
+import java.io.IOException;
+
+import org.eclipse.jdi.internal.connect.SocketTransportService;
+
+import com.sun.jdi.connect.Transport;
+import com.sun.jdi.connect.spi.Connection;
+import com.sun.jdi.connect.spi.TransportService.ListenKey;
+
+public class LocalhostSocketTransport implements Transport {
+ public static final String TRANSPORT_NAME = "dt_socket"; //$NON-NLS-1$
+ public static final int MIN_PORTNR = 0;
+ public static final int MAX_PORTNR = 65535;
+
+ LocalhostSocketTransportService service;
+ private ListenKey fListenKey;
+
+ /**
+ * Constructs new LocalhostSocketTransport.
+ */
+ public LocalhostSocketTransport() {
+ service = new LocalhostSocketTransportService();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sun.jdi.connect.Transport#name()
+ */
+ @Override
+ public String name() {
+ return TRANSPORT_NAME;
+ }
+
+ public Connection attach(String hostname, int port, long attachTimeout,
+ long handshakeTimeout) throws IOException {
+ return service.attach(hostname, port, attachTimeout, handshakeTimeout);
+ }
+
+ public String startListening(int port) throws IOException {
+ fListenKey = service.startListening("localhost:" + port); //$NON-NLS-1$
+ return fListenKey.address();
+ }
+
+ public void stopListening() throws IOException {
+ service.stopListening(fListenKey);
+ }
+
+ public Connection accept(long attachTimeout, long handshakeTimeout)
+ throws IOException {
+ return service.accept(fListenKey, attachTimeout, handshakeTimeout);
+ }
+
+}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketTransportService.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketTransportService.java
new file mode 100644
index 000000000..73c212324
--- /dev/null
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/LocalhostSocketTransportService.java
@@ -0,0 +1,326 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Ivan Popov - Bug 184211: JDI connectors throw NullPointerException if used separately
+ * from Eclipse
+ * Google Inc - add support for accepting multiple connections
+ *******************************************************************************/
+package com.microsoft.java.debug.plugin.internal;
+
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.util.Arrays;
+
+import org.eclipse.jdi.TimeoutException;
+import org.eclipse.jdi.internal.connect.ConnectMessages;
+
+import com.sun.jdi.connect.TransportTimeoutException;
+import com.sun.jdi.connect.spi.ClosedConnectionException;
+import com.sun.jdi.connect.spi.Connection;
+import com.sun.jdi.connect.spi.TransportService;
+
+public class LocalhostSocketTransportService extends TransportService {
+ /** Handshake bytes used just after connecting VM. */
+ private static final byte[] handshakeBytes = "JDWP-Handshake".getBytes(); //$NON-NLS-1$
+
+ private Capabilities fCapabilities = new Capabilities() {
+ @Override
+ public boolean supportsAcceptTimeout() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsAttachTimeout() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsHandshakeTimeout() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsMultipleConnections() {
+ return false;
+ }
+ };
+
+ private static class SocketListenKey extends ListenKey {
+ private String fAddress;
+
+ SocketListenKey(String address) {
+ fAddress = address;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sun.jdi.connect.spi.TransportService.ListenKey#address()
+ */
+ @Override
+ public String address() {
+ return fAddress;
+ }
+ }
+
+ // for listening or accepting connectors
+ private ServerSocket fServerSocket;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.sun.jdi.connect.spi.TransportService#accept(com.sun.jdi.connect.spi
+ * .TransportService.ListenKey, long, long)
+ */
+ @Override
+ public Connection accept(ListenKey listenKey, long attachTimeout,
+ long handshakeTimeout) throws IOException {
+ if (attachTimeout > 0) {
+ if (attachTimeout > Integer.MAX_VALUE) {
+ attachTimeout = Integer.MAX_VALUE; // approx 25 days!
+ }
+ fServerSocket.setSoTimeout((int) attachTimeout);
+ }
+ Socket socket;
+ try {
+ socket = fServerSocket.accept();
+ } catch (SocketTimeoutException e) {
+ throw new TransportTimeoutException();
+ }
+ InputStream input = socket.getInputStream();
+ OutputStream output = socket.getOutputStream();
+ performHandshake(input, output, handshakeTimeout);
+ return new LocalhostSocketConnection(socket, input, output);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sun.jdi.connect.spi.TransportService#attach(java.lang.String,
+ * long, long)
+ */
+ @Override
+ public Connection attach(String address, long attachTimeout,
+ long handshakeTimeout) throws IOException {
+ String[] strings = address.split(":"); //$NON-NLS-1$
+ String host = "localhost"; //$NON-NLS-1$
+ int port = 0;
+ if (strings.length == 2) {
+ host = strings[0];
+ port = Integer.parseInt(strings[1]);
+ } else {
+ port = Integer.parseInt(strings[0]);
+ }
+
+ return attach(host, port, attachTimeout, handshakeTimeout);
+ }
+
+ public Connection attach(final String host, final int port,
+ long attachTimeout, final long handshakeTimeout) throws IOException {
+ if (attachTimeout > 0) {
+ if (attachTimeout > Integer.MAX_VALUE) {
+ attachTimeout = Integer.MAX_VALUE; // approx 25 days!
+ }
+ }
+
+ final IOException[] ex = new IOException[1];
+ final LocalhostSocketConnection[] result = new LocalhostSocketConnection[1];
+ Thread attachThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Socket socket = new Socket(host, port);
+ InputStream input = socket.getInputStream();
+ OutputStream output = socket.getOutputStream();
+ performHandshake(input, output, handshakeTimeout);
+ result[0] = new LocalhostSocketConnection(socket, input, output);
+ } catch (IOException e) {
+ ex[0] = e;
+ }
+ }
+ }, ConnectMessages.SocketTransportService_0);
+ attachThread.setDaemon(true);
+ attachThread.start();
+ try {
+ attachThread.join(attachTimeout);
+ if (attachThread.isAlive()) {
+ attachThread.interrupt();
+ throw new TimeoutException();
+ }
+ } catch (InterruptedException e) {
+ }
+
+ if (ex[0] != null) {
+ throw ex[0];
+ }
+
+ return result[0];
+ }
+
+ void performHandshake(final InputStream in, final OutputStream out,
+ final long timeout) throws IOException {
+ final IOException[] ex = new IOException[1];
+ final boolean[] handshakeCompleted = new boolean[1];
+
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ writeHandshake(out);
+ readHandshake(in);
+ handshakeCompleted[0] = true;
+ } catch (IOException e) {
+ ex[0] = e;
+ }
+ }
+ }, ConnectMessages.SocketTransportService_1);
+ t.setDaemon(true);
+ t.start();
+ try {
+ t.join(timeout);
+ } catch (InterruptedException e1) {
+ }
+
+ if (handshakeCompleted[0]) {
+ return;
+ }
+
+ try {
+ in.close();
+ out.close();
+ } catch (IOException e) {
+ }
+
+ if (ex[0] != null) {
+ throw ex[0];
+ }
+
+ throw new TransportTimeoutException();
+ }
+
+ private void readHandshake(InputStream input) throws IOException {
+ try {
+ DataInputStream in = new DataInputStream(input);
+ byte[] handshakeInput = new byte[handshakeBytes.length];
+ in.readFully(handshakeInput);
+ if (!Arrays.equals(handshakeInput, handshakeBytes)) {
+ throw new IOException("Received invalid handshake"); //$NON-NLS-1$
+ }
+ } catch (EOFException e) {
+ throw new ClosedConnectionException();
+ }
+ }
+
+ private void writeHandshake(OutputStream out) throws IOException {
+ out.write(handshakeBytes);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sun.jdi.connect.spi.TransportService#capabilities()
+ */
+ @Override
+ public Capabilities capabilities() {
+ return fCapabilities;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sun.jdi.connect.spi.TransportService#description()
+ */
+ @Override
+ public String description() {
+ return "org.eclipse.jdt.debug: Socket Implementation of TransportService"; //$NON-NLS-1$
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sun.jdi.connect.spi.TransportService#name()
+ */
+ @Override
+ public String name() {
+ return "org.eclipse.jdt.debug_SocketTransportService"; //$NON-NLS-1$
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sun.jdi.connect.spi.TransportService#startListening()
+ */
+ @Override
+ public ListenKey startListening() throws IOException {
+ // not used by jdt debug.
+ return startListening(null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.sun.jdi.connect.spi.TransportService#startListening(java.lang.String)
+ */
+ @Override
+ public ListenKey startListening(String address) throws IOException {
+ String host = "localhost";
+ InetAddress bindAddr = InetAddress.getLocalHost();
+ int port = -1;
+ if (address != null) {
+ // jdt debugger will always specify an address in
+ // the form localhost:port
+ String[] strings = address.split(":"); //$NON-NLS-1$
+ if (strings.length == 2) {
+ host = strings[0];
+ bindAddr = InetAddress.getByName(host);
+ port = Integer.parseInt(strings[1]);
+ } else {
+ port = Integer.parseInt(strings[0]);
+ }
+ }
+ if (port == -1) {
+ throw new IOException("Unable to decode port from address: " + address); //$NON-NLS-1$
+ }
+
+ fServerSocket = new ServerSocket(port, 50, bindAddr);
+ port = fServerSocket.getLocalPort();
+ ListenKey listenKey = new SocketListenKey(host + ":" + port); //$NON-NLS-1$
+ return listenKey;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.sun.jdi.connect.spi.TransportService#stopListening(com.sun.jdi.connect
+ * .spi.TransportService.ListenKey)
+ */
+ @Override
+ public void stopListening(ListenKey arg1) throws IOException {
+ if (fServerSocket != null) {
+ try {
+ fServerSocket.close();
+ } catch (IOException e) {
+ }
+ }
+ fServerSocket = null;
+ }
+}