com.openfin.desktop.win32.NamedPipePortHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openfin-desktop-java-adapter Show documentation
Show all versions of openfin-desktop-java-adapter Show documentation
The Java API for OpenFin Runtime
package com.openfin.desktop.win32;
import com.openfin.desktop.ActionEvent;
import com.openfin.desktop.EventListener;
import com.openfin.desktop.JsonUtils;
import com.openfin.desktop.PortDiscoveryHandler;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.ptr.IntByReference;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Create named pipe for Runtime port discovery on Windows.
*
* @author wche
* @since 11/15/17
*
*/
public class NamedPipePortHandler implements PortDiscoveryHandler {
private final static Logger logger = LoggerFactory.getLogger(NamedPipePortHandler.class.getName());
private static final long RUNTIME_HELLO_MESSAGE = (long) Math.pow( 2, 16 ) - 1; // UINT16_MAX
private static final int RUNTIME_STRING_MESSAGE = 0;
private static final int UINT32_SIZE = 4; // 4 bytes
private static final int MESSAGE_HEADER_SIZE = 5 * UINT32_SIZE; // 5 * uint32
private String pipeName;
private PipeMessageThread pipeMessageThread;
public NamedPipePortHandler(String pipeName) {
this.pipeName = pipeName;
logger.debug(String.format("Created with %s", pipeName));
}
@Override
public String getEffectivePipeName() {
return this.pipeName;
}
@Override
public void registerEventListener(EventListener listener, int timeout) {
this.pipeMessageThread = new PipeMessageThread(this.pipeName, listener, timeout);
pipeMessageThread.start();
}
@Override
public void removeEventListener(EventListener listener) {
if (this.pipeMessageThread != null) {
this.pipeMessageThread.removeEventListener(listener);
}
}
protected class TimeoutThread extends Thread {
private PipeMessageThread messageThread;
private int timeout;
private volatile boolean interrupted = false;
private TimeoutThread(PipeMessageThread messageThread, int timeout) {
this.messageThread = messageThread;
this.timeout = timeout;
this.setName(NamedPipePortHandler.class.getName() + ".TimeoutThread");
}
@Override
public void run() {
logger.debug("Starting timeout thread");
try {
Thread.sleep(timeout * 1000);
} catch (InterruptedException e) {
}
messageThread.timeout();
logger.debug("exiting");
}
}
protected class PipeMessageThread extends Thread {
private EventListener eventListener;
private TimeoutThread timeoutThread;
private String pipeName, wPipeName;
private WinNT.HANDLE hNamedPipe;
private PipeMessageThread(String pipeName, EventListener listener, int timeout) {
super();
this.pipeName = pipeName;
this.setDaemon(true);
this.setName(NamedPipePortHandler.class.getName() + ".PipeMessageThread");
this.eventListener = listener;
this.timeoutThread = new TimeoutThread(this, timeout);
this.timeoutThread.start();
}
@Override
public void run() {
this.hNamedPipe = createPipe();
try {
if (this.hNamedPipe != null) {
MessageHeader header = new MessageHeader();
readMessageHeader(header);
if (header.messageType == RUNTIME_HELLO_MESSAGE) {
readRuntimeHello();
writeRuntimeHello(header);
readMessageHeader(header);
readRuntimeInfo(header);
} else {
logger.error(String.format("Invalid Runtime Hello message type %d", header.messageType));
}
}
} catch (Exception ex) {
logger.error("Error processing port discovery", ex);
}
finally {
closePipe();
}
}
private WinNT.HANDLE createPipe() {
WinNT.HANDLE pipe = null;
try {
this.wPipeName = String.format("\\\\.\\pipe\\chrome.%s", this.pipeName);
logger.debug(String.format("Creating pipe %s", this.wPipeName));
pipe = Kernel32.INSTANCE.CreateNamedPipe(this.wPipeName,
WinBase.PIPE_ACCESS_DUPLEX, // dwOpenMode
WinBase.PIPE_TYPE_BYTE | WinBase.PIPE_READMODE_BYTE | WinBase.PIPE_WAIT, // dwPipeMode
1, // nMaxInstances,
Byte.MAX_VALUE, // nOutBufferSize,
Byte.MAX_VALUE, // nInBufferSize,
1000, // nDefaultTimeOut,
null); // lpSecurityAttributes
if (Kernel32.INSTANCE.ConnectNamedPipe(pipe, null)) {
logger.debug("Client connected");
} else {
logger.debug(String.format("Error ConnectNamedPipe %d", Kernel32.INSTANCE.GetLastError()));
}
} catch (Exception ex) {
logger.error(String.format("Error creating name pipe %s", this.pipeName), ex);
}
return pipe;
}
private synchronized void closePipe() {
if (this.hNamedPipe != null) {
try {
logger.debug(String.format("Closing named pipe %s", this.wPipeName));
Kernel32.INSTANCE.CloseHandle(this.hNamedPipe);
} catch (Exception ex) {
logger.debug(String.format("Error closing pipe %s", ex.getMessage()));
}
}
this.hNamedPipe = null;
}
private void readMessageHeader(MessageHeader header) throws IOException {
header.payloadSize = readInt();
header.routingId = readInt();
header.messageType = readInt();
header.flags = readInt();
header.attachmentCount = readInt();
logger.debug(String.format("Runtime Header %d %d %d %d %d", header.payloadSize,
header.routingId, header.messageType, header.flags, header.attachmentCount));
}
private void writeRuntimeHello(MessageHeader header) throws IOException{
byte[] writeBuffer = new byte[MESSAGE_HEADER_SIZE + UINT32_SIZE];
ByteBuffer bb = ByteBuffer.wrap(writeBuffer);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.putInt(header.payloadSize);
bb.putInt(header.routingId);
bb.putInt(header.messageType);
bb.putInt(header.flags);
bb.putInt(header.attachmentCount);
bb.putInt(Kernel32.INSTANCE.GetCurrentProcessId());
IntByReference lpNumberOfBytesWrite = new IntByReference(0);
if (Kernel32.INSTANCE.WriteFile(this.hNamedPipe, writeBuffer, writeBuffer.length, lpNumberOfBytesWrite, null)) {
if (writeBuffer.length == lpNumberOfBytesWrite.getValue()) {
if (Kernel32.INSTANCE.FlushFileBuffers(this.hNamedPipe)) {
logger.debug(String.format("Wrote Runtime hello message %d ", Kernel32.INSTANCE.GetCurrentProcessId()));
} else {
throw new IOException(String.format("Error FlushFileBuffers %d", Kernel32.INSTANCE.GetLastError()));
}
logger.debug(String.format("Wrote Runtime hello message %d ", Kernel32.INSTANCE.GetCurrentProcessId()));
} else {
throw new IOException(String.format("Error WriteFile length mismatch %d %d",
writeBuffer.length, lpNumberOfBytesWrite.getValue()));
}
} else {
throw new IOException(String.format("Error WriteFile %d", Kernel32.INSTANCE.GetLastError()));
}
}
private void readRuntimeHello() throws IOException {
int helloPayload = readInt();
logger.debug(String.format("Hello Payload %d", helloPayload)); // supposed to be pid of Runtime
}
private String readRuntimeString() throws Exception {
int strLength = readInt();
logger.debug(String.format("Discovery Message length %d", strLength));
byte[] data = new byte[strLength];
IntByReference lpNumberOfBytesRead = new IntByReference(0);
Kernel32.INSTANCE.ReadFile(hNamedPipe, data, data.length, lpNumberOfBytesRead, null);
int readLength = lpNumberOfBytesRead.getValue();
if (readLength != strLength) {
throw new IOException(String.format("Runtime string length mismatch %d %d", readLength, strLength));
}
String value = new String(data);
logger.debug(String.format("Runtime String %s", value));
return value;
}
private void readRuntimeInfo(MessageHeader header) throws Exception{
if (header.messageType == RUNTIME_STRING_MESSAGE) {
String runtimeMsg = readRuntimeString();
JSONObject jsonObject = new JSONObject(runtimeMsg);
JSONObject payload = JsonUtils.getJsonValue(jsonObject,"payload", null);
if (payload != null) {
ActionEvent actionEvent = new ActionEvent(this.pipeName, payload, this);
if (this.eventListener != null) {
this.eventListener.eventReceived(actionEvent);
}
} else {
logger.error("Missing payload of Runtime info");
}
} else {
logger.error(String.format("Invalid RUNTIME_STRING_MESSAGE %d", header.messageType));
}
}
private int readInt() throws IOException {
byte[] readBuffer = new byte[UINT32_SIZE];
IntByReference lpNumberOfBytesRead = new IntByReference(0);
if (Kernel32.INSTANCE.ReadFile(hNamedPipe, readBuffer, readBuffer.length, lpNumberOfBytesRead, null)) {
if (readBuffer.length == lpNumberOfBytesRead.getValue()) {
ByteBuffer bb = ByteBuffer.wrap(readBuffer);
bb.order(ByteOrder.LITTLE_ENDIAN);
return bb.getInt();
} else {
throw new IOException(String.format("readInt length mismatch %d %d",
readBuffer.length, lpNumberOfBytesRead.getValue()));
}
} else {
throw new IOException(String.format("readInt failed with %d", Kernel32.INSTANCE.GetLastError()));
}
}
private void timeout() {
if (this.hNamedPipe != null) {
this.closePipe();
JSONObject jsonObject = new JSONObject();
ActionEvent actionEvent = new ActionEvent("TIMEOUT", jsonObject, this);
if (this.eventListener != null) {
this.eventListener.eventReceived(actionEvent);
}
}
}
private void removeEventListener(EventListener listener) {
if (this.eventListener == listener) {
this.eventListener = null;
}
}
}
private static class MessageHeader {
int payloadSize;
int routingId;
int messageType;
int flags;
int attachmentCount;
}
}