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; + } +}