WindowsNamedPipeBridge: Convert to Kotlin

This commit is contained in:
Sapphire
2026-03-13 17:00:11 -05:00
parent 10426cbe78
commit 2230f78d7b
4 changed files with 337 additions and 364 deletions

View File

@@ -1,7 +0,0 @@
package dev.slimevr.desktop.platform.windows;
public enum PipeState {
CREATED,
OPEN,
ERROR
}

View File

@@ -1,333 +0,0 @@
package dev.slimevr.desktop.platform.windows;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.InvalidProtocolBufferException;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.*;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.W32APIOptions;
import dev.slimevr.VRServer;
import dev.slimevr.bridge.BridgeThread;
import dev.slimevr.desktop.platform.ProtobufMessages.ProtobufMessage;
import dev.slimevr.desktop.platform.SteamVRBridge;
import dev.slimevr.tracking.trackers.Tracker;
import io.eiren.util.ann.ThreadSafe;
import io.eiren.util.logging.LogManager;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.List;
interface Kernel32IO extends Kernel32 {
Kernel32IO INSTANCE = Native.load("kernel32", Kernel32IO.class, W32APIOptions.DEFAULT_OPTIONS);
boolean GetOverlappedResult(
/* [in] */ HANDLE hFile,
/* [in] */ WinBase.OVERLAPPED lpOverlapped,
/* [out] */ IntByReference lpNumberOfBytesTransferred,
/* [in] */ boolean bWait
);
}
public class WindowsNamedPipeBridge extends SteamVRBridge {
private static final Kernel32 k32 = Kernel32.INSTANCE;
private static final Kernel32IO k32io = Kernel32IO.INSTANCE;
private static final Advapi32 adv32 = Advapi32.INSTANCE;
protected final String pipeName;
protected final String bridgeSettingsKey;
private final byte[] buffArray = new byte[2048];
protected WindowsPipe pipe;
protected WinNT.HANDLE openEvent = k32.CreateEvent(null, false, false, null);
protected WinNT.HANDLE readEvent = k32.CreateEvent(null, false, false, null);
protected WinNT.HANDLE writeEvent = k32.CreateEvent(null, false, false, null);
protected WinNT.HANDLE rxEvent = k32.CreateEvent(null, false, false, null);
protected WinNT.HANDLE txEvent = k32.CreateEvent(null, false, false, null);
protected WinNT.HANDLE[] events = new WinNT.HANDLE[] { rxEvent, txEvent };
private final WinBase.OVERLAPPED overlappedOpen = new WinBase.OVERLAPPED();
private final WinBase.OVERLAPPED overlappedWrite = new WinBase.OVERLAPPED();
private final WinBase.OVERLAPPED overlappedRead = new WinBase.OVERLAPPED();
private final WinBase.OVERLAPPED overlappedWait = new WinBase.OVERLAPPED();
private final IntByReference bytesWritten = new IntByReference(0);
private final IntByReference bytesAvailable = new IntByReference(0);
private final IntByReference bytesRead = new IntByReference(0);
private boolean pendingWait = false;
public WindowsNamedPipeBridge(
VRServer server,
String bridgeSettingsKey,
String bridgeName,
String pipeName,
List<Tracker> shareableTrackers
) {
super(server, "Named pipe thread", bridgeName, bridgeSettingsKey, shareableTrackers);
this.pipeName = pipeName;
this.bridgeSettingsKey = bridgeSettingsKey;
overlappedWait.hEvent = rxEvent;
}
@Override
@BridgeThread
public void run() {
try {
createPipe();
while (true) {
boolean pipesUpdated = false;
if (pipe.state == PipeState.CREATED) {
// Report that our pipe is disconnected right now
reportDisconnected();
tryOpeningPipe(pipe);
}
if (pipe.state == PipeState.OPEN) {
pipesUpdated = updatePipe();
if (pipesUpdated) {
reportConnected();
}
updateMessageQueue();
}
if (pipe.state == PipeState.ERROR) {
resetPipe();
}
if (!pipesUpdated) {
if (pipe.state == PipeState.OPEN) {
waitForData(10);
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
@ThreadSafe
protected void signalSend() {
k32.SetEvent(txEvent);
}
@BridgeThread
private void waitForData(int timeoutMs) {
if (pipe.state != PipeState.OPEN)
return;
if (!pendingWait) {
k32.ReadFile(pipe.pipeHandle, null, 0, null, overlappedWait);
pendingWait = true;
}
int evIdx = k32.WaitForMultipleObjects(events.length, events, false, timeoutMs);
if (evIdx == 0) {
// events[0] == overlappedWait.hEvent == rxEvent
pendingWait = false;
}
}
@Override
@BridgeThread
protected boolean sendMessageReal(ProtobufMessage message) {
if (pipe.state != PipeState.OPEN) {
return false;
}
try {
int size = message.getSerializedSize();
CodedOutputStream os = CodedOutputStream.newInstance(buffArray, 4, size);
message.writeTo(os);
size += 4;
buffArray[0] = (byte) (size & 0xFF);
buffArray[1] = (byte) ((size >> 8) & 0xFF);
buffArray[2] = (byte) ((size >> 16) & 0xFF);
buffArray[3] = (byte) ((size >> 24) & 0xFF);
overlappedWrite.clear();
overlappedWrite.hEvent = writeEvent;
boolean immediate = k32
.WriteFile(pipe.pipeHandle, buffArray, size, null, overlappedWrite);
int err = k32.GetLastError();
if (!immediate && err != WinError.ERROR_IO_PENDING) {
setPipeError("WriteFile failed: " + err);
return false;
}
if (!k32io.GetOverlappedResult(pipe.pipeHandle, overlappedWrite, bytesWritten, true)) {
setPipeError(
"sendMessageReal/GetOverlappedResult failed: " + k32.GetLastError()
);
return false;
}
if (bytesWritten.getValue() != size) {
setPipeError("Bytes written " + bytesWritten.getValue() + ", expected " + size);
return false;
}
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
private boolean updatePipe() throws IOException {
if (pipe.state != PipeState.OPEN) {
return false;
}
boolean readAnything = false;
while (k32.PeekNamedPipe(pipe.pipeHandle, buffArray, 4, null, bytesAvailable, null)) {
if (bytesAvailable.getValue() < 4) {
return readAnything; // Wait for more data
}
int messageLength = (Byte.toUnsignedInt(buffArray[3]) << 24)
| (Byte.toUnsignedInt(buffArray[2]) << 16)
| (Byte.toUnsignedInt(buffArray[1]) << 8)
| Byte.toUnsignedInt(buffArray[0]);
if (messageLength > 1024) { // Overflow
setPipeError("Pipe overflow. Message length: " + messageLength);
return readAnything;
}
if (bytesAvailable.getValue() < messageLength) {
return readAnything; // Wait for more data
}
overlappedRead.clear();
overlappedRead.hEvent = readEvent;
boolean immediate = k32
.ReadFile(pipe.pipeHandle, buffArray, messageLength, null, overlappedRead);
int err = k32.GetLastError();
if (!immediate && err != WinError.ERROR_IO_PENDING) {
setPipeError("ReadFile failed: " + err);
return readAnything;
}
if (!k32io.GetOverlappedResult(pipe.pipeHandle, overlappedRead, bytesRead, true)) {
setPipeError(
"updatePipe/GetOverlappedResult failed: " + k32.GetLastError()
);
return readAnything;
}
if (bytesRead.getValue() != messageLength) {
setPipeError(
"Bytes read " + bytesRead.getValue() + ", expected " + messageLength
);
return readAnything;
}
try {
ProtobufMessage message = ProtobufMessage
.parser()
.parseFrom(buffArray, 4, messageLength - 4);
messageReceived(message);
readAnything = true;
} catch (InvalidProtocolBufferException parseEx) {
parseEx.printStackTrace();
setPipeError("Failed to parse message: " + parseEx.getMessage());
return readAnything;
}
}
int err = k32.GetLastError();
if (err == WinError.ERROR_BROKEN_PIPE) {
setPipeError("Pipe closed");
} else {
setPipeError("Pipe error: " + err);
}
return readAnything;
}
private void setPipeError(String message) {
pipe.state = PipeState.ERROR;
LogManager.severe("[" + bridgeName + "] " + message);
}
private void resetPipe() {
WindowsPipe.safeDisconnect(pipe);
pipe.state = PipeState.CREATED;
VRServer.Companion.getInstance().queueTask(this::disconnected);
}
private void createPipe() throws IOException {
try {
WinNT.SECURITY_DESCRIPTOR descriptor = new WinNT.SECURITY_DESCRIPTOR(64 * 1024);
adv32.InitializeSecurityDescriptor(descriptor, WinNT.SECURITY_DESCRIPTOR_REVISION);
adv32.SetSecurityDescriptorDacl(descriptor, true, null, false);
adv32
.SetSecurityDescriptorControl(
descriptor,
(short) WinNT.SE_DACL_PROTECTED,
(short) WinNT.SE_DACL_PROTECTED
);
WinBase.SECURITY_ATTRIBUTES attributes = new WinBase.SECURITY_ATTRIBUTES();
attributes.lpSecurityDescriptor = descriptor.getPointer();
attributes.bInheritHandle = false;
pipe = new WindowsPipe(
k32
.CreateNamedPipe(
pipeName,
WinBase.PIPE_ACCESS_DUPLEX | WinNT.FILE_FLAG_OVERLAPPED, // dwOpenMode
WinBase.PIPE_TYPE_BYTE | WinBase.PIPE_READMODE_BYTE | WinBase.PIPE_WAIT, // dwPipeMode
1, // nMaxInstances,
1024 * 16, // nOutBufferSize,
1024 * 16, // nInBufferSize,
0, // nDefaultTimeOut,
attributes // lpSecurityAttributes
),
pipeName
);
LogManager.info("[" + bridgeName + "] Pipe " + pipe.name + " created");
if (WinBase.INVALID_HANDLE_VALUE.equals(pipe.pipeHandle)) {
throw new IOException("Can't open " + pipeName + " pipe: " + k32.GetLastError());
}
LogManager.info("[" + bridgeName + "] Pipes are created");
} catch (IOException e) {
WindowsPipe.safeDisconnect(pipe);
throw e;
}
}
private boolean tryOpeningPipe(WindowsPipe pipe) {
overlappedOpen.clear();
overlappedOpen.hEvent = openEvent;
boolean ok = k32.ConnectNamedPipe(pipe.pipeHandle, overlappedOpen);
int err = k32.GetLastError();
if (!ok && err != WinError.ERROR_PIPE_CONNECTED) {
if (err != WinError.ERROR_IO_PENDING) {
setPipeError("ConnectNamedPipe failed: " + err);
return false;
}
if (!k32io.GetOverlappedResult(pipe.pipeHandle, overlappedOpen, bytesRead, true)) {
setPipeError(
"tryOpeningPipe/GetOverlappedResult failed: " + k32.GetLastError()
);
return false;
}
}
pipe.state = PipeState.OPEN;
LogManager.info("[" + bridgeName + "] Pipe " + pipe.name + " is open");
VRServer.Companion.getInstance().queueTask(this::reconnected);
return true;
}
@Override
public boolean isConnected() {
return pipe != null && pipe.state == PipeState.OPEN;
}
@NotNull
@Override
public String getBridgeConfigKey() {
return this.bridgeSettingsKey;
}
}

View File

@@ -0,0 +1,337 @@
package dev.slimevr.desktop.platform.windows
import com.google.protobuf.CodedOutputStream
import com.google.protobuf.InvalidProtocolBufferException
import com.sun.jna.Native
import com.sun.jna.platform.win32.Advapi32
import com.sun.jna.platform.win32.Kernel32
import com.sun.jna.platform.win32.WinBase
import com.sun.jna.platform.win32.WinError
import com.sun.jna.platform.win32.WinNT
import com.sun.jna.ptr.IntByReference
import com.sun.jna.win32.W32APIOptions
import dev.slimevr.VRServer
import dev.slimevr.bridge.BridgeThread
import dev.slimevr.desktop.platform.ProtobufMessages.ProtobufMessage
import dev.slimevr.desktop.platform.SteamVRBridge
import dev.slimevr.tracking.trackers.Tracker
import io.eiren.util.ann.ThreadSafe
import io.eiren.util.logging.LogManager
import java.io.IOException
import java.lang.Byte
import kotlin.Boolean
import kotlin.ByteArray
import kotlin.Int
import kotlin.String
import kotlin.Suppress
import kotlin.Throws
import kotlin.arrayOf
internal interface Kernel32IO : Kernel32 {
@Suppress("FunctionName")
fun GetOverlappedResult(
hFile: WinNT.HANDLE?, /* [in] */
lpOverlapped: WinBase.OVERLAPPED?, /* [in] */
lpNumberOfBytesTransferred: IntByReference?, /* [out] */
bWait: Boolean, /* [in] */
): Boolean
companion object {
val INSTANCE: Kernel32IO =
Native.load("kernel32", Kernel32IO::class.java, W32APIOptions.DEFAULT_OPTIONS)
}
}
enum class PipeState {
CREATED,
OPEN,
ERROR,
}
class WindowsPipe(val pipeHandle: WinNT.HANDLE?, val name: String) {
var state: PipeState = PipeState.CREATED
fun safeDisconnect() {
if (pipeHandle != null && pipeHandle != WinBase.INVALID_HANDLE_VALUE) {
Kernel32.INSTANCE.DisconnectNamedPipe(pipeHandle)
}
}
}
class WindowsNamedPipeBridge(
server: VRServer,
bridgeSettingsKey: String,
bridgeName: String,
private val pipeName: String,
shareableTrackers: List<Tracker>,
) : SteamVRBridge(server, "Named pipe thread", bridgeName, bridgeSettingsKey, shareableTrackers) {
lateinit var pipe: WindowsPipe
private val buffArray = ByteArray(2048)
private val openEvent: WinNT.HANDLE? = k32.CreateEvent(null, false, false, null)
private val readEvent: WinNT.HANDLE? = k32.CreateEvent(null, false, false, null)
private val writeEvent: WinNT.HANDLE? = k32.CreateEvent(null, false, false, null)
private val rxEvent: WinNT.HANDLE? = k32.CreateEvent(null, false, false, null)
private val txEvent: WinNT.HANDLE? = k32.CreateEvent(null, false, false, null)
private val events = arrayOf(rxEvent, txEvent)
private val overlappedOpen = WinBase.OVERLAPPED()
private val overlappedWrite = WinBase.OVERLAPPED()
private val overlappedRead = WinBase.OVERLAPPED()
private val overlappedWait = WinBase.OVERLAPPED()
private val bytesWritten = IntByReference(0)
private val bytesAvailable = IntByReference(0)
private val bytesRead = IntByReference(0)
private var pendingWait = false
init {
overlappedWait.hEvent = rxEvent
}
@BridgeThread
override fun run() {
try {
createPipe()
while (true) {
var pipesUpdated = false
if (pipe.state == PipeState.CREATED) {
// Report that our pipe is disconnected right now
reportDisconnected()
tryOpeningPipe(pipe)
}
if (pipe.state == PipeState.OPEN) {
pipesUpdated = updatePipe()
if (pipesUpdated) {
reportConnected()
}
updateMessageQueue()
}
if (pipe.state == PipeState.ERROR) {
resetPipe()
}
if (!pipesUpdated) {
if (pipe.state == PipeState.OPEN) {
waitForData(10)
} else {
try {
Thread.sleep(10)
} catch (_: InterruptedException) {
Thread.currentThread().interrupt()
}
}
}
}
} catch (e: IOException) {
LogManager.severe("[$bridgeName] Exception while running bridge", e)
}
}
@ThreadSafe
override fun signalSend() {
k32.SetEvent(txEvent)
}
@BridgeThread
private fun waitForData(timeoutMs: Int) {
if (pipe.state != PipeState.OPEN) return
if (!pendingWait) {
k32.ReadFile(pipe.pipeHandle, null, 0, null, overlappedWait)
pendingWait = true
}
val evIdx: Int = k32.WaitForMultipleObjects(events.size, events, false, timeoutMs)
if (evIdx == 0) {
// events[0] == overlappedWait.hEvent == rxEvent
pendingWait = false
}
}
@BridgeThread
override fun sendMessageReal(message: ProtobufMessage): Boolean {
if (pipe.state != PipeState.OPEN) return false
try {
var size = message.getSerializedSize()
val os = CodedOutputStream.newInstance(buffArray, 4, size)
message.writeTo(os)
size += 4
buffArray[0] = (size and 0xFF).toByte()
buffArray[1] = ((size shr 8) and 0xFF).toByte()
buffArray[2] = ((size shr 16) and 0xFF).toByte()
buffArray[3] = ((size shr 24) and 0xFF).toByte()
overlappedWrite.clear()
overlappedWrite.hEvent = writeEvent
val immediate: Boolean = k32
.WriteFile(pipe.pipeHandle, buffArray, size, null, overlappedWrite)
val err: Int = k32.GetLastError()
if (!immediate && err != WinError.ERROR_IO_PENDING) {
setPipeError("WriteFile failed: $err")
return false
}
if (!k32io.GetOverlappedResult(pipe.pipeHandle, overlappedWrite, bytesWritten, true)) {
setPipeError("sendMessageReal/GetOverlappedResult failed: ${k32.GetLastError()}")
return false
}
if (bytesWritten.value != size) {
setPipeError("Bytes written ${bytesWritten.value}, expected $size")
return false
}
return true
} catch (e: IOException) {
LogManager.severe("[$bridgeName] Failed to send message", e)
}
return false
}
@Throws(IOException::class)
private fun updatePipe(): Boolean {
if (pipe.state != PipeState.OPEN) {
return false
}
var readAnything = false
while (k32.PeekNamedPipe(pipe.pipeHandle, buffArray, 4, null, bytesAvailable, null)) {
if (bytesAvailable.value < 4) {
return readAnything // Wait for more data
}
val messageLength = (
(Byte.toUnsignedInt(buffArray[3]) shl 24)
or (Byte.toUnsignedInt(buffArray[2]) shl 16)
or (Byte.toUnsignedInt(buffArray[1]) shl 8)
or Byte.toUnsignedInt(buffArray[0])
)
if (messageLength > 1024) { // Overflow
setPipeError("Pipe overflow. Message length: $messageLength")
return readAnything
}
if (bytesAvailable.value < messageLength) {
return readAnything // Wait for more data
}
overlappedRead.clear()
overlappedRead.hEvent = readEvent
val immediate: Boolean = k32
.ReadFile(pipe.pipeHandle, buffArray, messageLength, null, overlappedRead)
val err: Int = k32.GetLastError()
if (!immediate && err != WinError.ERROR_IO_PENDING) {
setPipeError("ReadFile failed: $err")
return readAnything
}
if (!k32io.GetOverlappedResult(pipe.pipeHandle, overlappedRead, bytesRead, true)) {
setPipeError("updatePipe/GetOverlappedResult failed: ${k32.GetLastError()}")
return readAnything
}
if (bytesRead.value != messageLength) {
setPipeError("Bytes read ${bytesRead.value}, expected $messageLength")
return readAnything
}
try {
val message = ProtobufMessage
.parser()
.parseFrom(buffArray, 4, messageLength - 4)
messageReceived(message)
readAnything = true
} catch (e: InvalidProtocolBufferException) {
setPipeError("Failed to parse message: ${e.message}")
e.printStackTrace()
return readAnything
}
}
val err = k32.GetLastError()
if (err == WinError.ERROR_BROKEN_PIPE) {
setPipeError("Pipe closed")
} else {
setPipeError("Pipe error: $err")
}
return readAnything
}
private fun setPipeError(message: String) {
pipe.state = PipeState.ERROR
LogManager.severe("[$bridgeName] $message")
}
private fun resetPipe() {
pipe.safeDisconnect()
pipe.state = PipeState.CREATED
server.queueTask { this.disconnected() }
}
@Throws(IOException::class)
private fun createPipe() {
try {
val descriptor = WinNT.SECURITY_DESCRIPTOR(64 * 1024)
adv32.InitializeSecurityDescriptor(descriptor, WinNT.SECURITY_DESCRIPTOR_REVISION)
adv32.SetSecurityDescriptorDacl(descriptor, true, null, false)
adv32
.SetSecurityDescriptorControl(
descriptor,
WinNT.SE_DACL_PROTECTED.toShort(),
WinNT.SE_DACL_PROTECTED.toShort(),
)
val attributes = WinBase.SECURITY_ATTRIBUTES()
attributes.lpSecurityDescriptor = descriptor.pointer
attributes.bInheritHandle = false
pipe = WindowsPipe(
k32
.CreateNamedPipe(
pipeName,
WinBase.PIPE_ACCESS_DUPLEX or WinNT.FILE_FLAG_OVERLAPPED, // dwOpenMode
WinBase.PIPE_TYPE_BYTE or WinBase.PIPE_READMODE_BYTE or WinBase.PIPE_WAIT, // dwPipeMode
1, // nMaxInstances,
1024 * 16, // nOutBufferSize,
1024 * 16, // nInBufferSize,
0, // nDefaultTimeOut,
attributes, // lpSecurityAttributes
),
pipeName,
)
LogManager.info("[$bridgeName] Pipe ${pipe.name} created")
if (WinBase.INVALID_HANDLE_VALUE == pipe.pipeHandle) {
throw IOException("Can't open $pipeName pipe: ${k32.GetLastError()}")
}
LogManager.info("[$bridgeName] Pipes are created")
} catch (e: IOException) {
pipe.safeDisconnect()
throw e
}
}
private fun tryOpeningPipe(pipe: WindowsPipe): Boolean {
overlappedOpen.clear()
overlappedOpen.hEvent = openEvent
val ok = k32.ConnectNamedPipe(pipe.pipeHandle, overlappedOpen)
val err = k32.GetLastError()
if (!ok && err != WinError.ERROR_PIPE_CONNECTED) {
if (err != WinError.ERROR_IO_PENDING) {
setPipeError("ConnectNamedPipe failed: $err")
return false
}
if (!k32io.GetOverlappedResult(pipe.pipeHandle, overlappedOpen, bytesRead, true)) {
setPipeError("tryOpeningPipe/GetOverlappedResult failed: ${k32.GetLastError()}")
return false
}
}
pipe.state = PipeState.OPEN
LogManager.info("[$bridgeName] Pipe ${pipe.name} is open")
server.queueTask { this.reconnected() }
return true
}
override fun isConnected() = pipe.state == PipeState.OPEN
override fun getBridgeConfigKey() = this.bridgeSettingsKey
companion object {
private val k32: Kernel32 = Kernel32.INSTANCE
private val k32io: Kernel32IO = Kernel32IO.INSTANCE
private val adv32: Advapi32 = Advapi32.INSTANCE
}
}

View File

@@ -1,24 +0,0 @@
package dev.slimevr.desktop.platform.windows;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinNT.HANDLE;
public class WindowsPipe {
public final String name;
public final HANDLE pipeHandle;
public PipeState state = PipeState.CREATED;
public WindowsPipe(HANDLE pipeHandle, String name) {
this.pipeHandle = pipeHandle;
this.name = name;
}
public static void safeDisconnect(WindowsPipe pipe) {
try {
if (pipe != null && pipe.pipeHandle != null)
Kernel32.INSTANCE.DisconnectNamedPipe(pipe.pipeHandle);
} catch (Exception ignored) {}
}
}