All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.openfin.desktop.win32.NamedPipePortHandler Maven / Gradle / Ivy

There is a newer version: 11.0.2
Show newest version
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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy