From a9436557fe60a1afcb3782d6ef4e02259c21d2db Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Tue, 12 Dec 2023 22:22:39 -0800 Subject: [PATCH] mroe refactoring toward simpler, cleaner code. --- src/api/flash/dfu-util.js | 476 +++++++--------------- src/api/flash/dfu.js | 822 +++++++++++++++++++------------------- 2 files changed, 560 insertions(+), 738 deletions(-) diff --git a/src/api/flash/dfu-util.js b/src/api/flash/dfu-util.js index 7812a386a..5a0e8ec9c 100644 --- a/src/api/flash/dfu-util.js +++ b/src/api/flash/dfu-util.js @@ -18,83 +18,19 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +import { DFUDeviceState, DFUUSBDevice, DFUDescriptorType, DFU } from "./dfu"; var device = null; (function () { "use strict"; - function hex4(n) { - let s = n.toString(16); - while (s.length < 4) { - s = "0" + s; - } - return s; - } - - function hexAddr8(n) { - let s = n.toString(16); - while (s.length < 8) { - s = "0" + s; - } - return "0x" + s; - } - - function niceSize(n) { - const gigabyte = 1024 * 1024 * 1024; - const megabyte = 1024 * 1024; - const kilobyte = 1024; - if (n >= gigabyte) { - return n / gigabyte + "GiB"; - } else if (n >= megabyte) { - return n / megabyte + "MiB"; - } else if (n >= kilobyte) { - return n / kilobyte + "KiB"; - } else { - return n + "B"; - } - } - - function formatDFUSummary(device) { - const vid = hex4(device.device_.vendorId); - const pid = hex4(device.device_.productId); - const name = device.device_.productName; - - let mode = "Unknown"; - if (device.settings.alternate.interfaceProtocol == 0x01) { - mode = "Runtime"; - } else if (device.settings.alternate.interfaceProtocol == 0x02) { - mode = "DFU"; - } - - const cfg = device.settings.configuration.configurationValue; - const intf = device.settings["interface"].interfaceNumber; - const alt = device.settings.alternate.alternateSetting; - const serial = device.device_.serialNumber; - const info = `${mode}: [${vid}:${pid}] cfg=${cfg}, intf=${intf}, alt=${alt}, name="${name}" serial="${serial}"`; - return info; - } - function formatDFUInterfaceAlternate(settings) { - let mode = "Unknown"; - if (settings.alternate.interfaceProtocol == 0x01) { - mode = "Runtime"; - } else if (settings.alternate.interfaceProtocol == 0x02) { - mode = "DFU"; - } - - const cfg = settings.configuration.configurationValue; - const intf = settings["interface"].interfaceNumber; - const alt = settings.alternate.alternateSetting; - const name = settings.name ? settings.name : "UNKNOWN"; - - return `${mode}: cfg=${cfg}, intf=${intf}, alt=${alt}, name="${name}"`; - } async function fixInterfaceNames(device_, interfaces) { // Check if any interface names were not read correctly if (interfaces.some((intf) => intf.name == null)) { // Manually retrieve the interface name string descriptors - const tempDevice = new dfu.Device(device_, interfaces[0]); + const tempDevice = new DFUUSBDevice(device_, interfaces[0]); await tempDevice.device_.open(); await tempDevice.device_.selectConfiguration(1); const mapping = await tempDevice.readInterfaceNames(); @@ -128,8 +64,7 @@ var device = null; radio.required = true; const label = document.createElement("label"); - label.textContent = formatDFUInterfaceAlternate(interfaces[i]); - label.className = "radio"; +\ label.className = "radio"; label.setAttribute("for", "interface" + i); const div = document.createElement("div"); @@ -139,121 +74,52 @@ var device = null; } } - function getDFUDescriptorProperties(device) { - // Attempt to read the DFU functional descriptor - // TODO: read the selected configuration's descriptor - return device.readConfigurationDescriptor(0).then( - (data) => { - const configDesc = dfu.parseConfigurationDescriptor(data); - let funcDesc = null; - const configValue = device.settings.configuration.configurationValue; - if (configDesc.bConfigurationValue == configValue) { - for (const desc of configDesc.descriptors) { - if ( - desc.bDescriptorType == 0x21 && - desc.hasOwnProperty("bcdDFUVersion") - ) { - funcDesc = desc; - break; - } + async function getDFUDescriptorProperties(device) { + try { + const data = await device.readConfigurationDescriptor(0); + const configDesc = USBParser.parseConfigurationDescriptor(data); + let funcDesc = null; + const configValue = device.settings.configuration.configurationValue; + + if (configDesc.bConfigurationValue === configValue) { + for (const desc of configDesc.descriptors) { + if ( + desc.bDescriptorType === DFUDescriptorType.DFU_FUNCTIONAL && + desc.hasOwnProperty("bcdDFUVersion") + ) { + funcDesc = desc; + break; } } - - if (funcDesc) { - return { - WillDetach: (funcDesc.bmAttributes & 0x08) != 0, - ManifestationTolerant: (funcDesc.bmAttributes & 0x04) != 0, - CanUpload: (funcDesc.bmAttributes & 0x02) != 0, - CanDnload: (funcDesc.bmAttributes & 0x01) != 0, - TransferSize: funcDesc.wTransferSize, - DetachTimeOut: funcDesc.wDetachTimeOut, - DFUVersion: funcDesc.bcdDFUVersion, - }; - } else { - return {}; - } - }, - (error) => {} - ); - } - - // Current log div element to append to - let logContext = null; - - function setLogContext(div) { - logContext = div; - } - - function clearLog(context) { - if (typeof context === "undefined") { - context = logContext; - } - if (context) { - context.innerHTML = ""; - } - } - - function logDebug(msg) { - console.log(msg); - } - - function logInfo(msg) { - console.log(msg); - - if (logContext) { - const info = document.createElement("p"); - info.className = "info"; - info.textContent = msg; - logContext.appendChild(info); - } - } - - function logWarning(msg) { - console.log(msg); - - if (logContext) { - const warning = document.createElement("p"); - warning.className = "warning"; - warning.textContent = msg; - logContext.appendChild(warning); - } - } - - function logError(msg) { - console.log(msg); - - if (logContext) { - const error = document.createElement("p"); - error.className = "error"; - error.textContent = msg; - logContext.appendChild(error); - } - } - - function logProgress(done, total) { - if (logContext) { - let progressBar; - if (logContext.lastChild.tagName.toLowerCase() == "progress") { - progressBar = logContext.lastChild; - } - if (!progressBar) { - progressBar = document.createElement("progress"); - logContext.appendChild(progressBar); } - progressBar.value = done; - if (typeof total !== "undefined") { - progressBar.max = total; + + if (funcDesc) { + return { + WillDetach: (funcDesc.bmAttributes & 0x08) !== 0, + ManifestationTolerant: (funcDesc.bmAttributes & 0x04) !== 0, + CanUpload: (funcDesc.bmAttributes & 0x02) !== 0, + CanDnload: (funcDesc.bmAttributes & 0x01) !== 0, + TransferSize: funcDesc.wTransferSize, + DetachTimeOut: funcDesc.wDetachTimeOut, + DFUVersion: funcDesc.bcdDFUVersion, + }; + } else { + return {}; } + } catch (error) { + // Handle or log the error as needed + console.error("Error reading DFU descriptor: ", error); + return {}; } } + document.addEventListener("DOMContentLoaded", (event) => { const connectButton = document.querySelector("#connect"); const downloadButton = document.querySelector("#download"); const statusDisplay = document.querySelector("#status"); const infoDisplay = document.querySelector("#usbInfo"); const dfuDisplay = document.querySelector("#dfuInfo"); - const vidField = document.querySelector("#vid"); const interfaceDialog = document.querySelector("#interfaceDialog"); const interfaceForm = document.querySelector("#interfaceForm"); @@ -269,7 +135,6 @@ var device = null; } else { vid = parseInt(vidString, 10); } - vidField.value = "0x" + hex4(vid).toUpperCase(); fromLandingPage = true; } catch (error) { console.log("Bad VID " + vidString + ":" + error); @@ -287,12 +152,9 @@ var device = null; fromLandingPage = true; } - const configForm = document.querySelector("#configForm"); - let transferSize = 1024; let firmwareFile = null; - const downloadLog = document.querySelector("#downloadLog"); const firmwareVersionSelect = document.getElementById("firmwareVersion"); let manifestationTolerant = true; @@ -313,14 +175,7 @@ var device = null; //let device; function onDisconnect(reason) { - if (reason) { - statusDisplay.textContent = reason; - } - - connectButton.textContent = "Connect"; - infoDisplay.textContent = ""; - dfuDisplay.textContent = ""; - downloadButton.disabled = true; + console.log("Disconnected: " + reason); } function onUnexpectedDisconnect(event) { @@ -332,36 +187,33 @@ var device = null; } } } - - function detach() { + async function detach() { if (device) { - device.detach().then( - async (len) => { - let detached = false; - try { - await device.close(); - await device.waitDisconnected(5000); - detached = true; - } catch (err) { - console.log("Detach failed: " + err); - } + try { + await device.detach(); + let detached = false; - onDisconnect(); - device = null; - if (detached) { - // Wait a few seconds and try reconnecting - setTimeout(autoConnect, 5000); - } - }, - async (error) => { + try { await device.close(); - onDisconnect(error); - device = null; + await device.waitDisconnected(5000); + detached = true; + } catch (err) { + console.log("Detach failed: " + err); } - ); + + onDisconnect(); + device = null; + + if (detached) { + setTimeout(autoConnect, 5000); // Wait a few seconds and try reconnecting + } + } catch (error) { + await device.close(); + onDisconnect(error); + device = null; + } } } - async function connect(device) { try { await device.open(); @@ -379,32 +231,15 @@ var device = null; throw error; } - const memorySummary = ""; if (desc && Object.keys(desc).length > 0) { device.properties = desc; - const info = `WillDetach=${desc.WillDetach}, ManifestationTolerant=${ - desc.ManifestationTolerant - }, CanUpload=${desc.CanUpload}, CanDnload=${ - desc.CanDnload - }, TransferSize=${desc.TransferSize}, DetachTimeOut=${ - desc.DetachTimeOut - }, Version=${hex4(desc.DFUVersion)}`; - dfuDisplay.textContent += "\n" + info; + transferSize = desc.TransferSize; if (desc.CanDnload) { manifestationTolerant = desc.ManifestationTolerant; } } - // Bind logging methods - device.logDebug = logDebug; - device.logInfo = logInfo; - device.logWarning = logWarning; - device.logError = logError; - device.logProgress = logProgress; - - // Clear logs - clearLog(downloadLog); // Display basic USB information statusDisplay.textContent = ""; @@ -421,7 +256,7 @@ var device = null; "\n"; // Display basic dfu-util style info - dfuDisplay.textContent = formatDFUSummary(device) + "\n" + memorySummary; + dfuDisplay.textContent = ""; // Update buttons based on capabilities if (device.settings.alternate.interfaceProtocol == 0x01) { @@ -434,103 +269,86 @@ var device = null; return device; } - function autoConnect(vid, serial) { - dfu.findAllDfuInterfaces().then(async (dfu_devices) => { - const matching_devices = []; - for (const dfu_device of dfu_devices) { + async function autoConnect(vid, serial) { + try { + const dfu_devices = await DFU.findAllDfuInterfaces(); + const matching_devices = dfu_devices.filter((dfu_device) => { if (serial) { - if (dfu_device.device_.serialNumber == serial) { - matching_devices.push(dfu_device); - } - } else if (dfu_device.device_.vendorId == vid) { - matching_devices.push(dfu_device); + return dfu_device.device_.serialNumber === serial; + } else { + return dfu_device.device_.vendorId === vid; } - } + }); - if (matching_devices.length == 0) { - statusDisplay.textContent = "No device found."; + if (matching_devices.length === 0) { + console.log("No device found." ); } else { - if (matching_devices.length == 1) { - statusDisplay.textContent = "Connecting..."; + if (matching_devices.length === 1) { + console.log("Connecting"); device = matching_devices[0]; console.log(device); device = await connect(device); } else { - statusDisplay.textContent = "Multiple DFU interfaces found."; + console.log( "Multiple DFU interfaces found."); } - vidField.value = - "0x" + hex4(matching_devices[0].device_.vendorId).toUpperCase(); + vid = matching_devices[0].device_.vendorId; } - }); + } catch (error) { + statusDisplay.textContent = `Error in autoConnect: ${error}`; + } } - vidField.addEventListener("change", function () { - vid = parseInt(vidField.value, 16); - }); - connectButton.addEventListener("click", clickConnect); - const clickConnect = () => { - if (device) { - device.close().then(onDisconnect); - device = null; - } else { - const filters = []; - if (serial) { - filters.push({ serialNumber: serial }); - } else if (vid) { - filters.push({ vendorId: vid }); - } - navigator.usb - .requestDevice({ filters: filters }) - .then(async (selectedDevice) => { - const interfaces = dfu.findDeviceDfuInterfaces(selectedDevice); - console.log(selectedDevice.productId); - - if (selectedDevice.productId == 0x0006) { - console.log(selectedDevice); - document.getElementById("found-device-need-bootloader").set; - statusDisplay.textContent = "Your Model 100 is in keyboard mode"; - } else if (interfaces.length == 0) { - console.log(selectedDevice); - statusDisplay.textContent = - "The sxelected device does not have any USB DFU interfaces."; - } else if (interfaces.length == 1) { - await fixInterfaceNames(selectedDevice, interfaces); + const clickConnect = async () => { + try { + if (device) { + await device.close(); + onDisconnect(); + device = null; + } else { + const filters = []; + if (serial) { + filters.push({ serialNumber: serial }); + } else if (vid) { + filters.push({ vendorId: vid }); + } + + const selectedDevice = await navigator.usb.requestDevice({ filters }); + const interfaces = DFU.findDeviceDfuInterfaces(selectedDevice); + console.log(selectedDevice.productId); + + if (selectedDevice.productId === 0x0006) { + console.log(selectedDevice); + document.getElementById("found-device-need-bootloader").set; + statusDisplay.textContent = "Your Model 100 is in keyboard mode"; + } else if (interfaces.length === 0) { + console.log(selectedDevice); + console.log( "The selected device does not have any USB DFU interfaces."); + } else { + await fixInterfaceNames(selectedDevice, interfaces); + + if (interfaces.length === 1) { device = await connect( - new dfu.Device(selectedDevice, interfaces[0]) + new DFUUSBDevice(selectedDevice, interfaces[0]) ); } else { - await fixInterfaceNames(selectedDevice, interfaces); populateInterfaceList(interfaceForm, selectedDevice, interfaces); - async function connectToSelectedInterface() { - interfaceForm.removeEventListener("submit", this); + interfaceForm.addEventListener("submit", async (event) => { + event.preventDefault(); const index = interfaceForm.elements["interfaceIndex"].value; device = await connect( - new dfu.Device(selectedDevice, interfaces[index]) - ); - } - - interfaceForm.addEventListener( - "submit", - connectToSelectedInterface - ); - - interfaceDialog.addEventListener("cancel", function () { - interfaceDialog.removeEventListener("cancel", this); - interfaceForm.removeEventListener( - "submit", - connectToSelectedInterface + new DFUUSBDevice(selectedDevice, interfaces[index]) ); }); interfaceDialog.showModal(); } - }) - .catch((error) => { - statusDisplay.textContent = error; - }); + } + } + } catch (error) { + statusDisplay.textContent = error; } }; @@ -538,65 +356,53 @@ var device = null; const clickDownload = async (event) => { event.preventDefault(); event.stopPropagation(); - if (!configForm.checkValidity()) { - configForm.reportValidity(); - return false; - } firmwareFile = await fetchSelectedFirmware(); if (device && firmwareFile != null) { - setLogContext(downloadLog); - clearLog(downloadLog); try { const status = await device.getStatus(); - if (status.state == dfu.dfuERROR) { + if (status.state === DFUDeviceState.dfuERROR) { await device.clearStatus(); } - } catch (error) { - device.logWarning("Failed to clear status"); - } - await device - .do_download(transferSize, firmwareFile, manifestationTolerant) - .then( - () => { - setLogContext(null); - if (!manifestationTolerant) { - device.waitDisconnected(5000).then( - (dev) => { - onDisconnect(); - device = null; - }, - (error) => { - // It didn't reset and disconnect for some reason... - console.log("Device unexpectedly tolerated manifestation."); - } - ); - } else { - detach(); + + try { + await device.do_download( + transferSize, + firmwareFile, + manifestationTolerant + ); + if (!manifestationTolerant) { + try { + await device.waitDisconnected(5000); + onDisconnect(); + device = null; + } catch (error) { + console.log("Device unexpectedly tolerated manifestation."); } - }, - (error) => { - logError(error); - setLogContext(null); + } else { + detach(); } - ); + } catch (error) { + console.error(error); + } + } catch (error) { + console.error("Failed to clear status"); + } } else { console.log("No device or firmware file", device, firmwareFile); } - - //return false; }; // Check if WebUSB is available - if (typeof navigator.usb !== "undefined") { + if (typeof navigator.usb === "undefined") { + statusDisplay.textContent = "WebUSB not available."; + connectButton.disabled = true; + } else { navigator.usb.addEventListener("disconnect", onUnexpectedDisconnect); // Try connecting automatically if (fromLandingPage) { autoConnect(vid, serial); } - } else { - statusDisplay.textContent = "WebUSB not available."; - connectButton.disabled = true; } }); })(); diff --git a/src/api/flash/dfu.js b/src/api/flash/dfu.js index 9b8526584..2049224a6 100644 --- a/src/api/flash/dfu.js +++ b/src/api/flash/dfu.js @@ -18,102 +18,212 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -var dfu = {}; - -(function () { - "use strict"; - - dfu.DETACH = 0x00; - dfu.DNLOAD = 0x01; - dfu.UPLOAD = 0x02; - dfu.GETSTATUS = 0x03; - dfu.CLRSTATUS = 0x04; - dfu.GETSTATE = 0x05; - dfu.ABORT = 6; - - dfu.appIDLE = 0; - dfu.appDETACH = 1; - dfu.dfuIDLE = 2; - dfu.dfuDNLOAD_SYNC = 3; - dfu.dfuDNBUSY = 4; - dfu.dfuDNLOAD_IDLE = 5; - dfu.dfuMANIFEST_SYNC = 6; - dfu.dfuMANIFEST = 7; - dfu.dfuMANIFEST_WAIT_RESET = 8; - dfu.dfuUPLOAD_IDLE = 9; - dfu.dfuERROR = 10; - - dfu.STATUS_OK = 0x0; - - dfu.Device = function (device, settings) { - this.device_ = device; - this.settings = settings; - this.intfNumber = settings["interface"].interfaceNumber; - }; +const DFUDeviceState = { + appIDLE: 0, // Device is running its normal application. + appDETACH: 1, // Device is running its normal application, has received the DFU_DETACH request, and is waiting for a USB reset. + dfuIDLE: 2, // Device is operating in the DFU mode and is waiting for requests. + dfuDNLOAD_SYNC: 3, // Device has received a block and is waiting for the host to solicit the status via DFU_GETSTATUS. + dfuDNBUSY: 4, // Device is programming a control-write block into its nonvolatile memories. + dfuDNLOAD_IDLE: 5, // Device is processing a download operation. Expecting DFU_DNLOAD requests. + dfuMANIFEST_SYNC: 6, // 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.) + dfuMANIFEST: 7, // Device is in the Manifestation phase. (Not all devices will be able to respond to DFU_GETSTATUS when in this state.) + dfuMANIFEST_WAIT_RESET: 8, // 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.) + dfuUPLOAD_IDLE: 9, // The device is processing an upload operation. Expecting DFU_UPLOAD requests. + dfuERROR: 10, // An error has occurred. Awaiting the DFU_CLRSTATUS request. +}; + +const USBTransferResult = { + OK: "ok", +}; + +const DFUDescriptorType = { + DEVICE: 1, + CONFIGURATION: 2, + STRING: 3, + INTERFACE: 4, + ENDPOINT: 5, + DFU_FUNCTIONAL: 0x21, +}; +const USBRequest = { + GET_DESCRIPTOR: 0x06, +}; + +const USBRequestType = { + STANDARD: "standard", + CLASS: "class", + VENDOR: "vendor", + RESERVED: "reserved", +}; + +const USBRecipient = { + DEVICE: "device", + INTERFACE: "interface", + ENDPOINT: "endpoint", + OTHER: "other", +}; + +const USBClass = { + APP_SPECIFIC: 0xfe, +}; + +const USBSubclass = { + DFU: 0x01, +}; + +const DFUCommand = { + DETACH: 0, + DNLOAD: 1, + UPLOAD: 2, + GETSTATUS: 3, + CLRSTATUS: 4, + GETSTATE: 5, + ABORT: 6, +}; + +const DFUDeviceStatus = { + OK: 0x00, // No error condition is present. + errTARGET: 0x01, // File is not targeted for use by this device. + errFILE: 0x02, // File is for this device but fails some vendor-specific verification test. + errWRITE: 0x03, // Device is unable to write memory. + errERASE: 0x04, // Memory erase function failed. + errCHECK_ERASED: 0x05, // Memory erase check failed. + errPROG: 0x06, // Program memory function failed. + errVERIFY: 0x07, // Programmed memory failed verification. + errADDRESS: 0x08, // Cannot program memory due to received address that is out of range. + errNOTDONE: 0x09, // Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet. + errFIRMWARE: 0x0a, // Device’s firmware is corrupt. It cannot return to run-time (non-DFU) operations. + errVENDOR: 0x0b, // iString indicates a vendor-specific error. + errUSBR: 0x0c, // Device detected unexpected USB reset signaling. + errPOR: 0x0d, // Device detected unexpected power on reset. + errUNKNOWN: 0x0e, // Something went wrong, but the device does not know what it was. + errSTALLEDPKT: 0x0f, // Device stalled an unexpected request. +}; + +const USBParser = { + parseFunctionalDescriptor: function (data) { + return { + bLength: data.getUint8(0), + bDescriptorType: data.getUint8(1), + bmAttributes: data.getUint8(2), + wDetachTimeOut: data.getUint16(3, true), + wTransferSize: data.getUint16(5, true), + bcdDFUVersion: data.getUint16(7, true), + }; + }, + parseInterfaceDescriptor: function (data) { + return { + bLength: data.getUint8(0), + bDescriptorType: data.getUint8(1), + bInterfaceNumber: data.getUint8(2), + bAlternateSetting: data.getUint8(3), + bNumEndpoints: data.getUint8(4), + bInterfaceClass: data.getUint8(5), + bInterfaceSubClass: data.getUint8(6), + bInterfaceProtocol: data.getUint8(7), + iInterface: data.getUint8(8), + descriptors: [], + }; + }, - dfu.findDeviceDfuInterfaces = function (device) { - const interfaces = []; - for (const conf of device.configurations) { - for (const intf of conf.interfaces) { - for (const alt of intf.alternates) { - if ( - alt.interfaceClass == 0xfe && - alt.interfaceSubclass == 0x01 && - (alt.interfaceProtocol == 0x01 || alt.interfaceProtocol == 0x02) - ) { - const settings = { - configuration: conf, - interface: intf, - alternate: alt, - name: alt.interfaceName, - }; - interfaces.push(settings); - } - } - } - } + parseConfigurationDescriptor: function (data) { + const descriptorData = new DataView(data.buffer.slice(9)); + const descriptors = this.parseSubDescriptors(descriptorData); // Updated reference + return { + bLength: data.getUint8(0), + bDescriptorType: data.getUint8(1), + wTotalLength: data.getUint16(2, true), + bNumInterfaces: data.getUint8(4), + bConfigurationValue: data.getUint8(5), + iConfiguration: data.getUint8(6), + bmAttributes: data.getUint8(7), + bMaxPower: data.getUint8(8), + descriptors: descriptors, + }; + }, - return interfaces; - }; + parseDeviceDescriptor: function (data) { + return { + bLength: data.getUint8(0), + bDescriptorType: data.getUint8(1), + bcdUSB: data.getUint16(2, true), + bDeviceClass: data.getUint8(4), + bDeviceSubClass: data.getUint8(5), + bDeviceProtocol: data.getUint8(6), + bMaxPacketSize: data.getUint8(7), + idVendor: data.getUint16(8, true), + idProduct: data.getUint16(10, true), + bcdDevice: data.getUint16(12, true), + iManufacturer: data.getUint8(14), + iProduct: data.getUint8(15), + iSerialNumber: data.getUint8(16), + bNumConfigurations: data.getUint8(17), + }; + }, - dfu.findAllDfuInterfaces = function () { - return navigator.usb.getDevices().then((devices) => { - const matches = []; - for (const device of devices) { - const interfaces = dfu.findDeviceDfuInterfaces(device); - for (const interface_ of interfaces) { - matches.push(new dfu.Device(device, interface_)); + parseSubDescriptors: function (descriptorData) { + let remainingData = descriptorData; + const descriptors = []; + let currentInterface; + let inDfuInterface = false; + while (remainingData.byteLength > 2) { + const bLength = remainingData.getUint8(0); + const bDescriptorType = remainingData.getUint8(1); + const descriptorData = new DataView( + remainingData.buffer.slice(0, bLength) + ); + if (bDescriptorType == DFUDescriptorType.INTERFACE) { + currentInterface = this.parseInterfaceDescriptor(descriptorData); + if ( + currentInterface.bInterfaceClass == USBClass.APP_SPECIFIC && + currentInterface.bInterfaceSubClass == USBSubclass.DFU + ) { + inDfuInterface = true; + } else { + inDfuInterface = false; + } + descriptors.push(currentInterface); + } else if ( + inDfuInterface && + bDescriptorType == DFUDescriptorType.DFU_FUNCTIONAL + ) { + const functionalDescriptor = + this.parseFunctionalDescriptor(descriptorData); + descriptors.push(functionalDescriptor); + currentInterface.descriptors.push(functionalDescriptor); + } else { + const descriptor = { + bLength: bLength, + bDescriptorType: bDescriptorType, + data: descriptorData, + }; + descriptors.push(descriptor); + if (currentInterface) { + currentInterface.descriptors.push(descriptor); } } - return matches; - }); - }; - - dfu.Device.prototype.logDebug = function (msg) { - console.log("Debug: ", msg); - }; - - dfu.Device.prototype.logInfo = function (msg) { - console.log(msg); - }; + remainingData = new DataView(remainingData.buffer.slice(bLength)); + } - dfu.Device.prototype.logWarning = function (msg) { - console.log(msg); - }; + return descriptors; + }, +}; - dfu.Device.prototype.logError = function (msg) { - console.log(msg); - }; +class DFUUSBDevice { + constructor(device, settings) { + this.device_ = device; + this.settings = settings; + this.intfNumber = settings["interface"].interfaceNumber; + } - dfu.Device.prototype.logProgress = function (done, total) { + logProgress(done, total) { if (typeof total === "undefined") { console.log(done); } else { - console.log(done + "/" + total); + console.log(`${done}/${total}`); } - }; + } - dfu.Device.prototype.open = async function () { + async open() { await this.device_.open(); const confValue = this.settings.configuration.configurationValue; if ( @@ -129,7 +239,7 @@ var dfu = {}; } const altSetting = this.settings.alternate.alternateSetting; - let intf = this.device_.configuration.interfaces[intfNumber]; + const intf = this.device_.configuration.interfaces[intfNumber]; if ( intf.alternate === null || intf.alternate.alternateSetting != altSetting || @@ -142,7 +252,7 @@ var dfu = {}; intf.alternate.alternateSetting == altSetting && error.endsWith("Unable to set device interface.") ) { - this.logWarning( + console.warn( `Redundant SET_INTERFACE request to select altSetting ${altSetting} failed` ); } else { @@ -150,54 +260,43 @@ var dfu = {}; } } } - }; + } - dfu.Device.prototype.close = async function () { + async close() { try { await this.device_.close(); } catch (error) { console.log(error); } - }; - - dfu.Device.prototype.readDeviceDescriptor = function () { - const GET_DESCRIPTOR = 0x06; - const DT_DEVICE = 0x01; - const wValue = DT_DEVICE << 8; - - return this.device_ - .controlTransferIn( - { - requestType: "standard", - recipient: "device", - request: GET_DESCRIPTOR, - value: wValue, - index: 0, - }, - 18 - ) - .then((result) => { - if (result.status == "ok") { - return Promise.resolve(result.data); - } else { - return Promise.reject(result.status); - } - }); - }; + } + async readDeviceDescriptor() { + const wValue = DFUDescriptorType.DEVICE << 8; + + const result = await this.device_.controlTransferIn( + { + requestType: USBRequestType.STANDARD, + recipient: USBRecipient.DEVICE, + request: USBRequest.GET_DESCRIPTOR, + value: wValue, + index: 0, + }, + 18 + ); - dfu.Device.prototype.readStringDescriptor = async function (index, langID) { - if (typeof langID === "undefined") { - langID = 0; + if (result.status == USBTransferResult.OK) { + return result.data; + } else { + throw new Error(result.status); } + } - const GET_DESCRIPTOR = 0x06; - const DT_STRING = 0x03; - const wValue = (DT_STRING << 8) | index; + async readStringDescriptor(index, langID = 0) { + const wValue = (DFUDescriptorType.STRING << 8) | index; const request_setup = { - requestType: "standard", - recipient: "device", - request: GET_DESCRIPTOR, + requestType: USBRequestType.STANDARD, + recipient: USBRecipient.DEVICE, + request: USBRequest.GET_DESCRIPTOR, value: wValue, index: langID, }; @@ -205,11 +304,11 @@ var dfu = {}; // Read enough for bLength var result = await this.device_.controlTransferIn(request_setup, 1); - if (result.status == "ok") { + if (result.status == USBTransferResult.OK) { // Retrieve the full descriptor const bLength = result.data.getUint8(0); result = await this.device_.controlTransferIn(request_setup, bLength); - if (result.status == "ok") { + if (result.status == USBTransferResult.OK) { const len = (bLength - 2) / 2; const u16_words = []; for (let i = 0; i < len; i++) { @@ -226,11 +325,9 @@ var dfu = {}; } throw `Failed to read string descriptor ${index}: ${result.status}`; - }; - - dfu.Device.prototype.readInterfaceNames = async function () { - const DT_INTERFACE = 4; + } + async readInterfaceNames() { const configs = {}; const allStringIndices = new Set(); for ( @@ -239,13 +336,13 @@ var dfu = {}; configIndex++ ) { const rawConfig = await this.readConfigurationDescriptor(configIndex); - const configDesc = dfu.parseConfigurationDescriptor(rawConfig); + const configDesc = USBParser.parseConfigurationDescriptor(rawConfig); const configValue = configDesc.bConfigurationValue; configs[configValue] = {}; // Retrieve string indices for interface names for (const desc of configDesc.descriptors) { - if (desc.bDescriptorType == DT_INTERFACE) { + if (desc.bDescriptorType == DFUDescriptorType.INTERFACE) { if (!(desc.bInterfaceNumber in configs[configValue])) { configs[configValue][desc.bInterfaceNumber] = {}; } @@ -279,215 +376,101 @@ var dfu = {}; } return configs; - }; + } + async readConfigurationDescriptor(index) { + const wValue = (DFUDescriptorType.CONFIGURATION << 8) | index; - dfu.parseDeviceDescriptor = function (data) { - return { - bLength: data.getUint8(0), - bDescriptorType: data.getUint8(1), - bcdUSB: data.getUint16(2, true), - bDeviceClass: data.getUint8(4), - bDeviceSubClass: data.getUint8(5), - bDeviceProtocol: data.getUint8(6), - bMaxPacketSize: data.getUint8(7), - idVendor: data.getUint16(8, true), - idProduct: data.getUint16(10, true), - bcdDevice: data.getUint16(12, true), - iManufacturer: data.getUint8(14), - iProduct: data.getUint8(15), - iSerialNumber: data.getUint8(16), - bNumConfigurations: data.getUint8(17), - }; - }; - - dfu.parseConfigurationDescriptor = function (data) { - const descriptorData = new DataView(data.buffer.slice(9)); - const descriptors = dfu.parseSubDescriptors(descriptorData); - return { - bLength: data.getUint8(0), - bDescriptorType: data.getUint8(1), - wTotalLength: data.getUint16(2, true), - bNumInterfaces: data.getUint8(4), - bConfigurationValue: data.getUint8(5), - iConfiguration: data.getUint8(6), - bmAttributes: data.getUint8(7), - bMaxPower: data.getUint8(8), - descriptors: descriptors, - }; - }; - - dfu.parseInterfaceDescriptor = function (data) { - return { - bLength: data.getUint8(0), - bDescriptorType: data.getUint8(1), - bInterfaceNumber: data.getUint8(2), - bAlternateSetting: data.getUint8(3), - bNumEndpoints: data.getUint8(4), - bInterfaceClass: data.getUint8(5), - bInterfaceSubClass: data.getUint8(6), - bInterfaceProtocol: data.getUint8(7), - iInterface: data.getUint8(8), - descriptors: [], - }; - }; + try { + let result = await this.device_.controlTransferIn( + { + requestType: USBRequestType.STANDARD, + recipient: USBRecipient.DEVICE, + request: USBRequest.GET_DESCRIPTOR, + value: wValue, + index: 0, + }, + 4 + ); - dfu.parseFunctionalDescriptor = function (data) { - return { - bLength: data.getUint8(0), - bDescriptorType: data.getUint8(1), - bmAttributes: data.getUint8(2), - wDetachTimeOut: data.getUint16(3, true), - wTransferSize: data.getUint16(5, true), - bcdDFUVersion: data.getUint16(7, true), - }; - }; - - dfu.parseSubDescriptors = function (descriptorData) { - const DT_INTERFACE = 4; - const DT_ENDPOINT = 5; - const DT_DFU_FUNCTIONAL = 0x21; - const USB_CLASS_APP_SPECIFIC = 0xfe; - const USB_SUBCLASS_DFU = 0x01; - let remainingData = descriptorData; - const descriptors = []; - let currIntf; - let inDfuIntf = false; - while (remainingData.byteLength > 2) { - const bLength = remainingData.getUint8(0); - const bDescriptorType = remainingData.getUint8(1); - const descData = new DataView(remainingData.buffer.slice(0, bLength)); - if (bDescriptorType == DT_INTERFACE) { - currIntf = dfu.parseInterfaceDescriptor(descData); - if ( - currIntf.bInterfaceClass == USB_CLASS_APP_SPECIFIC && - currIntf.bInterfaceSubClass == USB_SUBCLASS_DFU - ) { - inDfuIntf = true; - } else { - inDfuIntf = false; - } - descriptors.push(currIntf); - } else if (inDfuIntf && bDescriptorType == DT_DFU_FUNCTIONAL) { - const funcDesc = dfu.parseFunctionalDescriptor(descData); - descriptors.push(funcDesc); - currIntf.descriptors.push(funcDesc); - } else { - const desc = { - bLength: bLength, - bDescriptorType: bDescriptorType, - data: descData, - }; - descriptors.push(desc); - if (currIntf) { - currIntf.descriptors.push(desc); - } + if (result.status !== USBTransferResult.OK) { + throw new Error(result.status); } - remainingData = new DataView(remainingData.buffer.slice(bLength)); - } - return descriptors; - }; - - dfu.Device.prototype.readConfigurationDescriptor = function (index) { - const GET_DESCRIPTOR = 0x06; - const DT_CONFIGURATION = 0x02; - const wValue = (DT_CONFIGURATION << 8) | index; + // Read out length of the configuration descriptor + const wLength = result.data.getUint16(2, true); - return this.device_ - .controlTransferIn( + result = await this.device_.controlTransferIn( { - requestType: "standard", - recipient: "device", - request: GET_DESCRIPTOR, + requestType: USBRequestType.STANDARD, + recipient: USBRecipient.DEVICE, + request: USBRequest.GET_DESCRIPTOR, value: wValue, index: 0, }, - 4 - ) - .then((result) => { - if (result.status == "ok") { - // Read out length of the configuration descriptor - let wLength = result.data.getUint16(2, true); - return this.device_.controlTransferIn( - { - requestType: "standard", - recipient: "device", - request: GET_DESCRIPTOR, - value: wValue, - index: 0, - }, - wLength - ); - } else { - return Promise.reject(result.status); - } - }) - .then((result) => { - if (result.status == "ok") { - return Promise.resolve(result.data); - } else { - return Promise.reject(result.status); - } - }); - }; + wLength + ); + + if (result.status !== USBTransferResult.OK) { + throw new Error(result.status); + } + + return result.data; + } catch (error) { + throw new Error("ControlTransferIn failed: " + error); + } + } - dfu.Device.prototype.requestOut = function (bRequest, data, wValue = 0) { - return this.device_ - .controlTransferOut( + async _requestOut(bRequest, data, wValue = 0) { + try { + const result = await this.device_.controlTransferOut( { - requestType: "class", - recipient: "interface", + requestType: USBRequestType.CLASS, + recipient: USBRecipient.INTERFACE, request: bRequest, value: wValue, index: this.intfNumber, }, data - ) - .then( - (result) => { - if (result.status == "ok") { - return Promise.resolve(result.bytesWritten); - } else { - return Promise.reject(result.status); - } - }, - (error) => { - return Promise.reject("ControlTransferOut failed: " + error); - } ); - }; - dfu.Device.prototype.requestIn = function (bRequest, wLength, wValue = 0) { - return this.device_ - .controlTransferIn( + if (result.status === USBTransferResult.OK) { + return result.bytesWritten; + } else { + throw new Error(result.status); + } + } catch (error) { + throw new Error("ControlTransferOut failed: " + error); + } + } + + async _requestIn(bRequest, wLength, wValue = 0) { + try { + const result = await this.device_.controlTransferIn( { - requestType: "class", - recipient: "interface", + requestType: USBRequestType.CLASS, + recipient: USBRecipient.INTERFACE, request: bRequest, value: wValue, index: this.intfNumber, }, wLength - ) - .then( - (result) => { - if (result.status == "ok") { - return Promise.resolve(result.data); - } else { - return Promise.reject(result.status); - } - }, - (error) => { - return Promise.reject("ControlTransferIn failed: " + error); - } ); - }; - dfu.Device.prototype.detach = function () { - return this.requestOut(dfu.DETACH, undefined, 1000); - }; + if (result.status === USBTransferResult.OK) { + return result.data; + } else { + throw new Error(result.status); + } + } catch (error) { + throw new Error("ControlTransferIn failed: " + error); + } + } - dfu.Device.prototype.waitDisconnected = async function (timeout) { + detach() { + return this._requestOut(DFUCommand.DETACH, undefined, 1000); + } + + async waitDisconnected(timeout) { const device = this; const usbDevice = this.device_; return new Promise(function (resolve, reject) { @@ -516,72 +499,66 @@ var dfu = {}; navigator.usb.addEventListener("disconnect", onDisconnect); }); - }; - - dfu.Device.prototype.erase = function () { - return this.requestOut(dfu.ERASE); - }; - dfu.Device.prototype.download = function (data, blockNum) { - return this.requestOut(dfu.DNLOAD, data, blockNum); - }; - - dfu.Device.prototype.dnload = dfu.Device.prototype.download; - - dfu.Device.prototype.upload = function (length, blockNum) { - return this.requestIn(dfu.UPLOAD, length, blockNum); - }; - - dfu.Device.prototype.clearStatus = function () { - return this.requestOut(dfu.CLRSTATUS); - }; - - dfu.Device.prototype.clrStatus = dfu.Device.prototype.clearStatus; - - dfu.Device.prototype.getStatus = function () { - return this.requestIn(dfu.GETSTATUS, 6).then( - (data) => - Promise.resolve({ - status: data.getUint8(0), - pollTimeout: 5, // data.getUint32(1, true) & 0xFFFFFF, - state: data.getUint8(4), - }), - (error) => Promise.reject("DFU GETSTATUS failed: " + error) - ); - }; + } + + erase() { + // TODO XXX this doesn't exist in the spec? + return this._requestOut(dfu.ERASE); + } + _downloadBytes(data, blockNum) { + return this._requestOut(DFUCommand.DNLOAD, data, blockNum); + } + + _uploadBytes(length, blockNum) { + return this._requestIn(DFUCommand.UPLOAD, length, blockNum); + } + + clearStatus() { + return this._requestOut(DFUCommand.CLRSTATUS); + } - dfu.Device.prototype.getState = function () { - return this.requestIn(dfu.GETSTATE, 1).then( + async getStatus() { + try { + const data = await this._requestIn(DFUCommand.GETSTATUS, 6); + return { + status: data.getUint8(0), + pollTimeout: 5, // data.getUint32(1, true) & 0xFFFFFF, + state: data.getUint8(4), + }; + } catch (error) { + throw new Error("DFU GETSTATUS failed: " + error); + } + } + + getState() { + return this._requestIn(DFUCommand.GETSTATE, 1).then( (data) => Promise.resolve(data.getUint8(0)), (error) => Promise.reject("DFU GETSTATE failed: " + error) ); - }; + } - dfu.Device.prototype.abort = function () { - return this.requestOut(dfu.ABORT); - }; + _abort() { + return this._requestOut(DFUCommand.ABORT); + } - dfu.Device.prototype.abortToIdle = async function () { - await this.abort(); + async _abortToIdle() { + await this._abort(); let state = await this.getState(); - if (state == dfu.dfuERROR) { + if (state == DFUDeviceState.dfuERROR) { await this.clearStatus(); state = await this.getState(); } - if (state != dfu.dfuIDLE) { + if (state != DFUDeviceState.dfuIDLE) { throw "Failed to return to idle state after abort: state " + state.state; } - }; + } - dfu.Device.prototype.do_upload = async function ( - xfer_size, - max_size = Infinity, - first_block = 0 - ) { + async do_upload(xfer_size, max_size = Infinity, first_block = 0) { let transaction = first_block; const blocks = []; let bytes_read = 0; - this.logInfo("Copying data from DFU device to browser"); + console.log("Copying data from DFU device to browser"); // Initialize progress to 0 this.logProgress(0); @@ -589,8 +566,8 @@ var dfu = {}; let bytes_to_read; do { bytes_to_read = Math.min(xfer_size, max_size - bytes_read); - result = await this.upload(bytes_to_read, transaction++); - this.logDebug("Read " + result.byteLength + " bytes"); + result = await this._uploadBytes(bytes_to_read, transaction++); + console.debug("Read " + result.byteLength + " bytes"); if (result.byteLength > 0) { blocks.push(result); bytes_read += result.byteLength; @@ -603,50 +580,46 @@ var dfu = {}; } while (bytes_read < max_size && result.byteLength == bytes_to_read); if (bytes_read == max_size) { - await this.abortToIdle(); + await this._abortToIdle(); } - this.logInfo(`Read ${bytes_read} bytes`); + console.log(`Read ${bytes_read} bytes`); return new Blob(blocks, { type: "application/octet-stream" }); - }; + } - dfu.Device.prototype.poll_until = async function (state_predicate) { + async _poll_until(state_predicate) { let dfu_status = await this.getStatus(); const device = this; function async_sleep(duration_ms) { return new Promise(function (resolve, reject) { - device.logDebug("Sleeping for " + duration_ms + "ms"); + console.debug("Sleeping for " + duration_ms + "ms"); setTimeout(resolve, duration_ms); }); } while ( !state_predicate(dfu_status.state) && - dfu_status.state != dfu.dfuERROR + dfu_status.state != DFUDeviceState.dfuERROR ) { await async_sleep(dfu_status.pollTimeout); dfu_status = await this.getStatus(); } return dfu_status; - }; + } - dfu.Device.prototype.poll_until_idle = function (idle_state) { - return this.poll_until((state) => state == idle_state); - }; + _poll_until_idle(idle_state) { + return this._poll_until((state) => state == idle_state); + } - dfu.Device.prototype.do_download = async function ( - xfer_size, - data, - manifestationTolerant - ) { + async do_download(xfer_size, data, manifestationTolerant) { let bytes_sent = 0; const expected_size = data.byteLength; let transaction = 0; - this.logInfo("Copying data from browser to DFU device"); + console.log("Copying data from browser to DFU device"); // Initialize progress to 0 this.logProgress(bytes_sent, expected_size); @@ -658,52 +631,53 @@ var dfu = {}; let bytes_written = 0; let dfu_status; try { - bytes_written = await this.download( + bytes_written = await this._downloadBytes( data.slice(bytes_sent, bytes_sent + chunk_size), transaction++ ); - this.logDebug("Sent " + bytes_written + " bytes"); - dfu_status = await this.poll_until_idle(dfu.dfuDNLOAD_IDLE); + console.debug("Sent " + bytes_written + " bytes"); + dfu_status = await this._poll_until_idle(DFUDeviceState.dfuDNLOAD_IDLE); } catch (error) { throw "Error during DFU download: " + error; } - if (dfu_status.status != dfu.STATUS_OK) { + if (dfu_status.status != DFUDeviceStatus.OK) { throw `DFU DOWNLOAD failed state=${dfu_status.state}, status=${dfu_status.status}`; } - this.logDebug("Wrote " + bytes_written + " bytes"); + console.debug("Wrote " + bytes_written + " bytes"); bytes_sent += bytes_written; this.logProgress(bytes_sent, expected_size); } - this.logDebug("Sending empty block"); + console.debug("Sending empty block"); try { - await this.download(new ArrayBuffer([]), transaction++); + await this._downloadBytes(new ArrayBuffer([]), transaction++); } catch (error) { throw "Error during final DFU download: " + error; } - this.logInfo("Wrote " + bytes_sent + " bytes"); - this.logInfo("Manifesting new firmware"); + console.log("Wrote " + bytes_sent + " bytes. Manifesting new firmware."); if (manifestationTolerant) { - this.logInfo("Manifestation tolerant"); + console.log("Manifestation tolerant"); // Transition to MANIFEST_SYNC state let dfu_status; try { // Wait until it returns to idle. // If it's not really manifestation tolerant, it might transition to MANIFEST_WAIT_RESET - dfu_status = await this.poll_until( - (state) => state == dfu.dfuIDLE || state == dfu.dfuMANIFEST_WAIT_RESET + dfu_status = await this._poll_until( + (state) => + state == DFUDeviceState.dfuIDLE || + state == DFUDeviceState.dfuMANIFEST_WAIT_RESET ); - if (dfu_status.state == dfu.dfuMANIFEST_WAIT_RESET) { - this.logDebug( + if (dfu_status.state == DFUDeviceState.dfuMANIFEST_WAIT_RESET) { + console.debug( "Device transitioned to MANIFEST_WAIT_RESET even though it is manifestation tolerant" ); } - if (dfu_status.status != dfu.STATUS_OK) { + if (dfu_status.status != DFUDeviceStatus.OK) { throw `DFU MANIFEST failed state=${dfu_status.state}, status=${dfu_status.status}`; } } catch (error) { @@ -715,41 +689,83 @@ var dfu = {}; "ControlTransferIn failed: NotFoundError: The device was disconnected." ) ) { - this.logWarning("Unable to poll final manifestation status"); + console.warn("Unable to poll final manifestation status"); } else { throw "Error during DFU manifest: " + error; } } } else { - this.logInfo("manifestation not tolerant"); + console.log("manifestation not tolerant"); // Try polling once to initiate manifestation try { const final_status = await this.getStatus(); - this.logDebug( + console.debug( `Final DFU status: state=${final_status.state}, status=${final_status.status}` ); } catch (error) { - this.logDebug("Manifest GET_STATUS poll error: " + error); + console.debug("Manifest GET_STATUS poll error: " + error); } } // Reset to exit MANIFEST_WAIT_RESET try { - this.logInfo("Attempting a device reset"); + console.log("Attempting a device reset"); await this.device_.reset(); - this.logInfo("Done waiting;"); + console.log("Done waiting;"); } catch (error) { if ( error == "NetworkError: Unable to reset the device." || error == "NotFoundError: Device unavailable." || error == "NotFoundError: The device was disconnected." ) { - this.logDebug("Ignored reset error ", error); + console.debug("Ignored reset error ", error); } else { throw "Error during reset for manifestation: " + error; } } return; - }; -})(); + } +} + +const DFU = { + findDeviceDfuInterfaces: function (device) { + const interfaces = []; + for (const configuration of device.configurations) { + for (const configInterface of configuration.interfaces) { + for (const alt of configInterface.alternates) { + if ( + alt.interfaceClass == USBClass.APP_SPECIFIC && + alt.interfaceSubclass == USBSubclass.DFU && + (alt.interfaceProtocol == 0x01 || alt.interfaceProtocol == 0x02) + ) { + const settings = { + configuration: configuration, + interface: configInterface, + alternate: alt, + name: alt.interfaceName, + }; + interfaces.push(settings); + } + } + } + } + + return interfaces; + }, + + findAllDfuInterfaces: function () { + return navigator.usb.getDevices().then((devices) => { + const matches = []; + for (const device of devices) { + const interfaces = dfu.findDeviceDfuInterfaces(device); + for (const interface_ of interfaces) { + matches.push(new DFUUSBDevice(device, interface_)); + } + } + return matches; + }); + }, +}; + +export { DFUDeviceState, DFUDescriptorType, DFUUSBDevice, DFU, USBParser };