* Only the STM32 variant of the DFU protocol is supported.
* Only binary firmware format is supported (no .hex or .dfu files).
@@ -53,8 +53,8 @@ public static void main(String[] args) {
System.exit(4);
return;
}
- var device = devices.get(0);
- System.out.printf("DFU device found with serial %s.%n", device.serialNumber());
+ var device = devices.getFirst();
+ System.out.printf("DFU device found with serial %s.%n", device.getSerialNumber());
// download and verify firmware
try {
@@ -64,7 +64,8 @@ public static void main(String[] args) {
System.out.println("Firmware successfully downloaded and verified");
device.startApplication();
- System.out.println("DFU mode exited and firmware started");
+ device.waitForDisconnect();
+ System.out.println("DFU mode ended and firmware started");
device.close();
diff --git a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFUDevice.java b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFUDevice.java
index f5b12f88..4678e066 100644
--- a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFUDevice.java
+++ b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFUDevice.java
@@ -11,7 +11,9 @@
import java.util.Arrays;
import java.util.List;
-import java.util.stream.Collectors;
+
+import static net.codecrete.usb.UsbRecipient.INTERFACE;
+import static net.codecrete.usb.UsbRequestType.CLASS;
/**
* DFU device.
@@ -21,22 +23,22 @@
*/
public class DFUDevice {
- private final USBDevice usbDevice_;
- private final int interfaceNumber_;
- private final int transferSize_;
- private final Version dfuVersion_;
+ private final UsbDevice usbDevice;
+ private final int interfaceNumber;
+ private final int transferSize;
+ private final Version dfuVersion;
- private List segments_;
+ private List segments;
/**
* Gets all connected DFU devices
* @return List of DFU devices
*/
public static List getAll() {
- return USB.getAllDevices().stream()
- .filter(DFUDevice::hasDFUDescriptor)
+ return Usb.findDevices(DFUDevice::hasDFUDescriptor)
+ .stream()
.map(DFUDevice::new)
- .collect(Collectors.toList());
+ .toList();
}
/**
@@ -44,8 +46,8 @@ public static List getAll() {
* @param device USB device
* @return {@code true} if it has a DFU descriptor, {@code false} otherwise
*/
- public static boolean hasDFUDescriptor(USBDevice device) {
- return getDFUDescriptorOffset(device.configurationDescriptor()) > 0
+ public static boolean hasDFUDescriptor(UsbDevice device) {
+ return getDFUDescriptorOffset(device.getConfigurationDescriptor()) > 0
&& getDFUInterfaceNumber(device) >= 0;
}
@@ -69,11 +71,11 @@ public static int getDFUDescriptorOffset(byte[] descriptor) {
* @param device the USB device
* @return the interface number, of -1 if not found
*/
- public static int getDFUInterfaceNumber(USBDevice device) {
- for (var intf : device.interfaces()) {
- var alt = intf.alternate();
- if (alt.classCode() == 0xFE && alt.subclassCode() == 0x01 && alt.protocolCode() == 0x02)
- return intf.number();
+ public static int getDFUInterfaceNumber(UsbDevice device) {
+ for (var intf : device.getInterfaces()) {
+ var alt = intf.getCurrentAlternate();
+ if (alt.getClassCode() == 0xFE && alt.getSubclassCode() == 0x01 && alt.getProtocolCode() == 0x02)
+ return intf.getNumber();
}
return -1;
@@ -86,41 +88,41 @@ public static int getDFUInterfaceNumber(USBDevice device) {
*
* @param usbDevice the USB device
*/
- public DFUDevice(USBDevice usbDevice) {
- usbDevice_ = usbDevice;
- interfaceNumber_ = getDFUInterfaceNumber(usbDevice);
+ public DFUDevice(UsbDevice usbDevice) {
+ this.usbDevice = usbDevice;
+ interfaceNumber = getDFUInterfaceNumber(usbDevice);
- var configDesc = usbDevice.configurationDescriptor();
+ var configDesc = usbDevice.getConfigurationDescriptor();
int offset = getDFUDescriptorOffset(configDesc);
assert offset > 0;
- transferSize_ = getInt16(configDesc, offset + 5);
- dfuVersion_ = new Version(getInt16(configDesc, offset + 7));
+ transferSize = getInt16(configDesc, offset + 5);
+ dfuVersion = new Version(getInt16(configDesc, offset + 7));
}
/**
* Gets the DFU protocol version.
* @return the protocol version
*/
- public Version dfuVersion() {
- return dfuVersion_;
+ public Version getDfuVersion() {
+ return dfuVersion;
}
/**
* Gets the device serial number.
* @return the serial number
*/
- public String serialNumber() {
- return usbDevice_.serialNumber();
+ public String getSerialNumber() {
+ return usbDevice.getSerialNumber();
}
/**
* Opens the DFU device for communication.
*/
public void open() {
- usbDevice_.open();
- usbDevice_.claimInterface(interfaceNumber_);
- segments_ = Segment.getSegments(usbDevice_, interfaceNumber_);
+ usbDevice.open();
+ usbDevice.claimInterface(interfaceNumber);
+ segments = Segment.getSegments(usbDevice, interfaceNumber);
clearErrorIfNeeded();
}
@@ -128,23 +130,44 @@ public void open() {
* Closes the DFU device.
*/
public void close() {
- usbDevice_.close();
+ usbDevice.close();
+ }
+
+ /**
+ * Waits until the device disconnects.
+ *
+ * Disconnection is a side effect of leaving DFU mode.
+ *
+ *
+ * If the device does not disconnect after 5 seconds,
+ * an exception will be thrown.
+ *
+ */
+ public void waitForDisconnect() {
+ var waitingTime = 5000;
+ while (waitingTime > 0 && usbDevice.isConnected()) {
+ sleep(100);
+ waitingTime -= 100;
+ }
+
+ if (usbDevice.isConnected())
+ throw new DFUException("Device did not restart (try disconnecting and reconnecting it)");
}
/**
* Clears an error status.
*/
public void clearStatus() {
- var setup = new USBControlTransfer(USBRequestType.CLASS, USBRecipient.INTERFACE, DFURequest.CLEAR_STATUS.value(), 0, interfaceNumber_);
- usbDevice_.controlTransferOut(setup, null);
+ var transfer = createDfuControlTransfer(DFURequest.CLEAR_STATUS, 0);
+ usbDevice.controlTransferOut(transfer, null);
}
/**
* Aborts the download mode.
*/
public void abort() {
- var setup = new USBControlTransfer(USBRequestType.CLASS, USBRecipient.INTERFACE, DFURequest.ABORT.value(), 0, interfaceNumber_);
- usbDevice_.controlTransferOut(setup, null);
+ var transfer = createDfuControlTransfer(DFURequest.ABORT, 0);
+ usbDevice.controlTransferOut(transfer, null);
}
/**
@@ -152,8 +175,11 @@ public void abort() {
* @return the status
*/
public DFUStatus getStatus() {
- var setup = new USBControlTransfer(USBRequestType.CLASS, USBRecipient.INTERFACE, DFURequest.GET_STATUS.value(), 0, interfaceNumber_);
- return DFUStatus.fromBytes(usbDevice_.controlTransferIn(setup, 6));
+ var transfer = createDfuControlTransfer(DFURequest.GET_STATUS, 0);
+ var response = usbDevice.controlTransferIn(transfer, 6);
+ if (response.length != 6)
+ throw new DFUException("Invalid response from GET_STATUS request");
+ return DFUStatus.fromBytes(response);
}
/**
@@ -165,8 +191,7 @@ public DFUStatus getStatus() {
public byte[] read(int address, int length) {
expectState(DeviceState.DFU_IDLE, DeviceState.DFU_DNLOAD_IDLE);
setAddress(address);
- exitDownloadMode();
-
+ exitMode();
expectState(DeviceState.DFU_IDLE, DeviceState.DFU_UPLOAD_IDLE);
var result = new byte[length];
@@ -175,17 +200,15 @@ public byte[] read(int address, int length) {
int offset = 0;
int blockNum = 2;
while (offset < length) {
- int chunkSize = Math.min(transferSize_, length - offset);
- var setup = new USBControlTransfer(USBRequestType.CLASS, USBRecipient.INTERFACE, DFURequest.UPLOAD.value(), blockNum, interfaceNumber_);
- var chunk = usbDevice_.controlTransferIn(setup, chunkSize);
+ int chunkSize = Math.min(transferSize, length - offset);
+ var transfer = createDfuControlTransfer(DFURequest.UPLOAD, blockNum);
+ var chunk = usbDevice.controlTransferIn(transfer, chunkSize);
System.arraycopy(chunk, 0, result, offset, chunkSize);
offset += chunkSize;
blockNum += 1;
}
- // request zero lenght chunk to exit out of upload mode
- var setup = new USBControlTransfer(USBRequestType.CLASS, USBRecipient.INTERFACE, DFURequest.UPLOAD.value(), blockNum, interfaceNumber_);
- usbDevice_.controlTransferIn(setup, 0);
+ exitMode();
return result;
}
@@ -199,13 +222,13 @@ public void verify(byte[] firmware) {
public void download(byte[] firmware) {
int length = firmware.length;
- // validate start and end address exist and are writable
+ // validate that start and end address exist and are writable
int startAddress = STM32.FLASH_BASE_ADDRESS;
var firstPage = getWritablePage(startAddress);
getWritablePage(startAddress + length);
- usbDevice_.selectAlternateSetting(interfaceNumber_, firstPage.segment().altSetting());
- System.out.printf("Target memory segment: %s%n", firstPage.segment().name());
+ usbDevice.selectAlternateSetting(interfaceNumber, firstPage.segment().getAltSetting());
+ System.out.printf("Target memory segment: %s%n", firstPage.segment().getName());
// erase if needed
if (firstPage.isErasable())
@@ -217,14 +240,14 @@ public void download(byte[] firmware) {
int offset = 0;
int transaction = 2;
while (offset < length) {
- int chunkSize = Math.min(length - offset, transferSize_);
+ int chunkSize = Math.min(length - offset, transferSize);
byte[] chunk = new byte[chunkSize];
System.arraycopy(firmware, offset, chunk, 0, chunkSize);
System.out.printf("Writing data at 0x%x (size 0x%x)%n", startAddress + offset, chunkSize);
- var setup = new USBControlTransfer(USBRequestType.CLASS, USBRecipient.INTERFACE, DFURequest.DOWNLOAD.value(), transaction, interfaceNumber_);
- usbDevice_.controlTransferOut(setup, chunk);
+ var transfer = createDfuControlTransfer(DFURequest.DOWNLOAD, transaction);
+ usbDevice.controlTransferOut(transfer, chunk);
finishDownloadCommand("writing data");
@@ -232,7 +255,7 @@ public void download(byte[] firmware) {
transaction += 1;
}
- exitDownloadMode();
+ exitMode();
}
/**
@@ -241,7 +264,7 @@ public void download(byte[] firmware) {
* Only applicable to erasable sector, i.e. flash memory.
*
*
- * Only entire pages can be erase. If start and end address to not fall onto
+ * Only entire pages can be erased. If start and end address to not fall onto
* page boundaries, this method will extend the range to be erased.
*
* @param startAddress the start address of the range
@@ -259,20 +282,20 @@ public void erase(int startAddress, int length) {
System.out.printf("Erasing page at 0x%x (size 0x%x)%n", page.startAddress(), page.pageSize());
erasePage(page.startAddress());
- startAddress = page.endAddress();
+ startAddress = page.getEndAddress();
}
}
public void erasePage(int address) {
- execDownloadCommandWithAddress((byte) 0x41, "erasing page", address);
+ executeSpecialCommand((byte) 0x41, "erasing page", address);
}
public void setAddress(int address) {
- execDownloadCommandWithAddress((byte) 0x21, "setting address", address);
+ executeSpecialCommand((byte) 0x21, "setting address", address);
}
- private void execDownloadCommandWithAddress(byte command, String action, int address) {
- var setup = new USBControlTransfer(USBRequestType.CLASS, USBRecipient.INTERFACE, DFURequest.DOWNLOAD.value(), 0, interfaceNumber_);
+ private void executeSpecialCommand(byte command, String action, int address) {
+ var transfer = createDfuControlTransfer(DFURequest.DOWNLOAD, 0);
var data = new byte[] {
command,
(byte) address,
@@ -280,7 +303,7 @@ private void execDownloadCommandWithAddress(byte command, String action, int add
(byte) (address >> 16),
(byte) (address >> 24)
};
- usbDevice_.controlTransferOut(setup, data);
+ usbDevice.controlTransferOut(transfer, data);
finishDownloadCommand(action);
}
@@ -309,10 +332,10 @@ private Page getWritablePage(int address) {
}
private Page findPage(int address) {
- return Segment.findPage(segments_, address);
+ return Segment.findPage(segments, address);
}
- private void exitDownloadMode() {
+ private void exitMode() {
abort();
var status = getStatus();
@@ -325,8 +348,10 @@ private void exitDownloadMode() {
public void startApplication() {
expectState(DeviceState.DFU_IDLE, DeviceState.DFU_DNLOAD_IDLE);
- var setup = new USBControlTransfer(USBRequestType.CLASS, USBRecipient.INTERFACE, DFURequest.DOWNLOAD.value(), 2, interfaceNumber_);
- usbDevice_.controlTransferOut(setup, null);
+ // By sending a zero-length download packet and querying the status,
+ // the device will leave DFU mode and restart.
+ var transfer = createDfuControlTransfer(DFURequest.DOWNLOAD, 0);
+ usbDevice.controlTransferOut(transfer, null);
var status = getStatus();
if (status.state() != DeviceState.DFU_MANIFEST)
@@ -359,7 +384,13 @@ private static void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
throw new DFUException("Sleep failed", e);
}
}
+
+ private UsbControlTransfer createDfuControlTransfer(DFURequest request, int value) {
+ return new UsbControlTransfer(CLASS, INTERFACE, request.ordinal(), value, interfaceNumber);
+ }
+
}
diff --git a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFURequest.java b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFURequest.java
index 00607e37..c927e105 100644
--- a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFURequest.java
+++ b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFURequest.java
@@ -10,19 +10,47 @@
/**
* DFU request.
*
- * See USB Device Class Specification for Device Firmware Upgrade, version 1.1.
+ * See ST Microelectronics, application note AN3156.
*
*/
public enum DFURequest {
+ /**
+ * Requests the device to leave DFU mode and enter the application.
+ *
+ * The Detach request is not meaningful in the case of the bootloader. The bootloader starts
+ * with a system reset depending on the boot mode configuration settings, which means that
+ * no other application is running at that time.
+ *
+ */
DETACH,
+ /**
+ * Requests data transfer from Host to the device in order to load them
+ * into device internal flash memory. Includes also erase commands.
+ */
DOWNLOAD,
+ /**
+ * Requests data transfer from device to Host in order to load content
+ * of device internal flash memory into a Host file.
+ */
UPLOAD,
+ /**
+ * Requests device to send status report to the Host (including status
+ * resulting from the last request execution and the state the device
+ * enters immediately after this request).
+ */
GET_STATUS,
+ /**
+ * Requests device to clear error status and move to next step.
+ */
CLEAR_STATUS,
+ /**
+ * Requests the device to send only the state it enters immediately
+ * after this request.
+ */
GET_STATE,
- ABORT;
-
- public int value() {
- return ordinal();
- }
+ /**
+ * Requests device to exit the current state/operation and enter idle
+ * state immediately
+ */
+ ABORT
}
diff --git a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFUStatus.java b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFUStatus.java
index 4cd6e967..d6b44b00 100644
--- a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFUStatus.java
+++ b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DFUStatus.java
@@ -13,13 +13,12 @@
* See USB Device Class Specification for Device Firmware Upgrade, version 1.1.
*
*/
-public record DFUStatus(DeviceStatus status, int pollTimeout, DeviceState state, int iString) {
+public record DFUStatus(DeviceStatus status, int pollTimeout, DeviceState state) {
public static DFUStatus fromBytes(byte[] data) {
var status = DeviceStatus.fromValue(data[0]);
var pollTimeout = (data[1] & 0xff) + 256 * (data[2] & 0xff) + 256 * 256 * (data[3] & 0xff);
var state = DeviceState.fromValue(data[4]);
- var iString = data[5] & 0x55;
- return new DFUStatus(status, pollTimeout, state, iString);
+ return new DFUStatus(status, pollTimeout, state);
}
}
diff --git a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DeviceState.java b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DeviceState.java
index 759da304..35532fd9 100644
--- a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DeviceState.java
+++ b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DeviceState.java
@@ -14,31 +14,58 @@
*
*/
public enum DeviceState {
- APP_IDLE, // Device is running its normal application.
- APP_DETACH, // Device is running its normal application, has received the DFU_DETACH request,
- // and is waiting for a USB reset.
- DFU_IDLE, // Device is operating in the DFU mode and is waiting for requests.
- DFU_DNLOAD_SYNC, // Device has received a block and is waiting for the host to solicit the status via DFU_GETSTATUS.
- DFU_DNBUSY, // Device is programming a control-write block into its nonvolatile memories.
- DFU_DNLOAD_IDLE, // Device is processing a download operation. Expecting DFU_DNLOAD requests.
- DFU_MANIFEST_SYNC, // Device has received the final block of firmware from the host and is waiting for receipt of
- // DFU_GETSTATUS to begin the Manifestation phase; or device has completed the Manifestation phase and is
- // waiting for receipt of DFU_GETSTATUS. (Devices that can enter this state after the Manifestation phase
- // set bmAttributes bit bitManifestationTolerant to 1.)
- DFU_MANIFEST, // Device is in the Manifestation phase. (Not all devices will be able to respond to DFU_GETSTATUS
- // when in this state.)
- DFU_MANIFEST_WAIT_RESET, // Device has programmed its memories and is waiting for a USB reset or a power on reset.
- // (Devices that must enter this state clear bitManifestationTolerant to 0.)
- DFU_UPLOAD_IDLE, // The device is processing an upload operation. Expecting DFU_UPLOAD requests.
- DFU_ERROR; // An error has occurred. Awaiting the DFU_CLRSTATUS request.
-
- public byte value() {
- return (byte) ordinal();
- }
-
- private static final DeviceState[] values = values();
+ /**
+ * Device is running its normal application.
+ */
+ APP_IDLE,
+ /**
+ * Device is running its normal application, has received the DFU_DETACH request,
+ * and is waiting for a USB reset.
+ */
+ APP_DETACH,
+ /**
+ * Device is operating in the DFU mode and is waiting for requests.
+ */
+ DFU_IDLE,
+ /**
+ * Device has received a block and is waiting for the host to solicit the status via DFU_GETSTATUS.
+ */
+ DFU_DNLOAD_SYNC,
+ /**
+ * Device is programming a control-write block into its nonvolatile memories.
+ */
+ DFU_DNBUSY,
+ /**
+ * Device is processing a download operation. Expecting DFU_DNLOAD requests.
+ */
+ DFU_DNLOAD_IDLE,
+ /**
+ * Device has received the final block of firmware from the host and is waiting for receipt of
+ * DFU_GETSTATUS to begin the Manifestation phase; or device has completed the Manifestation phase and is
+ * waiting for receipt of DFU_GETSTATUS. (Devices that can enter this state after the Manifestation phase
+ * set bmAttributes bit bitManifestationTolerant to 1.)
+ */
+ DFU_MANIFEST_SYNC,
+ /**
+ * Device is in the Manifestation phase. (Not all devices will be able to respond to DFU_GETSTATUS
+ * when in this state.
+ */
+ DFU_MANIFEST,
+ /**
+ * Device has programmed its memories and is waiting for a USB reset or a power on reset.
+ * (Devices that must enter this state clear bitManifestationTolerant to 0.)
+ */
+ DFU_MANIFEST_WAIT_RESET,
+ /**
+ * The device is processing an upload operation. Expecting DFU_UPLOAD requests.
+ */
+ DFU_UPLOAD_IDLE,
+ /**
+ * An error has occurred. Awaiting the DFU_CLRSTATUS request.
+ */
+ DFU_ERROR;
public static DeviceState fromValue(byte value) {
- return values[value];
+ return values()[value];
}
}
diff --git a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DeviceStatus.java b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DeviceStatus.java
index 9ae30d63..c702112b 100644
--- a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DeviceStatus.java
+++ b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/DeviceStatus.java
@@ -15,30 +15,72 @@
*/
public enum DeviceStatus {
- OK, // No error condition is present
- ERR_TARGET, // File is not targeted for use by this device.
- ERR_FILE, // File is for this device but fails some vendor-specific verification test.
- ERR_WRITE, // Device is unable to write memory.
- ERR_ERASE, // Memory erase function failed.
- ERR_CHECK_ERASED, // Memory erase check failed.
- ERR_PROG, // Program memory function failed.
- ERR_VERIFY, // Programmed memory failed verification.
- ERR_ADDRESS, // Cannot program memory due to received address that is out of range.
- ERR_NOTDONE, // Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet.
- ERR_FIRMWARE, // Device’s firmware is corrupt. It cannot return to run-time (non-DFU) operations.
- ERR_VENDOR, // iString indicates a vendor-specific error.
- ERR_USBR, // Device detected unexpected USB reset signaling.
- ERR_POR, // Device detected unexpected power on reset.
- ERR_UNKNOWN, // Something went wrong, but the device does not know what it was.
- ERR_STALLEDPKT; // Device stalled an unexpected request.
+ /**
+ * No error condition is present
+ */
+ OK,
+ /**
+ * File is not targeted for use by this device.
+ */
+ ERR_TARGET,
+ /**
+ * File is for this device but fails some vendor-specific verification test.
+ */
+ ERR_FILE,
+ /**
+ * Device is unable to write memory.
+ */
+ ERR_WRITE,
+ /**
+ * Memory erase function failed.
+ */
+ ERR_ERASE,
+ /**
+ * Memory erase check failed.
+ */
+ ERR_CHECK_ERASED,
+ /**
+ * Program memory function failed.
+ */
+ ERR_PROG,
+ /**
+ * Programmed memory failed verification.
+ */
+ ERR_VERIFY,
+ /**
+ * Cannot program memory due to received address that is out of range.
+ */
+ ERR_ADDRESS,
+ /**
+ * Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet.
+ */
+ ERR_NOTDONE,
+ /**
+ * Device’s firmware is corrupt. It cannot return to run-time (non-DFU) operations.
+ */
+ ERR_FIRMWARE,
+ /**
+ * iString indicates a vendor-specific error.
+ */
+ ERR_VENDOR,
+ /**
+ * Device detected unexpected USB reset signaling.
+ */
+ ERR_USBR,
+ /**
+ * Device detected unexpected power on reset.
+ */
+ ERR_POR,
+ /**
+ * Something went wrong, but the device does not know what it was.
+ */
+ ERR_UNKNOWN,
+ /**
+ * Device stalled an unexpected request.
+ */
+ ERR_STALLEDPKT;
- public byte value() {
- return (byte) ordinal();
- }
-
- private static final DeviceStatus[] values = values();
-
public static DeviceStatus fromValue(byte value) {
- return values[value];
+ return values()[value];
}
}
diff --git a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/Page.java b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/Page.java
index d7d90b31..92158670 100644
--- a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/Page.java
+++ b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/Page.java
@@ -24,7 +24,7 @@ public record Page(Segment segment, int startAddress, int count, int pageSize, i
* Gets the end address of the page or sector.
* @return the end address
*/
- public int endAddress() {
+ public int getEndAddress() {
return startAddress + count * pageSize;
}
diff --git a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/STM32.java b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/STM32.java
index a157ca55..a9f40b57 100644
--- a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/STM32.java
+++ b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/STM32.java
@@ -11,5 +11,8 @@
* STM32 specific constants
*/
public class STM32 {
+
+ private STM32() { }
+
public static final int FLASH_BASE_ADDRESS = 0x08000000;
}
diff --git a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/Segment.java b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/Segment.java
index eef8d1fc..f3fdad2b 100644
--- a/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/Segment.java
+++ b/examples/stm_dfu/src/main/java/net/codecrete/usb/dfu/Segment.java
@@ -7,32 +7,37 @@
package net.codecrete.usb.dfu;
-import net.codecrete.usb.USBControlTransfer;
-import net.codecrete.usb.USBDevice;
-import net.codecrete.usb.USBRecipient;
-import net.codecrete.usb.USBRequestType;
+import net.codecrete.usb.UsbControlTransfer;
+import net.codecrete.usb.UsbDevice;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
+import java.util.regex.Pattern;
+
+import static net.codecrete.usb.UsbRecipient.DEVICE;
+import static net.codecrete.usb.UsbRequestType.STANDARD;
/**
* Represents a memory segment of the USB device, be it flash memory, RAM or any other type.
*/
public class Segment {
+ private static final Pattern SEGMENT_PATTERN = Pattern.compile("@([^/]+)/0x([0-9A-Fa-f]+)/");
+ private static final Pattern SECTOR_PATTERN = Pattern.compile(",?(\\d+)\\*(\\d+) ?([BKM]?)(.)");
+
/**
- * Derives the segments from the USB interface description.
+ * Decodes the segment information from the USB interface description.
* @param device the USB device
* @param interfaceNumber the number of the DFU USB interface
* @return list of segments
*/
- public static List getSegments(USBDevice device, int interfaceNumber) {
+ public static List getSegments(UsbDevice device, int interfaceNumber) {
var result = new ArrayList();
// STM uses multiple alternate interface settings to represent segments.
- // The alternate interface settings name describes the sectors within the segment.
- var configDesc = device.configurationDescriptor();
+ // The alternate interface setting name describes the sectors within the segment.
+ var configDesc = device.getConfigurationDescriptor();
int offset = 0;
while (offset < configDesc.length) {
if (configDesc[offset + 1] == 4 && (configDesc[offset + 2] & 0xff) == interfaceNumber) {
@@ -53,8 +58,8 @@ public static List getSegments(USBDevice device, int interfaceNumber) {
* @param index the index of the string descriptor
* @return the string
*/
- private static String getStringDescriptor(USBDevice device, int index) {
- var setup = new USBControlTransfer(USBRequestType.STANDARD, USBRecipient.DEVICE, 6, (3 << 8) | index, 0);
+ private static String getStringDescriptor(UsbDevice device, int index) {
+ var setup = new UsbControlTransfer(STANDARD, DEVICE, 6, (3 << 8) | index, 0);
byte[] stringDesc = device.controlTransferIn(setup, 255);
int descLen = stringDesc[0] & 0xff;
return new String(stringDesc, 2, descLen - 2, StandardCharsets.UTF_16LE);
@@ -68,8 +73,8 @@ private static String getStringDescriptor(USBDevice device, int index) {
*/
public static Page findPage(List segments, int address) {
for (var seg : segments) {
- for (var sec : seg.sectors()) {
- if (address >= sec.startAddress() && address < sec.endAddress()) {
+ for (var sec : seg.getSectors()) {
+ if (address >= sec.startAddress() && address < sec.getEndAddress()) {
int offset = address - sec.startAddress();
int pageNum = offset / sec.pageSize();
return new Page(seg, sec.startAddress() + pageNum * sec.pageSize(), 1, sec.pageSize(), sec.attributes());
@@ -80,10 +85,10 @@ public static Page findPage(List segments, int address) {
return null;
}
- private final int altSetting_;
- private final String name_;
+ private final int altSetting;
+ private final String name;
- private final List sectors_;
+ private final List sectors;
/**
@@ -96,58 +101,29 @@ public static Page findPage(List segments, int address) {
*/
private Segment(int altSetting, String segmentDesc) {
// The format is described in "UM0424 STM32 USB-FS-Device development kit", ch. 10.3.2
- altSetting_ = altSetting;
- sectors_ = new ArrayList();
- int offset = segmentDesc.indexOf('/', 1);
- name_ = segmentDesc.substring(1, offset).trim();
-
- int startAddress = 0;
- while (offset < segmentDesc.length()) {
- // parse start address
- if (segmentDesc.charAt(offset) == '/') {
- int addressEnd = segmentDesc.indexOf('/', offset + 1);
- startAddress = (int) Long.parseLong(segmentDesc.substring(offset + 3, addressEnd), 16);
- offset = addressEnd + 1;
- continue;
- }
-
- // skip comma
- if (segmentDesc.charAt(offset) == ',')
- offset += 1;
-
- // parse count
- int countEnd = segmentDesc.indexOf('*', offset);
- int count = Integer.parseInt(segmentDesc.substring(offset, countEnd));
- offset = countEnd + 1;
-
- // parse sector size
- int sizeEnd = offset;
- while (Character.isDigit(segmentDesc.charAt(sizeEnd)))
- sizeEnd += 1;
- int size = Integer.parseInt(segmentDesc.substring(offset, sizeEnd));
- offset = sizeEnd;
-
- // skip whitespace
- while (segmentDesc.charAt(offset) == ' ')
- offset += 1;
-
- // parse unit
- char unitChar = segmentDesc.charAt(offset);
- if (unitChar == 'B') {
- offset += 1;
- } else if (unitChar == 'K') {
+ this.altSetting = altSetting;
+ sectors = new ArrayList<>();
+
+ var match = SEGMENT_PATTERN.matcher(segmentDesc);
+ if (!match.find())
+ throw new DFUException("Invalid segment description: " + segmentDesc);
+ this.name = match.group(1).trim();
+ var startAddress = (int) Long.parseLong(match.group(2), 16);
+
+ match = SECTOR_PATTERN.matcher(segmentDesc.substring(match.end()));
+ while (match.find()) {
+ var count = Integer.parseInt(match.group(1));
+ var size = Integer.parseInt(match.group(2));
+ var multiplier = match.group(3);
+ var attributes = match.group(4).charAt(0) - 0x60;
+
+ if (multiplier.equals("K")) {
size *= 1024;
- offset += 1;
- } else if (unitChar == 'M') {
+ } else if (multiplier.equals("M")) {
size *= 1024 * 1024;
- offset += 1;
}
- // parse sector attributes
- int sectorAttrs = segmentDesc.charAt(offset) - 0x40;
- offset += 1;
-
- sectors_.add(new Page(this, startAddress, count, size, sectorAttrs));
+ sectors.add(new Page(this, startAddress, count, size, attributes));
startAddress += size;
}
}
@@ -156,23 +132,23 @@ private Segment(int altSetting, String segmentDesc) {
* Gets the alternative interface setting number
* @return the setting number
*/
- public int altSetting() {
- return altSetting_;
+ public int getAltSetting() {
+ return altSetting;
}
/**
* Gets the segment name.
* @return the name
*/
- public String name() {
- return name_;
+ public String getName() {
+ return name;
}
/**
* Gets the sectors withing the segment
* @return list of sectors
*/
- public List sectors() {
- return sectors_;
+ public List getSectors() {
+ return sectors;
}
}
diff --git a/java-does-usb/.mvn/wrapper/maven-wrapper.jar b/java-does-usb/.mvn/wrapper/maven-wrapper.jar
deleted file mode 100644
index cb28b0e3..00000000
Binary files a/java-does-usb/.mvn/wrapper/maven-wrapper.jar and /dev/null differ
diff --git a/java-does-usb/.mvn/wrapper/maven-wrapper.properties b/java-does-usb/.mvn/wrapper/maven-wrapper.properties
index ac184013..d58dfb70 100644
--- a/java-does-usb/.mvn/wrapper/maven-wrapper.properties
+++ b/java-does-usb/.mvn/wrapper/maven-wrapper.properties
@@ -14,5 +14,6 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/java-does-usb/jextract/README.md b/java-does-usb/jextract/README.md
index d700e3a8..79d0f270 100644
--- a/java-does-usb/jextract/README.md
+++ b/java-does-usb/jextract/README.md
@@ -1,31 +1,21 @@
# Code Generation with *jextract*
-Some of the binding code for accessing native functions and data structures is generated with [jextract](https://jdk.java.net/jextract/). The tool is still under construction and has its limitations.
+A major part of the binding code for accessing native functions and data structures is generated with [jextract](https://jdk.java.net/jextract/). *jextract* is not bundled with the JDK. The binaries can be downloaded from [jdk.java.net/jextract](https://jdk.java.net/jextract/)
-In order to generate the code, the scripts in the subdirectories have to be run (`linux/gen_linux.sh`, `macos/gen_macos.sh` and `/windowsgen_win.cmd`). Each script has to be run on the particular operating system.
+In order to generate the code, the scripts in the subdirectories have to be run (`linux/gen_linux.sh`, `macos/gen_macos.sh` and `windows/gen_win.cmd`). Each script has to be run on the particular operating system. The scripts expect the *jextract* binary to be in a *jextract* directory at the same parent directory as the *Java Does USB* project. If that is not the case, the *jextract* path can be modified at the top of the scripts.
-The code is generated in directories below `gen`, i.e. `main/java/net/codecrete/usb/linux/gen` and similar for the other operating systems. For each library (`xxx.so` or `xxx.dll`) and each macOS framework, a separate package is created.
+The code is generated in directories below `gen`, i.e. `main/java/net/codecrete/usb/linux/gen` and similarly for the other operating systems. For each library (`xxx.so` or `xxx.dll`) and each macOS framework, a separate package is created.
-The scripts explicitly specify the functions, structs etc. to include as generating code for entire operating system header files can result in an excessive amount of Java source files and classes.
+The scripts explicitly specify the functions, structs etc. to include as generating code for entire operating system header files will result in an excessive amount of Java source files and classes.
-The resulting code is then committed to the source code repository. Before the commit, imports are cleaned up to get rid of superfluous imports. Most IDEs provide a convenient command to execute this on entire directories.
+The resulting code is then committed to the source code repository.
## General limitations
-- The binaries for *jextract* on https://jdk.java.net/jextract/ have not been updated for JDK 21. So it must be built from source. Instructions can be found at [Building & Testing](https://github.com/openjdk/jextract#building--testing).
-
- According to the jextract mailing list, it would be required to create separate code for Intel x64 and ARM64 architecture. And jextract would need to be run on each architecture separately (no cross-compilation). Fortunately, this doesn't seem to be the case. Linux code generated on Intel x64 also runs on ARM64 without change. The same holds for macOS. However, jextract needs to be run on each operating system separately.
-- JDK 20 introduced a new feature for saving the thread-specific error values (`GetLastError()` on Windows, `errno` on Linux). To use it, an additional parameter must be added to function calls. Unfortunately, this is not yet supported by jextract. So a good number of function bindings have to be written manually.
-
-- `typedef` and `struct`:
-
- 1. If only the `typedef` is included (`--include-typedef`), an empty Java class is generated.
- 2. If both *typedef* and the `struct` it refers to are included, the `typedef` class inherits from the `struct` class, which contains all the `struct` members.
- 3. If the `typedef` refers to an unnamed `struct`, the generated class contains all the `struct` members.
-
- Case 1 looks like a bug.
+- The *Foreign Function And Memory* API has the abilitiy to save the thread-specific error values (`GetLastError()` on Windows, `errno` on Linux). This is required as the JVM calls operating system functions as well, which overwrite the result values. To save the values, an additional parameter must be added to function calls. Unfortunately, this is not supported by jextract. So a good number of function bindings have to be written manually.
- *jextract* is not really transparent about what it does. It often skips elements without providing any information. In particular, it will silently skip a requested element in these cases:
@@ -36,6 +26,7 @@ The resulting code is then committed to the source code repository. Before the c
- `--include-typedef mystruct` if `mystruct` is actually a `struct`.
- `--include-typedef mytypedef` if `mytypedef` is a `typedef` for a primitive type.
+- *jextract* resolves all _typedef_s to their actual types. So this library does not use any _--include-typedef_ option. And there does not seem any obvious use for it beyond cosmetics.
## Linux
@@ -48,66 +39,44 @@ sudo apt-get install libudev-dev
On Linux, the limitations are:
-- `usbdevice_fs.h`: The macro `USBDEVFS_CONTROL` and all similar ones are not generated. They are probably considered function-like macros. *jextract* does not generate code for function-like macros. But `USBDEVFS_CONTROL` evaluates to a constant.
+- `usbdevice_fs.h`: The macro `USBDEVFS_CONTROL` and all similar ones are not generated. They are probably considered function-like macros. *jextract* does not generate code for function-like macros. `USBDEVFS_CONTROL` would evaluate to a constant.
- `sd-device.h` (header file for *libsystemd*): *jextract* fails with *"Error: /usr/include/inttypes.h:290:8: error: unknown type name 'intmax_t'"*. The reason is yet unknown. This code is currently not needed as *libudev* is used instead of *libsystemd*. They are related, *libsystemd* is the future solution, but it is missing support for monitoring devices.
-- `libudev.h`: After code generation, the class `RuntimeHelper.java` in `.../linux/gen/udev` must be manually modified as the code to access the library does not work for the directory the library is located in. So replace:
-
-```
-System.loadLibrary("udev");
-SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
-```
-
-with:
-
-```
-SymbolLookup loaderLookup = SymbolLookup.libraryLookup("libudev.so", MemorySession.openImplicit());
-```
## MacOS
Most of the required native functions on macOS are part of a framework. Frameworks internally have a more complex file organization of header and binary files than appears from the outside. Thus, they require a special logic to locate framework header files. *clang* supports it with the `-F`. *jextract* allows to specify the options via `compiler_flags.txt` file. Since the file must be in the local directory and since it does not apply to Linux and Windows, separate directories must be used for the operating systems.
-The generated code has the same problem as the Linux code for *udev*. It must be manually changed to use `SymbolLookup.libraryLookup()` for the libraries `CoreFoundation.framework/CoreFoundation` and `IOKit.framework/IOKit` respectively.
## Windows
-Most Windows SDK header files are not independent. They require that `Windows.h` is included first. So instead of specifying the target header files directly, a helper header file (`windows_headers.h` in this directory) is specified.
-
-Compared to Linux and macOS, the code generation on Windows is very slow (about 1 min vs 3 seconds). And jextract crashes sometimes.
-
-The known limitations are:
+The Windows code is not generated with _jextract_ but with [Windows API Generator](https://github.com/manuelbl/WindowsApiGenerator)
+instead. It is run as a Maven plugin. The generated code is not committed to GitHub.
-- Variable size `struct`: Several Windows struct are of variable size. The last member is an array. The `struct` definition specifies array length 1. But you are expected to allocate more space depending on the actual array size you need. *jextract* generates code for array length 1 and checks the length when the members are accessed. So the generated code is difficult to use. Variable size `struct`s are a pain - in any language.
-
-- GUID constants like `GUID_DEVINTERFACE_USB_DEVICE` do not work. While code is generated, the code fails at run-time as it is unable to locate the symbol. This is due to the fact that `GUID_DEVINTERFACE_USB_DEVICE` actually resolve to a variable definition and not to a variable declaration. The GUID constant is not contained in any library; instead the header files use linkage options to generate the constant in the callers code, which does not work with FFM. Such constants should be skipped by *jextract*.
-
-- *jextract* is a batch script and turns off *echo mode*. If a single batch scripts has multiple calls of *jextract*, two things need to be considered:
-
- - If the regular command interpreter `cmd.exe` is used, *jextract* must be called using `call`, i.e. `call jextract header.h`.
- - If *PowerShell* is used instead, `call` is not needed but *PowerShell* must be configured to allow the execution of scripts.
- - *jextract* turns off *echo mode*. So the first call will behave differently than the following calls.
+Windows API Generator supports call state capturing (`GetLastError()`), structs with a
+variable size, GUID and device property key (`DEVPKEY`) constants etc.
## Code Size
*jextract* generates a comprehensive set of methods for each function, struct, struct member etc. Most of it will not be used as a typical application just uses a subset of struct members, might only read or write them etc. So a considerable amount of code is generated. For some types, it's a bit excessive.
-The worst example is [`IOUSBInterfaceStruct190`](https://github.com/manuelbl/JavaDoesUSB/blob/main/java-does-usb/src/main/java/net/codecrete/usb/macos/gen/iokit/IOUSBInterfaceStruct190.java) (macOS). This is a `struct` consisting of about 50 member functions. It's basically a vtable of a C++ class. For this single `struct`, *jextract* generates codes resulting in 70 class files with a total size of 227kByte..
+The worst example is [`IOUSBInterfaceStruct190`](https://github.com/manuelbl/JavaDoesUSB/blob/main/java-does-usb/src/main/java/net/codecrete/usb/macos/gen/iokit/IOUSBInterfaceStruct190.java) (macOS). This is a `struct` consisting of about 50 member functions. It's basically a vtable of a C++ class. For this single `struct`, *jextract* generates codes resulting in 100 class files with a total size of 213kByte.
+
+The table below shows class file size statistics for version 1.2.2 of the library:
-The table below shows statistics for version 0.6.0 of the library:
+| Operating Systems | Manually Written | % | Generated | % | Total | % |
+|-------------------|-----------------:|------:|----------:|------:|----------:|--------:|
+| Linux | 58,393 | 4.5% | 154,398 | 11.8% | 212,791 | 16.3% |
+| macOS | 81,441 | 6.2% | 427,258 | 32.8% | 508,699 | 39.0% |
+| Windows | 84,099 | 6.5% | 384,452 | 29.5% | 468,551 | 35.9% |
+| Common | 113,568 | 8.7% | | | 113,568 | 8.7% |
+| Grand Total | 337,501 | 25.9% | 966,108 | 74.1% | 1,303,609 | 100.0% |
-| Operating Systems | Manually Created | % | Generated | % | Total | % |
-|-------------------|-----------------:|-------:|----------:|-------:|----------:|--------:|
-| Linux | 48,516 | 3.64% | 197,169 | 14.79% | 245,685 | 18.42% |
-| macOS | 78,718 | 5.90% | 546,907 | 41.01% | 625,625 | 46.91% |
-| Windows | 104,811 | 7.86% | 256,079 | 19.20% | 360,890 | 27.06% |
-| Common | 101,364 | 7.60% | | | 101,364 | 7.60% |
-| Grand Total | 333,409 | 25.00% | 1,000,155 | 75.00% | 1,333,564 | 100.00% |
-*Code Size (compiled), in bytes and percentage of total size*
+*Class File Size (compiled), in bytes and percentage of total size*
If *jextract* could generate code for error state capturing, there would be even more generated and less manually written code.
diff --git a/java-does-usb/jextract/linux/epoll.h b/java-does-usb/jextract/linux/epoll.h
new file mode 100644
index 00000000..c7c68b45
--- /dev/null
+++ b/java-does-usb/jextract/linux/epoll.h
@@ -0,0 +1,3 @@
+typedef unsigned int uint32_t;
+typedef unsigned long int uint64_t;
+#include
diff --git a/java-does-usb/jextract/linux/gen_linux.sh b/java-does-usb/jextract/linux/gen_linux.sh
index 31b8654f..7e5eb5ec 100755
--- a/java-does-usb/jextract/linux/gen_linux.sh
+++ b/java-does-usb/jextract/linux/gen_linux.sh
@@ -1,34 +1,41 @@
#!/bin/sh
-JEXTRACT=../../../../jextract/build/jextract/bin/jextract
+JEXTRACT=../../../../jextract/bin/jextract
+
+rm -rf ../../src/main/java/net/codecrete/usb/linux/gen
# errno.h
-$JEXTRACT --source --output ../../src/main/java \
+$JEXTRACT --output ../../src/main/java \
--header-class-name errno \
--target-package net.codecrete.usb.linux.gen.errno \
--include-constant EPIPE \
--include-constant EAGAIN \
+ --include-constant EBADF \
+ --include-constant ECANCELED \
--include-constant EINVAL \
--include-constant ENODEV \
+ --include-constant EINTR \
+ --include-constant ENOENT \
/usr/include/errno.h
# string.h
-$JEXTRACT --source --output ../../src/main/java \
+$JEXTRACT --output ../../src/main/java \
--header-class-name string \
--target-package net.codecrete.usb.linux.gen.string \
--include-function strerror \
/usr/include/string.h
# fcntl.h
-$JEXTRACT --source --output ../../src/main/java \
+$JEXTRACT --output ../../src/main/java \
--header-class-name fcntl \
--target-package net.codecrete.usb.linux.gen.fcntl \
--include-constant O_CLOEXEC \
--include-constant O_RDWR \
+ --include-constant FD_CLOEXEC \
/usr/include/fcntl.h
# unistd.h
-$JEXTRACT --source --output ../../src/main/java \
+$JEXTRACT --output ../../src/main/java \
--header-class-name unistd \
--target-package net.codecrete.usb.linux.gen.unistd \
--include-function close \
@@ -36,7 +43,7 @@ $JEXTRACT --source --output ../../src/main/java \
# usbdevice_fs.h
# Missing constants like USBDEVFS_CLAIMINTERFACE
-$JEXTRACT --source --output ../../src/main/java \
+$JEXTRACT --output ../../src/main/java \
--header-class-name usbdevice_fs \
--target-package net.codecrete.usb.linux.gen.usbdevice_fs \
--include-struct usbdevfs_bulktransfer \
@@ -45,16 +52,7 @@ $JEXTRACT --source --output ../../src/main/java \
--include-struct usbdevfs_urb \
--include-struct usbdevfs_disconnect_claim \
--include-struct usbdevfs_ioctl \
- --include-constant USBDEVFS_CONTROL \
- --include-constant USBDEVFS_BULK \
- --include-constant USBDEVFS_CLAIMINTERFACE \
- --include-constant USBDEVFS_RELEASEINTERFACE \
- --include-constant USBDEVFS_SETINTERFACE \
- --include-constant USBDEVFS_CLEAR_HALT \
- --include-constant USBDEVFS_SUBMITURB \
- --include-constant USBDEVFS_DISCARDURB \
- --include-constant USBDEVFS_REAPURB \
- --include-constant USBDEVFS_DISCONNECT_CLAIM \
+ --include-struct usbdevfs_iso_packet_desc \
--include-constant USBDEVFS_URB_TYPE_INTERRUPT \
--include-constant USBDEVFS_URB_TYPE_CONTROL \
--include-constant USBDEVFS_URB_TYPE_BULK \
@@ -64,10 +62,10 @@ $JEXTRACT --source --output ../../src/main/java \
# libudev.h
# (install libudev-dev if file is missing)
-$JEXTRACT --source --output ../../src/main/java \
+$JEXTRACT --output ../../src/main/java \
--header-class-name udev \
--target-package net.codecrete.usb.linux.gen.udev \
- -l udev \
+ -l :libudev.so.1 \
--include-function udev_new \
--include-function udev_enumerate_new \
--include-function udev_enumerate_add_match_subsystem \
@@ -89,14 +87,14 @@ $JEXTRACT --source --output ../../src/main/java \
--include-function udev_monitor_get_fd \
/usr/include/libudev.h
-# poll.h
-$JEXTRACT --source --output ../../src/main/java \
- --header-class-name poll \
- --target-package net.codecrete.usb.linux.gen.poll \
- --include-function poll \
- --include-struct pollfd \
- --include-constant POLLIN \
- --include-constant POLLOUT \
- --include-constant POLLERR \
- /usr/include/poll.h
+# epoll.h
+$JEXTRACT --output ../../src/main/java \
+ --header-class-name epoll \
+ --target-package net.codecrete.usb.linux.gen.epoll \
+ --include-constant EPOLL_CTL_ADD \
+ --include-constant EPOLL_CTL_DEL \
+ --include-constant EPOLLIN \
+ --include-constant EPOLLOUT \
+ --include-constant EPOLLWAKEUP \
+ epoll.h
diff --git a/java-does-usb/jextract/macos/gen_macos.sh b/java-does-usb/jextract/macos/gen_macos.sh
index c74b5864..d74e38c2 100755
--- a/java-does-usb/jextract/macos/gen_macos.sh
+++ b/java-does-usb/jextract/macos/gen_macos.sh
@@ -1,17 +1,19 @@
#!/bin/sh
-JEXTRACT=../../../../jextract/build/jextract/bin/jextract
+JEXTRACT=../../../../jextract/bin/jextract
# If SDK_DIR is changed, it needs to be changed in compile_flags.txt as well.
SDK_DIR=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
+rm -rf ../../src/main/java/net/codecrete/usb/macos/gen
+
# CoreFoundation
-$JEXTRACT --source --output ../../src/main/java \
+$JEXTRACT --output ../../src/main/java \
-I $SDK_DIR/usr/include \
- -lCoreFoundation.framework \
+ -l :/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation \
--header-class-name CoreFoundation \
--target-package net.codecrete.usb.macos.gen.corefoundation \
- --include-typedef CFRange \
- --include-typedef CFUUIDBytes \
+ --include-struct CFRange \
+ --include-struct CFUUIDBytes \
--include-function CFUUIDCreateFromUUIDBytes \
--include-function CFRelease \
--include-function CFStringGetLength \
@@ -25,14 +27,20 @@ $JEXTRACT --source --output ../../src/main/java \
--include-function CFRunLoopAddSource \
--include-function CFRunLoopRemoveSource \
--include-function CFRunLoopRun \
+ --include-function CFMessagePortCreateLocal \
+ --include-function CFMessagePortCreateRunLoopSource \
+ --include-function CFMessagePortCreateRemote \
+ --include-function CFMessagePortSendRequest \
+ --include-function CFDataCreate \
+ --include-function CFDataGetBytePtr \
--include-function CFUUIDGetUUIDBytes \
--include-constant kCFNumberSInt32Type \
cf_helper.h
# IOKit
-$JEXTRACT --source --output ../../src/main/java \
+$JEXTRACT --output ../../src/main/java \
-I $SDK_DIR/usr/include \
- -lIOKit.framework \
+ -l :/System/Library/Frameworks/IOKit.framework/IOKit \
--header-class-name IOKit \
--target-package net.codecrete.usb.macos.gen.iokit \
--include-var kIOMasterPortDefault \
@@ -42,7 +50,6 @@ $JEXTRACT --source --output ../../src/main/java \
--include-constant kIOReturnExclusiveAccess \
--include-var kCFRunLoopDefaultMode \
--include-struct IOCFPlugInInterfaceStruct \
- --include-typedef IOCFPlugInInterface \
--include-function IOObjectRelease \
--include-function IOIteratorNext \
--include-function IOCreatePlugInInterfaceForService \
@@ -53,23 +60,26 @@ $JEXTRACT --source --output ../../src/main/java \
--include-function IOServiceAddMatchingNotification \
--include-function IOServiceMatching \
--include-struct IOUSBDeviceStruct187 \
- --include-typedef IOUSBDeviceInterface187 \
--include-constant kIOUSBFindInterfaceDontCare \
- --include-typedef IOUSBFindInterfaceRequest \
- --include-typedef IOUSBDevRequest \
+ --include-struct IOUSBFindInterfaceRequest \
+ --include-struct IOUSBDevRequest \
--include-struct IOUSBInterfaceStruct190 \
- --include-typedef IOUSBInterfaceInterface190 \
--include-constant kIOUSBTransactionTimeout \
--include-constant kIOReturnAborted \
--include-constant kIOUSBPipeStalled \
--include-constant kUSBReEnumerateCaptureDeviceMask \
--include-constant kUSBReEnumerateReleaseDeviceMask \
+ --include-struct CFUUIDBytes \
iokit_helper.h
# mach.h
-$JEXTRACT --source --output ../../src/main/java \
+$JEXTRACT --output ../../src/main/java \
-I $SDK_DIR/usr/include \
--header-class-name mach \
--target-package net.codecrete.usb.macos.gen.mach \
--include-function mach_error_string \
$SDK_DIR/usr/include/mach/mach.h
+
+sed -i '' -E -f remove_fp_upcall.sed ../../src/main/java/net/codecrete/usb/macos/gen/iokit/IOUSBDeviceStruct187.java
+sed -i '' -E -f remove_fp_upcall.sed ../../src/main/java/net/codecrete/usb/macos/gen/iokit/IOUSBInterfaceStruct190.java
+sed -i '' -E -f remove_fp_upcall.sed ../../src/main/java/net/codecrete/usb/macos/gen/iokit/IOCFPlugInInterfaceStruct.java
diff --git a/java-does-usb/jextract/macos/remove_fp_upcall.sed b/java-does-usb/jextract/macos/remove_fp_upcall.sed
new file mode 100644
index 00000000..d90e01d3
--- /dev/null
+++ b/java-does-usb/jextract/macos/remove_fp_upcall.sed
@@ -0,0 +1,6 @@
+/The function pointer signature/,/ \}/ {
+ s/^.*function pointer signature.*$/ *\//p
+ d
+
+}
+/MethodHandle UP\$MH = /,/ \}/d
diff --git a/java-does-usb/jextract/windows/gen_win.cmd b/java-does-usb/jextract/windows/gen_win.cmd
deleted file mode 100644
index a4084d2c..00000000
--- a/java-does-usb/jextract/windows/gen_win.cmd
+++ /dev/null
@@ -1,145 +0,0 @@
-set JEXTRACT=..\..\..\..\jextract\build\jextract\bin\jextract.bat
-set SDK_DIR=C:\Program Files (x86)\Windows Kits\10\Include\10.0.22000.0
-
-call %JEXTRACT% --source --output ../../src/main/java ^
- -D _AMD64_ -D _M_AMD64=100 -D UNICODE -D _UNICODE ^
- -I "%SDK_DIR%\um" ^
- -I "%SDK_DIR%\shared" ^
- -l Kernel32 ^
- --header-class-name Kernel32 ^
- --target-package net.codecrete.usb.windows.gen.kernel32 ^
- --include-function CloseHandle ^
- --include-function GetModuleHandleW ^
- --include-function FormatMessageW ^
- --include-function LocalFree ^
- --include-constant ERROR_NO_MORE_ITEMS ^
- --include-constant ERROR_MORE_DATA ^
- --include-constant ERROR_INSUFFICIENT_BUFFER ^
- --include-constant ERROR_FILE_NOT_FOUND ^
- --include-constant ERROR_GEN_FAILURE ^
- --include-constant ERROR_NOT_FOUND ^
- --include-constant ERROR_IO_PENDING ^
- --include-constant GENERIC_READ ^
- --include-constant GENERIC_WRITE ^
- --include-constant FILE_SHARE_READ ^
- --include-constant FILE_SHARE_WRITE ^
- --include-constant FILE_ATTRIBUTE_NORMAL ^
- --include-constant FILE_FLAG_OVERLAPPED ^
- --include-constant OPEN_EXISTING ^
- --include-constant FORMAT_MESSAGE_ALLOCATE_BUFFER ^
- --include-constant FORMAT_MESSAGE_FROM_SYSTEM ^
- --include-constant FORMAT_MESSAGE_IGNORE_INSERTS ^
- --include-constant FORMAT_MESSAGE_FROM_HMODULE ^
- --include-constant INFINITE ^
- --include-struct _GUID ^
- --include-typedef GUID ^
- --include-struct _OVERLAPPED ^
- --include-typedef OVERLAPPED ^
- windows_headers.h
-
-call %JEXTRACT% --source --output ../../src/main/java ^
- -D _AMD64_ -D _M_AMD64=100 -D UNICODE -D _UNICODE ^
- -I "%SDK_DIR%\um" ^
- -I "%SDK_DIR%\shared" ^
- -l SetupAPI ^
- --header-class-name SetupAPI ^
- --target-package net.codecrete.usb.windows.gen.setupapi ^
- --include-function SetupDiDestroyDeviceInfoList ^
- --include-function SetupDiDeleteDeviceInterfaceData ^
- --include-struct _SP_DEVINFO_DATA ^
- --include-typedef SP_DEVINFO_DATA ^
- --include-struct _SP_DEVICE_INTERFACE_DATA ^
- --include-typedef SP_DEVICE_INTERFACE_DATA ^
- --include-struct _SP_DEVICE_INTERFACE_DETAIL_DATA_W ^
- --include-typedef SP_DEVICE_INTERFACE_DETAIL_DATA_W ^
- --include-struct _DEVPROPKEY ^
- --include-typedef DEVPROPKEY ^
- --include-constant DIGCF_PRESENT ^
- --include-constant DIGCF_DEVICEINTERFACE ^
- --include-constant DEVPROP_TYPE_UINT32 ^
- --include-constant DEVPROP_TYPE_STRING ^
- --include-constant DEVPROP_TYPEMOD_LIST ^
- --include-constant DICS_FLAG_GLOBAL ^
- --include-constant DIREG_DEV ^
- windows_headers.h
-
-call %JEXTRACT% --source --output ../../src/main/java ^
- -D _AMD64_ -D _M_AMD64=100 -D UNICODE -D _UNICODE ^
- -I "%SDK_DIR%\um" ^
- -I "%SDK_DIR%\shared" ^
- --header-class-name USBIoctl ^
- --target-package net.codecrete.usb.windows.gen.usbioctl ^
- --include-struct _USB_NODE_CONNECTION_INFORMATION_EX ^
- --include-typedef USB_NODE_CONNECTION_INFORMATION_EX ^
- --include-struct _USB_DESCRIPTOR_REQUEST ^
- --include-typedef USB_DESCRIPTOR_REQUEST ^
- --include-constant IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX ^
- --include-constant IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION ^
- windows_headers.h
-
-call %JEXTRACT% --source --output ../../src/main/java ^
- -D _AMD64_ -D _M_AMD64=100 -D UNICODE -D _UNICODE ^
- -I "%SDK_DIR%\um" ^
- -I "%SDK_DIR%\shared" ^
- -l User32 ^
- --header-class-name User32 ^
- --target-package net.codecrete.usb.windows.gen.user32 ^
- --include-function DefWindowProcW ^
- --include-constant DEVICE_NOTIFY_WINDOW_HANDLE ^
- --include-constant HWND_MESSAGE ^
- --include-constant WM_DEVICECHANGE ^
- --include-constant DBT_DEVICEARRIVAL ^
- --include-constant DBT_DEVICEREMOVECOMPLETE ^
- --include-constant DBT_DEVTYP_DEVICEINTERFACE ^
- --include-struct tagMSG ^
- --include-typedef MSG ^
- --include-struct tagWNDCLASSEXW ^
- --include-typedef WNDCLASSEXW ^
- --include-struct _DEV_BROADCAST_HDR ^
- --include-typedef DEV_BROADCAST_HDR ^
- --include-struct _DEV_BROADCAST_DEVICEINTERFACE_W ^
- --include-typedef DEV_BROADCAST_DEVICEINTERFACE_W ^
- windows_headers.h
-
-call %JEXTRACT% --source --output ../../src/main/java ^
- -D _AMD64_ -D _M_AMD64=100 -D UNICODE -D _UNICODE ^
- -I "%SDK_DIR%\um" ^
- -I "%SDK_DIR%\shared" ^
- -l Winusb ^
- --header-class-name WinUSB ^
- --target-package net.codecrete.usb.windows.gen.winusb ^
- --include-function WinUsb_Free ^
- --include-constant PIPE_TRANSFER_TIMEOUT ^
- --include-constant RAW_IO ^
- windows_headers.h
-
-call %JEXTRACT% --source --output ../../src/main/java ^
- -D _AMD64_ -D _M_AMD64=100 -D UNICODE -D _UNICODE ^
- -I "%SDK_DIR%\um" ^
- -I "%SDK_DIR%\shared" ^
- -l Advapi32 ^
- --header-class-name Advapi32 ^
- --target-package net.codecrete.usb.windows.gen.advapi32 ^
- --include-function RegQueryValueExW ^
- --include-function RegCloseKey ^
- --include-constant KEY_READ ^
- windows_headers.h
-
-call %JEXTRACT% --source --output ../../src/main/java ^
- -D _AMD64_ -D _M_AMD64=100 -D UNICODE -D _UNICODE ^
- -I "%SDK_DIR%\um" ^
- -I "%SDK_DIR%\shared" ^
- -l Ole32 ^
- --header-class-name Ole32 ^
- --target-package net.codecrete.usb.windows.gen.ole32 ^
- --include-function CLSIDFromString ^
- windows_headers.h
-
-call %JEXTRACT% --source --output ../../src/main/java ^
- -D _AMD64_ -D _M_AMD64=100 -D UNICODE -D _UNICODE ^
- -I "%SDK_DIR%\um" ^
- -I "%SDK_DIR%\shared" ^
- --header-class-name NtDll ^
- --target-package net.codecrete.usb.windows.gen.ntdll ^
- --include-constant STATUS_UNSUCCESSFUL ^
- windows_headers.h
diff --git a/java-does-usb/jextract/windows/windows_headers.h b/java-does-usb/jextract/windows/windows_headers.h
deleted file mode 100644
index 171b98ee..00000000
--- a/java-does-usb/jextract/windows/windows_headers.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
diff --git a/java-does-usb/mvnw b/java-does-usb/mvnw
index 8d937f4c..19529ddf 100755
--- a/java-does-usb/mvnw
+++ b/java-does-usb/mvnw
@@ -19,290 +19,241 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Apache Maven Wrapper startup batch script, version 3.2.0
-#
-# Required ENV vars:
-# ------------------
-# JAVA_HOME - location of a JDK home dir
+# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
-# MAVEN_OPTS - parameters passed to the Java VM when running Maven
-# e.g. to debug Maven itself, use
-# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
-if [ -z "$MAVEN_SKIP_RC" ] ; then
-
- if [ -f /usr/local/etc/mavenrc ] ; then
- . /usr/local/etc/mavenrc
- fi
-
- if [ -f /etc/mavenrc ] ; then
- . /etc/mavenrc
- fi
-
- if [ -f "$HOME/.mavenrc" ] ; then
- . "$HOME/.mavenrc"
- fi
-
-fi
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
-# OS specific support. $var _must_ be set to either true or false.
-cygwin=false;
-darwin=false;
-mingw=false
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
case "$(uname)" in
- CYGWIN*) cygwin=true ;;
- MINGW*) mingw=true;;
- Darwin*) darwin=true
- # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
- # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
- if [ -z "$JAVA_HOME" ]; then
- if [ -x "/usr/libexec/java_home" ]; then
- JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
- else
- JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
- fi
- fi
- ;;
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
esac
-if [ -z "$JAVA_HOME" ] ; then
- if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=$(java-config --jre-home)
- fi
-fi
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
-fi
-
-# For Mingw, ensure paths are in UNIX format before anything is touched
-if $mingw ; then
- [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
- JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
-fi
-
-if [ -z "$JAVA_HOME" ]; then
- javaExecutable="$(which javac)"
- if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
- # readlink(1) is not available as standard on Solaris 10.
- readLink=$(which readlink)
- if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
- if $darwin ; then
- javaHome="$(dirname "\"$javaExecutable\"")"
- javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
- else
- javaExecutable="$(readlink -f "\"$javaExecutable\"")"
- fi
- javaHome="$(dirname "\"$javaExecutable\"")"
- javaHome=$(expr "$javaHome" : '\(.*\)/bin')
- JAVA_HOME="$javaHome"
- export JAVA_HOME
- fi
- fi
-fi
-
-if [ -z "$JAVACMD" ] ; then
- if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
fi
else
- JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
- fi
-fi
-
-if [ ! -x "$JAVACMD" ] ; then
- echo "Error: JAVA_HOME is not defined correctly." >&2
- echo " We cannot execute $JAVACMD" >&2
- exit 1
-fi
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
-if [ -z "$JAVA_HOME" ] ; then
- echo "Warning: JAVA_HOME environment variable is not set."
-fi
-
-# traverses directory structure from process work directory to filesystem root
-# first directory with .mvn subdirectory is considered project base directory
-find_maven_basedir() {
- if [ -z "$1" ]
- then
- echo "Path not specified to find_maven_basedir"
- return 1
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
fi
+}
- basedir="$1"
- wdir="$1"
- while [ "$wdir" != '/' ] ; do
- if [ -d "$wdir"/.mvn ] ; then
- basedir=$wdir
- break
- fi
- # workaround for JBEAP-8937 (on Solaris 10/Sparc)
- if [ -d "${wdir}" ]; then
- wdir=$(cd "$wdir/.." || exit 1; pwd)
- fi
- # end of workaround
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
done
- printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+ printf %x\\n $h
}
-# concatenates all lines of a file
-concat_lines() {
- if [ -f "$1" ]; then
- # Remove \r in case we run on Windows within Git Bash
- # and check out the repository with auto CRLF management
- # enabled. Otherwise, we may read lines that are delimited with
- # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
- # splitting rules.
- tr -s '\r\n' ' ' < "$1"
- fi
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
}
-log() {
- if [ "$MVNW_VERBOSE" = true ]; then
- printf '%s\n' "$1"
- fi
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
-BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
-if [ -z "$BASE_DIR" ]; then
- exit 1;
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
fi
-MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
-log "$MAVEN_PROJECTBASEDIR"
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
-##########################################################################################
-# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-# This allows using the maven wrapper in projects that prohibit checking in binary data.
-##########################################################################################
-wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
-if [ -r "$wrapperJarPath" ]; then
- log "Found $wrapperJarPath"
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
else
- log "Couldn't find $wrapperJarPath, downloading it ..."
+ die "cannot create temp dir"
+fi
- if [ -n "$MVNW_REPOURL" ]; then
- wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
- else
- wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
- fi
- while IFS="=" read -r key value; do
- # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
- safeValue=$(echo "$value" | tr -d '\r')
- case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
- esac
- done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
- log "Downloading from: $wrapperUrl"
+mkdir -p -- "${MAVEN_HOME%/*}"
- if $cygwin; then
- wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
- fi
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
- if command -v wget > /dev/null; then
- log "Found wget ... using wget"
- [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
- else
- wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
- fi
- elif command -v curl > /dev/null; then
- log "Found curl ... using curl"
- [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
- else
- curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
- fi
- else
- log "Falling back to using Java to download"
- javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
- javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
- # For Cygwin, switch paths to Windows format before running javac
- if $cygwin; then
- javaSource=$(cygpath --path --windows "$javaSource")
- javaClass=$(cygpath --path --windows "$javaClass")
- fi
- if [ -e "$javaSource" ]; then
- if [ ! -e "$javaClass" ]; then
- log " - Compiling MavenWrapperDownloader.java ..."
- ("$JAVA_HOME/bin/javac" "$javaSource")
- fi
- if [ -e "$javaClass" ]; then
- log " - Running MavenWrapperDownloader.java ..."
- ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
- fi
- fi
- fi
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
fi
-##########################################################################################
-# End of extension
-##########################################################################################
-# If specified, validate the SHA-256 sum of the Maven wrapper jar file
-wrapperSha256Sum=""
-while IFS="=" read -r key value; do
- case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
- esac
-done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
-if [ -n "$wrapperSha256Sum" ]; then
- wrapperSha256Result=false
- if command -v sha256sum > /dev/null; then
- if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
- wrapperSha256Result=true
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
- elif command -v shasum > /dev/null; then
- if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
- wrapperSha256Result=true
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
else
- echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
- echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
- if [ $wrapperSha256Result = false ]; then
- echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
- echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
- echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
-MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
- [ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
-# Provide a "standardized" way to retrieve the CLI args that will
-# work with both Windows and non-Windows executions.
-MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
-export MAVEN_CMD_LINE_ARGS
-
-WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-# shellcheck disable=SC2086 # safe args
-exec "$JAVACMD" \
- $MAVEN_OPTS \
- $MAVEN_DEBUG_OPTS \
- -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
- ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
+clean || :
+exec_maven "$@"
diff --git a/java-does-usb/mvnw.cmd b/java-does-usb/mvnw.cmd
index c4586b56..249bdf38 100644
--- a/java-does-usb/mvnw.cmd
+++ b/java-does-usb/mvnw.cmd
@@ -1,3 +1,4 @@
+<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@@ -18,188 +19,131 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Apache Maven Wrapper startup batch script, version 3.2.0
-@REM
-@REM Required ENV vars:
-@REM JAVA_HOME - location of a JDK home dir
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
-@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
-@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
-@REM e.g. to debug Maven itself, use
-@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
-@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
-@echo off
-@REM set title of command window
-title %0
-@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
-@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
-
-@REM set %HOME% to equivalent of $HOME
-if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
-
-@REM Execute a user defined script before this one
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
-@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
-if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
-:skipRcPre
-
-@setlocal
-
-set ERROR_CODE=0
-
-@REM To isolate internal variables from possible post scripts, we use another setlocal
-@setlocal
-
-@REM ==== START VALIDATION ====
-if not "%JAVA_HOME%" == "" goto OkJHome
-
-echo.
-echo Error: JAVA_HOME not found in your environment. >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-:OkJHome
-if exist "%JAVA_HOME%\bin\java.exe" goto init
-
-echo.
-echo Error: JAVA_HOME is set to an invalid directory. >&2
-echo JAVA_HOME = "%JAVA_HOME%" >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-@REM ==== END VALIDATION ====
-
-:init
-
-@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
-@REM Fallback to current working directory if not found.
-
-set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
-IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
-
-set EXEC_DIR=%CD%
-set WDIR=%EXEC_DIR%
-:findBaseDir
-IF EXIST "%WDIR%"\.mvn goto baseDirFound
-cd ..
-IF "%WDIR%"=="%CD%" goto baseDirNotFound
-set WDIR=%CD%
-goto findBaseDir
-
-:baseDirFound
-set MAVEN_PROJECTBASEDIR=%WDIR%
-cd "%EXEC_DIR%"
-goto endDetectBaseDir
-
-:baseDirNotFound
-set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
-cd "%EXEC_DIR%"
-
-:endDetectBaseDir
-
-IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
-
-@setlocal EnableExtensions EnableDelayedExpansion
-for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
-@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
-
-:endReadAdditionalConfig
-
-SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
-set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
-
-FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
-)
-
-@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
-if exist %WRAPPER_JAR% (
- if "%MVNW_VERBOSE%" == "true" (
- echo Found %WRAPPER_JAR%
- )
-) else (
- if not "%MVNW_REPOURL%" == "" (
- SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
- )
- if "%MVNW_VERBOSE%" == "true" (
- echo Couldn't find %WRAPPER_JAR%, downloading it ...
- echo Downloading from: %WRAPPER_URL%
- )
-
- powershell -Command "&{"^
- "$webclient = new-object System.Net.WebClient;"^
- "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
- "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
- "}"^
- "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
- "}"
- if "%MVNW_VERBOSE%" == "true" (
- echo Finished downloading %WRAPPER_JAR%
- )
-)
-@REM End of extension
-
-@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
-SET WRAPPER_SHA_256_SUM=""
-FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
-IF NOT %WRAPPER_SHA_256_SUM%=="" (
- powershell -Command "&{"^
- "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
- "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
- " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
- " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
- " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
- " exit 1;"^
- "}"^
- "}"
- if ERRORLEVEL 1 goto error
-)
-
-@REM Provide a "standardized" way to retrieve the CLI args that will
-@REM work with both Windows and non-Windows executions.
-set MAVEN_CMD_LINE_ARGS=%*
-
-%MAVEN_JAVA_EXE% ^
- %JVM_CONFIG_MAVEN_PROPS% ^
- %MAVEN_OPTS% ^
- %MAVEN_DEBUG_OPTS% ^
- -classpath %WRAPPER_JAR% ^
- "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
- %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
-if ERRORLEVEL 1 goto error
-goto end
-
-:error
-set ERROR_CODE=1
-
-:end
-@endlocal & set ERROR_CODE=%ERROR_CODE%
-
-if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
-@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
-if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
-:skipRcPost
-
-@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%"=="on" pause
-
-if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
-
-cmd /C exit /B %ERROR_CODE%
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/java-does-usb/pom.xml b/java-does-usb/pom.xml
index 32b756cf..e8ad6aec 100644
--- a/java-does-usb/pom.xml
+++ b/java-does-usb/pom.xml
@@ -6,14 +6,17 @@
net.codecrete.usbjava-does-usb
- 0.6.0-SNAPSHOT
+ 1.3.1-SNAPSHOT
- 21
- 21
+ 25
+ 25UTF-8
+ 0.8.0
+ jar
+
Java Does USBhttps://github.com/manuelbl/JavaDoesUSBAccess USB devices from Java without additional libraries
@@ -38,44 +41,113 @@
https://github.com/manuelbl/JavaDoesUSB
-
-
- ossrh
- https://oss.sonatype.org/content/repositories/snapshots
-
-
- ossrh
- https://oss.sonatype.org/service/local/staging/deploy/maven2/
-
-
-
+
+ net.codecrete.windows-api
+ windowsapi-maven-plugin
+
+
+
+ windows-api
+
+
+
+ CLSIDFromString
+ CreateFileW
+ CreateIoCompletionPort
+ CreateWindowExW
+ DefWindowProcW
+ DeviceIoControl
+ FormatMessageW
+ GetMessageW
+ GetModuleHandleW
+ GetQueuedCompletionStatus
+ LocalFree
+ RegCloseKey
+ RegQueryValueExW
+ RegisterClassExW
+ RegisterDeviceNotificationW
+ SetupDiCreateDeviceInfoList
+ SetupDiDeleteDeviceInterfaceData
+ SetupDiDestroyDeviceInfoList
+ SetupDiEnumDeviceInfo
+ SetupDiEnumDeviceInterfaces
+ SetupDiGetClassDevsW
+ SetupDiGetDeviceInterfaceDetailW
+ SetupDiGetDevicePropertyW
+ SetupDiOpenDevRegKey
+ SetupDiOpenDeviceInfoW
+ SetupDiOpenDeviceInterfaceW
+ WinUsb_AbortPipe
+ WinUsb_Free
+ WinUsb_GetAssociatedInterface
+ WinUsb_Initialize
+ WinUsb_ReadPipe
+ WinUsb_ResetPipe
+ WinUsb_SetCurrentAlternateSetting
+ WinUsb_SetPipePolicy
+ WinUsb_WritePipe
+
+
+ DEV_BROADCAST_DEVICEINTERFACE_W
+ DEV_BROADCAST_HDR
+ USB_DESCRIPTOR_REQUEST
+ USB_NODE_CONNECTION_INFORMATION_EX
+
+
+ DEV_BROADCAST_HDR_DEVICE_TYPE
+ FORMAT_MESSAGE_OPTIONS
+ GENERIC_ACCESS_RIGHTS
+ REG_SAM_FLAGS
+ SETUP_DI_PROPERTY_CHANGE_SCOPE
+
+
+ DBT_DEVICEARRIVAL
+ DBT_DEVICEREMOVECOMPLETE
+ DEVPKEY_Device_Address
+ DEVPKEY_Device_Children
+ DEVPKEY_Device_HardwareIds
+ DEVPKEY_Device_InstanceId
+ DEVPKEY_Device_Parent
+ DEVPKEY_Device_Service
+ DIREG_DEV
+ GUID_DEVINTERFACE_USB_DEVICE
+ GUID_DEVINTERFACE_USB_HUB
+ HWND_MESSAGE
+ INFINITE
+ IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION
+ IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX
+ STATUS_UNSUCCESSFUL
+ USB_REQUEST_GET_DESCRIPTOR
+ WM_DEVICECHANGE
+
+
+
+
+ org.apache.maven.pluginsmaven-compiler-plugin
- 3.11.0
+ 3.12.1
- 21
-
- --enable-preview
-
- 21
- 21
+ 25
+ 25
+ 25org.apache.maven.pluginsmaven-surefire-plugin
- 3.1.2
+ 3.2.5
- --enable-preview --enable-native-access=ALL-UNNAMED
+ --enable-native-access=ALL-UNNAMEDorg.apache.maven.pluginsmaven-javadoc-plugin
- 3.5.0
+ 3.6.3attach-javadocs
@@ -85,9 +157,9 @@
- 21
- --enable-preview
+ 25${java.home}/bin/javadoc
+ net.codecrete.usb.linux.gen.*:net.codecrete.usb.macos.gen.*:windows.*:system
@@ -117,36 +189,58 @@
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ ${sonatype-central-publishing.version}
+ true
+
+ central
+ true
+
+
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.13
- true
-
- ossrh
- https://s01.oss.sonatype.org/
- true
-
+ net.codecrete.windows-api
+ windowsapi-maven-plugin
+ 0.8.5
+
+ org.jetbrains
+ annotations
+ 24.1.0
+ compile
+ org.junit.jupiterjunit-jupiter
- 5.9.3
+ 5.10.2testorg.assertjassertj-core
- 3.24.2
+ 3.25.3
+ test
+
+
+ org.tinylog
+ tinylog-impl
+ 2.7.0
+ test
+
+
+ org.tinylog
+ jsl-tinylog
+ 2.7.0test
diff --git a/java-does-usb/src/main/java/module-info.java b/java-does-usb/src/main/java/module-info.java
index cf4daec9..496c87ec 100644
--- a/java-does-usb/src/main/java/module-info.java
+++ b/java-does-usb/src/main/java/module-info.java
@@ -9,5 +9,6 @@
* Java Does USB – work with USB devices
*/
module net.codecrete.usb {
+ requires org.jetbrains.annotations;
exports net.codecrete.usb;
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USB.java b/java-does-usb/src/main/java/net/codecrete/usb/USB.java
deleted file mode 100644
index 59564730..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/USB.java
+++ /dev/null
@@ -1,124 +0,0 @@
-//
-// Java Does USB
-// Copyright (c) 2022 Manuel Bleichenbacher
-// Licensed under MIT License
-// https://opensource.org/licenses/MIT
-//
-
-package net.codecrete.usb;
-
-import net.codecrete.usb.common.USBDeviceRegistry;
-import net.codecrete.usb.linux.LinuxUSBDeviceRegistry;
-import net.codecrete.usb.macos.MacosUSBDeviceRegistry;
-import net.codecrete.usb.windows.WindowsUSBDeviceRegistry;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Consumer;
-
-/**
- * Provides access to USB devices.
- */
-public class USB {
-
- private static USBDeviceRegistry createInstance() {
- var osName = System.getProperty("os.name");
- var osArch = System.getProperty("os.arch");
-
- USBDeviceRegistry impl;
- if (osName.equals("Mac OS X") && (osArch.equals("x86_64") || osArch.equals("aarch64"))) {
- impl = new MacosUSBDeviceRegistry();
- } else if (osName.startsWith("Windows") && osArch.equals("amd64")) {
- impl = new WindowsUSBDeviceRegistry();
- } else if (osName.equals("Linux") && (osArch.equals("amd64") || osArch.equals("aarch64"))) {
- impl = new LinuxUSBDeviceRegistry();
- } else {
- throw new UnsupportedOperationException(String.format(
- "Java Does USB has no implementation for architecture %s/%s",
- osName, osArch));
- }
- return impl;
- }
-
- private static USBDeviceRegistry singletonInstance = null;
-
- private static synchronized USBDeviceRegistry instance() {
- if (singletonInstance == null) {
- singletonInstance = createInstance();
- singletonInstance.start();
- }
- return singletonInstance;
- }
-
- // Private, so no instance can be created
- private USB() {
- }
-
- /**
- * Gets a list of all connected USB devices.
- *
- *
- * Depending on the operating system, the list might or might not include
- * USB hubs and USB host controllers.
- *
- *
- * @return list of USB devices
- */
- public static List getAllDevices() {
- return instance().getAllDevices();
- }
-
- /**
- * Gets a list of connected USB devices matching the specified predicate.
- *
- * @param predicate device predicate
- * @return list of USB devices
- */
- public static List getDevices(USBDevicePredicate predicate) {
- return instance().getAllDevices().stream().filter(predicate::matches).toList();
- }
-
- /**
- * Gets the first connected USB device matching the specified predicate.
- *
- * @param predicate device predicate
- * @return optional USB device
- */
- public static Optional getDevice(USBDevicePredicate predicate) {
- return instance().getAllDevices().stream().filter(predicate::matches).findFirst();
- }
-
- /**
- * Gets the first connected USB device with the specified vendor and product ID.
- *
- * @param vendorId vendor ID
- * @param productId product ID
- * @return optional USB device
- */
- public static Optional getDevice(int vendorId, int productId) {
- return getDevice(device -> device.vendorId() == vendorId && device.productId() == productId);
- }
-
- /**
- * Sets the handler to be called when a USB device is connected.
- *
- * @param handler handler function, or {@code null} to remove a previous handler
- */
- public static void setOnDeviceConnected(Consumer handler) {
- instance().setOnDeviceConnected(handler);
- }
-
- /**
- * Sets the handler to be called when a USB device is disconnected.
- *
- * When the handler is called, the {@link USBDevice} instance has already been closed.
- * Descriptive information (such as vendor and product ID, serial number, interfaces, endpoints)
- * can still be accessed.
- *
- *
- * @param handler handler function, or {@code null} to remove a previous handler
- */
- public static void setOnDeviceDisconnected(Consumer handler) {
- instance().setOnDeviceDisconnected(handler);
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/Usb.java b/java-does-usb/src/main/java/net/codecrete/usb/Usb.java
new file mode 100644
index 00000000..b17fc604
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/Usb.java
@@ -0,0 +1,153 @@
+//
+// Java Does USB
+// Copyright (c) 2022 Manuel Bleichenbacher
+// Licensed under MIT License
+// https://opensource.org/licenses/MIT
+//
+
+package net.codecrete.usb;
+
+import net.codecrete.usb.common.UsbDeviceRegistry;
+import net.codecrete.usb.linux.LinuxUsbDeviceRegistry;
+import net.codecrete.usb.macos.MacosUsbDeviceRegistry;
+import net.codecrete.usb.windows.WindowsUsbDeviceRegistry;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.Unmodifiable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * Provides access to USB devices.
+ */
+public class Usb {
+
+ @SuppressWarnings("java:S1192")
+ private static UsbDeviceRegistry createInstance() {
+ var osName = System.getProperty("os.name");
+ var osArch = System.getProperty("os.arch");
+
+ UsbDeviceRegistry impl;
+ if (osName.equals("Mac OS X") && (osArch.equals("x86_64") || osArch.equals("aarch64"))) {
+ impl = new MacosUsbDeviceRegistry();
+ } else if (osName.startsWith("Windows") && (osArch.equals("amd64") || osArch.equals("aarch64"))) {
+ impl = new WindowsUsbDeviceRegistry();
+ } else if (osName.equals("Linux") && (osArch.equals("amd64") || osArch.equals("aarch64"))) {
+ impl = new LinuxUsbDeviceRegistry();
+ } else {
+ throw new UnsupportedOperationException(String.format(
+ "The \"Java Does USB\" library has no implementation for JRE/JDK %s/%s",
+ osName, osArch));
+ }
+ return impl;
+ }
+
+ private static UsbDeviceRegistry singletonInstance = null;
+
+ private static synchronized UsbDeviceRegistry instance() {
+ if (singletonInstance == null) {
+ singletonInstance = createInstance();
+ singletonInstance.start();
+ }
+ return singletonInstance;
+ }
+
+ // Private, so no instance can be created
+ private Usb() {
+ }
+
+ /**
+ * Gets a list of all connected USB devices.
+ *
+ *
+ * Depending on the operating system, the list might or might not include
+ * USB hubs and USB host controllers.
+ *
+ *
+ * @return list of USB devices
+ */
+ public static @NotNull @Unmodifiable Collection getDevices() {
+ return Collections.unmodifiableCollection(instance().getAllDevices());
+ }
+
+ /**
+ * Gets a list of connected USB devices matching the specified predicate.
+ *
+ * @param predicate device predicate
+ * @return list of USB devices
+ */
+ public static @NotNull @Unmodifiable List findDevices(@NotNull UsbDevicePredicate predicate) {
+ return instance().getAllDevices().stream().filter(predicate::matches).toList();
+ }
+
+ /**
+ * Gets the first connected USB device matching the specified predicate.
+ *
+ * @param predicate device predicate
+ * @return optional USB device
+ */
+ public static Optional findDevice(@NotNull UsbDevicePredicate predicate) {
+ return instance().getAllDevices().stream().filter(predicate::matches).findFirst();
+ }
+
+ /**
+ * Gets the first connected USB device with the specified vendor and product ID.
+ *
+ * @param vendorId vendor ID
+ * @param productId product ID
+ * @return optional USB device
+ */
+ public static Optional findDevice(int vendorId, int productId) {
+ return findDevice(device -> device.getVendorId() == vendorId && device.getProductId() == productId);
+ }
+
+ /**
+ * Sets the handler to be called when a USB device is connected.
+ *
+ * The handler is called from a background thread.
+ *
+ *
+ * The handler should not execute any time-consuming operations but rather return quickly.
+ * While the handler is being executed, maintaining the list of connected devices is paused,
+ * methods of this class (such as {@link #getDevices()}) will possibly work with an outdated list
+ * of connected devices and handlers for connect and disconnect events will not be called.
+ *
+ *
+ * @param handler handler function, or {@code null} to remove a previous handler
+ */
+ public static void setOnDeviceConnected(@Nullable Consumer handler) {
+ instance().setOnDeviceConnected(handler);
+ }
+
+ /**
+ * Sets the handler to be called when a USB device is disconnected.
+ *
+ * The handler is called from a background thread.
+ *
+ *
+ * When the handler is called, the {@link UsbDevice} instance has already been closed.
+ * Descriptive information (such as vendor and product ID, serial number, interfaces, endpoints)
+ * can still be accessed.
+ *
+ *
+ * If the application was communicating with the device when it was disconnected, it will also receive
+ * an error for those operations. Due to the concurrency of the USB stack, there is no particular order
+ * for the disconnect event and the transmission errors.
+ *
+ *
+ * The handler should not execute any time-consuming operations but rather return quickly.
+ * While the handler is being executed, maintaining the list of connected devices is paused,
+ * methods of this class (such as {@link #getDevices()}) will possibly work with an outdated list
+ * of connected devices and handlers for connect and disconnect events will not be called.
+ *
+ *
+ * @param handler handler function, or {@code null} to remove a previous handler
+ */
+ public static void setOnDeviceDisconnected(@Nullable Consumer handler) {
+ instance().setOnDeviceDisconnected(handler);
+ }
+}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBAlternateInterface.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbAlternateInterface.java
similarity index 70%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBAlternateInterface.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbAlternateInterface.java
index b0445f9c..835f68aa 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBAlternateInterface.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbAlternateInterface.java
@@ -7,6 +7,9 @@
package net.codecrete.usb;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Unmodifiable;
+
import java.util.List;
/**
@@ -15,16 +18,17 @@
* Instances of this class describe an alternate setting of a USB interface.
*
*/
-public interface USBAlternateInterface {
+public interface UsbAlternateInterface {
/**
* Gets the alternate setting number.
*
* It is equal to the {@code bAlternateSetting} field of the interface descriptor.
+ *
*
* @return the alternate setting number
*/
- int number();
+ int getNumber();
/**
* Gets the interface class.
@@ -34,7 +38,7 @@ public interface USBAlternateInterface {
*
* @return the interface class
*/
- int classCode();
+ int getClassCode();
/**
* Gets the interface subclass.
@@ -44,7 +48,7 @@ public interface USBAlternateInterface {
*
* @return the interface subclass
*/
- int subclassCode();
+ int getSubclassCode();
/**
* Gets the interface protocol.
@@ -54,26 +58,32 @@ public interface USBAlternateInterface {
*
* @return the interface protocol
*/
- int protocolCode();
+ int getProtocolCode();
/**
- * Gets the endpoints of this alternate interface settings.
+ * Gets the endpoints of this alternate interface setting.
*
* The endpoint list does not include endpoint 0, which
* is always available and reserved for control transfers.
*
+ *
+ * The returned list is sorted by endpoint number.
+ *
*
* @return a list of endpoints.
*/
- List endpoints();
+ @NotNull
+ @Unmodifiable
+ List getEndpoints();
/**
* Gets the endpoint with the specified number and direction.
*
* @param endpointNumber endpoint number (in the range between 1 and 127, without the direction bit)
* @param direction endpoint direction
- * @return the endpoint, or {@code null} if no endpoint with the given number and direction exists
+ * @return the endpoint
+ * @exception UsbException if the endpoint does not exist
*/
- USBEndpoint getEndpoint(int endpointNumber, USBDirection direction);
+ @NotNull UsbEndpoint getEndpoint(int endpointNumber, UsbDirection direction);
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBControlTransfer.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbControlTransfer.java
similarity index 79%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBControlTransfer.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbControlTransfer.java
index ef937118..dbc2e042 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBControlTransfer.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbControlTransfer.java
@@ -20,13 +20,13 @@
*
* @param requestType request type (bits 5 and 6 of {@code bmRequestType})
* @param recipient recipient (bits 0–4 of {@code bmRequestType})
- * @param request request code (value between 0 and 255, called {@code bRequest} in USB specification)
- * @param value value (value between 0 and 65535, called {@code wValue} in USB specification)
- * @param index index (value between 0 and 65535, called {@code wIndex} in USB specification).
+ * @param request request code (value between 0 and 255, called {@code bRequest} in the USB specification)
+ * @param value value (value between 0 and 65535, called {@code wValue} in the USB specification)
+ * @param index index (value between 0 and 65535, called {@code wIndex} in the USB specification)
*/
-public record USBControlTransfer(
- USBRequestType requestType,
- USBRecipient recipient,
+public record UsbControlTransfer(
+ UsbRequestType requestType,
+ UsbRecipient recipient,
int request,
int value,
int index
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBDevice.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbDevice.java
similarity index 82%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBDevice.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbDevice.java
index 67837476..786956ba 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBDevice.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbDevice.java
@@ -7,6 +7,9 @@
package net.codecrete.usb;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Unmodifiable;
+
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
@@ -15,7 +18,7 @@
* USB device.
*
* In order to make control requests and transfer data, the device must be
- * opened and an interface must be claimed. In the open state, this current
+ * opened and an interface must be claimed. In the open state, the current
* process has exclusive access to the device.
*
*
@@ -23,38 +26,38 @@
* closed state.
*
*/
-public interface USBDevice {
+public interface UsbDevice {
/**
* USB product ID.
*
* @return product ID
*/
- int productId();
+ int getProductId();
/**
* USB vendor ID.
*
* @return vendor ID
*/
- int vendorId();
+ int getVendorId();
/**
* Product name.
*
* @return product name or {@code null} if not provided by the device
*/
- String product();
+ String getProduct();
/**
- * Manufacturer name
+ * Manufacturer name.
*
* @return manufacturer name or {@code null} if not provided by the device
*/
- String manufacturer();
+ String getManufacturer();
/**
- * Serial number
+ * Serial number.
*
* Even though this is supposed to be a human-readable string,
* some devices are known to provide binary data.
@@ -62,42 +65,42 @@ public interface USBDevice {
*
* @return serial number or {@code null} if not provided by the device
*/
- String serialNumber();
+ String getSerialNumber();
/**
* USB device class code ({@code bDeviceClass} from device descriptor).
*
* @return class code
*/
- int classCode();
+ int getClassCode();
/**
* USB device subclass code ({@code bDeviceSubClass} from device descriptor).
*
* @return subclass code
*/
- int subclassCode();
+ int getSubclassCode();
/**
* USB device protocol ({@code bDeviceProtocol} from device descriptor).
*
* @return protocol code
*/
- int protocolCode();
+ int getProtocolCode();
/**
* USB protocol version supported by this device.
*
* @return version
*/
- Version usbVersion();
+ @NotNull Version getUsbVersion();
/**
* Device version (as declared by the manufacturer).
*
* @return version
*/
- Version deviceVersion();
+ @NotNull Version getDeviceVersion();
/**
* Detaches the standard operating-system drivers of this device.
@@ -141,7 +144,7 @@ public interface USBDevice {
*
* On Linux, this method changes the behavior of {@link #claimInterface(int)}. Standard drivers will no longer be
* detached when the interface is claimed. Standard drivers are automatically reattached when the interfaces
- * are released, at the lasted when the device is closed.
+ * are released, at the latest when the device is closed.
*
*
* On Windows, this method does nothing.
@@ -149,6 +152,18 @@ public interface USBDevice {
*/
void attachStandardDrivers();
+ /**
+ * Indicates if the device is connected.
+ *
+ * When a {@link UsbDevice} instance is initially returned by {@link Usb#getDevices()} and related methods,
+ * it is connected. When the user unplugs the device, the application can still hold on to instance of
+ * {@link UsbDevice} even though the actual USB device is gone. This method can be used to check if the
+ * device is still connected.
+ *
+ * @return {@code true} if the device is connected, {@code false} if it is no longer connected
+ */
+ boolean isConnected();
+
/**
* Opens the device for communication.
*/
@@ -159,7 +174,7 @@ public interface USBDevice {
*
* @return {@code true} if the device is open, {@code false} if it is closed.
*/
- boolean isOpen();
+ boolean isOpened();
/**
* Closes the device.
@@ -168,27 +183,34 @@ public interface USBDevice {
/**
* Gets the interfaces of this device.
+ *
+ * The returned list is sorted by interface number.
+ *
*
* @return a list of USB interfaces
*/
- List interfaces();
+ @NotNull
+ @Unmodifiable
+ List getInterfaces();
/**
* Gets the interface with the specified number.
*
* @param interfaceNumber the interface number
- * @return the interface, or {@code null} if no interface with the given number exists
+ * @return the interface
+ * @exception UsbException if the interface does not exist
*/
- USBInterface getInterface(int interfaceNumber);
+ @NotNull UsbInterface getInterface(int interfaceNumber);
/**
* Gets the endpoint with the specified number.
*
* @param direction the endpoint direction
* @param endpointNumber the endpoint number (between 1 and 127)
- * @return the endpoint, or {@code null} if no endpoint with the given direction and number exists
+ * @return the endpoint
+ * @exception UsbException if the endpoint does not exist
*/
- USBEndpoint getEndpoint(USBDirection direction, int endpointNumber);
+ @NotNull UsbEndpoint getEndpoint(UsbDirection direction, int endpointNumber);
/**
* Claims the specified interface for exclusive use.
@@ -231,16 +253,16 @@ public interface USBDevice {
* or the interface of the addressed endpoint must have been claimed.
*
*
- * @param setup control transfer setup parameters
+ * @param transfer control transfer setup parameters
* @param length maximum length of expected data
* @return received data.
*/
- byte[] controlTransferIn(USBControlTransfer setup, int length);
+ byte @NotNull [] controlTransferIn(@NotNull UsbControlTransfer transfer, int length);
/**
* Executes a control transfer request and optionally sends data.
*
- * This method blocks until the device has acknowledge the request or an error has occurred.
+ * This method blocks until the device has acknowledged the request or an error has occurred.
*
*
* The control transfer request is sent to endpoint 0. The transfer is expected to either have
@@ -253,10 +275,10 @@ public interface USBDevice {
* or the interface of the addressed endpoint must have been claimed.
*
*
- * @param setup control transfer setup parameters
+ * @param transfer control transfer setup parameters
* @param data data to send, or {@code null} if the transfer has no data stage.
*/
- void controlTransferOut(USBControlTransfer setup, byte[] data);
+ void controlTransferOut(@NotNull UsbControlTransfer transfer, byte[] data);
/**
* Sends data to this device.
@@ -275,13 +297,13 @@ public interface USBDevice {
* @param endpointNumber endpoint number (in the range between 1 and 127)
* @param data data to send
*/
- void transferOut(int endpointNumber, byte[] data);
+ void transferOut(int endpointNumber, byte @NotNull [] data);
/**
* Sends data to this device.
*
* This method blocks until the data has been sent, the timeout period has expired
- * or an error has occurred. If the timeout expires, a {@link USBTimeoutException} is thrown.
+ * or an error has occurred. If the timeout expires, a {@link UsbTimeoutException} is thrown.
*
*
* This method can send data to bulk and interrupt endpoints.
@@ -296,13 +318,13 @@ public interface USBDevice {
* @param data data to send
* @param timeout the timeout period, in milliseconds (0 for no timeout)
*/
- void transferOut(int endpointNumber, byte[] data, int timeout);
+ void transferOut(int endpointNumber, byte @NotNull [] data, int timeout);
/**
* Sends data to this device.
*
* This method blocks until the data has been sent, the timeout period has expired
- * or an error has occurred. If the timeout expires, a {@link USBTimeoutException} is thrown.
+ * or an error has occurred. If the timeout expires, a {@link UsbTimeoutException} is thrown.
*
*
* This method can send data to bulk and interrupt endpoints.
@@ -319,7 +341,7 @@ public interface USBDevice {
* @param length number of bytes to send
* @param timeout the timeout period, in milliseconds (0 for no timeout)
*/
- void transferOut(int endpointNumber, byte[] data, int offset, int length, int timeout);
+ void transferOut(int endpointNumber, byte @NotNull [] data, int offset, int length, int timeout);
/**
* Receives data from this device.
@@ -337,13 +359,13 @@ public interface USBDevice {
* @param endpointNumber endpoint number (in the range between 1 and 127, i.e. without the direction bit)
* @return received data
*/
- byte[] transferIn(int endpointNumber);
+ byte @NotNull [] transferIn(int endpointNumber);
/**
* Receives data from this device.
*
* This method blocks until at least a packet has been received, the timeout period has expired
- * or an error has occurred. If the timeout expired, a {@link USBTimeoutException} is thrown.
+ * or an error has occurred. If the timeout expired, a {@link UsbTimeoutException} is thrown.
*
*
* The returned data is the payload of a packet. It can have a length of 0 if the USB device
@@ -357,7 +379,7 @@ public interface USBDevice {
* @param timeout the timeout period, in milliseconds (0 for no timeout)
* @return received data
*/
- byte[] transferIn(int endpointNumber, int timeout);
+ byte @NotNull [] transferIn(int endpointNumber, int timeout);
/**
* Opens a new output stream to send data to a bulk endpoint.
@@ -370,7 +392,7 @@ public interface USBDevice {
* and the last packet size was equal to maximum packet size of the endpoint.
*
*
- * If {@link #transferOut(int, byte[])} and a output stream or multiple output streams
+ * If {@link #transferOut(int, byte[])} and an output stream or multiple output streams
* are used concurrently for the same endpoint, the behavior is unpredictable.
*
*
@@ -378,7 +400,7 @@ public interface USBDevice {
* @param bufferSize approximate buffer size (in bytes)
* @return the new output stream
*/
- OutputStream openOutputStream(int endpointNumber, int bufferSize);
+ @NotNull OutputStream openOutputStream(int endpointNumber, int bufferSize);
/**
* Opens a new output stream to send data to a bulk endpoint.
@@ -389,7 +411,7 @@ public interface USBDevice {
* @param endpointNumber bulk endpoint number (in the range between 1 and 127)
* @return the new output stream
*/
- default OutputStream openOutputStream(int endpointNumber) {
+ default @NotNull OutputStream openOutputStream(int endpointNumber) {
return openOutputStream(endpointNumber, 1);
}
@@ -409,7 +431,7 @@ default OutputStream openOutputStream(int endpointNumber) {
* @param bufferSize approximate buffer size (in bytes)
* @return the new input stream
*/
- InputStream openInputStream(int endpointNumber, int bufferSize);
+ @NotNull InputStream openInputStream(int endpointNumber, int bufferSize);
/**
* Opens a new input stream to receive data from a bulk endpoint.
@@ -421,7 +443,7 @@ default OutputStream openOutputStream(int endpointNumber) {
* @param endpointNumber bulk endpoint number (in the range between 1 and 127, i.e. without the direction bit)
* @return the new input stream
*/
- default InputStream openInputStream(int endpointNumber) {
+ default @NotNull InputStream openInputStream(int endpointNumber) {
return openInputStream(endpointNumber, 1);
}
@@ -434,7 +456,7 @@ default InputStream openInputStream(int endpointNumber) {
* @param direction endpoint direction
* @param endpointNumber endpoint number (in the range between 1 and 127)
*/
- void abortTransfers(USBDirection direction, int endpointNumber);
+ void abortTransfers(UsbDirection direction, int endpointNumber);
/**
* Clears an endpoint's halt condition.
@@ -450,19 +472,19 @@ default InputStream openInputStream(int endpointNumber) {
* @param direction endpoint direction
* @param endpointNumber endpoint number (in the range between 1 and 127)
*/
- void clearHalt(USBDirection direction, int endpointNumber);
+ void clearHalt(UsbDirection direction, int endpointNumber);
/**
* Gets the device descriptor.
*
* @return the device descriptor (as a byte array)
*/
- byte[] deviceDescriptor();
+ byte @NotNull [] getDeviceDescriptor();
/**
* Gets the configuration descriptor.
*
* @return the configuration descriptor (as a byte array)
*/
- byte[] configurationDescriptor();
+ byte @NotNull [] getConfigurationDescriptor();
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBDevicePredicate.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbDevicePredicate.java
similarity index 65%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBDevicePredicate.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbDevicePredicate.java
index 6b1b773b..687db9ed 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBDevicePredicate.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbDevicePredicate.java
@@ -7,32 +7,34 @@
package net.codecrete.usb;
+import org.jetbrains.annotations.NotNull;
+
import java.util.List;
/**
* Represents a predicate (boolean-valued function) of one argument, evaluated for a given USB device.
*
- * This is a functional interface whose functional method is {@link #matches(USBDevice)}.
+ * This is a functional interface whose functional method is {@link #matches(UsbDevice)}.
*
*/
@FunctionalInterface
-public interface USBDevicePredicate {
+public interface UsbDevicePredicate {
/**
* Evaluates this predicate on the given USB device.
*
* @param device the USB device
- * @return {@code true} of the devices matches the predicate, otherwise {@code false}
+ * @return {@code true} if the device matches the predicate, otherwise {@code false}
*/
- boolean matches(USBDevice device);
+ boolean matches(@NotNull UsbDevice device);
/**
- * Test if the USB devices matches any of the filter conditions.
+ * Tests whether the USB device matches any of the filter conditions.
*
* @param device the USB device
* @param predicates a list of filter predicates
* @return {@code true} if it matches, {@code false} otherwise
*/
- static boolean matchesAny(USBDevice device, List predicates) {
+ static boolean matchesAny(@NotNull UsbDevice device, @NotNull List predicates) {
return predicates.stream().anyMatch(predicate -> predicate.matches(device));
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBDirection.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbDirection.java
similarity index 92%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBDirection.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbDirection.java
index 8e2e7981..8e561dc4 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBDirection.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbDirection.java
@@ -10,7 +10,7 @@
/**
* USB endpoint data direction enumeration.
*/
-public enum USBDirection {
+public enum UsbDirection {
/**
* Direction OUT (host to device)
*/
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBEndpoint.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbEndpoint.java
similarity index 87%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBEndpoint.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbEndpoint.java
index 434e0d34..1cebc06b 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBEndpoint.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbEndpoint.java
@@ -13,7 +13,7 @@
* Instances of this class describe a USB endpoint.
*
*/
-public interface USBEndpoint {
+public interface UsbEndpoint {
/**
* Gets the USB endpoint number.
@@ -24,12 +24,12 @@ public interface USBEndpoint {
*
*
* Use this number when calling any of the transfer methods of a
- * {@link USBDevice} instance.
+ * {@link UsbDevice} instance.
*
*
* @return the endpoint number
*/
- int number();
+ int getNumber();
/**
* Gets the direction of the endpoint.
@@ -40,14 +40,14 @@ public interface USBEndpoint {
*
* @return the direction
*/
- USBDirection direction();
+ UsbDirection getDirection();
/**
* Gets the USB endpoint transfer type.
*
* @return the transfer type
*/
- USBTransferType transferType();
+ UsbTransferType getTransferType();
/**
* Gets the packet size.
@@ -58,5 +58,5 @@ public interface USBEndpoint {
*
* @return the packet size, in bytes.
*/
- int packetSize();
+ int getPacketSize();
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBException.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbException.java
similarity index 74%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBException.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbException.java
index dacc902a..e0c62acb 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBException.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbException.java
@@ -7,10 +7,13 @@
package net.codecrete.usb;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
/**
* USB exception, thrown if an operation with USB devices fails.
*/
-public class USBException extends RuntimeException {
+public class UsbException extends RuntimeException {
/**
* Error code.
@@ -22,7 +25,7 @@ public class USBException extends RuntimeException {
*
* @param message the message
*/
- public USBException(String message) {
+ public UsbException(@NotNull String message) {
super(message);
code = -1;
}
@@ -33,7 +36,7 @@ public USBException(String message) {
* @param message the message
* @param errorCode the error code
*/
- public USBException(String message, int errorCode) {
+ public UsbException(@NotNull String message, int errorCode) {
super(message + " (error code: " + errorCode + ")");
code = errorCode;
}
@@ -44,7 +47,7 @@ public USBException(String message, int errorCode) {
* @param message the message
* @param cause the causal exception
*/
- public USBException(String message, Throwable cause) {
+ public UsbException(@NotNull String message, @Nullable Throwable cause) {
super(message, cause);
code = -1;
}
@@ -54,7 +57,7 @@ public USBException(String message, Throwable cause) {
*
* @return the error code
*/
- public int errorCode() {
+ public int getErrorCode() {
return code;
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBInterface.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbInterface.java
similarity index 58%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBInterface.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbInterface.java
index 8f63c649..67fde72f 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBInterface.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbInterface.java
@@ -7,6 +7,9 @@
package net.codecrete.usb;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Unmodifiable;
+
import java.util.List;
/**
@@ -15,7 +18,7 @@
* Instances of this class describe an interface of a USB device.
*
*/
-public interface USBInterface {
+public interface UsbInterface {
/**
* Gets the interface number.
@@ -25,10 +28,10 @@ public interface USBInterface {
*
* @return the interface number
*/
- int number();
+ int getNumber();
/**
- * Indicates if this interface is currently claimed for exclusive access.
+ * Indicates if this interface has been claimed by this program for exclusive access.
*
* @return {@code true} if it is claimed, {@code false} otherwise.
*/
@@ -37,25 +40,31 @@ public interface USBInterface {
/**
* Gets the currently selected alternate interface setting.
*
- * Initially, the alternate settings with number 0 is selected.
+ * Initially, the alternate setting with number 0 is selected.
*
*
* @return the alternate interface setting.
*/
- USBAlternateInterface alternate();
+ @NotNull UsbAlternateInterface getCurrentAlternate();
/**
- * Gets the alternate interface settings with the specified number.
+ * Gets the alternate interface setting with the specified number.
*
* @param alternateNumber alternate setting number
* @return alternate interface setting
+ * @throws UsbException if the alternate setting does not exist
*/
- USBAlternateInterface getAlternate(int alternateNumber);
+ @NotNull UsbAlternateInterface getAlternate(int alternateNumber);
/**
* Gets all alternate settings of this interface.
+ *
+ * The returned list is sorted by alternate setting number.
+ *
*
* @return a list of the alternate settings
*/
- List alternates();
+ @NotNull
+ @Unmodifiable
+ List getAlternates();
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBRecipient.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbRecipient.java
similarity index 93%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBRecipient.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbRecipient.java
index 250238a2..756027a2 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBRecipient.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbRecipient.java
@@ -10,7 +10,7 @@
/**
* USB control transfer recipient enumeration.
*/
-public enum USBRecipient {
+public enum UsbRecipient {
/**
* USB device
*/
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBRequestType.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbRequestType.java
similarity index 93%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBRequestType.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbRequestType.java
index 05ede7e6..08dec0d6 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBRequestType.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbRequestType.java
@@ -10,7 +10,7 @@
/**
* USB control transfer request type enumeration.
*/
-public enum USBRequestType {
+public enum UsbRequestType {
/**
* Standard request type
*/
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBStallException.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbStallException.java
similarity index 54%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBStallException.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbStallException.java
index 2b986a23..e60da595 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBStallException.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbStallException.java
@@ -7,24 +7,26 @@
package net.codecrete.usb;
+import org.jetbrains.annotations.NotNull;
+
/**
- * Exception thrown if a communication on a USB endpoint failed.
+ * Exception thrown if communication on a USB endpoint fails.
*
* If a USB endpoint stalls, it is halted and the halt condition must be cleared
- * using {@link USBDevice#clearHalt(USBDirection, int)} before communication can resume.
+ * using {@link UsbDevice#clearHalt(UsbDirection, int)} before communication can resume.
*
*
- * If the control endpoint 0 stalls, it throws this exception but is not halted.
+ * If the control endpoint 0 stalls, this exception is thrown but the endpoint is not halted.
*
*/
-public class USBStallException extends USBException {
+public class UsbStallException extends UsbException {
/**
* Creates a new instance with a message.
*
* @param message the message
*/
- public USBStallException(String message) {
+ public UsbStallException(@NotNull String message) {
super(message);
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBTimeoutException.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbTimeoutException.java
similarity index 69%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBTimeoutException.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbTimeoutException.java
index 3b18558f..28ae2f72 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBTimeoutException.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbTimeoutException.java
@@ -7,17 +7,19 @@
package net.codecrete.usb;
+import org.jetbrains.annotations.NotNull;
+
/**
* Exception thrown if a USB operation times out.
*/
-public class USBTimeoutException extends USBException {
+public class UsbTimeoutException extends UsbException {
/**
* Creates a new instance with a message.
*
* @param message the message
*/
- public USBTimeoutException(String message) {
+ public UsbTimeoutException(@NotNull String message) {
super(message);
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/USBTransferType.java b/java-does-usb/src/main/java/net/codecrete/usb/UsbTransferType.java
similarity index 93%
rename from java-does-usb/src/main/java/net/codecrete/usb/USBTransferType.java
rename to java-does-usb/src/main/java/net/codecrete/usb/UsbTransferType.java
index bfeaa0c0..f08a2e40 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/USBTransferType.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/UsbTransferType.java
@@ -10,7 +10,7 @@
/**
* USB endpoint transfer type enumeration.
*/
-public enum USBTransferType {
+public enum UsbTransferType {
/**
* Control transfer
*/
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/Version.java b/java-does-usb/src/main/java/net/codecrete/usb/Version.java
index 106d7b74..90b507f5 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/Version.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/Version.java
@@ -19,7 +19,7 @@ public final class Version {
*
* {@code bcdVersion} contains the version: the high byte is the major
* version. The low byte is split into two nibbles (4 bits), the high one
- * is minor version, the low one is the subminor version. As an example,
+ * is the minor version, the low one is the subminor version. As an example,
* 0x0321 represents the version 3.2.1.
*
*
@@ -34,7 +34,7 @@ public Version(int bcdVersion) {
*
* @return major version
*/
- public int major() {
+ public int getMajor() {
return bcdVersion >> 8;
}
@@ -43,7 +43,7 @@ public int major() {
*
* @return minor version
*/
- public int minor() {
+ public int getMinor() {
return (bcdVersion >> 4) & 0x0f;
}
@@ -52,13 +52,13 @@ public int minor() {
*
* @return subminor version
*/
- public int subminor() {
+ public int getSubminor() {
return bcdVersion & 0x0f;
}
@Override
public String toString() {
- return String.format("%d.%d.%d", major(), minor(), subminor());
+ return String.format("%d.%d.%d", getMajor(), getMinor(), getSubminor());
}
@Override
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/CompositeFunction.java b/java-does-usb/src/main/java/net/codecrete/usb/common/CompositeFunction.java
index 049bbdc6..b6e751a4 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/common/CompositeFunction.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/CompositeFunction.java
@@ -45,14 +45,31 @@ public CompositeFunction(int firstInterfaceNumber, int numInterfaces, int classC
functionProtocol = protocolCode;
}
+ /**
+ * Gets the number of the first interface contained in this function.
+ * @return the interface number
+ */
public int firstInterfaceNumber() {
return firstIntfNumber;
}
+ /**
+ * Gets the number of interfaces contained in this function.
+ * @return the number of interfaces
+ */
public int numInterfaces() {
return interfaceCount;
}
+ /**
+ * Indicates if this function contains the specified interface.
+ * @param interfaceNumber the interface number
+ * @return {@code true} if it is contained, {@code false} otherwise
+ */
+ public boolean containsInterface(int interfaceNumber) {
+ return interfaceNumber >= firstIntfNumber && interfaceNumber < firstIntfNumber + interfaceCount;
+ }
+
public int classCode() {
return functionCode;
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/Configuration.java b/java-does-usb/src/main/java/net/codecrete/usb/common/Configuration.java
index e1ad6992..dc5317d1 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/common/Configuration.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/Configuration.java
@@ -7,7 +7,7 @@
package net.codecrete.usb.common;
-import net.codecrete.usb.USBInterface;
+import net.codecrete.usb.UsbInterface;
import java.util.ArrayList;
import java.util.List;
@@ -17,7 +17,7 @@
*/
public class Configuration {
private final List functionList;
- private final List interfaceList;
+ private final List interfaceList;
private final int configurationValue;
private final int configurationAttributes;
private final int configurationMaxPower;
@@ -42,7 +42,7 @@ public int maxPower() {
return configurationMaxPower;
}
- public List interfaces() {
+ public List interfaces() {
return interfaceList;
}
@@ -50,12 +50,13 @@ public List functions() {
return functionList;
}
- public void addInterface(USBInterface intf) {
+ public void addInterface(UsbInterface intf) {
interfaceList.add(intf);
}
- public USBInterfaceImpl findInterfaceByNumber(int number) {
- return (USBInterfaceImpl) interfaceList.stream().filter(intf -> intf.number() == number).findFirst().orElse(null);
+ public UsbInterfaceImpl findInterfaceByNumber(int number) {
+ return (UsbInterfaceImpl) interfaceList.stream().filter(intf -> intf.getNumber() == number)
+ .findFirst().orElse(null);
}
public void addFunction(CompositeFunction function) {
@@ -63,6 +64,7 @@ public void addFunction(CompositeFunction function) {
}
public CompositeFunction findFunction(int interfaceNumber) {
- return functionList.stream().filter(f -> interfaceNumber >= f.firstInterfaceNumber() && interfaceNumber < f.firstInterfaceNumber() + f.numInterfaces()).findFirst().orElse(null);
+ return functionList.stream().filter(f -> f.containsInterface(interfaceNumber))
+ .findFirst().orElse(null);
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/ConfigurationParser.java b/java-does-usb/src/main/java/net/codecrete/usb/common/ConfigurationParser.java
index 16c989cd..f8023292 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/common/ConfigurationParser.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/ConfigurationParser.java
@@ -7,10 +7,10 @@
package net.codecrete.usb.common;
-import net.codecrete.usb.USBAlternateInterface;
-import net.codecrete.usb.USBDirection;
-import net.codecrete.usb.USBException;
-import net.codecrete.usb.USBTransferType;
+import net.codecrete.usb.UsbAlternateInterface;
+import net.codecrete.usb.UsbDirection;
+import net.codecrete.usb.UsbException;
+import net.codecrete.usb.UsbTransferType;
import net.codecrete.usb.usbstandard.ConfigurationDescriptor;
import net.codecrete.usb.usbstandard.EndpointDescriptor;
import net.codecrete.usb.usbstandard.InterfaceAssociationDescriptor;
@@ -20,10 +20,18 @@
import java.lang.foreign.ValueLayout;
import java.util.ArrayList;
-import static net.codecrete.usb.usbstandard.Constants.*;
+import static net.codecrete.usb.usbstandard.Constants.CONFIGURATION_DESCRIPTOR_TYPE;
+import static net.codecrete.usb.usbstandard.Constants.ENDPOINT_DESCRIPTOR_TYPE;
+import static net.codecrete.usb.usbstandard.Constants.INTERFACE_ASSOCIATION_DESCRIPTOR_TYPE;
+import static net.codecrete.usb.usbstandard.Constants.INTERFACE_DESCRIPTOR_TYPE;
/**
- * Parser for USB configuration descriptors
+ * Parser for USB configuration descriptors.
+ *
+ *
+ * It extracts the information about endpoints, interfaces (incl. alternate interfaces) and associations
+ * between interfaces to derive the functions. Other descriptor types are ignored.
+ *
*/
public class ConfigurationParser {
@@ -53,7 +61,7 @@ public ConfigurationParser(MemorySegment descriptor) {
public Configuration parse() {
parseHeader();
- USBAlternateInterfaceImpl lastAlternate = null;
+ UsbAlternateInterfaceImpl lastAlternate = null;
var offset = peekDescLength(0);
while (offset < descriptor.byteSize()) {
@@ -64,18 +72,18 @@ public Configuration parse() {
if (descType == INTERFACE_DESCRIPTOR_TYPE) {
var intf = parseInterface(offset);
- var parent = configuration.findInterfaceByNumber(intf.number());
+ var parent = configuration.findInterfaceByNumber(intf.getNumber());
if (parent != null) {
- parent.addAlternate(intf.alternate());
+ parent.addAlternate(intf.getCurrentAlternate());
} else {
configuration.addInterface(intf);
}
- lastAlternate = (USBAlternateInterfaceImpl) intf.alternate();
+ lastAlternate = (UsbAlternateInterfaceImpl) intf.getCurrentAlternate();
- var function = configuration.findFunction(intf.number());
+ var function = configuration.findFunction(intf.getNumber());
if (function == null) {
- function = new CompositeFunction(intf.number(), 1, lastAlternate.classCode(),
- lastAlternate.subclassCode(), lastAlternate.protocolCode());
+ function = new CompositeFunction(intf.getNumber(), 1, lastAlternate.getClassCode(),
+ lastAlternate.getSubclassCode(), lastAlternate.getProtocolCode());
configuration.addFunction(function);
}
@@ -97,22 +105,22 @@ public Configuration parse() {
private void parseHeader() {
var desc = new ConfigurationDescriptor(descriptor);
if (CONFIGURATION_DESCRIPTOR_TYPE != desc.descriptorType())
- throw new USBException("invalid USB configuration descriptor");
+ throw new UsbException("invalid USB configuration descriptor");
var totalLength = desc.totalLength();
if (descriptor.byteSize() != totalLength)
- throw new USBException("invalid USB configuration descriptor (invalid length)");
+ throw new UsbException("invalid USB configuration descriptor (invalid length)");
configuration = new Configuration(desc.configurationValue(), desc.attributes(), desc.maxPower());
}
- private USBInterfaceImpl parseInterface(int offset) {
+ private UsbInterfaceImpl parseInterface(int offset) {
var desc = new InterfaceDescriptor(descriptor, offset);
- var alternate = new USBAlternateInterfaceImpl(desc.alternateSetting(), desc.interfaceClass(),
+ var alternate = new UsbAlternateInterfaceImpl(desc.alternateSetting(), desc.interfaceClass(),
desc.interfaceSubClass(), desc.interfaceProtocol(), new ArrayList<>());
- var alternates = new ArrayList();
+ var alternates = new ArrayList();
alternates.add(alternate);
- return new USBInterfaceImpl(desc.interfaceNumber(), alternates);
+ return new UsbInterfaceImpl(desc.interfaceNumber(), alternates);
}
private void parseIAD(int offset) {
@@ -122,26 +130,26 @@ private void parseIAD(int offset) {
configuration.addFunction(function);
}
- private USBEndpointImpl parseEndpoint(int offset) {
+ private UsbEndpointImpl parseEndpoint(int offset) {
var desc = new EndpointDescriptor(descriptor, offset);
var address = desc.endpointAddress();
- return new USBEndpointImpl(getEndpointNumber(address), getEndpointDirection(address),
+ return new UsbEndpointImpl(getEndpointNumber(address), getEndpointDirection(address),
getEndpointType(desc.attributes()), desc.maxPacketSize());
}
- private static USBDirection getEndpointDirection(int address) {
- return (address & 0x80) != 0 ? USBDirection.IN : USBDirection.OUT;
+ private static UsbDirection getEndpointDirection(int address) {
+ return (address & 0x80) != 0 ? UsbDirection.IN : UsbDirection.OUT;
}
private static int getEndpointNumber(int address) {
return address & 0x7f;
}
- private static USBTransferType getEndpointType(int attributes) {
+ private static UsbTransferType getEndpointType(int attributes) {
return switch (attributes & 0x3) {
- case 1 -> USBTransferType.ISOCHRONOUS;
- case 2 -> USBTransferType.BULK;
- case 3 -> USBTransferType.INTERRUPT;
+ case 1 -> UsbTransferType.ISOCHRONOUS;
+ case 2 -> UsbTransferType.BULK;
+ case 3 -> UsbTransferType.INTERRUPT;
default -> null;
};
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/EndpointInputStream.java b/java-does-usb/src/main/java/net/codecrete/usb/common/EndpointInputStream.java
index 7e00f1fb..167b041c 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/common/EndpointInputStream.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/EndpointInputStream.java
@@ -7,15 +7,21 @@
package net.codecrete.usb.common;
-import net.codecrete.usb.USBDirection;
-import net.codecrete.usb.USBException;
+import net.codecrete.usb.UsbDirection;
+import net.codecrete.usb.UsbException;
+import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.InputStream;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
+import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import static net.codecrete.usb.common.EndpointStreams.toIOException;
+
+import static java.lang.System.Logger.Level.WARNING;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
/**
@@ -35,7 +41,14 @@
*/
public abstract class EndpointInputStream extends InputStream {
- protected USBDeviceImpl device;
+ private static final System.Logger LOG = System.getLogger(EndpointInputStream.class.getName());
+
+ // Maximum time (ms) to wait for outstanding transfers to complete during teardown.
+ // A completion that is never delivered (e.g. after an unplug) then degrades to a
+ // logged warning instead of a permanent hang.
+ private static final long TEARDOWN_TIMEOUT_MS = 1000;
+
+ protected UsbDeviceImpl device;
protected final int endpointNumber;
// Arena to allocate buffers and completion handlers
protected final Arena arena;
@@ -58,16 +71,17 @@ public abstract class EndpointInputStream extends InputStream {
* @param endpointNumber endpoint number
* @param bufferSize approximate buffer size (in bytes)
*/
- protected EndpointInputStream(USBDeviceImpl device, int endpointNumber, int bufferSize) {
+ protected EndpointInputStream(UsbDeviceImpl device, int endpointNumber, int bufferSize) {
this.device = device;
this.endpointNumber = endpointNumber;
- arena = Arena.ofShared();
+ //arena = Arena.ofShared(); // not supported by GraalVM
+ arena = Arena.ofAuto();
- var packetSize = device.getEndpoint(USBDirection.IN, endpointNumber).packetSize();
+ var packetSize = device.getEndpoint(UsbDirection.IN, endpointNumber).getPacketSize();
// use between 4 and 32 packets per transfer (256B to 2KB for FS, 2KB to 16KB for HS)
var numPacketsPerTransfer = (int) Math.round(Math.sqrt((double) bufferSize / packetSize));
- numPacketsPerTransfer = Math.min(Math.max(numPacketsPerTransfer, 4), 32);
+ numPacketsPerTransfer = Math.clamp(numPacketsPerTransfer, 4, 32);
transferSize = numPacketsPerTransfer * packetSize;
// use at least 2 outstanding transfers (3 in total)
@@ -109,9 +123,9 @@ public void close() throws IOException {
// abort all transfers on endpoint
try {
- device.abortTransfers(USBDirection.IN, endpointNumber);
+ device.abortTransfers(UsbDirection.IN, endpointNumber);
- } catch (USBException e) {
+ } catch (UsbException _) {
// If aborting the transfer is not possible, the device has
// likely been closed or unplugged. So all outstanding
// transfers will terminate anyway.
@@ -123,44 +137,66 @@ public void close() throws IOException {
@Override
public int read() throws IOException {
- if (isClosed())
- return -1;
+ ensureOpen();
- if (available() == 0)
- receiveMoreData();
+ try {
+ if (bufferedBytes() == 0)
+ receiveMoreData();
- var b = currentTransfer.data().get(JAVA_BYTE, readOffset) & 0xff;
- readOffset += 1;
- return b;
+ var b = currentTransfer.data().get(JAVA_BYTE, readOffset) & 0xff;
+ readOffset += 1;
+ return b;
+
+ } catch (UsbException e) {
+ throw toIOException(e);
+ }
}
@Override
- public int read(byte[] b, int off, int len) throws IOException {
- if (isClosed())
- return -1;
+ public int read(byte @NotNull [] b, int off, int len) throws IOException {
+ Objects.checkFromIndexSize(off, len, b.length);
+ ensureOpen();
+ if (len == 0)
+ return 0;
- var numRead = 0;
- do {
- if (available() == 0)
- receiveMoreData();
+ try {
+ var numRead = 0;
+ do {
+ if (bufferedBytes() == 0)
+ receiveMoreData();
- // copy data to receiving buffer
- var n = Math.min(len - numRead, currentTransfer.resultSize() - readOffset);
- MemorySegment.copy(currentTransfer.data(), readOffset, MemorySegment.ofArray(b), (long) off + numRead, n);
- readOffset += n;
- numRead += n;
+ // copy data to receiving buffer
+ var n = Math.min(len - numRead, currentTransfer.resultSize() - readOffset);
+ MemorySegment.copy(currentTransfer.data(), readOffset, MemorySegment.ofArray(b), (long) off + numRead, n);
+ readOffset += n;
+ numRead += n;
- } while (numRead < len && hasMoreTransfers());
+ } while (numRead < len && hasMoreTransfers());
- return numRead;
+ return numRead;
+
+ } catch (UsbException e) {
+ throw toIOException(e);
+ }
}
- @SuppressWarnings("RedundantThrows")
@Override
public int available() throws IOException {
+ ensureOpen();
+ return bufferedBytes();
+ }
+
+ // Bytes buffered in the current transfer, without a closed-stream check.
+ // Callers on the read path guard with ensureOpen() first.
+ private int bufferedBytes() {
return currentTransfer.resultSize() - readOffset;
}
+ private void ensureOpen() throws IOException {
+ if (isClosed())
+ throw new IOException("input stream has been closed");
+ }
+
private boolean hasMoreTransfers() {
return !completedTransferQueue.isEmpty();
}
@@ -190,14 +226,23 @@ private void receiveMoreData() throws IOException {
}
private Transfer waitForCompletedTransfer() {
- while (true) {
- try {
- var transfer = completedTransferQueue.take();
- numOutstandingTransfers -= 1;
- return transfer;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
+ // Defer interruption: keep a local flag instead of re-asserting the interrupt
+ // inside the loop (which would make the next take() throw immediately and
+ // busy-spin). Re-assert once the completion has actually arrived.
+ var wasInterrupted = false;
+ try {
+ while (true) {
+ try {
+ var transfer = completedTransferQueue.take();
+ numOutstandingTransfers -= 1;
+ return transfer;
+ } catch (InterruptedException _) {
+ wasInterrupted = true;
+ }
}
+ } finally {
+ if (wasInterrupted)
+ Thread.currentThread().interrupt();
}
}
@@ -210,14 +255,40 @@ private void onCompletion(Transfer transfer) {
completedTransferQueue.add(transfer);
}
+ @SuppressWarnings("java:S2142")
private void collectOutstandingTransfers() {
- // wait until completion handlers have been called
- while (numOutstandingTransfers > 0)
- waitForCompletedTransfer();
+ // Wait until the completion handlers have been called. This is a teardown path,
+ // so the wait is bounded: if a completion is never delivered (device unplugged,
+ // or the completion is lost in a source-removal race), abandon the transfer and
+ // log a warning instead of blocking the application thread forever.
+ var deadline = System.currentTimeMillis() + TEARDOWN_TIMEOUT_MS;
+ var wasInterrupted = false;
+
+ while (numOutstandingTransfers > 0) {
+ var remaining = deadline - System.currentTimeMillis();
+ if (remaining <= 0) {
+ LOG.log(WARNING,
+ "abandoning {0} outstanding transfer(s) during input stream teardown - no completion within {1} ms",
+ numOutstandingTransfers, TEARDOWN_TIMEOUT_MS);
+ break;
+ }
+
+ try {
+ if (completedTransferQueue.poll(remaining, TimeUnit.MILLISECONDS) != null)
+ numOutstandingTransfers -= 1;
+ } catch (InterruptedException _) {
+ // defer the interrupt: keep polling the remaining time without re-setting
+ // the flag (avoids a busy-spin), then re-assert it once we are done
+ wasInterrupted = true;
+ }
+ }
+
+ if (wasInterrupted)
+ Thread.currentThread().interrupt();
completedTransferQueue.clear();
currentTransfer = null;
- arena.close();
+ //arena.close();
}
protected abstract void submitTransferIn(Transfer transfer);
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/EndpointOutputStream.java b/java-does-usb/src/main/java/net/codecrete/usb/common/EndpointOutputStream.java
index 0a8c7f82..c371939e 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/common/EndpointOutputStream.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/EndpointOutputStream.java
@@ -7,15 +7,22 @@
package net.codecrete.usb.common;
-import net.codecrete.usb.USBDirection;
+import net.codecrete.usb.UsbDirection;
+import net.codecrete.usb.UsbException;
+import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.Arrays;
+import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import static net.codecrete.usb.common.EndpointStreams.toIOException;
+
+import static java.lang.System.Logger.Level.WARNING;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
/**
@@ -37,7 +44,14 @@
*/
public abstract class EndpointOutputStream extends OutputStream {
- protected USBDeviceImpl device;
+ private static final System.Logger LOG = System.getLogger(EndpointOutputStream.class.getName());
+
+ // Maximum time (ms) to wait for outstanding transfers to complete during teardown.
+ // A completion that is never delivered (e.g. after an unplug) then degrades to a
+ // logged warning instead of a permanent hang.
+ private static final long TEARDOWN_TIMEOUT_MS = 1000;
+
+ protected UsbDeviceImpl device;
protected final int endpointNumber;
protected final Arena arena;
// Endpoint packet size
@@ -60,16 +74,17 @@ public abstract class EndpointOutputStream extends OutputStream {
* @param endpointNumber endpoint number
* @param bufferSize approximate buffer size (in bytes)
*/
- protected EndpointOutputStream(USBDeviceImpl device, int endpointNumber, int bufferSize) {
+ protected EndpointOutputStream(UsbDeviceImpl device, int endpointNumber, int bufferSize) {
this.device = device;
this.endpointNumber = endpointNumber;
- arena = Arena.ofShared();
+ //arena = Arena.ofShared(); // not supported by GraalVM
+ arena = Arena.ofAuto();
- packetSize = device.getEndpoint(USBDirection.OUT, endpointNumber).packetSize();
+ packetSize = device.getEndpoint(UsbDirection.OUT, endpointNumber).getPacketSize();
// use between 4 and 32 packets per transfer (256B to 2KB for FS, 2KB to 16KB for HS)
var numPacketsPerTransfer = (int) Math.round(Math.sqrt((double) bufferSize / packetSize));
- numPacketsPerTransfer = Math.min(Math.max(numPacketsPerTransfer, 4), 32);
+ numPacketsPerTransfer = Math.clamp(numPacketsPerTransfer, 4, 32);
transferSize = numPacketsPerTransfer * packetSize;
// use at least 2 outstanding transfers (3 in total)
@@ -102,54 +117,183 @@ public void close() throws IOException {
if (isClosed())
return;
- if (!hasError)
- flush();
- else
- waitForOutstandingTransfers();
+ // Teardown path: every wait is bounded by a single deadline so a lost completion
+ // (device unplugged, or completion dropped in a source-removal race) degrades to a
+ // logged warning instead of hanging the application thread. Unlike the public
+ // flush(), this must not route through the unbounded waits.
+ var deadline = System.currentTimeMillis() + TEARDOWN_TIMEOUT_MS;
+
+ try {
+ if (!hasError) {
+ // best-effort: transmit any remaining buffered data (and a ZLP if needed)
+ if (writeOffset > 0)
+ submitForClose(writeOffset, deadline);
+ if (needsZlp && currentTransfer != null)
+ submitForClose(0, deadline);
+ }
+
+ drainOutstandingTransfers(deadline);
+
+ } catch (Exception e) {
+ // teardown must not fail; data-path errors are already surfaced by write()/flush()
+ LOG.log(WARNING, "error while closing output stream - ignoring", e);
+
+ } finally {
+ device = null;
+ availableTransferQueue.clear();
+ currentTransfer = null;
+ //arena.close();
+ }
+ }
+
+ /**
+ * Submits the current transfer during teardown and acquires a replacement,
+ * both bounded by the given deadline.
+ *
+ * Unlike {@link #submitTransfer(int)} this does not recurse into {@link #close()} on error,
+ * and it does not block indefinitely when acquiring the next transfer instance.
+ *
+ *
+ * @param size size of data to be transmitted
+ * @param deadline absolute deadline (ms since epoch) for acquiring the next transfer
+ */
+ private void submitForClose(int size, long deadline) {
+ currentTransfer.setDataSize(size);
+ submitTransferOut(currentTransfer);
+
+ synchronized (this) {
+ numOutstandingTransfers += 1;
+ }
- device = null;
- availableTransferQueue.clear();
- currentTransfer = null;
- arena.close();
+ needsZlp = size == packetSize;
+ writeOffset = 0;
+ // if no transfer becomes available within the deadline, currentTransfer stays null,
+ // the drain below still bounded-waits for the in-flight transfer to complete
+ currentTransfer = pollAvailableTransfer(deadline);
+ }
+
+ /**
+ * Waits for all outstanding transfers to complete, bounded by the given deadline.
+ *
+ * If a completion is not delivered in time, the remaining transfers are abandoned and a
+ * warning is logged.
+ *
+ *
+ * @param deadline absolute deadline (ms since epoch)
+ */
+ private void drainOutstandingTransfers(long deadline) {
+ int numTransfers;
+ synchronized (this) {
+ numTransfers = numOutstandingTransfers + availableTransferQueue.size();
+ }
+
+ for (var i = 0; i < numTransfers; i++) {
+ if (pollAvailableTransfer(deadline) == null) {
+ int abandoned;
+ synchronized (this) {
+ abandoned = numOutstandingTransfers;
+ }
+ LOG.log(WARNING,
+ "abandoning {0} outstanding transfer(s) during output stream teardown - no completion within {1} ms",
+ abandoned, TEARDOWN_TIMEOUT_MS);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Waits until a transfer instance is available for use, bounded by the given deadline.
+ *
+ * @param deadline absolute deadline (ms since epoch)
+ * @return transfer instance ready for use, or {@code null} if the deadline expired
+ */
+ private Transfer pollAvailableTransfer(long deadline) {
+ var wasInterrupted = false;
+ try {
+ while (true) {
+ var remaining = deadline - System.currentTimeMillis();
+ if (remaining <= 0)
+ return null;
+
+ try {
+ var transfer = availableTransferQueue.poll(remaining, TimeUnit.MILLISECONDS);
+ if (transfer == null)
+ return null;
+
+ // surface a transfer error unless we are already in the error path
+ var result = transfer.resultCode();
+ if (result != 0 && !hasError) {
+ transfer.setResultCode(0);
+ device.throwOSException(result, "error occurred while transmitting to endpoint %d", endpointNumber);
+ }
+
+ return transfer;
+
+ } catch (InterruptedException _) {
+ // defer the interrupt: keep polling the remaining time without re-setting
+ // the flag (avoids a busy-spin), then re-assert it once we are done
+ wasInterrupted = true;
+ }
+ }
+ } finally {
+ if (wasInterrupted)
+ Thread.currentThread().interrupt();
+ }
}
@Override
public void write(int b) throws IOException {
- checkIsOpen();
+ ensureOpen();
+
+ try {
+ currentTransfer.data().set(JAVA_BYTE, writeOffset, (byte) b);
+ writeOffset += 1;
+ if (writeOffset == transferSize)
+ submitTransfer(writeOffset);
- currentTransfer.data().set(JAVA_BYTE, writeOffset, (byte) b);
- writeOffset += 1;
- if (writeOffset == transferSize)
- submitTransfer(writeOffset);
+ } catch (UsbException e) {
+ throw toIOException(e);
+ }
}
@Override
- public void write(byte[] b, int off, int len) throws IOException {
- checkIsOpen();
+ public void write(byte @NotNull [] b, int off, int len) throws IOException {
+ Objects.checkFromIndexSize(off, len, b.length);
+ ensureOpen();
- while (len > 0) {
- var chunkSize = Math.min(len, transferSize - writeOffset);
- MemorySegment.copy(b, off, currentTransfer.data(), JAVA_BYTE, writeOffset, chunkSize);
- writeOffset += chunkSize;
- off += chunkSize;
- len -= chunkSize;
+ try {
+ while (len > 0) {
+ var chunkSize = Math.min(len, transferSize - writeOffset);
+ MemorySegment.copy(b, off, currentTransfer.data(), JAVA_BYTE, writeOffset, chunkSize);
+ writeOffset += chunkSize;
+ off += chunkSize;
+ len -= chunkSize;
+
+ if (writeOffset == transferSize)
+ submitTransfer(writeOffset);
+ }
- if (writeOffset == transferSize)
- submitTransfer(writeOffset);
+ } catch (UsbException e) {
+ throw toIOException(e);
}
}
@Override
public void flush() throws IOException {
- checkIsOpen();
+ ensureOpen();
+
+ try {
+ if (writeOffset > 0)
+ submitTransfer(writeOffset);
- if (writeOffset > 0)
- submitTransfer(writeOffset);
+ if (needsZlp)
+ submitTransfer(0);
- if (needsZlp)
- submitTransfer(0);
+ waitForOutstandingTransfers();
- waitForOutstandingTransfers();
+ } catch (UsbException e) {
+ throw toIOException(e);
+ }
}
/**
@@ -220,22 +364,31 @@ private void waitForOutstandingTransfers() {
* @return transfer instance ready for use
*/
private Transfer waitForAvailableTransfer() {
- while (true) {
- try {
- var transfer = availableTransferQueue.take();
-
- // check for error
- var result = transfer.resultCode();
- if (result != 0 && !hasError) {
- transfer.setResultCode(0);
- device.throwOSException(result, "error occurred while transmitting to endpoint %d", endpointNumber);
- }
+ // Defer interruption: keep a local flag instead of re-asserting the interrupt
+ // inside the loop (which would make the next take() throw immediately and
+ // busy-spin). Re-assert once a transfer has actually become available.
+ var wasInterrupted = false;
+ try {
+ while (true) {
+ try {
+ var transfer = availableTransferQueue.take();
- return transfer;
+ // check for error
+ var result = transfer.resultCode();
+ if (result != 0 && !hasError) {
+ transfer.setResultCode(0);
+ device.throwOSException(result, "error occurred while transmitting to endpoint %d", endpointNumber);
+ }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
+ return transfer;
+
+ } catch (InterruptedException _) {
+ wasInterrupted = true;
+ }
}
+ } finally {
+ if (wasInterrupted)
+ Thread.currentThread().interrupt();
}
}
@@ -254,8 +407,8 @@ private synchronized void onCompletion(Transfer transfer) {
protected void configureEndpoint() {
}
- private void checkIsOpen() throws IOException {
+ private void ensureOpen() throws IOException {
if (isClosed())
- throw new IOException("endpoint output stream has been closed");
+ throw new IOException("output stream has been closed");
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/EndpointStreams.java b/java-does-usb/src/main/java/net/codecrete/usb/common/EndpointStreams.java
new file mode 100644
index 00000000..5e9fda43
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/EndpointStreams.java
@@ -0,0 +1,44 @@
+//
+// Java Does USB
+// Copyright (c) 2023 Manuel Bleichenbacher
+// Licensed under MIT License
+// https://opensource.org/licenses/MIT
+//
+
+package net.codecrete.usb.common;
+
+import net.codecrete.usb.UsbException;
+import net.codecrete.usb.UsbTimeoutException;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+/**
+ * Helpers shared by {@link EndpointInputStream} and {@link EndpointOutputStream}.
+ */
+final class EndpointStreams {
+
+ private EndpointStreams() {
+ }
+
+ /**
+ * Wraps a USB error in an {@link IOException} so it is surfaced through the
+ * {@link java.io.InputStream}/{@link java.io.OutputStream} contract.
+ *
+ * A transfer timeout is mapped to {@link InterruptedIOException} (java.io's "I/O timed out").
+ * All other USB errors become a plain {@link IOException} with the {@link UsbException} as cause,
+ * which preserves the USB error code and any stall/timeout subtype for callers that inspect it.
+ *
+ *
+ * @param e the USB error
+ * @return the corresponding I/O exception
+ */
+ static IOException toIOException(UsbException e) {
+ if (e instanceof UsbTimeoutException) {
+ var ioException = new InterruptedIOException(e.getMessage());
+ ioException.initCause(e);
+ return ioException;
+ }
+ return new IOException(e.getMessage(), e);
+ }
+}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/ScopeCleanup.java b/java-does-usb/src/main/java/net/codecrete/usb/common/ScopeCleanup.java
index 4624a2d2..44f0d8ce 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/common/ScopeCleanup.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/ScopeCleanup.java
@@ -8,6 +8,7 @@
package net.codecrete.usb.common;
import java.util.ArrayList;
+import java.util.List;
/**
* Auto closeable object for clean up actions.
@@ -31,7 +32,7 @@
*/
public class ScopeCleanup implements AutoCloseable {
- private final ArrayList cleanupActions = new ArrayList<>();
+ private final List cleanupActions = new ArrayList<>();
/**
* Registers a cleanup action to be run later.
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/USBAlternateInterfaceImpl.java b/java-does-usb/src/main/java/net/codecrete/usb/common/USBAlternateInterfaceImpl.java
deleted file mode 100644
index 401ba45e..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/common/USBAlternateInterfaceImpl.java
+++ /dev/null
@@ -1,68 +0,0 @@
-//
-// Java Does USB
-// Copyright (c) 2022 Manuel Bleichenbacher
-// Licensed under MIT License
-// https://opensource.org/licenses/MIT
-//
-
-package net.codecrete.usb.common;
-
-import net.codecrete.usb.USBAlternateInterface;
-import net.codecrete.usb.USBDirection;
-import net.codecrete.usb.USBEndpoint;
-
-import java.util.List;
-
-import static java.util.Collections.unmodifiableList;
-
-public class USBAlternateInterfaceImpl implements USBAlternateInterface {
-
- private final int alternateInterfaceNumber;
- private final int alternateInterfaceClass;
- private final int alternateInterfaceSubclass;
- private final int alternateInterfaceProtocol;
- private final List endpointList;
-
- public USBAlternateInterfaceImpl(int number, int classCode, int subclassCode, int protocolCode,
- List endpoints) {
- alternateInterfaceNumber = number;
- alternateInterfaceClass = classCode;
- alternateInterfaceSubclass = subclassCode;
- alternateInterfaceProtocol = protocolCode;
- endpointList = endpoints;
- }
-
- @Override
- public int number() {
- return alternateInterfaceNumber;
- }
-
- @Override
- public int classCode() {
- return alternateInterfaceClass;
- }
-
- @Override
- public int subclassCode() {
- return alternateInterfaceSubclass;
- }
-
- @Override
- public int protocolCode() {
- return alternateInterfaceProtocol;
- }
-
- @Override
- public List endpoints() {
- return unmodifiableList(endpointList);
- }
-
- void addEndpoint(USBEndpoint endpoint) {
- endpointList.add(endpoint);
- }
-
- @Override
- public USBEndpoint getEndpoint(int endpointNumber, USBDirection direction) {
- return endpointList.stream().filter(ep -> ep.number() == endpointNumber && ep.direction() == direction).findFirst().orElse(null);
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/USBInterfaceImpl.java b/java-does-usb/src/main/java/net/codecrete/usb/common/USBInterfaceImpl.java
deleted file mode 100644
index 07234665..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/common/USBInterfaceImpl.java
+++ /dev/null
@@ -1,66 +0,0 @@
-//
-// Java Does USB
-// Copyright (c) 2022 Manuel Bleichenbacher
-// Licensed under MIT License
-// https://opensource.org/licenses/MIT
-//
-
-package net.codecrete.usb.common;
-
-import net.codecrete.usb.USBAlternateInterface;
-import net.codecrete.usb.USBInterface;
-
-import java.util.Collections;
-import java.util.List;
-
-public class USBInterfaceImpl implements USBInterface {
-
- private final int interfaceNumber;
- private USBAlternateInterface currentAlternate;
- private final List alternateInterfaces;
-
- private boolean claimed;
-
- public USBInterfaceImpl(int number, List alternates) {
- interfaceNumber = number;
- alternateInterfaces = alternates;
- currentAlternate = alternates.get(0);
- }
-
- @Override
- public int number() {
- return interfaceNumber;
- }
-
- @Override
- public boolean isClaimed() {
- return claimed;
- }
-
- public void setClaimed(boolean claimed) {
- this.claimed = claimed;
- }
-
- @Override
- public USBAlternateInterface alternate() {
- return currentAlternate;
- }
-
- @Override
- public USBAlternateInterface getAlternate(int alternateNumber) {
- return alternateInterfaces.stream().filter(alt -> alt.number() == alternateNumber).findFirst().orElse(null);
- }
-
- @Override
- public List alternates() {
- return Collections.unmodifiableList(alternateInterfaces);
- }
-
- void addAlternate(USBAlternateInterface alt) {
- alternateInterfaces.add(alt);
- }
-
- public void setAlternate(USBAlternateInterface alternate) {
- currentAlternate = alternate;
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/UsbAlternateInterfaceImpl.java b/java-does-usb/src/main/java/net/codecrete/usb/common/UsbAlternateInterfaceImpl.java
new file mode 100644
index 00000000..9a7d70b1
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/UsbAlternateInterfaceImpl.java
@@ -0,0 +1,74 @@
+//
+// Java Does USB
+// Copyright (c) 2022 Manuel Bleichenbacher
+// Licensed under MIT License
+// https://opensource.org/licenses/MIT
+//
+
+package net.codecrete.usb.common;
+
+import net.codecrete.usb.UsbAlternateInterface;
+import net.codecrete.usb.UsbDirection;
+import net.codecrete.usb.UsbEndpoint;
+import net.codecrete.usb.UsbException;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Comparator;
+import java.util.List;
+
+import static java.util.Collections.unmodifiableList;
+
+public class UsbAlternateInterfaceImpl implements UsbAlternateInterface {
+
+ private final int alternateInterfaceNumber;
+ private final int alternateInterfaceClass;
+ private final int alternateInterfaceSubclass;
+ private final int alternateInterfaceProtocol;
+ private final List endpointList;
+
+ public UsbAlternateInterfaceImpl(int number, int classCode, int subclassCode, int protocolCode,
+ List endpoints) {
+ alternateInterfaceNumber = number;
+ alternateInterfaceClass = classCode;
+ alternateInterfaceSubclass = subclassCode;
+ alternateInterfaceProtocol = protocolCode;
+ endpointList = endpoints;
+ endpointList.sort(Comparator.comparingInt(UsbEndpoint::getNumber));
+ }
+
+ @Override
+ public int getNumber() {
+ return alternateInterfaceNumber;
+ }
+
+ @Override
+ public int getClassCode() {
+ return alternateInterfaceClass;
+ }
+
+ @Override
+ public int getSubclassCode() {
+ return alternateInterfaceSubclass;
+ }
+
+ @Override
+ public int getProtocolCode() {
+ return alternateInterfaceProtocol;
+ }
+
+ @Override
+ public @NotNull List getEndpoints() {
+ return unmodifiableList(endpointList);
+ }
+
+ void addEndpoint(UsbEndpoint endpoint) {
+ endpointList.add(endpoint);
+ }
+
+ @Override
+ public @NotNull UsbEndpoint getEndpoint(int endpointNumber, UsbDirection direction) {
+ return endpointList.stream()
+ .filter(ep -> ep.getNumber() == endpointNumber && ep.getDirection() == direction).findFirst()
+ .orElseThrow(() -> new UsbException(String.format("Endpoint %d (%s) does not exist", endpointNumber, direction)));
+ }
+}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/USBDeviceImpl.java b/java-does-usb/src/main/java/net/codecrete/usb/common/UsbDeviceImpl.java
similarity index 58%
rename from java-does-usb/src/main/java/net/codecrete/usb/common/USBDeviceImpl.java
rename to java-does-usb/src/main/java/net/codecrete/usb/common/UsbDeviceImpl.java
index 6db36ed5..41a1fb4f 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/common/USBDeviceImpl.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/UsbDeviceImpl.java
@@ -7,18 +7,35 @@
package net.codecrete.usb.common;
-import net.codecrete.usb.*;
+import net.codecrete.usb.UsbDevice;
+import net.codecrete.usb.UsbDirection;
+import net.codecrete.usb.UsbEndpoint;
+import net.codecrete.usb.UsbException;
+import net.codecrete.usb.UsbInterface;
+import net.codecrete.usb.UsbTimeoutException;
+import net.codecrete.usb.UsbTransferType;
+import net.codecrete.usb.Version;
import net.codecrete.usb.usbstandard.DeviceDescriptor;
+import org.jetbrains.annotations.NotNull;
import java.lang.foreign.MemorySegment;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.function.IntFunction;
+import static java.lang.System.Logger.Level.WARNING;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
-public abstract class USBDeviceImpl implements USBDevice {
+public abstract class UsbDeviceImpl implements UsbDevice {
+
+ private static final System.Logger LOG = System.getLogger(UsbDeviceImpl.class.getName());
+
+ // Maximum time (ms) to wait for the completion of a transfer that was aborted due to a
+ // timeout. If the abort's completion is never delivered (e.g. after an unplug), the wait
+ // degrades to a logged warning instead of a permanent hang. The transfer is then abandoned.
+ private static final long ABORT_COMPLETION_TIMEOUT_MS = 1000;
/**
* Operating system-specific device ID used for {@link #equals(Object)} and {@link #hashCode()}.
@@ -29,7 +46,7 @@ public abstract class USBDeviceImpl implements USBDevice {
*/
protected final Object uniqueDeviceId;
- protected List interfaceList;
+ protected List interfaceList;
protected byte[] rawDeviceDescriptor;
@@ -44,8 +61,11 @@ public abstract class USBDeviceImpl implements USBDevice {
protected int deviceClass;
protected int deviceSubclass;
protected int deviceProtocol;
- protected Version versionUSB;
+ protected Version versionUsb;
protected Version versionDevice;
+ // volatile: written by the device monitor thread in disconnect(), read unlocked
+ // via isConnected() and checkIsClosed()
+ protected volatile boolean connected;
/**
* Creates a new instance.
@@ -54,93 +74,106 @@ public abstract class USBDeviceImpl implements USBDevice {
* @param vendorId USB vendor ID
* @param productId USB product ID
*/
- protected USBDeviceImpl(Object id, int vendorId, int productId) {
+ protected UsbDeviceImpl(Object id, int vendorId, int productId) {
assert id != null;
uniqueDeviceId = id;
vid = vendorId;
pid = productId;
+ connected = true;
}
@Override
public void detachStandardDrivers() {
- if (isOpen())
- throw new USBException("detachStandardDrivers() must not be called while the device is open");
+ if (isOpened())
+ throw new UsbException("detachStandardDrivers() must not be called while the device is open");
// default implementation: do nothing
}
@Override
public void attachStandardDrivers() {
- if (isOpen())
- throw new USBException("attachStandardDrivers() must not be called while the device is open");
+ if (isOpened())
+ throw new UsbException("attachStandardDrivers() must not be called while the device is open");
// default implementation: do nothing
}
protected void checkIsOpen() {
- if (!isOpen())
- throw new USBException("device needs to be opened first for this operation");
+ if (!isOpened())
+ throw new UsbException("device needs to be opened first for this operation");
+ }
+
+ protected void checkIsClosed(String message) {
+ if (!connected)
+ throw new UsbException("device has been disconnected");
+ if (isOpened())
+ throw new UsbException(message);
+ }
+
+ protected synchronized void disconnect() {
+ connected = false;
+ close();
}
@Override
- public int productId() {
+ public int getProductId() {
return pid;
}
@Override
- public int vendorId() {
+ public int getVendorId() {
return vid;
}
@Override
- public String product() {
+ public String getProduct() {
return productString;
}
@Override
- public String manufacturer() {
+ public String getManufacturer() {
return manufacturerString;
}
@Override
- public String serialNumber() {
+ public String getSerialNumber() {
return serialString;
}
@Override
- public int classCode() {
+ public int getClassCode() {
return deviceClass;
}
@Override
- public int subclassCode() {
+ public int getSubclassCode() {
return deviceSubclass;
}
@Override
- public int protocolCode() {
+ public int getProtocolCode() {
return deviceProtocol;
}
@Override
- public Version usbVersion() {
- return versionUSB;
+ public @NotNull Version getUsbVersion() {
+ return versionUsb;
}
@Override
- public Version deviceVersion() {
+ public @NotNull Version getDeviceVersion() {
return versionDevice;
}
@Override
- public byte[] configurationDescriptor() {
+ public byte @NotNull [] getConfigurationDescriptor() {
return rawConfigurationDescriptor;
}
@Override
- public byte[] deviceDescriptor() {
+ public byte @NotNull [] getDeviceDescriptor() {
return rawDeviceDescriptor;
}
@@ -148,6 +181,11 @@ public Object getUniqueId() {
return uniqueDeviceId;
}
+ @Override
+ public boolean isConnected() {
+ return connected;
+ }
+
/**
* Sets the class codes and version for the device descriptor.
*
@@ -159,7 +197,7 @@ public void setFromDeviceDescriptor(MemorySegment descriptor) {
deviceClass = deviceDescriptor.deviceClass() & 255;
deviceSubclass = deviceDescriptor.deviceSubClass() & 255;
deviceProtocol = deviceDescriptor.deviceProtocol() & 255;
- versionUSB = new Version(deviceDescriptor.usbVersion());
+ versionUsb = new Version(deviceDescriptor.usbVersion());
versionDevice = new Version(deviceDescriptor.deviceVersion());
}
@@ -173,6 +211,7 @@ protected Configuration setConfigurationDescriptor(MemorySegment descriptor) {
rawConfigurationDescriptor = descriptor.toArray(JAVA_BYTE);
var configuration = ConfigurationParser.parseConfigurationDescriptor(descriptor);
interfaceList = configuration.interfaces();
+ interfaceList.sort(Comparator.comparingInt(UsbInterface::getNumber));
return configuration;
}
@@ -192,7 +231,7 @@ public void setProductStrings(String manufacturer, String product, String serial
/**
* Sets the product strings from the device descriptor.
*
- * To lookup the string, a lookup function is provided. It takes the
+ * To look up the string, a lookup function is provided. It takes the
* string ID and returns the string from the string descriptor.
*
*
@@ -213,51 +252,51 @@ public void setClassCodes(int classCode, int subclassCode, int protocolCode) {
}
public void setVersions(int usbVersion, int deviceVersion) {
- versionUSB = new Version(usbVersion);
+ versionUsb = new Version(usbVersion);
versionDevice = new Version(deviceVersion);
}
@Override
- public List interfaces() {
+ public @NotNull List getInterfaces() {
return Collections.unmodifiableList(interfaceList);
}
public void setClaimed(int interfaceNumber, boolean claimed) {
for (var intf : interfaceList) {
- if (intf.number() == interfaceNumber) {
- ((USBInterfaceImpl) intf).setClaimed(claimed);
+ if (intf.getNumber() == interfaceNumber) {
+ ((UsbInterfaceImpl) intf).setClaimed(claimed);
return;
}
}
- throw new USBException("internal error (interface not found)");
+ throw new UsbException("internal error (interface not found)");
}
@Override
- public USBInterfaceImpl getInterface(int interfaceNumber) {
- return (USBInterfaceImpl) interfaceList.stream().filter(intf -> intf.number() == interfaceNumber).findFirst().orElse(null);
+ public @NotNull UsbInterfaceImpl getInterface(int interfaceNumber) {
+ return (UsbInterfaceImpl) interfaceList.stream()
+ .filter(intf -> intf.getNumber() == interfaceNumber).findFirst()
+ .orElseThrow(() -> new UsbException(String.format("USB device has no interface %d", interfaceNumber)));
}
- public USBInterfaceImpl getInterfaceWithCheck(int interfaceNumber, boolean isClaimed) {
+ public UsbInterfaceImpl getInterfaceWithCheck(int interfaceNumber, boolean isClaimed) {
var intf = getInterface(interfaceNumber);
- if (intf == null)
- throw new USBException(String.format("invalid interface number: %d", interfaceNumber));
if (isClaimed && !intf.isClaimed()) {
- throw new USBException(String.format("interface %d must be claimed first", interfaceNumber));
+ throw new UsbException(String.format("interface %d must be claimed first", interfaceNumber));
} else if (!isClaimed && intf.isClaimed()) {
- throw new USBException(String.format("interface %d has already been claimed", interfaceNumber));
+ throw new UsbException(String.format("interface %d has already been claimed", interfaceNumber));
}
return intf;
}
@Override
- public USBEndpoint getEndpoint(USBDirection direction, int endpointNumber) {
+ public @NotNull UsbEndpoint getEndpoint(UsbDirection direction, int endpointNumber) {
for (var intf : interfaceList) {
- for (var endpoint : intf.alternate().endpoints()) {
- if (endpoint.direction() == direction && endpoint.number() == endpointNumber)
+ for (var endpoint : intf.getCurrentAlternate().getEndpoints()) {
+ if (endpoint.getDirection() == direction && endpoint.getNumber() == endpointNumber)
return endpoint;
}
}
- return null;
+ throw new UsbException(String.format("endpoint %d (%s) does not exist", endpointNumber, direction.name()));
}
/**
@@ -270,20 +309,20 @@ public USBEndpoint getEndpoint(USBDirection direction, int endpointNumber) {
* @return endpoint
*/
@SuppressWarnings("java:S3776")
- protected EndpointInfo getEndpoint(USBDirection direction, int endpointNumber, USBTransferType transferType1,
- USBTransferType transferType2) {
+ protected EndpointInfo getEndpoint(UsbDirection direction, int endpointNumber, UsbTransferType transferType1,
+ UsbTransferType transferType2) {
checkIsOpen();
if (endpointNumber >= 1 && endpointNumber <= 127) {
for (var intf : interfaceList) {
if (intf.isClaimed()) {
- for (var ep : intf.alternate().endpoints()) {
- if (ep.number() == endpointNumber && ep.direction() == direction
- && (ep.transferType() == transferType1 || ep.transferType() == transferType2))
- return new EndpointInfo(intf.number(), ep.number(),
- (byte) (endpointNumber | (direction == USBDirection.IN ? 0x80 : 0)),
- ep.packetSize(), ep.transferType());
+ for (var ep : intf.getCurrentAlternate().getEndpoints()) {
+ if (ep.getNumber() == endpointNumber && ep.getDirection() == direction
+ && (ep.getTransferType() == transferType1 || ep.getTransferType() == transferType2))
+ return new EndpointInfo(intf.getNumber(), ep.getNumber(),
+ (byte) (endpointNumber | (direction == UsbDirection.IN ? 0x80 : 0)),
+ ep.getPacketSize(), ep.getTransferType());
}
}
}
@@ -293,27 +332,27 @@ protected EndpointInfo getEndpoint(USBDirection direction, int endpointNumber, U
return null; // will never be reached
}
- protected void throwInvalidEndpointException(USBDirection direction, int endpointNumber,
- USBTransferType transferType1, USBTransferType transferType2) {
+ protected void throwInvalidEndpointException(UsbDirection direction, int endpointNumber,
+ UsbTransferType transferType1, UsbTransferType transferType2) {
String transferTypeDesc;
if (transferType2 == null)
transferTypeDesc = transferType1.name();
else
transferTypeDesc = String.format("%s or %s", transferType1.name(), transferType2.name());
- throw new USBException(String.format(
+ throw new UsbException(String.format(
"endpoint number %d does not exist, is not part of a claimed interface or is not valid for %s transfer in %s direction",
endpointNumber, transferTypeDesc, direction.name()));
}
- protected int getInterfaceNumber(USBDirection direction, int endpointNumber) {
+ protected int getInterfaceNumber(UsbDirection direction, int endpointNumber) {
if (endpointNumber < 1 || endpointNumber > 127)
return -1;
for (var intf : interfaceList) {
if (intf.isClaimed()) {
- for (var ep : intf.alternate().endpoints()) {
- if (ep.number() == endpointNumber && ep.direction() == direction)
- return intf.number();
+ for (var ep : intf.getCurrentAlternate().getEndpoints()) {
+ if (ep.getNumber() == endpointNumber && ep.getDirection() == direction)
+ return intf.getNumber();
}
}
}
@@ -322,21 +361,21 @@ protected int getInterfaceNumber(USBDirection direction, int endpointNumber) {
}
@Override
- public void transferOut(int endpointNumber, byte[] data) {
+ public void transferOut(int endpointNumber, byte @NotNull [] data) {
transferOut(endpointNumber, data, 0, data.length, 0);
}
@Override
- public void transferOut(int endpointNumber, byte[] data, int timeout) {
+ public void transferOut(int endpointNumber, byte @NotNull [] data, int timeout) {
transferOut(endpointNumber, data, 0, data.length, timeout);
}
@Override
- public byte[] transferIn(int endpointNumber) {
+ public byte @NotNull [] transferIn(int endpointNumber) {
return transferIn(endpointNumber, 0);
}
- protected void waitForTransfer(Transfer transfer, int timeout, USBDirection direction, int endpointNumber) {
+ protected void waitForTransfer(Transfer transfer, int timeout, UsbDirection direction, int endpointNumber) {
if (timeout <= 0) {
waitNoTimeout(transfer);
@@ -346,9 +385,17 @@ protected void waitForTransfer(Transfer transfer, int timeout, USBDirection dire
// test for timeout
if (hasTimedOut && transfer.resultCode() == 0) {
abortTransfers(direction, endpointNumber);
- waitNoTimeout(transfer);
- throw new USBTimeoutException(getOperationDescription(direction, endpointNumber)
- + "aborted due to timeout");
+
+ // Wait for the abort's completion, but bounded: if it never arrives (device
+ // vanished such that neither the transfer nor the abort yields a callback),
+ // abandon the transfer instead of blocking forever. Abandoning is safe because
+ // buffers reaching this path come from an auto arena and survive a late completion.
+ var abortCompleted = !waitWithTimeout(transfer, (int) ABORT_COMPLETION_TIMEOUT_MS);
+ if (!abortCompleted)
+ LOG.log(WARNING, "abort completion for {0} did not arrive within {1} ms - abandoning transfer",
+ getOperationDescription(direction, endpointNumber), ABORT_COMPLETION_TIMEOUT_MS);
+
+ throw new UsbTimeoutException(getOperationDescription(direction, endpointNumber) + " aborted due to timeout");
}
}
@@ -359,37 +406,50 @@ protected void waitForTransfer(Transfer transfer, int timeout, USBDirection dire
}
}
- @SuppressWarnings("java:S2273")
+ @SuppressWarnings({"java:S2273", "java:S2142"})
private static void waitNoTimeout(Transfer transfer) {
- // wait for transfer
+ // wait for transfer.
+ // Defer interruption: keep a local flag instead of re-asserting the interrupt
+ // inside the loop (which would make the next wait() throw immediately and
+ // busy-spin). Re-assert once the transfer has actually completed.
+ var wasInterrupted = false;
while (transfer.resultSize() == -1) {
try {
transfer.wait();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
+ } catch (InterruptedException _) {
+ wasInterrupted = true;
}
}
+ if (wasInterrupted)
+ Thread.currentThread().interrupt();
}
- @SuppressWarnings("java:S2273")
+ @SuppressWarnings({"java:S2273", "java:S2142"})
private static boolean waitWithTimeout(Transfer transfer, int timeout) {
- // wait for transfer to complete, or abort when timeout occurs
+ // wait for transfer to complete, or abort when timeout occurs.
+ // Defer interruption: keep a local flag instead of re-asserting the interrupt
+ // inside the loop (which would make the next wait() throw immediately and
+ // busy-spin). The remaining timeout is recomputed on both the normal and the
+ // interrupted path, so the wait stays bounded by the original expiration.
var expiration = System.currentTimeMillis() + timeout;
long remainingTimeout = timeout;
+ var wasInterrupted = false;
while (remainingTimeout > 0 && transfer.resultSize() == -1) {
try {
transfer.wait(remainingTimeout);
- remainingTimeout = expiration - System.currentTimeMillis();
-
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
+ } catch (InterruptedException _) {
+ wasInterrupted = true;
}
+ remainingTimeout = expiration - System.currentTimeMillis();
}
+ if (wasInterrupted)
+ Thread.currentThread().interrupt();
+
return remainingTimeout <= 0;
}
- protected static String getOperationDescription(USBDirection direction, int endpointNumber) {
+ protected static String getOperationDescription(UsbDirection direction, int endpointNumber) {
if (endpointNumber == 0) {
return "control transfer";
} else {
@@ -436,7 +496,7 @@ public boolean equals(Object o) {
return true;
if (o == null || getClass() != o.getClass())
return false;
- var that = (USBDeviceImpl) o;
+ var that = (UsbDeviceImpl) o;
return uniqueDeviceId.equals(that.uniqueDeviceId);
}
@@ -447,10 +507,11 @@ public int hashCode() {
@Override
public String toString() {
- return "VID: 0x" + String.format("%04x", vid) + ", PID: 0x" + String.format("%04x", pid) + ", " + "manufacturer: " + manufacturerString + ", product: " + productString + ", serial: " + serialString + ", ID: " + uniqueDeviceId;
+ return String.format("VID: 0x%04x, PID: 0x%04x, manufacturer: %s, product: %s, serial: %s, ID: %s",
+ vid, pid, manufacturerString, productString, serialString, uniqueDeviceId);
}
public record EndpointInfo(int interfaceNumber, int endpointNumber, byte endpointAddress, int packetSize,
- USBTransferType transferType) {
+ UsbTransferType transferType) {
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/USBDeviceRegistry.java b/java-does-usb/src/main/java/net/codecrete/usb/common/UsbDeviceRegistry.java
similarity index 77%
rename from java-does-usb/src/main/java/net/codecrete/usb/common/USBDeviceRegistry.java
rename to java-does-usb/src/main/java/net/codecrete/usb/common/UsbDeviceRegistry.java
index b3224dfc..ed7999af 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/common/USBDeviceRegistry.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/UsbDeviceRegistry.java
@@ -7,11 +7,10 @@
package net.codecrete.usb.common;
-import net.codecrete.usb.USBDevice;
-import net.codecrete.usb.USBException;
+import net.codecrete.usb.UsbDevice;
+import net.codecrete.usb.UsbException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@@ -33,14 +32,16 @@
* and builds the initial device list.
*
*/
-public abstract class USBDeviceRegistry {
+@SuppressWarnings("java:S3077")
+public abstract class UsbDeviceRegistry {
- private static final System.Logger LOG = System.getLogger(USBDeviceRegistry.class.getName());
+ private static final System.Logger LOG = System.getLogger(UsbDeviceRegistry.class.getName());
- private List devices;
+ private List devices;
private Throwable failureCause;
- protected Consumer onDeviceConnectedHandler;
- protected Consumer onDeviceDisconnectedHandler;
+ // volatile: set by the application thread, read by the device monitor thread
+ protected volatile Consumer onDeviceConnectedHandler;
+ protected volatile Consumer onDeviceDisconnectedHandler;
private final Lock lock = new ReentrantLock();
private final Condition enumerationComplete = lock.newCondition();
@@ -74,36 +75,39 @@ public void start() {
*
* @return list of devices
*/
- public synchronized List getAllDevices() {
- return Collections.unmodifiableList(devices);
+ public synchronized List getAllDevices() {
+ return devices;
}
- public void setOnDeviceConnected(Consumer handler) {
+ public void setOnDeviceConnected(Consumer handler) {
onDeviceConnectedHandler = handler;
}
- public void setOnDeviceDisconnected(Consumer handler) {
+ public void setOnDeviceDisconnected(Consumer handler) {
onDeviceDisconnectedHandler = handler;
}
- protected void emitOnDeviceConnected(USBDevice device) {
- if (onDeviceConnectedHandler == null)
+ protected void emitOnDeviceConnected(UsbDevice device) {
+ // read once so a concurrent setOnDeviceConnected(null) cannot fail between check and call
+ var handler = onDeviceConnectedHandler;
+ if (handler == null)
return;
try {
- onDeviceConnectedHandler.accept(device);
+ handler.accept(device);
} catch (Exception e) {
LOG.log(WARNING, "unhandled exception in 'onDeviceConnected' handler - ignoring", e);
}
}
- protected void emitOnDeviceDisconnected(USBDevice device) {
- if (onDeviceDisconnectedHandler == null)
+ protected void emitOnDeviceDisconnected(UsbDevice device) {
+ var handler = onDeviceDisconnectedHandler;
+ if (handler == null)
return;
try {
- onDeviceDisconnectedHandler.accept(device);
+ handler.accept(device);
} catch (Exception e) {
LOG.log(WARNING, "unhandled exception in 'onDeviceDisconnected' handler - ignoring", e);
@@ -136,7 +140,7 @@ protected void startDeviceMonitor(Runnable monitorTask) {
}
if (failureCause != null)
- throw new USBException("initial device enumeration has failed", failureCause);
+ throw new UsbException("initial device enumeration has failed", failureCause);
}
/**
@@ -169,7 +173,7 @@ protected void enumerationFailed(Throwable e) {
*
* @param deviceList the device list
*/
- protected void setInitialDeviceList(List deviceList) {
+ protected void setInitialDeviceList(List deviceList) {
synchronized (this) {
devices = deviceList;
}
@@ -181,14 +185,14 @@ protected void setInitialDeviceList(List deviceList) {
*
* @param device device to add
*/
- protected void addDevice(USBDevice device) {
+ protected void addDevice(UsbDevice device) {
synchronized (this) {
// check for duplicates
- if (findDeviceIndex(devices, ((USBDeviceImpl) device).getUniqueId()) >= 0)
+ if (findDeviceIndex(devices, ((UsbDeviceImpl) device).getUniqueId()) >= 0)
return;
// copy list
- var newDeviceList = new ArrayList(devices.size() + 1);
+ var newDeviceList = new ArrayList(devices.size() + 1);
newDeviceList.addAll(devices);
newDeviceList.add(device);
devices = newDeviceList;
@@ -205,9 +209,9 @@ protected void closeAndRemoveDevice(Object deviceId) {
return;
try {
- device.close();
+ ((UsbDeviceImpl) device).disconnect();
} catch (Exception e) {
- LOG.log(INFO, "failed to close USB device - ignoring exception", e);
+ LOG.log(INFO, "failed to close disconnected USB device - ignoring exception", e);
}
removeDevice(deviceId);
@@ -219,7 +223,7 @@ protected void closeAndRemoveDevice(Object deviceId) {
* @param deviceId the unique ID of the device to remove
*/
protected void removeDevice(Object deviceId) {
- USBDevice device;
+ UsbDevice device;
synchronized (this) {
// locate device to be removed
int index = findDeviceIndex(devices, deviceId);
@@ -244,9 +248,9 @@ protected void removeDevice(Object deviceId) {
* @param deviceId the unique device ID
* @return return the index, or -1 if the device is not found
*/
- protected int findDeviceIndex(List deviceList, Object deviceId) {
+ protected int findDeviceIndex(List deviceList, Object deviceId) {
for (int i = 0; i < deviceList.size(); i++) {
- var dev = (USBDeviceImpl) deviceList.get(i);
+ var dev = (UsbDeviceImpl) deviceList.get(i);
if (deviceId.equals(dev.getUniqueId()))
return i;
}
@@ -259,7 +263,7 @@ protected int findDeviceIndex(List deviceList, Object deviceId) {
* @param deviceId the unique device ID
* @return return device, or {@code null} if not found.
*/
- protected USBDevice findDevice(Object deviceId) {
+ protected UsbDevice findDevice(Object deviceId) {
int index = findDeviceIndex(devices, deviceId);
if (index < 0)
return null;
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/USBEndpointImpl.java b/java-does-usb/src/main/java/net/codecrete/usb/common/UsbEndpointImpl.java
similarity index 51%
rename from java-does-usb/src/main/java/net/codecrete/usb/common/USBEndpointImpl.java
rename to java-does-usb/src/main/java/net/codecrete/usb/common/UsbEndpointImpl.java
index f896b0f2..f40525ea 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/common/USBEndpointImpl.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/UsbEndpointImpl.java
@@ -7,21 +7,21 @@
package net.codecrete.usb.common;
-import net.codecrete.usb.USBDirection;
-import net.codecrete.usb.USBEndpoint;
-import net.codecrete.usb.USBTransferType;
+import net.codecrete.usb.UsbDirection;
+import net.codecrete.usb.UsbEndpoint;
+import net.codecrete.usb.UsbTransferType;
/**
- * Implementation of {@code USBEndpoint} interface.
+ * Implementation of {@code UsbEndpoint} interface.
*/
-public class USBEndpointImpl implements USBEndpoint {
+public class UsbEndpointImpl implements UsbEndpoint {
private final int endpointNumber;
- private final USBDirection transferDirection;
- private final USBTransferType type;
+ private final UsbDirection transferDirection;
+ private final UsbTransferType type;
private final int maxPacketSize;
- public USBEndpointImpl(int number, USBDirection direction, USBTransferType type, int packetSize) {
+ public UsbEndpointImpl(int number, UsbDirection direction, UsbTransferType type, int packetSize) {
endpointNumber = number;
transferDirection = direction;
this.type = type;
@@ -29,22 +29,22 @@ public USBEndpointImpl(int number, USBDirection direction, USBTransferType type,
}
@Override
- public int number() {
+ public int getNumber() {
return endpointNumber;
}
@Override
- public USBDirection direction() {
+ public UsbDirection getDirection() {
return transferDirection;
}
@Override
- public USBTransferType transferType() {
+ public UsbTransferType getTransferType() {
return type;
}
@Override
- public int packetSize() {
+ public int getPacketSize() {
return maxPacketSize;
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/common/UsbInterfaceImpl.java b/java-does-usb/src/main/java/net/codecrete/usb/common/UsbInterfaceImpl.java
new file mode 100644
index 00000000..64697e87
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/common/UsbInterfaceImpl.java
@@ -0,0 +1,75 @@
+//
+// Java Does USB
+// Copyright (c) 2022 Manuel Bleichenbacher
+// Licensed under MIT License
+// https://opensource.org/licenses/MIT
+//
+
+package net.codecrete.usb.common;
+
+import net.codecrete.usb.UsbAlternateInterface;
+import net.codecrete.usb.UsbException;
+import net.codecrete.usb.UsbInterface;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class UsbInterfaceImpl implements UsbInterface {
+
+ private final int interfaceNumber;
+ private UsbAlternateInterface currentAlternate;
+ private final List alternateInterfaces;
+
+ private boolean claimed;
+
+ public UsbInterfaceImpl(int number, List alternates) {
+ interfaceNumber = number;
+ alternateInterfaces = alternates;
+ currentAlternate = alternates.getFirst();
+ alternateInterfaces.sort(Comparator.comparingInt(UsbAlternateInterface::getNumber));
+ }
+
+ @Override
+ public int getNumber() {
+ return interfaceNumber;
+ }
+
+ @Override
+ public boolean isClaimed() {
+ return claimed;
+ }
+
+ public void setClaimed(boolean claimed) {
+ this.claimed = claimed;
+ }
+
+ @Override
+ public @NotNull UsbAlternateInterface getCurrentAlternate() {
+ return currentAlternate;
+ }
+
+ @Override
+ public @NotNull UsbAlternateInterface getAlternate(int alternateNumber) {
+ return alternateInterfaces.stream()
+ .filter(alt -> alt.getNumber() == alternateNumber).findFirst()
+ .orElseThrow(() -> new UsbException(String.format(
+ "Interface %d does not have an alternate interface setting %d",
+ interfaceNumber, alternateNumber)
+ ));
+ }
+
+ @Override
+ public @NotNull List getAlternates() {
+ return Collections.unmodifiableList(alternateInterfaces);
+ }
+
+ void addAlternate(UsbAlternateInterface alt) {
+ alternateInterfaces.add(alt);
+ }
+
+ public void setAlternate(UsbAlternateInterface alternate) {
+ currentAlternate = alternate;
+ }
+}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/EPoll.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/EPoll.java
new file mode 100644
index 00000000..38b9d5bb
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/EPoll.java
@@ -0,0 +1,140 @@
+//
+// Java Does USB
+// Copyright (c) 2022 Manuel Bleichenbacher
+// Licensed under MIT License
+// https://opensource.org/licenses/MIT
+//
+
+package net.codecrete.usb.linux;
+
+import net.codecrete.usb.linux.gen.errno.errno;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.GroupLayout;
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.VarHandle;
+
+import static java.lang.foreign.ValueLayout.ADDRESS;
+import static java.lang.foreign.ValueLayout.ADDRESS_UNALIGNED;
+import static java.lang.foreign.ValueLayout.JAVA_INT;
+import static java.lang.foreign.ValueLayout.JAVA_INT_UNALIGNED;
+import static java.lang.foreign.ValueLayout.JAVA_LONG_UNALIGNED;
+import static net.codecrete.usb.linux.Linux.allocateErrorState;
+import static net.codecrete.usb.linux.LinuxUsbException.throwLastError;
+import static net.codecrete.usb.linux.gen.epoll.epoll.EPOLL_CTL_ADD;
+import static net.codecrete.usb.linux.gen.epoll.epoll.EPOLL_CTL_DEL;
+
+@SuppressWarnings({"OptionalGetWithoutIsPresent", "SameParameterValue", "java:S100", "java:S1192"})
+public class EPoll {
+ private EPoll() { }
+
+ private static final boolean IS_AARCH64 = System.getProperty("os.arch").equals("aarch64");
+
+ private static final GroupLayout DATA$LAYOUT = MemoryLayout.unionLayout(
+ ADDRESS_UNALIGNED.withName("ptr"),
+ JAVA_INT_UNALIGNED.withName("fd"),
+ JAVA_INT_UNALIGNED.withName("u32"),
+ JAVA_LONG_UNALIGNED.withName("u64")
+ ).withName("epoll_data");
+
+ static final GroupLayout EVENT$LAYOUT = IS_AARCH64 ?
+ MemoryLayout.structLayout(
+ JAVA_INT.withName("events"),
+ MemoryLayout.paddingLayout(4),
+ DATA$LAYOUT.withName("data")
+ ).withName("epoll_event") :
+ MemoryLayout.structLayout(
+ JAVA_INT_UNALIGNED.withName("events"),
+ DATA$LAYOUT.withName("data")
+ ).withName("epoll_event");
+
+ // varhandle to access the "fd" field in an epoll_event array
+ static final VarHandle EVENT_ARRAY_DATA_FD$VH = EVENT$LAYOUT.arrayElementVarHandle(
+ MemoryLayout.PathElement.groupElement("data"),
+ MemoryLayout.PathElement.groupElement("fd")
+ );
+
+ // varhandle to access the "fd" field in an epoll_event struct
+ private static final VarHandle EVENT_DATA_FD$VH = EVENT$LAYOUT.varHandle(
+ MemoryLayout.PathElement.groupElement("data"),
+ MemoryLayout.PathElement.groupElement("fd")
+ );
+
+ private static final VarHandle EVENTS$VH = EVENT$LAYOUT.varHandle(
+ MemoryLayout.PathElement.groupElement("events")
+ );
+
+ private static final Linker linker = Linker.nativeLinker();
+
+ private static final FunctionDescriptor epoll_create1$FUNC = FunctionDescriptor.of(JAVA_INT, JAVA_INT);
+ private static final MethodHandle epoll_create1$MH = linker.downcallHandle(linker.defaultLookup().find(
+ "epoll_create").get(), epoll_create1$FUNC, Linux.ERRNO_STATE);
+
+ private static final FunctionDescriptor epoll_ctl$FUNC = FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT, JAVA_INT, ADDRESS);
+ private static final MethodHandle epoll_ctl$MH = linker.downcallHandle(linker.defaultLookup().find(
+ "epoll_ctl").get(), epoll_ctl$FUNC, Linux.ERRNO_STATE);
+
+ private static final FunctionDescriptor epoll_wait$FUNC = FunctionDescriptor.of(JAVA_INT, JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT);
+ private static final MethodHandle epoll_wait$MH = linker.downcallHandle(linker.defaultLookup().find(
+ "epoll_wait").get(), epoll_wait$FUNC, Linux.ERRNO_STATE);
+
+ static int epoll_create1(int flags, MemorySegment errno) {
+ try {
+ return (int) epoll_create1$MH.invokeExact(errno, flags);
+ } catch (Throwable ex) {
+ throw new AssertionError(ex);
+ }
+ }
+
+ private static int epoll_ctl(int epfd, int op, int fd, MemorySegment event, MemorySegment errno) {
+ try {
+ return (int) epoll_ctl$MH.invokeExact(errno, epfd, op, fd, event);
+ } catch (Throwable ex) {
+ throw new AssertionError(ex);
+ }
+ }
+
+ static int epoll_wait(int epfd, MemorySegment events, int maxevent, int timeout, MemorySegment errno) {
+ try {
+ return (int) epoll_wait$MH.invokeExact(errno, epfd, events, maxevent, timeout);
+ } catch (Throwable ex) {
+ throw new AssertionError(ex);
+ }
+ }
+
+ static void addFileDescriptor(int epfd, int op, int fd) {
+ try (var arena = Arena.ofConfined()) {
+ var errorState = allocateErrorState(arena);
+
+ var event = arena.allocate(EVENT$LAYOUT);
+ EVENTS$VH.set(event, 0, op);
+ EVENT_DATA_FD$VH.set(event, 0, fd);
+ var ret = epoll_ctl(epfd, EPOLL_CTL_ADD(), fd, event, errorState);
+ if (ret < 0)
+ throwLastError(errorState, "internal error (epoll_ctl_add)");
+ }
+ }
+
+ static void removeFileDescriptor(int epfd, int fd) {
+ try (var arena = Arena.ofConfined()) {
+ var errorState = allocateErrorState(arena);
+
+ var event = arena.allocate(EVENT$LAYOUT);
+ EVENTS$VH.set(event, 0, 0);
+ EVENT_DATA_FD$VH.set(event, 0, fd);
+ var ret = epoll_ctl(epfd, EPOLL_CTL_DEL(), fd, event, errorState);
+ if (ret < 0) {
+ var err = Linux.getErrno(errorState);
+ // ignore ENOENT as this method might be called twice when cleaning up,
+ // and EBADF as the file descriptor might have been closed concurrently
+ // (closing deregisters it from epoll anyway)
+ if (err != errno.ENOENT() && err != errno.EBADF())
+ throwLastError(errorState, "internal error (epoll_ctl_del)");
+ }
+ }
+ }
+}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/IO.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/IO.java
index 88b6d364..367deb80 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/IO.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/IO.java
@@ -12,7 +12,9 @@
import java.lang.foreign.MemorySegment;
import java.lang.invoke.MethodHandle;
-import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.ValueLayout.ADDRESS;
+import static java.lang.foreign.ValueLayout.JAVA_INT;
+import static java.lang.foreign.ValueLayout.JAVA_LONG;
@SuppressWarnings({"OptionalGetWithoutIsPresent", "SameParameterValue", "java:S100"})
class IO {
@@ -27,15 +29,6 @@ private IO() {
private static final FunctionDescriptor open$FUNC = FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT);
private static final MethodHandle open$MH = linker.downcallHandle(linker.defaultLookup().find("open").get(),
open$FUNC, Linux.ERRNO_STATE);
- private static final FunctionDescriptor eventfd$FUNC = FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT);
- private static final MethodHandle eventfd$MH = linker.downcallHandle(linker.defaultLookup().find("eventfd").get()
- , eventfd$FUNC, Linux.ERRNO_STATE);
- private static final FunctionDescriptor eventfd_read$FUNC = FunctionDescriptor.of(JAVA_INT, JAVA_INT, ADDRESS);
- private static final MethodHandle eventfd_read$MH = linker.downcallHandle(linker.defaultLookup().find(
- "eventfd_read").get(), eventfd_read$FUNC, Linux.ERRNO_STATE);
- private static final FunctionDescriptor eventfd_write$FUNC = FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_LONG);
- private static final MethodHandle eventfd_write$MH = linker.downcallHandle(linker.defaultLookup().find(
- "eventfd_write").get(), eventfd_write$FUNC, Linux.ERRNO_STATE);
static int ioctl(int fd, long request, MemorySegment segment, MemorySegment errno) {
try {
@@ -52,29 +45,4 @@ static int open(MemorySegment file, int oflag, MemorySegment errno) {
throw new AssertionError(ex);
}
}
-
- static int eventfd(int count, int flags, MemorySegment errno) {
- try {
- return (int) eventfd$MH.invokeExact(errno, count, flags);
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static int eventfd_read(int fd, MemorySegment value, MemorySegment errno) {
- try {
- return (int) eventfd_read$MH.invokeExact(errno, fd, value);
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static int eventfd_write(int fd, long value, MemorySegment errno) {
- try {
- return (int) eventfd_write$MH.invokeExact(errno, fd, value);
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/Linux.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/Linux.java
index c8c7ac70..ab494b91 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/Linux.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/Linux.java
@@ -43,7 +43,7 @@ static MemorySegment allocateErrorState(Arena arena) {
* @return error message
*/
static String getErrorMessage(int err) {
- return string.strerror(err).getUtf8String(0);
+ return string.strerror(err).getString(0);
}
/**
@@ -56,6 +56,6 @@ static String getErrorMessage(int err) {
* @return error code
*/
static int getErrno(MemorySegment errorState) {
- return (int) callState_errno$VH.get(errorState);
+ return (int) callState_errno$VH.get(errorState, 0);
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxAsyncTask.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxAsyncTask.java
index 19d01492..3d6bf71f 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxAsyncTask.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxAsyncTask.java
@@ -7,10 +7,9 @@
package net.codecrete.usb.linux;
-import net.codecrete.usb.USBTransferType;
+import net.codecrete.usb.UsbException;
+import net.codecrete.usb.UsbTransferType;
import net.codecrete.usb.linux.gen.errno.errno;
-import net.codecrete.usb.linux.gen.poll.poll;
-import net.codecrete.usb.linux.gen.poll.pollfd;
import net.codecrete.usb.linux.gen.usbdevice_fs.usbdevfs_urb;
import java.lang.foreign.Arena;
@@ -20,14 +19,26 @@
import java.util.List;
import java.util.Map;
+import static java.lang.System.Logger.Level.ERROR;
import static java.lang.foreign.ValueLayout.ADDRESS;
-import static java.lang.foreign.ValueLayout.JAVA_LONG;
import static net.codecrete.usb.common.ForeignMemory.dereference;
+import static net.codecrete.usb.linux.EPoll.epoll_create1;
+import static net.codecrete.usb.linux.EPoll.epoll_wait;
import static net.codecrete.usb.linux.Linux.allocateErrorState;
-import static net.codecrete.usb.linux.LinuxUSBException.throwException;
-import static net.codecrete.usb.linux.LinuxUSBException.throwLastError;
-import static net.codecrete.usb.linux.USBDevFS.*;
-import static net.codecrete.usb.linux.gen.usbdevice_fs.usbdevice_fs.*;
+import static net.codecrete.usb.linux.LinuxUsbException.throwException;
+import static net.codecrete.usb.linux.LinuxUsbException.throwLastError;
+import static net.codecrete.usb.linux.UsbDevFS.DISCARDURB;
+import static net.codecrete.usb.linux.UsbDevFS.REAPURBNDELAY;
+import static net.codecrete.usb.linux.UsbDevFS.SUBMITURB;
+import static net.codecrete.usb.linux.gen.epoll.epoll.EPOLLOUT;
+import static net.codecrete.usb.linux.gen.epoll.epoll.EPOLLWAKEUP;
+import static net.codecrete.usb.linux.gen.errno.errno.EINTR;
+import static net.codecrete.usb.linux.gen.errno.errno.ENODEV;
+import static net.codecrete.usb.linux.gen.fcntl.fcntl.FD_CLOEXEC;
+import static net.codecrete.usb.linux.gen.usbdevice_fs.usbdevice_fs.USBDEVFS_URB_TYPE_BULK;
+import static net.codecrete.usb.linux.gen.usbdevice_fs.usbdevice_fs.USBDEVFS_URB_TYPE_CONTROL;
+import static net.codecrete.usb.linux.gen.usbdevice_fs.usbdevice_fs.USBDEVFS_URB_TYPE_INTERRUPT;
+import static net.codecrete.usb.linux.gen.usbdevice_fs.usbdevice_fs.USBDEVFS_URB_TYPE_ISO;
/**
* Background task for handling asynchronous transfers.
@@ -46,20 +57,25 @@
*/
@SuppressWarnings("java:S6548")
class LinuxAsyncTask {
+
+ private static final System.Logger LOG = System.getLogger(LinuxAsyncTask.class.getName());
+
/**
* Singleton instance of background task.
*/
static final LinuxAsyncTask INSTANCE = new LinuxAsyncTask();
+ private static final int NUM_EVENTS = 5;
+
private final Arena urbArena = Arena.ofAuto();
/// available URBs
private final List availableURBs = new ArrayList<>();
/// map of URB addresses to transfer (for outstanding transfers)
private final Map transfersByURB = new LinkedHashMap<>();
- /// array of file descriptors using asynchronous completion
- private int[] asyncFds;
- /// file descriptor to notify async IO background thread about an update
- private int asyncIOWakeUpEventFd;
+ /// file descriptor of epoll
+ private int epollFd = -1;
+ /// indicates that the background task has terminated due to an unrecoverable error
+ private boolean taskTerminated;
/**
* Background task for handling asynchronous IO completions.
@@ -67,83 +83,64 @@ class LinuxAsyncTask {
* It polls on all registered file descriptors. If a file descriptor is
* ready, the URB is "reaped".
*
- *
- * Using an additional {@code eventfd} file descriptor, this background task
- * can be woken up to refresh the list of polled file descriptors.
- *
*/
@SuppressWarnings({"java:S2189", "java:S135", "java:S3776"})
private void asyncCompletionTask() {
try (var arena = Arena.ofConfined()) {
var errorState = allocateErrorState(arena);
- var pollfdArray = pollfd.allocateArray(100, arena);
var urbPointerHolder = arena.allocate(ADDRESS);
- var eventfdValueHolder = arena.allocate(JAVA_LONG);
+ var events = arena.allocate(EPoll.EVENT$LAYOUT, NUM_EVENTS);
while (true) {
-
- // get current file descriptor array
- int[] fds;
- synchronized (this) {
- fds = asyncFds;
- }
-
- // poll for event
- fillPollfdArray(pollfdArray, fds);
- var n = fds.length;
- var res = poll.poll(pollfdArray, n + 1L, -1);
- if (res < 0)
- throwException("internal error (poll)");
-
- // acquire lock
- synchronized (this) {
-
- // check for wakeup event
- if ((pollfd.revents$get(pollfdArray, n) & poll.POLLIN()) != 0) {
- // wakeup to refresh list of file descriptors
- res = IO.eventfd_read(asyncIOWakeUpEventFd, eventfdValueHolder, errorState);
- if (res < 0)
- throwLastError(errorState, "internal error (eventfd_read)");
- continue;
+ try {
+ // wait for file descriptor to be ready
+ var res = epoll_wait(epollFd, events, NUM_EVENTS, -1, errorState);
+ if (res < 0) {
+ var err = Linux.getErrno(errorState);
+ if (err == EINTR())
+ continue; // continue on interrupt
+ throwException(err, "internal error (epoll_wait)");
}
- // check for USB device events
- for (var i = 0; i < n + 1; i++) {
- var revent = pollfd.revents$get(pollfdArray, i);
- if (revent == 0)
- continue;
-
- if ((revent & poll.POLLERR()) != 0) {
- // most likely the device has been disconnected,
- // remove from polled FD list to prevent further problems
- var fd = pollfd.fd$get(pollfdArray, i);
- removeFdFromAsyncIOCompletion(fd);
- continue;
- }
-
- // reap URB
- var fd = pollfd.fd$get(pollfdArray, i);
+ // for all ready file descriptors, reap URBs
+ for (int i = 0; i < res; i++) {
+ var fd = (int) EPoll.EVENT_ARRAY_DATA_FD$VH.get(events, 0L, i);
reapURBs(fd, urbPointerHolder, errorState);
}
+
+ } catch (Exception e) {
+ LOG.log(ERROR, "USB async IO thread failed and is terminating; "
+ + "all outstanding transfers will fail, and no further transfers are possible", e);
+ failAllPendingTransfers();
+ return;
}
}
}
}
- void fillPollfdArray(MemorySegment asyncPolls, int[] fds) {
- // device file descriptors
- var n = fds.length;
- for (var i = 0; i < n; i++) {
- pollfd.fd$set(asyncPolls, i, fds[i]);
- pollfd.events$set(asyncPolls, i, (short) poll.POLLOUT());
- pollfd.revents$set(asyncPolls, i, (short) 0);
+ /**
+ * Fails all outstanding transfers and marks this task as terminated.
+ *
+ * Called when the background task can no longer dispatch completions. Waiters blocked
+ * on the failed transfers wake up with an error result instead of hanging forever,
+ * and future submissions are rejected.
+ *
+ */
+ private void failAllPendingTransfers() {
+ var failedTransfers = new ArrayList();
+ synchronized (this) {
+ taskTerminated = true;
+ for (var transfer : transfersByURB.values()) {
+ transfer.urb = null;
+ transfer.setResultCode(errno.ECANCELED());
+ transfer.setResultSize(0);
+ failedTransfers.add(transfer);
+ }
+ transfersByURB.clear();
+ availableURBs.clear();
}
-
- // entry n is the wake-up event file descriptor
- pollfd.fd$set(asyncPolls, n, asyncIOWakeUpEventFd);
- pollfd.events$set(asyncPolls, n, (short) poll.POLLIN());
- pollfd.revents$set(asyncPolls, n, (short) 0);
+ completeTransfers(failedTransfers);
}
/**
@@ -154,38 +151,63 @@ void fillPollfdArray(MemorySegment asyncPolls, int[] fds) {
* @param errorState native memory to receive the errno
*/
private void reapURBs(int fd, MemorySegment urbPointerHolder, MemorySegment errorState) {
- while (true) {
- var res = IO.ioctl(fd, REAPURBNDELAY, urbPointerHolder, errorState);
- if (res < 0) {
- var err = Linux.getErrno(errorState);
- if (err == errno.EAGAIN())
- return; // no more pending URBs
- if (err == errno.ENODEV())
- return; // ignore, device might have been closed
- throwException(err, "internal error (reap URB)");
- }
- // call completion handler
- var urb = dereference(urbPointerHolder);
- var transfer = getTransferResult(urb);
- transfer.completion().completed(transfer);
+ var completedTransfers = new ArrayList();
+ try {
+ synchronized (this) {
+ while (true) {
+ var res = IO.ioctl(fd, REAPURBNDELAY, urbPointerHolder, errorState);
+ if (res < 0) {
+ var err = Linux.getErrno(errorState);
+ if (err == errno.EAGAIN())
+ return; // no more pending URBs
+ if (err == errno.EBADF())
+ return; // file descriptor was closed concurrently (deregisters it from epoll)
+ if (err == errno.ENODEV()) {
+ // device might have been unplugged
+ EPoll.removeFileDescriptor(epollFd, fd);
+ return;
+ }
+ // Unexpected error: stop handling this device's completions but keep
+ // the task alive for all other devices. The file descriptor must be
+ // deregistered, or epoll would report it ready again immediately,
+ // resulting in a hot loop.
+ LOG.log(ERROR, "reaping URBs for file descriptor {0} failed with errno {1}; "
+ + "no further transfers will complete for this device", fd, err);
+ EPoll.removeFileDescriptor(epollFd, fd);
+ return;
+ }
+
+ var urb = dereference(urbPointerHolder);
+ completedTransfers.add(getTransferWithResult(urb));
+ }
+ }
+ } finally {
+ // Even if reaping fails, the already reaped transfers must be completed.
+ completeTransfers(completedTransfers);
}
}
/**
- * Notifies background process about changed FD list
+ * Calls the completion handlers of the specified transfers.
+ *
+ * Must be called without holding the lock: handlers acquire other monitors
+ * (transfer, device), and threads submitting transfers acquire this task's lock
+ * while holding those monitors, so calling handlers under the lock can deadlock.
+ *
+ *
+ * @param transfers completed transfers
*/
- private void notifyAsyncIOTask() {
- // start background process if needed
- if (asyncIOWakeUpEventFd == 0) {
- startAsyncIOTask();
- return;
- }
-
- try (var arena = Arena.ofConfined()) {
- var errorState = allocateErrorState(arena);
- if (IO.eventfd_write(asyncIOWakeUpEventFd, 1, errorState) < 0)
- throwLastError(errorState, "internal error (eventfd_write)");
+ private void completeTransfers(List transfers) {
+ for (var transfer : transfers) {
+ try {
+ transfer.completion().completed(transfer);
+ } catch (Exception e) {
+ // This method also runs on the process-wide async IO thread. Any exception
+ // escaping would kill that thread and hang all async transfers for the
+ // entire library.
+ LOG.log(ERROR, "Unexpected exception while handling async IO completion", e);
+ }
}
}
@@ -194,16 +216,12 @@ private void notifyAsyncIOTask() {
*
* @param device USB device
*/
- synchronized void addForAsyncIOCompletion(LinuxUSBDevice device) {
- var n = asyncFds != null ? asyncFds.length : 0;
- var fds = new int[n + 1];
- if (n > 0)
- System.arraycopy(asyncFds, 0, fds, 0, n);
- fds[n] = device.fileDescriptor();
-
- // activate new array
- asyncFds = fds;
- notifyAsyncIOTask();
+ synchronized void addForAsyncIOCompletion(LinuxUsbDevice device) {
+ // start background process if needed
+ if (epollFd < 0)
+ startAsyncIOTask();
+
+ EPoll.addFileDescriptor(epollFd, EPOLLOUT() | EPOLLWAKEUP(), device.fileDescriptor());
}
/**
@@ -211,46 +229,59 @@ synchronized void addForAsyncIOCompletion(LinuxUSBDevice device) {
*
* @param device USB device
*/
- synchronized void removeFromAsyncIOCompletion(LinuxUSBDevice device) {
- removeFdFromAsyncIOCompletion(device.fileDescriptor());
- notifyAsyncIOTask();
- }
+ void removeFromAsyncIOCompletion(LinuxUsbDevice device) {
+ int fd = device.fileDescriptor();
- private synchronized void removeFdFromAsyncIOCompletion(int fd) {
- // copy file descriptor (except the device's) into new array
- var n = asyncFds.length;
- if (n == 0)
- return;
-
- var fds = new int[n - 1];
- var tgt = 0;
- for (var asyncFd : asyncFds) {
- if (asyncFd != fd) {
- if (tgt == n)
- return;
- fds[tgt] = asyncFd;
- tgt += 1;
- }
+ // remove file descriptor from epoll
+ synchronized (this) {
+ EPoll.removeFileDescriptor(epollFd, fd);
}
- // make new array to active one
- asyncFds = fds;
+ // reap outstanding URBs
+ try (var arena = Arena.ofConfined()) {
+ var errorState = allocateErrorState(arena);
+ var urbPointerHolder = arena.allocate(ADDRESS);
+ reapURBs(fd, urbPointerHolder, errorState);
+ }
+
+ // reclaim stale URBs
+ var staleTransfers = new ArrayList();
+ synchronized (this) {
+ transfersByURB.entrySet().removeIf(e -> {
+ var urb = e.getKey();
+ var isMatch = usbdevfs_urb.usercontext(urb).address() == fd;
+ if (isMatch) {
+ var transfer = e.getValue();
+ transfer.urb = null;
+ transfer.setResultCode(ENODEV());
+ transfer.setResultSize(0);
+ availableURBs.add(urb);
+ staleTransfers.add(transfer);
+ }
+ return isMatch;
+ });
+ }
+ completeTransfers(staleTransfers);
}
- synchronized void submitTransfer(LinuxUSBDevice device, int endpointAddress, USBTransferType transferType, LinuxTransfer transfer) {
+ synchronized void submitTransfer(LinuxUsbDevice device, int endpointAddress, UsbTransferType transferType, LinuxTransfer transfer) {
+ if (taskTerminated)
+ throw new UsbException("USB async IO background thread has terminated due to an unrecoverable error; "
+ + "USB transfers are no longer possible");
- addURB(transfer);
+ linkToUrb(transfer);
var urb = transfer.urb;
- usbdevfs_urb.type$set(urb, (byte) urbTransferType(transferType));
- usbdevfs_urb.endpoint$set(urb, (byte) endpointAddress);
- usbdevfs_urb.buffer$set(urb, transfer.data());
- usbdevfs_urb.buffer_length$set(urb, transfer.dataSize());
- usbdevfs_urb.usercontext$set(urb, MemorySegment.ofAddress(device.fileDescriptor()));
+ usbdevfs_urb.type(urb, (byte) urbTransferType(transferType));
+ usbdevfs_urb.endpoint(urb, (byte) endpointAddress);
+ usbdevfs_urb.buffer(urb, transfer.data());
+ usbdevfs_urb.buffer_length(urb, transfer.dataSize());
+ usbdevfs_urb.usercontext(urb, MemorySegment.ofAddress(device.fileDescriptor()));
try (var arena = Arena.ofConfined()) {
var errorState = allocateErrorState(arena);
if (IO.ioctl(device.fileDescriptor(), SUBMITURB, urb, errorState) < 0) {
+ submissionFailed(transfer);
var action = endpointAddress >= 128 ? "reading from" : "writing to";
var endpoint = endpointAddress == 0 ? "control endpoint" : String.format("endpoint %d", endpointAddress);
throwLastError(errorState, "error occurred while %s %s", action, endpoint);
@@ -258,7 +289,24 @@ synchronized void submitTransfer(LinuxUSBDevice device, int endpointAddress, USB
}
}
- private static int urbTransferType(USBTransferType transferType) {
+ /**
+ * Undoes the registration performed by {@link #linkToUrb(LinuxTransfer)}.
+ *
+ * Must be called if the {@code SUBMITURB} ioctl for a linked transfer fails. In that
+ * case, the kernel has not queued the URB and it will never be reaped, so its map
+ * entry would leak and the URB would never return to the pool unless they are
+ * cleaned up here.
+ *
+ *
+ * @param transfer transfer whose submission failed
+ */
+ private void submissionFailed(LinuxTransfer transfer) {
+ transfersByURB.remove(transfer.urb);
+ availableURBs.add(transfer.urb);
+ transfer.urb = null;
+ }
+
+ private static int urbTransferType(UsbTransferType transferType) {
return switch (transferType) {
case BULK -> USBDEVFS_URB_TYPE_BULK();
case INTERRUPT -> USBDEVFS_URB_TYPE_INTERRUPT();
@@ -267,7 +315,15 @@ private static int urbTransferType(USBTransferType transferType) {
};
}
- private void addURB(LinuxTransfer transfer) {
+ /**
+ * Links the specified transfer instance to a URB.
+ *
+ * The transfer is assigned a URB instance, and a list
+ * of associations from URB to transfer is maintained.
+ *
+ * @param transfer the transfer to assign a URB.
+ */
+ private void linkToUrb(LinuxTransfer transfer) {
MemorySegment urb;
var size = availableURBs.size();
if (size > 0) {
@@ -280,50 +336,59 @@ private void addURB(LinuxTransfer transfer) {
transfersByURB.put(urb, transfer);
}
+ /**
+ * Gets the transfer associated with the specified URB and adds the result.
+ *
+ * The URB is returned into the list of URBs available for further transfers.
+ *
+ *
+ * @param urb URB instance
+ * @return transfer associated with the URB
+ */
@SuppressWarnings("java:S2259")
- private synchronized LinuxTransfer getTransferResult(MemorySegment urb) {
+ private synchronized LinuxTransfer getTransferWithResult(MemorySegment urb) {
var transfer = transfersByURB.remove(urb);
if (transfer == null)
throwException("internal error (unknown URB)");
- transfer.setResultCode(-usbdevfs_urb.status$get(transfer.urb));
- transfer.setResultSize(usbdevfs_urb.actual_length$get(transfer.urb));
+ transfer.setResultCode(-usbdevfs_urb.status(transfer.urb));
+ transfer.setResultSize(usbdevfs_urb.actual_length(transfer.urb));
availableURBs.add(transfer.urb);
transfer.urb = null;
+
return transfer;
}
@SuppressWarnings("java:S1066")
- synchronized void abortTransfers(LinuxUSBDevice device, byte endpointAddress) {
+ synchronized void abortTransfers(LinuxUsbDevice device, byte endpointAddress) {
var fd = device.fileDescriptor();
try (var arena = Arena.ofConfined()) {
var errorState = allocateErrorState(arena);
// iterate all URBs and discard the ones for the specified endpoint
- for (var urb : transfersByURB.keySet()) {
- if (fd != (int) usbdevfs_urb.usercontext$get(urb).address()
- || endpointAddress != usbdevfs_urb.endpoint$get(urb))
- continue;
-
- if (IO.ioctl(fd, DISCARDURB, urb, errorState) < 0) {
- // ignore EINVAL; it occurs if the URB has completed at the same time
- if (Linux.getErrno(errorState) != errno.EINVAL())
- throwLastError(errorState, "error occurred while aborting transfer");
- }
- }
+ transfersByURB.keySet().stream()
+ .filter(urb ->
+ usbdevfs_urb.usercontext(urb).address() == fd
+ && usbdevfs_urb.endpoint(urb) == endpointAddress)
+ .forEach(urb -> {
+ if (IO.ioctl(fd, DISCARDURB, urb, errorState) < 0) {
+ // ignore EINVAL; it occurs if the URB has completed at the same time
+ if (Linux.getErrno(errorState) != errno.EINVAL())
+ throwLastError(errorState, "error occurred while aborting transfer");
+ }
+ }
+ );
}
}
private void startAsyncIOTask() {
try (var arena = Arena.ofConfined()) {
var errorState = allocateErrorState(arena);
- asyncIOWakeUpEventFd = IO.eventfd(0, 0, errorState);
- if (asyncIOWakeUpEventFd == -1) {
- asyncIOWakeUpEventFd = 0;
- throwLastError(errorState, "internal error (eventfd)");
- }
+ epollFd = epoll_create1(FD_CLOEXEC(), errorState);
+ if (epollFd < 0)
+ throwLastError(errorState, "internal error (epoll_create)");
}
// start background thread for handling IO completion
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxEndpointInputStream.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxEndpointInputStream.java
index 9d1f7ce0..2f48a0e6 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxEndpointInputStream.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxEndpointInputStream.java
@@ -7,18 +7,18 @@
package net.codecrete.usb.linux;
-import net.codecrete.usb.USBDirection;
+import net.codecrete.usb.UsbDirection;
import net.codecrete.usb.common.EndpointInputStream;
import net.codecrete.usb.common.Transfer;
public class LinuxEndpointInputStream extends EndpointInputStream {
- LinuxEndpointInputStream(LinuxUSBDevice device, int endpointNumber, int bufferSize) {
+ LinuxEndpointInputStream(LinuxUsbDevice device, int endpointNumber, int bufferSize) {
super(device, endpointNumber, bufferSize);
}
@Override
protected void submitTransferIn(Transfer transfer) {
- ((LinuxUSBDevice) device).submitTransfer(USBDirection.IN, endpointNumber, (LinuxTransfer) transfer);
+ ((LinuxUsbDevice) device).submitTransfer(UsbDirection.IN, endpointNumber, (LinuxTransfer) transfer);
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxEndpointOutputStream.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxEndpointOutputStream.java
index ab5b997d..024b8da0 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxEndpointOutputStream.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxEndpointOutputStream.java
@@ -7,18 +7,18 @@
package net.codecrete.usb.linux;
-import net.codecrete.usb.USBDirection;
+import net.codecrete.usb.UsbDirection;
import net.codecrete.usb.common.EndpointOutputStream;
import net.codecrete.usb.common.Transfer;
public class LinuxEndpointOutputStream extends EndpointOutputStream {
- LinuxEndpointOutputStream(LinuxUSBDevice device, int endpointNumber, int bufferSize) {
+ LinuxEndpointOutputStream(LinuxUsbDevice device, int endpointNumber, int bufferSize) {
super(device, endpointNumber, bufferSize);
}
@Override
protected void submitTransferOut(Transfer transfer) {
- ((LinuxUSBDevice) device).submitTransfer(USBDirection.OUT, endpointNumber, (LinuxTransfer) transfer);
+ ((LinuxUsbDevice) device).submitTransfer(UsbDirection.OUT, endpointNumber, (LinuxTransfer) transfer);
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUSBDevice.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUsbDevice.java
similarity index 57%
rename from java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUSBDevice.java
rename to java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUsbDevice.java
index 897d3c16..d0c87f09 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUSBDevice.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUsbDevice.java
@@ -7,13 +7,13 @@
package net.codecrete.usb.linux;
-import net.codecrete.usb.USBControlTransfer;
-import net.codecrete.usb.USBDirection;
-import net.codecrete.usb.USBException;
-import net.codecrete.usb.USBTransferType;
+import net.codecrete.usb.UsbControlTransfer;
+import net.codecrete.usb.UsbDirection;
+import net.codecrete.usb.UsbException;
+import net.codecrete.usb.UsbTransferType;
import net.codecrete.usb.common.Transfer;
-import net.codecrete.usb.common.USBDeviceImpl;
-import net.codecrete.usb.common.USBInterfaceImpl;
+import net.codecrete.usb.common.UsbDeviceImpl;
+import net.codecrete.usb.common.UsbInterfaceImpl;
import net.codecrete.usb.linux.gen.fcntl.fcntl;
import net.codecrete.usb.linux.gen.unistd.unistd;
import net.codecrete.usb.linux.gen.usbdevice_fs.usbdevfs_disconnect_claim;
@@ -22,6 +22,7 @@
import net.codecrete.usb.linux.gen.usbdevice_fs.usbdevice_fs;
import net.codecrete.usb.usbstandard.DeviceDescriptor;
import net.codecrete.usb.usbstandard.SetupPacket;
+import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.InputStream;
@@ -34,22 +35,22 @@
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static net.codecrete.usb.linux.Linux.allocateErrorState;
-import static net.codecrete.usb.linux.LinuxUSBException.throwException;
-import static net.codecrete.usb.linux.LinuxUSBException.throwLastError;
+import static net.codecrete.usb.linux.LinuxUsbException.throwException;
+import static net.codecrete.usb.linux.LinuxUsbException.throwLastError;
@SuppressWarnings("java:S2160")
-public class LinuxUSBDevice extends USBDeviceImpl {
+public class LinuxUsbDevice extends UsbDeviceImpl {
- @SuppressWarnings("resource")
- private static final MemorySegment DRIVER_NAME_USBFS = Arena.global().allocateUtf8String("usbfs");
+ private static final MemorySegment DRIVER_NAME_USBFS = Arena.global().allocateFrom("usbfs");
- private int fd = -1;
+ // volatile: written under the device monitor, read unlocked via isOpened()
+ private volatile int fd = -1;
private final LinuxAsyncTask asyncTask;
private boolean detachDrivers = false;
- LinuxUSBDevice(Object id, int vendorId, int productId) {
+ LinuxUsbDevice(Object id, int vendorId, int productId) {
super(id, vendorId, productId);
asyncTask = LinuxAsyncTask.INSTANCE;
loadDescription((String) id);
@@ -60,7 +61,7 @@ private void loadDescription(String path) {
try {
descriptors = Files.readAllBytes(Path.of(path));
} catch (IOException e) {
- throw new USBException("reading configuration descriptor failed", e);
+ throw new UsbException("reading configuration descriptor failed", e);
}
// `descriptors` contains the device descriptor followed by the configuration descriptor
@@ -71,31 +72,28 @@ private void loadDescription(String path) {
}
@Override
- public void detachStandardDrivers() {
- if (isOpen())
- throwException("detachStandardDrivers() must not be called while the device is open");
+ public synchronized void detachStandardDrivers() {
+ checkIsClosed("detachStandardDrivers() must not be called while the device is open");
detachDrivers = true;
}
@Override
- public void attachStandardDrivers() {
- if (isOpen())
- throwException("attachStandardDrivers() must not be called while the device is open");
+ public synchronized void attachStandardDrivers() {
+ checkIsClosed("attachStandardDrivers() must not be called while the device is open");
detachDrivers = false;
}
@Override
- public boolean isOpen() {
+ public boolean isOpened() {
return fd != -1;
}
@Override
public synchronized void open() {
- if (isOpen())
- throwException("device is already open");
+ checkIsClosed("device is already open");
try (var arena = Arena.ofConfined()) {
- var pathUtf8 = arena.allocateUtf8String(uniqueDeviceId.toString());
+ var pathUtf8 = arena.allocateFrom(uniqueDeviceId.toString());
var errorState = allocateErrorState(arena);
fd = IO.open(pathUtf8, fcntl.O_RDWR() | fcntl.O_CLOEXEC(), errorState);
if (fd == -1)
@@ -106,13 +104,13 @@ public synchronized void open() {
@Override
public synchronized void close() {
- if (!isOpen())
+ if (!isOpened())
return;
asyncTask.removeFromAsyncIOCompletion(this);
for (var intf : interfaceList)
- ((USBInterfaceImpl) intf).setClaimed(false);
+ ((UsbInterfaceImpl) intf).setClaimed(false);
unistd.close(fd);
fd = -1;
@@ -134,15 +132,16 @@ public synchronized void claimInterface(int interfaceNumber) {
if (detachDrivers) {
// claim interface (detaching kernel driver)
var disconnectClaim = usbdevfs_disconnect_claim.allocate(arena);
- usbdevfs_disconnect_claim.interface_$set(disconnectClaim, interfaceNumber);
- usbdevfs_disconnect_claim.flags$set(disconnectClaim, usbdevice_fs.USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER());
- usbdevfs_disconnect_claim.driver$slice(disconnectClaim).copyFrom(DRIVER_NAME_USBFS);
- ret = IO.ioctl(fd, USBDevFS.DISCONNECT_CLAIM, disconnectClaim, errorState);
+ usbdevfs_disconnect_claim.interface_(disconnectClaim, interfaceNumber);
+ usbdevfs_disconnect_claim.flags(disconnectClaim, usbdevice_fs.USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER());
+ usbdevfs_disconnect_claim.driver(disconnectClaim).copyFrom(DRIVER_NAME_USBFS);
+ ret = IO.ioctl(fd, UsbDevFS.DISCONNECT_CLAIM, disconnectClaim, errorState);
} else {
// claim interface (without detaching kernel driver)
- var intfNumSegment = arena.allocate(JAVA_INT, interfaceNumber);
- ret = IO.ioctl(fd, USBDevFS.CLAIMINTERFACE, intfNumSegment, errorState);
+ var intfNumSegment = arena.allocate(JAVA_INT);
+ intfNumSegment.setAtIndex(JAVA_INT, 0, interfaceNumber);
+ ret = IO.ioctl(fd, UsbDevFS.CLAIMINTERFACE, intfNumSegment, errorState);
}
if (ret != 0)
@@ -160,16 +159,13 @@ public synchronized void selectAlternateSetting(int interfaceNumber, int alterna
// check alternate setting
var altSetting = intf.getAlternate(alternateNumber);
- if (altSetting == null)
- throwException("interface %d does not have an alternate interface setting %d", interfaceNumber,
- alternateNumber);
try (var arena = Arena.ofConfined()) {
var setIntfSegment = usbdevfs_setinterface.allocate(arena);
- usbdevfs_setinterface.interface_$set(setIntfSegment, interfaceNumber);
- usbdevfs_setinterface.altsetting$set(setIntfSegment, alternateNumber);
+ usbdevfs_setinterface.interface_(setIntfSegment, interfaceNumber);
+ usbdevfs_setinterface.altsetting(setIntfSegment, alternateNumber);
var errorState = allocateErrorState(arena);
- var ret = IO.ioctl(fd, USBDevFS.SETINTERFACE, setIntfSegment, errorState);
+ var ret = IO.ioctl(fd, UsbDevFS.SETINTERFACE, setIntfSegment, errorState);
if (ret != 0)
throwLastError(errorState, "setting alternate interface failed");
}
@@ -183,9 +179,10 @@ public synchronized void releaseInterface(int interfaceNumber) {
getInterfaceWithCheck(interfaceNumber, true);
try (var arena = Arena.ofConfined()) {
- var intfNumSegment = arena.allocate(JAVA_INT, interfaceNumber);
+ var intfNumSegment = arena.allocate(JAVA_INT);
+ intfNumSegment.setAtIndex(JAVA_INT, 0, interfaceNumber);
var errorState = allocateErrorState(arena);
- var ret = IO.ioctl(fd, USBDevFS.RELEASEINTERFACE, intfNumSegment, errorState);
+ var ret = IO.ioctl(fd, UsbDevFS.RELEASEINTERFACE, intfNumSegment, errorState);
if (ret != 0)
throwLastError(errorState, "releasing USB interface failed");
@@ -194,37 +191,37 @@ public synchronized void releaseInterface(int interfaceNumber) {
if (detachDrivers) {
// reattach kernel driver
var request = usbdevfs_ioctl.allocate(arena);
- usbdevfs_ioctl.ifno$set(request, interfaceNumber);
- usbdevfs_ioctl.ioctl_code$set(request, USBDevFS.CONNECT);
- usbdevfs_ioctl.data$set(request, MemorySegment.NULL);
- IO.ioctl(fd, USBDevFS.IOCTL, request, errorState);
+ usbdevfs_ioctl.ifno(request, interfaceNumber);
+ usbdevfs_ioctl.ioctl_code(request, UsbDevFS.CONNECT);
+ usbdevfs_ioctl.data(request, MemorySegment.NULL);
+ IO.ioctl(fd, UsbDevFS.IOCTL, request, errorState);
}
}
}
@Override
- public void controlTransferOut(USBControlTransfer setup, byte[] data) {
+ public void controlTransferOut(@NotNull UsbControlTransfer setup, byte[] data) {
try (var arena = Arena.ofConfined()) {
var dataLength = data != null ? data.length : 0;
- var transfer = createSyncCtrlTransfer(arena, USBDirection.OUT, setup, dataLength);
+ var transfer = createSyncCtrlTransfer(arena, UsbDirection.OUT, setup, dataLength);
if (dataLength != 0)
transfer.data().asSlice(8).copyFrom(MemorySegment.ofArray(data));
synchronized (transfer) {
- submitTransfer(USBDirection.OUT, 0, transfer);
- waitForTransfer(transfer, 0, USBDirection.OUT, 0);
+ submitTransfer(UsbDirection.OUT, 0, transfer);
+ waitForTransfer(transfer, 0, UsbDirection.OUT, 0);
}
}
}
@Override
- public byte[] controlTransferIn(USBControlTransfer setup, int length) {
+ public byte @NotNull [] controlTransferIn(@NotNull UsbControlTransfer setup, int length) {
try (var arena = Arena.ofConfined()) {
- var transfer = createSyncCtrlTransfer(arena, USBDirection.IN, setup, length);
+ var transfer = createSyncCtrlTransfer(arena, UsbDirection.IN, setup, length);
synchronized (transfer) {
- submitTransfer(USBDirection.IN, 0, transfer);
- waitForTransfer(transfer, 0, USBDirection.IN, 0);
+ submitTransfer(UsbDirection.IN, 0, transfer);
+ waitForTransfer(transfer, 0, UsbDirection.IN, 0);
}
return transfer.data().asSlice(8, transfer.resultSize()).toArray(JAVA_BYTE);
@@ -240,10 +237,10 @@ public byte[] controlTransferIn(USBControlTransfer setup, int length) {
* @param dataLength data length (in addition to setup data)
* @return transfer object
*/
- private LinuxTransfer createSyncCtrlTransfer(Arena arena, USBDirection direction, USBControlTransfer setup,
+ private LinuxTransfer createSyncCtrlTransfer(Arena arena, UsbDirection direction, UsbControlTransfer setup,
int dataLength) {
var bmRequest =
- (direction == USBDirection.IN ? 0x80 : 0) | (setup.requestType().ordinal() << 5) | setup.recipient().ordinal();
+ (direction == UsbDirection.IN ? 0x80 : 0) | (setup.requestType().ordinal() << 5) | setup.recipient().ordinal();
var buffer = arena.allocate(8L + dataLength, 8);
var setupPacket = new SetupPacket(buffer);
setupPacket.setRequestType(bmRequest);
@@ -256,40 +253,42 @@ private LinuxTransfer createSyncCtrlTransfer(Arena arena, USBDirection direction
transfer.setData(buffer);
transfer.setDataSize((int) buffer.byteSize());
transfer.setResultSize(-1);
- transfer.setCompletion(USBDeviceImpl::onSyncTransferCompleted);
+ transfer.setCompletion(UsbDeviceImpl::onSyncTransferCompleted);
return transfer;
}
@Override
- public void transferOut(int endpointNumber, byte[] data, int offset, int length, int timeout) {
- try (var arena = Arena.ofConfined()) {
- var buffer = arena.allocate(length);
- buffer.copyFrom(MemorySegment.ofArray(data).asSlice(offset, length));
- var transfer = createSyncTransfer(buffer);
-
- synchronized (transfer) {
- submitTransfer(USBDirection.OUT, endpointNumber, transfer);
- waitForTransfer(transfer, timeout, USBDirection.OUT, endpointNumber);
- }
+ public void transferOut(int endpointNumber, byte @NotNull [] data, int offset, int length, int timeout) {
+ // Auto arena: a transfer that times out may be abandoned (see UsbDeviceImpl.waitForTransfer),
+ // so the buffer must outlive a possible late completion instead of being freed deterministically.
+ var arena = Arena.ofAuto();
+ var buffer = arena.allocate(length);
+ buffer.copyFrom(MemorySegment.ofArray(data).asSlice(offset, length));
+ var transfer = createSyncTransfer(buffer);
+
+ synchronized (transfer) {
+ submitTransfer(UsbDirection.OUT, endpointNumber, transfer);
+ waitForTransfer(transfer, timeout, UsbDirection.OUT, endpointNumber);
}
}
@Override
- public byte[] transferIn(int endpointNumber, int timeout) {
- var endpoint = getEndpoint(USBDirection.IN, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
-
- try (var arena = Arena.ofConfined()) {
- var buffer = arena.allocate(endpoint.packetSize());
- var transfer = createSyncTransfer(buffer);
-
- synchronized (transfer) {
- submitTransfer(USBDirection.IN, endpointNumber, transfer);
- waitForTransfer(transfer, timeout, USBDirection.IN, endpointNumber);
- }
-
- return buffer.asSlice(0, transfer.resultSize()).toArray(JAVA_BYTE);
+ public byte @NotNull [] transferIn(int endpointNumber, int timeout) {
+ var endpoint = getEndpoint(UsbDirection.IN, endpointNumber, UsbTransferType.BULK, UsbTransferType.INTERRUPT);
+
+ // Auto arena: a transfer that times out may be abandoned (see UsbDeviceImpl.waitForTransfer),
+ // so the buffer must outlive a possible late completion instead of being freed deterministically.
+ var arena = Arena.ofAuto();
+ var buffer = arena.allocate(endpoint.packetSize());
+ var transfer = createSyncTransfer(buffer);
+
+ synchronized (transfer) {
+ submitTransfer(UsbDirection.IN, endpointNumber, transfer);
+ waitForTransfer(transfer, timeout, UsbDirection.IN, endpointNumber);
}
+
+ return buffer.asSlice(0, transfer.resultSize()).toArray(JAVA_BYTE);
}
private LinuxTransfer createSyncTransfer(MemorySegment data) {
@@ -297,16 +296,16 @@ private LinuxTransfer createSyncTransfer(MemorySegment data) {
transfer.setData(data);
transfer.setDataSize((int) data.byteSize());
transfer.setResultSize(-1);
- transfer.setCompletion(USBDeviceImpl::onSyncTransferCompleted);
+ transfer.setCompletion(UsbDeviceImpl::onSyncTransferCompleted);
return transfer;
}
- synchronized void submitTransfer(USBDirection direction, int endpointNumber, LinuxTransfer transfer) {
+ synchronized void submitTransfer(UsbDirection direction, int endpointNumber, LinuxTransfer transfer) {
if (endpointNumber != 0) {
- var endpoint = getEndpoint(direction, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
+ var endpoint = getEndpoint(direction, endpointNumber, UsbTransferType.BULK, UsbTransferType.INTERRUPT);
asyncTask.submitTransfer(this, endpoint.endpointAddress(), endpoint.transferType(), transfer);
} else {
- asyncTask.submitTransfer(this, 0, USBTransferType.CONTROL, transfer);
+ asyncTask.submitTransfer(this, 0, UsbTransferType.CONTROL, transfer);
}
}
@@ -321,37 +320,38 @@ protected void throwOSException(int errorCode, String message, Object... args) {
}
@Override
- public void clearHalt(USBDirection direction, int endpointNumber) {
- var endpoint = getEndpoint(direction, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
+ public void clearHalt(UsbDirection direction, int endpointNumber) {
+ var endpoint = getEndpoint(direction, endpointNumber, UsbTransferType.BULK, UsbTransferType.INTERRUPT);
try (var arena = Arena.ofConfined()) {
- var endpointAddrSegment = arena.allocate(JAVA_INT, endpoint.endpointAddress() & 0xff);
+ var endpointAddrSegment = arena.allocate(JAVA_INT);
+ endpointAddrSegment.setAtIndex(JAVA_INT, 0, endpoint.endpointAddress() & 0xff);
var errorState = allocateErrorState(arena);
- var res = IO.ioctl(fd, USBDevFS.CLEAR_HALT, endpointAddrSegment, errorState);
+ var res = IO.ioctl(fd, UsbDevFS.CLEAR_HALT, endpointAddrSegment, errorState);
if (res < 0)
throwLastError(errorState, "clearing halt failed");
}
}
@Override
- public synchronized void abortTransfers(USBDirection direction, int endpointNumber) {
- var endpoint = getEndpoint(direction, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
+ public synchronized void abortTransfers(UsbDirection direction, int endpointNumber) {
+ var endpoint = getEndpoint(direction, endpointNumber, UsbTransferType.BULK, UsbTransferType.INTERRUPT);
asyncTask.abortTransfers(this, endpoint.endpointAddress());
}
@Override
- public synchronized InputStream openInputStream(int endpointNumber, int bufferSize) {
+ public synchronized @NotNull InputStream openInputStream(int endpointNumber, int bufferSize) {
// check that endpoint number is valid
- getEndpoint(USBDirection.IN, endpointNumber, USBTransferType.BULK, null);
+ getEndpoint(UsbDirection.IN, endpointNumber, UsbTransferType.BULK, null);
return new LinuxEndpointInputStream(this, endpointNumber, bufferSize);
}
@Override
- public synchronized OutputStream openOutputStream(int endpointNumber, int bufferSize) {
+ public synchronized @NotNull OutputStream openOutputStream(int endpointNumber, int bufferSize) {
// check that endpoint number is valid
- getEndpoint(USBDirection.OUT, endpointNumber, USBTransferType.BULK, null);
+ getEndpoint(UsbDirection.OUT, endpointNumber, UsbTransferType.BULK, null);
return new LinuxEndpointOutputStream(this, endpointNumber, bufferSize);
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUSBDeviceRegistry.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUsbDeviceRegistry.java
similarity index 62%
rename from java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUSBDeviceRegistry.java
rename to java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUsbDeviceRegistry.java
index ec12e75d..6c39abe9 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUSBDeviceRegistry.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUsbDeviceRegistry.java
@@ -7,11 +7,9 @@
package net.codecrete.usb.linux;
-import net.codecrete.usb.USBDevice;
+import net.codecrete.usb.UsbDevice;
import net.codecrete.usb.common.ScopeCleanup;
-import net.codecrete.usb.common.USBDeviceRegistry;
-import net.codecrete.usb.linux.gen.poll.poll;
-import net.codecrete.usb.linux.gen.poll.pollfd;
+import net.codecrete.usb.common.UsbDeviceRegistry;
import net.codecrete.usb.linux.gen.udev.udev;
import java.lang.foreign.Arena;
@@ -20,14 +18,22 @@
import java.util.List;
import static java.lang.System.Logger.Level.INFO;
-import static net.codecrete.usb.linux.LinuxUSBException.throwException;
+import static java.lang.foreign.MemorySegment.NULL;
+import static net.codecrete.usb.linux.EPoll.epoll_create1;
+import static net.codecrete.usb.linux.EPoll.epoll_wait;
+import static net.codecrete.usb.linux.Linux.allocateErrorState;
+import static net.codecrete.usb.linux.LinuxUsbException.throwException;
+import static net.codecrete.usb.linux.LinuxUsbException.throwLastError;
+import static net.codecrete.usb.linux.gen.epoll.epoll.EPOLLIN;
+import static net.codecrete.usb.linux.gen.errno.errno.EINTR;
+import static net.codecrete.usb.linux.gen.fcntl.fcntl.FD_CLOEXEC;
/**
* Linux implementation of USB device registry.
*/
-public class LinuxUSBDeviceRegistry extends USBDeviceRegistry {
+public class LinuxUsbDeviceRegistry extends UsbDeviceRegistry {
- private static final System.Logger LOG = System.getLogger(LinuxUSBDeviceRegistry.class.getName());
+ private static final System.Logger LOG = System.getLogger(LinuxUsbDeviceRegistry.class.getName());
private static final MemorySegment SUBSYSTEM_USB;
private static final MemorySegment MONITOR_NAME;
@@ -39,28 +45,25 @@ public class LinuxUSBDeviceRegistry extends USBDeviceRegistry {
private static final MemorySegment ATTR_PRODUCT;
private static final MemorySegment ATTR_SERIAL;
+ private MemorySegment monitor;
+ private int monitorFd;
+
static {
- @SuppressWarnings("resource")
var global = Arena.global();
- SUBSYSTEM_USB = global.allocateUtf8String("usb");
- MONITOR_NAME = global.allocateUtf8String("udev");
- DEVTYPE_USB_DEVICE = global.allocateUtf8String("usb_device");
+ SUBSYSTEM_USB = global.allocateFrom("usb");
+ MONITOR_NAME = global.allocateFrom("udev");
+ DEVTYPE_USB_DEVICE = global.allocateFrom("usb_device");
- ATTR_ID_VENDOR = global.allocateUtf8String("idVendor");
- ATTR_ID_PRODUCT = global.allocateUtf8String("idProduct");
- ATTR_MANUFACTURER = global.allocateUtf8String("manufacturer");
- ATTR_PRODUCT = global.allocateUtf8String("product");
- ATTR_SERIAL = global.allocateUtf8String("serial");
+ ATTR_ID_VENDOR = global.allocateFrom("idVendor");
+ ATTR_ID_PRODUCT = global.allocateFrom("idProduct");
+ ATTR_MANUFACTURER = global.allocateFrom("manufacturer");
+ ATTR_PRODUCT = global.allocateFrom("product");
+ ATTR_SERIAL = global.allocateFrom("serial");
}
- @SuppressWarnings({"java:S1181", "java:S2189"})
- @Override
- protected void monitorDevices() {
-
- int fd;
- MemorySegment monitor;
-
+ @SuppressWarnings("java:S1181")
+ private boolean setupMonitor() {
try {
// setup udev monitor
var udevInstance = udev.udev_new();
@@ -77,49 +80,74 @@ protected void monitorDevices() {
if (udev.udev_monitor_enable_receiving(monitor) < 0)
throwException("internal error (udev_monitor_enable_receiving)");
- fd = udev.udev_monitor_get_fd(monitor);
- if (fd < 0)
+ monitorFd = udev.udev_monitor_get_fd(monitor);
+ if (monitorFd < 0)
throwException("internal error (udev_monitor_get_fd)");
// create initial list of devices
var deviceList = enumeratePresentDevices(udevInstance);
setInitialDeviceList(deviceList);
+ return true;
} catch (Throwable e) {
enumerationFailed(e);
- return;
+ return false;
}
+ }
- // monitor device changes
- //noinspection InfiniteLoopStatement
- while (true) {
- try (var arena = Arena.ofConfined(); var cleanup = new ScopeCleanup()) {
-
- // wait for next change
- waitForFileDescriptor(fd, arena);
+ @SuppressWarnings("java:S2189")
+ @Override
+ protected void monitorDevices() {
+ if (!setupMonitor())
+ return;
- // retrieve change
- var udevDevice = udev.udev_monitor_receive_device(monitor);
- if (udevDevice == null)
- continue; // shouldn't happen
+ try (var arena = Arena.ofConfined()) {
+ // create epoll
+ var errorState = allocateErrorState(arena);
+ var epfd = epoll_create1(FD_CLOEXEC(), errorState);
+ if (epfd < 0)
+ throwLastError(errorState, "internal error (epoll_create)");
+ EPoll.addFileDescriptor(epfd, EPOLLIN(), monitorFd);
- cleanup.add(() -> udev.udev_device_unref(udevDevice));
+ // allocate event (as output for epoll_wait)
+ var event = arena.allocate(EPoll.EVENT$LAYOUT);
- // get details
- var action = getDeviceAction(udevDevice);
+ // monitor device changes
+ //noinspection InfiniteLoopStatement
+ while (true) {
+ try (var cleanup = new ScopeCleanup()) {
- if ("add".equals(action)) {
- onDeviceConnected(udevDevice);
- } else if ("remove".equals(action)) {
- onDeviceDisconnected(udevDevice);
+ // wait for next change
+ int res = epoll_wait(epfd, event, 1, -1, errorState);
+ if (res < 0) {
+ var err = Linux.getErrno(errorState);
+ if (err == EINTR())
+ continue; // continue on interrupt
+ throwException(err, "internal error (epoll_wait)");
+ }
+
+ // retrieve change
+ var udevDevice = udev.udev_monitor_receive_device(monitor);
+ if (udevDevice != NULL) {
+ cleanup.add(() -> udev.udev_device_unref(udevDevice));
+
+ // get details
+ var action = getDeviceAction(udevDevice);
+
+ if ("add".equals(action)) {
+ onDeviceConnected(udevDevice);
+ } else if ("remove".equals(action)) {
+ onDeviceDisconnected(udevDevice);
+ }
+ }
}
}
}
}
@SuppressWarnings("java:S135")
- private List enumeratePresentDevices(MemorySegment udevInstance) {
- List result = new ArrayList<>();
+ private List enumeratePresentDevices(MemorySegment udevInstance) {
+ List result = new ArrayList<>();
try (var outerCleanup = new ScopeCleanup()) {
// create device enumerator
@@ -181,7 +209,7 @@ private void onDeviceDisconnected(MemorySegment udevDevice) {
}
/**
- * Retrieves the device details and returns a {@code USBDevice} instance.
+ * Retrieves the device details and returns a {@code UsbDevice} instance.
*
* If the device is missing one of vendor ID, product ID or device path,
* {@code null} is returned.
@@ -191,7 +219,7 @@ private void onDeviceDisconnected(MemorySegment udevDevice) {
* @return the device instance
*/
@SuppressWarnings("java:S106")
- private USBDevice getDeviceDetails(MemorySegment udevDevice) {
+ private UsbDevice getDeviceDetails(MemorySegment udevDevice) {
int vendorId = 0;
int productId = 0;
@@ -215,7 +243,7 @@ private USBDevice getDeviceDetails(MemorySegment udevDevice) {
productId = Integer.parseInt(idProduct, 16);
// create device instance
- var device = new LinuxUSBDevice(devPath, vendorId, productId);
+ var device = new LinuxUsbDevice(devPath, vendorId, productId);
device.setProductStrings(getDeviceAttribute(udevDevice, ATTR_MANUFACTURER), getDeviceAttribute(udevDevice
, ATTR_PRODUCT), getDeviceAttribute(udevDevice, ATTR_SERIAL));
@@ -233,30 +261,14 @@ private static String getDeviceAttribute(MemorySegment udevDevice, MemorySegment
if (value.address() == 0)
return null;
- return value.getUtf8String(0);
+ return value.getString(0);
}
private static String getDeviceName(MemorySegment udevDevice) {
- return udev.udev_device_get_devnode(udevDevice).getUtf8String(0);
+ return udev.udev_device_get_devnode(udevDevice).getString(0);
}
private static String getDeviceAction(MemorySegment udevDevice) {
- return udev.udev_device_get_action(udevDevice).getUtf8String(0);
+ return udev.udev_device_get_action(udevDevice).getString(0);
}
-
- /**
- * Waits until the specified file descriptor becomes ready for reading.
- *
- * @param fd the file descriptor
- * @param arena an arena for allocating memory
- */
- private static void waitForFileDescriptor(int fd, Arena arena) {
- var fds = pollfd.allocate(arena);
- pollfd.fd$set(fds, fd);
- pollfd.events$set(fds, (short) poll.POLLIN());
- int res = poll.poll(fds, 1, -1);
- if (res < 0)
- throwException("internal error (poll)");
- }
-
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUSBException.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUsbException.java
similarity index 85%
rename from java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUSBException.java
rename to java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUsbException.java
index 43924a9a..4f0d95c5 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUSBException.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUsbException.java
@@ -6,8 +6,8 @@
//
package net.codecrete.usb.linux;
-import net.codecrete.usb.USBException;
-import net.codecrete.usb.USBStallException;
+import net.codecrete.usb.UsbException;
+import net.codecrete.usb.UsbStallException;
import net.codecrete.usb.linux.gen.errno.errno;
import java.lang.foreign.MemorySegment;
@@ -15,7 +15,7 @@
/**
* Exception thrown if a Linux specific error occurs.
*/
-public class LinuxUSBException extends USBException {
+public class LinuxUsbException extends UsbException {
/**
* Creates a new instance.
@@ -26,7 +26,7 @@ public class LinuxUSBException extends USBException {
* @param message exception message
* @param errorCode Linux error code (returned by {@code errno})
*/
- public LinuxUSBException(String message, int errorCode) {
+ public LinuxUsbException(String message, int errorCode) {
super(String.format("%s: %s", message, Linux.getErrorMessage(errorCode)), errorCode);
}
@@ -43,9 +43,9 @@ public LinuxUSBException(String message, int errorCode) {
static void throwException(int errorCode, String message, Object... args) {
var formattedMessage = String.format(message, args);
if (errorCode == errno.EPIPE()) {
- throw new USBStallException(formattedMessage);
+ throw new UsbStallException(formattedMessage);
} else {
- throw new LinuxUSBException(formattedMessage, errorCode);
+ throw new LinuxUsbException(formattedMessage, errorCode);
}
}
@@ -56,7 +56,7 @@ static void throwException(int errorCode, String message, Object... args) {
* @param args arguments for exception message
*/
static void throwException(String message, Object... args) {
- throw new USBException(String.format(message, args));
+ throw new UsbException(String.format(message, args));
}
/**
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/USBDevFS.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/UsbDevFS.java
similarity index 87%
rename from java-does-usb/src/main/java/net/codecrete/usb/linux/USBDevFS.java
rename to java-does-usb/src/main/java/net/codecrete/usb/linux/UsbDevFS.java
index f3be0b38..15f1d270 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/USBDevFS.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/UsbDevFS.java
@@ -14,11 +14,12 @@
* Thus, they cannot be generated using jextract.
*
*/
-class USBDevFS {
+class UsbDevFS {
- private USBDevFS() {
+ private UsbDevFS() {
}
+ // constants that jextract cannot generate as they are built from function-like macros
static final long CLAIMINTERFACE = 0x8004550FL;
static final long RELEASEINTERFACE = 0x80045510L;
static final long SETINTERFACE = 0x80085504L;
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/epoll/epoll$shared.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/epoll/epoll$shared.java
new file mode 100644
index 00000000..5cb27567
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/epoll/epoll$shared.java
@@ -0,0 +1,63 @@
+// Generated by jextract
+
+package net.codecrete.usb.linux.gen.epoll;
+
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class epoll$shared {
+
+ epoll$shared() {
+ // Should not be called directly
+ }
+
+ public static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) Linker.nativeLinker().canonicalLayouts().get("bool");
+ public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char");
+ public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) Linker.nativeLinker().canonicalLayouts().get("short");
+ public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int");
+ public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long long");
+ public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) Linker.nativeLinker().canonicalLayouts().get("float");
+ public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double");
+ public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*"))
+ .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR));
+ public static final ValueLayout.OfLong C_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long");
+
+ static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls");
+
+ static void traceDowncall(String name, Object... args) {
+ String traceArgs = Arrays.stream(args)
+ .map(Object::toString)
+ .collect(Collectors.joining(", "));
+ System.out.printf("%s(%s)\n", name, traceArgs);
+ }
+
+ static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
+ try {
+ return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
+ } catch (ReflectiveOperationException ex) {
+ throw new AssertionError(ex);
+ }
+ }
+
+ static MemoryLayout align(MemoryLayout layout, long align) {
+ return switch (layout) {
+ case PaddingLayout p -> p;
+ case ValueLayout v -> v.withByteAlignment(align);
+ case GroupLayout g -> {
+ MemoryLayout[] alignedMembers = g.memberLayouts().stream()
+ .map(m -> align(m, align)).toArray(MemoryLayout[]::new);
+ yield g instanceof StructLayout ?
+ MemoryLayout.structLayout(alignedMembers) : MemoryLayout.unionLayout(alignedMembers);
+ }
+ case SequenceLayout s -> MemoryLayout.sequenceLayout(s.elementCount(), align(s.elementLayout(), align));
+ };
+ }
+}
+
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/epoll/epoll.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/epoll/epoll.java
new file mode 100644
index 00000000..22e0cb02
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/epoll/epoll.java
@@ -0,0 +1,72 @@
+// Generated by jextract
+
+package net.codecrete.usb.linux.gen.epoll;
+
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class epoll extends epoll$shared {
+
+ epoll() {
+ // Should not be called directly
+ }
+
+ static final Arena LIBRARY_ARENA = Arena.ofAuto();
+
+ static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup()
+ .or(Linker.nativeLinker().defaultLookup());
+
+ private static final int EPOLL_CTL_ADD = (int)1L;
+ /**
+ * {@snippet lang=c :
+ * #define EPOLL_CTL_ADD 1
+ * }
+ */
+ public static int EPOLL_CTL_ADD() {
+ return EPOLL_CTL_ADD;
+ }
+ private static final int EPOLL_CTL_DEL = (int)2L;
+ /**
+ * {@snippet lang=c :
+ * #define EPOLL_CTL_DEL 2
+ * }
+ */
+ public static int EPOLL_CTL_DEL() {
+ return EPOLL_CTL_DEL;
+ }
+ private static final int EPOLLIN = (int)1L;
+ /**
+ * {@snippet lang=c :
+ * enum EPOLL_EVENTS.EPOLLIN = 1
+ * }
+ */
+ public static int EPOLLIN() {
+ return EPOLLIN;
+ }
+ private static final int EPOLLOUT = (int)4L;
+ /**
+ * {@snippet lang=c :
+ * enum EPOLL_EVENTS.EPOLLOUT = 4
+ * }
+ */
+ public static int EPOLLOUT() {
+ return EPOLLOUT;
+ }
+ private static final int EPOLLWAKEUP = (int)536870912L;
+ /**
+ * {@snippet lang=c :
+ * enum EPOLL_EVENTS.EPOLLWAKEUP = 536870912
+ * }
+ */
+ public static int EPOLLWAKEUP() {
+ return EPOLLWAKEUP;
+ }
+}
+
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/RuntimeHelper.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/RuntimeHelper.java
deleted file mode 100644
index f46e44cf..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/RuntimeHelper.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package net.codecrete.usb.linux.gen.errno;
-// Generated by jextract
-
-import java.lang.foreign.*;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-
-import static java.lang.foreign.ValueLayout.*;
-
-final class RuntimeHelper {
-
- private static final Linker LINKER = Linker.nativeLinker();
- private static final ClassLoader LOADER = RuntimeHelper.class.getClassLoader();
- private static final MethodHandles.Lookup MH_LOOKUP = MethodHandles.lookup();
- private static final SymbolLookup SYMBOL_LOOKUP;
- private static final SegmentAllocator THROWING_ALLOCATOR = (x, y) -> { throw new AssertionError("should not reach here"); };
- static final AddressLayout POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE));
-
- final static SegmentAllocator CONSTANT_ALLOCATOR =
- (size, align) -> Arena.ofAuto().allocate(size, align);
-
- static {
-
- SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
- SYMBOL_LOOKUP = name -> loaderLookup.find(name).or(() -> LINKER.defaultLookup().find(name));
- }
-
- // Suppresses default constructor, ensuring non-instantiability.
- private RuntimeHelper() {}
-
- static T requireNonNull(T obj, String symbolName) {
- if (obj == null) {
- throw new UnsatisfiedLinkError("unresolved symbol: " + symbolName);
- }
- return obj;
- }
-
- static MemorySegment lookupGlobalVariable(String name, MemoryLayout layout) {
- return SYMBOL_LOOKUP.find(name)
- .map(s -> s.reinterpret(layout.byteSize()))
- .orElse(null);
- }
-
- static MethodHandle downcallHandle(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> LINKER.downcallHandle(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle downcallHandle(FunctionDescriptor fdesc) {
- return LINKER.downcallHandle(fdesc);
- }
-
- static MethodHandle downcallHandleVariadic(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> VarargsInvoker.make(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
- try {
- return MH_LOOKUP.findVirtual(fi, name, fdesc.toMethodType());
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment upcallStub(MethodHandle fiHandle, Z z, FunctionDescriptor fdesc, Arena scope) {
- try {
- fiHandle = fiHandle.bindTo(z);
- return LINKER.upcallStub(fiHandle, fdesc, scope);
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment asArray(MemorySegment addr, MemoryLayout layout, int numElements, Arena arena) {
- return addr.reinterpret(numElements * layout.byteSize(), arena, null);
- }
-
- // Internals only below this point
-
- private static final class VarargsInvoker {
- private static final MethodHandle INVOKE_MH;
- private final MemorySegment symbol;
- private final FunctionDescriptor function;
-
- private VarargsInvoker(MemorySegment symbol, FunctionDescriptor function) {
- this.symbol = symbol;
- this.function = function;
- }
-
- static {
- try {
- INVOKE_MH = MethodHandles.lookup().findVirtual(VarargsInvoker.class, "invoke", MethodType.methodType(Object.class, SegmentAllocator.class, Object[].class));
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException(e);
- }
- }
-
- static MethodHandle make(MemorySegment symbol, FunctionDescriptor function) {
- VarargsInvoker invoker = new VarargsInvoker(symbol, function);
- MethodHandle handle = INVOKE_MH.bindTo(invoker).asCollector(Object[].class, function.argumentLayouts().size() + 1);
- MethodType mtype = MethodType.methodType(function.returnLayout().isPresent() ? carrier(function.returnLayout().get(), true) : void.class);
- for (MemoryLayout layout : function.argumentLayouts()) {
- mtype = mtype.appendParameterTypes(carrier(layout, false));
- }
- mtype = mtype.appendParameterTypes(Object[].class);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mtype = mtype.insertParameterTypes(0, SegmentAllocator.class);
- } else {
- handle = MethodHandles.insertArguments(handle, 0, THROWING_ALLOCATOR);
- }
- return handle.asType(mtype);
- }
-
- static Class> carrier(MemoryLayout layout, boolean ret) {
- if (layout instanceof ValueLayout valueLayout) {
- return valueLayout.carrier();
- } else if (layout instanceof GroupLayout) {
- return MemorySegment.class;
- } else {
- throw new AssertionError("Cannot get here!");
- }
- }
-
- private Object invoke(SegmentAllocator allocator, Object[] args) throws Throwable {
- // one trailing Object[]
- int nNamedArgs = function.argumentLayouts().size();
- assert(args.length == nNamedArgs + 1);
- // The last argument is the array of vararg collector
- Object[] unnamedArgs = (Object[]) args[args.length - 1];
-
- int argsCount = nNamedArgs + unnamedArgs.length;
- Class>[] argTypes = new Class>[argsCount];
- MemoryLayout[] argLayouts = new MemoryLayout[nNamedArgs + unnamedArgs.length];
-
- int pos = 0;
- for (pos = 0; pos < nNamedArgs; pos++) {
- argLayouts[pos] = function.argumentLayouts().get(pos);
- }
-
- assert pos == nNamedArgs;
- for (Object o: unnamedArgs) {
- argLayouts[pos] = variadicLayout(normalize(o.getClass()));
- pos++;
- }
- assert pos == argsCount;
-
- FunctionDescriptor f = (function.returnLayout().isEmpty()) ?
- FunctionDescriptor.ofVoid(argLayouts) :
- FunctionDescriptor.of(function.returnLayout().get(), argLayouts);
- MethodHandle mh = LINKER.downcallHandle(symbol, f);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mh = mh.bindTo(allocator);
- }
- // flatten argument list so that it can be passed to an asSpreader MH
- Object[] allArgs = new Object[nNamedArgs + unnamedArgs.length];
- System.arraycopy(args, 0, allArgs, 0, nNamedArgs);
- System.arraycopy(unnamedArgs, 0, allArgs, nNamedArgs, unnamedArgs.length);
-
- return mh.asSpreader(Object[].class, argsCount).invoke(allArgs);
- }
-
- private static Class> unboxIfNeeded(Class> clazz) {
- if (clazz == Boolean.class) {
- return boolean.class;
- } else if (clazz == Void.class) {
- return void.class;
- } else if (clazz == Byte.class) {
- return byte.class;
- } else if (clazz == Character.class) {
- return char.class;
- } else if (clazz == Short.class) {
- return short.class;
- } else if (clazz == Integer.class) {
- return int.class;
- } else if (clazz == Long.class) {
- return long.class;
- } else if (clazz == Float.class) {
- return float.class;
- } else if (clazz == Double.class) {
- return double.class;
- } else {
- return clazz;
- }
- }
-
- private Class> promote(Class> c) {
- if (c == byte.class || c == char.class || c == short.class || c == int.class) {
- return long.class;
- } else if (c == float.class) {
- return double.class;
- } else {
- return c;
- }
- }
-
- private Class> normalize(Class> c) {
- c = unboxIfNeeded(c);
- if (c.isPrimitive()) {
- return promote(c);
- }
- if (c == MemorySegment.class) {
- return MemorySegment.class;
- }
- throw new IllegalArgumentException("Invalid type for ABI: " + c.getTypeName());
- }
-
- private MemoryLayout variadicLayout(Class> c) {
- if (c == long.class) {
- return JAVA_LONG;
- } else if (c == double.class) {
- return JAVA_DOUBLE;
- } else if (c == MemorySegment.class) {
- return ADDRESS;
- } else {
- throw new IllegalArgumentException("Unhandled variadic argument class: " + c);
- }
- }
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/constants$0.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/constants$0.java
deleted file mode 100644
index d06cfe72..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/constants$0.java
+++ /dev/null
@@ -1,11 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.errno;
-
-final class constants$0 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$0() {}
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/errno$shared.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/errno$shared.java
new file mode 100644
index 00000000..499be5f7
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/errno$shared.java
@@ -0,0 +1,63 @@
+// Generated by jextract
+
+package net.codecrete.usb.linux.gen.errno;
+
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class errno$shared {
+
+ errno$shared() {
+ // Should not be called directly
+ }
+
+ public static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) Linker.nativeLinker().canonicalLayouts().get("bool");
+ public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char");
+ public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) Linker.nativeLinker().canonicalLayouts().get("short");
+ public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int");
+ public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long long");
+ public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) Linker.nativeLinker().canonicalLayouts().get("float");
+ public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double");
+ public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*"))
+ .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR));
+ public static final ValueLayout.OfLong C_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long");
+
+ static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls");
+
+ static void traceDowncall(String name, Object... args) {
+ String traceArgs = Arrays.stream(args)
+ .map(Object::toString)
+ .collect(Collectors.joining(", "));
+ System.out.printf("%s(%s)\n", name, traceArgs);
+ }
+
+ static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
+ try {
+ return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
+ } catch (ReflectiveOperationException ex) {
+ throw new AssertionError(ex);
+ }
+ }
+
+ static MemoryLayout align(MemoryLayout layout, long align) {
+ return switch (layout) {
+ case PaddingLayout p -> p;
+ case ValueLayout v -> v.withByteAlignment(align);
+ case GroupLayout g -> {
+ MemoryLayout[] alignedMembers = g.memberLayouts().stream()
+ .map(m -> align(m, align)).toArray(MemoryLayout[]::new);
+ yield g instanceof StructLayout ?
+ MemoryLayout.structLayout(alignedMembers) : MemoryLayout.unionLayout(alignedMembers);
+ }
+ case SequenceLayout s -> MemoryLayout.sequenceLayout(s.elementCount(), align(s.elementLayout(), align));
+ };
+ }
+}
+
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/errno.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/errno.java
index 5db1b041..d4397f45 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/errno.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/errno.java
@@ -2,51 +2,98 @@
package net.codecrete.usb.linux.gen.errno;
-import java.lang.foreign.AddressLayout;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
import static java.lang.foreign.ValueLayout.*;
-public class errno {
-
- public static final OfByte C_CHAR = JAVA_BYTE;
- public static final OfShort C_SHORT = JAVA_SHORT;
- public static final OfInt C_INT = JAVA_INT;
- public static final OfLong C_LONG = JAVA_LONG;
- public static final OfLong C_LONG_LONG = JAVA_LONG;
- public static final OfFloat C_FLOAT = JAVA_FLOAT;
- public static final OfDouble C_DOUBLE = JAVA_DOUBLE;
- public static final AddressLayout C_POINTER = RuntimeHelper.POINTER;
- /**
- * {@snippet :
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class errno extends errno$shared {
+
+ errno() {
+ // Should not be called directly
+ }
+
+ static final Arena LIBRARY_ARENA = Arena.ofAuto();
+
+ static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup()
+ .or(Linker.nativeLinker().defaultLookup());
+
+ private static final int ENOENT = (int)2L;
+ /**
+ * {@snippet lang=c :
+ * #define ENOENT 2
+ * }
+ */
+ public static int ENOENT() {
+ return ENOENT;
+ }
+ private static final int EINTR = (int)4L;
+ /**
+ * {@snippet lang=c :
+ * #define EINTR 4
+ * }
+ */
+ public static int EINTR() {
+ return EINTR;
+ }
+ private static final int EBADF = (int)9L;
+ /**
+ * {@snippet lang=c :
+ * #define EBADF 9
+ * }
+ */
+ public static int EBADF() {
+ return EBADF;
+ }
+ private static final int EAGAIN = (int)11L;
+ /**
+ * {@snippet lang=c :
* #define EAGAIN 11
* }
*/
public static int EAGAIN() {
- return (int)11L;
+ return EAGAIN;
}
+ private static final int ENODEV = (int)19L;
/**
- * {@snippet :
+ * {@snippet lang=c :
* #define ENODEV 19
* }
*/
public static int ENODEV() {
- return (int)19L;
+ return ENODEV;
}
+ private static final int EINVAL = (int)22L;
/**
- * {@snippet :
+ * {@snippet lang=c :
* #define EINVAL 22
* }
*/
public static int EINVAL() {
- return (int)22L;
+ return EINVAL;
}
+ private static final int EPIPE = (int)32L;
/**
- * {@snippet :
+ * {@snippet lang=c :
* #define EPIPE 32
* }
*/
public static int EPIPE() {
- return (int)32L;
+ return EPIPE;
+ }
+ private static final int ECANCELED = (int)125L;
+ /**
+ * {@snippet lang=c :
+ * #define ECANCELED 125
+ * }
+ */
+ public static int ECANCELED() {
+ return ECANCELED;
}
}
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/RuntimeHelper.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/RuntimeHelper.java
deleted file mode 100644
index 6b36ff84..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/RuntimeHelper.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package net.codecrete.usb.linux.gen.fcntl;
-// Generated by jextract
-
-import java.lang.foreign.*;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-
-import static java.lang.foreign.ValueLayout.*;
-
-final class RuntimeHelper {
-
- private static final Linker LINKER = Linker.nativeLinker();
- private static final ClassLoader LOADER = RuntimeHelper.class.getClassLoader();
- private static final MethodHandles.Lookup MH_LOOKUP = MethodHandles.lookup();
- private static final SymbolLookup SYMBOL_LOOKUP;
- private static final SegmentAllocator THROWING_ALLOCATOR = (x, y) -> { throw new AssertionError("should not reach here"); };
- static final AddressLayout POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE));
-
- final static SegmentAllocator CONSTANT_ALLOCATOR =
- (size, align) -> Arena.ofAuto().allocate(size, align);
-
- static {
-
- SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
- SYMBOL_LOOKUP = name -> loaderLookup.find(name).or(() -> LINKER.defaultLookup().find(name));
- }
-
- // Suppresses default constructor, ensuring non-instantiability.
- private RuntimeHelper() {}
-
- static T requireNonNull(T obj, String symbolName) {
- if (obj == null) {
- throw new UnsatisfiedLinkError("unresolved symbol: " + symbolName);
- }
- return obj;
- }
-
- static MemorySegment lookupGlobalVariable(String name, MemoryLayout layout) {
- return SYMBOL_LOOKUP.find(name)
- .map(s -> s.reinterpret(layout.byteSize()))
- .orElse(null);
- }
-
- static MethodHandle downcallHandle(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> LINKER.downcallHandle(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle downcallHandle(FunctionDescriptor fdesc) {
- return LINKER.downcallHandle(fdesc);
- }
-
- static MethodHandle downcallHandleVariadic(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> VarargsInvoker.make(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
- try {
- return MH_LOOKUP.findVirtual(fi, name, fdesc.toMethodType());
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment upcallStub(MethodHandle fiHandle, Z z, FunctionDescriptor fdesc, Arena scope) {
- try {
- fiHandle = fiHandle.bindTo(z);
- return LINKER.upcallStub(fiHandle, fdesc, scope);
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment asArray(MemorySegment addr, MemoryLayout layout, int numElements, Arena arena) {
- return addr.reinterpret(numElements * layout.byteSize(), arena, null);
- }
-
- // Internals only below this point
-
- private static final class VarargsInvoker {
- private static final MethodHandle INVOKE_MH;
- private final MemorySegment symbol;
- private final FunctionDescriptor function;
-
- private VarargsInvoker(MemorySegment symbol, FunctionDescriptor function) {
- this.symbol = symbol;
- this.function = function;
- }
-
- static {
- try {
- INVOKE_MH = MethodHandles.lookup().findVirtual(VarargsInvoker.class, "invoke", MethodType.methodType(Object.class, SegmentAllocator.class, Object[].class));
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException(e);
- }
- }
-
- static MethodHandle make(MemorySegment symbol, FunctionDescriptor function) {
- VarargsInvoker invoker = new VarargsInvoker(symbol, function);
- MethodHandle handle = INVOKE_MH.bindTo(invoker).asCollector(Object[].class, function.argumentLayouts().size() + 1);
- MethodType mtype = MethodType.methodType(function.returnLayout().isPresent() ? carrier(function.returnLayout().get(), true) : void.class);
- for (MemoryLayout layout : function.argumentLayouts()) {
- mtype = mtype.appendParameterTypes(carrier(layout, false));
- }
- mtype = mtype.appendParameterTypes(Object[].class);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mtype = mtype.insertParameterTypes(0, SegmentAllocator.class);
- } else {
- handle = MethodHandles.insertArguments(handle, 0, THROWING_ALLOCATOR);
- }
- return handle.asType(mtype);
- }
-
- static Class> carrier(MemoryLayout layout, boolean ret) {
- if (layout instanceof ValueLayout valueLayout) {
- return valueLayout.carrier();
- } else if (layout instanceof GroupLayout) {
- return MemorySegment.class;
- } else {
- throw new AssertionError("Cannot get here!");
- }
- }
-
- private Object invoke(SegmentAllocator allocator, Object[] args) throws Throwable {
- // one trailing Object[]
- int nNamedArgs = function.argumentLayouts().size();
- assert(args.length == nNamedArgs + 1);
- // The last argument is the array of vararg collector
- Object[] unnamedArgs = (Object[]) args[args.length - 1];
-
- int argsCount = nNamedArgs + unnamedArgs.length;
- Class>[] argTypes = new Class>[argsCount];
- MemoryLayout[] argLayouts = new MemoryLayout[nNamedArgs + unnamedArgs.length];
-
- int pos = 0;
- for (pos = 0; pos < nNamedArgs; pos++) {
- argLayouts[pos] = function.argumentLayouts().get(pos);
- }
-
- assert pos == nNamedArgs;
- for (Object o: unnamedArgs) {
- argLayouts[pos] = variadicLayout(normalize(o.getClass()));
- pos++;
- }
- assert pos == argsCount;
-
- FunctionDescriptor f = (function.returnLayout().isEmpty()) ?
- FunctionDescriptor.ofVoid(argLayouts) :
- FunctionDescriptor.of(function.returnLayout().get(), argLayouts);
- MethodHandle mh = LINKER.downcallHandle(symbol, f);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mh = mh.bindTo(allocator);
- }
- // flatten argument list so that it can be passed to an asSpreader MH
- Object[] allArgs = new Object[nNamedArgs + unnamedArgs.length];
- System.arraycopy(args, 0, allArgs, 0, nNamedArgs);
- System.arraycopy(unnamedArgs, 0, allArgs, nNamedArgs, unnamedArgs.length);
-
- return mh.asSpreader(Object[].class, argsCount).invoke(allArgs);
- }
-
- private static Class> unboxIfNeeded(Class> clazz) {
- if (clazz == Boolean.class) {
- return boolean.class;
- } else if (clazz == Void.class) {
- return void.class;
- } else if (clazz == Byte.class) {
- return byte.class;
- } else if (clazz == Character.class) {
- return char.class;
- } else if (clazz == Short.class) {
- return short.class;
- } else if (clazz == Integer.class) {
- return int.class;
- } else if (clazz == Long.class) {
- return long.class;
- } else if (clazz == Float.class) {
- return float.class;
- } else if (clazz == Double.class) {
- return double.class;
- } else {
- return clazz;
- }
- }
-
- private Class> promote(Class> c) {
- if (c == byte.class || c == char.class || c == short.class || c == int.class) {
- return long.class;
- } else if (c == float.class) {
- return double.class;
- } else {
- return c;
- }
- }
-
- private Class> normalize(Class> c) {
- c = unboxIfNeeded(c);
- if (c.isPrimitive()) {
- return promote(c);
- }
- if (c == MemorySegment.class) {
- return MemorySegment.class;
- }
- throw new IllegalArgumentException("Invalid type for ABI: " + c.getTypeName());
- }
-
- private MemoryLayout variadicLayout(Class> c) {
- if (c == long.class) {
- return JAVA_LONG;
- } else if (c == double.class) {
- return JAVA_DOUBLE;
- } else if (c == MemorySegment.class) {
- return ADDRESS;
- } else {
- throw new IllegalArgumentException("Unhandled variadic argument class: " + c);
- }
- }
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/constants$0.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/constants$0.java
deleted file mode 100644
index 384b1450..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/constants$0.java
+++ /dev/null
@@ -1,11 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.fcntl;
-
-final class constants$0 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$0() {}
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/fcntl$shared.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/fcntl$shared.java
new file mode 100644
index 00000000..0a6ca8e4
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/fcntl$shared.java
@@ -0,0 +1,63 @@
+// Generated by jextract
+
+package net.codecrete.usb.linux.gen.fcntl;
+
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class fcntl$shared {
+
+ fcntl$shared() {
+ // Should not be called directly
+ }
+
+ public static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) Linker.nativeLinker().canonicalLayouts().get("bool");
+ public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char");
+ public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) Linker.nativeLinker().canonicalLayouts().get("short");
+ public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int");
+ public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long long");
+ public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) Linker.nativeLinker().canonicalLayouts().get("float");
+ public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double");
+ public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*"))
+ .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR));
+ public static final ValueLayout.OfLong C_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long");
+
+ static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls");
+
+ static void traceDowncall(String name, Object... args) {
+ String traceArgs = Arrays.stream(args)
+ .map(Object::toString)
+ .collect(Collectors.joining(", "));
+ System.out.printf("%s(%s)\n", name, traceArgs);
+ }
+
+ static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
+ try {
+ return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
+ } catch (ReflectiveOperationException ex) {
+ throw new AssertionError(ex);
+ }
+ }
+
+ static MemoryLayout align(MemoryLayout layout, long align) {
+ return switch (layout) {
+ case PaddingLayout p -> p;
+ case ValueLayout v -> v.withByteAlignment(align);
+ case GroupLayout g -> {
+ MemoryLayout[] alignedMembers = g.memberLayouts().stream()
+ .map(m -> align(m, align)).toArray(MemoryLayout[]::new);
+ yield g instanceof StructLayout ?
+ MemoryLayout.structLayout(alignedMembers) : MemoryLayout.unionLayout(alignedMembers);
+ }
+ case SequenceLayout s -> MemoryLayout.sequenceLayout(s.elementCount(), align(s.elementLayout(), align));
+ };
+ }
+}
+
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/fcntl.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/fcntl.java
index a6dd2f36..72cbfb53 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/fcntl.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/fcntl/fcntl.java
@@ -2,35 +2,53 @@
package net.codecrete.usb.linux.gen.fcntl;
-import java.lang.foreign.AddressLayout;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
import static java.lang.foreign.ValueLayout.*;
-public class fcntl {
-
- public static final OfByte C_CHAR = JAVA_BYTE;
- public static final OfShort C_SHORT = JAVA_SHORT;
- public static final OfInt C_INT = JAVA_INT;
- public static final OfLong C_LONG = JAVA_LONG;
- public static final OfLong C_LONG_LONG = JAVA_LONG;
- public static final OfFloat C_FLOAT = JAVA_FLOAT;
- public static final OfDouble C_DOUBLE = JAVA_DOUBLE;
- public static final AddressLayout C_POINTER = RuntimeHelper.POINTER;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class fcntl extends fcntl$shared {
+
+ fcntl() {
+ // Should not be called directly
+ }
+
+ static final Arena LIBRARY_ARENA = Arena.ofAuto();
+
+ static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup()
+ .or(Linker.nativeLinker().defaultLookup());
+
+ private static final int O_RDWR = (int)2L;
/**
- * {@snippet :
+ * {@snippet lang=c :
* #define O_RDWR 2
* }
*/
public static int O_RDWR() {
- return (int)2L;
+ return O_RDWR;
}
+ private static final int FD_CLOEXEC = (int)1L;
/**
- * {@snippet :
+ * {@snippet lang=c :
+ * #define FD_CLOEXEC 1
+ * }
+ */
+ public static int FD_CLOEXEC() {
+ return FD_CLOEXEC;
+ }
+ private static final int O_CLOEXEC = (int)524288L;
+ /**
+ * {@snippet lang=c :
* #define O_CLOEXEC 524288
* }
*/
public static int O_CLOEXEC() {
- return (int)524288L;
+ return O_CLOEXEC;
}
}
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/RuntimeHelper.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/RuntimeHelper.java
deleted file mode 100644
index 5fdcd815..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/RuntimeHelper.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package net.codecrete.usb.linux.gen.poll;
-// Generated by jextract
-
-import java.lang.foreign.*;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-
-import static java.lang.foreign.ValueLayout.*;
-
-final class RuntimeHelper {
-
- private static final Linker LINKER = Linker.nativeLinker();
- private static final ClassLoader LOADER = RuntimeHelper.class.getClassLoader();
- private static final MethodHandles.Lookup MH_LOOKUP = MethodHandles.lookup();
- private static final SymbolLookup SYMBOL_LOOKUP;
- private static final SegmentAllocator THROWING_ALLOCATOR = (x, y) -> { throw new AssertionError("should not reach here"); };
- static final AddressLayout POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE));
-
- final static SegmentAllocator CONSTANT_ALLOCATOR =
- (size, align) -> Arena.ofAuto().allocate(size, align);
-
- static {
-
- SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
- SYMBOL_LOOKUP = name -> loaderLookup.find(name).or(() -> LINKER.defaultLookup().find(name));
- }
-
- // Suppresses default constructor, ensuring non-instantiability.
- private RuntimeHelper() {}
-
- static T requireNonNull(T obj, String symbolName) {
- if (obj == null) {
- throw new UnsatisfiedLinkError("unresolved symbol: " + symbolName);
- }
- return obj;
- }
-
- static MemorySegment lookupGlobalVariable(String name, MemoryLayout layout) {
- return SYMBOL_LOOKUP.find(name)
- .map(s -> s.reinterpret(layout.byteSize()))
- .orElse(null);
- }
-
- static MethodHandle downcallHandle(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> LINKER.downcallHandle(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle downcallHandle(FunctionDescriptor fdesc) {
- return LINKER.downcallHandle(fdesc);
- }
-
- static MethodHandle downcallHandleVariadic(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> VarargsInvoker.make(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
- try {
- return MH_LOOKUP.findVirtual(fi, name, fdesc.toMethodType());
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment upcallStub(MethodHandle fiHandle, Z z, FunctionDescriptor fdesc, Arena scope) {
- try {
- fiHandle = fiHandle.bindTo(z);
- return LINKER.upcallStub(fiHandle, fdesc, scope);
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment asArray(MemorySegment addr, MemoryLayout layout, int numElements, Arena arena) {
- return addr.reinterpret(numElements * layout.byteSize(), arena, null);
- }
-
- // Internals only below this point
-
- private static final class VarargsInvoker {
- private static final MethodHandle INVOKE_MH;
- private final MemorySegment symbol;
- private final FunctionDescriptor function;
-
- private VarargsInvoker(MemorySegment symbol, FunctionDescriptor function) {
- this.symbol = symbol;
- this.function = function;
- }
-
- static {
- try {
- INVOKE_MH = MethodHandles.lookup().findVirtual(VarargsInvoker.class, "invoke", MethodType.methodType(Object.class, SegmentAllocator.class, Object[].class));
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException(e);
- }
- }
-
- static MethodHandle make(MemorySegment symbol, FunctionDescriptor function) {
- VarargsInvoker invoker = new VarargsInvoker(symbol, function);
- MethodHandle handle = INVOKE_MH.bindTo(invoker).asCollector(Object[].class, function.argumentLayouts().size() + 1);
- MethodType mtype = MethodType.methodType(function.returnLayout().isPresent() ? carrier(function.returnLayout().get(), true) : void.class);
- for (MemoryLayout layout : function.argumentLayouts()) {
- mtype = mtype.appendParameterTypes(carrier(layout, false));
- }
- mtype = mtype.appendParameterTypes(Object[].class);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mtype = mtype.insertParameterTypes(0, SegmentAllocator.class);
- } else {
- handle = MethodHandles.insertArguments(handle, 0, THROWING_ALLOCATOR);
- }
- return handle.asType(mtype);
- }
-
- static Class> carrier(MemoryLayout layout, boolean ret) {
- if (layout instanceof ValueLayout valueLayout) {
- return valueLayout.carrier();
- } else if (layout instanceof GroupLayout) {
- return MemorySegment.class;
- } else {
- throw new AssertionError("Cannot get here!");
- }
- }
-
- private Object invoke(SegmentAllocator allocator, Object[] args) throws Throwable {
- // one trailing Object[]
- int nNamedArgs = function.argumentLayouts().size();
- assert(args.length == nNamedArgs + 1);
- // The last argument is the array of vararg collector
- Object[] unnamedArgs = (Object[]) args[args.length - 1];
-
- int argsCount = nNamedArgs + unnamedArgs.length;
- Class>[] argTypes = new Class>[argsCount];
- MemoryLayout[] argLayouts = new MemoryLayout[nNamedArgs + unnamedArgs.length];
-
- int pos = 0;
- for (pos = 0; pos < nNamedArgs; pos++) {
- argLayouts[pos] = function.argumentLayouts().get(pos);
- }
-
- assert pos == nNamedArgs;
- for (Object o: unnamedArgs) {
- argLayouts[pos] = variadicLayout(normalize(o.getClass()));
- pos++;
- }
- assert pos == argsCount;
-
- FunctionDescriptor f = (function.returnLayout().isEmpty()) ?
- FunctionDescriptor.ofVoid(argLayouts) :
- FunctionDescriptor.of(function.returnLayout().get(), argLayouts);
- MethodHandle mh = LINKER.downcallHandle(symbol, f);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mh = mh.bindTo(allocator);
- }
- // flatten argument list so that it can be passed to an asSpreader MH
- Object[] allArgs = new Object[nNamedArgs + unnamedArgs.length];
- System.arraycopy(args, 0, allArgs, 0, nNamedArgs);
- System.arraycopy(unnamedArgs, 0, allArgs, nNamedArgs, unnamedArgs.length);
-
- return mh.asSpreader(Object[].class, argsCount).invoke(allArgs);
- }
-
- private static Class> unboxIfNeeded(Class> clazz) {
- if (clazz == Boolean.class) {
- return boolean.class;
- } else if (clazz == Void.class) {
- return void.class;
- } else if (clazz == Byte.class) {
- return byte.class;
- } else if (clazz == Character.class) {
- return char.class;
- } else if (clazz == Short.class) {
- return short.class;
- } else if (clazz == Integer.class) {
- return int.class;
- } else if (clazz == Long.class) {
- return long.class;
- } else if (clazz == Float.class) {
- return float.class;
- } else if (clazz == Double.class) {
- return double.class;
- } else {
- return clazz;
- }
- }
-
- private Class> promote(Class> c) {
- if (c == byte.class || c == char.class || c == short.class || c == int.class) {
- return long.class;
- } else if (c == float.class) {
- return double.class;
- } else {
- return c;
- }
- }
-
- private Class> normalize(Class> c) {
- c = unboxIfNeeded(c);
- if (c.isPrimitive()) {
- return promote(c);
- }
- if (c == MemorySegment.class) {
- return MemorySegment.class;
- }
- throw new IllegalArgumentException("Invalid type for ABI: " + c.getTypeName());
- }
-
- private MemoryLayout variadicLayout(Class> c) {
- if (c == long.class) {
- return JAVA_LONG;
- } else if (c == double.class) {
- return JAVA_DOUBLE;
- } else if (c == MemorySegment.class) {
- return ADDRESS;
- } else {
- throw new IllegalArgumentException("Unhandled variadic argument class: " + c);
- }
- }
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/constants$0.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/constants$0.java
deleted file mode 100644
index 112fe8b5..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/constants$0.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.poll;
-
-import java.lang.foreign.FunctionDescriptor;
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.StructLayout;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.VarHandle;
-
-import static java.lang.foreign.ValueLayout.*;
-final class constants$0 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$0() {}
- static final StructLayout const$0 = MemoryLayout.structLayout(
- JAVA_INT.withName("fd"),
- JAVA_SHORT.withName("events"),
- JAVA_SHORT.withName("revents")
- ).withName("pollfd");
- static final VarHandle const$1 = constants$0.const$0.varHandle(MemoryLayout.PathElement.groupElement("fd"));
- static final VarHandle const$2 = constants$0.const$0.varHandle(MemoryLayout.PathElement.groupElement("events"));
- static final VarHandle const$3 = constants$0.const$0.varHandle(MemoryLayout.PathElement.groupElement("revents"));
- static final FunctionDescriptor const$4 = FunctionDescriptor.of(JAVA_INT,
- RuntimeHelper.POINTER,
- JAVA_LONG,
- JAVA_INT
- );
- static final MethodHandle const$5 = RuntimeHelper.downcallHandle(
- "poll",
- constants$0.const$4
- );
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/poll.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/poll.java
deleted file mode 100644
index c3002abe..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/poll.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.poll;
-
-import java.lang.foreign.AddressLayout;
-import java.lang.foreign.MemorySegment;
-import java.lang.invoke.MethodHandle;
-
-import static java.lang.foreign.ValueLayout.*;
-public class poll {
-
- public static final OfByte C_CHAR = JAVA_BYTE;
- public static final OfShort C_SHORT = JAVA_SHORT;
- public static final OfInt C_INT = JAVA_INT;
- public static final OfLong C_LONG = JAVA_LONG;
- public static final OfLong C_LONG_LONG = JAVA_LONG;
- public static final OfFloat C_FLOAT = JAVA_FLOAT;
- public static final OfDouble C_DOUBLE = JAVA_DOUBLE;
- public static final AddressLayout C_POINTER = RuntimeHelper.POINTER;
- /**
- * {@snippet :
- * #define POLLIN 1
- * }
- */
- public static int POLLIN() {
- return (int)1L;
- }
- /**
- * {@snippet :
- * #define POLLOUT 4
- * }
- */
- public static int POLLOUT() {
- return (int)4L;
- }
- /**
- * {@snippet :
- * #define POLLERR 8
- * }
- */
- public static int POLLERR() {
- return (int)8L;
- }
- public static MethodHandle poll$MH() {
- return RuntimeHelper.requireNonNull(constants$0.const$5,"poll");
- }
- /**
- * {@snippet :
- * int poll(struct pollfd* __fds, nfds_t __nfds, int __timeout);
- * }
- */
- public static int poll(MemorySegment __fds, long __nfds, int __timeout) {
- var mh$ = poll$MH();
- try {
- return (int)mh$.invokeExact(__fds, __nfds, __timeout);
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
- }
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/pollfd.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/pollfd.java
deleted file mode 100644
index 6d632aa7..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/poll/pollfd.java
+++ /dev/null
@@ -1,113 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.poll;
-
-import java.lang.foreign.Arena;
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentAllocator;
-import java.lang.invoke.VarHandle;
-/**
- * {@snippet :
- * struct pollfd {
- * int fd;
- * short events;
- * short revents;
- * };
- * }
- */
-public class pollfd {
-
- public static MemoryLayout $LAYOUT() {
- return constants$0.const$0;
- }
- public static VarHandle fd$VH() {
- return constants$0.const$1;
- }
- /**
- * Getter for field:
- * {@snippet :
- * int fd;
- * }
- */
- public static int fd$get(MemorySegment seg) {
- return (int)constants$0.const$1.get(seg);
- }
- /**
- * Setter for field:
- * {@snippet :
- * int fd;
- * }
- */
- public static void fd$set(MemorySegment seg, int x) {
- constants$0.const$1.set(seg, x);
- }
- public static int fd$get(MemorySegment seg, long index) {
- return (int)constants$0.const$1.get(seg.asSlice(index*sizeof()));
- }
- public static void fd$set(MemorySegment seg, long index, int x) {
- constants$0.const$1.set(seg.asSlice(index*sizeof()), x);
- }
- public static VarHandle events$VH() {
- return constants$0.const$2;
- }
- /**
- * Getter for field:
- * {@snippet :
- * short events;
- * }
- */
- public static short events$get(MemorySegment seg) {
- return (short)constants$0.const$2.get(seg);
- }
- /**
- * Setter for field:
- * {@snippet :
- * short events;
- * }
- */
- public static void events$set(MemorySegment seg, short x) {
- constants$0.const$2.set(seg, x);
- }
- public static short events$get(MemorySegment seg, long index) {
- return (short)constants$0.const$2.get(seg.asSlice(index*sizeof()));
- }
- public static void events$set(MemorySegment seg, long index, short x) {
- constants$0.const$2.set(seg.asSlice(index*sizeof()), x);
- }
- public static VarHandle revents$VH() {
- return constants$0.const$3;
- }
- /**
- * Getter for field:
- * {@snippet :
- * short revents;
- * }
- */
- public static short revents$get(MemorySegment seg) {
- return (short)constants$0.const$3.get(seg);
- }
- /**
- * Setter for field:
- * {@snippet :
- * short revents;
- * }
- */
- public static void revents$set(MemorySegment seg, short x) {
- constants$0.const$3.set(seg, x);
- }
- public static short revents$get(MemorySegment seg, long index) {
- return (short)constants$0.const$3.get(seg.asSlice(index*sizeof()));
- }
- public static void revents$set(MemorySegment seg, long index, short x) {
- constants$0.const$3.set(seg.asSlice(index*sizeof()), x);
- }
- public static long sizeof() { return $LAYOUT().byteSize(); }
- public static MemorySegment allocate(SegmentAllocator allocator) { return allocator.allocate($LAYOUT()); }
- public static MemorySegment allocateArray(long len, SegmentAllocator allocator) {
- return allocator.allocate(MemoryLayout.sequenceLayout(len, $LAYOUT()));
- }
- public static MemorySegment ofAddress(MemorySegment addr, Arena scope) { return RuntimeHelper.asArray(addr, $LAYOUT(), 1, scope); }
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/RuntimeHelper.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/RuntimeHelper.java
deleted file mode 100644
index e0a365d5..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/RuntimeHelper.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package net.codecrete.usb.linux.gen.string;
-// Generated by jextract
-
-import java.lang.foreign.*;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-
-import static java.lang.foreign.ValueLayout.*;
-
-final class RuntimeHelper {
-
- private static final Linker LINKER = Linker.nativeLinker();
- private static final ClassLoader LOADER = RuntimeHelper.class.getClassLoader();
- private static final MethodHandles.Lookup MH_LOOKUP = MethodHandles.lookup();
- private static final SymbolLookup SYMBOL_LOOKUP;
- private static final SegmentAllocator THROWING_ALLOCATOR = (x, y) -> { throw new AssertionError("should not reach here"); };
- static final AddressLayout POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE));
-
- final static SegmentAllocator CONSTANT_ALLOCATOR =
- (size, align) -> Arena.ofAuto().allocate(size, align);
-
- static {
-
- SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
- SYMBOL_LOOKUP = name -> loaderLookup.find(name).or(() -> LINKER.defaultLookup().find(name));
- }
-
- // Suppresses default constructor, ensuring non-instantiability.
- private RuntimeHelper() {}
-
- static T requireNonNull(T obj, String symbolName) {
- if (obj == null) {
- throw new UnsatisfiedLinkError("unresolved symbol: " + symbolName);
- }
- return obj;
- }
-
- static MemorySegment lookupGlobalVariable(String name, MemoryLayout layout) {
- return SYMBOL_LOOKUP.find(name)
- .map(s -> s.reinterpret(layout.byteSize()))
- .orElse(null);
- }
-
- static MethodHandle downcallHandle(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> LINKER.downcallHandle(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle downcallHandle(FunctionDescriptor fdesc) {
- return LINKER.downcallHandle(fdesc);
- }
-
- static MethodHandle downcallHandleVariadic(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> VarargsInvoker.make(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
- try {
- return MH_LOOKUP.findVirtual(fi, name, fdesc.toMethodType());
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment upcallStub(MethodHandle fiHandle, Z z, FunctionDescriptor fdesc, Arena scope) {
- try {
- fiHandle = fiHandle.bindTo(z);
- return LINKER.upcallStub(fiHandle, fdesc, scope);
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment asArray(MemorySegment addr, MemoryLayout layout, int numElements, Arena arena) {
- return addr.reinterpret(numElements * layout.byteSize(), arena, null);
- }
-
- // Internals only below this point
-
- private static final class VarargsInvoker {
- private static final MethodHandle INVOKE_MH;
- private final MemorySegment symbol;
- private final FunctionDescriptor function;
-
- private VarargsInvoker(MemorySegment symbol, FunctionDescriptor function) {
- this.symbol = symbol;
- this.function = function;
- }
-
- static {
- try {
- INVOKE_MH = MethodHandles.lookup().findVirtual(VarargsInvoker.class, "invoke", MethodType.methodType(Object.class, SegmentAllocator.class, Object[].class));
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException(e);
- }
- }
-
- static MethodHandle make(MemorySegment symbol, FunctionDescriptor function) {
- VarargsInvoker invoker = new VarargsInvoker(symbol, function);
- MethodHandle handle = INVOKE_MH.bindTo(invoker).asCollector(Object[].class, function.argumentLayouts().size() + 1);
- MethodType mtype = MethodType.methodType(function.returnLayout().isPresent() ? carrier(function.returnLayout().get(), true) : void.class);
- for (MemoryLayout layout : function.argumentLayouts()) {
- mtype = mtype.appendParameterTypes(carrier(layout, false));
- }
- mtype = mtype.appendParameterTypes(Object[].class);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mtype = mtype.insertParameterTypes(0, SegmentAllocator.class);
- } else {
- handle = MethodHandles.insertArguments(handle, 0, THROWING_ALLOCATOR);
- }
- return handle.asType(mtype);
- }
-
- static Class> carrier(MemoryLayout layout, boolean ret) {
- if (layout instanceof ValueLayout valueLayout) {
- return valueLayout.carrier();
- } else if (layout instanceof GroupLayout) {
- return MemorySegment.class;
- } else {
- throw new AssertionError("Cannot get here!");
- }
- }
-
- private Object invoke(SegmentAllocator allocator, Object[] args) throws Throwable {
- // one trailing Object[]
- int nNamedArgs = function.argumentLayouts().size();
- assert(args.length == nNamedArgs + 1);
- // The last argument is the array of vararg collector
- Object[] unnamedArgs = (Object[]) args[args.length - 1];
-
- int argsCount = nNamedArgs + unnamedArgs.length;
- Class>[] argTypes = new Class>[argsCount];
- MemoryLayout[] argLayouts = new MemoryLayout[nNamedArgs + unnamedArgs.length];
-
- int pos = 0;
- for (pos = 0; pos < nNamedArgs; pos++) {
- argLayouts[pos] = function.argumentLayouts().get(pos);
- }
-
- assert pos == nNamedArgs;
- for (Object o: unnamedArgs) {
- argLayouts[pos] = variadicLayout(normalize(o.getClass()));
- pos++;
- }
- assert pos == argsCount;
-
- FunctionDescriptor f = (function.returnLayout().isEmpty()) ?
- FunctionDescriptor.ofVoid(argLayouts) :
- FunctionDescriptor.of(function.returnLayout().get(), argLayouts);
- MethodHandle mh = LINKER.downcallHandle(symbol, f);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mh = mh.bindTo(allocator);
- }
- // flatten argument list so that it can be passed to an asSpreader MH
- Object[] allArgs = new Object[nNamedArgs + unnamedArgs.length];
- System.arraycopy(args, 0, allArgs, 0, nNamedArgs);
- System.arraycopy(unnamedArgs, 0, allArgs, nNamedArgs, unnamedArgs.length);
-
- return mh.asSpreader(Object[].class, argsCount).invoke(allArgs);
- }
-
- private static Class> unboxIfNeeded(Class> clazz) {
- if (clazz == Boolean.class) {
- return boolean.class;
- } else if (clazz == Void.class) {
- return void.class;
- } else if (clazz == Byte.class) {
- return byte.class;
- } else if (clazz == Character.class) {
- return char.class;
- } else if (clazz == Short.class) {
- return short.class;
- } else if (clazz == Integer.class) {
- return int.class;
- } else if (clazz == Long.class) {
- return long.class;
- } else if (clazz == Float.class) {
- return float.class;
- } else if (clazz == Double.class) {
- return double.class;
- } else {
- return clazz;
- }
- }
-
- private Class> promote(Class> c) {
- if (c == byte.class || c == char.class || c == short.class || c == int.class) {
- return long.class;
- } else if (c == float.class) {
- return double.class;
- } else {
- return c;
- }
- }
-
- private Class> normalize(Class> c) {
- c = unboxIfNeeded(c);
- if (c.isPrimitive()) {
- return promote(c);
- }
- if (c == MemorySegment.class) {
- return MemorySegment.class;
- }
- throw new IllegalArgumentException("Invalid type for ABI: " + c.getTypeName());
- }
-
- private MemoryLayout variadicLayout(Class> c) {
- if (c == long.class) {
- return JAVA_LONG;
- } else if (c == double.class) {
- return JAVA_DOUBLE;
- } else if (c == MemorySegment.class) {
- return ADDRESS;
- } else {
- throw new IllegalArgumentException("Unhandled variadic argument class: " + c);
- }
- }
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/constants$0.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/constants$0.java
deleted file mode 100644
index bfe9c830..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/constants$0.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.string;
-
-import java.lang.foreign.FunctionDescriptor;
-import java.lang.invoke.MethodHandle;
-
-import static java.lang.foreign.ValueLayout.JAVA_INT;
-final class constants$0 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$0() {}
- static final FunctionDescriptor const$0 = FunctionDescriptor.of(RuntimeHelper.POINTER,
- JAVA_INT
- );
- static final MethodHandle const$1 = RuntimeHelper.downcallHandle(
- "strerror",
- constants$0.const$0
- );
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/string$shared.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/string$shared.java
new file mode 100644
index 00000000..824bb277
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/string$shared.java
@@ -0,0 +1,63 @@
+// Generated by jextract
+
+package net.codecrete.usb.linux.gen.string;
+
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class string$shared {
+
+ string$shared() {
+ // Should not be called directly
+ }
+
+ public static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) Linker.nativeLinker().canonicalLayouts().get("bool");
+ public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char");
+ public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) Linker.nativeLinker().canonicalLayouts().get("short");
+ public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int");
+ public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long long");
+ public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) Linker.nativeLinker().canonicalLayouts().get("float");
+ public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double");
+ public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*"))
+ .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR));
+ public static final ValueLayout.OfLong C_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long");
+
+ static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls");
+
+ static void traceDowncall(String name, Object... args) {
+ String traceArgs = Arrays.stream(args)
+ .map(Object::toString)
+ .collect(Collectors.joining(", "));
+ System.out.printf("%s(%s)\n", name, traceArgs);
+ }
+
+ static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
+ try {
+ return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
+ } catch (ReflectiveOperationException ex) {
+ throw new AssertionError(ex);
+ }
+ }
+
+ static MemoryLayout align(MemoryLayout layout, long align) {
+ return switch (layout) {
+ case PaddingLayout p -> p;
+ case ValueLayout v -> v.withByteAlignment(align);
+ case GroupLayout g -> {
+ MemoryLayout[] alignedMembers = g.memberLayouts().stream()
+ .map(m -> align(m, align)).toArray(MemoryLayout[]::new);
+ yield g instanceof StructLayout ?
+ MemoryLayout.structLayout(alignedMembers) : MemoryLayout.unionLayout(alignedMembers);
+ }
+ case SequenceLayout s -> MemoryLayout.sequenceLayout(s.elementCount(), align(s.elementLayout(), align));
+ };
+ }
+}
+
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/string.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/string.java
index 895d225f..de0e4452 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/string.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/string/string.java
@@ -2,37 +2,86 @@
package net.codecrete.usb.linux.gen.string;
-import java.lang.foreign.AddressLayout;
-import java.lang.foreign.MemorySegment;
-import java.lang.invoke.MethodHandle;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
import static java.lang.foreign.ValueLayout.*;
-public class string {
-
- public static final OfByte C_CHAR = JAVA_BYTE;
- public static final OfShort C_SHORT = JAVA_SHORT;
- public static final OfInt C_INT = JAVA_INT;
- public static final OfLong C_LONG = JAVA_LONG;
- public static final OfLong C_LONG_LONG = JAVA_LONG;
- public static final OfFloat C_FLOAT = JAVA_FLOAT;
- public static final OfDouble C_DOUBLE = JAVA_DOUBLE;
- public static final AddressLayout C_POINTER = RuntimeHelper.POINTER;
- public static MethodHandle strerror$MH() {
- return RuntimeHelper.requireNonNull(constants$0.const$1,"strerror");
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class string extends string$shared {
+
+ string() {
+ // Should not be called directly
+ }
+
+ static final Arena LIBRARY_ARENA = Arena.ofAuto();
+
+ static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup()
+ .or(Linker.nativeLinker().defaultLookup());
+
+
+ private static class strerror {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ string.C_POINTER,
+ string.C_INT
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("strerror");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * extern char *strerror(int __errnum)
+ * }
+ */
+ public static FunctionDescriptor strerror$descriptor() {
+ return strerror.DESC;
}
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * extern char *strerror(int __errnum)
+ * }
+ */
+ public static MethodHandle strerror$handle() {
+ return strerror.HANDLE;
+ }
+
/**
- * {@snippet :
- * char* strerror(int __errnum);
+ * Address for:
+ * {@snippet lang=c :
+ * extern char *strerror(int __errnum)
+ * }
+ */
+ public static MemorySegment strerror$address() {
+ return strerror.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * extern char *strerror(int __errnum)
* }
*/
public static MemorySegment strerror(int __errnum) {
- var mh$ = strerror$MH();
+ var mh$ = strerror.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(__errnum);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("strerror", __errnum);
+ }
+ return (MemorySegment)mh$.invokeExact(__errnum);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
}
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/RuntimeHelper.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/RuntimeHelper.java
deleted file mode 100644
index c2ccf243..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/RuntimeHelper.java
+++ /dev/null
@@ -1,228 +0,0 @@
-package net.codecrete.usb.linux.gen.udev;
-// Generated by jextract
-
-import java.lang.foreign.*;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-
-import static java.lang.foreign.ValueLayout.*;
-
-final class RuntimeHelper {
-
- private static final Linker LINKER = Linker.nativeLinker();
- private static final ClassLoader LOADER = RuntimeHelper.class.getClassLoader();
- private static final MethodHandles.Lookup MH_LOOKUP = MethodHandles.lookup();
- private static final SymbolLookup SYMBOL_LOOKUP;
- private static final SegmentAllocator THROWING_ALLOCATOR = (x, y) -> { throw new AssertionError("should not reach here"); };
- static final AddressLayout POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE));
-
- final static SegmentAllocator CONSTANT_ALLOCATOR =
- (size, align) -> Arena.ofAuto().allocate(size, align);
-
- static {
-// System.loadLibrary("udev");
-// SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
- SymbolLookup loaderLookup = SymbolLookup.libraryLookup("libudev.so.1", Arena.global());
- SYMBOL_LOOKUP = name -> loaderLookup.find(name).or(() -> LINKER.defaultLookup().find(name));
- }
-
- // Suppresses default constructor, ensuring non-instantiability.
- private RuntimeHelper() {}
-
- static T requireNonNull(T obj, String symbolName) {
- if (obj == null) {
- throw new UnsatisfiedLinkError("unresolved symbol: " + symbolName);
- }
- return obj;
- }
-
- static MemorySegment lookupGlobalVariable(String name, MemoryLayout layout) {
- return SYMBOL_LOOKUP.find(name)
- .map(s -> s.reinterpret(layout.byteSize()))
- .orElse(null);
- }
-
- static MethodHandle downcallHandle(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> LINKER.downcallHandle(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle downcallHandle(FunctionDescriptor fdesc) {
- return LINKER.downcallHandle(fdesc);
- }
-
- static MethodHandle downcallHandleVariadic(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> VarargsInvoker.make(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
- try {
- return MH_LOOKUP.findVirtual(fi, name, fdesc.toMethodType());
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment upcallStub(MethodHandle fiHandle, Z z, FunctionDescriptor fdesc, Arena scope) {
- try {
- fiHandle = fiHandle.bindTo(z);
- return LINKER.upcallStub(fiHandle, fdesc, scope);
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment asArray(MemorySegment addr, MemoryLayout layout, int numElements, Arena arena) {
- return addr.reinterpret(numElements * layout.byteSize(), arena, null);
- }
-
- // Internals only below this point
-
- private static final class VarargsInvoker {
- private static final MethodHandle INVOKE_MH;
- private final MemorySegment symbol;
- private final FunctionDescriptor function;
-
- private VarargsInvoker(MemorySegment symbol, FunctionDescriptor function) {
- this.symbol = symbol;
- this.function = function;
- }
-
- static {
- try {
- INVOKE_MH = MethodHandles.lookup().findVirtual(VarargsInvoker.class, "invoke", MethodType.methodType(Object.class, SegmentAllocator.class, Object[].class));
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException(e);
- }
- }
-
- static MethodHandle make(MemorySegment symbol, FunctionDescriptor function) {
- VarargsInvoker invoker = new VarargsInvoker(symbol, function);
- MethodHandle handle = INVOKE_MH.bindTo(invoker).asCollector(Object[].class, function.argumentLayouts().size() + 1);
- MethodType mtype = MethodType.methodType(function.returnLayout().isPresent() ? carrier(function.returnLayout().get(), true) : void.class);
- for (MemoryLayout layout : function.argumentLayouts()) {
- mtype = mtype.appendParameterTypes(carrier(layout, false));
- }
- mtype = mtype.appendParameterTypes(Object[].class);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mtype = mtype.insertParameterTypes(0, SegmentAllocator.class);
- } else {
- handle = MethodHandles.insertArguments(handle, 0, THROWING_ALLOCATOR);
- }
- return handle.asType(mtype);
- }
-
- static Class> carrier(MemoryLayout layout, boolean ret) {
- if (layout instanceof ValueLayout valueLayout) {
- return valueLayout.carrier();
- } else if (layout instanceof GroupLayout) {
- return MemorySegment.class;
- } else {
- throw new AssertionError("Cannot get here!");
- }
- }
-
- private Object invoke(SegmentAllocator allocator, Object[] args) throws Throwable {
- // one trailing Object[]
- int nNamedArgs = function.argumentLayouts().size();
- assert(args.length == nNamedArgs + 1);
- // The last argument is the array of vararg collector
- Object[] unnamedArgs = (Object[]) args[args.length - 1];
-
- int argsCount = nNamedArgs + unnamedArgs.length;
- Class>[] argTypes = new Class>[argsCount];
- MemoryLayout[] argLayouts = new MemoryLayout[nNamedArgs + unnamedArgs.length];
-
- int pos = 0;
- for (pos = 0; pos < nNamedArgs; pos++) {
- argLayouts[pos] = function.argumentLayouts().get(pos);
- }
-
- assert pos == nNamedArgs;
- for (Object o: unnamedArgs) {
- argLayouts[pos] = variadicLayout(normalize(o.getClass()));
- pos++;
- }
- assert pos == argsCount;
-
- FunctionDescriptor f = (function.returnLayout().isEmpty()) ?
- FunctionDescriptor.ofVoid(argLayouts) :
- FunctionDescriptor.of(function.returnLayout().get(), argLayouts);
- MethodHandle mh = LINKER.downcallHandle(symbol, f);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mh = mh.bindTo(allocator);
- }
- // flatten argument list so that it can be passed to an asSpreader MH
- Object[] allArgs = new Object[nNamedArgs + unnamedArgs.length];
- System.arraycopy(args, 0, allArgs, 0, nNamedArgs);
- System.arraycopy(unnamedArgs, 0, allArgs, nNamedArgs, unnamedArgs.length);
-
- return mh.asSpreader(Object[].class, argsCount).invoke(allArgs);
- }
-
- private static Class> unboxIfNeeded(Class> clazz) {
- if (clazz == Boolean.class) {
- return boolean.class;
- } else if (clazz == Void.class) {
- return void.class;
- } else if (clazz == Byte.class) {
- return byte.class;
- } else if (clazz == Character.class) {
- return char.class;
- } else if (clazz == Short.class) {
- return short.class;
- } else if (clazz == Integer.class) {
- return int.class;
- } else if (clazz == Long.class) {
- return long.class;
- } else if (clazz == Float.class) {
- return float.class;
- } else if (clazz == Double.class) {
- return double.class;
- } else {
- return clazz;
- }
- }
-
- private Class> promote(Class> c) {
- if (c == byte.class || c == char.class || c == short.class || c == int.class) {
- return long.class;
- } else if (c == float.class) {
- return double.class;
- } else {
- return c;
- }
- }
-
- private Class> normalize(Class> c) {
- c = unboxIfNeeded(c);
- if (c.isPrimitive()) {
- return promote(c);
- }
- if (c == MemorySegment.class) {
- return MemorySegment.class;
- }
- throw new IllegalArgumentException("Invalid type for ABI: " + c.getTypeName());
- }
-
- private MemoryLayout variadicLayout(Class> c) {
- if (c == long.class) {
- return JAVA_LONG;
- } else if (c == double.class) {
- return JAVA_DOUBLE;
- } else if (c == MemorySegment.class) {
- return ADDRESS;
- } else {
- throw new IllegalArgumentException("Unhandled variadic argument class: " + c);
- }
- }
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$0.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$0.java
deleted file mode 100644
index 1017f6fe..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$0.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.udev;
-
-import java.lang.foreign.FunctionDescriptor;
-import java.lang.invoke.MethodHandle;
-final class constants$0 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$0() {}
- static final FunctionDescriptor const$0 = FunctionDescriptor.of(RuntimeHelper.POINTER);
- static final MethodHandle const$1 = RuntimeHelper.downcallHandle(
- "udev_new",
- constants$0.const$0
- );
- static final FunctionDescriptor const$2 = FunctionDescriptor.of(RuntimeHelper.POINTER,
- RuntimeHelper.POINTER
- );
- static final MethodHandle const$3 = RuntimeHelper.downcallHandle(
- "udev_list_entry_get_next",
- constants$0.const$2
- );
- static final MethodHandle const$4 = RuntimeHelper.downcallHandle(
- "udev_list_entry_get_name",
- constants$0.const$2
- );
- static final MethodHandle const$5 = RuntimeHelper.downcallHandle(
- "udev_device_unref",
- constants$0.const$2
- );
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$1.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$1.java
deleted file mode 100644
index 9d318029..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$1.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.udev;
-
-import java.lang.foreign.FunctionDescriptor;
-import java.lang.invoke.MethodHandle;
-final class constants$1 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$1() {}
- static final FunctionDescriptor const$0 = FunctionDescriptor.of(RuntimeHelper.POINTER,
- RuntimeHelper.POINTER,
- RuntimeHelper.POINTER
- );
- static final MethodHandle const$1 = RuntimeHelper.downcallHandle(
- "udev_device_new_from_syspath",
- constants$1.const$0
- );
- static final MethodHandle const$2 = RuntimeHelper.downcallHandle(
- "udev_device_get_devtype",
- constants$0.const$2
- );
- static final MethodHandle const$3 = RuntimeHelper.downcallHandle(
- "udev_device_get_devnode",
- constants$0.const$2
- );
- static final MethodHandle const$4 = RuntimeHelper.downcallHandle(
- "udev_device_get_action",
- constants$0.const$2
- );
- static final MethodHandle const$5 = RuntimeHelper.downcallHandle(
- "udev_device_get_sysattr_value",
- constants$1.const$0
- );
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$2.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$2.java
deleted file mode 100644
index 88ac3987..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$2.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.udev;
-
-import java.lang.foreign.FunctionDescriptor;
-import java.lang.invoke.MethodHandle;
-
-import static java.lang.foreign.ValueLayout.JAVA_INT;
-final class constants$2 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$2() {}
- static final MethodHandle const$0 = RuntimeHelper.downcallHandle(
- "udev_monitor_new_from_netlink",
- constants$1.const$0
- );
- static final FunctionDescriptor const$1 = FunctionDescriptor.of(JAVA_INT,
- RuntimeHelper.POINTER
- );
- static final MethodHandle const$2 = RuntimeHelper.downcallHandle(
- "udev_monitor_enable_receiving",
- constants$2.const$1
- );
- static final MethodHandle const$3 = RuntimeHelper.downcallHandle(
- "udev_monitor_get_fd",
- constants$2.const$1
- );
- static final MethodHandle const$4 = RuntimeHelper.downcallHandle(
- "udev_monitor_receive_device",
- constants$0.const$2
- );
- static final FunctionDescriptor const$5 = FunctionDescriptor.of(JAVA_INT,
- RuntimeHelper.POINTER,
- RuntimeHelper.POINTER,
- RuntimeHelper.POINTER
- );
- static final MethodHandle const$6 = RuntimeHelper.downcallHandle(
- "udev_monitor_filter_add_match_subsystem_devtype",
- constants$2.const$5
- );
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$3.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$3.java
deleted file mode 100644
index b469b77f..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/constants$3.java
+++ /dev/null
@@ -1,39 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.udev;
-
-import java.lang.foreign.FunctionDescriptor;
-import java.lang.invoke.MethodHandle;
-
-import static java.lang.foreign.ValueLayout.JAVA_INT;
-final class constants$3 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$3() {}
- static final MethodHandle const$0 = RuntimeHelper.downcallHandle(
- "udev_enumerate_unref",
- constants$0.const$2
- );
- static final MethodHandle const$1 = RuntimeHelper.downcallHandle(
- "udev_enumerate_new",
- constants$0.const$2
- );
- static final FunctionDescriptor const$2 = FunctionDescriptor.of(JAVA_INT,
- RuntimeHelper.POINTER,
- RuntimeHelper.POINTER
- );
- static final MethodHandle const$3 = RuntimeHelper.downcallHandle(
- "udev_enumerate_add_match_subsystem",
- constants$3.const$2
- );
- static final MethodHandle const$4 = RuntimeHelper.downcallHandle(
- "udev_enumerate_scan_devices",
- constants$2.const$1
- );
- static final MethodHandle const$5 = RuntimeHelper.downcallHandle(
- "udev_enumerate_get_list_entry",
- constants$0.const$2
- );
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/udev$shared.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/udev$shared.java
new file mode 100644
index 00000000..b7958526
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/udev$shared.java
@@ -0,0 +1,63 @@
+// Generated by jextract
+
+package net.codecrete.usb.linux.gen.udev;
+
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class udev$shared {
+
+ udev$shared() {
+ // Should not be called directly
+ }
+
+ public static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) Linker.nativeLinker().canonicalLayouts().get("bool");
+ public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char");
+ public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) Linker.nativeLinker().canonicalLayouts().get("short");
+ public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int");
+ public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long long");
+ public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) Linker.nativeLinker().canonicalLayouts().get("float");
+ public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double");
+ public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*"))
+ .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR));
+ public static final ValueLayout.OfLong C_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long");
+
+ static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls");
+
+ static void traceDowncall(String name, Object... args) {
+ String traceArgs = Arrays.stream(args)
+ .map(Object::toString)
+ .collect(Collectors.joining(", "));
+ System.out.printf("%s(%s)\n", name, traceArgs);
+ }
+
+ static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
+ try {
+ return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
+ } catch (ReflectiveOperationException ex) {
+ throw new AssertionError(ex);
+ }
+ }
+
+ static MemoryLayout align(MemoryLayout layout, long align) {
+ return switch (layout) {
+ case PaddingLayout p -> p;
+ case ValueLayout v -> v.withByteAlignment(align);
+ case GroupLayout g -> {
+ MemoryLayout[] alignedMembers = g.memberLayouts().stream()
+ .map(m -> align(m, align)).toArray(MemoryLayout[]::new);
+ yield g instanceof StructLayout ?
+ MemoryLayout.structLayout(alignedMembers) : MemoryLayout.unionLayout(alignedMembers);
+ }
+ case SequenceLayout s -> MemoryLayout.sequenceLayout(s.elementCount(), align(s.elementLayout(), align));
+ };
+ }
+}
+
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/udev.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/udev.java
index bcc40cb5..f43a699d 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/udev.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/udev/udev.java
@@ -2,325 +2,1171 @@
package net.codecrete.usb.linux.gen.udev;
-import java.lang.foreign.AddressLayout;
-import java.lang.foreign.MemorySegment;
-import java.lang.invoke.MethodHandle;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
import static java.lang.foreign.ValueLayout.*;
-public class udev {
+import static java.lang.foreign.MemoryLayout.PathElement.*;
- public static final OfByte C_CHAR = JAVA_BYTE;
- public static final OfShort C_SHORT = JAVA_SHORT;
- public static final OfInt C_INT = JAVA_INT;
- public static final OfLong C_LONG = JAVA_LONG;
- public static final OfLong C_LONG_LONG = JAVA_LONG;
- public static final OfFloat C_FLOAT = JAVA_FLOAT;
- public static final OfDouble C_DOUBLE = JAVA_DOUBLE;
- public static final AddressLayout C_POINTER = RuntimeHelper.POINTER;
- public static MethodHandle udev_new$MH() {
- return RuntimeHelper.requireNonNull(constants$0.const$1,"udev_new");
+public class udev extends udev$shared {
+
+ udev() {
+ // Should not be called directly
+ }
+
+ static final Arena LIBRARY_ARENA = Arena.ofAuto();
+
+ static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup("libudev.so.1", LIBRARY_ARENA)
+ .or(SymbolLookup.loaderLookup())
+ .or(Linker.nativeLinker().defaultLookup());
+
+
+ private static class udev_new {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_new");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * struct udev *udev_new(void)
+ * }
+ */
+ public static FunctionDescriptor udev_new$descriptor() {
+ return udev_new.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * struct udev *udev_new(void)
+ * }
+ */
+ public static MethodHandle udev_new$handle() {
+ return udev_new.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * struct udev *udev_new(void)
+ * }
+ */
+ public static MemorySegment udev_new$address() {
+ return udev_new.ADDR;
}
+
/**
- * {@snippet :
- * struct udev* udev_new();
+ * {@snippet lang=c :
+ * struct udev *udev_new(void)
* }
*/
public static MemorySegment udev_new() {
- var mh$ = udev_new$MH();
+ var mh$ = udev_new.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact();
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_new");
+ }
+ return (MemorySegment)mh$.invokeExact();
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_list_entry_get_next$MH() {
- return RuntimeHelper.requireNonNull(constants$0.const$3,"udev_list_entry_get_next");
+
+ private static class udev_list_entry_get_next {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_list_entry_get_next");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry)
+ * }
+ */
+ public static FunctionDescriptor udev_list_entry_get_next$descriptor() {
+ return udev_list_entry_get_next.DESC;
}
+
/**
- * {@snippet :
- * struct udev_list_entry* udev_list_entry_get_next(struct udev_list_entry* list_entry);
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry)
+ * }
+ */
+ public static MethodHandle udev_list_entry_get_next$handle() {
+ return udev_list_entry_get_next.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry)
+ * }
+ */
+ public static MemorySegment udev_list_entry_get_next$address() {
+ return udev_list_entry_get_next.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry)
* }
*/
public static MemorySegment udev_list_entry_get_next(MemorySegment list_entry) {
- var mh$ = udev_list_entry_get_next$MH();
+ var mh$ = udev_list_entry_get_next.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(list_entry);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_list_entry_get_next", list_entry);
+ }
+ return (MemorySegment)mh$.invokeExact(list_entry);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_list_entry_get_name$MH() {
- return RuntimeHelper.requireNonNull(constants$0.const$4,"udev_list_entry_get_name");
+
+ private static class udev_list_entry_get_name {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_list_entry_get_name");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * const char *udev_list_entry_get_name(struct udev_list_entry *list_entry)
+ * }
+ */
+ public static FunctionDescriptor udev_list_entry_get_name$descriptor() {
+ return udev_list_entry_get_name.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * const char *udev_list_entry_get_name(struct udev_list_entry *list_entry)
+ * }
+ */
+ public static MethodHandle udev_list_entry_get_name$handle() {
+ return udev_list_entry_get_name.HANDLE;
}
+
/**
- * {@snippet :
- * char* udev_list_entry_get_name(struct udev_list_entry* list_entry);
+ * Address for:
+ * {@snippet lang=c :
+ * const char *udev_list_entry_get_name(struct udev_list_entry *list_entry)
+ * }
+ */
+ public static MemorySegment udev_list_entry_get_name$address() {
+ return udev_list_entry_get_name.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * const char *udev_list_entry_get_name(struct udev_list_entry *list_entry)
* }
*/
public static MemorySegment udev_list_entry_get_name(MemorySegment list_entry) {
- var mh$ = udev_list_entry_get_name$MH();
+ var mh$ = udev_list_entry_get_name.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(list_entry);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_list_entry_get_name", list_entry);
+ }
+ return (MemorySegment)mh$.invokeExact(list_entry);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_device_unref$MH() {
- return RuntimeHelper.requireNonNull(constants$0.const$5,"udev_device_unref");
+
+ private static class udev_device_unref {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_device_unref");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * struct udev_device *udev_device_unref(struct udev_device *udev_device)
+ * }
+ */
+ public static FunctionDescriptor udev_device_unref$descriptor() {
+ return udev_device_unref.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * struct udev_device *udev_device_unref(struct udev_device *udev_device)
+ * }
+ */
+ public static MethodHandle udev_device_unref$handle() {
+ return udev_device_unref.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * struct udev_device *udev_device_unref(struct udev_device *udev_device)
+ * }
+ */
+ public static MemorySegment udev_device_unref$address() {
+ return udev_device_unref.ADDR;
}
+
/**
- * {@snippet :
- * struct udev_device* udev_device_unref(struct udev_device* udev_device);
+ * {@snippet lang=c :
+ * struct udev_device *udev_device_unref(struct udev_device *udev_device)
* }
*/
public static MemorySegment udev_device_unref(MemorySegment udev_device) {
- var mh$ = udev_device_unref$MH();
+ var mh$ = udev_device_unref.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(udev_device);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_device_unref", udev_device);
+ }
+ return (MemorySegment)mh$.invokeExact(udev_device);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_device_new_from_syspath$MH() {
- return RuntimeHelper.requireNonNull(constants$1.const$1,"udev_device_new_from_syspath");
+
+ private static class udev_device_new_from_syspath {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_device_new_from_syspath");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath)
+ * }
+ */
+ public static FunctionDescriptor udev_device_new_from_syspath$descriptor() {
+ return udev_device_new_from_syspath.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath)
+ * }
+ */
+ public static MethodHandle udev_device_new_from_syspath$handle() {
+ return udev_device_new_from_syspath.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath)
+ * }
+ */
+ public static MemorySegment udev_device_new_from_syspath$address() {
+ return udev_device_new_from_syspath.ADDR;
}
+
/**
- * {@snippet :
- * struct udev_device* udev_device_new_from_syspath(struct udev* udev, char* syspath);
+ * {@snippet lang=c :
+ * struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath)
* }
*/
public static MemorySegment udev_device_new_from_syspath(MemorySegment udev, MemorySegment syspath) {
- var mh$ = udev_device_new_from_syspath$MH();
+ var mh$ = udev_device_new_from_syspath.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(udev, syspath);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_device_new_from_syspath", udev, syspath);
+ }
+ return (MemorySegment)mh$.invokeExact(udev, syspath);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_device_get_devtype$MH() {
- return RuntimeHelper.requireNonNull(constants$1.const$2,"udev_device_get_devtype");
+
+ private static class udev_device_get_devtype {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_device_get_devtype");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_devtype(struct udev_device *udev_device)
+ * }
+ */
+ public static FunctionDescriptor udev_device_get_devtype$descriptor() {
+ return udev_device_get_devtype.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_devtype(struct udev_device *udev_device)
+ * }
+ */
+ public static MethodHandle udev_device_get_devtype$handle() {
+ return udev_device_get_devtype.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_devtype(struct udev_device *udev_device)
+ * }
+ */
+ public static MemorySegment udev_device_get_devtype$address() {
+ return udev_device_get_devtype.ADDR;
}
+
/**
- * {@snippet :
- * char* udev_device_get_devtype(struct udev_device* udev_device);
+ * {@snippet lang=c :
+ * const char *udev_device_get_devtype(struct udev_device *udev_device)
* }
*/
public static MemorySegment udev_device_get_devtype(MemorySegment udev_device) {
- var mh$ = udev_device_get_devtype$MH();
+ var mh$ = udev_device_get_devtype.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(udev_device);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_device_get_devtype", udev_device);
+ }
+ return (MemorySegment)mh$.invokeExact(udev_device);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_device_get_devnode$MH() {
- return RuntimeHelper.requireNonNull(constants$1.const$3,"udev_device_get_devnode");
+
+ private static class udev_device_get_devnode {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_device_get_devnode");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_devnode(struct udev_device *udev_device)
+ * }
+ */
+ public static FunctionDescriptor udev_device_get_devnode$descriptor() {
+ return udev_device_get_devnode.DESC;
}
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_devnode(struct udev_device *udev_device)
+ * }
+ */
+ public static MethodHandle udev_device_get_devnode$handle() {
+ return udev_device_get_devnode.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_devnode(struct udev_device *udev_device)
+ * }
+ */
+ public static MemorySegment udev_device_get_devnode$address() {
+ return udev_device_get_devnode.ADDR;
+ }
+
/**
- * {@snippet :
- * char* udev_device_get_devnode(struct udev_device* udev_device);
+ * {@snippet lang=c :
+ * const char *udev_device_get_devnode(struct udev_device *udev_device)
* }
*/
public static MemorySegment udev_device_get_devnode(MemorySegment udev_device) {
- var mh$ = udev_device_get_devnode$MH();
+ var mh$ = udev_device_get_devnode.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(udev_device);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_device_get_devnode", udev_device);
+ }
+ return (MemorySegment)mh$.invokeExact(udev_device);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_device_get_action$MH() {
- return RuntimeHelper.requireNonNull(constants$1.const$4,"udev_device_get_action");
+
+ private static class udev_device_get_action {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_device_get_action");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_action(struct udev_device *udev_device)
+ * }
+ */
+ public static FunctionDescriptor udev_device_get_action$descriptor() {
+ return udev_device_get_action.DESC;
}
+
/**
- * {@snippet :
- * char* udev_device_get_action(struct udev_device* udev_device);
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_action(struct udev_device *udev_device)
+ * }
+ */
+ public static MethodHandle udev_device_get_action$handle() {
+ return udev_device_get_action.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_action(struct udev_device *udev_device)
+ * }
+ */
+ public static MemorySegment udev_device_get_action$address() {
+ return udev_device_get_action.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * const char *udev_device_get_action(struct udev_device *udev_device)
* }
*/
public static MemorySegment udev_device_get_action(MemorySegment udev_device) {
- var mh$ = udev_device_get_action$MH();
+ var mh$ = udev_device_get_action.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(udev_device);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_device_get_action", udev_device);
+ }
+ return (MemorySegment)mh$.invokeExact(udev_device);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_device_get_sysattr_value$MH() {
- return RuntimeHelper.requireNonNull(constants$1.const$5,"udev_device_get_sysattr_value");
+
+ private static class udev_device_get_sysattr_value {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_device_get_sysattr_value");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr)
+ * }
+ */
+ public static FunctionDescriptor udev_device_get_sysattr_value$descriptor() {
+ return udev_device_get_sysattr_value.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr)
+ * }
+ */
+ public static MethodHandle udev_device_get_sysattr_value$handle() {
+ return udev_device_get_sysattr_value.HANDLE;
}
+
/**
- * {@snippet :
- * char* udev_device_get_sysattr_value(struct udev_device* udev_device, char* sysattr);
+ * Address for:
+ * {@snippet lang=c :
+ * const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr)
+ * }
+ */
+ public static MemorySegment udev_device_get_sysattr_value$address() {
+ return udev_device_get_sysattr_value.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr)
* }
*/
public static MemorySegment udev_device_get_sysattr_value(MemorySegment udev_device, MemorySegment sysattr) {
- var mh$ = udev_device_get_sysattr_value$MH();
+ var mh$ = udev_device_get_sysattr_value.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(udev_device, sysattr);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_device_get_sysattr_value", udev_device, sysattr);
+ }
+ return (MemorySegment)mh$.invokeExact(udev_device, sysattr);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_monitor_new_from_netlink$MH() {
- return RuntimeHelper.requireNonNull(constants$2.const$0,"udev_monitor_new_from_netlink");
+
+ private static class udev_monitor_new_from_netlink {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_monitor_new_from_netlink");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
}
+
/**
- * {@snippet :
- * struct udev_monitor* udev_monitor_new_from_netlink(struct udev* udev, char* name);
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name)
+ * }
+ */
+ public static FunctionDescriptor udev_monitor_new_from_netlink$descriptor() {
+ return udev_monitor_new_from_netlink.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name)
+ * }
+ */
+ public static MethodHandle udev_monitor_new_from_netlink$handle() {
+ return udev_monitor_new_from_netlink.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name)
+ * }
+ */
+ public static MemorySegment udev_monitor_new_from_netlink$address() {
+ return udev_monitor_new_from_netlink.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name)
* }
*/
public static MemorySegment udev_monitor_new_from_netlink(MemorySegment udev, MemorySegment name) {
- var mh$ = udev_monitor_new_from_netlink$MH();
+ var mh$ = udev_monitor_new_from_netlink.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(udev, name);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_monitor_new_from_netlink", udev, name);
+ }
+ return (MemorySegment)mh$.invokeExact(udev, name);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_monitor_enable_receiving$MH() {
- return RuntimeHelper.requireNonNull(constants$2.const$2,"udev_monitor_enable_receiving");
+
+ private static class udev_monitor_enable_receiving {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_INT,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_monitor_enable_receiving");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor)
+ * }
+ */
+ public static FunctionDescriptor udev_monitor_enable_receiving$descriptor() {
+ return udev_monitor_enable_receiving.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor)
+ * }
+ */
+ public static MethodHandle udev_monitor_enable_receiving$handle() {
+ return udev_monitor_enable_receiving.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor)
+ * }
+ */
+ public static MemorySegment udev_monitor_enable_receiving$address() {
+ return udev_monitor_enable_receiving.ADDR;
}
+
/**
- * {@snippet :
- * int udev_monitor_enable_receiving(struct udev_monitor* udev_monitor);
+ * {@snippet lang=c :
+ * int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor)
* }
*/
public static int udev_monitor_enable_receiving(MemorySegment udev_monitor) {
- var mh$ = udev_monitor_enable_receiving$MH();
+ var mh$ = udev_monitor_enable_receiving.HANDLE;
try {
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_monitor_enable_receiving", udev_monitor);
+ }
return (int)mh$.invokeExact(udev_monitor);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_monitor_get_fd$MH() {
- return RuntimeHelper.requireNonNull(constants$2.const$3,"udev_monitor_get_fd");
+
+ private static class udev_monitor_get_fd {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_INT,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_monitor_get_fd");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * int udev_monitor_get_fd(struct udev_monitor *udev_monitor)
+ * }
+ */
+ public static FunctionDescriptor udev_monitor_get_fd$descriptor() {
+ return udev_monitor_get_fd.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * int udev_monitor_get_fd(struct udev_monitor *udev_monitor)
+ * }
+ */
+ public static MethodHandle udev_monitor_get_fd$handle() {
+ return udev_monitor_get_fd.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * int udev_monitor_get_fd(struct udev_monitor *udev_monitor)
+ * }
+ */
+ public static MemorySegment udev_monitor_get_fd$address() {
+ return udev_monitor_get_fd.ADDR;
}
+
/**
- * {@snippet :
- * int udev_monitor_get_fd(struct udev_monitor* udev_monitor);
+ * {@snippet lang=c :
+ * int udev_monitor_get_fd(struct udev_monitor *udev_monitor)
* }
*/
public static int udev_monitor_get_fd(MemorySegment udev_monitor) {
- var mh$ = udev_monitor_get_fd$MH();
+ var mh$ = udev_monitor_get_fd.HANDLE;
try {
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_monitor_get_fd", udev_monitor);
+ }
return (int)mh$.invokeExact(udev_monitor);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_monitor_receive_device$MH() {
- return RuntimeHelper.requireNonNull(constants$2.const$4,"udev_monitor_receive_device");
+
+ private static class udev_monitor_receive_device {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_monitor_receive_device");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor)
+ * }
+ */
+ public static FunctionDescriptor udev_monitor_receive_device$descriptor() {
+ return udev_monitor_receive_device.DESC;
}
+
/**
- * {@snippet :
- * struct udev_device* udev_monitor_receive_device(struct udev_monitor* udev_monitor);
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor)
+ * }
+ */
+ public static MethodHandle udev_monitor_receive_device$handle() {
+ return udev_monitor_receive_device.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor)
+ * }
+ */
+ public static MemorySegment udev_monitor_receive_device$address() {
+ return udev_monitor_receive_device.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor)
* }
*/
public static MemorySegment udev_monitor_receive_device(MemorySegment udev_monitor) {
- var mh$ = udev_monitor_receive_device$MH();
+ var mh$ = udev_monitor_receive_device.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(udev_monitor);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_monitor_receive_device", udev_monitor);
+ }
+ return (MemorySegment)mh$.invokeExact(udev_monitor);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_monitor_filter_add_match_subsystem_devtype$MH() {
- return RuntimeHelper.requireNonNull(constants$2.const$6,"udev_monitor_filter_add_match_subsystem_devtype");
+
+ private static class udev_monitor_filter_add_match_subsystem_devtype {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_INT,
+ udev.C_POINTER,
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_monitor_filter_add_match_subsystem_devtype");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype)
+ * }
+ */
+ public static FunctionDescriptor udev_monitor_filter_add_match_subsystem_devtype$descriptor() {
+ return udev_monitor_filter_add_match_subsystem_devtype.DESC;
}
+
/**
- * {@snippet :
- * int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor* udev_monitor, char* subsystem, char* devtype);
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype)
+ * }
+ */
+ public static MethodHandle udev_monitor_filter_add_match_subsystem_devtype$handle() {
+ return udev_monitor_filter_add_match_subsystem_devtype.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype)
+ * }
+ */
+ public static MemorySegment udev_monitor_filter_add_match_subsystem_devtype$address() {
+ return udev_monitor_filter_add_match_subsystem_devtype.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype)
* }
*/
public static int udev_monitor_filter_add_match_subsystem_devtype(MemorySegment udev_monitor, MemorySegment subsystem, MemorySegment devtype) {
- var mh$ = udev_monitor_filter_add_match_subsystem_devtype$MH();
+ var mh$ = udev_monitor_filter_add_match_subsystem_devtype.HANDLE;
try {
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_monitor_filter_add_match_subsystem_devtype", udev_monitor, subsystem, devtype);
+ }
return (int)mh$.invokeExact(udev_monitor, subsystem, devtype);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_enumerate_unref$MH() {
- return RuntimeHelper.requireNonNull(constants$3.const$0,"udev_enumerate_unref");
+
+ private static class udev_enumerate_unref {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_enumerate_unref");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
}
+
/**
- * {@snippet :
- * struct udev_enumerate* udev_enumerate_unref(struct udev_enumerate* udev_enumerate);
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate)
+ * }
+ */
+ public static FunctionDescriptor udev_enumerate_unref$descriptor() {
+ return udev_enumerate_unref.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate)
+ * }
+ */
+ public static MethodHandle udev_enumerate_unref$handle() {
+ return udev_enumerate_unref.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate)
+ * }
+ */
+ public static MemorySegment udev_enumerate_unref$address() {
+ return udev_enumerate_unref.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate)
* }
*/
public static MemorySegment udev_enumerate_unref(MemorySegment udev_enumerate) {
- var mh$ = udev_enumerate_unref$MH();
+ var mh$ = udev_enumerate_unref.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(udev_enumerate);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_enumerate_unref", udev_enumerate);
+ }
+ return (MemorySegment)mh$.invokeExact(udev_enumerate);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_enumerate_new$MH() {
- return RuntimeHelper.requireNonNull(constants$3.const$1,"udev_enumerate_new");
+
+ private static class udev_enumerate_new {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_enumerate_new");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * struct udev_enumerate *udev_enumerate_new(struct udev *udev)
+ * }
+ */
+ public static FunctionDescriptor udev_enumerate_new$descriptor() {
+ return udev_enumerate_new.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * struct udev_enumerate *udev_enumerate_new(struct udev *udev)
+ * }
+ */
+ public static MethodHandle udev_enumerate_new$handle() {
+ return udev_enumerate_new.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * struct udev_enumerate *udev_enumerate_new(struct udev *udev)
+ * }
+ */
+ public static MemorySegment udev_enumerate_new$address() {
+ return udev_enumerate_new.ADDR;
}
+
/**
- * {@snippet :
- * struct udev_enumerate* udev_enumerate_new(struct udev* udev);
+ * {@snippet lang=c :
+ * struct udev_enumerate *udev_enumerate_new(struct udev *udev)
* }
*/
public static MemorySegment udev_enumerate_new(MemorySegment udev) {
- var mh$ = udev_enumerate_new$MH();
+ var mh$ = udev_enumerate_new.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(udev);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_enumerate_new", udev);
+ }
+ return (MemorySegment)mh$.invokeExact(udev);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_enumerate_add_match_subsystem$MH() {
- return RuntimeHelper.requireNonNull(constants$3.const$3,"udev_enumerate_add_match_subsystem");
+
+ private static class udev_enumerate_add_match_subsystem {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_INT,
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_enumerate_add_match_subsystem");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
}
+
/**
- * {@snippet :
- * int udev_enumerate_add_match_subsystem(struct udev_enumerate* udev_enumerate, char* subsystem);
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem)
+ * }
+ */
+ public static FunctionDescriptor udev_enumerate_add_match_subsystem$descriptor() {
+ return udev_enumerate_add_match_subsystem.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem)
+ * }
+ */
+ public static MethodHandle udev_enumerate_add_match_subsystem$handle() {
+ return udev_enumerate_add_match_subsystem.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem)
+ * }
+ */
+ public static MemorySegment udev_enumerate_add_match_subsystem$address() {
+ return udev_enumerate_add_match_subsystem.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem)
* }
*/
public static int udev_enumerate_add_match_subsystem(MemorySegment udev_enumerate, MemorySegment subsystem) {
- var mh$ = udev_enumerate_add_match_subsystem$MH();
+ var mh$ = udev_enumerate_add_match_subsystem.HANDLE;
try {
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_enumerate_add_match_subsystem", udev_enumerate, subsystem);
+ }
return (int)mh$.invokeExact(udev_enumerate, subsystem);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_enumerate_scan_devices$MH() {
- return RuntimeHelper.requireNonNull(constants$3.const$4,"udev_enumerate_scan_devices");
+
+ private static class udev_enumerate_scan_devices {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_INT,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_enumerate_scan_devices");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
}
+
/**
- * {@snippet :
- * int udev_enumerate_scan_devices(struct udev_enumerate* udev_enumerate);
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate)
+ * }
+ */
+ public static FunctionDescriptor udev_enumerate_scan_devices$descriptor() {
+ return udev_enumerate_scan_devices.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate)
+ * }
+ */
+ public static MethodHandle udev_enumerate_scan_devices$handle() {
+ return udev_enumerate_scan_devices.HANDLE;
+ }
+
+ /**
+ * Address for:
+ * {@snippet lang=c :
+ * int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate)
+ * }
+ */
+ public static MemorySegment udev_enumerate_scan_devices$address() {
+ return udev_enumerate_scan_devices.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate)
* }
*/
public static int udev_enumerate_scan_devices(MemorySegment udev_enumerate) {
- var mh$ = udev_enumerate_scan_devices$MH();
+ var mh$ = udev_enumerate_scan_devices.HANDLE;
try {
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_enumerate_scan_devices", udev_enumerate);
+ }
return (int)mh$.invokeExact(udev_enumerate);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
- public static MethodHandle udev_enumerate_get_list_entry$MH() {
- return RuntimeHelper.requireNonNull(constants$3.const$5,"udev_enumerate_get_list_entry");
+
+ private static class udev_enumerate_get_list_entry {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ udev.C_POINTER,
+ udev.C_POINTER
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("udev_enumerate_get_list_entry");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate)
+ * }
+ */
+ public static FunctionDescriptor udev_enumerate_get_list_entry$descriptor() {
+ return udev_enumerate_get_list_entry.DESC;
+ }
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate)
+ * }
+ */
+ public static MethodHandle udev_enumerate_get_list_entry$handle() {
+ return udev_enumerate_get_list_entry.HANDLE;
}
+
/**
- * {@snippet :
- * struct udev_list_entry* udev_enumerate_get_list_entry(struct udev_enumerate* udev_enumerate);
+ * Address for:
+ * {@snippet lang=c :
+ * struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate)
+ * }
+ */
+ public static MemorySegment udev_enumerate_get_list_entry$address() {
+ return udev_enumerate_get_list_entry.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate)
* }
*/
public static MemorySegment udev_enumerate_get_list_entry(MemorySegment udev_enumerate) {
- var mh$ = udev_enumerate_get_list_entry$MH();
+ var mh$ = udev_enumerate_get_list_entry.HANDLE;
try {
- return (java.lang.foreign.MemorySegment)mh$.invokeExact(udev_enumerate);
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("udev_enumerate_get_list_entry", udev_enumerate);
+ }
+ return (MemorySegment)mh$.invokeExact(udev_enumerate);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
}
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/RuntimeHelper.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/RuntimeHelper.java
deleted file mode 100644
index f3cff469..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/RuntimeHelper.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package net.codecrete.usb.linux.gen.unistd;
-// Generated by jextract
-
-import java.lang.foreign.*;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-
-import static java.lang.foreign.ValueLayout.*;
-
-final class RuntimeHelper {
-
- private static final Linker LINKER = Linker.nativeLinker();
- private static final ClassLoader LOADER = RuntimeHelper.class.getClassLoader();
- private static final MethodHandles.Lookup MH_LOOKUP = MethodHandles.lookup();
- private static final SymbolLookup SYMBOL_LOOKUP;
- private static final SegmentAllocator THROWING_ALLOCATOR = (x, y) -> { throw new AssertionError("should not reach here"); };
- static final AddressLayout POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE));
-
- final static SegmentAllocator CONSTANT_ALLOCATOR =
- (size, align) -> Arena.ofAuto().allocate(size, align);
-
- static {
-
- SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
- SYMBOL_LOOKUP = name -> loaderLookup.find(name).or(() -> LINKER.defaultLookup().find(name));
- }
-
- // Suppresses default constructor, ensuring non-instantiability.
- private RuntimeHelper() {}
-
- static T requireNonNull(T obj, String symbolName) {
- if (obj == null) {
- throw new UnsatisfiedLinkError("unresolved symbol: " + symbolName);
- }
- return obj;
- }
-
- static MemorySegment lookupGlobalVariable(String name, MemoryLayout layout) {
- return SYMBOL_LOOKUP.find(name)
- .map(s -> s.reinterpret(layout.byteSize()))
- .orElse(null);
- }
-
- static MethodHandle downcallHandle(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> LINKER.downcallHandle(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle downcallHandle(FunctionDescriptor fdesc) {
- return LINKER.downcallHandle(fdesc);
- }
-
- static MethodHandle downcallHandleVariadic(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> VarargsInvoker.make(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
- try {
- return MH_LOOKUP.findVirtual(fi, name, fdesc.toMethodType());
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment upcallStub(MethodHandle fiHandle, Z z, FunctionDescriptor fdesc, Arena scope) {
- try {
- fiHandle = fiHandle.bindTo(z);
- return LINKER.upcallStub(fiHandle, fdesc, scope);
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment asArray(MemorySegment addr, MemoryLayout layout, int numElements, Arena arena) {
- return addr.reinterpret(numElements * layout.byteSize(), arena, null);
- }
-
- // Internals only below this point
-
- private static final class VarargsInvoker {
- private static final MethodHandle INVOKE_MH;
- private final MemorySegment symbol;
- private final FunctionDescriptor function;
-
- private VarargsInvoker(MemorySegment symbol, FunctionDescriptor function) {
- this.symbol = symbol;
- this.function = function;
- }
-
- static {
- try {
- INVOKE_MH = MethodHandles.lookup().findVirtual(VarargsInvoker.class, "invoke", MethodType.methodType(Object.class, SegmentAllocator.class, Object[].class));
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException(e);
- }
- }
-
- static MethodHandle make(MemorySegment symbol, FunctionDescriptor function) {
- VarargsInvoker invoker = new VarargsInvoker(symbol, function);
- MethodHandle handle = INVOKE_MH.bindTo(invoker).asCollector(Object[].class, function.argumentLayouts().size() + 1);
- MethodType mtype = MethodType.methodType(function.returnLayout().isPresent() ? carrier(function.returnLayout().get(), true) : void.class);
- for (MemoryLayout layout : function.argumentLayouts()) {
- mtype = mtype.appendParameterTypes(carrier(layout, false));
- }
- mtype = mtype.appendParameterTypes(Object[].class);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mtype = mtype.insertParameterTypes(0, SegmentAllocator.class);
- } else {
- handle = MethodHandles.insertArguments(handle, 0, THROWING_ALLOCATOR);
- }
- return handle.asType(mtype);
- }
-
- static Class> carrier(MemoryLayout layout, boolean ret) {
- if (layout instanceof ValueLayout valueLayout) {
- return valueLayout.carrier();
- } else if (layout instanceof GroupLayout) {
- return MemorySegment.class;
- } else {
- throw new AssertionError("Cannot get here!");
- }
- }
-
- private Object invoke(SegmentAllocator allocator, Object[] args) throws Throwable {
- // one trailing Object[]
- int nNamedArgs = function.argumentLayouts().size();
- assert(args.length == nNamedArgs + 1);
- // The last argument is the array of vararg collector
- Object[] unnamedArgs = (Object[]) args[args.length - 1];
-
- int argsCount = nNamedArgs + unnamedArgs.length;
- Class>[] argTypes = new Class>[argsCount];
- MemoryLayout[] argLayouts = new MemoryLayout[nNamedArgs + unnamedArgs.length];
-
- int pos = 0;
- for (pos = 0; pos < nNamedArgs; pos++) {
- argLayouts[pos] = function.argumentLayouts().get(pos);
- }
-
- assert pos == nNamedArgs;
- for (Object o: unnamedArgs) {
- argLayouts[pos] = variadicLayout(normalize(o.getClass()));
- pos++;
- }
- assert pos == argsCount;
-
- FunctionDescriptor f = (function.returnLayout().isEmpty()) ?
- FunctionDescriptor.ofVoid(argLayouts) :
- FunctionDescriptor.of(function.returnLayout().get(), argLayouts);
- MethodHandle mh = LINKER.downcallHandle(symbol, f);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mh = mh.bindTo(allocator);
- }
- // flatten argument list so that it can be passed to an asSpreader MH
- Object[] allArgs = new Object[nNamedArgs + unnamedArgs.length];
- System.arraycopy(args, 0, allArgs, 0, nNamedArgs);
- System.arraycopy(unnamedArgs, 0, allArgs, nNamedArgs, unnamedArgs.length);
-
- return mh.asSpreader(Object[].class, argsCount).invoke(allArgs);
- }
-
- private static Class> unboxIfNeeded(Class> clazz) {
- if (clazz == Boolean.class) {
- return boolean.class;
- } else if (clazz == Void.class) {
- return void.class;
- } else if (clazz == Byte.class) {
- return byte.class;
- } else if (clazz == Character.class) {
- return char.class;
- } else if (clazz == Short.class) {
- return short.class;
- } else if (clazz == Integer.class) {
- return int.class;
- } else if (clazz == Long.class) {
- return long.class;
- } else if (clazz == Float.class) {
- return float.class;
- } else if (clazz == Double.class) {
- return double.class;
- } else {
- return clazz;
- }
- }
-
- private Class> promote(Class> c) {
- if (c == byte.class || c == char.class || c == short.class || c == int.class) {
- return long.class;
- } else if (c == float.class) {
- return double.class;
- } else {
- return c;
- }
- }
-
- private Class> normalize(Class> c) {
- c = unboxIfNeeded(c);
- if (c.isPrimitive()) {
- return promote(c);
- }
- if (c == MemorySegment.class) {
- return MemorySegment.class;
- }
- throw new IllegalArgumentException("Invalid type for ABI: " + c.getTypeName());
- }
-
- private MemoryLayout variadicLayout(Class> c) {
- if (c == long.class) {
- return JAVA_LONG;
- } else if (c == double.class) {
- return JAVA_DOUBLE;
- } else if (c == MemorySegment.class) {
- return ADDRESS;
- } else {
- throw new IllegalArgumentException("Unhandled variadic argument class: " + c);
- }
- }
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/constants$0.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/constants$0.java
deleted file mode 100644
index 90d07fa3..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/constants$0.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.unistd;
-
-import java.lang.foreign.FunctionDescriptor;
-import java.lang.invoke.MethodHandle;
-
-import static java.lang.foreign.ValueLayout.JAVA_INT;
-final class constants$0 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$0() {}
- static final FunctionDescriptor const$0 = FunctionDescriptor.of(JAVA_INT,
- JAVA_INT
- );
- static final MethodHandle const$1 = RuntimeHelper.downcallHandle(
- "close",
- constants$0.const$0
- );
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/unistd$shared.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/unistd$shared.java
new file mode 100644
index 00000000..11064c54
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/unistd$shared.java
@@ -0,0 +1,63 @@
+// Generated by jextract
+
+package net.codecrete.usb.linux.gen.unistd;
+
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class unistd$shared {
+
+ unistd$shared() {
+ // Should not be called directly
+ }
+
+ public static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) Linker.nativeLinker().canonicalLayouts().get("bool");
+ public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char");
+ public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) Linker.nativeLinker().canonicalLayouts().get("short");
+ public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int");
+ public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long long");
+ public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) Linker.nativeLinker().canonicalLayouts().get("float");
+ public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double");
+ public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*"))
+ .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR));
+ public static final ValueLayout.OfLong C_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long");
+
+ static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls");
+
+ static void traceDowncall(String name, Object... args) {
+ String traceArgs = Arrays.stream(args)
+ .map(Object::toString)
+ .collect(Collectors.joining(", "));
+ System.out.printf("%s(%s)\n", name, traceArgs);
+ }
+
+ static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
+ try {
+ return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
+ } catch (ReflectiveOperationException ex) {
+ throw new AssertionError(ex);
+ }
+ }
+
+ static MemoryLayout align(MemoryLayout layout, long align) {
+ return switch (layout) {
+ case PaddingLayout p -> p;
+ case ValueLayout v -> v.withByteAlignment(align);
+ case GroupLayout g -> {
+ MemoryLayout[] alignedMembers = g.memberLayouts().stream()
+ .map(m -> align(m, align)).toArray(MemoryLayout[]::new);
+ yield g instanceof StructLayout ?
+ MemoryLayout.structLayout(alignedMembers) : MemoryLayout.unionLayout(alignedMembers);
+ }
+ case SequenceLayout s -> MemoryLayout.sequenceLayout(s.elementCount(), align(s.elementLayout(), align));
+ };
+ }
+}
+
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/unistd.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/unistd.java
index 581e508e..e0f89fcf 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/unistd.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/unistd/unistd.java
@@ -2,36 +2,86 @@
package net.codecrete.usb.linux.gen.unistd;
-import java.lang.foreign.AddressLayout;
-import java.lang.invoke.MethodHandle;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
import static java.lang.foreign.ValueLayout.*;
-public class unistd {
-
- public static final OfByte C_CHAR = JAVA_BYTE;
- public static final OfShort C_SHORT = JAVA_SHORT;
- public static final OfInt C_INT = JAVA_INT;
- public static final OfLong C_LONG = JAVA_LONG;
- public static final OfLong C_LONG_LONG = JAVA_LONG;
- public static final OfFloat C_FLOAT = JAVA_FLOAT;
- public static final OfDouble C_DOUBLE = JAVA_DOUBLE;
- public static final AddressLayout C_POINTER = RuntimeHelper.POINTER;
- public static MethodHandle close$MH() {
- return RuntimeHelper.requireNonNull(constants$0.const$1,"close");
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class unistd extends unistd$shared {
+
+ unistd() {
+ // Should not be called directly
+ }
+
+ static final Arena LIBRARY_ARENA = Arena.ofAuto();
+
+ static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup()
+ .or(Linker.nativeLinker().defaultLookup());
+
+
+ private static class close {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ unistd.C_INT,
+ unistd.C_INT
+ );
+
+ public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("close");
+
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ /**
+ * Function descriptor for:
+ * {@snippet lang=c :
+ * extern int close(int __fd)
+ * }
+ */
+ public static FunctionDescriptor close$descriptor() {
+ return close.DESC;
}
+
+ /**
+ * Downcall method handle for:
+ * {@snippet lang=c :
+ * extern int close(int __fd)
+ * }
+ */
+ public static MethodHandle close$handle() {
+ return close.HANDLE;
+ }
+
/**
- * {@snippet :
- * int close(int __fd);
+ * Address for:
+ * {@snippet lang=c :
+ * extern int close(int __fd)
+ * }
+ */
+ public static MemorySegment close$address() {
+ return close.ADDR;
+ }
+
+ /**
+ * {@snippet lang=c :
+ * extern int close(int __fd)
* }
*/
public static int close(int __fd) {
- var mh$ = close$MH();
+ var mh$ = close.HANDLE;
try {
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("close", __fd);
+ }
return (int)mh$.invokeExact(__fd);
+ } catch (Error | RuntimeException ex) {
+ throw ex;
} catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ throw new AssertionError("should not reach here", ex$);
}
}
}
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/RuntimeHelper.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/RuntimeHelper.java
deleted file mode 100644
index 5e6d3cd8..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/RuntimeHelper.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package net.codecrete.usb.linux.gen.usbdevice_fs;
-// Generated by jextract
-
-import java.lang.foreign.*;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-
-import static java.lang.foreign.ValueLayout.*;
-
-final class RuntimeHelper {
-
- private static final Linker LINKER = Linker.nativeLinker();
- private static final ClassLoader LOADER = RuntimeHelper.class.getClassLoader();
- private static final MethodHandles.Lookup MH_LOOKUP = MethodHandles.lookup();
- private static final SymbolLookup SYMBOL_LOOKUP;
- private static final SegmentAllocator THROWING_ALLOCATOR = (x, y) -> { throw new AssertionError("should not reach here"); };
- static final AddressLayout POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE));
-
- final static SegmentAllocator CONSTANT_ALLOCATOR =
- (size, align) -> Arena.ofAuto().allocate(size, align);
-
- static {
-
- SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
- SYMBOL_LOOKUP = name -> loaderLookup.find(name).or(() -> LINKER.defaultLookup().find(name));
- }
-
- // Suppresses default constructor, ensuring non-instantiability.
- private RuntimeHelper() {}
-
- static T requireNonNull(T obj, String symbolName) {
- if (obj == null) {
- throw new UnsatisfiedLinkError("unresolved symbol: " + symbolName);
- }
- return obj;
- }
-
- static MemorySegment lookupGlobalVariable(String name, MemoryLayout layout) {
- return SYMBOL_LOOKUP.find(name)
- .map(s -> s.reinterpret(layout.byteSize()))
- .orElse(null);
- }
-
- static MethodHandle downcallHandle(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> LINKER.downcallHandle(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle downcallHandle(FunctionDescriptor fdesc) {
- return LINKER.downcallHandle(fdesc);
- }
-
- static MethodHandle downcallHandleVariadic(String name, FunctionDescriptor fdesc) {
- return SYMBOL_LOOKUP.find(name).
- map(addr -> VarargsInvoker.make(addr, fdesc)).
- orElse(null);
- }
-
- static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
- try {
- return MH_LOOKUP.findVirtual(fi, name, fdesc.toMethodType());
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment upcallStub(MethodHandle fiHandle, Z z, FunctionDescriptor fdesc, Arena scope) {
- try {
- fiHandle = fiHandle.bindTo(z);
- return LINKER.upcallStub(fiHandle, fdesc, scope);
- } catch (Throwable ex) {
- throw new AssertionError(ex);
- }
- }
-
- static MemorySegment asArray(MemorySegment addr, MemoryLayout layout, int numElements, Arena arena) {
- return addr.reinterpret(numElements * layout.byteSize(), arena, null);
- }
-
- // Internals only below this point
-
- private static final class VarargsInvoker {
- private static final MethodHandle INVOKE_MH;
- private final MemorySegment symbol;
- private final FunctionDescriptor function;
-
- private VarargsInvoker(MemorySegment symbol, FunctionDescriptor function) {
- this.symbol = symbol;
- this.function = function;
- }
-
- static {
- try {
- INVOKE_MH = MethodHandles.lookup().findVirtual(VarargsInvoker.class, "invoke", MethodType.methodType(Object.class, SegmentAllocator.class, Object[].class));
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException(e);
- }
- }
-
- static MethodHandle make(MemorySegment symbol, FunctionDescriptor function) {
- VarargsInvoker invoker = new VarargsInvoker(symbol, function);
- MethodHandle handle = INVOKE_MH.bindTo(invoker).asCollector(Object[].class, function.argumentLayouts().size() + 1);
- MethodType mtype = MethodType.methodType(function.returnLayout().isPresent() ? carrier(function.returnLayout().get(), true) : void.class);
- for (MemoryLayout layout : function.argumentLayouts()) {
- mtype = mtype.appendParameterTypes(carrier(layout, false));
- }
- mtype = mtype.appendParameterTypes(Object[].class);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mtype = mtype.insertParameterTypes(0, SegmentAllocator.class);
- } else {
- handle = MethodHandles.insertArguments(handle, 0, THROWING_ALLOCATOR);
- }
- return handle.asType(mtype);
- }
-
- static Class> carrier(MemoryLayout layout, boolean ret) {
- if (layout instanceof ValueLayout valueLayout) {
- return valueLayout.carrier();
- } else if (layout instanceof GroupLayout) {
- return MemorySegment.class;
- } else {
- throw new AssertionError("Cannot get here!");
- }
- }
-
- private Object invoke(SegmentAllocator allocator, Object[] args) throws Throwable {
- // one trailing Object[]
- int nNamedArgs = function.argumentLayouts().size();
- assert(args.length == nNamedArgs + 1);
- // The last argument is the array of vararg collector
- Object[] unnamedArgs = (Object[]) args[args.length - 1];
-
- int argsCount = nNamedArgs + unnamedArgs.length;
- Class>[] argTypes = new Class>[argsCount];
- MemoryLayout[] argLayouts = new MemoryLayout[nNamedArgs + unnamedArgs.length];
-
- int pos = 0;
- for (pos = 0; pos < nNamedArgs; pos++) {
- argLayouts[pos] = function.argumentLayouts().get(pos);
- }
-
- assert pos == nNamedArgs;
- for (Object o: unnamedArgs) {
- argLayouts[pos] = variadicLayout(normalize(o.getClass()));
- pos++;
- }
- assert pos == argsCount;
-
- FunctionDescriptor f = (function.returnLayout().isEmpty()) ?
- FunctionDescriptor.ofVoid(argLayouts) :
- FunctionDescriptor.of(function.returnLayout().get(), argLayouts);
- MethodHandle mh = LINKER.downcallHandle(symbol, f);
- boolean needsAllocator = function.returnLayout().isPresent() &&
- function.returnLayout().get() instanceof GroupLayout;
- if (needsAllocator) {
- mh = mh.bindTo(allocator);
- }
- // flatten argument list so that it can be passed to an asSpreader MH
- Object[] allArgs = new Object[nNamedArgs + unnamedArgs.length];
- System.arraycopy(args, 0, allArgs, 0, nNamedArgs);
- System.arraycopy(unnamedArgs, 0, allArgs, nNamedArgs, unnamedArgs.length);
-
- return mh.asSpreader(Object[].class, argsCount).invoke(allArgs);
- }
-
- private static Class> unboxIfNeeded(Class> clazz) {
- if (clazz == Boolean.class) {
- return boolean.class;
- } else if (clazz == Void.class) {
- return void.class;
- } else if (clazz == Byte.class) {
- return byte.class;
- } else if (clazz == Character.class) {
- return char.class;
- } else if (clazz == Short.class) {
- return short.class;
- } else if (clazz == Integer.class) {
- return int.class;
- } else if (clazz == Long.class) {
- return long.class;
- } else if (clazz == Float.class) {
- return float.class;
- } else if (clazz == Double.class) {
- return double.class;
- } else {
- return clazz;
- }
- }
-
- private Class> promote(Class> c) {
- if (c == byte.class || c == char.class || c == short.class || c == int.class) {
- return long.class;
- } else if (c == float.class) {
- return double.class;
- } else {
- return c;
- }
- }
-
- private Class> normalize(Class> c) {
- c = unboxIfNeeded(c);
- if (c.isPrimitive()) {
- return promote(c);
- }
- if (c == MemorySegment.class) {
- return MemorySegment.class;
- }
- throw new IllegalArgumentException("Invalid type for ABI: " + c.getTypeName());
- }
-
- private MemoryLayout variadicLayout(Class> c) {
- if (c == long.class) {
- return JAVA_LONG;
- } else if (c == double.class) {
- return JAVA_DOUBLE;
- } else if (c == MemorySegment.class) {
- return ADDRESS;
- } else {
- throw new IllegalArgumentException("Unhandled variadic argument class: " + c);
- }
- }
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$0.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$0.java
deleted file mode 100644
index 24688f13..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$0.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.usbdevice_fs;
-
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.StructLayout;
-import java.lang.invoke.VarHandle;
-
-import static java.lang.foreign.ValueLayout.*;
-final class constants$0 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$0() {}
- static final StructLayout const$0 = MemoryLayout.structLayout(
- JAVA_BYTE.withName("bRequestType"),
- JAVA_BYTE.withName("bRequest"),
- JAVA_SHORT.withName("wValue"),
- JAVA_SHORT.withName("wIndex"),
- JAVA_SHORT.withName("wLength"),
- JAVA_INT.withName("timeout"),
- MemoryLayout.paddingLayout(4),
- RuntimeHelper.POINTER.withName("data")
- ).withName("usbdevfs_ctrltransfer");
- static final VarHandle const$1 = constants$0.const$0.varHandle(MemoryLayout.PathElement.groupElement("bRequestType"));
- static final VarHandle const$2 = constants$0.const$0.varHandle(MemoryLayout.PathElement.groupElement("bRequest"));
- static final VarHandle const$3 = constants$0.const$0.varHandle(MemoryLayout.PathElement.groupElement("wValue"));
- static final VarHandle const$4 = constants$0.const$0.varHandle(MemoryLayout.PathElement.groupElement("wIndex"));
- static final VarHandle const$5 = constants$0.const$0.varHandle(MemoryLayout.PathElement.groupElement("wLength"));
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$1.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$1.java
deleted file mode 100644
index 9666d212..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$1.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.usbdevice_fs;
-
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.StructLayout;
-import java.lang.invoke.VarHandle;
-
-import static java.lang.foreign.ValueLayout.JAVA_INT;
-final class constants$1 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$1() {}
- static final VarHandle const$0 = constants$0.const$0.varHandle(MemoryLayout.PathElement.groupElement("timeout"));
- static final VarHandle const$1 = constants$0.const$0.varHandle(MemoryLayout.PathElement.groupElement("data"));
- static final StructLayout const$2 = MemoryLayout.structLayout(
- JAVA_INT.withName("ep"),
- JAVA_INT.withName("len"),
- JAVA_INT.withName("timeout"),
- MemoryLayout.paddingLayout(4),
- RuntimeHelper.POINTER.withName("data")
- ).withName("usbdevfs_bulktransfer");
- static final VarHandle const$3 = constants$1.const$2.varHandle(MemoryLayout.PathElement.groupElement("ep"));
- static final VarHandle const$4 = constants$1.const$2.varHandle(MemoryLayout.PathElement.groupElement("len"));
- static final VarHandle const$5 = constants$1.const$2.varHandle(MemoryLayout.PathElement.groupElement("timeout"));
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$2.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$2.java
deleted file mode 100644
index 384a0622..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$2.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.usbdevice_fs;
-
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.StructLayout;
-import java.lang.invoke.VarHandle;
-
-import static java.lang.foreign.ValueLayout.JAVA_BYTE;
-import static java.lang.foreign.ValueLayout.JAVA_INT;
-final class constants$2 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$2() {}
- static final VarHandle const$0 = constants$1.const$2.varHandle(MemoryLayout.PathElement.groupElement("data"));
- static final StructLayout const$1 = MemoryLayout.structLayout(
- JAVA_INT.withName("interface"),
- JAVA_INT.withName("altsetting")
- ).withName("usbdevfs_setinterface");
- static final VarHandle const$2 = constants$2.const$1.varHandle(MemoryLayout.PathElement.groupElement("interface"));
- static final VarHandle const$3 = constants$2.const$1.varHandle(MemoryLayout.PathElement.groupElement("altsetting"));
- static final StructLayout const$4 = MemoryLayout.structLayout(
- JAVA_BYTE.withName("type"),
- JAVA_BYTE.withName("endpoint"),
- MemoryLayout.paddingLayout(2),
- JAVA_INT.withName("status"),
- JAVA_INT.withName("flags"),
- MemoryLayout.paddingLayout(4),
- RuntimeHelper.POINTER.withName("buffer"),
- JAVA_INT.withName("buffer_length"),
- JAVA_INT.withName("actual_length"),
- JAVA_INT.withName("start_frame"),
- MemoryLayout.unionLayout(
- JAVA_INT.withName("number_of_packets"),
- JAVA_INT.withName("stream_id")
- ).withName("$anon$0"),
- JAVA_INT.withName("error_count"),
- JAVA_INT.withName("signr"),
- RuntimeHelper.POINTER.withName("usercontext"),
- MemoryLayout.sequenceLayout(0, MemoryLayout.structLayout(
- JAVA_INT.withName("length"),
- JAVA_INT.withName("actual_length"),
- JAVA_INT.withName("status")
- ).withName("usbdevfs_iso_packet_desc")).withName("iso_frame_desc")
- ).withName("usbdevfs_urb");
- static final VarHandle const$5 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("type"));
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$3.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$3.java
deleted file mode 100644
index 2f58300d..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$3.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.usbdevice_fs;
-
-import java.lang.foreign.MemoryLayout;
-import java.lang.invoke.VarHandle;
-final class constants$3 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$3() {}
- static final VarHandle const$0 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("endpoint"));
- static final VarHandle const$1 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("status"));
- static final VarHandle const$2 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("flags"));
- static final VarHandle const$3 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("buffer"));
- static final VarHandle const$4 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("buffer_length"));
- static final VarHandle const$5 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("actual_length"));
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$4.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$4.java
deleted file mode 100644
index dd410c61..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$4.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.usbdevice_fs;
-
-import java.lang.foreign.MemoryLayout;
-import java.lang.invoke.VarHandle;
-final class constants$4 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$4() {}
- static final VarHandle const$0 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("start_frame"));
- static final VarHandle const$1 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("$anon$0"), MemoryLayout.PathElement.groupElement("number_of_packets"));
- static final VarHandle const$2 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("$anon$0"), MemoryLayout.PathElement.groupElement("stream_id"));
- static final VarHandle const$3 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("error_count"));
- static final VarHandle const$4 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("signr"));
- static final VarHandle const$5 = constants$2.const$4.varHandle(MemoryLayout.PathElement.groupElement("usercontext"));
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$5.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$5.java
deleted file mode 100644
index fedeaec6..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$5.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.usbdevice_fs;
-
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.StructLayout;
-import java.lang.invoke.VarHandle;
-
-import static java.lang.foreign.ValueLayout.JAVA_BYTE;
-import static java.lang.foreign.ValueLayout.JAVA_INT;
-final class constants$5 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$5() {}
- static final StructLayout const$0 = MemoryLayout.structLayout(
- JAVA_INT.withName("ifno"),
- JAVA_INT.withName("ioctl_code"),
- RuntimeHelper.POINTER.withName("data")
- ).withName("usbdevfs_ioctl");
- static final VarHandle const$1 = constants$5.const$0.varHandle(MemoryLayout.PathElement.groupElement("ifno"));
- static final VarHandle const$2 = constants$5.const$0.varHandle(MemoryLayout.PathElement.groupElement("ioctl_code"));
- static final VarHandle const$3 = constants$5.const$0.varHandle(MemoryLayout.PathElement.groupElement("data"));
- static final StructLayout const$4 = MemoryLayout.structLayout(
- JAVA_INT.withName("interface"),
- JAVA_INT.withName("flags"),
- MemoryLayout.sequenceLayout(256, JAVA_BYTE).withName("driver")
- ).withName("usbdevfs_disconnect_claim");
- static final VarHandle const$5 = constants$5.const$4.varHandle(MemoryLayout.PathElement.groupElement("interface"));
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$6.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$6.java
deleted file mode 100644
index 787c95d8..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/constants$6.java
+++ /dev/null
@@ -1,14 +0,0 @@
-// Generated by jextract
-
-package net.codecrete.usb.linux.gen.usbdevice_fs;
-
-import java.lang.foreign.MemoryLayout;
-import java.lang.invoke.VarHandle;
-final class constants$6 {
-
- // Suppresses default constructor, ensuring non-instantiability.
- private constants$6() {}
- static final VarHandle const$0 = constants$5.const$4.varHandle(MemoryLayout.PathElement.groupElement("flags"));
-}
-
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_bulktransfer.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_bulktransfer.java
index 07aa5f5e..5e4eba47 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_bulktransfer.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_bulktransfer.java
@@ -2,140 +2,265 @@
package net.codecrete.usb.linux.gen.usbdevice_fs;
-import java.lang.foreign.Arena;
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentAllocator;
-import java.lang.invoke.VarHandle;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
/**
- * {@snippet :
+ * {@snippet lang=c :
* struct usbdevfs_bulktransfer {
* unsigned int ep;
* unsigned int len;
* unsigned int timeout;
- * void* data;
- * };
+ * void *data;
+ * }
* }
*/
public class usbdevfs_bulktransfer {
- public static MemoryLayout $LAYOUT() {
- return constants$1.const$2;
+ usbdevfs_bulktransfer() {
+ // Should not be called directly
}
- public static VarHandle ep$VH() {
- return constants$1.const$3;
+
+ private static final GroupLayout $LAYOUT = MemoryLayout.structLayout(
+ usbdevice_fs.C_INT.withName("ep"),
+ usbdevice_fs.C_INT.withName("len"),
+ usbdevice_fs.C_INT.withName("timeout"),
+ MemoryLayout.paddingLayout(4),
+ usbdevice_fs.C_POINTER.withName("data")
+ ).withName("usbdevfs_bulktransfer");
+
+ /**
+ * The layout of this struct
+ */
+ public static final GroupLayout layout() {
+ return $LAYOUT;
}
+
+ private static final OfInt ep$LAYOUT = (OfInt)$LAYOUT.select(groupElement("ep"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int ep
+ * }
+ */
+ public static final OfInt ep$layout() {
+ return ep$LAYOUT;
+ }
+
+ private static final long ep$OFFSET = $LAYOUT.byteOffset(groupElement("ep"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int ep
+ * }
+ */
+ public static final long ep$offset() {
+ return ep$OFFSET;
+ }
+
/**
* Getter for field:
- * {@snippet :
- * unsigned int ep;
+ * {@snippet lang=c :
+ * unsigned int ep
* }
*/
- public static int ep$get(MemorySegment seg) {
- return (int)constants$1.const$3.get(seg);
+ public static int ep(MemorySegment struct) {
+ return struct.get(ep$LAYOUT, ep$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * unsigned int ep;
+ * {@snippet lang=c :
+ * unsigned int ep
* }
*/
- public static void ep$set(MemorySegment seg, int x) {
- constants$1.const$3.set(seg, x);
+ public static void ep(MemorySegment struct, int fieldValue) {
+ struct.set(ep$LAYOUT, ep$OFFSET, fieldValue);
}
- public static int ep$get(MemorySegment seg, long index) {
- return (int)constants$1.const$3.get(seg.asSlice(index*sizeof()));
- }
- public static void ep$set(MemorySegment seg, long index, int x) {
- constants$1.const$3.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt len$LAYOUT = (OfInt)$LAYOUT.select(groupElement("len"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int len
+ * }
+ */
+ public static final OfInt len$layout() {
+ return len$LAYOUT;
}
- public static VarHandle len$VH() {
- return constants$1.const$4;
+
+ private static final long len$OFFSET = $LAYOUT.byteOffset(groupElement("len"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int len
+ * }
+ */
+ public static final long len$offset() {
+ return len$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * unsigned int len;
+ * {@snippet lang=c :
+ * unsigned int len
* }
*/
- public static int len$get(MemorySegment seg) {
- return (int)constants$1.const$4.get(seg);
+ public static int len(MemorySegment struct) {
+ return struct.get(len$LAYOUT, len$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * unsigned int len;
+ * {@snippet lang=c :
+ * unsigned int len
* }
*/
- public static void len$set(MemorySegment seg, int x) {
- constants$1.const$4.set(seg, x);
+ public static void len(MemorySegment struct, int fieldValue) {
+ struct.set(len$LAYOUT, len$OFFSET, fieldValue);
}
- public static int len$get(MemorySegment seg, long index) {
- return (int)constants$1.const$4.get(seg.asSlice(index*sizeof()));
- }
- public static void len$set(MemorySegment seg, long index, int x) {
- constants$1.const$4.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt timeout$LAYOUT = (OfInt)$LAYOUT.select(groupElement("timeout"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int timeout
+ * }
+ */
+ public static final OfInt timeout$layout() {
+ return timeout$LAYOUT;
}
- public static VarHandle timeout$VH() {
- return constants$1.const$5;
+
+ private static final long timeout$OFFSET = $LAYOUT.byteOffset(groupElement("timeout"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int timeout
+ * }
+ */
+ public static final long timeout$offset() {
+ return timeout$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * unsigned int timeout;
+ * {@snippet lang=c :
+ * unsigned int timeout
* }
*/
- public static int timeout$get(MemorySegment seg) {
- return (int)constants$1.const$5.get(seg);
+ public static int timeout(MemorySegment struct) {
+ return struct.get(timeout$LAYOUT, timeout$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * unsigned int timeout;
+ * {@snippet lang=c :
+ * unsigned int timeout
* }
*/
- public static void timeout$set(MemorySegment seg, int x) {
- constants$1.const$5.set(seg, x);
- }
- public static int timeout$get(MemorySegment seg, long index) {
- return (int)constants$1.const$5.get(seg.asSlice(index*sizeof()));
+ public static void timeout(MemorySegment struct, int fieldValue) {
+ struct.set(timeout$LAYOUT, timeout$OFFSET, fieldValue);
}
- public static void timeout$set(MemorySegment seg, long index, int x) {
- constants$1.const$5.set(seg.asSlice(index*sizeof()), x);
+
+ private static final AddressLayout data$LAYOUT = (AddressLayout)$LAYOUT.select(groupElement("data"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * void *data
+ * }
+ */
+ public static final AddressLayout data$layout() {
+ return data$LAYOUT;
}
- public static VarHandle data$VH() {
- return constants$2.const$0;
+
+ private static final long data$OFFSET = $LAYOUT.byteOffset(groupElement("data"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * void *data
+ * }
+ */
+ public static final long data$offset() {
+ return data$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * void* data;
+ * {@snippet lang=c :
+ * void *data
* }
*/
- public static MemorySegment data$get(MemorySegment seg) {
- return (java.lang.foreign.MemorySegment)constants$2.const$0.get(seg);
+ public static MemorySegment data(MemorySegment struct) {
+ return struct.get(data$LAYOUT, data$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * void* data;
+ * {@snippet lang=c :
+ * void *data
* }
*/
- public static void data$set(MemorySegment seg, MemorySegment x) {
- constants$2.const$0.set(seg, x);
+ public static void data(MemorySegment struct, MemorySegment fieldValue) {
+ struct.set(data$LAYOUT, data$OFFSET, fieldValue);
+ }
+
+ /**
+ * Obtains a slice of {@code arrayParam} which selects the array element at {@code index}.
+ * The returned segment has address {@code arrayParam.address() + index * layout().byteSize()}
+ */
+ public static MemorySegment asSlice(MemorySegment array, long index) {
+ return array.asSlice(layout().byteSize() * index);
+ }
+
+ /**
+ * The size (in bytes) of this struct
+ */
+ public static long sizeof() { return layout().byteSize(); }
+
+ /**
+ * Allocate a segment of size {@code layout().byteSize()} using {@code allocator}
+ */
+ public static MemorySegment allocate(SegmentAllocator allocator) {
+ return allocator.allocate(layout());
}
- public static MemorySegment data$get(MemorySegment seg, long index) {
- return (java.lang.foreign.MemorySegment)constants$2.const$0.get(seg.asSlice(index*sizeof()));
+
+ /**
+ * Allocate an array of size {@code elementCount} using {@code allocator}.
+ * The returned segment has size {@code elementCount * layout().byteSize()}.
+ */
+ public static MemorySegment allocateArray(long elementCount, SegmentAllocator allocator) {
+ return allocator.allocate(MemoryLayout.sequenceLayout(elementCount, layout()));
}
- public static void data$set(MemorySegment seg, long index, MemorySegment x) {
- constants$2.const$0.set(seg.asSlice(index*sizeof()), x);
+
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, Arena arena, Consumer cleanup) {
+ return reinterpret(addr, 1, arena, cleanup);
}
- public static long sizeof() { return $LAYOUT().byteSize(); }
- public static MemorySegment allocate(SegmentAllocator allocator) { return allocator.allocate($LAYOUT()); }
- public static MemorySegment allocateArray(long len, SegmentAllocator allocator) {
- return allocator.allocate(MemoryLayout.sequenceLayout(len, $LAYOUT()));
+
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code elementCount * layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, long elementCount, Arena arena, Consumer cleanup) {
+ return addr.reinterpret(layout().byteSize() * elementCount, arena, cleanup);
}
- public static MemorySegment ofAddress(MemorySegment addr, Arena scope) { return RuntimeHelper.asArray(addr, $LAYOUT(), 1, scope); }
}
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_ctrltransfer.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_ctrltransfer.java
index c32eba2f..c5071566 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_ctrltransfer.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_ctrltransfer.java
@@ -2,13 +2,18 @@
package net.codecrete.usb.linux.gen.usbdevice_fs;
-import java.lang.foreign.Arena;
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentAllocator;
-import java.lang.invoke.VarHandle;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
/**
- * {@snippet :
+ * {@snippet lang=c :
* struct usbdevfs_ctrltransfer {
* __u8 bRequestType;
* __u8 bRequest;
@@ -16,210 +21,384 @@
* __u16 wIndex;
* __u16 wLength;
* __u32 timeout;
- * void* data;
- * };
+ * void *data;
+ * }
* }
*/
public class usbdevfs_ctrltransfer {
- public static MemoryLayout $LAYOUT() {
- return constants$0.const$0;
+ usbdevfs_ctrltransfer() {
+ // Should not be called directly
+ }
+
+ private static final GroupLayout $LAYOUT = MemoryLayout.structLayout(
+ usbdevice_fs.C_CHAR.withName("bRequestType"),
+ usbdevice_fs.C_CHAR.withName("bRequest"),
+ usbdevice_fs.C_SHORT.withName("wValue"),
+ usbdevice_fs.C_SHORT.withName("wIndex"),
+ usbdevice_fs.C_SHORT.withName("wLength"),
+ usbdevice_fs.C_INT.withName("timeout"),
+ MemoryLayout.paddingLayout(4),
+ usbdevice_fs.C_POINTER.withName("data")
+ ).withName("usbdevfs_ctrltransfer");
+
+ /**
+ * The layout of this struct
+ */
+ public static final GroupLayout layout() {
+ return $LAYOUT;
}
- public static VarHandle bRequestType$VH() {
- return constants$0.const$1;
+
+ private static final OfByte bRequestType$LAYOUT = (OfByte)$LAYOUT.select(groupElement("bRequestType"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * __u8 bRequestType
+ * }
+ */
+ public static final OfByte bRequestType$layout() {
+ return bRequestType$LAYOUT;
+ }
+
+ private static final long bRequestType$OFFSET = $LAYOUT.byteOffset(groupElement("bRequestType"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * __u8 bRequestType
+ * }
+ */
+ public static final long bRequestType$offset() {
+ return bRequestType$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * __u8 bRequestType;
+ * {@snippet lang=c :
+ * __u8 bRequestType
* }
*/
- public static byte bRequestType$get(MemorySegment seg) {
- return (byte)constants$0.const$1.get(seg);
+ public static byte bRequestType(MemorySegment struct) {
+ return struct.get(bRequestType$LAYOUT, bRequestType$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * __u8 bRequestType;
+ * {@snippet lang=c :
+ * __u8 bRequestType
* }
*/
- public static void bRequestType$set(MemorySegment seg, byte x) {
- constants$0.const$1.set(seg, x);
+ public static void bRequestType(MemorySegment struct, byte fieldValue) {
+ struct.set(bRequestType$LAYOUT, bRequestType$OFFSET, fieldValue);
}
- public static byte bRequestType$get(MemorySegment seg, long index) {
- return (byte)constants$0.const$1.get(seg.asSlice(index*sizeof()));
- }
- public static void bRequestType$set(MemorySegment seg, long index, byte x) {
- constants$0.const$1.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfByte bRequest$LAYOUT = (OfByte)$LAYOUT.select(groupElement("bRequest"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * __u8 bRequest
+ * }
+ */
+ public static final OfByte bRequest$layout() {
+ return bRequest$LAYOUT;
}
- public static VarHandle bRequest$VH() {
- return constants$0.const$2;
+
+ private static final long bRequest$OFFSET = $LAYOUT.byteOffset(groupElement("bRequest"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * __u8 bRequest
+ * }
+ */
+ public static final long bRequest$offset() {
+ return bRequest$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * __u8 bRequest;
+ * {@snippet lang=c :
+ * __u8 bRequest
* }
*/
- public static byte bRequest$get(MemorySegment seg) {
- return (byte)constants$0.const$2.get(seg);
+ public static byte bRequest(MemorySegment struct) {
+ return struct.get(bRequest$LAYOUT, bRequest$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * __u8 bRequest;
+ * {@snippet lang=c :
+ * __u8 bRequest
* }
*/
- public static void bRequest$set(MemorySegment seg, byte x) {
- constants$0.const$2.set(seg, x);
+ public static void bRequest(MemorySegment struct, byte fieldValue) {
+ struct.set(bRequest$LAYOUT, bRequest$OFFSET, fieldValue);
}
- public static byte bRequest$get(MemorySegment seg, long index) {
- return (byte)constants$0.const$2.get(seg.asSlice(index*sizeof()));
- }
- public static void bRequest$set(MemorySegment seg, long index, byte x) {
- constants$0.const$2.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfShort wValue$LAYOUT = (OfShort)$LAYOUT.select(groupElement("wValue"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * __u16 wValue
+ * }
+ */
+ public static final OfShort wValue$layout() {
+ return wValue$LAYOUT;
}
- public static VarHandle wValue$VH() {
- return constants$0.const$3;
+
+ private static final long wValue$OFFSET = $LAYOUT.byteOffset(groupElement("wValue"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * __u16 wValue
+ * }
+ */
+ public static final long wValue$offset() {
+ return wValue$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * __u16 wValue;
+ * {@snippet lang=c :
+ * __u16 wValue
* }
*/
- public static short wValue$get(MemorySegment seg) {
- return (short)constants$0.const$3.get(seg);
+ public static short wValue(MemorySegment struct) {
+ return struct.get(wValue$LAYOUT, wValue$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * __u16 wValue;
+ * {@snippet lang=c :
+ * __u16 wValue
* }
*/
- public static void wValue$set(MemorySegment seg, short x) {
- constants$0.const$3.set(seg, x);
+ public static void wValue(MemorySegment struct, short fieldValue) {
+ struct.set(wValue$LAYOUT, wValue$OFFSET, fieldValue);
}
- public static short wValue$get(MemorySegment seg, long index) {
- return (short)constants$0.const$3.get(seg.asSlice(index*sizeof()));
- }
- public static void wValue$set(MemorySegment seg, long index, short x) {
- constants$0.const$3.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfShort wIndex$LAYOUT = (OfShort)$LAYOUT.select(groupElement("wIndex"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * __u16 wIndex
+ * }
+ */
+ public static final OfShort wIndex$layout() {
+ return wIndex$LAYOUT;
}
- public static VarHandle wIndex$VH() {
- return constants$0.const$4;
+
+ private static final long wIndex$OFFSET = $LAYOUT.byteOffset(groupElement("wIndex"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * __u16 wIndex
+ * }
+ */
+ public static final long wIndex$offset() {
+ return wIndex$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * __u16 wIndex;
+ * {@snippet lang=c :
+ * __u16 wIndex
* }
*/
- public static short wIndex$get(MemorySegment seg) {
- return (short)constants$0.const$4.get(seg);
+ public static short wIndex(MemorySegment struct) {
+ return struct.get(wIndex$LAYOUT, wIndex$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * __u16 wIndex;
+ * {@snippet lang=c :
+ * __u16 wIndex
* }
*/
- public static void wIndex$set(MemorySegment seg, short x) {
- constants$0.const$4.set(seg, x);
+ public static void wIndex(MemorySegment struct, short fieldValue) {
+ struct.set(wIndex$LAYOUT, wIndex$OFFSET, fieldValue);
}
- public static short wIndex$get(MemorySegment seg, long index) {
- return (short)constants$0.const$4.get(seg.asSlice(index*sizeof()));
- }
- public static void wIndex$set(MemorySegment seg, long index, short x) {
- constants$0.const$4.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfShort wLength$LAYOUT = (OfShort)$LAYOUT.select(groupElement("wLength"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * __u16 wLength
+ * }
+ */
+ public static final OfShort wLength$layout() {
+ return wLength$LAYOUT;
}
- public static VarHandle wLength$VH() {
- return constants$0.const$5;
+
+ private static final long wLength$OFFSET = $LAYOUT.byteOffset(groupElement("wLength"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * __u16 wLength
+ * }
+ */
+ public static final long wLength$offset() {
+ return wLength$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * __u16 wLength;
+ * {@snippet lang=c :
+ * __u16 wLength
* }
*/
- public static short wLength$get(MemorySegment seg) {
- return (short)constants$0.const$5.get(seg);
+ public static short wLength(MemorySegment struct) {
+ return struct.get(wLength$LAYOUT, wLength$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * __u16 wLength;
+ * {@snippet lang=c :
+ * __u16 wLength
* }
*/
- public static void wLength$set(MemorySegment seg, short x) {
- constants$0.const$5.set(seg, x);
- }
- public static short wLength$get(MemorySegment seg, long index) {
- return (short)constants$0.const$5.get(seg.asSlice(index*sizeof()));
+ public static void wLength(MemorySegment struct, short fieldValue) {
+ struct.set(wLength$LAYOUT, wLength$OFFSET, fieldValue);
}
- public static void wLength$set(MemorySegment seg, long index, short x) {
- constants$0.const$5.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt timeout$LAYOUT = (OfInt)$LAYOUT.select(groupElement("timeout"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * __u32 timeout
+ * }
+ */
+ public static final OfInt timeout$layout() {
+ return timeout$LAYOUT;
}
- public static VarHandle timeout$VH() {
- return constants$1.const$0;
+
+ private static final long timeout$OFFSET = $LAYOUT.byteOffset(groupElement("timeout"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * __u32 timeout
+ * }
+ */
+ public static final long timeout$offset() {
+ return timeout$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * __u32 timeout;
+ * {@snippet lang=c :
+ * __u32 timeout
* }
*/
- public static int timeout$get(MemorySegment seg) {
- return (int)constants$1.const$0.get(seg);
+ public static int timeout(MemorySegment struct) {
+ return struct.get(timeout$LAYOUT, timeout$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * __u32 timeout;
+ * {@snippet lang=c :
+ * __u32 timeout
* }
*/
- public static void timeout$set(MemorySegment seg, int x) {
- constants$1.const$0.set(seg, x);
- }
- public static int timeout$get(MemorySegment seg, long index) {
- return (int)constants$1.const$0.get(seg.asSlice(index*sizeof()));
+ public static void timeout(MemorySegment struct, int fieldValue) {
+ struct.set(timeout$LAYOUT, timeout$OFFSET, fieldValue);
}
- public static void timeout$set(MemorySegment seg, long index, int x) {
- constants$1.const$0.set(seg.asSlice(index*sizeof()), x);
+
+ private static final AddressLayout data$LAYOUT = (AddressLayout)$LAYOUT.select(groupElement("data"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * void *data
+ * }
+ */
+ public static final AddressLayout data$layout() {
+ return data$LAYOUT;
}
- public static VarHandle data$VH() {
- return constants$1.const$1;
+
+ private static final long data$OFFSET = $LAYOUT.byteOffset(groupElement("data"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * void *data
+ * }
+ */
+ public static final long data$offset() {
+ return data$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * void* data;
+ * {@snippet lang=c :
+ * void *data
* }
*/
- public static MemorySegment data$get(MemorySegment seg) {
- return (java.lang.foreign.MemorySegment)constants$1.const$1.get(seg);
+ public static MemorySegment data(MemorySegment struct) {
+ return struct.get(data$LAYOUT, data$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * void* data;
+ * {@snippet lang=c :
+ * void *data
* }
*/
- public static void data$set(MemorySegment seg, MemorySegment x) {
- constants$1.const$1.set(seg, x);
+ public static void data(MemorySegment struct, MemorySegment fieldValue) {
+ struct.set(data$LAYOUT, data$OFFSET, fieldValue);
+ }
+
+ /**
+ * Obtains a slice of {@code arrayParam} which selects the array element at {@code index}.
+ * The returned segment has address {@code arrayParam.address() + index * layout().byteSize()}
+ */
+ public static MemorySegment asSlice(MemorySegment array, long index) {
+ return array.asSlice(layout().byteSize() * index);
+ }
+
+ /**
+ * The size (in bytes) of this struct
+ */
+ public static long sizeof() { return layout().byteSize(); }
+
+ /**
+ * Allocate a segment of size {@code layout().byteSize()} using {@code allocator}
+ */
+ public static MemorySegment allocate(SegmentAllocator allocator) {
+ return allocator.allocate(layout());
}
- public static MemorySegment data$get(MemorySegment seg, long index) {
- return (java.lang.foreign.MemorySegment)constants$1.const$1.get(seg.asSlice(index*sizeof()));
+
+ /**
+ * Allocate an array of size {@code elementCount} using {@code allocator}.
+ * The returned segment has size {@code elementCount * layout().byteSize()}.
+ */
+ public static MemorySegment allocateArray(long elementCount, SegmentAllocator allocator) {
+ return allocator.allocate(MemoryLayout.sequenceLayout(elementCount, layout()));
}
- public static void data$set(MemorySegment seg, long index, MemorySegment x) {
- constants$1.const$1.set(seg.asSlice(index*sizeof()), x);
+
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, Arena arena, Consumer cleanup) {
+ return reinterpret(addr, 1, arena, cleanup);
}
- public static long sizeof() { return $LAYOUT().byteSize(); }
- public static MemorySegment allocate(SegmentAllocator allocator) { return allocator.allocate($LAYOUT()); }
- public static MemorySegment allocateArray(long len, SegmentAllocator allocator) {
- return allocator.allocate(MemoryLayout.sequenceLayout(len, $LAYOUT()));
+
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code elementCount * layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, long elementCount, Arena arena, Consumer cleanup) {
+ return addr.reinterpret(layout().byteSize() * elementCount, arena, cleanup);
}
- public static MemorySegment ofAddress(MemorySegment addr, Arena scope) { return RuntimeHelper.asArray(addr, $LAYOUT(), 1, scope); }
}
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_disconnect_claim.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_disconnect_claim.java
index e238d86e..a41ba1db 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_disconnect_claim.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_disconnect_claim.java
@@ -2,88 +2,251 @@
package net.codecrete.usb.linux.gen.usbdevice_fs;
-import java.lang.foreign.Arena;
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentAllocator;
-import java.lang.invoke.VarHandle;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
/**
- * {@snippet :
+ * {@snippet lang=c :
* struct usbdevfs_disconnect_claim {
* unsigned int interface;
* unsigned int flags;
* char driver[256];
- * };
+ * }
* }
*/
public class usbdevfs_disconnect_claim {
- public static MemoryLayout $LAYOUT() {
- return constants$5.const$4;
+ usbdevfs_disconnect_claim() {
+ // Should not be called directly
}
- public static VarHandle interface_$VH() {
- return constants$5.const$5;
+
+ private static final GroupLayout $LAYOUT = MemoryLayout.structLayout(
+ usbdevice_fs.C_INT.withName("interface"),
+ usbdevice_fs.C_INT.withName("flags"),
+ MemoryLayout.sequenceLayout(256, usbdevice_fs.C_CHAR).withName("driver")
+ ).withName("usbdevfs_disconnect_claim");
+
+ /**
+ * The layout of this struct
+ */
+ public static final GroupLayout layout() {
+ return $LAYOUT;
}
+
+ private static final OfInt interface_$LAYOUT = (OfInt)$LAYOUT.select(groupElement("interface"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int interface
+ * }
+ */
+ public static final OfInt interface_$layout() {
+ return interface_$LAYOUT;
+ }
+
+ private static final long interface_$OFFSET = $LAYOUT.byteOffset(groupElement("interface"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int interface
+ * }
+ */
+ public static final long interface_$offset() {
+ return interface_$OFFSET;
+ }
+
/**
* Getter for field:
- * {@snippet :
- * unsigned int interface;
+ * {@snippet lang=c :
+ * unsigned int interface
* }
*/
- public static int interface_$get(MemorySegment seg) {
- return (int)constants$5.const$5.get(seg);
+ public static int interface_(MemorySegment struct) {
+ return struct.get(interface_$LAYOUT, interface_$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * unsigned int interface;
+ * {@snippet lang=c :
+ * unsigned int interface
+ * }
+ */
+ public static void interface_(MemorySegment struct, int fieldValue) {
+ struct.set(interface_$LAYOUT, interface_$OFFSET, fieldValue);
+ }
+
+ private static final OfInt flags$LAYOUT = (OfInt)$LAYOUT.select(groupElement("flags"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int flags
* }
*/
- public static void interface_$set(MemorySegment seg, int x) {
- constants$5.const$5.set(seg, x);
+ public static final OfInt flags$layout() {
+ return flags$LAYOUT;
}
- public static int interface_$get(MemorySegment seg, long index) {
- return (int)constants$5.const$5.get(seg.asSlice(index*sizeof()));
+
+ private static final long flags$OFFSET = $LAYOUT.byteOffset(groupElement("flags"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int flags
+ * }
+ */
+ public static final long flags$offset() {
+ return flags$OFFSET;
}
- public static void interface_$set(MemorySegment seg, long index, int x) {
- constants$5.const$5.set(seg.asSlice(index*sizeof()), x);
+
+ /**
+ * Getter for field:
+ * {@snippet lang=c :
+ * unsigned int flags
+ * }
+ */
+ public static int flags(MemorySegment struct) {
+ return struct.get(flags$LAYOUT, flags$OFFSET);
+ }
+
+ /**
+ * Setter for field:
+ * {@snippet lang=c :
+ * unsigned int flags
+ * }
+ */
+ public static void flags(MemorySegment struct, int fieldValue) {
+ struct.set(flags$LAYOUT, flags$OFFSET, fieldValue);
+ }
+
+ private static final SequenceLayout driver$LAYOUT = (SequenceLayout)$LAYOUT.select(groupElement("driver"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * char driver[256]
+ * }
+ */
+ public static final SequenceLayout driver$layout() {
+ return driver$LAYOUT;
}
- public static VarHandle flags$VH() {
- return constants$6.const$0;
+
+ private static final long driver$OFFSET = $LAYOUT.byteOffset(groupElement("driver"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * char driver[256]
+ * }
+ */
+ public static final long driver$offset() {
+ return driver$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * unsigned int flags;
+ * {@snippet lang=c :
+ * char driver[256]
* }
*/
- public static int flags$get(MemorySegment seg) {
- return (int)constants$6.const$0.get(seg);
+ public static MemorySegment driver(MemorySegment struct) {
+ return struct.asSlice(driver$OFFSET, driver$LAYOUT.byteSize());
}
+
/**
* Setter for field:
- * {@snippet :
- * unsigned int flags;
+ * {@snippet lang=c :
+ * char driver[256]
+ * }
+ */
+ public static void driver(MemorySegment struct, MemorySegment fieldValue) {
+ MemorySegment.copy(fieldValue, 0L, struct, driver$OFFSET, driver$LAYOUT.byteSize());
+ }
+
+ private static long[] driver$DIMS = { 256 };
+
+ /**
+ * Dimensions for array field:
+ * {@snippet lang=c :
+ * char driver[256]
+ * }
+ */
+ public static long[] driver$dimensions() {
+ return driver$DIMS;
+ }
+ private static final VarHandle driver$ELEM_HANDLE = driver$LAYOUT.varHandle(sequenceElement());
+
+ /**
+ * Indexed getter for field:
+ * {@snippet lang=c :
+ * char driver[256]
+ * }
+ */
+ public static byte driver(MemorySegment struct, long index0) {
+ return (byte)driver$ELEM_HANDLE.get(struct, 0L, index0);
+ }
+
+ /**
+ * Indexed setter for field:
+ * {@snippet lang=c :
+ * char driver[256]
* }
*/
- public static void flags$set(MemorySegment seg, int x) {
- constants$6.const$0.set(seg, x);
+ public static void driver(MemorySegment struct, long index0, byte fieldValue) {
+ driver$ELEM_HANDLE.set(struct, 0L, index0, fieldValue);
}
- public static int flags$get(MemorySegment seg, long index) {
- return (int)constants$6.const$0.get(seg.asSlice(index*sizeof()));
+
+ /**
+ * Obtains a slice of {@code arrayParam} which selects the array element at {@code index}.
+ * The returned segment has address {@code arrayParam.address() + index * layout().byteSize()}
+ */
+ public static MemorySegment asSlice(MemorySegment array, long index) {
+ return array.asSlice(layout().byteSize() * index);
}
- public static void flags$set(MemorySegment seg, long index, int x) {
- constants$6.const$0.set(seg.asSlice(index*sizeof()), x);
+
+ /**
+ * The size (in bytes) of this struct
+ */
+ public static long sizeof() { return layout().byteSize(); }
+
+ /**
+ * Allocate a segment of size {@code layout().byteSize()} using {@code allocator}
+ */
+ public static MemorySegment allocate(SegmentAllocator allocator) {
+ return allocator.allocate(layout());
}
- public static MemorySegment driver$slice(MemorySegment seg) {
- return seg.asSlice(8, 256);
+
+ /**
+ * Allocate an array of size {@code elementCount} using {@code allocator}.
+ * The returned segment has size {@code elementCount * layout().byteSize()}.
+ */
+ public static MemorySegment allocateArray(long elementCount, SegmentAllocator allocator) {
+ return allocator.allocate(MemoryLayout.sequenceLayout(elementCount, layout()));
}
- public static long sizeof() { return $LAYOUT().byteSize(); }
- public static MemorySegment allocate(SegmentAllocator allocator) { return allocator.allocate($LAYOUT()); }
- public static MemorySegment allocateArray(long len, SegmentAllocator allocator) {
- return allocator.allocate(MemoryLayout.sequenceLayout(len, $LAYOUT()));
+
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, Arena arena, Consumer cleanup) {
+ return reinterpret(addr, 1, arena, cleanup);
}
- public static MemorySegment ofAddress(MemorySegment addr, Arena scope) { return RuntimeHelper.asArray(addr, $LAYOUT(), 1, scope); }
-}
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code elementCount * layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, long elementCount, Arena arena, Consumer cleanup) {
+ return addr.reinterpret(layout().byteSize() * elementCount, arena, cleanup);
+ }
+}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_ioctl.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_ioctl.java
index 8135f579..8d4b6143 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_ioctl.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_ioctl.java
@@ -2,112 +2,218 @@
package net.codecrete.usb.linux.gen.usbdevice_fs;
-import java.lang.foreign.Arena;
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentAllocator;
-import java.lang.invoke.VarHandle;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
/**
- * {@snippet :
+ * {@snippet lang=c :
* struct usbdevfs_ioctl {
* int ifno;
* int ioctl_code;
- * void* data;
- * };
+ * void *data;
+ * }
* }
*/
public class usbdevfs_ioctl {
- public static MemoryLayout $LAYOUT() {
- return constants$5.const$0;
+ usbdevfs_ioctl() {
+ // Should not be called directly
}
- public static VarHandle ifno$VH() {
- return constants$5.const$1;
+
+ private static final GroupLayout $LAYOUT = MemoryLayout.structLayout(
+ usbdevice_fs.C_INT.withName("ifno"),
+ usbdevice_fs.C_INT.withName("ioctl_code"),
+ usbdevice_fs.C_POINTER.withName("data")
+ ).withName("usbdevfs_ioctl");
+
+ /**
+ * The layout of this struct
+ */
+ public static final GroupLayout layout() {
+ return $LAYOUT;
+ }
+
+ private static final OfInt ifno$LAYOUT = (OfInt)$LAYOUT.select(groupElement("ifno"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * int ifno
+ * }
+ */
+ public static final OfInt ifno$layout() {
+ return ifno$LAYOUT;
+ }
+
+ private static final long ifno$OFFSET = $LAYOUT.byteOffset(groupElement("ifno"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * int ifno
+ * }
+ */
+ public static final long ifno$offset() {
+ return ifno$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * int ifno;
+ * {@snippet lang=c :
+ * int ifno
* }
*/
- public static int ifno$get(MemorySegment seg) {
- return (int)constants$5.const$1.get(seg);
+ public static int ifno(MemorySegment struct) {
+ return struct.get(ifno$LAYOUT, ifno$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * int ifno;
+ * {@snippet lang=c :
+ * int ifno
* }
*/
- public static void ifno$set(MemorySegment seg, int x) {
- constants$5.const$1.set(seg, x);
- }
- public static int ifno$get(MemorySegment seg, long index) {
- return (int)constants$5.const$1.get(seg.asSlice(index*sizeof()));
+ public static void ifno(MemorySegment struct, int fieldValue) {
+ struct.set(ifno$LAYOUT, ifno$OFFSET, fieldValue);
}
- public static void ifno$set(MemorySegment seg, long index, int x) {
- constants$5.const$1.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt ioctl_code$LAYOUT = (OfInt)$LAYOUT.select(groupElement("ioctl_code"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * int ioctl_code
+ * }
+ */
+ public static final OfInt ioctl_code$layout() {
+ return ioctl_code$LAYOUT;
}
- public static VarHandle ioctl_code$VH() {
- return constants$5.const$2;
+
+ private static final long ioctl_code$OFFSET = $LAYOUT.byteOffset(groupElement("ioctl_code"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * int ioctl_code
+ * }
+ */
+ public static final long ioctl_code$offset() {
+ return ioctl_code$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * int ioctl_code;
+ * {@snippet lang=c :
+ * int ioctl_code
* }
*/
- public static int ioctl_code$get(MemorySegment seg) {
- return (int)constants$5.const$2.get(seg);
+ public static int ioctl_code(MemorySegment struct) {
+ return struct.get(ioctl_code$LAYOUT, ioctl_code$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * int ioctl_code;
+ * {@snippet lang=c :
+ * int ioctl_code
* }
*/
- public static void ioctl_code$set(MemorySegment seg, int x) {
- constants$5.const$2.set(seg, x);
+ public static void ioctl_code(MemorySegment struct, int fieldValue) {
+ struct.set(ioctl_code$LAYOUT, ioctl_code$OFFSET, fieldValue);
}
- public static int ioctl_code$get(MemorySegment seg, long index) {
- return (int)constants$5.const$2.get(seg.asSlice(index*sizeof()));
- }
- public static void ioctl_code$set(MemorySegment seg, long index, int x) {
- constants$5.const$2.set(seg.asSlice(index*sizeof()), x);
+
+ private static final AddressLayout data$LAYOUT = (AddressLayout)$LAYOUT.select(groupElement("data"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * void *data
+ * }
+ */
+ public static final AddressLayout data$layout() {
+ return data$LAYOUT;
}
- public static VarHandle data$VH() {
- return constants$5.const$3;
+
+ private static final long data$OFFSET = $LAYOUT.byteOffset(groupElement("data"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * void *data
+ * }
+ */
+ public static final long data$offset() {
+ return data$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * void* data;
+ * {@snippet lang=c :
+ * void *data
* }
*/
- public static MemorySegment data$get(MemorySegment seg) {
- return (java.lang.foreign.MemorySegment)constants$5.const$3.get(seg);
+ public static MemorySegment data(MemorySegment struct) {
+ return struct.get(data$LAYOUT, data$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * void* data;
+ * {@snippet lang=c :
+ * void *data
* }
*/
- public static void data$set(MemorySegment seg, MemorySegment x) {
- constants$5.const$3.set(seg, x);
+ public static void data(MemorySegment struct, MemorySegment fieldValue) {
+ struct.set(data$LAYOUT, data$OFFSET, fieldValue);
+ }
+
+ /**
+ * Obtains a slice of {@code arrayParam} which selects the array element at {@code index}.
+ * The returned segment has address {@code arrayParam.address() + index * layout().byteSize()}
+ */
+ public static MemorySegment asSlice(MemorySegment array, long index) {
+ return array.asSlice(layout().byteSize() * index);
}
- public static MemorySegment data$get(MemorySegment seg, long index) {
- return (java.lang.foreign.MemorySegment)constants$5.const$3.get(seg.asSlice(index*sizeof()));
+
+ /**
+ * The size (in bytes) of this struct
+ */
+ public static long sizeof() { return layout().byteSize(); }
+
+ /**
+ * Allocate a segment of size {@code layout().byteSize()} using {@code allocator}
+ */
+ public static MemorySegment allocate(SegmentAllocator allocator) {
+ return allocator.allocate(layout());
}
- public static void data$set(MemorySegment seg, long index, MemorySegment x) {
- constants$5.const$3.set(seg.asSlice(index*sizeof()), x);
+
+ /**
+ * Allocate an array of size {@code elementCount} using {@code allocator}.
+ * The returned segment has size {@code elementCount * layout().byteSize()}.
+ */
+ public static MemorySegment allocateArray(long elementCount, SegmentAllocator allocator) {
+ return allocator.allocate(MemoryLayout.sequenceLayout(elementCount, layout()));
}
- public static long sizeof() { return $LAYOUT().byteSize(); }
- public static MemorySegment allocate(SegmentAllocator allocator) { return allocator.allocate($LAYOUT()); }
- public static MemorySegment allocateArray(long len, SegmentAllocator allocator) {
- return allocator.allocate(MemoryLayout.sequenceLayout(len, $LAYOUT()));
+
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, Arena arena, Consumer cleanup) {
+ return reinterpret(addr, 1, arena, cleanup);
}
- public static MemorySegment ofAddress(MemorySegment addr, Arena scope) { return RuntimeHelper.asArray(addr, $LAYOUT(), 1, scope); }
-}
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code elementCount * layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, long elementCount, Arena arena, Consumer cleanup) {
+ return addr.reinterpret(layout().byteSize() * elementCount, arena, cleanup);
+ }
+}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_iso_packet_desc.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_iso_packet_desc.java
new file mode 100644
index 00000000..c269241e
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_iso_packet_desc.java
@@ -0,0 +1,219 @@
+// Generated by jextract
+
+package net.codecrete.usb.linux.gen.usbdevice_fs;
+
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+/**
+ * {@snippet lang=c :
+ * struct usbdevfs_iso_packet_desc {
+ * unsigned int length;
+ * unsigned int actual_length;
+ * unsigned int status;
+ * }
+ * }
+ */
+public class usbdevfs_iso_packet_desc {
+
+ usbdevfs_iso_packet_desc() {
+ // Should not be called directly
+ }
+
+ private static final GroupLayout $LAYOUT = MemoryLayout.structLayout(
+ usbdevice_fs.C_INT.withName("length"),
+ usbdevice_fs.C_INT.withName("actual_length"),
+ usbdevice_fs.C_INT.withName("status")
+ ).withName("usbdevfs_iso_packet_desc");
+
+ /**
+ * The layout of this struct
+ */
+ public static final GroupLayout layout() {
+ return $LAYOUT;
+ }
+
+ private static final OfInt length$LAYOUT = (OfInt)$LAYOUT.select(groupElement("length"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int length
+ * }
+ */
+ public static final OfInt length$layout() {
+ return length$LAYOUT;
+ }
+
+ private static final long length$OFFSET = $LAYOUT.byteOffset(groupElement("length"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int length
+ * }
+ */
+ public static final long length$offset() {
+ return length$OFFSET;
+ }
+
+ /**
+ * Getter for field:
+ * {@snippet lang=c :
+ * unsigned int length
+ * }
+ */
+ public static int length(MemorySegment struct) {
+ return struct.get(length$LAYOUT, length$OFFSET);
+ }
+
+ /**
+ * Setter for field:
+ * {@snippet lang=c :
+ * unsigned int length
+ * }
+ */
+ public static void length(MemorySegment struct, int fieldValue) {
+ struct.set(length$LAYOUT, length$OFFSET, fieldValue);
+ }
+
+ private static final OfInt actual_length$LAYOUT = (OfInt)$LAYOUT.select(groupElement("actual_length"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int actual_length
+ * }
+ */
+ public static final OfInt actual_length$layout() {
+ return actual_length$LAYOUT;
+ }
+
+ private static final long actual_length$OFFSET = $LAYOUT.byteOffset(groupElement("actual_length"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int actual_length
+ * }
+ */
+ public static final long actual_length$offset() {
+ return actual_length$OFFSET;
+ }
+
+ /**
+ * Getter for field:
+ * {@snippet lang=c :
+ * unsigned int actual_length
+ * }
+ */
+ public static int actual_length(MemorySegment struct) {
+ return struct.get(actual_length$LAYOUT, actual_length$OFFSET);
+ }
+
+ /**
+ * Setter for field:
+ * {@snippet lang=c :
+ * unsigned int actual_length
+ * }
+ */
+ public static void actual_length(MemorySegment struct, int fieldValue) {
+ struct.set(actual_length$LAYOUT, actual_length$OFFSET, fieldValue);
+ }
+
+ private static final OfInt status$LAYOUT = (OfInt)$LAYOUT.select(groupElement("status"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int status
+ * }
+ */
+ public static final OfInt status$layout() {
+ return status$LAYOUT;
+ }
+
+ private static final long status$OFFSET = $LAYOUT.byteOffset(groupElement("status"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int status
+ * }
+ */
+ public static final long status$offset() {
+ return status$OFFSET;
+ }
+
+ /**
+ * Getter for field:
+ * {@snippet lang=c :
+ * unsigned int status
+ * }
+ */
+ public static int status(MemorySegment struct) {
+ return struct.get(status$LAYOUT, status$OFFSET);
+ }
+
+ /**
+ * Setter for field:
+ * {@snippet lang=c :
+ * unsigned int status
+ * }
+ */
+ public static void status(MemorySegment struct, int fieldValue) {
+ struct.set(status$LAYOUT, status$OFFSET, fieldValue);
+ }
+
+ /**
+ * Obtains a slice of {@code arrayParam} which selects the array element at {@code index}.
+ * The returned segment has address {@code arrayParam.address() + index * layout().byteSize()}
+ */
+ public static MemorySegment asSlice(MemorySegment array, long index) {
+ return array.asSlice(layout().byteSize() * index);
+ }
+
+ /**
+ * The size (in bytes) of this struct
+ */
+ public static long sizeof() { return layout().byteSize(); }
+
+ /**
+ * Allocate a segment of size {@code layout().byteSize()} using {@code allocator}
+ */
+ public static MemorySegment allocate(SegmentAllocator allocator) {
+ return allocator.allocate(layout());
+ }
+
+ /**
+ * Allocate an array of size {@code elementCount} using {@code allocator}.
+ * The returned segment has size {@code elementCount * layout().byteSize()}.
+ */
+ public static MemorySegment allocateArray(long elementCount, SegmentAllocator allocator) {
+ return allocator.allocate(MemoryLayout.sequenceLayout(elementCount, layout()));
+ }
+
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, Arena arena, Consumer cleanup) {
+ return reinterpret(addr, 1, arena, cleanup);
+ }
+
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code elementCount * layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, long elementCount, Arena arena, Consumer cleanup) {
+ return addr.reinterpret(layout().byteSize() * elementCount, arena, cleanup);
+ }
+}
+
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_setinterface.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_setinterface.java
index b9989942..fdae516e 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_setinterface.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_setinterface.java
@@ -2,84 +2,172 @@
package net.codecrete.usb.linux.gen.usbdevice_fs;
-import java.lang.foreign.Arena;
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentAllocator;
-import java.lang.invoke.VarHandle;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
/**
- * {@snippet :
+ * {@snippet lang=c :
* struct usbdevfs_setinterface {
* unsigned int interface;
* unsigned int altsetting;
- * };
+ * }
* }
*/
public class usbdevfs_setinterface {
- public static MemoryLayout $LAYOUT() {
- return constants$2.const$1;
+ usbdevfs_setinterface() {
+ // Should not be called directly
+ }
+
+ private static final GroupLayout $LAYOUT = MemoryLayout.structLayout(
+ usbdevice_fs.C_INT.withName("interface"),
+ usbdevice_fs.C_INT.withName("altsetting")
+ ).withName("usbdevfs_setinterface");
+
+ /**
+ * The layout of this struct
+ */
+ public static final GroupLayout layout() {
+ return $LAYOUT;
+ }
+
+ private static final OfInt interface_$LAYOUT = (OfInt)$LAYOUT.select(groupElement("interface"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int interface
+ * }
+ */
+ public static final OfInt interface_$layout() {
+ return interface_$LAYOUT;
}
- public static VarHandle interface_$VH() {
- return constants$2.const$2;
+
+ private static final long interface_$OFFSET = $LAYOUT.byteOffset(groupElement("interface"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int interface
+ * }
+ */
+ public static final long interface_$offset() {
+ return interface_$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * unsigned int interface;
+ * {@snippet lang=c :
+ * unsigned int interface
* }
*/
- public static int interface_$get(MemorySegment seg) {
- return (int)constants$2.const$2.get(seg);
+ public static int interface_(MemorySegment struct) {
+ return struct.get(interface_$LAYOUT, interface_$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * unsigned int interface;
+ * {@snippet lang=c :
+ * unsigned int interface
* }
*/
- public static void interface_$set(MemorySegment seg, int x) {
- constants$2.const$2.set(seg, x);
+ public static void interface_(MemorySegment struct, int fieldValue) {
+ struct.set(interface_$LAYOUT, interface_$OFFSET, fieldValue);
}
- public static int interface_$get(MemorySegment seg, long index) {
- return (int)constants$2.const$2.get(seg.asSlice(index*sizeof()));
- }
- public static void interface_$set(MemorySegment seg, long index, int x) {
- constants$2.const$2.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt altsetting$LAYOUT = (OfInt)$LAYOUT.select(groupElement("altsetting"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int altsetting
+ * }
+ */
+ public static final OfInt altsetting$layout() {
+ return altsetting$LAYOUT;
}
- public static VarHandle altsetting$VH() {
- return constants$2.const$3;
+
+ private static final long altsetting$OFFSET = $LAYOUT.byteOffset(groupElement("altsetting"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int altsetting
+ * }
+ */
+ public static final long altsetting$offset() {
+ return altsetting$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * unsigned int altsetting;
+ * {@snippet lang=c :
+ * unsigned int altsetting
* }
*/
- public static int altsetting$get(MemorySegment seg) {
- return (int)constants$2.const$3.get(seg);
+ public static int altsetting(MemorySegment struct) {
+ return struct.get(altsetting$LAYOUT, altsetting$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * unsigned int altsetting;
+ * {@snippet lang=c :
+ * unsigned int altsetting
* }
*/
- public static void altsetting$set(MemorySegment seg, int x) {
- constants$2.const$3.set(seg, x);
+ public static void altsetting(MemorySegment struct, int fieldValue) {
+ struct.set(altsetting$LAYOUT, altsetting$OFFSET, fieldValue);
}
- public static int altsetting$get(MemorySegment seg, long index) {
- return (int)constants$2.const$3.get(seg.asSlice(index*sizeof()));
+
+ /**
+ * Obtains a slice of {@code arrayParam} which selects the array element at {@code index}.
+ * The returned segment has address {@code arrayParam.address() + index * layout().byteSize()}
+ */
+ public static MemorySegment asSlice(MemorySegment array, long index) {
+ return array.asSlice(layout().byteSize() * index);
}
- public static void altsetting$set(MemorySegment seg, long index, int x) {
- constants$2.const$3.set(seg.asSlice(index*sizeof()), x);
+
+ /**
+ * The size (in bytes) of this struct
+ */
+ public static long sizeof() { return layout().byteSize(); }
+
+ /**
+ * Allocate a segment of size {@code layout().byteSize()} using {@code allocator}
+ */
+ public static MemorySegment allocate(SegmentAllocator allocator) {
+ return allocator.allocate(layout());
}
- public static long sizeof() { return $LAYOUT().byteSize(); }
- public static MemorySegment allocate(SegmentAllocator allocator) { return allocator.allocate($LAYOUT()); }
- public static MemorySegment allocateArray(long len, SegmentAllocator allocator) {
- return allocator.allocate(MemoryLayout.sequenceLayout(len, $LAYOUT()));
+
+ /**
+ * Allocate an array of size {@code elementCount} using {@code allocator}.
+ * The returned segment has size {@code elementCount * layout().byteSize()}.
+ */
+ public static MemorySegment allocateArray(long elementCount, SegmentAllocator allocator) {
+ return allocator.allocate(MemoryLayout.sequenceLayout(elementCount, layout()));
}
- public static MemorySegment ofAddress(MemorySegment addr, Arena scope) { return RuntimeHelper.asArray(addr, $LAYOUT(), 1, scope); }
-}
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, Arena arena, Consumer cleanup) {
+ return reinterpret(addr, 1, arena, cleanup);
+ }
+
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code elementCount * layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, long elementCount, Arena arena, Consumer cleanup) {
+ return addr.reinterpret(layout().byteSize() * elementCount, arena, cleanup);
+ }
+}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_urb.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_urb.java
index ab7fd8e6..556be6c4 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_urb.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevfs_urb.java
@@ -2,19 +2,24 @@
package net.codecrete.usb.linux.gen.usbdevice_fs;
-import java.lang.foreign.Arena;
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentAllocator;
-import java.lang.invoke.VarHandle;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
/**
- * {@snippet :
+ * {@snippet lang=c :
* struct usbdevfs_urb {
* unsigned char type;
* unsigned char endpoint;
* int status;
* unsigned int flags;
- * void* buffer;
+ * void *buffer;
* int buffer_length;
* int actual_length;
* int start_frame;
@@ -24,373 +29,612 @@
* };
* int error_count;
* unsigned int signr;
- * void* usercontext;
- * struct usbdevfs_iso_packet_desc iso_frame_desc[0];
- * };
+ * void *usercontext;
+ * struct usbdevfs_iso_packet_desc iso_frame_desc[];
+ * }
* }
*/
public class usbdevfs_urb {
- public static MemoryLayout $LAYOUT() {
- return constants$2.const$4;
+ usbdevfs_urb() {
+ // Should not be called directly
}
- public static VarHandle type$VH() {
- return constants$2.const$5;
+
+ private static final GroupLayout $LAYOUT = MemoryLayout.structLayout(
+ usbdevice_fs.C_CHAR.withName("type"),
+ usbdevice_fs.C_CHAR.withName("endpoint"),
+ MemoryLayout.paddingLayout(2),
+ usbdevice_fs.C_INT.withName("status"),
+ usbdevice_fs.C_INT.withName("flags"),
+ MemoryLayout.paddingLayout(4),
+ usbdevice_fs.C_POINTER.withName("buffer"),
+ usbdevice_fs.C_INT.withName("buffer_length"),
+ usbdevice_fs.C_INT.withName("actual_length"),
+ usbdevice_fs.C_INT.withName("start_frame"),
+ MemoryLayout.paddingLayout(4),
+ usbdevice_fs.C_INT.withName("error_count"),
+ usbdevice_fs.C_INT.withName("signr"),
+ usbdevice_fs.C_POINTER.withName("usercontext"),
+ MemoryLayout.sequenceLayout(0, usbdevfs_iso_packet_desc.layout()).withName("iso_frame_desc")
+ ).withName("usbdevfs_urb");
+
+ /**
+ * The layout of this struct
+ */
+ public static final GroupLayout layout() {
+ return $LAYOUT;
}
+
+ private static final OfByte type$LAYOUT = (OfByte)$LAYOUT.select(groupElement("type"));
+
/**
- * Getter for field:
- * {@snippet :
- * unsigned char type;
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned char type
* }
*/
- public static byte type$get(MemorySegment seg) {
- return (byte)constants$2.const$5.get(seg);
+ public static final OfByte type$layout() {
+ return type$LAYOUT;
}
+
+ private static final long type$OFFSET = $LAYOUT.byteOffset(groupElement("type"));
+
/**
- * Setter for field:
- * {@snippet :
- * unsigned char type;
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned char type
* }
*/
- public static void type$set(MemorySegment seg, byte x) {
- constants$2.const$5.set(seg, x);
- }
- public static byte type$get(MemorySegment seg, long index) {
- return (byte)constants$2.const$5.get(seg.asSlice(index*sizeof()));
- }
- public static void type$set(MemorySegment seg, long index, byte x) {
- constants$2.const$5.set(seg.asSlice(index*sizeof()), x);
- }
- public static VarHandle endpoint$VH() {
- return constants$3.const$0;
+ public static final long type$offset() {
+ return type$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * unsigned char endpoint;
+ * {@snippet lang=c :
+ * unsigned char type
* }
*/
- public static byte endpoint$get(MemorySegment seg) {
- return (byte)constants$3.const$0.get(seg);
+ public static byte type(MemorySegment struct) {
+ return struct.get(type$LAYOUT, type$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * unsigned char endpoint;
+ * {@snippet lang=c :
+ * unsigned char type
* }
*/
- public static void endpoint$set(MemorySegment seg, byte x) {
- constants$3.const$0.set(seg, x);
+ public static void type(MemorySegment struct, byte fieldValue) {
+ struct.set(type$LAYOUT, type$OFFSET, fieldValue);
}
- public static byte endpoint$get(MemorySegment seg, long index) {
- return (byte)constants$3.const$0.get(seg.asSlice(index*sizeof()));
- }
- public static void endpoint$set(MemorySegment seg, long index, byte x) {
- constants$3.const$0.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfByte endpoint$LAYOUT = (OfByte)$LAYOUT.select(groupElement("endpoint"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned char endpoint
+ * }
+ */
+ public static final OfByte endpoint$layout() {
+ return endpoint$LAYOUT;
}
- public static VarHandle status$VH() {
- return constants$3.const$1;
+
+ private static final long endpoint$OFFSET = $LAYOUT.byteOffset(groupElement("endpoint"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned char endpoint
+ * }
+ */
+ public static final long endpoint$offset() {
+ return endpoint$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * int status;
+ * {@snippet lang=c :
+ * unsigned char endpoint
* }
*/
- public static int status$get(MemorySegment seg) {
- return (int)constants$3.const$1.get(seg);
+ public static byte endpoint(MemorySegment struct) {
+ return struct.get(endpoint$LAYOUT, endpoint$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * int status;
+ * {@snippet lang=c :
+ * unsigned char endpoint
* }
*/
- public static void status$set(MemorySegment seg, int x) {
- constants$3.const$1.set(seg, x);
- }
- public static int status$get(MemorySegment seg, long index) {
- return (int)constants$3.const$1.get(seg.asSlice(index*sizeof()));
+ public static void endpoint(MemorySegment struct, byte fieldValue) {
+ struct.set(endpoint$LAYOUT, endpoint$OFFSET, fieldValue);
}
- public static void status$set(MemorySegment seg, long index, int x) {
- constants$3.const$1.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt status$LAYOUT = (OfInt)$LAYOUT.select(groupElement("status"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * int status
+ * }
+ */
+ public static final OfInt status$layout() {
+ return status$LAYOUT;
}
- public static VarHandle flags$VH() {
- return constants$3.const$2;
+
+ private static final long status$OFFSET = $LAYOUT.byteOffset(groupElement("status"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * int status
+ * }
+ */
+ public static final long status$offset() {
+ return status$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * unsigned int flags;
+ * {@snippet lang=c :
+ * int status
* }
*/
- public static int flags$get(MemorySegment seg) {
- return (int)constants$3.const$2.get(seg);
+ public static int status(MemorySegment struct) {
+ return struct.get(status$LAYOUT, status$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * unsigned int flags;
+ * {@snippet lang=c :
+ * int status
* }
*/
- public static void flags$set(MemorySegment seg, int x) {
- constants$3.const$2.set(seg, x);
+ public static void status(MemorySegment struct, int fieldValue) {
+ struct.set(status$LAYOUT, status$OFFSET, fieldValue);
}
- public static int flags$get(MemorySegment seg, long index) {
- return (int)constants$3.const$2.get(seg.asSlice(index*sizeof()));
- }
- public static void flags$set(MemorySegment seg, long index, int x) {
- constants$3.const$2.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt flags$LAYOUT = (OfInt)$LAYOUT.select(groupElement("flags"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int flags
+ * }
+ */
+ public static final OfInt flags$layout() {
+ return flags$LAYOUT;
}
- public static VarHandle buffer$VH() {
- return constants$3.const$3;
+
+ private static final long flags$OFFSET = $LAYOUT.byteOffset(groupElement("flags"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int flags
+ * }
+ */
+ public static final long flags$offset() {
+ return flags$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * void* buffer;
+ * {@snippet lang=c :
+ * unsigned int flags
* }
*/
- public static MemorySegment buffer$get(MemorySegment seg) {
- return (java.lang.foreign.MemorySegment)constants$3.const$3.get(seg);
+ public static int flags(MemorySegment struct) {
+ return struct.get(flags$LAYOUT, flags$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * void* buffer;
+ * {@snippet lang=c :
+ * unsigned int flags
* }
*/
- public static void buffer$set(MemorySegment seg, MemorySegment x) {
- constants$3.const$3.set(seg, x);
- }
- public static MemorySegment buffer$get(MemorySegment seg, long index) {
- return (java.lang.foreign.MemorySegment)constants$3.const$3.get(seg.asSlice(index*sizeof()));
+ public static void flags(MemorySegment struct, int fieldValue) {
+ struct.set(flags$LAYOUT, flags$OFFSET, fieldValue);
}
- public static void buffer$set(MemorySegment seg, long index, MemorySegment x) {
- constants$3.const$3.set(seg.asSlice(index*sizeof()), x);
+
+ private static final AddressLayout buffer$LAYOUT = (AddressLayout)$LAYOUT.select(groupElement("buffer"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * void *buffer
+ * }
+ */
+ public static final AddressLayout buffer$layout() {
+ return buffer$LAYOUT;
}
- public static VarHandle buffer_length$VH() {
- return constants$3.const$4;
+
+ private static final long buffer$OFFSET = $LAYOUT.byteOffset(groupElement("buffer"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * void *buffer
+ * }
+ */
+ public static final long buffer$offset() {
+ return buffer$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * int buffer_length;
+ * {@snippet lang=c :
+ * void *buffer
* }
*/
- public static int buffer_length$get(MemorySegment seg) {
- return (int)constants$3.const$4.get(seg);
+ public static MemorySegment buffer(MemorySegment struct) {
+ return struct.get(buffer$LAYOUT, buffer$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * int buffer_length;
+ * {@snippet lang=c :
+ * void *buffer
* }
*/
- public static void buffer_length$set(MemorySegment seg, int x) {
- constants$3.const$4.set(seg, x);
- }
- public static int buffer_length$get(MemorySegment seg, long index) {
- return (int)constants$3.const$4.get(seg.asSlice(index*sizeof()));
+ public static void buffer(MemorySegment struct, MemorySegment fieldValue) {
+ struct.set(buffer$LAYOUT, buffer$OFFSET, fieldValue);
}
- public static void buffer_length$set(MemorySegment seg, long index, int x) {
- constants$3.const$4.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt buffer_length$LAYOUT = (OfInt)$LAYOUT.select(groupElement("buffer_length"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * int buffer_length
+ * }
+ */
+ public static final OfInt buffer_length$layout() {
+ return buffer_length$LAYOUT;
}
- public static VarHandle actual_length$VH() {
- return constants$3.const$5;
+
+ private static final long buffer_length$OFFSET = $LAYOUT.byteOffset(groupElement("buffer_length"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * int buffer_length
+ * }
+ */
+ public static final long buffer_length$offset() {
+ return buffer_length$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * int actual_length;
+ * {@snippet lang=c :
+ * int buffer_length
* }
*/
- public static int actual_length$get(MemorySegment seg) {
- return (int)constants$3.const$5.get(seg);
+ public static int buffer_length(MemorySegment struct) {
+ return struct.get(buffer_length$LAYOUT, buffer_length$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * int actual_length;
+ * {@snippet lang=c :
+ * int buffer_length
* }
*/
- public static void actual_length$set(MemorySegment seg, int x) {
- constants$3.const$5.set(seg, x);
+ public static void buffer_length(MemorySegment struct, int fieldValue) {
+ struct.set(buffer_length$LAYOUT, buffer_length$OFFSET, fieldValue);
}
- public static int actual_length$get(MemorySegment seg, long index) {
- return (int)constants$3.const$5.get(seg.asSlice(index*sizeof()));
- }
- public static void actual_length$set(MemorySegment seg, long index, int x) {
- constants$3.const$5.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt actual_length$LAYOUT = (OfInt)$LAYOUT.select(groupElement("actual_length"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * int actual_length
+ * }
+ */
+ public static final OfInt actual_length$layout() {
+ return actual_length$LAYOUT;
}
- public static VarHandle start_frame$VH() {
- return constants$4.const$0;
+
+ private static final long actual_length$OFFSET = $LAYOUT.byteOffset(groupElement("actual_length"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * int actual_length
+ * }
+ */
+ public static final long actual_length$offset() {
+ return actual_length$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * int start_frame;
+ * {@snippet lang=c :
+ * int actual_length
* }
*/
- public static int start_frame$get(MemorySegment seg) {
- return (int)constants$4.const$0.get(seg);
+ public static int actual_length(MemorySegment struct) {
+ return struct.get(actual_length$LAYOUT, actual_length$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * int start_frame;
+ * {@snippet lang=c :
+ * int actual_length
* }
*/
- public static void start_frame$set(MemorySegment seg, int x) {
- constants$4.const$0.set(seg, x);
- }
- public static int start_frame$get(MemorySegment seg, long index) {
- return (int)constants$4.const$0.get(seg.asSlice(index*sizeof()));
+ public static void actual_length(MemorySegment struct, int fieldValue) {
+ struct.set(actual_length$LAYOUT, actual_length$OFFSET, fieldValue);
}
- public static void start_frame$set(MemorySegment seg, long index, int x) {
- constants$4.const$0.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt start_frame$LAYOUT = (OfInt)$LAYOUT.select(groupElement("start_frame"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * int start_frame
+ * }
+ */
+ public static final OfInt start_frame$layout() {
+ return start_frame$LAYOUT;
}
- public static VarHandle number_of_packets$VH() {
- return constants$4.const$1;
+
+ private static final long start_frame$OFFSET = $LAYOUT.byteOffset(groupElement("start_frame"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * int start_frame
+ * }
+ */
+ public static final long start_frame$offset() {
+ return start_frame$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * int number_of_packets;
+ * {@snippet lang=c :
+ * int start_frame
* }
*/
- public static int number_of_packets$get(MemorySegment seg) {
- return (int)constants$4.const$1.get(seg);
+ public static int start_frame(MemorySegment struct) {
+ return struct.get(start_frame$LAYOUT, start_frame$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * int number_of_packets;
+ * {@snippet lang=c :
+ * int start_frame
* }
*/
- public static void number_of_packets$set(MemorySegment seg, int x) {
- constants$4.const$1.set(seg, x);
+ public static void start_frame(MemorySegment struct, int fieldValue) {
+ struct.set(start_frame$LAYOUT, start_frame$OFFSET, fieldValue);
}
- public static int number_of_packets$get(MemorySegment seg, long index) {
- return (int)constants$4.const$1.get(seg.asSlice(index*sizeof()));
- }
- public static void number_of_packets$set(MemorySegment seg, long index, int x) {
- constants$4.const$1.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt error_count$LAYOUT = (OfInt)$LAYOUT.select(groupElement("error_count"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * int error_count
+ * }
+ */
+ public static final OfInt error_count$layout() {
+ return error_count$LAYOUT;
}
- public static VarHandle stream_id$VH() {
- return constants$4.const$2;
+
+ private static final long error_count$OFFSET = $LAYOUT.byteOffset(groupElement("error_count"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * int error_count
+ * }
+ */
+ public static final long error_count$offset() {
+ return error_count$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * unsigned int stream_id;
+ * {@snippet lang=c :
+ * int error_count
* }
*/
- public static int stream_id$get(MemorySegment seg) {
- return (int)constants$4.const$2.get(seg);
+ public static int error_count(MemorySegment struct) {
+ return struct.get(error_count$LAYOUT, error_count$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * unsigned int stream_id;
+ * {@snippet lang=c :
+ * int error_count
* }
*/
- public static void stream_id$set(MemorySegment seg, int x) {
- constants$4.const$2.set(seg, x);
- }
- public static int stream_id$get(MemorySegment seg, long index) {
- return (int)constants$4.const$2.get(seg.asSlice(index*sizeof()));
+ public static void error_count(MemorySegment struct, int fieldValue) {
+ struct.set(error_count$LAYOUT, error_count$OFFSET, fieldValue);
}
- public static void stream_id$set(MemorySegment seg, long index, int x) {
- constants$4.const$2.set(seg.asSlice(index*sizeof()), x);
+
+ private static final OfInt signr$LAYOUT = (OfInt)$LAYOUT.select(groupElement("signr"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * unsigned int signr
+ * }
+ */
+ public static final OfInt signr$layout() {
+ return signr$LAYOUT;
}
- public static VarHandle error_count$VH() {
- return constants$4.const$3;
+
+ private static final long signr$OFFSET = $LAYOUT.byteOffset(groupElement("signr"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * unsigned int signr
+ * }
+ */
+ public static final long signr$offset() {
+ return signr$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * int error_count;
+ * {@snippet lang=c :
+ * unsigned int signr
* }
*/
- public static int error_count$get(MemorySegment seg) {
- return (int)constants$4.const$3.get(seg);
+ public static int signr(MemorySegment struct) {
+ return struct.get(signr$LAYOUT, signr$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * int error_count;
+ * {@snippet lang=c :
+ * unsigned int signr
* }
*/
- public static void error_count$set(MemorySegment seg, int x) {
- constants$4.const$3.set(seg, x);
+ public static void signr(MemorySegment struct, int fieldValue) {
+ struct.set(signr$LAYOUT, signr$OFFSET, fieldValue);
}
- public static int error_count$get(MemorySegment seg, long index) {
- return (int)constants$4.const$3.get(seg.asSlice(index*sizeof()));
- }
- public static void error_count$set(MemorySegment seg, long index, int x) {
- constants$4.const$3.set(seg.asSlice(index*sizeof()), x);
+
+ private static final AddressLayout usercontext$LAYOUT = (AddressLayout)$LAYOUT.select(groupElement("usercontext"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * void *usercontext
+ * }
+ */
+ public static final AddressLayout usercontext$layout() {
+ return usercontext$LAYOUT;
}
- public static VarHandle signr$VH() {
- return constants$4.const$4;
+
+ private static final long usercontext$OFFSET = $LAYOUT.byteOffset(groupElement("usercontext"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * void *usercontext
+ * }
+ */
+ public static final long usercontext$offset() {
+ return usercontext$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * unsigned int signr;
+ * {@snippet lang=c :
+ * void *usercontext
* }
*/
- public static int signr$get(MemorySegment seg) {
- return (int)constants$4.const$4.get(seg);
+ public static MemorySegment usercontext(MemorySegment struct) {
+ return struct.get(usercontext$LAYOUT, usercontext$OFFSET);
}
+
/**
* Setter for field:
- * {@snippet :
- * unsigned int signr;
+ * {@snippet lang=c :
+ * void *usercontext
* }
*/
- public static void signr$set(MemorySegment seg, int x) {
- constants$4.const$4.set(seg, x);
- }
- public static int signr$get(MemorySegment seg, long index) {
- return (int)constants$4.const$4.get(seg.asSlice(index*sizeof()));
+ public static void usercontext(MemorySegment struct, MemorySegment fieldValue) {
+ struct.set(usercontext$LAYOUT, usercontext$OFFSET, fieldValue);
}
- public static void signr$set(MemorySegment seg, long index, int x) {
- constants$4.const$4.set(seg.asSlice(index*sizeof()), x);
+
+ private static final SequenceLayout iso_frame_desc$LAYOUT = (SequenceLayout)$LAYOUT.select(groupElement("iso_frame_desc"));
+
+ /**
+ * Layout for field:
+ * {@snippet lang=c :
+ * struct usbdevfs_iso_packet_desc iso_frame_desc[]
+ * }
+ */
+ public static final SequenceLayout iso_frame_desc$layout() {
+ return iso_frame_desc$LAYOUT;
}
- public static VarHandle usercontext$VH() {
- return constants$4.const$5;
+
+ private static final long iso_frame_desc$OFFSET = $LAYOUT.byteOffset(groupElement("iso_frame_desc"));
+
+ /**
+ * Offset for field:
+ * {@snippet lang=c :
+ * struct usbdevfs_iso_packet_desc iso_frame_desc[]
+ * }
+ */
+ public static final long iso_frame_desc$offset() {
+ return iso_frame_desc$OFFSET;
}
+
/**
* Getter for field:
- * {@snippet :
- * void* usercontext;
+ * {@snippet lang=c :
+ * struct usbdevfs_iso_packet_desc iso_frame_desc[]
* }
*/
- public static MemorySegment usercontext$get(MemorySegment seg) {
- return (java.lang.foreign.MemorySegment)constants$4.const$5.get(seg);
+ public static MemorySegment iso_frame_desc(MemorySegment struct) {
+ return struct.asSlice(iso_frame_desc$OFFSET, iso_frame_desc$LAYOUT.byteSize());
}
+
/**
* Setter for field:
- * {@snippet :
- * void* usercontext;
+ * {@snippet lang=c :
+ * struct usbdevfs_iso_packet_desc iso_frame_desc[]
* }
*/
- public static void usercontext$set(MemorySegment seg, MemorySegment x) {
- constants$4.const$5.set(seg, x);
+ public static void iso_frame_desc(MemorySegment struct, MemorySegment fieldValue) {
+ MemorySegment.copy(fieldValue, 0L, struct, iso_frame_desc$OFFSET, iso_frame_desc$LAYOUT.byteSize());
+ }
+
+ /**
+ * Obtains a slice of {@code arrayParam} which selects the array element at {@code index}.
+ * The returned segment has address {@code arrayParam.address() + index * layout().byteSize()}
+ */
+ public static MemorySegment asSlice(MemorySegment array, long index) {
+ return array.asSlice(layout().byteSize() * index);
+ }
+
+ /**
+ * The size (in bytes) of this struct
+ */
+ public static long sizeof() { return layout().byteSize(); }
+
+ /**
+ * Allocate a segment of size {@code layout().byteSize()} using {@code allocator}
+ */
+ public static MemorySegment allocate(SegmentAllocator allocator) {
+ return allocator.allocate(layout());
}
- public static MemorySegment usercontext$get(MemorySegment seg, long index) {
- return (java.lang.foreign.MemorySegment)constants$4.const$5.get(seg.asSlice(index*sizeof()));
+
+ /**
+ * Allocate an array of size {@code elementCount} using {@code allocator}.
+ * The returned segment has size {@code elementCount * layout().byteSize()}.
+ */
+ public static MemorySegment allocateArray(long elementCount, SegmentAllocator allocator) {
+ return allocator.allocate(MemoryLayout.sequenceLayout(elementCount, layout()));
}
- public static void usercontext$set(MemorySegment seg, long index, MemorySegment x) {
- constants$4.const$5.set(seg.asSlice(index*sizeof()), x);
+
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, Arena arena, Consumer cleanup) {
+ return reinterpret(addr, 1, arena, cleanup);
}
- public static long sizeof() { return $LAYOUT().byteSize(); }
- public static MemorySegment allocate(SegmentAllocator allocator) { return allocator.allocate($LAYOUT()); }
- public static MemorySegment allocateArray(long len, SegmentAllocator allocator) {
- return allocator.allocate(MemoryLayout.sequenceLayout(len, $LAYOUT()));
+
+ /**
+ * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any).
+ * The returned segment has size {@code elementCount * layout().byteSize()}
+ */
+ public static MemorySegment reinterpret(MemorySegment addr, long elementCount, Arena arena, Consumer cleanup) {
+ return addr.reinterpret(layout().byteSize() * elementCount, arena, cleanup);
}
- public static MemorySegment ofAddress(MemorySegment addr, Arena scope) { return RuntimeHelper.asArray(addr, $LAYOUT(), 1, scope); }
}
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevice_fs$shared.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevice_fs$shared.java
new file mode 100644
index 00000000..bdf645da
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevice_fs$shared.java
@@ -0,0 +1,63 @@
+// Generated by jextract
+
+package net.codecrete.usb.linux.gen.usbdevice_fs;
+
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class usbdevice_fs$shared {
+
+ usbdevice_fs$shared() {
+ // Should not be called directly
+ }
+
+ public static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) Linker.nativeLinker().canonicalLayouts().get("bool");
+ public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char");
+ public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) Linker.nativeLinker().canonicalLayouts().get("short");
+ public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int");
+ public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long long");
+ public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) Linker.nativeLinker().canonicalLayouts().get("float");
+ public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double");
+ public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*"))
+ .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR));
+ public static final ValueLayout.OfLong C_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long");
+
+ static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls");
+
+ static void traceDowncall(String name, Object... args) {
+ String traceArgs = Arrays.stream(args)
+ .map(Object::toString)
+ .collect(Collectors.joining(", "));
+ System.out.printf("%s(%s)\n", name, traceArgs);
+ }
+
+ static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
+ try {
+ return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
+ } catch (ReflectiveOperationException ex) {
+ throw new AssertionError(ex);
+ }
+ }
+
+ static MemoryLayout align(MemoryLayout layout, long align) {
+ return switch (layout) {
+ case PaddingLayout p -> p;
+ case ValueLayout v -> v.withByteAlignment(align);
+ case GroupLayout g -> {
+ MemoryLayout[] alignedMembers = g.memberLayouts().stream()
+ .map(m -> align(m, align)).toArray(MemoryLayout[]::new);
+ yield g instanceof StructLayout ?
+ MemoryLayout.structLayout(alignedMembers) : MemoryLayout.unionLayout(alignedMembers);
+ }
+ case SequenceLayout s -> MemoryLayout.sequenceLayout(s.elementCount(), align(s.elementLayout(), align));
+ };
+ }
+}
+
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevice_fs.java b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevice_fs.java
index 68d05fef..8521d71b 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevice_fs.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/linux/gen/usbdevice_fs/usbdevice_fs.java
@@ -2,59 +2,71 @@
package net.codecrete.usb.linux.gen.usbdevice_fs;
-import java.lang.foreign.AddressLayout;
+import java.lang.invoke.*;
+import java.lang.foreign.*;
+import java.nio.ByteOrder;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
import static java.lang.foreign.ValueLayout.*;
-public class usbdevice_fs {
-
- public static final OfByte C_CHAR = JAVA_BYTE;
- public static final OfShort C_SHORT = JAVA_SHORT;
- public static final OfInt C_INT = JAVA_INT;
- public static final OfLong C_LONG = JAVA_LONG;
- public static final OfLong C_LONG_LONG = JAVA_LONG;
- public static final OfFloat C_FLOAT = JAVA_FLOAT;
- public static final OfDouble C_DOUBLE = JAVA_DOUBLE;
- public static final AddressLayout C_POINTER = RuntimeHelper.POINTER;
+import static java.lang.foreign.MemoryLayout.PathElement.*;
+
+public class usbdevice_fs extends usbdevice_fs$shared {
+
+ usbdevice_fs() {
+ // Should not be called directly
+ }
+
+ static final Arena LIBRARY_ARENA = Arena.ofAuto();
+
+ static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup()
+ .or(Linker.nativeLinker().defaultLookup());
+
+ private static final int USBDEVFS_URB_TYPE_ISO = (int)0L;
/**
- * {@snippet :
+ * {@snippet lang=c :
* #define USBDEVFS_URB_TYPE_ISO 0
* }
*/
public static int USBDEVFS_URB_TYPE_ISO() {
- return (int)0L;
+ return USBDEVFS_URB_TYPE_ISO;
}
+ private static final int USBDEVFS_URB_TYPE_INTERRUPT = (int)1L;
/**
- * {@snippet :
+ * {@snippet lang=c :
* #define USBDEVFS_URB_TYPE_INTERRUPT 1
* }
*/
public static int USBDEVFS_URB_TYPE_INTERRUPT() {
- return (int)1L;
+ return USBDEVFS_URB_TYPE_INTERRUPT;
}
+ private static final int USBDEVFS_URB_TYPE_CONTROL = (int)2L;
/**
- * {@snippet :
+ * {@snippet lang=c :
* #define USBDEVFS_URB_TYPE_CONTROL 2
* }
*/
public static int USBDEVFS_URB_TYPE_CONTROL() {
- return (int)2L;
+ return USBDEVFS_URB_TYPE_CONTROL;
}
+ private static final int USBDEVFS_URB_TYPE_BULK = (int)3L;
/**
- * {@snippet :
+ * {@snippet lang=c :
* #define USBDEVFS_URB_TYPE_BULK 3
* }
*/
public static int USBDEVFS_URB_TYPE_BULK() {
- return (int)3L;
+ return USBDEVFS_URB_TYPE_BULK;
}
+ private static final int USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER = (int)2L;
/**
- * {@snippet :
+ * {@snippet lang=c :
* #define USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER 2
* }
*/
public static int USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER() {
- return (int)2L;
+ return USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER;
}
}
-
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/macos/CoreFoundationHelper.java b/java-does-usb/src/main/java/net/codecrete/usb/macos/CoreFoundationHelper.java
index 74459d50..206d7a63 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/macos/CoreFoundationHelper.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/macos/CoreFoundationHelper.java
@@ -35,10 +35,10 @@ private CoreFoundationHelper() {
static String stringFromCFStringRef(MemorySegment string, Arena arena) {
var strLen = CoreFoundation.CFStringGetLength(string);
- var buffer = arena.allocateArray(JAVA_CHAR, strLen);
+ var buffer = arena.allocate(JAVA_CHAR, strLen);
var range = CFRange.allocate(arena);
- CFRange.location$set(range, 0);
- CFRange.length$set(range, strLen);
+ CFRange.location(range, 0);
+ CFRange.length(range, strLen);
CoreFoundation.CFStringGetCharacters(string, range, buffer);
return new String(buffer.toArray(JAVA_CHAR));
}
@@ -56,7 +56,7 @@ static String stringFromCFStringRef(MemorySegment string, Arena arena) {
*/
static MemorySegment createCFStringRef(String string, SegmentAllocator allocator) {
var charArray = string.toCharArray();
- var chars = allocator.allocateArray(JAVA_CHAR, charArray.length);
+ var chars = allocator.allocate(JAVA_CHAR, charArray.length);
chars.copyFrom(MemorySegment.ofArray(charArray));
return CoreFoundation.CFStringCreateWithCharacters(NULL, chars, string.length());
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitHelper.java b/java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitHelper.java
index b508a015..08d35279 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitHelper.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitHelper.java
@@ -93,7 +93,7 @@ private IoKitHelper() {
* @return vtable
*/
static MemorySegment getVtable(MemorySegment self) {
- return (MemorySegment) vtable$VH.get(self);
+ return (MemorySegment) vtable$VH.get(self, 0);
}
/**
@@ -111,9 +111,9 @@ static MemorySegment getVtable(MemorySegment self) {
static MemorySegment getInterface(int service, MemorySegment pluginType, MemorySegment interfaceId) {
try (var arena = Arena.ofConfined()) {
// MemorySegment for holding IOCFPlugInInterface**
- var plugHolder = arena.allocate(ADDRESS, NULL);
+ var plugHolder = arena.allocate(ADDRESS);
// MemorySegment for holding score
- var score = arena.allocate(JAVA_INT, 0);
+ var score = arena.allocate(JAVA_INT);
var ret = IOKit.IOCreatePlugInInterfaceForService(service, pluginType, kIOCFPlugInInterfaceID, plugHolder
, score);
if (ret != 0)
@@ -123,9 +123,9 @@ static MemorySegment getInterface(int service, MemorySegment pluginType, MemoryS
// UUID bytes
var refiid = CoreFoundation.CFUUIDGetUUIDBytes(arena, interfaceId);
// MemorySegment for holding xxxInterface**
- var intfHolder = arena.allocate(ADDRESS, NULL);
- ret = IoKitUSB.QueryInterface(plug, refiid, intfHolder);
- IoKitUSB.Release(plug);
+ var intfHolder = arena.allocate(ADDRESS);
+ ret = IoKitUsb.QueryInterface(plug, refiid, intfHolder);
+ IoKitUsb.Release(plug);
if (ret != 0)
return null;
return dereference(intfHolder, COM_OBJECT);
@@ -152,7 +152,7 @@ static Integer getPropertyInt(int service, MemorySegment key, Arena arena) {
Integer result = null;
var type = CoreFoundation.CFGetTypeID(value);
if (type == CoreFoundation.CFNumberGetTypeID()) {
- var numberValue = arena.allocate(JAVA_INT, 0);
+ var numberValue = arena.allocate(JAVA_INT);
if (CoreFoundation.CFNumberGetValue(value, CoreFoundation.kCFNumberSInt32Type(), numberValue) != 0)
result = numberValue.get(JAVA_INT, 0);
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitUSB.java b/java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitUsb.java
similarity index 60%
rename from java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitUSB.java
rename to java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitUsb.java
index 2ba04f12..b1fd6fbe 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitUSB.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitUsb.java
@@ -10,7 +10,6 @@
import net.codecrete.usb.macos.gen.iokit.IOUSBDeviceStruct187;
import net.codecrete.usb.macos.gen.iokit.IOUSBInterfaceStruct190;
-import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import static net.codecrete.usb.macos.IoKitHelper.getVtable;
@@ -19,169 +18,169 @@
* Helper functions to call the virtual methods of IOKit USB interfaces.
*/
@SuppressWarnings({"java:S100", "java:S107", "UnusedReturnValue", "SameParameterValue"})
-class IoKitUSB {
+class IoKitUsb {
- private IoKitUSB() {
+ private IoKitUsb() {
}
// HRESULT (STDMETHODCALLTYPE *QueryInterface)(void *thisPointer, REFIID iid, LPVOID *ppv)
static int QueryInterface(MemorySegment self, MemorySegment iid, MemorySegment ppv) {
- return IOUSBDeviceStruct187.QueryInterface(getVtable(self), Arena.global()).apply(self, iid, ppv);
+ return IOUSBDeviceStruct187.QueryInterface.invoke(IOUSBDeviceStruct187.QueryInterface(getVtable(self)), self, iid, ppv);
}
// ULONG (STDMETHODCALLTYPE *AddRef)(void *thisPointer)
static int AddRef(MemorySegment self) {
- return IOUSBDeviceStruct187.AddRef(getVtable(self), Arena.global()).apply(self);
+ return IOUSBDeviceStruct187.AddRef.invoke(IOUSBDeviceStruct187.AddRef(getVtable(self)), self);
}
// ULONG (STDMETHODCALLTYPE *Release)(void *thisPointer)
static int Release(MemorySegment self) {
- return IOUSBDeviceStruct187.Release(getVtable(self), Arena.global()).apply(self);
+ return IOUSBDeviceStruct187.Release.invoke(IOUSBDeviceStruct187.Release(getVtable(self)), self);
}
// IOReturn (* CreateDeviceAsyncEventSource)(void* self, CFRunLoopSourceRef* source)
static int CreateDeviceAsyncEventSource(MemorySegment self, MemorySegment source) {
- return IOUSBDeviceStruct187.CreateDeviceAsyncEventSource(getVtable(self), Arena.global()).apply(self,
+ return IOUSBDeviceStruct187.CreateDeviceAsyncEventSource.invoke(IOUSBDeviceStruct187.CreateDeviceAsyncEventSource(getVtable(self)), self,
source);
}
// CFRunLoopSourceRef (* GetDeviceAsyncEventSource)(void* self)
static MemorySegment GetDeviceAsyncEventSource(MemorySegment self) {
- return IOUSBDeviceStruct187.GetDeviceAsyncEventSource(getVtable(self), Arena.global()).apply(self);
+ return IOUSBDeviceStruct187.GetDeviceAsyncEventSource.invoke(IOUSBDeviceStruct187.GetDeviceAsyncEventSource(getVtable(self)), self);
}
// IOReturn (*USBDeviceOpenSeize)(void *self)
static int USBDeviceOpenSeize(MemorySegment self) {
- return IOUSBDeviceStruct187.USBDeviceOpenSeize(getVtable(self), Arena.global()).apply(self);
+ return IOUSBDeviceStruct187.USBDeviceOpenSeize.invoke(IOUSBDeviceStruct187.USBDeviceOpenSeize(getVtable(self)), self);
}
// IOReturn (*USBDeviceClose)(void *self)
static int USBDeviceClose(MemorySegment self) {
- return IOUSBDeviceStruct187.USBDeviceClose(getVtable(self), Arena.global()).apply(self);
+ return IOUSBDeviceStruct187.USBDeviceClose.invoke(IOUSBDeviceStruct187.USBDeviceClose(getVtable(self)), self);
}
// IOReturn (* USBDeviceReEnumerate)(void* self, UInt32 options)
static int USBDeviceReEnumerate(MemorySegment self, int options) {
- return IOUSBDeviceStruct187.USBDeviceReEnumerate(getVtable(self), Arena.global()).apply(self, options);
+ return IOUSBDeviceStruct187.USBDeviceReEnumerate.invoke(IOUSBDeviceStruct187.USBDeviceReEnumerate(getVtable(self)), self, options);
}
// IOReturn (*GetConfigurationDescriptorPtr)(void *self, UInt8 configIndex, IOUSBConfigurationDescriptorPtr *desc)
static int GetConfigurationDescriptorPtr(MemorySegment self, byte configIndex, MemorySegment descHolder) {
- return IOUSBDeviceStruct187.GetConfigurationDescriptorPtr(getVtable(self), Arena.global()).apply(self,
+ return IOUSBDeviceStruct187.GetConfigurationDescriptorPtr.invoke(IOUSBDeviceStruct187.GetConfigurationDescriptorPtr(getVtable(self)), self,
configIndex, descHolder);
}
// IOReturn (*SetConfiguration)(void *self, UInt8 configNum)
static int SetConfiguration(MemorySegment self, byte configValue) {
- return IOUSBDeviceStruct187.SetConfiguration(getVtable(self), Arena.global()).apply(self, configValue);
+ return IOUSBDeviceStruct187.SetConfiguration.invoke(IOUSBDeviceStruct187.SetConfiguration(getVtable(self)), self, configValue);
}
// IOReturn (*CreateInterfaceIterator)(void *self, IOUSBFindInterfaceRequest *req, io_iterator_t *iter)
static int CreateInterfaceIterator(MemorySegment self, MemorySegment req, MemorySegment iter) {
- return IOUSBDeviceStruct187.CreateInterfaceIterator(getVtable(self), Arena.global()).apply(self, req, iter);
+ return IOUSBDeviceStruct187.CreateInterfaceIterator.invoke(IOUSBDeviceStruct187.CreateInterfaceIterator(getVtable(self)), self, req, iter);
}
// IOReturn (* DeviceRequest)(void* self, IOUSBDevRequest* req)
static int DeviceRequest(MemorySegment self, MemorySegment deviceRequest) {
- return IOUSBDeviceStruct187.DeviceRequest(getVtable(self), Arena.global()).apply(self, deviceRequest);
+ return IOUSBDeviceStruct187.DeviceRequest.invoke(IOUSBDeviceStruct187.DeviceRequest(getVtable(self)), self, deviceRequest);
}
// IOReturn (* DeviceRequestAsync)(void* self, IOUSBDevRequest* req, IOAsyncCallback1 callback, void* refCon)
static int DeviceRequestAsync(MemorySegment self, MemorySegment deviceRequest, MemorySegment callback,
MemorySegment refCon) {
- return IOUSBDeviceStruct187.DeviceRequestAsync(getVtable(self), Arena.global()).apply(self, deviceRequest,
+ return IOUSBDeviceStruct187.DeviceRequestAsync.invoke(IOUSBDeviceStruct187.DeviceRequestAsync(getVtable(self)), self, deviceRequest,
callback, refCon);
}
// IOReturn (*USBInterfaceOpen)(void *self)
static int USBInterfaceOpen(MemorySegment self) {
- return IOUSBInterfaceStruct190.USBInterfaceOpen(getVtable(self), Arena.global()).apply(self);
+ return IOUSBInterfaceStruct190.USBInterfaceOpen.invoke(IOUSBInterfaceStruct190.USBInterfaceOpen(getVtable(self)), self);
}
// IOReturn (*USBInterfaceClose)(void *self)
static int USBInterfaceClose(MemorySegment self) {
- return IOUSBInterfaceStruct190.USBInterfaceClose(getVtable(self), Arena.global()).apply(self);
+ return IOUSBInterfaceStruct190.USBInterfaceClose.invoke(IOUSBInterfaceStruct190.USBInterfaceClose(getVtable(self)), self);
}
// IOReturn (*GetInterfaceNumber)(void *self, UInt8 *intfNumber)
static int GetInterfaceNumber(MemorySegment self, MemorySegment intfNumberHolder) {
- return IOUSBInterfaceStruct190.GetInterfaceNumber(getVtable(self), Arena.global()).apply(self,
+ return IOUSBInterfaceStruct190.GetInterfaceNumber.invoke(IOUSBInterfaceStruct190.GetInterfaceNumber(getVtable(self)), self,
intfNumberHolder);
}
// IOReturn (*GetNumEndpoints)(void *self, UInt8 *intfNumEndpoints)
static int GetNumEndpoints(MemorySegment self, MemorySegment intfNumEndpointsHolder) {
- return IOUSBInterfaceStruct190.GetNumEndpoints(getVtable(self), Arena.global()).apply(self,
+ return IOUSBInterfaceStruct190.GetNumEndpoints.invoke(IOUSBInterfaceStruct190.GetNumEndpoints(getVtable(self)), self,
intfNumEndpointsHolder);
}
// IOReturn (*GetPipeProperties)(void *self, UInt8 pipeRef, UInt8 *direction, UInt8 *number, UInt8 *transferType,
// UInt16 *maxPacketSize, UInt8 *interval)
static int GetPipeProperties(MemorySegment self, byte pipeRef, MemorySegment directionHolder,
- MemorySegment numberHolder, MemorySegment transferTypeHolder,
- MemorySegment maxPacketSizeHolder, MemorySegment intervalHolder) {
- return IOUSBInterfaceStruct190.GetPipeProperties(getVtable(self), Arena.global()).apply(self, pipeRef,
+ MemorySegment numberHolder, MemorySegment transferTypeHolder,
+ MemorySegment maxPacketSizeHolder, MemorySegment intervalHolder) {
+ return IOUSBInterfaceStruct190.GetPipeProperties.invoke(IOUSBInterfaceStruct190.GetPipeProperties(getVtable(self)), self, pipeRef,
directionHolder, numberHolder, transferTypeHolder, maxPacketSizeHolder, intervalHolder);
}
// IOReturn (*ReadPipeAsync)(void *self, UInt8 pipeRef, void *buf, UInt32 size, IOAsyncCallback1 callback, void
// *refcon)
static int ReadPipeAsync(MemorySegment self, byte pipeRef, MemorySegment buf, int size,
- MemorySegment callback, MemorySegment refcon) {
- return IOUSBInterfaceStruct190.ReadPipeAsync(getVtable(self), Arena.global()).apply(self, pipeRef, buf,
+ MemorySegment callback, MemorySegment refcon) {
+ return IOUSBInterfaceStruct190.ReadPipeAsync.invoke(IOUSBInterfaceStruct190.ReadPipeAsync(getVtable(self)), self, pipeRef, buf,
size, callback, refcon);
}
// IOReturn (*ReadPipeAsyncTO)(void *self, UInt8 pipeRef, void *buf, UInt32 size, UInt32 noDataTimeout, UInt32
// completionTimeout, IOAsyncCallback1 callback, void *refcon)
static int ReadPipeAsyncTO(MemorySegment self, byte pipeRef, MemorySegment buf, int size,
- int noDataTimeout, int completionTimeout, MemorySegment callback,
- MemorySegment refcon) {
- return IOUSBInterfaceStruct190.ReadPipeAsyncTO(getVtable(self), Arena.global()).apply(self, pipeRef, buf,
+ int noDataTimeout, int completionTimeout, MemorySegment callback,
+ MemorySegment refcon) {
+ return IOUSBInterfaceStruct190.ReadPipeAsyncTO.invoke(IOUSBInterfaceStruct190.ReadPipeAsyncTO(getVtable(self)), self, pipeRef, buf,
size, noDataTimeout, completionTimeout, callback, refcon);
}
// IOReturn (*WritePipeAsync)(vovoid *self, UInt8 pipeRef, void *buf, UInt32 size, IOAsyncCallback1 callback,
// void *refcon)
static int WritePipeAsync(MemorySegment self, byte pipeRef, MemorySegment buf, int size,
- MemorySegment callback, MemorySegment refcon) {
- return IOUSBInterfaceStruct190.WritePipeAsync(getVtable(self), Arena.global()).apply(self, pipeRef, buf,
+ MemorySegment callback, MemorySegment refcon) {
+ return IOUSBInterfaceStruct190.WritePipeAsync.invoke(IOUSBInterfaceStruct190.WritePipeAsync(getVtable(self)), self, pipeRef, buf,
size, callback, refcon);
}
// IOReturn (*WritePipeAsyncTO)(void *self, UInt8 pipeRef, void *buf, UInt32 size, UInt32 noDataTimeout, UInt32
// completionTimeout, IOAsyncCallback1 callback, void *refcon)
static int WritePipeAsyncTO(MemorySegment self, byte pipeRef, MemorySegment buf, int size,
- int noDataTimeout, int completionTimeout, MemorySegment callback,
- MemorySegment refcon) {
- return IOUSBInterfaceStruct190.WritePipeAsyncTO(getVtable(self), Arena.global()).apply(self, pipeRef, buf,
+ int noDataTimeout, int completionTimeout, MemorySegment callback,
+ MemorySegment refcon) {
+ return IOUSBInterfaceStruct190.WritePipeAsyncTO.invoke(IOUSBInterfaceStruct190.WritePipeAsyncTO(getVtable(self)), self, pipeRef, buf,
size, noDataTimeout, completionTimeout, callback, refcon);
}
// IOReturn (* AbortPipe)(void* self, UInt8 pipeRef)
static int AbortPipe(MemorySegment self, byte pipeRef) {
- return IOUSBInterfaceStruct190.AbortPipe(getVtable(self), Arena.global()).apply(self, pipeRef);
+ return IOUSBInterfaceStruct190.AbortPipe.invoke(IOUSBInterfaceStruct190.AbortPipe(getVtable(self)), self, pipeRef);
}
// IOReturn (*SetAlternateInterface)(void *self, UInt8 alternateSetting)
static int SetAlternateInterface(MemorySegment self, byte alternateSetting) {
- return IOUSBInterfaceStruct190.SetAlternateInterface(getVtable(self), Arena.global()).apply(self,
+ return IOUSBInterfaceStruct190.SetAlternateInterface.invoke(IOUSBInterfaceStruct190.SetAlternateInterface(getVtable(self)), self,
alternateSetting);
}
// IOReturn (* ClearPipeStallBothEnds)(void* self, UInt8 pipeRef)
static int ClearPipeStallBothEnds(MemorySegment self, byte pipeRef) {
- return IOUSBInterfaceStruct190.ClearPipeStallBothEnds(getVtable(self), Arena.global()).apply(self, pipeRef);
+ return IOUSBInterfaceStruct190.ClearPipeStallBothEnds.invoke(IOUSBInterfaceStruct190.ClearPipeStallBothEnds(getVtable(self)), self, pipeRef);
}
// CFRunLoopSourceRef (*GetInterfaceAsyncEventSource)(void* self)
static MemorySegment GetInterfaceAsyncEventSource(MemorySegment self) {
- return IOUSBInterfaceStruct190.GetInterfaceAsyncEventSource(getVtable(self), Arena.global()).apply(self);
+ return IOUSBInterfaceStruct190.GetInterfaceAsyncEventSource.invoke(IOUSBInterfaceStruct190.GetInterfaceAsyncEventSource(getVtable(self)), self);
}
// IOReturn (*CreateInterfaceAsyncEventSource)(void *self, CFRunLoopSourceRef *source)
static int CreateInterfaceAsyncEventSource(MemorySegment self, MemorySegment source) {
- return IOUSBInterfaceStruct190.CreateInterfaceAsyncEventSource(getVtable(self), Arena.global()).apply(self
+ return IOUSBInterfaceStruct190.CreateInterfaceAsyncEventSource.invoke(IOUSBInterfaceStruct190.CreateInterfaceAsyncEventSource(getVtable(self)), self
, source);
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/macos/MacosAsyncTask.java b/java-does-usb/src/main/java/net/codecrete/usb/macos/MacosAsyncTask.java
index 0ca192b5..b4b71b62 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/macos/MacosAsyncTask.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/macos/MacosAsyncTask.java
@@ -7,7 +7,8 @@
package net.codecrete.usb.macos;
-import net.codecrete.usb.USBException;
+import net.codecrete.usb.UsbException;
+import net.codecrete.usb.macos.gen.corefoundation.CFMessagePortCreateLocal$callout;
import net.codecrete.usb.macos.gen.corefoundation.CoreFoundation;
import net.codecrete.usb.macos.gen.iokit.IOKit;
@@ -22,8 +23,14 @@
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
+import static java.lang.System.Logger.Level.ERROR;
+import static java.lang.System.Logger.Level.WARNING;
+import static java.lang.foreign.MemorySegment.NULL;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_INT;
+import static java.lang.foreign.ValueLayout.JAVA_LONG;
+import static java.lang.foreign.ValueLayout.JAVA_LONG_UNALIGNED;
+
/**
* Background task for handling asynchronous transfers.
@@ -49,11 +56,14 @@ enum TaskState {
*/
static final MacosAsyncTask INSTANCE = new MacosAsyncTask();
+ private static final System.Logger LOG = System.getLogger(MacosAsyncTask.class.getName());
+
private final ReentrantLock asyncIoLock = new ReentrantLock();
private final Condition asyncIoReady = asyncIoLock.newCondition();
private TaskState state = TaskState.NOT_STARTED;
private MemorySegment asyncIoRunLoop;
private MemorySegment completionUpcallStub;
+ private MemorySegment messagePort;
private long lastTransferId;
private final Map transfersById = new HashMap<>();
@@ -67,18 +77,12 @@ void addEventSource(MemorySegment source) {
asyncIoLock.lock();
if (state != TaskState.RUNNING) {
- if (state == TaskState.NOT_STARTED) {
- startAsyncIOThread(source);
- waitForRunLoopReady();
- return;
-
- } else {
- // special case: run loop is not ready yet but background process is already starting
- waitForRunLoopReady();
- }
+ if (state == TaskState.NOT_STARTED)
+ startAsyncIOThread();
+ waitForRunLoopReady();
}
- CoreFoundation.CFRunLoopAddSource(asyncIoRunLoop, source, IOKit.kCFRunLoopDefaultMode$get());
+ CoreFoundation.CFRunLoopAddSource(asyncIoRunLoop, source, IOKit.kCFRunLoopDefaultMode());
} finally {
asyncIoLock.unlock();
@@ -92,34 +96,72 @@ private void waitForRunLoopReady() {
/**
* Removes an event source from this background task.
- *
+ *
+ * The event source is not immediately removed. Instead, it is posted to a message queue
+ * processed by the same background thread processing the completion callbacks. This ensures
+ * that the events from releasing interfaces and closing devices are processed.
+ *
* @param source event source
*/
void removeEventSource(MemorySegment source) {
- CoreFoundation.CFRunLoopRemoveSource(asyncIoRunLoop, source, IOKit.kCFRunLoopDefaultMode$get());
+ try (var arena = Arena.ofConfined()) {
+ var eventSourceRef = arena.allocate(JAVA_LONG, 1);
+ eventSourceRef.set(JAVA_LONG, 0, source.address());
+ var dataRef = CoreFoundation.CFDataCreate(NULL, eventSourceRef, eventSourceRef.byteSize());
+ CoreFoundation.CFMessagePortSendRequest(messagePort, 0, dataRef, 0, 0, NULL, NULL);
+ CoreFoundation.CFRelease(dataRef);
+ }
}
/**
* Starts the background thread.
- *
- * @param firstSource first event source
*/
- private void startAsyncIOThread(MemorySegment firstSource) {
+ @SuppressWarnings("java:S125")
+ private void startAsyncIOThread() {
+ MemorySegment messagePortSource = NULL;
+ MemorySegment localPort = NULL;
+
try {
state = TaskState.STARTING;
+
+ // create descriptor for completion callback function
var completionHandlerFuncDesc = FunctionDescriptor.ofVoid(ADDRESS, JAVA_INT, ADDRESS);
var asyncIOCompletedMH = MethodHandles.lookup().findVirtual(MacosAsyncTask.class, "asyncIOCompleted",
MethodType.methodType(void.class, MemorySegment.class, int.class, MemorySegment.class));
-
var methodHandle = asyncIOCompletedMH.bindTo(this);
- completionUpcallStub = Linker.nativeLinker().upcallStub(methodHandle, completionHandlerFuncDesc,
- Arena.global());
+ completionUpcallStub = Linker.nativeLinker().upcallStub(methodHandle, completionHandlerFuncDesc, Arena.global());
+
+ // create local and remote message ports (all three CF creations can return NULL,
+ // e.g. if bootstrap port registration fails in a sandboxed process)
+ var pid = ProcessHandle.current().pid();
+ var portName = CoreFoundationHelper.createCFStringRef("net.codecrete.usb.macos.eventsource." + pid, Arena.global());
+ var messagePortCallback = CFMessagePortCreateLocal$callout.allocate(this::messagePortCallback, Arena.global());
+ localPort = CoreFoundation.CFMessagePortCreateLocal(NULL, portName, messagePortCallback, NULL, NULL);
+ if (localPort.address() == 0)
+ throw new UsbException("internal error (CFMessagePortCreateLocal failed)");
+ messagePortSource = CoreFoundation.CFMessagePortCreateRunLoopSource(NULL, localPort, 0);
+ if (messagePortSource.address() == 0)
+ throw new UsbException("internal error (CFMessagePortCreateRunLoopSource failed)");
+ var remotePort = CoreFoundation.CFMessagePortCreateRemote(NULL, portName);
+ if (remotePort.address() == 0)
+ throw new UsbException("internal error (CFMessagePortCreateRemote failed)");
+ messagePort = remotePort;
- } catch (IllegalAccessException | NoSuchMethodException e) {
- throw new USBException("internal error (creating method handle)", e);
+ } catch (Exception e) {
+ // release partially created ports and reset the state; otherwise the task would be
+ // stuck in STARTING and all later addEventSource() calls would wait forever
+ if (messagePortSource.address() != 0)
+ CoreFoundation.CFRelease(messagePortSource);
+ if (localPort.address() != 0)
+ CoreFoundation.CFRelease(localPort);
+ state = TaskState.NOT_STARTED;
+ if (e instanceof RuntimeException runtimeException)
+ throw runtimeException;
+ throw new UsbException("internal error (creating method handle)", e);
}
- var thread = new Thread(() -> asyncIOCompletionTask(firstSource), "USB async IO");
+ var source = messagePortSource;
+ var thread = new Thread(() -> asyncIOCompletionTask(source), "USB async IO");
thread.setDaemon(true);
thread.start();
}
@@ -137,7 +179,7 @@ private void asyncIOCompletionTask(MemorySegment firstSource) {
try {
asyncIoLock.lock();
asyncIoRunLoop = CoreFoundation.CFRunLoopGetCurrent();
- CoreFoundation.CFRunLoopAddSource(asyncIoRunLoop, firstSource, IOKit.kCFRunLoopDefaultMode$get());
+ CoreFoundation.CFRunLoopAddSource(asyncIoRunLoop, firstSource, IOKit.kCFRunLoopDefaultMode());
state = TaskState.RUNNING;
asyncIoReady.signalAll();
} finally {
@@ -146,6 +188,7 @@ private void asyncIOCompletionTask(MemorySegment firstSource) {
// loop forever
CoreFoundation.CFRunLoopRun();
+ LOG.log(WARNING, "unexpected end of CFRunLoopRun");
}
/**
@@ -164,6 +207,20 @@ synchronized void prepareForSubmission(MacosTransfer transfer) {
transfersById.put(lastTransferId, transfer);
}
+ /**
+ * Undoes the registration performed by {@link #prepareForSubmission(MacosTransfer)}.
+ *
+ * Must be called if the native submission of a prepared transfer fails. In that case,
+ * no completion callback will ever fire for the transfer, so its map entry would leak
+ * unless it is removed here.
+ *
+ *
+ * @param transfer transfer whose submission failed
+ */
+ synchronized void submissionFailed(MacosTransfer transfer) {
+ transfersById.remove(transfer.id());
+ }
+
/**
* Callback function called when an asynchronous transfer has completed.
*
@@ -174,14 +231,43 @@ synchronized void prepareForSubmission(MacosTransfer transfer) {
@SuppressWarnings("java:S1144")
private void asyncIOCompleted(MemorySegment refcon, int result, MemorySegment arg0) {
- MacosTransfer transfer;
- synchronized (this) {
- transfer = transfersById.remove(refcon.address());
+ try {
+ MacosTransfer transfer;
+ synchronized (this) {
+ transfer = transfersById.remove(refcon.address());
+ }
+
+ if (transfer == null) {
+ // A completion for an unknown transfer ID (e.g. a duplicate or spurious
+ // callback). Ignore it rather than dereferencing null.
+ LOG.log(WARNING, "Ignoring async IO completion for unknown transfer ID {0}", refcon.address());
+ return;
+ }
+
+ transfer.setResultCode(result);
+ transfer.setResultSize((int) arg0.address());
+ transfer.completion().completed(transfer);
+
+ } catch (Exception e) {
+ // This method is a native upcall running on the process-wide async IO thread.
+ // Any exception escaping into CFRunLoopRun() would kill that thread and hang
+ // all async transfers for the entire library, so nothing must escape here.
+ LOG.log(ERROR, "Unexpected exception while handling async IO completion", e);
}
+ }
- transfer.setResultCode(result);
- transfer.setResultSize((int) arg0.address());
- transfer.completion().completed(transfer);
+ /**
+ * Callback function called when a message is received on the message port.
+ *
+ * All messages are related to removing event sources. They just contain the run loop source reference.
+ *
* All read and write operations on endpoints are submitted through synchronized methods in order to control
* concurrency. If it wasn't controlled, the danger is that device and interface pointers are used, which have
@@ -42,8 +51,8 @@
* asynchronous transfer and waiting for the completion.
*
*/
-@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "java:S2160"})
-public class MacosUSBDevice extends USBDeviceImpl {
+@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "java:S2160", "java:S3077"})
+public class MacosUsbDevice extends UsbDeviceImpl {
private final MacosAsyncTask asyncTask;
// Native USB device interface (IOUSBDeviceInterface**)
@@ -51,13 +60,15 @@ public class MacosUSBDevice extends USBDeviceImpl {
// Currently selected configuration
private int configurationValue;
// Details about interfaces that have been claimed
- private List claimedInterfaces;
+ // (volatile: written under the device monitor, read unlocked via isOpened();
+ // the list contents are only accessed while holding the device monitor)
+ private volatile List claimedInterfaces;
// Details about endpoints of current alternate settings (for claimed interfaces)
private Map endpoints;
private final long discoveryTime;
- MacosUSBDevice(MemorySegment device, Object id, int vendorId, int productId) {
+ MacosUsbDevice(MemorySegment device, Object id, int vendorId, int productId) {
super(id, vendorId, productId);
discoveryTime = System.currentTimeMillis();
asyncTask = MacosAsyncTask.INSTANCE;
@@ -65,55 +76,58 @@ public class MacosUSBDevice extends USBDeviceImpl {
loadDescription(device);
this.device = device;
- IoKitUSB.AddRef(device);
+ IoKitUsb.AddRef(device);
}
@Override
- public void detachStandardDrivers() {
- if (isOpen())
- throwException("detachStandardDrivers() must not be called while the device is open");
- var ret = IoKitUSB.USBDeviceReEnumerate(device, IOKit.kUSBReEnumerateCaptureDeviceMask());
+ public synchronized void detachStandardDrivers() {
+ checkIsClosed("detachStandardDrivers() must not be called while the device is open");
+ var ret = IoKitUsb.USBDeviceReEnumerate(device, IOKit.kUSBReEnumerateCaptureDeviceMask());
if (ret != 0)
throwException(ret, "detaching standard drivers failed");
}
@Override
- public void attachStandardDrivers() {
- if (isOpen())
- throwException("attachStandardDrivers() must not be called while the device is open");
- var ret = IoKitUSB.USBDeviceReEnumerate(device, IOKit.kUSBReEnumerateReleaseDeviceMask());
+ public synchronized void attachStandardDrivers() {
+ checkIsClosed("attachStandardDrivers() must not be called while the device is open");
+ var ret = IoKitUsb.USBDeviceReEnumerate(device, IOKit.kUSBReEnumerateReleaseDeviceMask());
if (ret != 0)
throwException(ret, "attaching standard drivers failed");
}
@Override
- public boolean isOpen() {
+ public boolean isOpened() {
return claimedInterfaces != null;
}
- @SuppressWarnings("java:S2276")
+ @SuppressWarnings({"java:S2276", "java:S2142"})
@Override
public synchronized void open() {
- if (isOpen())
- throwException("device is already open");
+ checkIsClosed("device is already open");
// open device (several retries if device has just been connected/discovered)
var duration = System.currentTimeMillis() - discoveryTime;
var numTries = duration < 1000 ? 4 : 1;
var ret = 0;
+ // Defer interruption: keep a local flag instead of re-asserting the interrupt
+ // (which would make the remaining backoff sleeps throw immediately and defeat
+ // the retry delay). Re-assert once the retries are done.
+ var wasInterrupted = false;
while (numTries > 0) {
numTries -= 1;
- ret = IoKitUSB.USBDeviceOpenSeize(device);
+ ret = IoKitUsb.USBDeviceOpenSeize(device);
if (ret != IOKit.kIOReturnExclusiveAccess())
break;
// sleep and retry
try {
Thread.sleep(90);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
+ } catch (InterruptedException _) {
+ wasInterrupted = true;
}
}
+ if (wasInterrupted)
+ Thread.currentThread().interrupt();
if (ret != 0)
throwException(ret, "opening USB device failed");
@@ -121,7 +135,7 @@ public synchronized void open() {
addDeviceEventSource();
// set configuration
- ret = IoKitUSB.SetConfiguration(device, (byte) configurationValue);
+ ret = IoKitUsb.SetConfiguration(device, (byte) configurationValue);
if (ret != 0)
throwException(ret, "setting configuration failed");
@@ -130,28 +144,31 @@ public synchronized void open() {
@Override
public synchronized void close() {
- if (!isOpen())
+ if (!isOpened())
return;
for (var interfaceInfo : claimedInterfaces) {
- IoKitUSB.USBInterfaceClose(interfaceInfo.iokitInterface);
- IoKitUSB.Release(interfaceInfo.iokitInterface);
setClaimed(interfaceInfo.interfaceNumber, false);
+ var source = IoKitUsb.GetInterfaceAsyncEventSource(interfaceInfo.iokitInterface());
+ IoKitUsb.USBInterfaceClose(interfaceInfo.iokitInterface);
+ IoKitUsb.Release(interfaceInfo.iokitInterface);
+ if (source.address() != 0)
+ asyncTask.removeEventSource(source);
}
claimedInterfaces = null;
endpoints = null;
- var source = IoKitUSB.GetDeviceAsyncEventSource(device);
+ var source = IoKitUsb.GetDeviceAsyncEventSource(device);
+ IoKitUsb.USBDeviceClose(device);
if (source.address() != 0)
asyncTask.removeEventSource(source);
-
- IoKitUSB.USBDeviceClose(device);
}
- synchronized void closeFully() {
- close();
- IoKitUSB.Release(device);
+ @Override
+ protected synchronized void disconnect() {
+ super.disconnect();
+ IoKitUsb.Release(device);
device = null;
}
@@ -160,31 +177,36 @@ private void loadDescription(MemorySegment device) {
// retrieve device descriptor using synchronous control transfer
var data = arena.allocate(255);
- var deviceRequest = createDeviceRequest(arena, USBDirection.IN, new USBControlTransfer(
- USBRequestType.STANDARD,
- USBRecipient.DEVICE,
+ var deviceRequest = createDeviceRequest(arena, UsbDirection.IN, new UsbControlTransfer(
+ UsbRequestType.STANDARD,
+ UsbRecipient.DEVICE,
6, // get descriptor
Constants.DEVICE_DESCRIPTOR_TYPE << 8,
0
), data);
- var ret = IoKitUSB.DeviceRequest(device, deviceRequest);
+ var ret = IoKitUsb.DeviceRequest(device, deviceRequest);
if (ret != 0)
throwException(ret, "querying device descriptor failed");
- var len = IOUSBDevRequest.wLenDone$get(deviceRequest);
+ var len = IOUSBDevRequest.wLenDone(deviceRequest);
rawDeviceDescriptor = data.asSlice(0, len).toArray(JAVA_BYTE);
configurationValue = 0;
// retrieve information of first configuration
var descPtrHolder = arena.allocate(ADDRESS);
- ret = IoKitUSB.GetConfigurationDescriptorPtr(device, (byte) 0, descPtrHolder);
+ ret = IoKitUsb.GetConfigurationDescriptorPtr(device, (byte) 0, descPtrHolder);
if (ret != 0)
throwException(ret, "querying first configuration failed");
- var configDesc = dereference(descPtrHolder).reinterpret(999999);
- var configDescHeader = new ConfigurationDescriptor(configDesc);
- configDesc = configDesc.asSlice(0, configDescHeader.totalLength());
+ // read the descriptor header with a minimally sized view first, then resize to the
+ // total length the header reports (the kernel buffer was sized from the same field)
+ var headerSize = ConfigurationDescriptor.LAYOUT.byteSize();
+ var configDescHeader = new ConfigurationDescriptor(dereference(descPtrHolder).reinterpret(headerSize));
+ var totalLength = configDescHeader.totalLength();
+ if (totalLength < headerSize)
+ throwException("invalid configuration descriptor (wTotalLength: %d)", totalLength);
+ var configDesc = dereference(descPtrHolder).reinterpret(totalLength);
var configuration = setConfigurationDescriptor(configDesc);
configurationValue = 255 & configuration.configValue();
@@ -192,17 +214,17 @@ private void loadDescription(MemorySegment device) {
}
@SuppressWarnings("java:S135")
- private InterfaceInfo findInterface(int interfaceNumber) {
+ private InterfaceInfo findInterfaceInfo(int interfaceNumber) {
try (var arena = Arena.ofConfined(); var outerCleanup = new ScopeCleanup()) {
var request = IOUSBFindInterfaceRequest.allocate(arena);
- IOUSBFindInterfaceRequest.bInterfaceClass$set(request, (short) IOKit.kIOUSBFindInterfaceDontCare());
- IOUSBFindInterfaceRequest.bInterfaceSubClass$set(request, (short) IOKit.kIOUSBFindInterfaceDontCare());
- IOUSBFindInterfaceRequest.bInterfaceProtocol$set(request, (short) IOKit.kIOUSBFindInterfaceDontCare());
- IOUSBFindInterfaceRequest.bAlternateSetting$set(request, (short) IOKit.kIOUSBFindInterfaceDontCare());
+ IOUSBFindInterfaceRequest.bInterfaceClass(request, (short) IOKit.kIOUSBFindInterfaceDontCare());
+ IOUSBFindInterfaceRequest.bInterfaceSubClass(request, (short) IOKit.kIOUSBFindInterfaceDontCare());
+ IOUSBFindInterfaceRequest.bInterfaceProtocol(request, (short) IOKit.kIOUSBFindInterfaceDontCare());
+ IOUSBFindInterfaceRequest.bAlternateSetting(request, (short) IOKit.kIOUSBFindInterfaceDontCare());
var iterHolder = arena.allocate(JAVA_INT);
- var ret = IoKitUSB.CreateInterfaceIterator(device, request, iterHolder);
+ var ret = IoKitUsb.CreateInterfaceIterator(device, request, iterHolder);
if (ret != 0)
throwException("internal error (CreateInterfaceIterator)");
@@ -223,13 +245,13 @@ private InterfaceInfo findInterface(int interfaceNumber) {
if (intf == null)
continue;
- cleanup.add(() -> IoKitUSB.Release(intf));
+ cleanup.add(() -> IoKitUsb.Release(intf));
- IoKitUSB.GetInterfaceNumber(intf, intfNumberHolder);
+ IoKitUsb.GetInterfaceNumber(intf, intfNumberHolder);
if (intfNumberHolder.get(JAVA_INT, 0) != interfaceNumber)
continue;
- IoKitUSB.AddRef(intf);
+ IoKitUsb.AddRef(intf);
return new InterfaceInfo(intf, interfaceNumber);
}
}
@@ -245,14 +267,14 @@ public synchronized void claimInterface(int interfaceNumber) {
try (var cleanup = new ScopeCleanup()) {
- var interfaceInfo = findInterface(interfaceNumber);
- cleanup.add(() -> IoKitUSB.Release(interfaceInfo.iokitInterface()));
+ var interfaceInfo = findInterfaceInfo(interfaceNumber);
+ cleanup.add(() -> IoKitUsb.Release(interfaceInfo.iokitInterface()));
- var ret = IoKitUSB.USBInterfaceOpen(interfaceInfo.iokitInterface());
+ var ret = IoKitUsb.USBInterfaceOpen(interfaceInfo.iokitInterface());
if (ret != 0)
throwException(ret, "claiming interface failed");
- IoKitUSB.AddRef(interfaceInfo.iokitInterface());
+ IoKitUsb.AddRef(interfaceInfo.iokitInterface());
claimedInterfaces.add(interfaceInfo);
setClaimed(interfaceNumber, true);
addInterfaceEventSource(interfaceInfo);
@@ -268,14 +290,10 @@ public synchronized void selectAlternateSetting(int interfaceNumber, int alterna
// check alternate setting
var altSetting = intf.getAlternate(alternateNumber);
- if (altSetting == null)
- throwException("interface %d does not have an alternate interface setting %d", interfaceNumber,
- alternateNumber);
-
var intfInfo =
claimedInterfaces.stream().filter(interf -> interf.interfaceNumber() == interfaceNumber).findFirst().get();
- var ret = IoKitUSB.SetAlternateInterface(intfInfo.iokitInterface(), (byte) alternateNumber);
+ var ret = IoKitUsb.SetAlternateInterface(intfInfo.iokitInterface(), (byte) alternateNumber);
if (ret != 0)
throwException(ret, "setting alternate interface failed");
@@ -292,16 +310,16 @@ public synchronized void releaseInterface(int interfaceNumber) {
var interfaceInfo =
claimedInterfaces.stream().filter(info -> info.interfaceNumber == interfaceNumber).findFirst().get();
- var source = IoKitUSB.GetInterfaceAsyncEventSource(interfaceInfo.iokitInterface());
+ var source = IoKitUsb.GetInterfaceAsyncEventSource(interfaceInfo.iokitInterface());
if (source.address() != 0)
asyncTask.removeEventSource(source);
- var ret = IoKitUSB.USBInterfaceClose(interfaceInfo.iokitInterface());
+ var ret = IoKitUsb.USBInterfaceClose(interfaceInfo.iokitInterface());
if (ret != 0)
throwException(ret, "releasing interface failed");
claimedInterfaces.remove(interfaceInfo);
- IoKitUSB.Release(interfaceInfo.iokitInterface());
+ IoKitUsb.Release(interfaceInfo.iokitInterface());
setClaimed(interfaceNumber, false);
updateEndpointList();
@@ -329,14 +347,14 @@ private void updateEndpointList() {
var intf = interfaceInfo.iokitInterface();
var numEndpointsHolder = arena.allocate(JAVA_BYTE);
- var ret = IoKitUSB.GetNumEndpoints(intf, numEndpointsHolder);
+ var ret = IoKitUsb.GetNumEndpoints(intf, numEndpointsHolder);
if (ret != 0)
throwException(ret, "internal error (GetNumEndpoints)");
var numEndpoints = numEndpointsHolder.get(JAVA_BYTE, 0) & 255;
for (var pipeIndex = 1; pipeIndex <= numEndpoints; pipeIndex++) {
- ret = IoKitUSB.GetPipeProperties(intf, (byte) pipeIndex, directionHolder, numberHolder,
+ ret = IoKitUsb.GetPipeProperties(intf, (byte) pipeIndex, directionHolder, numberHolder,
transferTypeHolder, maxPacketSizeHolder, intervalHolder);
if (ret != 0)
throwException(ret, "internal error (GetPipeProperties)");
@@ -355,10 +373,10 @@ private void updateEndpointList() {
}
@SuppressWarnings("SameParameterValue")
- private synchronized EndpointInfo getEndpointInfo(int endpointNumber, USBDirection direction,
- USBTransferType transferType1, USBTransferType transferType2) {
+ private synchronized EndpointInfo getEndpointInfo(int endpointNumber, UsbDirection direction,
+ UsbTransferType transferType1, UsbTransferType transferType2) {
if (endpoints != null) {
- var endpointAddress = (byte) (endpointNumber | (direction == USBDirection.IN ? 0x80 : 0));
+ var endpointAddress = (byte) (endpointNumber | (direction == UsbDirection.IN ? 0x80 : 0));
var endpointInfo = endpoints.get(endpointAddress);
if (endpointInfo != null && (endpointInfo.transferType == transferType1 || endpointInfo.transferType == transferType2))
return endpointInfo;
@@ -376,32 +394,32 @@ private synchronized EndpointInfo getEndpointInfo(int endpointNumber, USBDirecti
throw new AssertionError("not reached");
}
- private static MemorySegment createDeviceRequest(Arena arena, USBDirection direction, USBControlTransfer setup,
+ private static MemorySegment createDeviceRequest(Arena arena, UsbDirection direction, UsbControlTransfer setup,
MemorySegment data) {
var deviceRequest = IOUSBDevRequest.allocate(arena);
var bmRequestType =
- (direction == USBDirection.IN ? 0x80 : 0x00) | (setup.requestType().ordinal() << 5) | setup.recipient().ordinal();
- IOUSBDevRequest.bmRequestType$set(deviceRequest, (byte) bmRequestType);
- IOUSBDevRequest.bRequest$set(deviceRequest, (byte) setup.request());
- IOUSBDevRequest.wValue$set(deviceRequest, (short) setup.value());
- IOUSBDevRequest.wIndex$set(deviceRequest, (short) setup.index());
- IOUSBDevRequest.wLength$set(deviceRequest, (short) data.byteSize());
- IOUSBDevRequest.pData$set(deviceRequest, data);
+ (direction == UsbDirection.IN ? 0x80 : 0x00) | (setup.requestType().ordinal() << 5) | setup.recipient().ordinal();
+ IOUSBDevRequest.bmRequestType(deviceRequest, (byte) bmRequestType);
+ IOUSBDevRequest.bRequest(deviceRequest, (byte) setup.request());
+ IOUSBDevRequest.wValue(deviceRequest, (short) setup.value());
+ IOUSBDevRequest.wIndex(deviceRequest, (short) setup.index());
+ IOUSBDevRequest.wLength(deviceRequest, (short) data.byteSize());
+ IOUSBDevRequest.pData(deviceRequest, data);
return deviceRequest;
}
@Override
- public byte[] controlTransferIn(USBControlTransfer setup, int length) {
+ public byte @NotNull [] controlTransferIn(@NotNull UsbControlTransfer setup, int length) {
try (var arena = Arena.ofConfined()) {
var data = arena.allocate(length);
- var deviceRequest = createDeviceRequest(arena, USBDirection.IN, setup, data);
+ var deviceRequest = createDeviceRequest(arena, UsbDirection.IN, setup, data);
var transfer = new MacosTransfer();
- transfer.setCompletion(USBDeviceImpl::onSyncTransferCompleted);
+ transfer.setCompletion(UsbDeviceImpl::onSyncTransferCompleted);
synchronized (transfer) {
submitControlTransfer(deviceRequest, transfer);
- waitForTransfer(transfer, 0, USBDirection.IN, 0);
+ waitForTransfer(transfer, 0, UsbDirection.IN, 0);
}
return data.asSlice(0, transfer.resultSize()).toArray(JAVA_BYTE);
@@ -409,83 +427,85 @@ public byte[] controlTransferIn(USBControlTransfer setup, int length) {
}
@Override
- public void controlTransferOut(USBControlTransfer setup, byte[] data) {
+ public void controlTransferOut(@NotNull UsbControlTransfer setup, byte[] data) {
try (var arena = Arena.ofConfined()) {
var dataLength = data != null ? data.length : 0;
var dataSegment = arena.allocate(dataLength);
if (dataLength > 0)
dataSegment.copyFrom(MemorySegment.ofArray(data));
- var deviceRequest = createDeviceRequest(arena, USBDirection.OUT, setup, dataSegment);
+ var deviceRequest = createDeviceRequest(arena, UsbDirection.OUT, setup, dataSegment);
var transfer = new MacosTransfer();
- transfer.setCompletion(USBDeviceImpl::onSyncTransferCompleted);
+ transfer.setCompletion(UsbDeviceImpl::onSyncTransferCompleted);
synchronized (transfer) {
submitControlTransfer(deviceRequest, transfer);
- waitForTransfer(transfer, 0, USBDirection.OUT, 0);
+ waitForTransfer(transfer, 0, UsbDirection.OUT, 0);
}
}
}
@Override
- public void transferOut(int endpointNumber, byte[] data, int offset, int length, int timeout) {
-
- var epInfo = getEndpointInfo(endpointNumber, USBDirection.OUT, USBTransferType.BULK,
- USBTransferType.INTERRUPT);
-
- try (var arena = Arena.ofConfined()) {
- var nativeData = arena.allocateArray(JAVA_BYTE, length);
- nativeData.copyFrom(MemorySegment.ofArray(data).asSlice(offset, length));
-
- var transfer = new MacosTransfer();
- transfer.setData(nativeData);
- transfer.setDataSize(length);
- transfer.setCompletion(USBDeviceImpl::onSyncTransferCompleted);
-
- synchronized (transfer) {
- if (timeout <= 0 || epInfo.transferType() == USBTransferType.BULK) {
- // no timeout or timeout handled by operating system
- submitTransferOut(endpointNumber, transfer, timeout);
- waitForTransfer(transfer, 0, USBDirection.OUT, endpointNumber);
-
- } else {
- // interrupt transfer with timeout
- submitTransferOut(endpointNumber, transfer, 0);
- waitForTransfer(transfer, timeout, USBDirection.OUT, endpointNumber);
- }
+ public void transferOut(int endpointNumber, byte @NotNull [] data, int offset, int length, int timeout) {
+
+ var epInfo = getEndpointInfo(endpointNumber, UsbDirection.OUT, UsbTransferType.BULK,
+ UsbTransferType.INTERRUPT);
+
+ // Auto arena: a transfer that times out may be abandoned (see UsbDeviceImpl.waitForTransfer),
+ // so the buffer must outlive a possible late completion instead of being freed deterministically.
+ var arena = Arena.ofAuto();
+ var nativeData = arena.allocate(JAVA_BYTE, length);
+ nativeData.copyFrom(MemorySegment.ofArray(data).asSlice(offset, length));
+
+ var transfer = new MacosTransfer();
+ transfer.setData(nativeData);
+ transfer.setDataSize(length);
+ transfer.setCompletion(UsbDeviceImpl::onSyncTransferCompleted);
+
+ synchronized (transfer) {
+ if (timeout <= 0 || epInfo.transferType() == UsbTransferType.BULK) {
+ // no timeout or timeout handled by operating system
+ submitTransferOut(endpointNumber, transfer, timeout);
+ waitForTransfer(transfer, 0, UsbDirection.OUT, endpointNumber);
+
+ } else {
+ // interrupt transfer with timeout
+ submitTransferOut(endpointNumber, transfer, 0);
+ waitForTransfer(transfer, timeout, UsbDirection.OUT, endpointNumber);
}
}
}
@Override
- public byte[] transferIn(int endpointNumber, int timeout) {
-
- var epInfo = getEndpointInfo(endpointNumber, USBDirection.IN, USBTransferType.BULK,
- USBTransferType.INTERRUPT);
-
- try (var arena = Arena.ofConfined()) {
- var nativeData = arena.allocateArray(JAVA_BYTE, epInfo.packetSize());
-
- var transfer = new MacosTransfer();
- transfer.setData(nativeData);
- transfer.setDataSize(epInfo.packetSize());
- transfer.setCompletion(USBDeviceImpl::onSyncTransferCompleted);
-
- synchronized (transfer) {
- if (timeout <= 0 || epInfo.transferType() == USBTransferType.BULK) {
- // no timeout, or timeout handled by operating system
- submitTransferIn(endpointNumber, transfer, timeout);
- waitForTransfer(transfer, 0, USBDirection.IN, endpointNumber);
-
- } else {
- // interrupt transfer with timeout
- submitTransferIn(endpointNumber, transfer, 0);
- waitForTransfer(transfer, timeout, USBDirection.IN, endpointNumber);
- }
+ public byte @NotNull [] transferIn(int endpointNumber, int timeout) {
+
+ var epInfo = getEndpointInfo(endpointNumber, UsbDirection.IN, UsbTransferType.BULK,
+ UsbTransferType.INTERRUPT);
+
+ // Auto arena: a transfer that times out may be abandoned (see UsbDeviceImpl.waitForTransfer),
+ // so the buffer must outlive a possible late completion instead of being freed deterministically.
+ var arena = Arena.ofAuto();
+ var nativeData = arena.allocate(JAVA_BYTE, epInfo.packetSize());
+
+ var transfer = new MacosTransfer();
+ transfer.setData(nativeData);
+ transfer.setDataSize(epInfo.packetSize());
+ transfer.setCompletion(UsbDeviceImpl::onSyncTransferCompleted);
+
+ synchronized (transfer) {
+ if (timeout <= 0 || epInfo.transferType() == UsbTransferType.BULK) {
+ // no timeout, or timeout handled by operating system
+ submitTransferIn(endpointNumber, transfer, timeout);
+ waitForTransfer(transfer, 0, UsbDirection.IN, endpointNumber);
+
+ } else {
+ // interrupt transfer with timeout
+ submitTransferIn(endpointNumber, transfer, 0);
+ waitForTransfer(transfer, timeout, UsbDirection.IN, endpointNumber);
}
-
- return nativeData.asSlice(0, transfer.resultSize()).toArray(JAVA_BYTE);
}
+
+ return nativeData.asSlice(0, transfer.resultSize()).toArray(JAVA_BYTE);
}
/**
@@ -500,22 +520,24 @@ public byte[] transferIn(int endpointNumber, int timeout) {
*/
synchronized void submitTransferIn(int endpointNumber, MacosTransfer transfer, int timeout) {
- var epInfo = getEndpointInfo(endpointNumber, USBDirection.IN, USBTransferType.BULK,
- USBTransferType.INTERRUPT);
+ var epInfo = getEndpointInfo(endpointNumber, UsbDirection.IN, UsbTransferType.BULK,
+ UsbTransferType.INTERRUPT);
asyncTask.prepareForSubmission(transfer);
// submit transfer
int ret;
if (timeout <= 0)
- ret = IoKitUSB.ReadPipeAsync(epInfo.iokitInterface(), epInfo.pipeIndex(), transfer.data(),
+ ret = IoKitUsb.ReadPipeAsync(epInfo.iokitInterface(), epInfo.pipeIndex(), transfer.data(),
transfer.dataSize(), asyncTask.nativeCompletionCallback(), MemorySegment.ofAddress(transfer.id()));
else
- ret = IoKitUSB.ReadPipeAsyncTO(epInfo.iokitInterface(), epInfo.pipeIndex(), transfer.data(),
+ ret = IoKitUsb.ReadPipeAsyncTO(epInfo.iokitInterface(), epInfo.pipeIndex(), transfer.data(),
transfer.dataSize(), timeout, timeout, asyncTask.nativeCompletionCallback(),
MemorySegment.ofAddress(transfer.id()));
- if (ret != 0)
+ if (ret != 0) {
+ asyncTask.submissionFailed(transfer);
throwException(ret, "error occurred while reading from endpoint %d", endpointNumber);
+ }
}
/**
@@ -530,22 +552,24 @@ synchronized void submitTransferIn(int endpointNumber, MacosTransfer transfer, i
*/
synchronized void submitTransferOut(int endpointNumber, MacosTransfer transfer, int timeout) {
- var epInfo = getEndpointInfo(endpointNumber, USBDirection.OUT, USBTransferType.BULK,
- USBTransferType.INTERRUPT);
+ var epInfo = getEndpointInfo(endpointNumber, UsbDirection.OUT, UsbTransferType.BULK,
+ UsbTransferType.INTERRUPT);
asyncTask.prepareForSubmission(transfer);
// submit transfer
int ret;
if (timeout <= 0)
- ret = IoKitUSB.WritePipeAsync(epInfo.iokitInterface(), epInfo.pipeIndex(), transfer.data(),
+ ret = IoKitUsb.WritePipeAsync(epInfo.iokitInterface(), epInfo.pipeIndex(), transfer.data(),
transfer.dataSize(), asyncTask.nativeCompletionCallback(), MemorySegment.ofAddress(transfer.id()));
else
- ret = IoKitUSB.WritePipeAsyncTO(epInfo.iokitInterface(), epInfo.pipeIndex(), transfer.data(),
+ ret = IoKitUsb.WritePipeAsyncTO(epInfo.iokitInterface(), epInfo.pipeIndex(), transfer.data(),
transfer.dataSize(), timeout, timeout, asyncTask.nativeCompletionCallback(),
MemorySegment.ofAddress(transfer.id()));
- if (ret != 0)
+ if (ret != 0) {
+ asyncTask.submissionFailed(transfer);
throwException(ret, "error occurred while transmitting to endpoint %d", endpointNumber);
+ }
}
/**
@@ -560,11 +584,13 @@ synchronized void submitControlTransfer(MemorySegment deviceRequest, MacosTransf
asyncTask.prepareForSubmission(transfer);
// submit transfer
- var ret = IoKitUSB.DeviceRequestAsync(device, deviceRequest, asyncTask.nativeCompletionCallback(),
+ var ret = IoKitUsb.DeviceRequestAsync(device, deviceRequest, asyncTask.nativeCompletionCallback(),
MemorySegment.ofAddress(transfer.id()));
- if (ret != 0)
+ if (ret != 0) {
+ asyncTask.submissionFailed(transfer);
throwException(ret, "control transfer failed");
+ }
}
@Override
@@ -573,37 +599,37 @@ protected Transfer createTransfer() {
}
@Override
- public void abortTransfers(USBDirection direction, int endpointNumber) {
- var epInfo = getEndpointInfo(endpointNumber, direction, USBTransferType.BULK,
- USBTransferType.INTERRUPT);
+ public synchronized void abortTransfers(UsbDirection direction, int endpointNumber) {
+ var epInfo = getEndpointInfo(endpointNumber, direction, UsbTransferType.BULK,
+ UsbTransferType.INTERRUPT);
- var ret = IoKitUSB.AbortPipe(epInfo.iokitInterface(), epInfo.pipeIndex());
+ var ret = IoKitUsb.AbortPipe(epInfo.iokitInterface(), epInfo.pipeIndex());
if (ret != 0)
throwException(ret, "aborting transfers failed");
}
@Override
- public void clearHalt(USBDirection direction, int endpointNumber) {
- var epInfo = getEndpointInfo(endpointNumber, direction, USBTransferType.BULK,
- USBTransferType.INTERRUPT);
+ public synchronized void clearHalt(UsbDirection direction, int endpointNumber) {
+ var epInfo = getEndpointInfo(endpointNumber, direction, UsbTransferType.BULK,
+ UsbTransferType.INTERRUPT);
- var ret = IoKitUSB.ClearPipeStallBothEnds(epInfo.iokitInterface(), epInfo.pipeIndex());
+ var ret = IoKitUsb.ClearPipeStallBothEnds(epInfo.iokitInterface(), epInfo.pipeIndex());
if (ret != 0)
throwException(ret, "clearing halt condition failed");
}
@Override
- public synchronized InputStream openInputStream(int endpointNumber, int bufferSize) {
+ public synchronized @NotNull InputStream openInputStream(int endpointNumber, int bufferSize) {
// check that endpoint number is valid
- getEndpointInfo(endpointNumber, USBDirection.IN, USBTransferType.BULK, null);
+ getEndpointInfo(endpointNumber, UsbDirection.IN, UsbTransferType.BULK, null);
return new MacosEndpointInputStream(this, endpointNumber, bufferSize);
}
@Override
- public synchronized OutputStream openOutputStream(int endpointNumber, int bufferSize) {
+ public synchronized @NotNull OutputStream openOutputStream(int endpointNumber, int bufferSize) {
// check that endpoint number is valid
- getEndpointInfo(endpointNumber, USBDirection.OUT, USBTransferType.BULK, null);
+ getEndpointInfo(endpointNumber, UsbDirection.OUT, UsbTransferType.BULK, null);
return new MacosEndpointOutputStream(this, endpointNumber, bufferSize);
}
@@ -613,11 +639,11 @@ protected void throwOSException(int errorCode, String message, Object... args) {
throwException(errorCode, message, args);
}
- private static USBTransferType getTransferType(byte macosTransferType) {
+ private static UsbTransferType getTransferType(byte macosTransferType) {
return switch (macosTransferType) {
- case 1 -> USBTransferType.ISOCHRONOUS;
- case 2 -> USBTransferType.BULK;
- case 3 -> USBTransferType.INTERRUPT;
+ case 1 -> UsbTransferType.ISOCHRONOUS;
+ case 2 -> UsbTransferType.BULK;
+ case 3 -> UsbTransferType.INTERRUPT;
default -> null;
};
}
@@ -625,7 +651,7 @@ private static USBTransferType getTransferType(byte macosTransferType) {
private synchronized void addDeviceEventSource() {
try (var innerArena = Arena.ofConfined()) {
var sourceHolder = innerArena.allocate(ADDRESS);
- var ret = IoKitUSB.CreateDeviceAsyncEventSource(device, sourceHolder);
+ var ret = IoKitUsb.CreateDeviceAsyncEventSource(device, sourceHolder);
if (ret != 0)
throwException(ret, "internal error (CreateDeviceAsyncEventSource)");
var source = dereference(sourceHolder);
@@ -636,7 +662,7 @@ private synchronized void addDeviceEventSource() {
private synchronized void addInterfaceEventSource(InterfaceInfo interfaceInfo) {
try (var innerArena = Arena.ofConfined()) {
var sourceHolder = innerArena.allocate(ADDRESS);
- var ret = IoKitUSB.CreateInterfaceAsyncEventSource(interfaceInfo.iokitInterface(), sourceHolder);
+ var ret = IoKitUsb.CreateInterfaceAsyncEventSource(interfaceInfo.iokitInterface(), sourceHolder);
if (ret != 0)
throwException(ret, "internal error (CreateInterfaceAsyncEventSource)");
var source = dereference(sourceHolder);
@@ -647,6 +673,6 @@ private synchronized void addInterfaceEventSource(InterfaceInfo interfaceInfo) {
record InterfaceInfo(MemorySegment iokitInterface, int interfaceNumber) {
}
- record EndpointInfo(MemorySegment iokitInterface, byte pipeIndex, USBTransferType transferType, int packetSize) {
+ record EndpointInfo(MemorySegment iokitInterface, byte pipeIndex, UsbTransferType transferType, int packetSize) {
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/macos/MacosUSBDeviceRegistry.java b/java-does-usb/src/main/java/net/codecrete/usb/macos/MacosUsbDeviceRegistry.java
similarity index 82%
rename from java-does-usb/src/main/java/net/codecrete/usb/macos/MacosUSBDeviceRegistry.java
rename to java-does-usb/src/main/java/net/codecrete/usb/macos/MacosUsbDeviceRegistry.java
index d2bb8c0a..b2126246 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/macos/MacosUSBDeviceRegistry.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/macos/MacosUsbDeviceRegistry.java
@@ -7,32 +7,34 @@
package net.codecrete.usb.macos;
-import net.codecrete.usb.USBDevice;
+import net.codecrete.usb.UsbDevice;
import net.codecrete.usb.common.ScopeCleanup;
-import net.codecrete.usb.common.USBDeviceRegistry;
+import net.codecrete.usb.common.UsbDeviceRegistry;
import net.codecrete.usb.macos.gen.corefoundation.CoreFoundation;
import net.codecrete.usb.macos.gen.iokit.IOKit;
+import net.codecrete.usb.macos.gen.iokit.IOServiceAddMatchingNotification$callback;
-import java.lang.foreign.*;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SegmentAllocator;
import java.util.ArrayList;
import java.util.function.Consumer;
import static java.lang.System.Logger.Level.INFO;
+import static java.lang.System.Logger.Level.WARNING;
import static java.lang.foreign.MemorySegment.NULL;
-import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.ValueLayout.JAVA_INT;
+import static java.lang.foreign.ValueLayout.JAVA_LONG;
import static net.codecrete.usb.macos.CoreFoundationHelper.createCFStringRef;
-import static net.codecrete.usb.macos.MacosUSBException.throwException;
+import static net.codecrete.usb.macos.MacosUsbException.throwException;
/**
* MacOS implementation of USB device registry.
*/
@SuppressWarnings("java:S116")
-public class MacosUSBDeviceRegistry extends USBDeviceRegistry {
+public class MacosUsbDeviceRegistry extends UsbDeviceRegistry {
- private static final System.Logger LOG = System.getLogger(MacosUSBDeviceRegistry.class.getName());
+ private static final System.Logger LOG = System.getLogger(MacosUsbDeviceRegistry.class.getName());
private static final MemorySegment KEY_ID_VENDOR;
private static final MemorySegment KEY_ID_PRODUCT;
@@ -74,27 +76,23 @@ protected void monitorDevices() {
try {
// setup run loop, run loop source and notification port
- var notifyPort = IOKit.IONotificationPortCreate(IOKit.kIOMasterPortDefault$get());
+ var notifyPort = IOKit.IONotificationPortCreate(IOKit.kIOMasterPortDefault());
var runLoopSource = IOKit.IONotificationPortGetRunLoopSource(notifyPort);
var runLoop = CoreFoundation.CFRunLoopGetCurrent();
- CoreFoundation.CFRunLoopAddSource(runLoop, runLoopSource, IOKit.kCFRunLoopDefaultMode$get());
+ CoreFoundation.CFRunLoopAddSource(runLoop, runLoopSource, IOKit.kCFRunLoopDefaultMode());
// setup notification for connected devices
- var onDeviceConnectedMH = MethodHandles.lookup().findVirtual(MacosUSBDeviceRegistry.class,
- "onDevicesConnected", MethodType.methodType(void.class, MemorySegment.class, int.class));
var deviceConnectedIter = setupNotification(arena, notifyPort, IOKit.kIOFirstMatchNotification(),
- onDeviceConnectedMH);
+ this::onDevicesConnected);
// iterate current devices in order to arm the notifications (and build initial device list)
- var deviceList = new ArrayList();
+ var deviceList = new ArrayList();
iterateDevices(deviceConnectedIter, device -> deviceList.add(device)); // NOSONAR
setInitialDeviceList(deviceList);
// setup notification for disconnected devices
- var onDeviceDisconnectedMH = MethodHandles.lookup().findVirtual(MacosUSBDeviceRegistry.class,
- "onDevicesDisconnected", MethodType.methodType(void.class, MemorySegment.class, int.class));
var deviceDisconnectedIter = setupNotification(arena, notifyPort, IOKit.kIOTerminatedNotification(),
- onDeviceDisconnectedMH);
+ this::onDevicesDisconnected);
// iterate current devices in order to arm the notifications
onDevicesDisconnected(NULL, deviceDisconnectedIter);
@@ -106,6 +104,7 @@ protected void monitorDevices() {
// loop forever
CoreFoundation.CFRunLoopRun();
+ LOG.log(WARNING, "unexpected end of CFRunLoopRun");
}
}
@@ -130,7 +129,7 @@ private void iterateDevices(int iterator, IOKitDeviceConsumer consumer) {
var device = IoKitHelper.getInterface(service, IoKitHelper.kIOUSBDeviceUserClientTypeID,
IoKitHelper.kIOUSBDeviceInterfaceID187);
if (device != null)
- cleanup.add(() -> IoKitUSB.Release(device));
+ cleanup.add(() -> IoKitUsb.Release(device));
// get entry ID (as unique ID)
var ret = IOKit.IORegistryEntryGetRegistryEntryID(service, entryIdHolder);
@@ -148,7 +147,7 @@ private void iterateDevices(int iterator, IOKitDeviceConsumer consumer) {
/**
* Calls the consumer for all devices produced by the iterator.
*
- * This method tries to create a {@link USBDevice} instance.
+ * This method tries to create a {@link UsbDevice} instance.
* If it fails, an information is printed, but the consumer is not called.
*
+ * Invalid string descriptors might be missing the header,
+ * have a descriptor type that is not a string descriptor,
+ * indicate an incorrect length or have incomplete UTF-16 code units.
+ *
+ * @return if this descriptor is valid
+ */
+ public boolean isValid() {
+ return descriptor.byteSize() >= 2
+ && descriptor.get(JAVA_BYTE, bDescriptorType$OFFSET) == 3
+ && length() == descriptor.byteSize()
+ && (descriptor.byteSize() & 1) == 0;
+ }
+
public int length() {
- return 0xff & (byte) bLength$VH.get(descriptor);
+ return 0xff & descriptor.get(JAVA_BYTE, bLength$OFFSET);
}
+ /**
+ * Returns the string of this string descriptor.
+ *
+ * Invalid UTF-16 code units are replaced with the Unicode replacement character.
+ * Trailing 0s (UTF-16 code unit with value 0) are truncated.
+ *
+ * @throws UsbException if the string descriptor is invalid
+ * @return the string value
+ */
public String string() {
- var chars = descriptor.asSlice(string$offset, length() - 2L).toArray(JAVA_CHAR);
- return new String(chars);
+ if (!isValid())
+ throw new UsbException("String descriptor is invalid");
+ var len = (int) (length() - 2L);
+ var bytes = descriptor.asSlice(string$OFFSET, len).toArray(JAVA_BYTE);
+
+ // truncate trailing 0s
+ while (len > 0 && bytes[len - 2] == 0 && bytes[len - 1] == 0)
+ len--;
+
+ return new String(bytes, 0, len, StandardCharsets.UTF_16LE);
}
// struct USBStringDescriptor {
@@ -47,6 +82,7 @@ public String string() {
JAVA_SHORT.withName("string")
);
- private static final VarHandle bLength$VH = LAYOUT.varHandle(groupElement("bLength"));
- private static final long string$offset = LAYOUT.byteOffset(groupElement("string")); // NOSONAR
+ private static final long bLength$OFFSET = 0;
+ private static final long bDescriptorType$OFFSET = 1;
+ private static final long string$OFFSET = 2;
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/CustomApis.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/CustomApis.java
new file mode 100644
index 00000000..34ecb727
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/windows/CustomApis.java
@@ -0,0 +1,58 @@
+package net.codecrete.usb.windows;
+
+import net.codecrete.usb.usbstandard.SetupPacket;
+
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SymbolLookup;
+import java.lang.invoke.MethodHandle;
+
+import static java.lang.foreign.ValueLayout.ADDRESS;
+import static java.lang.foreign.ValueLayout.JAVA_INT;
+
+@SuppressWarnings({"java:S100", "java:S101", "java:S112", "java:S117"})
+public class CustomApis {
+ private CustomApis() {
+ }
+
+ static {
+ System.loadLibrary("KERNEL32");
+ System.loadLibrary("WINUSB");
+ }
+
+ private static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup();
+ private static final Linker LINKER = Linker.nativeLinker();
+ private static final Linker.Option LAST_ERROR_STATE = Linker.Option.captureCallState("GetLastError");
+
+ // Custom implementation of WinUsb_ControlTransfer as FFM cannot deal with the
+ // WINUSB_SETUP_PACKET being passed by value as it uses unaligned fields.
+ // SetupPacket does not use unaligned fields.
+ private static class WinUsb_ControlTransfer$IMPL {
+ private static final FunctionDescriptor DESC = FunctionDescriptor.of(JAVA_INT, ADDRESS, SetupPacket.LAYOUT, ADDRESS, JAVA_INT, ADDRESS, ADDRESS);
+ private static final MethodHandle HANDLE = LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow("WinUsb_ControlTransfer"), DESC, LAST_ERROR_STATE);
+ }
+
+ public static int WinUsb_ControlTransfer(MemorySegment lastErrorState, MemorySegment InterfaceHandle, MemorySegment SetupPacket, MemorySegment Buffer, int BufferLength, MemorySegment LengthTransferred, MemorySegment Overlapped) {
+ try {
+ return (int) WinUsb_ControlTransfer$IMPL.HANDLE.invokeExact(lastErrorState, InterfaceHandle, SetupPacket, Buffer, BufferLength, LengthTransferred, Overlapped);
+ } catch (Throwable ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ // CloseHandle implementation without error state
+ private static class CloseHandle$IMPL {
+ private static final FunctionDescriptor DESC = FunctionDescriptor.of(JAVA_INT, ADDRESS);
+ private static final MethodHandle HANDLE = LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow("CloseHandle"), DESC);
+ }
+
+ public static int CloseHandle(MemorySegment hObject) {
+ try {
+ return (int) CloseHandle$IMPL.HANDLE.invokeExact(hObject);
+ } catch (Throwable ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/DeviceInfoSet.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/DeviceInfoSet.java
index 623168f5..e9d3bfb8 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/windows/DeviceInfoSet.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/windows/DeviceInfoSet.java
@@ -1,15 +1,10 @@
package net.codecrete.usb.windows;
import net.codecrete.usb.common.ScopeCleanup;
-import net.codecrete.usb.windows.gen.advapi32.Advapi32;
-import net.codecrete.usb.windows.gen.kernel32.Kernel32;
-import net.codecrete.usb.windows.gen.kernel32._GUID;
-import net.codecrete.usb.windows.gen.ole32.Ole32;
-import net.codecrete.usb.windows.gen.setupapi.SetupAPI;
-import net.codecrete.usb.windows.gen.setupapi._SP_DEVICE_INTERFACE_DATA;
-import net.codecrete.usb.windows.gen.setupapi._SP_DEVICE_INTERFACE_DETAIL_DATA_W;
-import net.codecrete.usb.windows.gen.setupapi._SP_DEVINFO_DATA;
-import net.codecrete.usb.windows.winsdk.SetupAPI2;
+import system.Guid;
+import windows.win32.devices.deviceanddriverinstallation.SP_DEVICE_INTERFACE_DATA;
+import windows.win32.devices.deviceanddriverinstallation.SP_DEVICE_INTERFACE_DETAIL_DATA_W;
+import windows.win32.devices.deviceanddriverinstallation.SP_DEVINFO_DATA;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
@@ -18,10 +13,38 @@
import static java.lang.foreign.MemorySegment.NULL;
import static java.lang.foreign.ValueLayout.JAVA_CHAR;
import static java.lang.foreign.ValueLayout.JAVA_INT;
-import static net.codecrete.usb.windows.DevicePropertyKey.Service;
+import static java.nio.charset.StandardCharsets.UTF_16LE;
import static net.codecrete.usb.windows.Win.allocateErrorState;
-import static net.codecrete.usb.windows.WindowsUSBException.throwException;
-import static net.codecrete.usb.windows.WindowsUSBException.throwLastError;
+import static net.codecrete.usb.windows.WindowsUsbException.throwException;
+import static net.codecrete.usb.windows.WindowsUsbException.throwLastError;
+import static windows.win32.devices.deviceanddriverinstallation.Apis.SetupDiDeleteDeviceInterfaceData;
+import static windows.win32.devices.deviceanddriverinstallation.Apis.SetupDiDestroyDeviceInfoList;
+import static windows.win32.devices.deviceanddriverinstallation.Apis.SetupDiEnumDeviceInfo;
+import static windows.win32.devices.deviceanddriverinstallation.Apis.SetupDiEnumDeviceInterfaces;
+import static windows.win32.devices.deviceanddriverinstallation.Apis.SetupDiGetClassDevsW;
+import static windows.win32.devices.deviceanddriverinstallation.Apis.SetupDiCreateDeviceInfoList;
+import static windows.win32.devices.deviceanddriverinstallation.Apis.SetupDiGetDeviceInterfaceDetailW;
+import static windows.win32.devices.deviceanddriverinstallation.Apis.SetupDiGetDevicePropertyW;
+import static windows.win32.devices.deviceanddriverinstallation.Apis.SetupDiOpenDevRegKey;
+import static windows.win32.devices.deviceanddriverinstallation.Apis.SetupDiOpenDeviceInfoW;
+import static windows.win32.devices.deviceanddriverinstallation.Apis.SetupDiOpenDeviceInterfaceW;
+import static windows.win32.devices.deviceanddriverinstallation.Constants.DIREG_DEV;
+import static windows.win32.devices.deviceanddriverinstallation.SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_DEVICEINTERFACE;
+import static windows.win32.devices.deviceanddriverinstallation.SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_PRESENT;
+import static windows.win32.devices.deviceanddriverinstallation.SETUP_DI_PROPERTY_CHANGE_SCOPE.DICS_FLAG_GLOBAL;
+import static windows.win32.devices.properties.Constants.DEVPKEY_Device_Service;
+import static windows.win32.devices.properties.DEVPROPTYPE.DEVPROP_TYPEMOD_LIST;
+import static windows.win32.devices.properties.DEVPROPTYPE.DEVPROP_TYPE_STRING;
+import static windows.win32.devices.properties.DEVPROPTYPE.DEVPROP_TYPE_UINT32;
+import static windows.win32.foundation.WIN32_ERROR.ERROR_FILE_NOT_FOUND;
+import static windows.win32.foundation.WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER;
+import static windows.win32.foundation.WIN32_ERROR.ERROR_MORE_DATA;
+import static windows.win32.foundation.WIN32_ERROR.ERROR_NOT_FOUND;
+import static windows.win32.foundation.WIN32_ERROR.ERROR_NO_MORE_ITEMS;
+import static windows.win32.system.com.Apis.CLSIDFromString;
+import static windows.win32.system.registry.Apis.RegCloseKey;
+import static windows.win32.system.registry.Apis.RegQueryValueExW;
+import static windows.win32.system.registry.REG_SAM_FLAGS.KEY_READ;
/**
* Device information set (of Windows Setup API).
@@ -35,12 +58,12 @@ public class DeviceInfoSet implements AutoCloseable {
@FunctionalInterface
interface InfoSetCreator {
- MemorySegment create(Arena arena, MemorySegment errorState);
+ long create(Arena arena, MemorySegment errorState);
}
private final Arena arena;
private final MemorySegment errorState;
- private final MemorySegment devInfoSet;
+ private final long devInfoSet;
private final MemorySegment devInfoData;
private MemorySegment devIntfData;
private int iterationIndex = -1;
@@ -60,9 +83,9 @@ interface InfoSetCreator {
*/
static DeviceInfoSet ofPresentDevices(MemorySegment interfaceGuid, String instanceId) {
return new DeviceInfoSet((arena, errorState) -> {
- var instanceIdSegment = instanceId != null ? Win.createSegmentFromString(instanceId, arena) : NULL;
- return SetupAPI2.SetupDiGetClassDevsW(interfaceGuid, instanceIdSegment, NULL,
- SetupAPI.DIGCF_PRESENT() | SetupAPI.DIGCF_DEVICEINTERFACE(), errorState);
+ var instanceIdSegment = instanceId != null ? arena.allocateFrom(instanceId, UTF_16LE) : NULL;
+ return SetupDiGetClassDevsW(errorState, interfaceGuid, instanceIdSegment, NULL,
+ DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
});
}
@@ -77,7 +100,12 @@ static DeviceInfoSet ofPresentDevices(MemorySegment interfaceGuid, String instan
*/
static DeviceInfoSet ofInstance(String instanceId) {
var devInfoSet = ofEmpty();
- devInfoSet.addInstanceId(instanceId);
+ try {
+ devInfoSet.addInstanceId(instanceId);
+ } catch (Exception t) {
+ devInfoSet.close();
+ throw t;
+ }
return devInfoSet;
}
@@ -92,7 +120,12 @@ static DeviceInfoSet ofInstance(String instanceId) {
*/
static DeviceInfoSet ofPath(String devicePath) {
var devInfoSet = ofEmpty();
- devInfoSet.addDevicePath(devicePath);
+ try {
+ devInfoSet.addDevicePath(devicePath);
+ } catch (Exception t) {
+ devInfoSet.close();
+ throw t;
+ }
return devInfoSet;
}
@@ -102,7 +135,7 @@ static DeviceInfoSet ofPath(String devicePath) {
* @return device info set
*/
private static DeviceInfoSet ofEmpty() {
- return new DeviceInfoSet((arena, errorState) -> SetupAPI2.SetupDiCreateDeviceInfoList(NULL, NULL, errorState));
+ return new DeviceInfoSet((_, errorState) -> SetupDiCreateDeviceInfoList(errorState, NULL, NULL));
}
private DeviceInfoSet(InfoSetCreator creator) {
@@ -115,8 +148,7 @@ private DeviceInfoSet(InfoSetCreator creator) {
throwLastError(errorState, "internal error (creating device info set)");
// allocate SP_DEVINFO_DATA (will receive device details)
- devInfoData = _SP_DEVINFO_DATA.allocate(arena);
- _SP_DEVINFO_DATA.cbSize$set(devInfoData, (int) _SP_DEVINFO_DATA.$LAYOUT().byteSize());
+ devInfoData = SP_DEVINFO_DATA.allocate(arena);
} catch (Exception e) {
arena.close();
@@ -127,14 +159,14 @@ private DeviceInfoSet(InfoSetCreator creator) {
@Override
public void close() {
if (devIntfData != null)
- SetupAPI.SetupDiDeleteDeviceInterfaceData(devInfoSet, devIntfData);
- SetupAPI.SetupDiDestroyDeviceInfoList(devInfoSet);
+ SetupDiDeleteDeviceInterfaceData(errorState, devInfoSet, devIntfData);
+ SetupDiDestroyDeviceInfoList(errorState, devInfoSet);
arena.close();
}
private void addInstanceId(String instanceId) {
- var instanceIdSegment = Win.createSegmentFromString(instanceId, arena);
- if (SetupAPI2.SetupDiOpenDeviceInfoW(devInfoSet, instanceIdSegment, NULL, 0, devInfoData, errorState) == 0)
+ var instanceIdSegment = arena.allocateFrom(instanceId, UTF_16LE);
+ if (SetupDiOpenDeviceInfoW(errorState, devInfoSet, instanceIdSegment, NULL, 0, devInfoData) == 0)
throwLastError(errorState, "internal error (SetupDiOpenDeviceInfoW)");
}
@@ -143,18 +175,16 @@ private void addDevicePath(String devicePath) {
throw new AssertionError("calling addDevice() multiple times is not implemented");
// load device information into dev info set
- var intfData = _SP_DEVICE_INTERFACE_DATA.allocate(arena);
- _SP_DEVICE_INTERFACE_DATA.cbSize$set(intfData, (int) intfData.byteSize());
- var devicePathSegment = Win.createSegmentFromString(devicePath, arena);
- if (SetupAPI2.SetupDiOpenDeviceInterfaceW(devInfoSet, devicePathSegment, 0, intfData, errorState) == 0)
+ var intfData = SP_DEVICE_INTERFACE_DATA.allocate(arena);
+ var devicePathSegment = arena.allocateFrom(devicePath, UTF_16LE);
+ if (SetupDiOpenDeviceInterfaceW(errorState, devInfoSet, devicePathSegment, 0, intfData) == 0)
throwLastError(errorState, "internal error (SetupDiOpenDeviceInterfaceW)");
devIntfData = intfData; // for later cleanup
- if (SetupAPI2.SetupDiGetDeviceInterfaceDetailW(devInfoSet, intfData, NULL, 0, NULL,
- devInfoData, errorState) == 0) {
+ if (SetupDiGetDeviceInterfaceDetailW(errorState, devInfoSet, intfData, NULL, 0, NULL, devInfoData) == 0) {
var err = Win.getLastError(errorState);
- if (err != Kernel32.ERROR_INSUFFICIENT_BUFFER())
+ if (err != ERROR_INSUFFICIENT_BUFFER)
throwException(err, "internal error (SetupDiGetDeviceInterfaceDetailW)");
}
}
@@ -166,9 +196,9 @@ private void addDevicePath(String devicePath) {
*/
boolean next() {
iterationIndex += 1;
- if (SetupAPI2.SetupDiEnumDeviceInfo(devInfoSet, iterationIndex, devInfoData, errorState) == 0) {
+ if (SetupDiEnumDeviceInfo(errorState, devInfoSet, iterationIndex, devInfoData) == 0) {
var err = Win.getLastError(errorState);
- if (err == Kernel32.ERROR_NO_MORE_ITEMS())
+ if (err == ERROR_NO_MORE_ITEMS)
return false;
throwLastError(errorState, "internal error (SetupDiEnumDeviceInfo)");
}
@@ -182,7 +212,7 @@ boolean next() {
* @return {@code true} if it is a composite device
*/
boolean isCompositeDevice() {
- var deviceService = getStringProperty(Service);
+ var deviceService = getStringProperty(DEVPKEY_Device_Service());
// usbccgp is the USB Generic Parent Driver used for composite devices
return "usbccgp".equalsIgnoreCase(deviceService);
@@ -202,14 +232,14 @@ String getDevicePathByGUID(String instanceId) {
for (var guid : guids) {
// check for class GUID
- var guidSegment = Win.createSegmentFromString(guid, arena);
- var clsid = _GUID.allocate(arena);
- if (Ole32.CLSIDFromString(guidSegment, clsid) != 0)
+ var guidSegment = arena.allocateFrom(guid, UTF_16LE);
+ var clsid = Guid.allocate(arena);
+ if (CLSIDFromString(guidSegment, clsid) != 0)
continue;
try {
return getDevicePath(instanceId, clsid);
- } catch (Exception e) {
+ } catch (Exception _) {
// ignore and try next one
}
}
@@ -228,26 +258,26 @@ private List findDeviceInterfaceGUIDs(Arena arena) {
try (var cleanup = new ScopeCleanup()) {
// open device registry key
- var regKey = SetupAPI2.SetupDiOpenDevRegKey(devInfoSet, devInfoData, SetupAPI.DICS_FLAG_GLOBAL(), 0,
- SetupAPI.DIREG_DEV(), Advapi32.KEY_READ(), errorState);
+ var regKey = SetupDiOpenDevRegKey(errorState, devInfoSet, devInfoData, DICS_FLAG_GLOBAL, 0,
+ DIREG_DEV, KEY_READ);
if (Win.isInvalidHandle(regKey))
throwLastError(errorState, "internal error (SetupDiOpenDevRegKey)");
- cleanup.add(() -> Advapi32.RegCloseKey(regKey));
+ cleanup.add(() -> RegCloseKey(regKey));
// read registry value (without buffer, to query length)
- var keyNameSegment = Win.createSegmentFromString("DeviceInterfaceGUIDs", arena);
+ var keyNameSegment = arena.allocateFrom("DeviceInterfaceGUIDs", UTF_16LE);
var valueTypeHolder = arena.allocate(JAVA_INT);
var valueSizeHolder = arena.allocate(JAVA_INT);
- var res = Advapi32.RegQueryValueExW(regKey, keyNameSegment, NULL, valueTypeHolder, NULL, valueSizeHolder);
- if (res == Kernel32.ERROR_FILE_NOT_FOUND())
+ var res = RegQueryValueExW(regKey, keyNameSegment, NULL, valueTypeHolder, NULL, valueSizeHolder);
+ if (res == ERROR_FILE_NOT_FOUND)
return List.of(); // no device interface GUIDs
- if (res != 0 && res != Kernel32.ERROR_MORE_DATA())
+ if (res != 0 && res != ERROR_MORE_DATA)
throwException(res, "internal error (RegQueryValueExW)");
// read registry value (with buffer)
var valueSize = valueSizeHolder.get(JAVA_INT, 0);
var value = arena.allocate(valueSize);
- res = Advapi32.RegQueryValueExW(regKey, keyNameSegment, NULL, valueTypeHolder, value, valueSizeHolder);
+ res = RegQueryValueExW(regKey, keyNameSegment, NULL, valueTypeHolder, value, valueSizeHolder);
if (res != 0)
throwException(res, "internal error (RegQueryValueExW)");
@@ -265,11 +295,11 @@ private List findDeviceInterfaceGUIDs(Arena arena) {
int getIntProperty(MemorySegment propertyKey) {
var propertyTypeHolder = arena.allocate(JAVA_INT);
var propertyValueHolder = arena.allocate(JAVA_INT);
- if (SetupAPI2.SetupDiGetDevicePropertyW(devInfoSet, devInfoData, propertyKey, propertyTypeHolder,
- propertyValueHolder, (int) propertyValueHolder.byteSize(), NULL, 0, errorState) == 0)
+ if (SetupDiGetDevicePropertyW(errorState, devInfoSet, devInfoData, propertyKey, propertyTypeHolder,
+ propertyValueHolder, (int) propertyValueHolder.byteSize(), NULL, 0) == 0)
throwLastError(errorState, "internal error (SetupDiGetDevicePropertyW - A)");
- if (propertyTypeHolder.get(JAVA_INT, 0) != SetupAPI.DEVPROP_TYPE_UINT32())
+ if (propertyTypeHolder.get(JAVA_INT, 0) != DEVPROP_TYPE_UINT32)
throwException("internal error (expected property type UINT32)");
return propertyValueHolder.get(JAVA_INT, 0);
@@ -282,10 +312,10 @@ int getIntProperty(MemorySegment propertyKey) {
* @return property value
*/
String getStringProperty(MemorySegment propertyKey) {
- var propertyValue = getVariableLengthProperty(propertyKey, SetupAPI.DEVPROP_TYPE_STRING(), arena);
+ var propertyValue = getVariableLengthProperty(propertyKey, DEVPROP_TYPE_STRING, arena);
if (propertyValue == null)
return null;
- return Win.createStringFromSegment(propertyValue);
+ return propertyValue.getString(0, UTF_16LE);
}
/**
@@ -297,7 +327,7 @@ String getStringProperty(MemorySegment propertyKey) {
@SuppressWarnings("java:S1168")
List getStringListProperty(MemorySegment propertyKey) {
var propertyValue = getVariableLengthProperty(propertyKey,
- SetupAPI.DEVPROP_TYPE_STRING() | SetupAPI.DEVPROP_TYPEMOD_LIST(), arena);
+ DEVPROP_TYPE_STRING | DEVPROP_TYPEMOD_LIST, arena);
if (propertyValue == null)
return null;
@@ -309,26 +339,26 @@ private MemorySegment getVariableLengthProperty(MemorySegment propertyKey, int p
// query length (thus no buffer)
var propertyTypeHolder = arena.allocate(JAVA_INT);
var requiredSizeHolder = arena.allocate(JAVA_INT);
- if (SetupAPI2.SetupDiGetDevicePropertyW(devInfoSet, devInfoData, propertyKey, propertyTypeHolder, NULL, 0,
- requiredSizeHolder, 0, errorState) == 0) {
+ if (SetupDiGetDevicePropertyW(errorState, devInfoSet, devInfoData, propertyKey, propertyTypeHolder, NULL, 0,
+ requiredSizeHolder, 0) == 0) {
var err = Win.getLastError(errorState);
- if (err == Kernel32.ERROR_NOT_FOUND())
+ if (err == ERROR_NOT_FOUND)
return null;
- if (err != Kernel32.ERROR_INSUFFICIENT_BUFFER())
+ if (err != ERROR_INSUFFICIENT_BUFFER)
throwException(err, "internal error (SetupDiGetDevicePropertyW - B)");
}
if (propertyTypeHolder.get(JAVA_INT, 0) != propertyType)
throwException("internal error (unexpected property type)");
- var stringLen = requiredSizeHolder.get(JAVA_INT, 0) / 2 - 1;
+ var stringLen = (requiredSizeHolder.get(JAVA_INT, 0) + 1) / 2;
// allocate buffer
- var propertyValueHolder = arena.allocateArray(JAVA_CHAR, stringLen + 1L);
+ var propertyValueHolder = arena.allocate(JAVA_CHAR, stringLen);
// get property value
- if (SetupAPI2.SetupDiGetDevicePropertyW(devInfoSet, devInfoData, propertyKey, propertyTypeHolder,
- propertyValueHolder, (int) propertyValueHolder.byteSize(), NULL, 0, errorState) == 0)
+ if (SetupDiGetDevicePropertyW(errorState, devInfoSet, devInfoData, propertyKey, propertyTypeHolder,
+ propertyValueHolder, (int) propertyValueHolder.byteSize(), NULL, 0) == 0)
throwLastError(errorState, "internal error (SetupDiGetDevicePropertyW - C)");
return propertyValueHolder;
@@ -342,29 +372,24 @@ private MemorySegment getVariableLengthProperty(MemorySegment propertyKey, int p
* @return the device path
*/
static String getDevicePath(String instanceId, MemorySegment interfaceGuid) {
- try (var arena = Arena.ofConfined();
- var deviceInfoSet = DeviceInfoSet.ofPresentDevices(interfaceGuid, instanceId)) {
-
- // retrieve first element of enumeration
- var errorState = allocateErrorState(arena);
- var devIntfData = _SP_DEVICE_INTERFACE_DATA.allocate(arena);
- _SP_DEVICE_INTERFACE_DATA.cbSize$set(devIntfData, (int) devIntfData.byteSize());
- if (SetupAPI2.SetupDiEnumDeviceInterfaces(deviceInfoSet.devInfoSet, NULL, interfaceGuid, 0, devIntfData,
- errorState) == 0)
- throwLastError(errorState, "internal error (SetupDiEnumDeviceInterfaces)");
-
- // get device path
- // (SP_DEVICE_INTERFACE_DETAIL_DATA_W is of variable length and requires a bigger allocation so
- // the device path fits)
- final var devicePathOffset = 4;
- var intfDetailData = arena.allocate(4L + 260 * 2);
- _SP_DEVICE_INTERFACE_DETAIL_DATA_W.cbSize$set(intfDetailData,
- (int) _SP_DEVICE_INTERFACE_DETAIL_DATA_W.sizeof());
- if (SetupAPI2.SetupDiGetDeviceInterfaceDetailW(deviceInfoSet.devInfoSet, devIntfData, intfDetailData,
- (int) intfDetailData.byteSize(), NULL, NULL, errorState) == 0)
- throwLastError(errorState, "Internal error (SetupDiGetDeviceInterfaceDetailW)");
-
- return Win.createStringFromSegment(intfDetailData.asSlice(devicePathOffset));
+ try (var deviceInfoSet = DeviceInfoSet.ofPresentDevices(interfaceGuid, instanceId)) {
+ return deviceInfoSet.getDevicePathForGuid(interfaceGuid);
}
}
+
+ private String getDevicePathForGuid(MemorySegment interfaceGuid) {
+ // retrieve first element of enumeration
+ devIntfData = SP_DEVICE_INTERFACE_DATA.allocate(arena);
+ if (SetupDiEnumDeviceInterfaces(errorState, devInfoSet, NULL, interfaceGuid, 0, devIntfData) == 0)
+ throwLastError(errorState, "internal error (SetupDiEnumDeviceInterfaces)");
+
+ // get device path
+ var intfDetailData = SP_DEVICE_INTERFACE_DETAIL_DATA_W.allocate(arena, 260);
+ if (SetupDiGetDeviceInterfaceDetailW(errorState, devInfoSet, devIntfData, intfDetailData,
+ (int) intfDetailData.byteSize(), NULL, NULL) == 0)
+ throwLastError(errorState, "Internal error (SetupDiGetDeviceInterfaceDetailW)");
+
+ var devicePath = SP_DEVICE_INTERFACE_DETAIL_DATA_W.DevicePath(intfDetailData);
+ return devicePath.getString(0, UTF_16LE);
+ }
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/DevicePropertyKey.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/DevicePropertyKey.java
deleted file mode 100644
index 72b97501..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/windows/DevicePropertyKey.java
+++ /dev/null
@@ -1,77 +0,0 @@
-//
-// Java Does USB
-// Copyright (c) 2022 Manuel Bleichenbacher
-// Licensed under MIT License
-// https://opensource.org/licenses/MIT
-//
-
-package net.codecrete.usb.windows;
-
-import net.codecrete.usb.windows.gen.setupapi._DEVPROPKEY;
-
-import java.lang.foreign.Arena;
-import java.lang.foreign.MemorySegment;
-
-/**
- * Device property keys (GUIDs)
- */
-class DevicePropertyKey {
-
- private DevicePropertyKey() {
- }
-
- /**
- * DEVPKEY_Device_Address
- */
- static final MemorySegment Address = createDEVPROPKEY(0xa45c254e, (short) 0xdf1c,
- (short) 0x4efd, (byte) 0x80, (byte) 0x20, (byte) 0x67, (byte) 0xd1, (byte) 0x46, (byte) 0xa8, (byte) 0x50
- , (byte) 0xe0, 30);
-
- /**
- * DEVPKEY_Device_InstanceId
- */
- static final MemorySegment InstanceId = createDEVPROPKEY(0x78c34fc8, (short) 0x104a,
- (short) 0x4aca, (byte) 0x9e, (byte) 0xa4, (byte) 0x52, (byte) 0x4d, (byte) 0x52, (byte) 0x99, (byte) 0x6e
- , (byte) 0x57, 256);
-
- /**
- * DEVPKEY_Device_Parent
- */
- static final MemorySegment Parent = createDEVPROPKEY(0x4340a6c5, (short) 0x93fa,
- (short) 0x4706, (byte) 0x97, (byte) 0x2c, (byte) 0x7b, (byte) 0x64, (byte) 0x80, (byte) 0x08, (byte) 0xa5
- , (byte) 0xa7, 8);
-
- /**
- * DEVPKEY_Device_Service
- */
- static final MemorySegment Service = createDEVPROPKEY(0xa45c254e, (short) 0xdf1c,
- (short) 0x4efd, (byte) 0x80, (byte) 0x20, (byte) 0x67, (byte) 0xd1, (byte) 0x46, (byte) 0xa8, (byte) 0x50
- , (byte) 0xe0, 6);
-
- /**
- * DEVPKEY_Device_Children
- */
- static final MemorySegment Children = createDEVPROPKEY(0x4340a6c5, (short) 0x93fa,
- (short) 0x4706, (byte) 0x97, (byte) 0x2c, (byte) 0x7b, (byte) 0x64, (byte) 0x80, (byte) 0x08, (byte) 0xa5
- , (byte) 0xa7, 9);
-
- /**
- * DEVPKEY_Device_HardwareIds
- */
- static final MemorySegment HardwareIds = createDEVPROPKEY(0xa45c254e, (short) 0xdf1c,
- (short) 0x4efd, (byte) 0x80, (byte) 0x20, (byte) 0x67, (byte) 0xd1, (byte) 0x46, (byte) 0xa8, (byte) 0x50
- , (byte) 0xe0, 3);
-
-
- @SuppressWarnings({"java:S107", "java:S117"})
- private static MemorySegment createDEVPROPKEY(int data1, short data2, short data3, byte data4_0, byte data4_1,
- byte data4_2, byte data4_3, byte data4_4, byte data4_5,
- byte data4_6, byte data4_7, int pid) {
- @SuppressWarnings("resource")
- var propKey = Arena.global().allocate(_DEVPROPKEY.$LAYOUT());
- Win.setGUID(_DEVPROPKEY.fmtid$slice(propKey), data1, data2, data3, data4_0, data4_1, data4_2, data4_3, data4_4
- , data4_5, data4_6, data4_7);
- _DEVPROPKEY.pid$set(propKey, pid);
- return propKey;
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/InterfaceHandle.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/InterfaceHandle.java
index 91cc9bd4..c3d56a74 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/windows/InterfaceHandle.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/windows/InterfaceHandle.java
@@ -13,21 +13,19 @@
* Handles for WinUSB devices and interfaces
*/
class InterfaceHandle {
+ InterfaceHandle(int interfaceNumber, int firstInterfaceNumber) {
+ this.interfaceNumber = interfaceNumber;
+ this.firstInterfaceNumber = firstInterfaceNumber;
+ }
+
/**
* The number of this interface.
*/
- int interfaceNumber;
+ final int interfaceNumber;
/**
* The number of the first interface in the same composite function.
*/
- int firstInterfaceNumber;
- /**
- * The device path.
- *
- * This is only set for the first interface in a composite function.
- *
- */
- String devicePath;
+ final int firstInterfaceNumber;
/**
* The file handle of the device.
*
@@ -39,7 +37,7 @@ class InterfaceHandle {
* The WinUSB handle of the interface.
*/
@SuppressWarnings("java:S1700")
- MemorySegment interfaceHandle;
+ MemorySegment winusbHandle;
/**
* Count indicating how many interface depend on the device being open.
*/
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/USBConstants.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/USBConstants.java
deleted file mode 100644
index c4e18577..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/windows/USBConstants.java
+++ /dev/null
@@ -1,32 +0,0 @@
-//
-// Java Does USB
-// Copyright (c) 2022 Manuel Bleichenbacher
-// Licensed under MIT License
-// https://opensource.org/licenses/MIT
-//
-
-package net.codecrete.usb.windows;
-
-import java.lang.foreign.MemorySegment;
-
-/**
- * USB constants (general ones and Windows specific ones)
- */
-@SuppressWarnings({"java:S125", "java:S1192", "java:S115", "java:S100"})
-class USBConstants {
-
- private USBConstants() {
- }
-
- static final byte USB_REQUEST_GET_DESCRIPTOR = 0x06;
-
- // A5DCBF10-6530-11D2-901F-00C04FB951ED
- static final MemorySegment GUID_DEVINTERFACE_USB_DEVICE = Win.createGUID(0xA5DCBF10, (short) 0x6530,
- (short) 0x11D2, (byte) 0x90, (byte) 0x1F, (byte) 0x00, (byte) 0xC0, (byte) 0x4F, (byte) 0xB9, (byte) 0x51
- , (byte) 0xED);
-
- // f18a0e88-c30c-11d0-8815-00a0c906bed8
- static final MemorySegment GUID_DEVINTERFACE_USB_HUB = Win.createGUID(0xf18a0e88, (short) 0xc30c,
- (short) 0x11d0, (byte) 0x88, (byte) 0x15, (byte) 0x00, (byte) 0xa0, (byte) 0xc9, (byte) 0x06, (byte) 0xbe
- , (byte) 0xd8);
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/Win.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/Win.java
index 1bea7e6d..6f5791bf 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/windows/Win.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/windows/Win.java
@@ -7,16 +7,17 @@
package net.codecrete.usb.windows;
-import net.codecrete.usb.windows.gen.kernel32._GUID;
-
-import java.lang.foreign.*;
+import java.lang.foreign.Arena;
+import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout.PathElement;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.StructLayout;
import java.lang.invoke.VarHandle;
import java.util.ArrayList;
import java.util.List;
-import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_CHAR;
+import static java.nio.charset.StandardCharsets.UTF_16LE;
/**
* Windows helpers.
@@ -45,7 +46,7 @@ static MemorySegment allocateErrorState(Arena arena) {
* @return the error code
*/
public static int getLastError(MemorySegment callState) {
- return (int) callState_GetLastError$VH.get(callState);
+ return (int) callState_GetLastError$VH.get(callState, 0);
}
/**
@@ -59,45 +60,19 @@ public static boolean isInvalidHandle(MemorySegment handle) {
}
/**
- * Creates a memory segment as a copy of a Java string.
- *
- * The memory segment contains a copy of the string (null-terminated, UTF-16/wide characters).
- *
- *
- * @param str the string to copy
- * @param arena the arena for the memory segment
- * @return the resulting memory segment
- */
- public static MemorySegment createSegmentFromString(String str, Arena arena) {
- // allocate segment (including space for terminating null)
- var segment = arena.allocateArray(ValueLayout.JAVA_CHAR, str.length() + 1L);
- // copy characters
- segment.copyFrom(MemorySegment.ofArray(str.toCharArray()));
- return segment;
- }
-
- /**
- * Creates a copy of the string in the memory segment.
- *
- * The string must be a null-terminated UTF-16 (wide character) string.
- *
+ * Checks if a Windows handle is invalid.
*
- * @param segment the memory segment
- * @return copied string
+ * @param handle Windows handle
+ * @return {@code true} if the handle is invalid, {@code false} otherwise
*/
- public static String createStringFromSegment(MemorySegment segment) {
- var len = 0;
- while (segment.get(JAVA_CHAR, len) != 0) {
- len += 2;
- }
-
- return new String(segment.asSlice(0, len).toArray(JAVA_CHAR));
+ public static boolean isInvalidHandle(long handle) {
+ return handle == -1L;
}
/**
* Creates a copy of the string list in the memory segment.
*
- * The string list a a series of null-terminated UTF-16 (wide character) strings.
+ * The string list is a series of null-terminated UTF-16 (wide character) strings.
* The list is terminated with yet another null character.
*
*
@@ -108,53 +83,10 @@ public static List createStringListFromSegment(MemorySegment segment) {
var stringList = new ArrayList();
var offset = 0;
while (segment.get(JAVA_CHAR, offset) != '\0') {
- var str = Win.createStringFromSegment(segment.asSlice(offset));
+ var str = segment.getString(offset, UTF_16LE);
offset += str.length() * 2 + 2;
stringList.add(str);
}
return stringList;
}
-
- /**
- * Creates a GUID in native memory.
- *
- * @param data1 Group 1 (4 bytes).
- * @param data2 Group 2 (2 bytes).
- * @param data3 Group 3 (2 bytes).
- * @param data4_0 Byte 0 of group 4
- * @param data4_1 Byte 1 of group 4
- * @param data4_2 Byte 2 of group 4
- * @param data4_3 Byte 3 of group 4
- * @param data4_4 Byte 4 of group 4
- * @param data4_5 Byte 5 of group 4
- * @param data4_6 Byte 6 of group 4
- * @param data4_7 Byte 7 of group 4
- * @return GUID as memory segment
- */
- @SuppressWarnings({"java:S117", "java:S107"})
- public static MemorySegment createGUID(int data1, short data2, short data3, byte data4_0, byte data4_1,
- byte data4_2, byte data4_3, byte data4_4, byte data4_5, byte data4_6,
- byte data4_7) {
- @SuppressWarnings("resource")
- var guid = Arena.global().allocate(_GUID.$LAYOUT());
- setGUID(guid, data1, data2, data3, data4_0, data4_1, data4_2, data4_3, data4_4, data4_5, data4_6, data4_7);
- return guid;
- }
-
- @SuppressWarnings({"java:S117", "java:S107"})
- public static void setGUID(MemorySegment guid, int data1, short data2, short data3, byte data4_0, byte data4_1,
- byte data4_2, byte data4_3, byte data4_4, byte data4_5, byte data4_6, byte data4_7) {
- _GUID.Data1$set(guid, data1);
- _GUID.Data2$set(guid, data2);
- _GUID.Data3$set(guid, data3);
- var data4 = _GUID.Data4$slice(guid);
- data4.set(JAVA_BYTE, 0, data4_0);
- data4.set(JAVA_BYTE, 1, data4_1);
- data4.set(JAVA_BYTE, 2, data4_2);
- data4.set(JAVA_BYTE, 3, data4_3);
- data4.set(JAVA_BYTE, 4, data4_4);
- data4.set(JAVA_BYTE, 5, data4_5);
- data4.set(JAVA_BYTE, 6, data4_6);
- data4.set(JAVA_BYTE, 7, data4_7);
- }
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsAsyncTask.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsAsyncTask.java
index 3a509650..fa31dad2 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsAsyncTask.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsAsyncTask.java
@@ -7,9 +7,8 @@
package net.codecrete.usb.windows;
-import net.codecrete.usb.windows.gen.kernel32.Kernel32;
-import net.codecrete.usb.windows.gen.kernel32._OVERLAPPED;
-import net.codecrete.usb.windows.winsdk.Kernel32B;
+import net.codecrete.usb.UsbException;
+import windows.win32.system.io.OVERLAPPED;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
@@ -18,10 +17,17 @@
import java.util.List;
import java.util.Map;
+import static java.lang.System.Logger.Level.ERROR;
import static java.lang.foreign.MemorySegment.NULL;
-import static java.lang.foreign.ValueLayout.*;
+import static java.lang.foreign.ValueLayout.ADDRESS;
+import static java.lang.foreign.ValueLayout.JAVA_INT;
+import static java.lang.foreign.ValueLayout.JAVA_LONG;
import static net.codecrete.usb.windows.Win.allocateErrorState;
-import static net.codecrete.usb.windows.WindowsUSBException.throwLastError;
+import static net.codecrete.usb.windows.WindowsUsbException.throwLastError;
+import static windows.win32.foundation.WIN32_ERROR.ERROR_OPERATION_ABORTED;
+import static windows.win32.system.io.Apis.CreateIoCompletionPort;
+import static windows.win32.system.io.Apis.GetQueuedCompletionStatus;
+import static windows.win32.system.threading.Constants.INFINITE;
/**
* Background task for handling asynchronous transfers.
@@ -41,6 +47,8 @@
@SuppressWarnings("java:S6548")
class WindowsAsyncTask {
+ private static final System.Logger LOG = System.getLogger(WindowsAsyncTask.class.getName());
+
/**
* Singleton instance of background task.
*/
@@ -59,33 +67,80 @@ class WindowsAsyncTask {
*/
private MemorySegment asyncIoCompletionPort = NULL;
+ /**
+ * Indicates that the background task has terminated due to an unrecoverable error.
+ */
+ private boolean taskTerminated;
+
/**
* Background task for handling asynchronous IO completions.
*/
+ @SuppressWarnings("java:S2189")
private void asyncCompletionTask() {
try (var arena = Arena.ofConfined()) {
- var overlappedHolder = arena.allocate(ADDRESS, NULL);
- var numBytesHolder = arena.allocate(JAVA_INT, 0);
- var completionKeyHolder = arena.allocate(JAVA_LONG, 0);
+ var overlappedHolder = arena.allocate(ADDRESS);
+ var numBytesHolder = arena.allocate(JAVA_INT);
+ var completionKeyHolder = arena.allocate(JAVA_LONG);
var errorState = allocateErrorState(arena);
while (true) {
- overlappedHolder.set(ADDRESS, 0, NULL);
- completionKeyHolder.set(JAVA_LONG, 0, 0);
+ try {
+ overlappedHolder.set(ADDRESS, 0, NULL);
+ completionKeyHolder.set(JAVA_LONG, 0, 0);
- var res = Kernel32B.GetQueuedCompletionStatus(asyncIoCompletionPort, numBytesHolder,
- completionKeyHolder, overlappedHolder, Kernel32.INFINITE(), errorState);
- var overlappedAddr = overlappedHolder.get(JAVA_LONG, 0);
+ var res = GetQueuedCompletionStatus(errorState, asyncIoCompletionPort, numBytesHolder,
+ completionKeyHolder, overlappedHolder, INFINITE);
+ var overlappedAddr = overlappedHolder.get(JAVA_LONG, 0);
- if (res == 0 && overlappedAddr == 0)
- throwLastError(errorState, "internal error (SetupDiGetDeviceInterfaceDetailW)");
+ // A null OVERLAPPED means no completion packet was dequeued (nothing posts
+ // packets without an OVERLAPPED): the completion port itself has failed,
+ // and no further completions will ever be delivered.
+ if (overlappedAddr == 0) {
+ var success = res != 0;
+ throwLastError(errorState, "internal error (GetQueuedCompletionStatus, success: %s)", success);
+ }
- if (overlappedAddr == 0)
- return; // registry closing?
+ completeTransfer(overlappedAddr);
+
+ } catch (Exception e) {
+ LOG.log(ERROR, "USB async IO thread failed and is terminating; "
+ + "all outstanding transfers will fail, and no further transfers are possible", e);
+ failAllPendingTransfers();
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Fails all outstanding transfers and marks this task as terminated.
+ *
+ * Called when the background task can no longer dispatch completions. Waiters blocked
+ * on the failed transfers wake up with an error result instead of hanging forever,
+ * and future submissions are rejected.
+ *
+ */
+ private void failAllPendingTransfers() {
+ List pendingTransfers;
+ synchronized (this) {
+ taskTerminated = true;
+ pendingTransfers = new ArrayList<>(requestsByOverlapped.values());
+ requestsByOverlapped.clear();
+ availableOverlappedStructs.clear();
+ for (var transfer : pendingTransfers) {
+ transfer.setResultCode(ERROR_OPERATION_ABORTED);
+ transfer.setResultSize(0);
+ transfer.setOverlapped(null);
+ }
+ }
- completeTransfer(overlappedAddr);
+ for (var transfer : pendingTransfers) {
+ try {
+ transfer.completion().completed(transfer);
+ } catch (Exception e) {
+ LOG.log(ERROR, "Unexpected exception while handling async IO completion", e);
}
}
}
@@ -104,8 +159,8 @@ synchronized void addDevice(MemorySegment handle) {
var errorState = allocateErrorState(arena);
// Creates a new port if it doesn't exist; adds handle to existing port if it exists
- var portHandle = Kernel32B.CreateIoCompletionPort(handle, asyncIoCompletionPort,
- handle.address(), 0, errorState);
+ var portHandle = CreateIoCompletionPort(errorState, handle, asyncIoCompletionPort,
+ handle.address(), 0);
if (portHandle == MemorySegment.NULL)
throwLastError(errorState, "internal error (CreateIoCompletionPort)");
@@ -133,10 +188,14 @@ private void startAsyncIOTask() {
* @param transfer transfer to prepare
*/
synchronized void prepareForSubmission(WindowsTransfer transfer) {
+ if (taskTerminated)
+ throw new UsbException("USB async IO background thread has terminated due to an unrecoverable error; "
+ + "USB transfers are no longer possible");
+
MemorySegment overlapped;
var size = availableOverlappedStructs.size();
if (size == 0) {
- overlapped = _OVERLAPPED.allocate(overlappedArena);
+ overlapped = OVERLAPPED.allocate(overlappedArena);
} else {
overlapped = availableOverlappedStructs.remove(size - 1);
}
@@ -146,21 +205,54 @@ synchronized void prepareForSubmission(WindowsTransfer transfer) {
requestsByOverlapped.put(overlapped.address(), transfer);
}
+ /**
+ * Undoes the registration performed by {@link #prepareForSubmission(WindowsTransfer)}.
+ *
+ * Must be called if the native submission of a prepared transfer fails synchronously.
+ * In that case, no completion packet will ever be posted for the transfer, so its map
+ * entry would leak and the OVERLAPPED struct would never return to the pool unless
+ * they are cleaned up here.
+ *
+ *
+ * @param transfer transfer whose submission failed
+ */
+ synchronized void submissionFailed(WindowsTransfer transfer) {
+ requestsByOverlapped.remove(transfer.overlapped().address());
+ availableOverlappedStructs.add(transfer.overlapped());
+ transfer.setOverlapped(null);
+ }
+
/**
* Completes the transfer by calling the completion handler.
*
* @param overlappedAddr address of OVERLAPPED struct
*/
- private synchronized void completeTransfer(long overlappedAddr) {
- var transfer = requestsByOverlapped.remove(overlappedAddr);
- if (transfer == null)
- return;
+ private void completeTransfer(long overlappedAddr) {
+ WindowsTransfer transfer;
+ synchronized (this) {
+ transfer = requestsByOverlapped.remove(overlappedAddr);
+ if (transfer == null)
+ return;
- transfer.setResultCode((int) _OVERLAPPED.Internal$get(transfer.overlapped()));
- transfer.setResultSize((int) _OVERLAPPED.InternalHigh$get(transfer.overlapped()));
+ // the results must be read from the OVERLAPPED struct before it is
+ // returned to the pool and possibly reused by another submission
+ transfer.setResultCode((int) OVERLAPPED.Internal(transfer.overlapped()));
+ transfer.setResultSize((int) OVERLAPPED.InternalHigh(transfer.overlapped()));
- availableOverlappedStructs.add(transfer.overlapped());
- transfer.setOverlapped(null);
- transfer.completion().completed(transfer);
+ availableOverlappedStructs.add(transfer.overlapped());
+ transfer.setOverlapped(null);
+ }
+
+ // The completion handler must be called without holding the lock: handlers acquire
+ // other monitors (transfer, device), and threads submitting transfers acquire this
+ // task's lock while holding those monitors, so calling handlers under the lock can
+ // deadlock.
+ try {
+ transfer.completion().completed(transfer);
+ } catch (Exception e) {
+ // This method runs on the process-wide async IO thread. Any exception escaping
+ // would kill that thread and hang all async transfers for the entire library.
+ LOG.log(ERROR, "Unexpected exception while handling async IO completion", e);
+ }
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsEndpointInputStream.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsEndpointInputStream.java
index d331954e..0c96fc94 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsEndpointInputStream.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsEndpointInputStream.java
@@ -7,23 +7,23 @@
package net.codecrete.usb.windows;
-import net.codecrete.usb.USBDirection;
+import net.codecrete.usb.UsbDirection;
import net.codecrete.usb.common.EndpointInputStream;
import net.codecrete.usb.common.Transfer;
public class WindowsEndpointInputStream extends EndpointInputStream {
- WindowsEndpointInputStream(WindowsUSBDevice device, int endpointNumber, int bufferSize) {
+ WindowsEndpointInputStream(WindowsUsbDevice device, int endpointNumber, int bufferSize) {
super(device, endpointNumber, bufferSize);
}
@Override
protected void submitTransferIn(Transfer transfer) {
- ((WindowsUSBDevice) device).submitTransferIn(endpointNumber, (WindowsTransfer) transfer);
+ ((WindowsUsbDevice) device).submitTransferIn(endpointNumber, (WindowsTransfer) transfer);
}
@Override
protected void configureEndpoint() {
- ((WindowsUSBDevice) device).configureForAsyncIo(USBDirection.IN, endpointNumber);
+ ((WindowsUsbDevice) device).configureForAsyncIo(UsbDirection.IN, endpointNumber);
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsEndpointOutputStream.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsEndpointOutputStream.java
index 46eb0f31..8125dd5a 100644
--- a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsEndpointOutputStream.java
+++ b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsEndpointOutputStream.java
@@ -7,23 +7,23 @@
package net.codecrete.usb.windows;
-import net.codecrete.usb.USBDirection;
+import net.codecrete.usb.UsbDirection;
import net.codecrete.usb.common.EndpointOutputStream;
import net.codecrete.usb.common.Transfer;
public class WindowsEndpointOutputStream extends EndpointOutputStream {
- WindowsEndpointOutputStream(WindowsUSBDevice device, int endpointNumber, int bufferSize) {
+ WindowsEndpointOutputStream(WindowsUsbDevice device, int endpointNumber, int bufferSize) {
super(device, endpointNumber, bufferSize);
}
@Override
protected void submitTransferOut(Transfer request) {
- ((WindowsUSBDevice) device).submitTransferOut(endpointNumber, (WindowsTransfer) request);
+ ((WindowsUsbDevice) device).submitTransferOut(endpointNumber, (WindowsTransfer) request);
}
@Override
protected void configureEndpoint() {
- ((WindowsUSBDevice) device).configureForAsyncIo(USBDirection.OUT, endpointNumber);
+ ((WindowsUsbDevice) device).configureForAsyncIo(UsbDirection.OUT, endpointNumber);
}
}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUSBDevice.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUSBDevice.java
deleted file mode 100644
index ed7601fe..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUSBDevice.java
+++ /dev/null
@@ -1,474 +0,0 @@
-//
-// Java Does USB
-// Copyright (c) 2022 Manuel Bleichenbacher
-// Licensed under MIT License
-// https://opensource.org/licenses/MIT
-//
-
-package net.codecrete.usb.windows;
-
-import net.codecrete.usb.USBControlTransfer;
-import net.codecrete.usb.USBDirection;
-import net.codecrete.usb.USBRecipient;
-import net.codecrete.usb.USBTransferType;
-import net.codecrete.usb.common.Transfer;
-import net.codecrete.usb.common.USBDeviceImpl;
-import net.codecrete.usb.usbstandard.SetupPacket;
-import net.codecrete.usb.windows.gen.kernel32.Kernel32;
-import net.codecrete.usb.windows.gen.winusb.WinUSB;
-import net.codecrete.usb.windows.winsdk.Kernel32B;
-import net.codecrete.usb.windows.winsdk.WinUSB2;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.foreign.Arena;
-import java.lang.foreign.MemorySegment;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import static java.lang.foreign.MemorySegment.NULL;
-import static java.lang.foreign.ValueLayout.*;
-import static net.codecrete.usb.common.ForeignMemory.dereference;
-import static net.codecrete.usb.windows.Win.allocateErrorState;
-import static net.codecrete.usb.windows.WindowsUSBException.throwException;
-import static net.codecrete.usb.windows.WindowsUSBException.throwLastError;
-
-/**
- * Windows implementation for USB device.
- */
-@SuppressWarnings("java:S2160")
-public class WindowsUSBDevice extends USBDeviceImpl {
-
- private final WindowsAsyncTask asyncTask;
- private List interfaceHandles;
- /**
- * Indicates if {@link #open()} has been called. Since separate interfaces can have separate underlying
- * Windows device, {@link #claimInterface(int)} instead of {@link #open()} will open the Windows device.
- */
- private boolean showAsOpen;
-
- WindowsUSBDevice(String devicePath, Map children,
- int vendorId, int productId, MemorySegment configDesc) {
- super(devicePath, vendorId, productId);
- asyncTask = WindowsAsyncTask.INSTANCE;
- readDescription(configDesc, devicePath, children);
- }
-
- private void readDescription(MemorySegment configDesc, String devicePath, Map children) {
- var configuration = setConfigurationDescriptor(configDesc);
-
- // build list of interface handles
- interfaceHandles = new ArrayList<>();
- for (var intf : configuration.interfaces()) {
- var interfaceNumber = intf.number();
- var function = configuration.findFunction(interfaceNumber);
-
- var intfHandle = new InterfaceHandle();
- intfHandle.interfaceNumber = interfaceNumber;
- if (function.firstInterfaceNumber() == interfaceNumber) {
- if (children == null) {
- intfHandle.devicePath = devicePath;
- } else {
- intfHandle.devicePath = children.get(interfaceNumber);
- }
- }
- intfHandle.firstInterfaceNumber = function.firstInterfaceNumber();
- interfaceHandles.add(intfHandle);
- }
- }
-
- @Override
- public boolean isOpen() {
- return showAsOpen;
- }
-
- @Override
- public synchronized void open() {
- if (isOpen())
- throwException("device is already open");
-
- showAsOpen = true;
- }
-
- @Override
- public synchronized void close() {
- if (!isOpen())
- return;
-
- for (var intf : interfaceList) {
- if (intf.isClaimed())
- releaseInterface(intf.number());
- }
-
- showAsOpen = false;
- }
-
- public synchronized void claimInterface(int interfaceNumber) {
- checkIsOpen();
-
- var intfHandle = getInterfaceHandle(interfaceNumber);
- if (intfHandle.interfaceHandle != null)
- throwException("interface %d has already been claimed", interfaceNumber);
-
- var firstIntfHandle = intfHandle;
- if (intfHandle.firstInterfaceNumber != interfaceNumber)
- firstIntfHandle = getInterfaceHandle(intfHandle.firstInterfaceNumber);
-
- if (firstIntfHandle.devicePath == null)
- throwException("interface number %d cannot be claimed (non WinUSB device?)", interfaceNumber);
-
- try (var arena = Arena.ofConfined()) {
-
- MemorySegment deviceHandle;
- var errorState = allocateErrorState(arena);
-
- // open Windows device if needed
- if (firstIntfHandle.deviceHandle == null) {
- var pathSegment = Win.createSegmentFromString(firstIntfHandle.devicePath, arena);
- deviceHandle = Kernel32B.CreateFileW(pathSegment, Kernel32.GENERIC_WRITE() | Kernel32.GENERIC_READ(),
- Kernel32.FILE_SHARE_WRITE() | Kernel32.FILE_SHARE_READ(), NULL, Kernel32.OPEN_EXISTING(),
- Kernel32.FILE_ATTRIBUTE_NORMAL() | Kernel32.FILE_FLAG_OVERLAPPED(), NULL, errorState);
-
- if (Win.isInvalidHandle(deviceHandle))
- throwLastError(errorState, "opening USB device %s failed", firstIntfHandle.devicePath);
-
- asyncTask.addDevice(deviceHandle);
-
- } else {
- deviceHandle = firstIntfHandle.deviceHandle;
- }
-
- try {
- // open interface
- var interfaceHandleHolder = arena.allocate(ADDRESS);
- if (WinUSB2.WinUsb_Initialize(deviceHandle, interfaceHandleHolder, errorState) == 0)
- throwLastError(errorState, "opening WinUSB device failed");
- var interfaceHandle = dereference(interfaceHandleHolder);
-
- firstIntfHandle.deviceHandle = deviceHandle;
- firstIntfHandle.deviceOpenCount += 1;
- intfHandle.interfaceHandle = interfaceHandle;
-
- } catch (Exception e) {
- Kernel32.CloseHandle(deviceHandle);
- throw e;
- }
- }
-
- setClaimed(interfaceNumber, true);
- }
-
- @Override
- public synchronized void selectAlternateSetting(int interfaceNumber, int alternateNumber) {
- checkIsOpen();
-
- var intfHandle = getInterfaceHandle(interfaceNumber);
- if (intfHandle.interfaceHandle == null)
- throwException("interface %d has not been claimed", interfaceNumber);
-
- var intf = getInterface(interfaceNumber);
-
- // check alternate setting
- var altSetting = intf.getAlternate(alternateNumber);
- if (altSetting == null)
- throwException("interface %d does not have an alternate interface setting %d", interfaceNumber,
- alternateNumber);
-
- try (var arena = Arena.ofConfined()) {
- var errorState = allocateErrorState(arena);
- if (WinUSB2.WinUsb_SetCurrentAlternateSetting(intfHandle.interfaceHandle, (byte) alternateNumber,
- errorState) == 0)
- throwLastError(errorState, "setting alternate interface failed");
- }
- intf.setAlternate(altSetting);
- }
-
- public synchronized void releaseInterface(int interfaceNumber) {
- checkIsOpen();
-
- var intfHandle = getInterfaceHandle(interfaceNumber);
- if (intfHandle.interfaceHandle == null)
- throwException("interface %d has not been claimed", interfaceNumber);
-
- var firstIntfHandle = intfHandle;
- if (intfHandle.firstInterfaceNumber != interfaceNumber)
- firstIntfHandle = getInterfaceHandle(intfHandle.firstInterfaceNumber);
-
- // close interface
- WinUSB.WinUsb_Free(intfHandle.interfaceHandle);
- intfHandle.interfaceHandle = null;
-
- // close device
- firstIntfHandle.deviceOpenCount -= 1;
- if (firstIntfHandle.deviceOpenCount == 0) {
- Kernel32.CloseHandle(firstIntfHandle.deviceHandle);
- firstIntfHandle.deviceHandle = null;
- }
-
- setClaimed(interfaceNumber, false);
- }
-
- @Override
- public void controlTransferOut(USBControlTransfer setup, byte[] data) {
- try (var arena = Arena.ofConfined()) {
-
- // copy data to native memory
- var transfer = createSyncControlTransfer();
- var dataLength = data != null ? data.length : 0;
- transfer.setDataSize(dataLength);
- if (dataLength != 0) {
- var buffer = arena.allocate(data.length);
- buffer.copyFrom(MemorySegment.ofArray(data));
- transfer.setData(buffer);
- } else {
- transfer.setData(NULL);
- }
-
- synchronized (transfer) {
- submitControlTransfer(USBDirection.OUT, setup, transfer);
- waitForTransfer(transfer, 0, USBDirection.OUT, 0);
- }
- }
- }
-
- @Override
- public byte[] controlTransferIn(USBControlTransfer setup, int length) {
- try (var arena = Arena.ofConfined()) {
- var transfer = createSyncControlTransfer();
- transfer.setData(arena.allocate(length));
- transfer.setDataSize(length);
-
- synchronized (transfer) {
- submitControlTransfer(USBDirection.IN, setup, transfer);
- waitForTransfer(transfer, 0, USBDirection.IN, 0);
- }
-
- return transfer.data().asSlice(0, transfer.resultSize()).toArray(JAVA_BYTE);
- }
- }
-
- @Override
- public void transferOut(int endpointNumber, byte[] data, int offset, int length, int timeout) {
- try (var arena = Arena.ofConfined()) {
- var buffer = arena.allocate(data.length);
- buffer.copyFrom(MemorySegment.ofArray(data).asSlice(offset, length));
- var transfer = createSyncTransfer(buffer);
-
- synchronized (transfer) {
- submitTransferOut(endpointNumber, transfer);
- waitForTransfer(transfer, timeout, USBDirection.OUT, endpointNumber);
- }
- }
- }
-
- @Override
- public byte[] transferIn(int endpointNumber, int timeout) {
- var endpoint = getEndpoint(USBDirection.IN, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
-
- try (var arena = Arena.ofConfined()) {
- var buffer = arena.allocate(endpoint.packetSize());
- var transfer = createSyncTransfer(buffer);
-
- synchronized (transfer) {
- submitTransferIn(endpointNumber, transfer);
- waitForTransfer(transfer, timeout, USBDirection.IN, endpointNumber);
- }
-
- return buffer.asSlice(0, transfer.resultSize()).toArray(JAVA_BYTE);
- }
- }
-
- private WindowsTransfer createSyncControlTransfer() {
- var transfer = new WindowsTransfer();
- transfer.setCompletion(USBDeviceImpl::onSyncTransferCompleted);
- return transfer;
- }
-
- private WindowsTransfer createSyncTransfer(MemorySegment data) {
- var transfer = new WindowsTransfer();
- transfer.setData(data);
- transfer.setDataSize((int) data.byteSize());
- transfer.setCompletion(USBDeviceImpl::onSyncTransferCompleted);
- return transfer;
- }
-
- @Override
- protected Transfer createTransfer() {
- return new WindowsTransfer();
- }
-
- @Override
- protected void throwOSException(int errorCode, String message, Object... args) {
- throwException(errorCode, message, args);
- }
-
- synchronized void submitControlTransfer(USBDirection direction, USBControlTransfer setup, WindowsTransfer transfer) {
- checkIsOpen();
- var intfHandle = findControlTransferInterface(setup);
-
- try (var arena = Arena.ofConfined()) {
- var setupPacket = new SetupPacket(arena);
- var bmRequest =
- (direction == USBDirection.IN ? 0x80 : 0) | (setup.requestType().ordinal() << 5) | setup.recipient().ordinal();
- setupPacket.setRequestType(bmRequest);
- setupPacket.setRequest(setup.request());
- setupPacket.setValue(setup.value());
- setupPacket.setIndex(setup.index());
- setupPacket.setLength(transfer.dataSize());
-
- var errorState = allocateErrorState(arena);
- asyncTask.prepareForSubmission(transfer);
-
- // submit transfer
- if (WinUSB2.WinUsb_ControlTransfer(intfHandle.interfaceHandle, setupPacket.segment(), transfer.data(),
- transfer.dataSize(), NULL, transfer.overlapped(), errorState) == 0) {
- var err = Win.getLastError(errorState);
- if (err != Kernel32.ERROR_IO_PENDING())
- throwException(err, "submitting control transfer failed");
- }
- }
- }
-
- synchronized void submitTransferOut(int endpointNumber, WindowsTransfer transfer) {
- var endpoint = getEndpoint(USBDirection.OUT, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
- var intfHandle = getInterfaceHandle(endpoint.interfaceNumber());
-
- try (var arena = Arena.ofConfined()) {
- var errorState = allocateErrorState(arena);
- asyncTask.prepareForSubmission(transfer);
-
- // submit transfer
- if (WinUSB2.WinUsb_WritePipe(intfHandle.interfaceHandle, endpoint.endpointAddress(), transfer.data(),
- transfer.dataSize(), NULL, transfer.overlapped(), errorState) == 0) {
- var err = Win.getLastError(errorState);
- if (err != Kernel32.ERROR_IO_PENDING())
- throwException(err, "submitting transfer OUT failed");
- }
- }
- }
-
- synchronized void submitTransferIn(int endpointNumber, WindowsTransfer transfer) {
- var endpoint = getEndpoint(USBDirection.IN, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
- var intfHandle = getInterfaceHandle(endpoint.interfaceNumber());
-
- try (var arena = Arena.ofConfined()) {
- var errorState = allocateErrorState(arena);
- asyncTask.prepareForSubmission(transfer);
-
- // submit transfer
- if (WinUSB2.WinUsb_ReadPipe(intfHandle.interfaceHandle, endpoint.endpointAddress(), transfer.data(),
- transfer.dataSize(), NULL, transfer.overlapped(), errorState) == 0) {
- var err = Win.getLastError(errorState);
- if (err != Kernel32.ERROR_IO_PENDING())
- throwException(err, "submitting transfer IN failed");
- }
- }
- }
-
- synchronized void configureForAsyncIo(USBDirection direction, int endpointNumber) {
- var endpoint = getEndpoint(direction, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
- var intfHandle = getInterfaceHandle(endpoint.interfaceNumber());
-
- try (var arena = Arena.ofConfined()) {
- var errorState = allocateErrorState(arena);
-
- var timeoutHolder = arena.allocate(JAVA_INT, 0);
- if (WinUSB2.WinUsb_SetPipePolicy(intfHandle.interfaceHandle, endpoint.endpointAddress(),
- WinUSB.PIPE_TRANSFER_TIMEOUT(), (int) timeoutHolder.byteSize(), timeoutHolder, errorState) == 0)
- throwLastError(errorState, "setting timeout failed");
-
- var rawIoHolder = arena.allocate(JAVA_BYTE, (byte) 1);
- if (WinUSB2.WinUsb_SetPipePolicy(intfHandle.interfaceHandle, endpoint.endpointAddress(), WinUSB.RAW_IO(),
- (int) rawIoHolder.byteSize(), rawIoHolder, errorState) == 0)
- throwLastError(errorState, "setting raw IO failed");
- }
- }
-
- @Override
- public synchronized void clearHalt(USBDirection direction, int endpointNumber) {
- var endpoint = getEndpoint(direction, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
- var intfHandle = getInterfaceHandle(endpoint.interfaceNumber());
-
- try (var arena = Arena.ofConfined()) {
- var errorState = allocateErrorState(arena);
- if (WinUSB2.WinUsb_ResetPipe(intfHandle.interfaceHandle, endpoint.endpointAddress(), errorState) == 0)
- throwLastError(errorState, "clearing halt failed");
- }
- }
-
- @Override
- public synchronized void abortTransfers(USBDirection direction, int endpointNumber) {
- var endpoint = getEndpoint(direction, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
- var intfHandle = getInterfaceHandle(endpoint.interfaceNumber());
-
- try (var arena = Arena.ofConfined()) {
- var errorState = allocateErrorState(arena);
- if (WinUSB2.WinUsb_AbortPipe(intfHandle.interfaceHandle, endpoint.endpointAddress(), errorState) == 0)
- throwLastError(errorState, "aborting transfers on endpoint failed");
- }
- }
-
- @Override
- public synchronized InputStream openInputStream(int endpointNumber, int bufferSize) {
- // check that endpoint number is valid
- getEndpoint(USBDirection.IN, endpointNumber, USBTransferType.BULK, null);
-
- return new WindowsEndpointInputStream(this, endpointNumber, bufferSize);
- }
-
- @Override
- public synchronized OutputStream openOutputStream(int endpointNumber, int bufferSize) {
- // check that endpoint number is valid
- getEndpoint(USBDirection.OUT, endpointNumber, USBTransferType.BULK, null);
-
- return new WindowsEndpointOutputStream(this, endpointNumber, bufferSize);
- }
-
- private InterfaceHandle getInterfaceHandle(int interfaceNumber) {
- for (var intfHandle : interfaceHandles) {
- if (intfHandle.interfaceNumber == interfaceNumber)
- return intfHandle;
- }
-
- throwException("invalid interface number %s", interfaceNumber);
- throw new AssertionError("not reached");
- }
-
- private InterfaceHandle findControlTransferInterface(USBControlTransfer setup) {
-
- var interfaceNumber = -1;
- int endpointNumber;
-
- if (setup.recipient() == USBRecipient.INTERFACE) {
-
- interfaceNumber = setup.index() & 0xff;
-
- } else if (setup.recipient() == USBRecipient.ENDPOINT) {
-
- endpointNumber = setup.index() & 0x7f;
- var direction = (setup.index() & 0x80) != 0 ? USBDirection.IN : USBDirection.OUT;
- if (endpointNumber != 0) {
- interfaceNumber = getInterfaceNumber(direction, endpointNumber);
- if (interfaceNumber == -1)
- throwException("invalid endpoint number %d or interface not claimed", endpointNumber);
- }
- }
-
- if (interfaceNumber >= 0) {
- var intfHandle = getInterfaceHandle(interfaceNumber);
- if (intfHandle.interfaceHandle == null)
- throwException("interface number %d has not been claimed", interfaceNumber);
- return intfHandle;
- }
-
- // for control transfer to device, use any claimed interface
- for (var intfHandle : interfaceHandles) {
- if (intfHandle.interfaceHandle != null)
- return intfHandle;
- }
-
- throwException("control transfer cannot be executed as no interface has been claimed");
- throw new AssertionError("not reached");
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUSBDeviceRegistry.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUSBDeviceRegistry.java
deleted file mode 100644
index e9f05e69..00000000
--- a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUSBDeviceRegistry.java
+++ /dev/null
@@ -1,450 +0,0 @@
-//
-// Java Does USB
-// Copyright (c) 2022 Manuel Bleichenbacher
-// Licensed under MIT License
-// https://opensource.org/licenses/MIT
-//
-
-package net.codecrete.usb.windows;
-
-import net.codecrete.usb.USBDevice;
-import net.codecrete.usb.USBException;
-import net.codecrete.usb.common.ScopeCleanup;
-import net.codecrete.usb.common.USBDeviceImpl;
-import net.codecrete.usb.common.USBDeviceRegistry;
-import net.codecrete.usb.usbstandard.ConfigurationDescriptor;
-import net.codecrete.usb.usbstandard.DeviceDescriptor;
-import net.codecrete.usb.usbstandard.SetupPacket;
-import net.codecrete.usb.usbstandard.StringDescriptor;
-import net.codecrete.usb.windows.gen.kernel32.Kernel32;
-import net.codecrete.usb.windows.gen.usbioctl.USBIoctl;
-import net.codecrete.usb.windows.gen.usbioctl._USB_DESCRIPTOR_REQUEST;
-import net.codecrete.usb.windows.gen.usbioctl._USB_NODE_CONNECTION_INFORMATION_EX;
-import net.codecrete.usb.windows.gen.user32.*;
-import net.codecrete.usb.windows.winsdk.Kernel32B;
-import net.codecrete.usb.windows.winsdk.User32B;
-
-import java.lang.foreign.Arena;
-import java.lang.foreign.FunctionDescriptor;
-import java.lang.foreign.Linker;
-import java.lang.foreign.MemorySegment;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-import java.util.*;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-import static java.lang.System.Logger.Level.DEBUG;
-import static java.lang.System.Logger.Level.INFO;
-import static java.lang.foreign.MemorySegment.NULL;
-import static java.lang.foreign.ValueLayout.*;
-import static net.codecrete.usb.usbstandard.Constants.*;
-import static net.codecrete.usb.windows.DevicePropertyKey.*;
-import static net.codecrete.usb.windows.USBConstants.GUID_DEVINTERFACE_USB_DEVICE;
-import static net.codecrete.usb.windows.USBConstants.GUID_DEVINTERFACE_USB_HUB;
-import static net.codecrete.usb.windows.Win.allocateErrorState;
-import static net.codecrete.usb.windows.WindowsUSBException.throwException;
-import static net.codecrete.usb.windows.WindowsUSBException.throwLastError;
-
-/**
- * Windows implementation of USB device registry.
- *
- * To retrieve details of a USB device, this class accesses it indirectly
- * via the parent. To address it the parent's handle (hub handle) and
- * the device's port number is needed.
- *
- */
-public class WindowsUSBDeviceRegistry extends USBDeviceRegistry {
-
- private static final System.Logger LOG = System.getLogger(WindowsUSBDeviceRegistry.class.getName());
-
- private static final long REQUEST_DATA_OFFSET
- = _USB_DESCRIPTOR_REQUEST.$LAYOUT().byteOffset(PathElement.groupElement("Data"));
-
- @Override
- protected void monitorDevices() {
- try (var arena = Arena.ofConfined()) {
-
- MemorySegment hwnd;
- var errorState = allocateErrorState(arena);
-
- try {
- final var className = Win.createSegmentFromString("USB_MONITOR", arena);
- final var windowName = Win.createSegmentFromString("USB device monitor", arena);
- final var instance = Kernel32.GetModuleHandleW(NULL);
-
- // create upcall for handling window messages
- var handleWindowMessageMH = MethodHandles.lookup().findVirtual(WindowsUSBDeviceRegistry.class,
- "handleWindowMessage", MethodType.methodType(long.class, MemorySegment.class, int.class,
- long.class, long.class)).bindTo(this);
- var handleWindowMessageStub = Linker.nativeLinker().upcallStub(handleWindowMessageMH,
- FunctionDescriptor.of(JAVA_LONG, ADDRESS, JAVA_INT, JAVA_LONG, JAVA_LONG), arena);
-
- // register window class
- var wx = tagWNDCLASSEXW.allocate(arena);
- tagWNDCLASSEXW.cbSize$set(wx, (int) wx.byteSize());
- tagWNDCLASSEXW.lpfnWndProc$set(wx, handleWindowMessageStub);
- tagWNDCLASSEXW.hInstance$set(wx, instance);
- tagWNDCLASSEXW.lpszClassName$set(wx, className);
- var atom = User32B.RegisterClassExW(wx, errorState);
- if (atom == 0)
- throwLastError(errorState, "internal error (RegisterClassExW)");
-
- // create message-only window
- hwnd = User32B.CreateWindowExW(0, className, windowName, 0, 0, 0, 0, 0, User32.HWND_MESSAGE(), NULL,
- instance, NULL, errorState);
- if (hwnd.address() == 0)
- throwLastError(errorState, "internal error (CreateWindowExW)");
-
- // configure notifications
- var notificationFilter = _DEV_BROADCAST_DEVICEINTERFACE_W.allocate(arena);
- _DEV_BROADCAST_DEVICEINTERFACE_W.dbcc_size$set(notificationFilter, (int) notificationFilter.byteSize());
- _DEV_BROADCAST_DEVICEINTERFACE_W.dbcc_devicetype$set(notificationFilter,
- User32.DBT_DEVTYP_DEVICEINTERFACE());
- _DEV_BROADCAST_DEVICEINTERFACE_W.dbcc_classguid$slice(notificationFilter).copyFrom(GUID_DEVINTERFACE_USB_DEVICE);
-
- var notifyHandle = User32B.RegisterDeviceNotificationW(hwnd, notificationFilter,
- User32.DEVICE_NOTIFY_WINDOW_HANDLE(), errorState);
- if (notifyHandle.address() == 0)
- throwLastError(errorState, "internal error (RegisterDeviceNotificationW)");
-
- // initial device enumeration
- enumeratePresentDevices();
-
- } catch (Exception e) {
- enumerationFailed(e);
- return;
- }
-
- // process messages
- var msg = tagMSG.allocate(arena);
- int err;
- //noinspection StatementWithEmptyBody
- while ((err = User32B.GetMessageW(msg, hwnd, 0, 0, errorState)) > 0)
- ; // do nothing
-
- if (err == -1)
- throwLastError(errorState, "internal error (GetMessageW)");
- }
- }
-
- @SuppressWarnings("java:S106")
- private void enumeratePresentDevices() {
-
- List deviceList = new ArrayList<>();
- try (var cleanup = new ScopeCleanup();
- var deviceInfoSet = DeviceInfoSet.ofPresentDevices(GUID_DEVINTERFACE_USB_DEVICE, null)) {
-
- // ensure all hubs are closed later
- final var hubHandles = new HashMap();
- cleanup.add(() -> hubHandles.forEach((path, handle) -> Kernel32.CloseHandle(handle)));
-
- // iterate all devices
- while (deviceInfoSet.next()) {
-
- var instanceId = deviceInfoSet.getStringProperty(InstanceId);
- var devicePath = DeviceInfoSet.getDevicePath(instanceId, GUID_DEVINTERFACE_USB_DEVICE);
-
- try {
- deviceList.add(createDeviceFromDeviceInfo(deviceInfoSet, devicePath, hubHandles));
-
- } catch (Exception e) {
- LOG.log(INFO, String.format("failed to retrieve information about device %s - ignoring device", devicePath), e);
- }
- }
-
- setInitialDeviceList(deviceList);
- }
- }
-
- private USBDevice createDeviceFromDeviceInfo(DeviceInfoSet deviceInfoSet, String devicePath,
- HashMap hubHandles) {
- try (var arena = Arena.ofConfined()) {
-
- var usbPortNum = deviceInfoSet.getIntProperty(Address);
- var parentInstanceId = deviceInfoSet.getStringProperty(Parent);
- var hubPath = DeviceInfoSet.getDevicePath(parentInstanceId, GUID_DEVINTERFACE_USB_HUB);
-
- // open hub if not open yet
- var hubHandle = hubHandles.get(hubPath);
- if (hubHandle == null) {
- var hubPathSeg = Win.createSegmentFromString(hubPath, arena);
- var errorState = allocateErrorState(arena);
- hubHandle = Kernel32B.CreateFileW(hubPathSeg, Kernel32.GENERIC_WRITE(), Kernel32.FILE_SHARE_WRITE(),
- NULL, Kernel32.OPEN_EXISTING(), 0, NULL, errorState);
- if (Win.isInvalidHandle(hubHandle))
- throwLastError(errorState, "internal error (opening hub device)");
- hubHandles.put(hubPath, hubHandle);
- }
-
- // check for composite device
- var children = getChildDevices(deviceInfoSet, devicePath);
-
- return createDevice(devicePath, children, hubHandle, usbPortNum);
- }
- }
-
- @SuppressWarnings({"java:S106", "java:S1168"})
- private Map getChildDevices(DeviceInfoSet deviceInfoSet, String devicePath) {
- if (!deviceInfoSet.isCompositeDevice())
- return null;
-
- // For certain devices, it seems to take some time until the "Device_Children"
- // entry is present. So we retry a few times if needed and pause in between.
- List childrenInstanceIDs;
- var numTries = 5;
- while (true) {
- numTries -= 1;
- childrenInstanceIDs = deviceInfoSet.getStringListProperty(Children);
- if (childrenInstanceIDs != null || numTries == 0)
- break;
-
- // sleep and retry
- try {
- LOG.log(DEBUG, "Sleeping for 200ms (after unsuccessfully retrieving DEVPKEY_Device_Children)");
- //noinspection BusyWait
- Thread.sleep(200);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
-
- if (childrenInstanceIDs == null) {
- LOG.log(DEBUG, "unable to retrieve information about children of device {0} - ignoring", devicePath);
- return null;
- }
-
- // create children map (interface number -> device path)
- return childrenInstanceIDs.stream()
- .map(WindowsUSBDeviceRegistry::getNumberPathTuple)
- .filter(Objects::nonNull)
- .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
- }
-
- /**
- * Retrieve device descriptor and create {@code USBDevice} instance
- *
- * @param devicePath the device path
- * @param children map of child device paths, indexed by the first interface number
- * @param hubHandle the hub handle (parent)
- * @param usbPortNum the USB port number
- * @return the {@code USBDevice} instance
- */
- private USBDevice createDevice(String devicePath, Map children, MemorySegment hubHandle,
- int usbPortNum) {
-
- try (var arena = Arena.ofConfined()) {
-
- // get device descriptor
- var connInfo = _USB_NODE_CONNECTION_INFORMATION_EX.allocate(arena);
- _USB_NODE_CONNECTION_INFORMATION_EX.ConnectionIndex$set(connInfo, usbPortNum);
- var sizeHolder = arena.allocate(JAVA_INT);
- var errorState = allocateErrorState(arena);
- if (Kernel32B.DeviceIoControl(hubHandle, USBIoctl.IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX(),
- connInfo, (int) connInfo.byteSize(), connInfo, (int) connInfo.byteSize(), sizeHolder, NULL,
- errorState) == 0)
- throwLastError(errorState, "internal error (getting device descriptor failed)");
-
- var descriptorSegment = _USB_NODE_CONNECTION_INFORMATION_EX.DeviceDescriptor$slice(connInfo);
- var deviceDescriptor = new DeviceDescriptor(descriptorSegment);
-
- var vendorId = deviceDescriptor.vendorID();
- var productId = deviceDescriptor.productID();
-
- var configDesc = getDescriptor(hubHandle, usbPortNum, CONFIGURATION_DESCRIPTOR_TYPE, 0, (short) 0, arena);
-
- var device = new WindowsUSBDevice(devicePath, children, vendorId, productId, configDesc);
- device.setFromDeviceDescriptor(descriptorSegment);
- device.setProductString(descriptorSegment, index -> getStringDescriptor(hubHandle, usbPortNum, index));
-
- return device;
- }
- }
-
- private MemorySegment getDescriptor(MemorySegment hubHandle, int usbPortNumber, int descriptorType, int index,
- short languageID, Arena arena) {
- return getDescriptor(hubHandle, usbPortNumber, descriptorType, index, languageID, 0, arena);
-
- }
-
- private MemorySegment getDescriptor(MemorySegment hubHandle, int usbPortNumber, int descriptorType, int index,
- short languageID, int requestSize, Arena arena) {
- var size = requestSize != 0 ? requestSize + (int) REQUEST_DATA_OFFSET : 256;
-
- // create descriptor requests
- var descriptorRequest = arena.allocate(size);
- _USB_DESCRIPTOR_REQUEST.ConnectionIndex$set(descriptorRequest, usbPortNumber);
- var setupPacket = new SetupPacket(_USB_DESCRIPTOR_REQUEST.SetupPacket$slice(descriptorRequest));
- setupPacket.setRequestType(0x80); // device-to-host / type standard / recipient device
- setupPacket.setRequest(USBConstants.USB_REQUEST_GET_DESCRIPTOR);
- setupPacket.setValue((descriptorType << 8) | index);
- setupPacket.setIndex(languageID);
- setupPacket.setLength(size - (int) REQUEST_DATA_OFFSET);
-
- // execute request
- var effectiveSizeHolder = arena.allocate(JAVA_INT);
- var errorState = allocateErrorState(arena);
- if (Kernel32B.DeviceIoControl(hubHandle, USBIoctl.IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION(),
- descriptorRequest, size, descriptorRequest, size, effectiveSizeHolder, NULL, errorState) == 0)
- throwLastError(errorState, "internal error (retrieving descriptor %d failed)", index);
-
- // determine size of descriptor
- int expectedSize;
- if (descriptorType != CONFIGURATION_DESCRIPTOR_TYPE) {
- expectedSize = 255 & descriptorRequest.get(JAVA_BYTE, REQUEST_DATA_OFFSET);
- } else {
- var configDesc =
- new ConfigurationDescriptor(descriptorRequest.asSlice(REQUEST_DATA_OFFSET, ConfigurationDescriptor.LAYOUT.byteSize()));
- expectedSize = configDesc.totalLength();
- }
-
- // check against effective size
- var effectiveSize = effectiveSizeHolder.get(JAVA_INT, 0) - REQUEST_DATA_OFFSET;
- if (effectiveSize != expectedSize) {
- if (requestSize != 0)
- throwException("internal error (unexpected descriptor size)");
-
- // repeat with correct size
- return getDescriptor(hubHandle, usbPortNumber, descriptorType, index, languageID, expectedSize, arena);
- }
-
- return descriptorRequest.asSlice(REQUEST_DATA_OFFSET, effectiveSize);
- }
-
- @SuppressWarnings("java:S106")
- private String getStringDescriptor(MemorySegment hubHandle, int usbPortNumber, int index) {
- if (index == 0)
- return null;
-
- try (var arena = Arena.ofConfined()) {
- var stringDesc = new StringDescriptor(getDescriptor(hubHandle, usbPortNumber, STRING_DESCRIPTOR_TYPE,
- index, DEFAULT_LANGUAGE, arena));
- return stringDesc.string();
-
- } catch (USBException e) {
- return null;
- }
- }
-
- @SuppressWarnings("java:S1144")
- private long handleWindowMessage(MemorySegment hWnd, int uMsg, long wParam, long lParam) {
-
- // check for message related to connecting/disconnecting devices
- if (uMsg == User32.WM_DEVICECHANGE() && (wParam == User32.DBT_DEVICEARRIVAL() || wParam == User32.DBT_DEVICEREMOVECOMPLETE())) {
- var data = MemorySegment.ofAddress(lParam).reinterpret(_DEV_BROADCAST_DEVICEINTERFACE_W.sizeof());
- if (_DEV_BROADCAST_HDR.dbch_devicetype$get(data) == User32.DBT_DEVTYP_DEVICEINTERFACE()) {
-
- // get device path
- var nameSlice =
- MemorySegment.ofAddress(_DEV_BROADCAST_DEVICEINTERFACE_W.dbcc_name$slice(data).address()).reinterpret(500);
- var devicePath = Win.createStringFromSegment(nameSlice);
- if (wParam == User32.DBT_DEVICEARRIVAL())
- onDeviceConnected(devicePath);
- else
- onDeviceDisconnected(devicePath);
- return 0;
- }
- }
-
- // default message handling
- return User32.DefWindowProcW(hWnd, uMsg, wParam, lParam);
- }
-
- @SuppressWarnings("java:S106")
- private void onDeviceConnected(String devicePath) {
- try (var cleanup = new ScopeCleanup();
- var deviceInfoSet = DeviceInfoSet.ofPath(devicePath)) {
-
- // ensure all hubs are closed later
- final var hubHandles = new HashMap();
- cleanup.add(() -> hubHandles.forEach((path, handle) -> Kernel32.CloseHandle(handle)));
-
- try {
- // create device instance
- var device = createDeviceFromDeviceInfo(deviceInfoSet, devicePath, hubHandles);
-
- // add it to device list
- addDevice(device);
-
- } catch (Exception e) {
- LOG.log(INFO, String.format("failed to retrieve information about device %s - ignoring device", devicePath), e);
- }
- }
- }
-
- private void onDeviceDisconnected(String devicePath) {
- closeAndRemoveDevice(devicePath);
- }
-
- /**
- * Finds the index of the device in the list.
- *
- * This override uses a case-insensitive string comparison as Windows uses different casing
- * when initially enumerating devices and during later monitoring.
- *
- *
- * @param deviceList the device list
- * @param deviceId the unique device ID
- * @return index, or -1 if not found
- */
- @Override
- protected int findDeviceIndex(List deviceList, Object deviceId) {
- var id = deviceId.toString();
- for (var i = 0; i < deviceList.size(); i++) {
- var dev = (USBDeviceImpl) deviceList.get(i);
- if (id.equalsIgnoreCase(dev.getUniqueId().toString()))
- return i;
- }
- return -1;
- }
-
- /**
- * Looks up the interface number and device path for the child device with the given instance ID.
- *
- * @param instanceId child instance ID
- * @return tuple consisting of interface number and device path, or {@code null} if unsuccessful
- */
- private static Map.Entry getNumberPathTuple(String instanceId) {
- try (var deviceInfoSet = DeviceInfoSet.ofInstance(instanceId)) {
-
- // get hardware IDs (to extract interface number)
- var hardwareIds = deviceInfoSet.getStringListProperty(HardwareIds);
- if (hardwareIds == null)
- throwException("internal error (device property 'HardwareIds' is missing)");
- var interfaceNumber = extractInterfaceNumber(hardwareIds);
- if (interfaceNumber == -1) {
- LOG.log(DEBUG, "Child device {0} has no interface number", instanceId);
- return null;
- }
-
- var devicePath = deviceInfoSet.getDevicePathByGUID(instanceId);
- if (devicePath == null) {
- LOG.log(DEBUG, "Child device {0} has no device path", instanceId);
- return null;
- }
-
- return new AbstractMap.SimpleImmutableEntry<>(interfaceNumber, devicePath);
- }
- }
-
- private static final Pattern MULTIPLE_INTERFACE_ID = Pattern.compile(
- "USB\\\\VID_[0-9A-Fa-f]{4}&PID_[0-9A-Fa-f]{4}&MI_([0-9A-Fa-f]{2})");
-
- private static int extractInterfaceNumber(List hardwareIds) {
- // Also see https://docs.microsoft.com/en-us/windows-hardware/drivers/install/standard-usb-identifiers#multiple-interface-usb-devices
-
- for (var id : hardwareIds) {
- var matcher = MULTIPLE_INTERFACE_ID.matcher(id);
- if (matcher.find()) {
- var intfHexNumber = matcher.group(1);
- try {
- return Integer.parseInt(intfHexNumber, 16);
- } catch (NumberFormatException e) {
- // ignore and try next one
- }
- }
- }
-
- return -1;
- }
-}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUsbDevice.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUsbDevice.java
new file mode 100644
index 00000000..b98839c2
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUsbDevice.java
@@ -0,0 +1,669 @@
+//
+// Java Does USB
+// Copyright (c) 2022 Manuel Bleichenbacher
+// Licensed under MIT License
+// https://opensource.org/licenses/MIT
+//
+
+package net.codecrete.usb.windows;
+
+import net.codecrete.usb.UsbControlTransfer;
+import net.codecrete.usb.UsbDirection;
+import net.codecrete.usb.UsbException;
+import net.codecrete.usb.UsbRecipient;
+import net.codecrete.usb.UsbTransferType;
+import net.codecrete.usb.common.Transfer;
+import net.codecrete.usb.common.UsbDeviceImpl;
+import net.codecrete.usb.usbstandard.SetupPacket;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.INFO;
+import static java.lang.foreign.MemorySegment.NULL;
+import static java.lang.foreign.ValueLayout.ADDRESS;
+import static java.lang.foreign.ValueLayout.JAVA_BYTE;
+import static java.lang.foreign.ValueLayout.JAVA_INT;
+import static java.nio.charset.StandardCharsets.UTF_16LE;
+import static net.codecrete.usb.common.ForeignMemory.dereference;
+import static net.codecrete.usb.windows.CustomApis.CloseHandle;
+import static net.codecrete.usb.windows.CustomApis.WinUsb_ControlTransfer;
+import static net.codecrete.usb.windows.Win.allocateErrorState;
+import static net.codecrete.usb.windows.WindowsUsbException.throwException;
+import static net.codecrete.usb.windows.WindowsUsbException.throwLastError;
+import static windows.win32.devices.properties.Constants.DEVPKEY_Device_Children;
+import static windows.win32.devices.properties.Constants.DEVPKEY_Device_HardwareIds;
+import static windows.win32.devices.usb.Apis.WinUsb_AbortPipe;
+import static windows.win32.devices.usb.Apis.WinUsb_Free;
+import static windows.win32.devices.usb.Apis.WinUsb_GetAssociatedInterface;
+import static windows.win32.devices.usb.Apis.WinUsb_Initialize;
+import static windows.win32.devices.usb.Apis.WinUsb_ReadPipe;
+import static windows.win32.devices.usb.Apis.WinUsb_ResetPipe;
+import static windows.win32.devices.usb.Apis.WinUsb_SetCurrentAlternateSetting;
+import static windows.win32.devices.usb.Apis.WinUsb_SetPipePolicy;
+import static windows.win32.devices.usb.Apis.WinUsb_WritePipe;
+import static windows.win32.devices.usb.WINUSB_PIPE_POLICY.PIPE_TRANSFER_TIMEOUT;
+import static windows.win32.devices.usb.WINUSB_PIPE_POLICY.RAW_IO;
+import static windows.win32.foundation.GENERIC_ACCESS_RIGHTS.GENERIC_READ;
+import static windows.win32.foundation.GENERIC_ACCESS_RIGHTS.GENERIC_WRITE;
+import static windows.win32.foundation.WIN32_ERROR.ERROR_INVALID_PARAMETER;
+import static windows.win32.foundation.WIN32_ERROR.ERROR_IO_PENDING;
+import static windows.win32.storage.filesystem.Apis.CreateFileW;
+import static windows.win32.storage.filesystem.FILE_CREATION_DISPOSITION.OPEN_EXISTING;
+import static windows.win32.storage.filesystem.FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL;
+import static windows.win32.storage.filesystem.FILE_FLAGS_AND_ATTRIBUTES.FILE_FLAG_OVERLAPPED;
+import static windows.win32.storage.filesystem.FILE_SHARE_MODE.FILE_SHARE_READ;
+import static windows.win32.storage.filesystem.FILE_SHARE_MODE.FILE_SHARE_WRITE;
+
+/**
+ * Windows implementation for USB device.
+ */
+@SuppressWarnings("java:S2160")
+public class WindowsUsbDevice extends UsbDeviceImpl {
+
+ private static final System.Logger LOG = System.getLogger(WindowsUsbDevice.class.getName());
+
+ private final WindowsAsyncTask asyncTask;
+ /**
+ * Indicates if the device is a composite device
+ */
+ private final boolean isComposite;
+
+ private List interfaceHandles;
+
+ // device paths by interface number (first interface of function)
+ private Map devicePaths;
+
+ /**
+ * Indicates if {@link #open()} has been called. Since separate interfaces can have separate underlying
+ * Windows device, {@link #claimInterface(int)} instead of {@link #open()} will open the Windows device.
+ * (volatile: written under the device monitor, read unlocked via {@link #isOpened()})
+ */
+ private volatile boolean showAsOpen;
+
+ WindowsUsbDevice(String devicePath, int vendorId, int productId, MemorySegment configDesc, boolean isComposite) {
+ super(devicePath, vendorId, productId);
+ asyncTask = WindowsAsyncTask.INSTANCE;
+ this.isComposite = isComposite;
+ if (isComposite)
+ devicePaths = new HashMap<>();
+ readDescription(configDesc);
+ }
+
+ private void readDescription(MemorySegment configDesc) {
+ var configuration = setConfigurationDescriptor(configDesc);
+
+ // build list of interface handles
+ interfaceHandles = configuration.interfaces().stream()
+ .map(intf -> {
+ var interfaceNumber = intf.getNumber();
+ var function = configuration.findFunction(interfaceNumber);
+ return new InterfaceHandle(interfaceNumber, function.firstInterfaceNumber());
+ }).
+ toList();
+ }
+
+ @Override
+ public boolean isOpened() {
+ return showAsOpen;
+ }
+
+ @Override
+ public synchronized void open() {
+ checkIsClosed("device is already open");
+ showAsOpen = true;
+ }
+
+ @Override
+ public synchronized void close() {
+ if (!isOpened())
+ return;
+
+ for (var intf : interfaceList) {
+ if (intf.isClaimed())
+ releaseInterface(intf.getNumber());
+ }
+
+ showAsOpen = false;
+ }
+
+ public void claimInterface(int interfaceNumber) {
+ // When a device is plugged in, a notification is sent. For composite devices, it is a notification
+ // that the composite device is ready. Each composite function will be registered separately and
+ // the related information will be available with a delay. So for composite functions, several
+ // retries might be needed until the device path is available.
+ var numRetries = 30; // 30 x 100ms
+ // Defer interruption: keep a local flag instead of re-asserting the interrupt
+ // (which would make the remaining backoff sleeps throw immediately and defeat
+ // the retry delay). Re-assert when leaving the method.
+ var wasInterrupted = false;
+ try {
+ while (true) {
+ if (claimInterfaceSynchronized(interfaceNumber))
+ return; // success
+
+ numRetries -= 1;
+ if (numRetries == 0)
+ throw new UsbException("claiming interface failed (function has no device path / interface GUID, might be missing WinUSB driver)");
+
+ // sleep and retry
+ try {
+ LOG.log(DEBUG, "Sleeping for 100ms...");
+ //noinspection BusyWait
+ Thread.sleep(100);
+ } catch (InterruptedException _) {
+ wasInterrupted = true;
+ }
+ }
+ } finally {
+ if (wasInterrupted)
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ @SuppressWarnings("java:S3776")
+ private synchronized boolean claimInterfaceSynchronized(int interfaceNumber) {
+ checkIsOpen();
+
+ getInterfaceWithCheck(interfaceNumber, false);
+
+ var intfHandle = getInterfaceHandle(interfaceNumber);
+ var firstIntfHandle = intfHandle;
+ if (intfHandle.firstInterfaceNumber != interfaceNumber)
+ firstIntfHandle = getInterfaceHandle(intfHandle.firstInterfaceNumber);
+
+ var deviceOpenedHere = false;
+
+ try (var arena = Arena.ofConfined()) {
+ var errorState = allocateErrorState(arena);
+
+ // both the device and the first interface must be opened for any interface belonging to the same function
+ if (firstIntfHandle.deviceHandle == null) {
+ var devicePath = getInterfaceDevicePath(firstIntfHandle.interfaceNumber);
+ if (devicePath == null)
+ return false; // retry later
+
+ LOG.log(DEBUG, "opening device {0}", devicePath);
+
+ // open Windows device if needed
+ var pathSegment = arena.allocateFrom(devicePath, UTF_16LE);
+ var deviceHandle = CreateFileW(errorState, pathSegment, GENERIC_WRITE | GENERIC_READ,
+ FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
+
+ if (Win.isInvalidHandle(deviceHandle))
+ throwLastError(errorState, "claiming interface failed (opening USB device %s failed)", devicePath);
+
+ MemorySegment interfaceHandle = null;
+ try {
+ // open first interface
+ var interfaceHandleHolder = arena.allocate(ADDRESS);
+ if (WinUsb_Initialize(errorState, deviceHandle, interfaceHandleHolder) == 0) {
+ if (Win.getLastError(errorState) == ERROR_INVALID_PARAMETER)
+ throw new UsbException(
+ "claiming interface failed (required WinUSB driver is probably not installed for the device)",
+ ERROR_INVALID_PARAMETER
+ );
+ throwLastError(errorState, "claiming interface failed");
+ }
+ interfaceHandle = dereference(interfaceHandleHolder);
+
+ asyncTask.addDevice(deviceHandle);
+
+ // Assign handles only after all fallible operations have succeeded.
+ // Otherwise a later claim would see them and submit I/O on a closed handle.
+ firstIntfHandle.deviceHandle = deviceHandle;
+ firstIntfHandle.winusbHandle = interfaceHandle;
+ deviceOpenedHere = true;
+
+ } catch (Exception e) {
+ if (interfaceHandle != null)
+ WinUsb_Free(interfaceHandle);
+ CloseHandle(deviceHandle);
+ throw e;
+ }
+ }
+
+ if (intfHandle != firstIntfHandle) {
+ try {
+ // open associated interface
+ var interfaceHandleHolder = arena.allocate(ADDRESS);
+ if (WinUsb_GetAssociatedInterface(errorState, firstIntfHandle.winusbHandle,
+ (byte) (intfHandle.interfaceNumber - firstIntfHandle.interfaceNumber - 1),
+ interfaceHandleHolder) == 0)
+ throwLastError(errorState, "claiming (associated) interface failed");
+ intfHandle.winusbHandle = dereference(interfaceHandleHolder);
+
+ } catch (Exception e) {
+ if (deviceOpenedHere) {
+ // no interface has been claimed yet, so close() would never release the device
+ WinUsb_Free(firstIntfHandle.winusbHandle);
+ CloseHandle(firstIntfHandle.deviceHandle);
+ firstIntfHandle.winusbHandle = null;
+ firstIntfHandle.deviceHandle = null;
+ }
+ throw e;
+ }
+ }
+ }
+
+ firstIntfHandle.deviceOpenCount += 1;
+ setClaimed(interfaceNumber, true);
+ return true;
+ }
+
+ @Override
+ public synchronized void selectAlternateSetting(int interfaceNumber, int alternateNumber) {
+ checkIsOpen();
+
+ var intf = getInterfaceWithCheck(interfaceNumber, true);
+ var intfHandle = getInterfaceHandle(interfaceNumber);
+
+ // check alternate setting
+ var altSetting = intf.getAlternate(alternateNumber);
+
+ try (var arena = Arena.ofConfined()) {
+ var errorState = allocateErrorState(arena);
+ if (WinUsb_SetCurrentAlternateSetting(errorState, intfHandle.winusbHandle, (byte) alternateNumber) == 0)
+ throwLastError(errorState, "setting alternate interface failed");
+ }
+ intf.setAlternate(altSetting);
+ }
+
+ public synchronized void releaseInterface(int interfaceNumber) {
+ checkIsOpen();
+
+ getInterfaceWithCheck(interfaceNumber, true);
+
+ var intfHandle = getInterfaceHandle(interfaceNumber);
+ var firstIntfHandle = intfHandle;
+ if (intfHandle.firstInterfaceNumber != interfaceNumber)
+ firstIntfHandle = getInterfaceHandle(intfHandle.firstInterfaceNumber);
+
+ setClaimed(interfaceNumber, false);
+
+ if (intfHandle != firstIntfHandle) {
+ // close associated interface
+ WinUsb_Free(intfHandle.winusbHandle);
+ intfHandle.winusbHandle = null;
+ }
+
+ // close device if needed
+ firstIntfHandle.deviceOpenCount -= 1;
+ if (firstIntfHandle.deviceOpenCount == 0) {
+ WinUsb_Free(firstIntfHandle.winusbHandle);
+ firstIntfHandle.winusbHandle = null;
+
+ LOG.log(DEBUG, "closing device {0}", getCachedInterfaceDevicePath(interfaceNumber));
+
+ CloseHandle(firstIntfHandle.deviceHandle);
+ firstIntfHandle.deviceHandle = null;
+ }
+ }
+
+ @Override
+ public void controlTransferOut(@NotNull UsbControlTransfer setup, byte[] data) {
+ try (var arena = Arena.ofConfined()) {
+
+ // copy data to native memory
+ var transfer = createSyncControlTransfer();
+ var dataLength = data != null ? data.length : 0;
+ transfer.setDataSize(dataLength);
+ if (dataLength != 0) {
+ var buffer = arena.allocate(data.length);
+ buffer.copyFrom(MemorySegment.ofArray(data));
+ transfer.setData(buffer);
+ } else {
+ transfer.setData(NULL);
+ }
+
+ synchronized (transfer) {
+ submitControlTransfer(UsbDirection.OUT, setup, transfer);
+ waitForTransfer(transfer, 0, UsbDirection.OUT, 0);
+ }
+ }
+ }
+
+ @Override
+ public byte @NotNull [] controlTransferIn(@NotNull UsbControlTransfer setup, int length) {
+ try (var arena = Arena.ofConfined()) {
+ var transfer = createSyncControlTransfer();
+ transfer.setData(arena.allocate(length));
+ transfer.setDataSize(length);
+
+ synchronized (transfer) {
+ submitControlTransfer(UsbDirection.IN, setup, transfer);
+ waitForTransfer(transfer, 0, UsbDirection.IN, 0);
+ }
+
+ return transfer.data().asSlice(0, transfer.resultSize()).toArray(JAVA_BYTE);
+ }
+ }
+
+ @Override
+ public void transferOut(int endpointNumber, byte @NotNull [] data, int offset, int length, int timeout) {
+ // Auto arena: a transfer that times out may be abandoned (see UsbDeviceImpl.waitForTransfer),
+ // so the buffer must outlive a possible late completion instead of being freed deterministically.
+ var arena = Arena.ofAuto();
+ var buffer = arena.allocate(data.length);
+ buffer.copyFrom(MemorySegment.ofArray(data).asSlice(offset, length));
+ var transfer = createSyncTransfer(buffer);
+
+ synchronized (transfer) {
+ submitTransferOut(endpointNumber, transfer);
+ waitForTransfer(transfer, timeout, UsbDirection.OUT, endpointNumber);
+ }
+ }
+
+ @Override
+ public byte @NotNull [] transferIn(int endpointNumber, int timeout) {
+ var endpoint = getEndpoint(UsbDirection.IN, endpointNumber, UsbTransferType.BULK, UsbTransferType.INTERRUPT);
+
+ // Auto arena: a transfer that times out may be abandoned (see UsbDeviceImpl.waitForTransfer),
+ // so the buffer must outlive a possible late completion instead of being freed deterministically.
+ var arena = Arena.ofAuto();
+ var buffer = arena.allocate(endpoint.packetSize());
+ var transfer = createSyncTransfer(buffer);
+
+ synchronized (transfer) {
+ submitTransferIn(endpointNumber, transfer);
+ waitForTransfer(transfer, timeout, UsbDirection.IN, endpointNumber);
+ }
+
+ return buffer.asSlice(0, transfer.resultSize()).toArray(JAVA_BYTE);
+ }
+
+ private WindowsTransfer createSyncControlTransfer() {
+ var transfer = new WindowsTransfer();
+ transfer.setCompletion(UsbDeviceImpl::onSyncTransferCompleted);
+ return transfer;
+ }
+
+ private WindowsTransfer createSyncTransfer(MemorySegment data) {
+ var transfer = new WindowsTransfer();
+ transfer.setData(data);
+ transfer.setDataSize((int) data.byteSize());
+ transfer.setCompletion(UsbDeviceImpl::onSyncTransferCompleted);
+ return transfer;
+ }
+
+ @Override
+ protected Transfer createTransfer() {
+ return new WindowsTransfer();
+ }
+
+ @Override
+ protected void throwOSException(int errorCode, String message, Object... args) {
+ throwException(errorCode, message, args);
+ }
+
+ synchronized void submitControlTransfer(UsbDirection direction, UsbControlTransfer setup, WindowsTransfer transfer) {
+ checkIsOpen();
+ var intfHandle = findControlTransferInterface(setup);
+
+ try (var arena = Arena.ofConfined()) {
+ var setupPacket = new SetupPacket(arena);
+ var bmRequest =
+ (direction == UsbDirection.IN ? 0x80 : 0) | (setup.requestType().ordinal() << 5) | setup.recipient().ordinal();
+ setupPacket.setRequestType(bmRequest);
+ setupPacket.setRequest(setup.request());
+ setupPacket.setValue(setup.value());
+ setupPacket.setIndex(setup.index());
+ setupPacket.setLength(transfer.dataSize());
+
+ var errorState = allocateErrorState(arena);
+ asyncTask.prepareForSubmission(transfer);
+
+ // submit transfer
+ if (WinUsb_ControlTransfer(errorState, intfHandle.winusbHandle, setupPacket.segment(), transfer.data(),
+ transfer.dataSize(), NULL, transfer.overlapped()) == 0) {
+ var err = Win.getLastError(errorState);
+ if (err != ERROR_IO_PENDING) {
+ asyncTask.submissionFailed(transfer);
+ throwException(err, "submitting control transfer failed");
+ }
+ }
+ }
+ }
+
+ synchronized void submitTransferOut(int endpointNumber, WindowsTransfer transfer) {
+ var endpoint = getEndpoint(UsbDirection.OUT, endpointNumber, UsbTransferType.BULK, UsbTransferType.INTERRUPT);
+ var intfHandle = getInterfaceHandle(endpoint.interfaceNumber());
+
+ try (var arena = Arena.ofConfined()) {
+ var errorState = allocateErrorState(arena);
+ asyncTask.prepareForSubmission(transfer);
+
+ // submit transfer
+ if (WinUsb_WritePipe(errorState, intfHandle.winusbHandle, endpoint.endpointAddress(), transfer.data(),
+ transfer.dataSize(), NULL, transfer.overlapped()) == 0) {
+ var err = Win.getLastError(errorState);
+ if (err != ERROR_IO_PENDING) {
+ asyncTask.submissionFailed(transfer);
+ throwException(err, "submitting transfer OUT failed");
+ }
+ }
+ }
+ }
+
+ synchronized void submitTransferIn(int endpointNumber, WindowsTransfer transfer) {
+ var endpoint = getEndpoint(UsbDirection.IN, endpointNumber, UsbTransferType.BULK, UsbTransferType.INTERRUPT);
+ var intfHandle = getInterfaceHandle(endpoint.interfaceNumber());
+
+ try (var arena = Arena.ofConfined()) {
+ var errorState = allocateErrorState(arena);
+ asyncTask.prepareForSubmission(transfer);
+
+ // submit transfer
+ if (WinUsb_ReadPipe(errorState, intfHandle.winusbHandle, endpoint.endpointAddress(), transfer.data(),
+ transfer.dataSize(), NULL, transfer.overlapped()) == 0) {
+ var err = Win.getLastError(errorState);
+ if (err != ERROR_IO_PENDING) {
+ asyncTask.submissionFailed(transfer);
+ throwException(err, "submitting transfer IN failed");
+ }
+ }
+ }
+ }
+
+ synchronized void configureForAsyncIo(UsbDirection direction, int endpointNumber) {
+ var endpoint = getEndpoint(direction, endpointNumber, UsbTransferType.BULK, UsbTransferType.INTERRUPT);
+ var intfHandle = getInterfaceHandle(endpoint.interfaceNumber());
+
+ try (var arena = Arena.ofConfined()) {
+ var errorState = allocateErrorState(arena);
+
+ var timeoutHolder = arena.allocate(JAVA_INT);
+ if (WinUsb_SetPipePolicy(errorState, intfHandle.winusbHandle, endpoint.endpointAddress(),
+ PIPE_TRANSFER_TIMEOUT, (int) timeoutHolder.byteSize(), timeoutHolder) == 0)
+ throwLastError(errorState, "setting timeout failed");
+
+ var rawIoHolder = arena.allocate(JAVA_BYTE);
+ rawIoHolder.setAtIndex(JAVA_BYTE, 0, (byte) 1);
+ if (WinUsb_SetPipePolicy(errorState, intfHandle.winusbHandle, endpoint.endpointAddress(), RAW_IO,
+ (int) rawIoHolder.byteSize(), rawIoHolder) == 0)
+ throwLastError(errorState, "setting raw IO failed");
+ }
+ }
+
+ @Override
+ public synchronized void clearHalt(UsbDirection direction, int endpointNumber) {
+ var endpoint = getEndpoint(direction, endpointNumber, UsbTransferType.BULK, UsbTransferType.INTERRUPT);
+ var intfHandle = getInterfaceHandle(endpoint.interfaceNumber());
+
+ try (var arena = Arena.ofConfined()) {
+ var errorState = allocateErrorState(arena);
+ if (WinUsb_ResetPipe(errorState, intfHandle.winusbHandle, endpoint.endpointAddress()) == 0)
+ throwLastError(errorState, "clearing halt failed");
+ }
+ }
+
+ @Override
+ public synchronized void abortTransfers(UsbDirection direction, int endpointNumber) {
+ var endpoint = getEndpoint(direction, endpointNumber, UsbTransferType.BULK, UsbTransferType.INTERRUPT);
+ var intfHandle = getInterfaceHandle(endpoint.interfaceNumber());
+
+ try (var arena = Arena.ofConfined()) {
+ var errorState = allocateErrorState(arena);
+ if (WinUsb_AbortPipe(errorState, intfHandle.winusbHandle, endpoint.endpointAddress()) == 0)
+ throwLastError(errorState, "aborting transfers on endpoint failed");
+ }
+ }
+
+ @Override
+ public synchronized @NotNull InputStream openInputStream(int endpointNumber, int bufferSize) {
+ // check that endpoint number is valid
+ getEndpoint(UsbDirection.IN, endpointNumber, UsbTransferType.BULK, null);
+
+ return new WindowsEndpointInputStream(this, endpointNumber, bufferSize);
+ }
+
+ @Override
+ public synchronized @NotNull OutputStream openOutputStream(int endpointNumber, int bufferSize) {
+ // check that endpoint number is valid
+ getEndpoint(UsbDirection.OUT, endpointNumber, UsbTransferType.BULK, null);
+
+ return new WindowsEndpointOutputStream(this, endpointNumber, bufferSize);
+ }
+
+ private InterfaceHandle getInterfaceHandle(int interfaceNumber) {
+ for (var intfHandle : interfaceHandles) {
+ if (intfHandle.interfaceNumber == interfaceNumber)
+ return intfHandle;
+ }
+
+ throwException("invalid interface number %s", interfaceNumber);
+ throw new AssertionError("not reached");
+ }
+
+ private InterfaceHandle findControlTransferInterface(UsbControlTransfer setup) {
+
+ var interfaceNumber = -1;
+ int endpointNumber;
+
+ if (setup.recipient() == UsbRecipient.INTERFACE) {
+
+ interfaceNumber = setup.index() & 0xff;
+
+ } else if (setup.recipient() == UsbRecipient.ENDPOINT) {
+
+ endpointNumber = setup.index() & 0x7f;
+ var direction = (setup.index() & 0x80) != 0 ? UsbDirection.IN : UsbDirection.OUT;
+ if (endpointNumber != 0) {
+ interfaceNumber = getInterfaceNumber(direction, endpointNumber);
+ if (interfaceNumber == -1)
+ throwException("invalid endpoint number %d or interface not claimed", endpointNumber);
+ }
+ }
+
+ if (interfaceNumber >= 0) {
+ var intfHandle = getInterfaceHandle(interfaceNumber);
+ if (intfHandle.winusbHandle == null)
+ throwException("interface number %d has not been claimed", interfaceNumber);
+ return intfHandle;
+ }
+
+ // for control transfer to device, use any claimed interface
+ for (var intfHandle : interfaceHandles) {
+ if (intfHandle.winusbHandle != null)
+ return intfHandle;
+ }
+
+ throwException("control transfer cannot be executed as no interface has been claimed");
+ throw new AssertionError("not reached");
+ }
+
+ private String getInterfaceDevicePath(int interfaceNumber) {
+ var devicePath = getCachedInterfaceDevicePath(interfaceNumber);
+ if (devicePath != null)
+ return devicePath;
+
+ var parentDevicePath = (String) getUniqueId();
+
+ try (var deviceInfoSet = DeviceInfoSet.ofPath(parentDevicePath)) {
+ var childrenInstanceIDs = deviceInfoSet.getStringListProperty(DEVPKEY_Device_Children());
+ if (childrenInstanceIDs == null) {
+ LOG.log(DEBUG, "missing children instance IDs for device {0}", parentDevicePath);
+ return null;
+
+ } else {
+ LOG.log(DEBUG, "children instance IDs: {0}", childrenInstanceIDs);
+
+ for (var instanceId : childrenInstanceIDs) {
+ devicePath = getChildDevicePath(instanceId, interfaceNumber);
+ if (devicePath != null)
+ return devicePath;
+ }
+ }
+ }
+
+ return null; // retry later
+ }
+
+ private String getCachedInterfaceDevicePath(int interfaceNumber) {
+ if (!isComposite)
+ return (String) getUniqueId();
+ return devicePaths.get(interfaceNumber);
+ }
+
+ private String getChildDevicePath(String instanceId, int interfaceNumber) {
+ try (var deviceInfoSet = DeviceInfoSet.ofInstance(instanceId)) {
+
+ // get hardware IDs (to extract interface number)
+ var hardwareIds = deviceInfoSet.getStringListProperty(DEVPKEY_Device_HardwareIds());
+ if (hardwareIds == null) {
+ LOG.log(DEBUG, "child device {0} has no hardware IDs", instanceId);
+ return null;
+ }
+
+ var extractedNumber = extractInterfaceNumber(hardwareIds);
+ if (extractedNumber == -1) {
+ LOG.log(DEBUG, "child device {0} has no interface number", instanceId);
+ return null;
+ }
+
+ if (extractedNumber != interfaceNumber)
+ return null;
+
+ var devicePath = deviceInfoSet.getDevicePathByGUID(instanceId);
+ if (devicePath == null) {
+ LOG.log(INFO, "Child device {0} has no device path / interface GUID", instanceId);
+ throw new UsbException("claiming interface failed (composite function has no device path / interface GUID; the required WinUSB driver is probably not installed)");
+ }
+
+ if (devicePaths == null)
+ devicePaths = new HashMap<>();
+ devicePaths.put(interfaceNumber, devicePath);
+ return devicePath;
+ }
+ }
+
+ private static final Pattern MULTIPLE_INTERFACE_ID = Pattern.compile(
+ "USB\\\\VID_[0-9A-Fa-f]{4}&PID_[0-9A-Fa-f]{4}&MI_([0-9A-Fa-f]{2})");
+
+ private static int extractInterfaceNumber(List hardwareIds) {
+ // Also see https://docs.microsoft.com/en-us/windows-hardware/drivers/install/standard-usb-identifiers#multiple-interface-usb-devices
+
+ for (var id : hardwareIds) {
+ var matcher = MULTIPLE_INTERFACE_ID.matcher(id);
+ if (matcher.find()) {
+ var intfHexNumber = matcher.group(1);
+ try {
+ return Integer.parseInt(intfHexNumber, 16);
+ } catch (NumberFormatException _) {
+ // ignore and try next one
+ }
+ }
+ }
+
+ return -1;
+ }
+}
diff --git a/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUsbDeviceRegistry.java b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUsbDeviceRegistry.java
new file mode 100644
index 00000000..d9f86393
--- /dev/null
+++ b/java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUsbDeviceRegistry.java
@@ -0,0 +1,396 @@
+//
+// Java Does USB
+// Copyright (c) 2022 Manuel Bleichenbacher
+// Licensed under MIT License
+// https://opensource.org/licenses/MIT
+//
+
+package net.codecrete.usb.windows;
+
+import net.codecrete.usb.UsbDevice;
+import net.codecrete.usb.UsbException;
+import net.codecrete.usb.common.ScopeCleanup;
+import net.codecrete.usb.common.UsbDeviceImpl;
+import net.codecrete.usb.common.UsbDeviceRegistry;
+import net.codecrete.usb.usbstandard.ConfigurationDescriptor;
+import net.codecrete.usb.usbstandard.DeviceDescriptor;
+import net.codecrete.usb.usbstandard.SetupPacket;
+import net.codecrete.usb.usbstandard.StringDescriptor;
+import windows.win32.devices.usb.USB_DESCRIPTOR_REQUEST;
+import windows.win32.devices.usb.USB_NODE_CONNECTION_INFORMATION_EX;
+import windows.win32.ui.windowsandmessaging.DEV_BROADCAST_DEVICEINTERFACE_W;
+import windows.win32.ui.windowsandmessaging.DEV_BROADCAST_HDR;
+import windows.win32.ui.windowsandmessaging.MSG;
+import windows.win32.ui.windowsandmessaging.WNDCLASSEXW;
+import windows.win32.ui.windowsandmessaging.WNDPROC;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.lang.System.Logger.Level.INFO;
+import static java.lang.foreign.MemorySegment.NULL;
+import static java.lang.foreign.ValueLayout.JAVA_BYTE;
+import static java.lang.foreign.ValueLayout.JAVA_INT;
+import static java.lang.foreign.ValueLayout.JAVA_SHORT;
+import static java.lang.foreign.ValueLayout.PathElement;
+import static java.nio.charset.StandardCharsets.UTF_16LE;
+import static net.codecrete.usb.usbstandard.Constants.CONFIGURATION_DESCRIPTOR_TYPE;
+import static net.codecrete.usb.usbstandard.Constants.DEFAULT_LANGUAGE;
+import static net.codecrete.usb.usbstandard.Constants.STRING_DESCRIPTOR_TYPE;
+import static net.codecrete.usb.windows.CustomApis.CloseHandle;
+import static net.codecrete.usb.windows.Win.allocateErrorState;
+import static net.codecrete.usb.windows.WindowsUsbException.throwException;
+import static net.codecrete.usb.windows.WindowsUsbException.throwLastError;
+import static windows.win32.devices.properties.Constants.DEVPKEY_Device_Address;
+import static windows.win32.devices.properties.Constants.DEVPKEY_Device_InstanceId;
+import static windows.win32.devices.properties.Constants.DEVPKEY_Device_Parent;
+import static windows.win32.devices.usb.Constants.GUID_DEVINTERFACE_USB_DEVICE;
+import static windows.win32.devices.usb.Constants.GUID_DEVINTERFACE_USB_HUB;
+import static windows.win32.devices.usb.Constants.IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION;
+import static windows.win32.devices.usb.Constants.IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX;
+import static windows.win32.devices.usb.Constants.USB_REQUEST_GET_DESCRIPTOR;
+import static windows.win32.foundation.GENERIC_ACCESS_RIGHTS.GENERIC_WRITE;
+import static windows.win32.storage.filesystem.Apis.CreateFileW;
+import static windows.win32.storage.filesystem.FILE_CREATION_DISPOSITION.OPEN_EXISTING;
+import static windows.win32.storage.filesystem.FILE_SHARE_MODE.FILE_SHARE_WRITE;
+import static windows.win32.system.io.Apis.DeviceIoControl;
+import static windows.win32.system.libraryloader.Apis.GetModuleHandleW;
+import static windows.win32.ui.windowsandmessaging.Apis.CreateWindowExW;
+import static windows.win32.ui.windowsandmessaging.Apis.DefWindowProcW;
+import static windows.win32.ui.windowsandmessaging.Apis.GetMessageW;
+import static windows.win32.ui.windowsandmessaging.Apis.RegisterClassExW;
+import static windows.win32.ui.windowsandmessaging.Apis.RegisterDeviceNotificationW;
+import static windows.win32.ui.windowsandmessaging.Constants.DBT_DEVICEARRIVAL;
+import static windows.win32.ui.windowsandmessaging.Constants.DBT_DEVICEREMOVECOMPLETE;
+import static windows.win32.ui.windowsandmessaging.Constants.HWND_MESSAGE;
+import static windows.win32.ui.windowsandmessaging.Constants.WM_DEVICECHANGE;
+import static windows.win32.ui.windowsandmessaging.DEV_BROADCAST_HDR_DEVICE_TYPE.DBT_DEVTYP_DEVICEINTERFACE;
+import static windows.win32.ui.windowsandmessaging.REGISTER_NOTIFICATION_FLAGS.DEVICE_NOTIFY_WINDOW_HANDLE;
+
+/**
+ * Windows implementation of USB device registry.
+ *
+ * To retrieve details of a USB device, this class accesses it indirectly
+ * via the parent. To address it the parent's handle (hub handle) and
+ * the device's port number is needed.
+ *
+ */
+public class WindowsUsbDeviceRegistry extends UsbDeviceRegistry {
+
+ private static final System.Logger LOG = System.getLogger(WindowsUsbDeviceRegistry.class.getName());
+
+ private static final long REQUEST_DATA_OFFSET
+ = USB_DESCRIPTOR_REQUEST.layout().byteOffset(PathElement.groupElement("Data"));
+
+ @Override
+ protected void monitorDevices() {
+ try (var arena = Arena.ofConfined()) {
+
+ MemorySegment hwnd;
+ var errorState = allocateErrorState(arena);
+
+ try {
+ final var className = arena.allocateFrom("USB_MONITOR", UTF_16LE);
+ final var windowName = arena.allocateFrom("USB device monitor", UTF_16LE);
+ final var instance = GetModuleHandleW(errorState, NULL);
+
+ // register window class
+ var wx = WNDCLASSEXW.allocate(arena);
+ WNDCLASSEXW.lpfnWndProc(wx, WNDPROC.allocate(arena, this::handleWindowMessage));
+ WNDCLASSEXW.hInstance(wx, instance);
+ WNDCLASSEXW.lpszClassName(wx, className);
+ var atom = RegisterClassExW(errorState, wx);
+ if (atom == 0)
+ throwLastError(errorState, "internal error (RegisterClassExW)");
+
+ // create message-only window
+ hwnd = CreateWindowExW(errorState, 0, className, windowName, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL,
+ instance, NULL);
+ if (hwnd.address() == 0)
+ throwLastError(errorState, "internal error (CreateWindowExW)");
+
+ // configure notifications
+ var notificationFilter = DEV_BROADCAST_DEVICEINTERFACE_W.allocate(arena, 260);
+ DEV_BROADCAST_DEVICEINTERFACE_W.dbcc_size(notificationFilter, (int) notificationFilter.byteSize());
+ DEV_BROADCAST_DEVICEINTERFACE_W.dbcc_devicetype(notificationFilter, DBT_DEVTYP_DEVICEINTERFACE);
+ DEV_BROADCAST_DEVICEINTERFACE_W.dbcc_classguid(notificationFilter).copyFrom(GUID_DEVINTERFACE_USB_DEVICE());
+
+ var notifyHandle = RegisterDeviceNotificationW(errorState, hwnd, notificationFilter,
+ DEVICE_NOTIFY_WINDOW_HANDLE);
+ if (notifyHandle.address() == 0)
+ throwLastError(errorState, "internal error (RegisterDeviceNotificationW)");
+
+ // initial device enumeration
+ enumeratePresentDevices();
+
+ } catch (Exception e) {
+ enumerationFailed(e);
+ return;
+ }
+
+ // process messages
+ var msg = MSG.allocate(arena);
+ int err;
+ //noinspection StatementWithEmptyBody
+ while ((err = GetMessageW(errorState, msg, hwnd, 0, 0)) > 0)
+ ; // do nothing
+
+ if (err == -1)
+ throwLastError(errorState, "internal error (GetMessageW)");
+ }
+ }
+
+ @SuppressWarnings("java:S106")
+ private void enumeratePresentDevices() {
+
+ List deviceList = new ArrayList<>();
+ try (var cleanup = new ScopeCleanup();
+ var deviceInfoSet = DeviceInfoSet.ofPresentDevices(GUID_DEVINTERFACE_USB_DEVICE(), null)) {
+
+ // ensure all hubs are closed later
+ final var hubHandles = new HashMap();
+ cleanup.add(() -> hubHandles.forEach((_, handle) -> CloseHandle(handle)));
+
+ // iterate all devices
+ while (deviceInfoSet.next()) {
+
+ var instanceId = deviceInfoSet.getStringProperty(DEVPKEY_Device_InstanceId());
+ var devicePath = DeviceInfoSet.getDevicePath(instanceId, GUID_DEVINTERFACE_USB_DEVICE());
+
+ try {
+ deviceList.add(createDeviceFromDeviceInfo(deviceInfoSet, devicePath, hubHandles));
+
+ } catch (Exception e) {
+ LOG.log(INFO, String.format("failed to retrieve information about device %s - ignoring device", devicePath), e);
+ }
+ }
+
+ setInitialDeviceList(deviceList);
+ }
+ }
+
+ private UsbDevice createDeviceFromDeviceInfo(DeviceInfoSet deviceInfoSet, String devicePath,
+ Map hubHandles) {
+ try (var arena = Arena.ofConfined()) {
+
+ var usbPortNum = deviceInfoSet.getIntProperty(DEVPKEY_Device_Address());
+ var parentInstanceId = deviceInfoSet.getStringProperty(DEVPKEY_Device_Parent());
+ var hubPath = DeviceInfoSet.getDevicePath(parentInstanceId, GUID_DEVINTERFACE_USB_HUB());
+
+ // open hub if not open yet
+ var hubHandle = hubHandles.get(hubPath);
+ if (hubHandle == null) {
+ var hubPathSeg = arena.allocateFrom(hubPath, UTF_16LE);
+ var errorState = allocateErrorState(arena);
+ hubHandle = CreateFileW(errorState, hubPathSeg, GENERIC_WRITE, FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, NULL);
+ if (Win.isInvalidHandle(hubHandle))
+ throwLastError(errorState, "internal error (opening hub device)");
+ hubHandles.put(hubPath, hubHandle);
+ }
+
+ return createDevice(devicePath, deviceInfoSet.isCompositeDevice(), hubHandle, usbPortNum);
+ }
+ }
+
+ /**
+ * Retrieve device descriptor and create {@code UsbDevice} instance
+ *
+ * @param devicePath the device path
+ * @param hubHandle the hub handle (parent)
+ * @param usbPortNum the USB port number
+ * @return the {@code UsbDevice} instance
+ */
+ private UsbDevice createDevice(String devicePath, boolean isComposite, MemorySegment hubHandle, int usbPortNum) {
+
+ try (var arena = Arena.ofConfined()) {
+
+ // get device descriptor
+ var connInfo = USB_NODE_CONNECTION_INFORMATION_EX.allocate(arena, 0);
+ USB_NODE_CONNECTION_INFORMATION_EX.ConnectionIndex(connInfo, usbPortNum);
+ var sizeHolder = arena.allocate(JAVA_INT);
+ var errorState = allocateErrorState(arena);
+ if (DeviceIoControl(errorState, hubHandle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX,
+ connInfo, (int) connInfo.byteSize(), connInfo, (int) connInfo.byteSize(), sizeHolder, NULL) == 0)
+ throwLastError(errorState, "internal error (getting device descriptor failed)");
+
+ var descriptorSegment = USB_NODE_CONNECTION_INFORMATION_EX.DeviceDescriptor(connInfo);
+ var deviceDescriptor = new DeviceDescriptor(descriptorSegment);
+
+ var vendorId = deviceDescriptor.vendorID();
+ var productId = deviceDescriptor.productID();
+
+ var configDesc = getDescriptor(hubHandle, usbPortNum, CONFIGURATION_DESCRIPTOR_TYPE, 0, (short) 0, arena);
+
+ // create new device
+ var device = new WindowsUsbDevice(devicePath, vendorId, productId, configDesc, isComposite);
+ device.setFromDeviceDescriptor(descriptorSegment);
+
+ var languages = getLanguages(hubHandle, usbPortNum, arena);
+ device.setProductString(descriptorSegment, index -> getStringDescriptor(hubHandle, usbPortNum, index, languages));
+ return device;
+ }
+ }
+
+ private MemorySegment getDescriptor(MemorySegment hubHandle, int usbPortNumber, int descriptorType, int index,
+ short languageID, Arena arena) {
+ return getDescriptor(hubHandle, usbPortNumber, descriptorType, index, languageID, 0, arena);
+
+ }
+
+ private MemorySegment getDescriptor(MemorySegment hubHandle, int usbPortNumber, int descriptorType, int index,
+ short languageID, int requestSize, Arena arena) {
+ var size = requestSize != 0 ? requestSize + (int) REQUEST_DATA_OFFSET : 256;
+
+ // create descriptor requests
+ var descriptorRequest = arena.allocate(size);
+ USB_DESCRIPTOR_REQUEST.ConnectionIndex(descriptorRequest, usbPortNumber);
+ var setupPacket = new SetupPacket(descriptorRequest.asSlice(
+ USB_DESCRIPTOR_REQUEST.SetupPacket_bmRequest$offset(), SetupPacket.LAYOUT.byteSize()));
+ setupPacket.setRequestType(0x80); // device-to-host / type standard / recipient device
+ setupPacket.setRequest(USB_REQUEST_GET_DESCRIPTOR);
+ setupPacket.setValue((descriptorType << 8) | index);
+ setupPacket.setIndex(languageID);
+ setupPacket.setLength(size - (int) REQUEST_DATA_OFFSET);
+
+ // execute request
+ var effectiveSizeHolder = arena.allocate(JAVA_INT);
+ var errorState = allocateErrorState(arena);
+ if (DeviceIoControl(errorState, hubHandle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,
+ descriptorRequest, size, descriptorRequest, size, effectiveSizeHolder, NULL) == 0)
+ throwLastError(errorState, "internal error (retrieving descriptor %d failed)", index);
+
+ // determine size of descriptor
+ int expectedSize;
+ if (descriptorType != CONFIGURATION_DESCRIPTOR_TYPE) {
+ expectedSize = 255 & descriptorRequest.get(JAVA_BYTE, REQUEST_DATA_OFFSET);
+ } else {
+ var configDesc =
+ new ConfigurationDescriptor(descriptorRequest.asSlice(REQUEST_DATA_OFFSET, ConfigurationDescriptor.LAYOUT.byteSize()));
+ expectedSize = configDesc.totalLength();
+ }
+
+ // check against effective size
+ var effectiveSize = effectiveSizeHolder.get(JAVA_INT, 0) - REQUEST_DATA_OFFSET;
+ if (effectiveSize != expectedSize) {
+ if (requestSize != 0)
+ throwException("internal error (unexpected descriptor size)");
+
+ // repeat with correct size
+ return getDescriptor(hubHandle, usbPortNumber, descriptorType, index, languageID, expectedSize, arena);
+ }
+
+ return descriptorRequest.asSlice(REQUEST_DATA_OFFSET, effectiveSize);
+ }
+
+ @SuppressWarnings("java:S106")
+ private String getStringDescriptor(MemorySegment hubHandle, int usbPortNumber, int index, short[] languages) {
+ if (index == 0)
+ return null;
+
+ try (var arena = Arena.ofConfined()) {
+ for (var language : languages) {
+ try {
+ var stringDesc = new StringDescriptor(getDescriptor(hubHandle, usbPortNumber, STRING_DESCRIPTOR_TYPE,
+ index, language, arena));
+ return stringDesc.string();
+
+ } catch (UsbException _) {
+ // ignore and try next language
+ }
+ }
+ }
+
+ // Even though this function is only called for string descriptors referenced in the
+ // configuration descriptor, some device might not provide them; so ignore it.
+ return null;
+ }
+
+ private short[] getLanguages(MemorySegment hubHandle, int usbPortNumber, Arena arena) {
+ try {
+ var languages = getDescriptor(hubHandle, usbPortNumber, STRING_DESCRIPTOR_TYPE, 0, (short) 0, arena);
+ var n = (languages.byteSize() - 2) / 2;
+ if (n == 0)
+ return new short[]{DEFAULT_LANGUAGE};
+ return languages.asSlice(2, n * 2).toArray(JAVA_SHORT);
+ } catch (UsbException _) {
+ return new short[]{DEFAULT_LANGUAGE};
+ }
+ }
+
+ @SuppressWarnings("java:S1144")
+ private long handleWindowMessage(MemorySegment hWnd, int uMsg, long wParam, long lParam) {
+
+ // check for message related to connecting/disconnecting devices
+ if (uMsg == WM_DEVICECHANGE && (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE)) {
+ var data = MemorySegment.ofAddress(lParam).reinterpret(DEV_BROADCAST_DEVICEINTERFACE_W.sizeof());
+ if (DEV_BROADCAST_HDR.dbch_devicetype(data) == DBT_DEVTYP_DEVICEINTERFACE) {
+
+ // get device path
+ var nameSlice =
+ MemorySegment.ofAddress(DEV_BROADCAST_DEVICEINTERFACE_W.dbcc_name(data).address()).reinterpret(500);
+ var devicePath = nameSlice.getString(0, UTF_16LE);
+ if (wParam == DBT_DEVICEARRIVAL)
+ onDeviceConnected(devicePath);
+ else
+ onDeviceDisconnected(devicePath);
+ return 0;
+ }
+ }
+
+ // default message handling
+ return DefWindowProcW(hWnd, uMsg, wParam, lParam);
+ }
+
+ @SuppressWarnings("java:S106")
+ private void onDeviceConnected(String devicePath) {
+ try (var cleanup = new ScopeCleanup();
+ var deviceInfoSet = DeviceInfoSet.ofPath(devicePath)) {
+
+ // ensure all hubs are closed later
+ final var hubHandles = new HashMap();
+ cleanup.add(() -> hubHandles.forEach((_, handle) -> CloseHandle(handle)));
+
+ try {
+ // create device instance
+ var device = createDeviceFromDeviceInfo(deviceInfoSet, devicePath, hubHandles);
+
+ // add it to device list
+ addDevice(device);
+
+ } catch (Exception e) {
+ LOG.log(INFO, String.format("failed to retrieve information about device %s - ignoring device", devicePath), e);
+ }
+ }
+ }
+
+ private void onDeviceDisconnected(String devicePath) {
+ closeAndRemoveDevice(devicePath);
+ }
+
+ /**
+ * Finds the index of the device in the list.
+ *
+ * This override uses a case-insensitive string comparison as Windows uses different casing
+ * when initially enumerating devices and during later monitoring.
+ *
- * This code is manually created to include the additional parameters for capturing
- * {@code GetLastError()} until jextract catches up and can generate the corresponding code.
- *
- * This code is manually created to include the additional parameters for capturing
- * {@code GetLastError()} until jextract catches up and can generate the corresponding code.
- *
- * This code is manually created to include the additional parameters for capturing
- * {@code GetLastError()} until jextract catches up and can generate the corresponding code.
- *
- * This code is manually created to include the additional parameters for capturing
- * {@code GetLastError()} until jextract catches up and can generate the corresponding code.
- *