package com.oracle.truffle.tools.chromeinspector.client;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import com.oracle.truffle.tools.chromeinspector.instrument.Token;
import org.graalvm.polyglot.io.MessageEndpoint;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.tools.utils.java_websocket.client.WebSocketClient;
import com.oracle.truffle.tools.utils.java_websocket.handshake.ServerHandshake;
import com.oracle.truffle.tools.chromeinspector.InspectorExecutionContext;
import com.oracle.truffle.tools.chromeinspector.instrument.InspectorWSConnection;
import com.oracle.truffle.tools.chromeinspector.instrument.KeyStoreOptions;
import com.oracle.truffle.tools.chromeinspector.server.ConnectionWatcher;
import com.oracle.truffle.tools.chromeinspector.server.InspectServerSession;
public class InspectWSClient extends WebSocketClient implements InspectorWSConnection {
private final String host;
private final int port;
private final InspectorExecutionContext executionContext;
private final boolean debugBreak;
private final ConnectionWatcher connectionWatcher;
private InspectServerSession iss;
private static URI getURI(InetSocketAddress isa, String wsspath) {
try {
return new URI("ws://" + isa.getHostString() + ":" + isa.getPort() + wsspath);
} catch (URISyntaxException ex) {
throw new IllegalStateException(ex);
}
}
public InspectWSClient(InetSocketAddress isa, String wsspath, InspectorExecutionContext executionContext, boolean debugBreak, boolean secure, KeyStoreOptions keyStoreOptions,
ConnectionWatcher connectionWatcher, PrintWriter info) throws IOException {
super(getURI(isa, wsspath));
this.host = isa.getHostString();
this.port = isa.getPort();
this.executionContext = executionContext;
this.debugBreak = debugBreak;
this.connectionWatcher = connectionWatcher;
if (secure) {
if (TruffleOptions.AOT) {
throw new IOException("Secure connection is not available in the native-image yet.");
} else {
setSocketFactory(createSecureSocketFactory(keyStoreOptions));
}
}
try {
boolean success = connectBlocking();
if (!success) {
info.println("Could not attach to " + host + ":" + port);
info.flush();
}
} catch (InterruptedException ex) {
throw new IOException("Interrupted " + ex.getLocalizedMessage());
}
}
private static SSLSocketFactory createSecureSocketFactory(KeyStoreOptions keyStoreOptions) throws IOException {
String keyStoreFile = keyStoreOptions.getKeyStore();
if (keyStoreFile != null) {
try {
String filePasswordProperty = keyStoreOptions.getKeyStorePassword();
char[] filePassword = filePasswordProperty == null ? "".toCharArray() : filePasswordProperty.toCharArray();
String keystoreType = keyStoreOptions.getKeyStoreType();
if (keystoreType == null) {
keystoreType = KeyStore.getDefaultType();
}
KeyStore keystore = KeyStore.getInstance(keystoreType);
File keyFile = new File(keyStoreFile);
try (FileInputStream keyIn = new FileInputStream(keyFile)) {
keystore.load(keyIn, filePassword);
}
String keyRecoverPasswordProperty = keyStoreOptions.getKeyPassword();
char[] keyRecoverPassword = keyRecoverPasswordProperty == null ? filePassword : keyRecoverPasswordProperty.toCharArray();
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore, keyRecoverPassword);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keystore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
} catch (KeyStoreException | KeyManagementException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException ex) {
throw new IOException(ex);
}
} else {
throw new IOException("Use options to specify the keystore");
}
}
@Override
public int getPort() {
return port;
}
@Override
public void onOpen(ServerHandshake sh) {
executionContext.logMessage("CLIENT ws connection opened at ", getURI());
iss = InspectServerSession.create(executionContext, debugBreak, connectionWatcher);
connectionWatcher.notifyOpen();
iss.open(new MessageEndpoint() {
@Override
public void sendText(String message) {
executionContext.logMessage("SERVER: ", message);
send(message);
}
@Override
public void sendBinary(ByteBuffer data) throws IOException {
throw new UnsupportedOperationException("Binary messages are not supported.");
}
@Override
public void sendPing(ByteBuffer data) throws IOException {
}
@Override
public void sendPong(ByteBuffer data) throws IOException {
}
@Override
public void sendClose() throws IOException {
close();
}
});
}
@Override
public void onMessage(String message) {
executionContext.logMessage("CLIENT: ", message);
try {
iss.sendText(message);
} catch (IOException e) {
executionContext.logException(e);
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
executionContext.logMessage("SERVER closed ", reason);
connectionWatcher.notifyClosing();
if (!executionContext.canRun()) {
executionContext.doRunIfWaitingForDebugger();
}
}
@Override
public void onError(Exception excptn) {
executionContext.logException("SERVER error ", excptn);
}
@Override
public void consoleAPICall(Token token, String type, Object text) {
iss.consoleAPICall(type, text);
}
@Override
public void close(Token token) {
close();
}
}