usb 2.12.1 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -1
  3. package/dist/index.js +6 -2
  4. package/dist/index.js.map +1 -1
  5. package/dist/usb/bindings.js +1 -1
  6. package/dist/usb/bindings.js.map +1 -1
  7. package/dist/usb/capability.js.map +1 -1
  8. package/dist/usb/device.js.map +1 -1
  9. package/dist/usb/endpoint.d.ts +1 -0
  10. package/dist/usb/endpoint.js +2 -2
  11. package/dist/usb/endpoint.js.map +1 -1
  12. package/dist/usb/index.js.map +1 -1
  13. package/dist/usb/interface.js +2 -2
  14. package/dist/usb/interface.js.map +1 -1
  15. package/dist/webusb/index.d.ts +2 -2
  16. package/dist/webusb/index.js.map +1 -1
  17. package/dist/webusb/webusb-device.js +10 -10
  18. package/dist/webusb/webusb-device.js.map +1 -1
  19. package/libusb/.codespellrc +3 -0
  20. package/libusb/.private/appveyor_build.sh +5 -1
  21. package/libusb/.private/ci-build.sh +26 -1
  22. package/libusb/.private/ci-container-build.sh +2 -5
  23. package/libusb/.private/wbs.txt +5 -8
  24. package/libusb/AUTHORS +22 -0
  25. package/libusb/ChangeLog +19 -2
  26. package/libusb/HACKING +25 -0
  27. package/libusb/INSTALL_WIN.txt +11 -10
  28. package/libusb/NEWS +2 -2
  29. package/libusb/README +3 -2
  30. package/libusb/README.git +1 -1
  31. package/libusb/Xcode/common.xcconfig +8 -1
  32. package/libusb/Xcode/config.h +0 -6
  33. package/libusb/Xcode/debug.xcconfig +4 -1
  34. package/libusb/Xcode/libusb.xcconfig +1 -1
  35. package/libusb/Xcode/libusb.xcodeproj/project.pbxproj +60 -30
  36. package/libusb/Xcode/libusb_debug.xcconfig +1 -1
  37. package/libusb/Xcode/libusb_release.xcconfig +1 -1
  38. package/libusb/Xcode/release.xcconfig +1 -1
  39. package/libusb/appveyor.yml +33 -9
  40. package/libusb/configure.ac +68 -37
  41. package/libusb/examples/dpfp.c +2 -2
  42. package/libusb/examples/fxload.c +2 -2
  43. package/libusb/examples/hotplugtest.c +28 -13
  44. package/libusb/examples/listdevs.c +1 -1
  45. package/libusb/examples/sam3u_benchmark.c +1 -1
  46. package/libusb/examples/testlibusb.c +1 -1
  47. package/libusb/examples/xusb.c +74 -19
  48. package/libusb/libusb/Makefile.am +11 -1
  49. package/libusb/libusb/core.c +364 -186
  50. package/libusb/libusb/descriptor.c +276 -16
  51. package/libusb/libusb/hotplug.c +5 -4
  52. package/libusb/libusb/io.c +72 -61
  53. package/libusb/libusb/libusb-1.0.def +14 -1
  54. package/libusb/libusb/libusb.h +245 -76
  55. package/libusb/libusb/libusbi.h +35 -13
  56. package/libusb/libusb/os/darwin_usb.c +542 -279
  57. package/libusb/libusb/os/darwin_usb.h +44 -115
  58. package/libusb/libusb/os/emscripten_webusb.cpp +870 -0
  59. package/libusb/libusb/os/events_posix.c +40 -0
  60. package/libusb/libusb/os/events_posix.h +3 -0
  61. package/libusb/libusb/os/linux_usbfs.c +27 -16
  62. package/libusb/libusb/os/netbsd_usb.c +36 -36
  63. package/libusb/libusb/os/openbsd_usb.c +34 -34
  64. package/libusb/libusb/os/sunos_usb.c +25 -15
  65. package/libusb/libusb/os/threads_posix.c +1 -5
  66. package/libusb/libusb/os/windows_common.c +13 -5
  67. package/libusb/libusb/os/windows_common.h +8 -0
  68. package/libusb/libusb/os/windows_winusb.c +366 -174
  69. package/libusb/libusb/os/windows_winusb.h +13 -9
  70. package/libusb/libusb/strerror.c +5 -5
  71. package/libusb/libusb/sync.c +24 -19
  72. package/libusb/libusb/version.h +1 -1
  73. package/libusb/libusb/version_nano.h +1 -1
  74. package/libusb/msvc/Base.props +60 -0
  75. package/libusb/msvc/Configuration.Application.props +7 -0
  76. package/libusb/msvc/Configuration.Base.props +47 -0
  77. package/libusb/msvc/Configuration.DynamicLibrary.props +21 -0
  78. package/libusb/msvc/Configuration.StaticLibrary.props +7 -0
  79. package/libusb/msvc/ProjectConfigurations.Base.props +69 -0
  80. package/libusb/msvc/build_all.ps1 +17 -0
  81. package/libusb/msvc/config.h +2 -2
  82. package/libusb/msvc/dpfp.vcxproj +33 -0
  83. package/libusb/msvc/dpfp_threaded.vcxproj +38 -0
  84. package/libusb/msvc/fxload.vcxproj +46 -0
  85. package/libusb/msvc/getopt.vcxproj +33 -0
  86. package/libusb/msvc/hotplugtest.vcxproj +32 -0
  87. package/libusb/msvc/init_context.vcxproj +35 -0
  88. package/libusb/msvc/libusb.sln +542 -0
  89. package/libusb/msvc/libusb_dll.vcxproj +61 -0
  90. package/libusb/msvc/libusb_static.vcxproj +49 -0
  91. package/libusb/msvc/listdevs.vcxproj +32 -0
  92. package/libusb/msvc/sam3u_benchmark.vcxproj +33 -0
  93. package/libusb/msvc/set_option.vcxproj +35 -0
  94. package/libusb/msvc/stress.vcxproj +35 -0
  95. package/libusb/msvc/stress_mt.vcxproj +33 -0
  96. package/libusb/msvc/testlibusb.vcxproj +32 -0
  97. package/libusb/msvc/xusb.vcxproj +38 -0
  98. package/libusb/tests/Makefile.am +25 -3
  99. package/libusb/tests/init_context.c +153 -0
  100. package/libusb/tests/macos.c +130 -0
  101. package/libusb/tests/set_option.c +253 -0
  102. package/libusb/tests/stress.c +17 -14
  103. package/libusb/tests/stress_mt.c +265 -0
  104. package/libusb/tests/testlib.c +1 -1
  105. package/libusb/tests/umockdev.c +9 -9
  106. package/libusb/tests/webusb-test-shim/index.js +12 -0
  107. package/libusb/tests/webusb-test-shim/package-lock.json +50 -0
  108. package/libusb/tests/webusb-test-shim/package.json +10 -0
  109. package/package.json +7 -7
  110. package/prebuilds/android-arm/node.napi.armv7.node +0 -0
  111. package/prebuilds/android-arm64/node.napi.armv8.node +0 -0
  112. package/prebuilds/darwin-x64+arm64/node.napi.node +0 -0
  113. package/prebuilds/linux-arm/node.napi.armv6.node +0 -0
  114. package/prebuilds/linux-arm/node.napi.armv7.node +0 -0
  115. package/prebuilds/linux-arm64/node.napi.armv8.node +0 -0
  116. package/prebuilds/linux-ia32/node.napi.node +0 -0
  117. package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
  118. package/prebuilds/linux-x64/node.napi.musl.node +0 -0
  119. package/prebuilds/win32-arm64/node.napi.node +0 -0
  120. package/prebuilds/win32-ia32/node.napi.node +0 -0
  121. package/prebuilds/win32-x64/node.napi.node +0 -0
  122. package/src/device.cc +15 -5
  123. package/libusb/msvc/dpfp_2013.vcxproj +0 -87
  124. package/libusb/msvc/dpfp_2013.vcxproj.filters +0 -26
  125. package/libusb/msvc/dpfp_2015.vcxproj +0 -87
  126. package/libusb/msvc/dpfp_2015.vcxproj.filters +0 -26
  127. package/libusb/msvc/dpfp_2017.vcxproj +0 -106
  128. package/libusb/msvc/dpfp_2017.vcxproj.filters +0 -26
  129. package/libusb/msvc/dpfp_2019.vcxproj +0 -106
  130. package/libusb/msvc/dpfp_2019.vcxproj.filters +0 -26
  131. package/libusb/msvc/dpfp_threaded_2013.vcxproj +0 -87
  132. package/libusb/msvc/dpfp_threaded_2013.vcxproj.filters +0 -26
  133. package/libusb/msvc/dpfp_threaded_2015.vcxproj +0 -87
  134. package/libusb/msvc/dpfp_threaded_2015.vcxproj.filters +0 -26
  135. package/libusb/msvc/dpfp_threaded_2017.vcxproj +0 -106
  136. package/libusb/msvc/dpfp_threaded_2017.vcxproj.filters +0 -26
  137. package/libusb/msvc/dpfp_threaded_2019.vcxproj +0 -106
  138. package/libusb/msvc/dpfp_threaded_2019.vcxproj.filters +0 -26
  139. package/libusb/msvc/fxload_2013.vcxproj +0 -94
  140. package/libusb/msvc/fxload_2013.vcxproj.filters +0 -35
  141. package/libusb/msvc/fxload_2015.vcxproj +0 -94
  142. package/libusb/msvc/fxload_2015.vcxproj.filters +0 -35
  143. package/libusb/msvc/fxload_2017.vcxproj +0 -113
  144. package/libusb/msvc/fxload_2017.vcxproj.filters +0 -35
  145. package/libusb/msvc/fxload_2019.vcxproj +0 -113
  146. package/libusb/msvc/fxload_2019.vcxproj.filters +0 -35
  147. package/libusb/msvc/getopt_2013.vcxproj +0 -72
  148. package/libusb/msvc/getopt_2013.vcxproj.filters +0 -26
  149. package/libusb/msvc/getopt_2015.vcxproj +0 -73
  150. package/libusb/msvc/getopt_2015.vcxproj.filters +0 -26
  151. package/libusb/msvc/getopt_2017.vcxproj +0 -92
  152. package/libusb/msvc/getopt_2017.vcxproj.filters +0 -26
  153. package/libusb/msvc/getopt_2019.vcxproj +0 -92
  154. package/libusb/msvc/getopt_2019.vcxproj.filters +0 -26
  155. package/libusb/msvc/hotplugtest_2013.vcxproj +0 -86
  156. package/libusb/msvc/hotplugtest_2013.vcxproj.filters +0 -23
  157. package/libusb/msvc/hotplugtest_2015.vcxproj +0 -86
  158. package/libusb/msvc/hotplugtest_2015.vcxproj.filters +0 -23
  159. package/libusb/msvc/hotplugtest_2017.vcxproj +0 -105
  160. package/libusb/msvc/hotplugtest_2017.vcxproj.filters +0 -23
  161. package/libusb/msvc/hotplugtest_2019.vcxproj +0 -105
  162. package/libusb/msvc/hotplugtest_2019.vcxproj.filters +0 -23
  163. package/libusb/msvc/libusb_2013.sln +0 -137
  164. package/libusb/msvc/libusb_2015.sln +0 -137
  165. package/libusb/msvc/libusb_2017.sln +0 -240
  166. package/libusb/msvc/libusb_2019.sln +0 -240
  167. package/libusb/msvc/libusb_dll_2013.vcxproj +0 -104
  168. package/libusb/msvc/libusb_dll_2013.vcxproj.filters +0 -94
  169. package/libusb/msvc/libusb_dll_2015.vcxproj +0 -105
  170. package/libusb/msvc/libusb_dll_2015.vcxproj.filters +0 -94
  171. package/libusb/msvc/libusb_dll_2017.vcxproj +0 -124
  172. package/libusb/msvc/libusb_dll_2017.vcxproj.filters +0 -94
  173. package/libusb/msvc/libusb_dll_2019.vcxproj +0 -124
  174. package/libusb/msvc/libusb_dll_2019.vcxproj.filters +0 -94
  175. package/libusb/msvc/libusb_static_2013.vcxproj +0 -94
  176. package/libusb/msvc/libusb_static_2013.vcxproj.filters +0 -80
  177. package/libusb/msvc/libusb_static_2015.vcxproj +0 -95
  178. package/libusb/msvc/libusb_static_2015.vcxproj.filters +0 -80
  179. package/libusb/msvc/libusb_static_2017.vcxproj +0 -114
  180. package/libusb/msvc/libusb_static_2017.vcxproj.filters +0 -80
  181. package/libusb/msvc/libusb_static_2019.vcxproj +0 -114
  182. package/libusb/msvc/libusb_static_2019.vcxproj.filters +0 -80
  183. package/libusb/msvc/listdevs_2013.vcxproj +0 -86
  184. package/libusb/msvc/listdevs_2013.vcxproj.filters +0 -23
  185. package/libusb/msvc/listdevs_2015.vcxproj +0 -86
  186. package/libusb/msvc/listdevs_2015.vcxproj.filters +0 -23
  187. package/libusb/msvc/listdevs_2017.vcxproj +0 -105
  188. package/libusb/msvc/listdevs_2017.vcxproj.filters +0 -23
  189. package/libusb/msvc/listdevs_2019.vcxproj +0 -105
  190. package/libusb/msvc/listdevs_2019.vcxproj.filters +0 -23
  191. package/libusb/msvc/sam3u_benchmark_2013.vcxproj +0 -87
  192. package/libusb/msvc/sam3u_benchmark_2013.vcxproj.filters +0 -26
  193. package/libusb/msvc/sam3u_benchmark_2015.vcxproj +0 -87
  194. package/libusb/msvc/sam3u_benchmark_2015.vcxproj.filters +0 -26
  195. package/libusb/msvc/sam3u_benchmark_2017.vcxproj +0 -106
  196. package/libusb/msvc/sam3u_benchmark_2017.vcxproj.filters +0 -26
  197. package/libusb/msvc/sam3u_benchmark_2019.vcxproj +0 -106
  198. package/libusb/msvc/sam3u_benchmark_2019.vcxproj.filters +0 -26
  199. package/libusb/msvc/stress_2013.vcxproj +0 -89
  200. package/libusb/msvc/stress_2013.vcxproj.filters +0 -32
  201. package/libusb/msvc/stress_2015.vcxproj +0 -89
  202. package/libusb/msvc/stress_2015.vcxproj.filters +0 -32
  203. package/libusb/msvc/stress_2017.vcxproj +0 -108
  204. package/libusb/msvc/stress_2017.vcxproj.filters +0 -32
  205. package/libusb/msvc/stress_2019.vcxproj +0 -108
  206. package/libusb/msvc/stress_2019.vcxproj.filters +0 -32
  207. package/libusb/msvc/testlibusb_2013.vcxproj +0 -86
  208. package/libusb/msvc/testlibusb_2013.vcxproj.filters +0 -23
  209. package/libusb/msvc/testlibusb_2015.vcxproj +0 -86
  210. package/libusb/msvc/testlibusb_2015.vcxproj.filters +0 -23
  211. package/libusb/msvc/testlibusb_2017.vcxproj +0 -105
  212. package/libusb/msvc/testlibusb_2017.vcxproj.filters +0 -23
  213. package/libusb/msvc/testlibusb_2019.vcxproj +0 -105
  214. package/libusb/msvc/testlibusb_2019.vcxproj.filters +0 -23
  215. package/libusb/msvc/xusb_2013.vcxproj +0 -86
  216. package/libusb/msvc/xusb_2013.vcxproj.filters +0 -23
  217. package/libusb/msvc/xusb_2015.vcxproj +0 -86
  218. package/libusb/msvc/xusb_2015.vcxproj.filters +0 -23
  219. package/libusb/msvc/xusb_2017.vcxproj +0 -105
  220. package/libusb/msvc/xusb_2017.vcxproj.filters +0 -23
  221. package/libusb/msvc/xusb_2019.vcxproj +0 -105
  222. package/libusb/msvc/xusb_2019.vcxproj.filters +0 -23
@@ -0,0 +1,870 @@
1
+ /*
2
+ * Copyright © 2021 Google LLC
3
+ * Copyright © 2023 Ingvar Stepanyan <me@rreverser.com>
4
+ *
5
+ * This library is free software; you can redistribute it and/or
6
+ * modify it under the terms of the GNU Lesser General Public
7
+ * License as published by the Free Software Foundation; either
8
+ * version 2.1 of the License, or (at your option) any later version.
9
+ *
10
+ * This library is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ * Lesser General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public
16
+ * License along with this library; if not, write to the Free Software
17
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ *
19
+ * Authors:
20
+ * Ingvar Stepanyan <me@rreverser.com>
21
+ */
22
+
23
+ #include <emscripten/version.h>
24
+
25
+ static_assert((__EMSCRIPTEN_major__ * 100 * 100 + __EMSCRIPTEN_minor__ * 100 +
26
+ __EMSCRIPTEN_tiny__) >= 30148,
27
+ "Emscripten 3.1.48 or newer is required.");
28
+
29
+ #include <assert.h>
30
+ #include <emscripten.h>
31
+ #include <emscripten/val.h>
32
+
33
+ #include <type_traits>
34
+ #include <utility>
35
+
36
+ #include "libusbi.h"
37
+
38
+ using namespace emscripten;
39
+
40
+ #ifdef _REENTRANT
41
+ #include <emscripten/proxying.h>
42
+ #include <emscripten/threading.h>
43
+ #include <pthread.h>
44
+
45
+ static ProxyingQueue queue;
46
+ #endif
47
+
48
+ #pragma clang diagnostic push
49
+ #pragma clang diagnostic ignored "-Wmissing-prototypes"
50
+ #pragma clang diagnostic ignored "-Wunused-parameter"
51
+ #pragma clang diagnostic ignored "-Wshadow"
52
+
53
+ namespace {
54
+
55
+ // clang-format off
56
+ EM_JS(EM_VAL, usbi_em_promise_catch, (EM_VAL handle), {
57
+ let promise = Emval.toValue(handle);
58
+ promise = promise.then(
59
+ value => ({error : 0, value}),
60
+ error => {
61
+ console.error(error);
62
+ let errorCode = -99; // LIBUSB_ERROR_OTHER
63
+ if (error instanceof DOMException) {
64
+ const ERROR_CODES = {
65
+ // LIBUSB_ERROR_IO
66
+ NetworkError : -1,
67
+ // LIBUSB_ERROR_INVALID_PARAM
68
+ DataError : -2,
69
+ TypeMismatchError : -2,
70
+ IndexSizeError : -2,
71
+ // LIBUSB_ERROR_ACCESS
72
+ SecurityError : -3,
73
+ // LIBUSB_ERROR_NOT_FOUND
74
+ NotFoundError : -5,
75
+ // LIBUSB_ERROR_BUSY
76
+ InvalidStateError : -6,
77
+ // LIBUSB_ERROR_TIMEOUT
78
+ TimeoutError : -7,
79
+ // LIBUSB_ERROR_INTERRUPTED
80
+ AbortError : -10,
81
+ // LIBUSB_ERROR_NOT_SUPPORTED
82
+ NotSupportedError : -12,
83
+ };
84
+ errorCode = ERROR_CODES[error.name] ?? errorCode;
85
+ } else if (error instanceof RangeError || error instanceof TypeError) {
86
+ errorCode = -2; // LIBUSB_ERROR_INVALID_PARAM
87
+ }
88
+ return {error: errorCode, value: undefined};
89
+ }
90
+ );
91
+ return Emval.toHandle(promise);
92
+ });
93
+
94
+ EM_JS(void, usbi_em_copy_from_dataview, (void* dst, EM_VAL src), {
95
+ src = Emval.toValue(src);
96
+ src = new Uint8Array(src.buffer, src.byteOffset, src.byteLength);
97
+ HEAPU8.set(src, dst);
98
+ });
99
+
100
+ // Our implementation proxies operations from multiple threads to the same
101
+ // underlying USBDevice on the main thread. This can lead to issues when
102
+ // multiple threads try to open/close the same device at the same time.
103
+ //
104
+ // First, since open/close operations are asynchronous in WebUSB, we can end up
105
+ // with multiple open/close operations in flight at the same time, which can
106
+ // lead to unpredictable outcome (e.g. device got closed but opening succeeded
107
+ // right before that).
108
+ //
109
+ // Second, since multiple threads are allowed to have their own handles to the
110
+ // same device, we need to keep track of number of open handles and close the
111
+ // device only when the last handle is closed.
112
+ //
113
+ // We fix both of these issues by using a shared promise chain that executes
114
+ // open and close operations sequentially and keeps track of the reference count
115
+ // in each promise's result. This way, we can ensure that only one open/close
116
+ // operation is in flight at any given time. Note that we don't need to worry
117
+ // about all other operations because they're preconditioned on the device being
118
+ // open and having at least 1 reference anyway.
119
+ EM_JS(EM_VAL, usbi_em_device_safe_open_close, (EM_VAL device, bool open), {
120
+ device = Emval.toValue(device);
121
+ const symbol = Symbol.for('libusb.open_close_chain');
122
+ let promiseChain = device[symbol] ?? Promise.resolve(0);
123
+ device[symbol] = promiseChain = promiseChain.then(async refCount => {
124
+ if (open) {
125
+ if (!refCount++) {
126
+ await device.open();
127
+ }
128
+ } else {
129
+ if (!--refCount) {
130
+ await device.close();
131
+ }
132
+ }
133
+ return refCount;
134
+ });
135
+ return Emval.toHandle(promiseChain);
136
+ });
137
+ // clang-format on
138
+
139
+ libusb_transfer_status getTransferStatus(const val& transfer_result) {
140
+ auto status = transfer_result["status"].as<std::string>();
141
+ if (status == "ok") {
142
+ return LIBUSB_TRANSFER_COMPLETED;
143
+ } else if (status == "stall") {
144
+ return LIBUSB_TRANSFER_STALL;
145
+ } else if (status == "babble") {
146
+ return LIBUSB_TRANSFER_OVERFLOW;
147
+ } else {
148
+ return LIBUSB_TRANSFER_ERROR;
149
+ }
150
+ }
151
+
152
+ // Note: this assumes that `dst` is valid for at least `src.byteLength` bytes.
153
+ // This is true for all results returned from WebUSB as we pass max length to
154
+ // the transfer APIs.
155
+ void copyFromDataView(void* dst, const val& src) {
156
+ usbi_em_copy_from_dataview(dst, src.as_handle());
157
+ }
158
+
159
+ auto getUnsharedMemoryView(void* src, size_t len) {
160
+ auto view = typed_memory_view(len, (uint8_t*)src);
161
+ #ifdef _REENTRANT
162
+ // Unfortunately, TypedArrays backed by SharedArrayBuffers are not accepted
163
+ // by most Web APIs, trading off guaranteed thread-safety for performance
164
+ // loss. The usual workaround is to copy them into a new TypedArray, which
165
+ // is what we do here via the `.slice()` method.
166
+ return val(view).call<val>("slice");
167
+ #else
168
+ // Non-threaded builds can avoid the copy penalty.
169
+ return view;
170
+ #endif
171
+ }
172
+
173
+ // A helper that proxies a function call to the main thread if not already
174
+ // there. This is a wrapper around Emscripten's raw proxying API with couple of
175
+ // high-level improvements, namely support for destroying lambda on the target
176
+ // thread as well as custom return types.
177
+ template <typename Func>
178
+ auto runOnMain(Func&& func) {
179
+ #ifdef _REENTRANT
180
+ if (!emscripten_is_main_runtime_thread()) {
181
+ if constexpr (std::is_same_v<std::invoke_result_t<Func>, void>) {
182
+ bool proxied =
183
+ queue.proxySync(emscripten_main_runtime_thread_id(), [&func] {
184
+ // Capture func by reference and move into a local variable
185
+ // to render the captured func inert on the first (and only)
186
+ // call. This way it can be safely destructed on the main
187
+ // thread instead of the current one when this call
188
+ // finishes. TODO: remove this when
189
+ // https://github.com/emscripten-core/emscripten/issues/20610
190
+ // is fixed.
191
+ auto func_ = std::move(func);
192
+ func_();
193
+ });
194
+ assert(proxied);
195
+ return;
196
+ } else {
197
+ // A storage for the result of the function call.
198
+ // TODO: remove when
199
+ // https://github.com/emscripten-core/emscripten/issues/20611 is
200
+ // implemented.
201
+ std::optional<std::invoke_result_t<Func>> result;
202
+ runOnMain(
203
+ [&result, func = std::move(func)] { result.emplace(func()); });
204
+ return std::move(result.value());
205
+ }
206
+ }
207
+ #endif
208
+ return func();
209
+ }
210
+
211
+ // C++ struct representation for `{value, error}` object used by `CaughtPromise`
212
+ // below.
213
+ struct PromiseResult {
214
+ int error;
215
+ val value;
216
+
217
+ PromiseResult() = delete;
218
+ PromiseResult(PromiseResult&&) = default;
219
+
220
+ PromiseResult(val&& result)
221
+ : error(result["error"].as<int>()), value(result["value"]) {}
222
+
223
+ ~PromiseResult() {
224
+ // make sure value is freed on the thread it exists on
225
+ runOnMain([value = std::move(value)] {});
226
+ }
227
+ };
228
+
229
+ struct CaughtPromise : val {
230
+ CaughtPromise(val&& promise)
231
+ : val(wrapPromiseWithCatch(std::move(promise))) {}
232
+
233
+ using AwaitResult = PromiseResult;
234
+
235
+ private:
236
+
237
+ // Wrap promise with conversion from some value T to `{value: T, error:
238
+ // number}`.
239
+ static val wrapPromiseWithCatch(val&& promise) {
240
+ auto handle = promise.as_handle();
241
+ handle = usbi_em_promise_catch(handle);
242
+ return val::take_ownership(handle);
243
+ }
244
+ };
245
+
246
+ #define co_await_try(promise) \
247
+ ({ \
248
+ PromiseResult result = co_await CaughtPromise(promise); \
249
+ if (result.error) { \
250
+ co_return result.error; \
251
+ } \
252
+ std::move(result.value); \
253
+ })
254
+
255
+ // A helper that runs an asynchronous callback when the promise is resolved.
256
+ template <typename Promise, typename OnResult>
257
+ val promiseThen(Promise&& promise, OnResult&& onResult) {
258
+ // Save captures from the callback while we can, or they'll be destructed.
259
+ // https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870
260
+ auto onResult_ = std::move(onResult);
261
+ onResult_(co_await promise);
262
+ co_return val::undefined();
263
+ }
264
+
265
+ // A helper that runs an asynchronous function on the main thread and blocks the
266
+ // current thread until the promise is resolved (via Asyncify "blocking" if
267
+ // already on the main thread or regular blocking otherwise).
268
+ template <typename Func>
269
+ static std::invoke_result_t<Func>::AwaitResult awaitOnMain(Func&& func) {
270
+ #ifdef _REENTRANT
271
+ if (!emscripten_is_main_runtime_thread()) {
272
+ // If we're on a different thread, we can't use main thread's Asyncify
273
+ // as multiple threads might be fighting for its state; instead, use
274
+ // proxying to synchronously block the current thread until the promise
275
+ // is complete.
276
+ std::optional<typename std::invoke_result_t<Func>::AwaitResult> result;
277
+ queue.proxySyncWithCtx(
278
+ emscripten_main_runtime_thread_id(),
279
+ [&result, &func](ProxyingQueue::ProxyingCtx ctx) {
280
+ // Same as `func` in `runOnMain`, move to destruct on the first
281
+ // call.
282
+ auto func_ = std::move(func);
283
+ promiseThen(
284
+ func_(),
285
+ [&result, ctx = std::move(ctx)](auto&& result_) mutable {
286
+ result.emplace(std::move(result_));
287
+ ctx.finish();
288
+ });
289
+ });
290
+ return std::move(result.value());
291
+ }
292
+ #endif
293
+ // If we're already on the main thread, use Asyncify to block until the
294
+ // promise is resolved.
295
+ return func().await();
296
+ }
297
+
298
+ // A helper that makes a control transfer given a setup pointer (assumed to be
299
+ // followed by data payload for out-transfers).
300
+ val makeControlTransferPromise(const val& dev, libusb_control_setup* setup) {
301
+ auto params = val::object();
302
+
303
+ const char* request_type = "unknown";
304
+ // See LIBUSB_REQ_TYPE in windows_winusb.h (or docs for `bmRequestType`).
305
+ switch (setup->bmRequestType & (0x03 << 5)) {
306
+ case LIBUSB_REQUEST_TYPE_STANDARD:
307
+ request_type = "standard";
308
+ break;
309
+ case LIBUSB_REQUEST_TYPE_CLASS:
310
+ request_type = "class";
311
+ break;
312
+ case LIBUSB_REQUEST_TYPE_VENDOR:
313
+ request_type = "vendor";
314
+ break;
315
+ }
316
+ params.set("requestType", request_type);
317
+
318
+ const char* recipient = "other";
319
+ switch (setup->bmRequestType & 0x0f) {
320
+ case LIBUSB_RECIPIENT_DEVICE:
321
+ recipient = "device";
322
+ break;
323
+ case LIBUSB_RECIPIENT_INTERFACE:
324
+ recipient = "interface";
325
+ break;
326
+ case LIBUSB_RECIPIENT_ENDPOINT:
327
+ recipient = "endpoint";
328
+ break;
329
+ }
330
+ params.set("recipient", recipient);
331
+
332
+ params.set("request", setup->bRequest);
333
+ params.set("value", setup->wValue);
334
+ params.set("index", setup->wIndex);
335
+
336
+ if (setup->bmRequestType & LIBUSB_ENDPOINT_IN) {
337
+ return dev.call<val>("controlTransferIn", params, setup->wLength);
338
+ } else {
339
+ return dev.call<val>("controlTransferOut", params,
340
+ getUnsharedMemoryView(setup + 1, setup->wLength));
341
+ }
342
+ }
343
+
344
+ // Smart pointer for managing pointers to places allocated by libusb inside its
345
+ // backend structures.
346
+ template <typename T>
347
+ struct ValPtr {
348
+ template <typename... Args>
349
+ void emplace(Args&&... args) {
350
+ new (ptr) T(std::forward<Args>(args)...);
351
+ }
352
+
353
+ const T& operator*() const { return *ptr; }
354
+ T& operator*() { return *ptr; }
355
+
356
+ const T* operator->() const { return ptr; }
357
+ T* operator->() { return ptr; }
358
+
359
+ void free() { ptr->~T(); }
360
+
361
+ T take() {
362
+ auto value = std::move(*ptr);
363
+ free();
364
+ return value;
365
+ }
366
+
367
+ protected:
368
+
369
+ ValPtr(void* ptr) : ptr(static_cast<T*>(ptr)) {}
370
+
371
+ private:
372
+
373
+ // Note: this is not a heap-allocated pointer, but a pointer to a part
374
+ // of the backend structure allocated by libusb itself.
375
+ T* ptr;
376
+ };
377
+
378
+ struct CachedDevice;
379
+
380
+ struct WebUsbDevicePtr : ValPtr<CachedDevice> {
381
+ public:
382
+
383
+ WebUsbDevicePtr(libusb_device* dev) : ValPtr(usbi_get_device_priv(dev)) {}
384
+ WebUsbDevicePtr(libusb_device_handle* handle)
385
+ : WebUsbDevicePtr(handle->dev) {}
386
+ };
387
+
388
+ struct WebUsbTransferPtr : ValPtr<PromiseResult> {
389
+ public:
390
+
391
+ WebUsbTransferPtr(usbi_transfer* itransfer)
392
+ : ValPtr(usbi_get_transfer_priv(itransfer)) {}
393
+ };
394
+
395
+ enum class OpenClose : bool {
396
+ Open = true,
397
+ Close = false,
398
+ };
399
+
400
+ struct CachedDevice {
401
+ CachedDevice() = delete;
402
+ CachedDevice(CachedDevice&&) = delete;
403
+
404
+ // Fill in the device descriptor and configurations by reading them from the
405
+ // WebUSB device.
406
+ static val initFromDevice(val&& web_usb_dev, libusb_device* libusb_dev) {
407
+ auto cachedDevicePtr = WebUsbDevicePtr(libusb_dev);
408
+ cachedDevicePtr.emplace(std::move(web_usb_dev));
409
+ bool must_close = false;
410
+ val result = co_await cachedDevicePtr->initFromDeviceWithoutClosing(
411
+ libusb_dev, must_close);
412
+ if (must_close) {
413
+ co_await_try(cachedDevicePtr->safeOpenCloseAssumingMainThread(
414
+ OpenClose::Close));
415
+ }
416
+ co_return std::move(result);
417
+ }
418
+
419
+ const val& getDeviceAssumingMainThread() const { return device; }
420
+
421
+ uint8_t getActiveConfigValue() const {
422
+ return runOnMain([&] {
423
+ auto web_usb_config = device["configuration"];
424
+ return web_usb_config.isNull()
425
+ ? 0
426
+ : web_usb_config["configurationValue"].as<uint8_t>();
427
+ });
428
+ }
429
+
430
+ usbi_configuration_descriptor* getConfigDescriptor(uint8_t config_id) {
431
+ return config_id < configurations.size()
432
+ ? configurations[config_id].get()
433
+ : nullptr;
434
+ }
435
+
436
+ usbi_configuration_descriptor* findConfigDescriptorByValue(
437
+ uint8_t config_id) const {
438
+ for (auto& config : configurations) {
439
+ if (config->bConfigurationValue == config_id) {
440
+ return config.get();
441
+ }
442
+ }
443
+ return nullptr;
444
+ }
445
+
446
+ int copyConfigDescriptor(const usbi_configuration_descriptor* config,
447
+ void* buf,
448
+ size_t buf_len) {
449
+ auto len = std::min(buf_len, (size_t)config->wTotalLength);
450
+ memcpy(buf, config, len);
451
+ return len;
452
+ }
453
+
454
+ template <typename... Args>
455
+ int awaitOnMain(const char* methodName, Args&&... args) const {
456
+ return ::awaitOnMain([&] {
457
+ return CaughtPromise(device.call<val>(
458
+ methodName, std::forward<Args>(args)...));
459
+ })
460
+ .error;
461
+ }
462
+
463
+ ~CachedDevice() {
464
+ runOnMain([device = std::move(device)] {});
465
+ }
466
+
467
+ CaughtPromise safeOpenCloseAssumingMainThread(OpenClose open) {
468
+ return val::take_ownership(usbi_em_device_safe_open_close(
469
+ device.as_handle(), static_cast<bool>(open)));
470
+ }
471
+
472
+ int safeOpenCloseOnMain(OpenClose open) {
473
+ return ::awaitOnMain([this, open] {
474
+ return safeOpenCloseAssumingMainThread(open);
475
+ })
476
+ .error;
477
+ }
478
+
479
+ private:
480
+
481
+ val device;
482
+ std::vector<std::unique_ptr<usbi_configuration_descriptor>> configurations;
483
+
484
+ CaughtPromise requestDescriptor(libusb_descriptor_type desc_type,
485
+ uint8_t desc_index,
486
+ uint16_t max_length) const {
487
+ libusb_control_setup setup = {
488
+ .bmRequestType = LIBUSB_ENDPOINT_IN,
489
+ .bRequest = LIBUSB_REQUEST_GET_DESCRIPTOR,
490
+ .wValue = (uint16_t)((desc_type << 8) | desc_index),
491
+ .wIndex = 0,
492
+ .wLength = max_length,
493
+ };
494
+ return makeControlTransferPromise(device, &setup);
495
+ }
496
+
497
+ // Implementation of the `CachedDevice::initFromDevice` above. This is a
498
+ // separate function just because we need to close the device on exit if
499
+ // we opened it successfully, and we can't use an async operation (`close`)
500
+ // in RAII destructor.
501
+ val initFromDeviceWithoutClosing(libusb_device* dev, bool& must_close) {
502
+ co_await_try(safeOpenCloseAssumingMainThread(OpenClose::Open));
503
+
504
+ // Can't use RAII to close on exit as co_await is not permitted in
505
+ // destructors (yet:
506
+ // https://github.com/cplusplus/papers/issues/445), so use a good
507
+ // old boolean + a wrapper instead.
508
+ must_close = true;
509
+
510
+ {
511
+ auto result = co_await_try(
512
+ requestDescriptor(LIBUSB_DT_DEVICE, 0, LIBUSB_DT_DEVICE_SIZE));
513
+ if (auto error = getTransferStatus(result)) {
514
+ co_return error;
515
+ }
516
+ copyFromDataView(&dev->device_descriptor, result["data"]);
517
+ }
518
+
519
+ // Infer the device speed (which is not yet provided by WebUSB) from
520
+ // the descriptor.
521
+ if (dev->device_descriptor.bMaxPacketSize0 ==
522
+ /* actually means 2^9, only valid for superspeeds */ 9) {
523
+ dev->speed = dev->device_descriptor.bcdUSB >= 0x0310
524
+ ? LIBUSB_SPEED_SUPER_PLUS
525
+ : LIBUSB_SPEED_SUPER;
526
+ } else if (dev->device_descriptor.bcdUSB >= 0x0200) {
527
+ dev->speed = LIBUSB_SPEED_HIGH;
528
+ } else if (dev->device_descriptor.bMaxPacketSize0 > 8) {
529
+ dev->speed = LIBUSB_SPEED_FULL;
530
+ } else {
531
+ dev->speed = LIBUSB_SPEED_LOW;
532
+ }
533
+
534
+ if (auto error = usbi_sanitize_device(dev)) {
535
+ co_return error;
536
+ }
537
+
538
+ auto configurations_len = dev->device_descriptor.bNumConfigurations;
539
+ configurations.reserve(configurations_len);
540
+ for (uint8_t j = 0; j < configurations_len; j++) {
541
+ // Note: requesting more than (platform-specific limit) bytes
542
+ // here will cause the transfer to fail, see
543
+ // https://crbug.com/1489414. Use the most common limit of 4096
544
+ // bytes for now.
545
+ constexpr uint16_t MAX_CTRL_BUFFER_LENGTH = 4096;
546
+ auto result = co_await_try(
547
+ requestDescriptor(LIBUSB_DT_CONFIG, j, MAX_CTRL_BUFFER_LENGTH));
548
+ if (auto error = getTransferStatus(result)) {
549
+ co_return error;
550
+ }
551
+ auto configVal = result["data"];
552
+ auto configLen = configVal["byteLength"].as<size_t>();
553
+ auto& config = configurations.emplace_back(
554
+ (usbi_configuration_descriptor*)::operator new(configLen));
555
+ copyFromDataView(config.get(), configVal);
556
+ }
557
+
558
+ co_return (int) LIBUSB_SUCCESS;
559
+ }
560
+
561
+ CachedDevice(val device) : device(std::move(device)) {}
562
+
563
+ friend struct ValPtr<CachedDevice>;
564
+ };
565
+
566
+ unsigned long getDeviceSessionId(val& web_usb_device) {
567
+ thread_local const val SessionIdSymbol =
568
+ val::global("Symbol")(val("libusb.session_id"));
569
+
570
+ val session_id_val = web_usb_device[SessionIdSymbol];
571
+ if (!session_id_val.isUndefined()) {
572
+ return session_id_val.as<unsigned long>();
573
+ }
574
+
575
+ // If the device doesn't have a session ID, it means we haven't seen
576
+ // it before. Generate a new session ID for it. We can associate an
577
+ // incrementing ID with the `USBDevice` object itself. It's
578
+ // guaranteed to be alive and, thus, stable as long as the device is
579
+ // connected, even between different libusb invocations. See
580
+ // https://github.com/WICG/webusb/issues/241.
581
+
582
+ static unsigned long next_session_id = 0;
583
+
584
+ web_usb_device.set(SessionIdSymbol, next_session_id);
585
+ return next_session_id++;
586
+ }
587
+
588
+ val getDeviceList(libusb_context* ctx, discovered_devs** devs) {
589
+ // C++ equivalent of `await navigator.usb.getDevices()`. Note: at this point
590
+ // we must already have some devices exposed - caller must have called
591
+ // `await navigator.usb.requestDevice(...)` in response to user interaction
592
+ // before going to LibUSB. Otherwise this list will be empty.
593
+ auto web_usb_devices =
594
+ co_await_try(val::global("navigator")["usb"].call<val>("getDevices"));
595
+ for (auto&& web_usb_device : web_usb_devices) {
596
+ auto session_id = getDeviceSessionId(web_usb_device);
597
+
598
+ auto dev = usbi_get_device_by_session_id(ctx, session_id);
599
+ if (dev == NULL) {
600
+ dev = usbi_alloc_device(ctx, session_id);
601
+ if (dev == NULL) {
602
+ usbi_err(ctx, "failed to allocate a new device structure");
603
+ continue;
604
+ }
605
+
606
+ auto statusVal = co_await CachedDevice::initFromDevice(
607
+ std::move(web_usb_device), dev);
608
+ if (auto error = statusVal.as<int>()) {
609
+ usbi_err(ctx, "failed to read device information: %s",
610
+ libusb_error_name(error));
611
+ libusb_unref_device(dev);
612
+ continue;
613
+ }
614
+
615
+ // We don't have real buses in WebUSB, just pretend everything
616
+ // is on bus 1.
617
+ dev->bus_number = 1;
618
+ // This can wrap around but it's the best approximation of a stable
619
+ // device address and port number we can provide.
620
+ dev->device_address = dev->port_number = (uint8_t)session_id;
621
+ }
622
+ *devs = discovered_devs_append(*devs, dev);
623
+ libusb_unref_device(dev);
624
+ }
625
+ co_return (int) LIBUSB_SUCCESS;
626
+ }
627
+
628
+ int em_get_device_list(libusb_context* ctx, discovered_devs** devs) {
629
+ // No need to wrap into CaughtPromise as we catch all individual ops in the
630
+ // inner implementation and return just the error code. We do need a custom
631
+ // promise type to ensure conversion to int happens on the main thread
632
+ // though.
633
+ struct IntPromise : val {
634
+ IntPromise(val&& promise) : val(std::move(promise)) {}
635
+
636
+ struct AwaitResult {
637
+ int error;
638
+
639
+ AwaitResult(val&& result) : error(result.as<int>()) {}
640
+ };
641
+ };
642
+
643
+ return awaitOnMain(
644
+ [ctx, devs] { return IntPromise(getDeviceList(ctx, devs)); })
645
+ .error;
646
+ }
647
+
648
+ int em_open(libusb_device_handle* handle) {
649
+ return WebUsbDevicePtr(handle)->safeOpenCloseOnMain(OpenClose::Open);
650
+ }
651
+
652
+ void em_close(libusb_device_handle* handle) {
653
+ // LibUSB API doesn't allow us to handle an error here, but we still need to
654
+ // wait for the promise to make sure that subsequent attempt to reopen the
655
+ // same device doesn't fail with a "device busy" error.
656
+ if (auto error =
657
+ WebUsbDevicePtr(handle)->safeOpenCloseOnMain(OpenClose::Close)) {
658
+ usbi_err(handle->dev->ctx, "failed to close device: %s",
659
+ libusb_error_name(error));
660
+ }
661
+ }
662
+
663
+ int em_get_active_config_descriptor(libusb_device* dev, void* buf, size_t len) {
664
+ auto& cached_device = *WebUsbDevicePtr(dev);
665
+ auto config_value = cached_device.getActiveConfigValue();
666
+ if (auto config = cached_device.findConfigDescriptorByValue(config_value)) {
667
+ return cached_device.copyConfigDescriptor(config, buf, len);
668
+ } else {
669
+ return LIBUSB_ERROR_NOT_FOUND;
670
+ }
671
+ }
672
+
673
+ int em_get_config_descriptor(libusb_device* dev,
674
+ uint8_t config_id,
675
+ void* buf,
676
+ size_t len) {
677
+ auto& cached_device = *WebUsbDevicePtr(dev);
678
+ if (auto config = cached_device.getConfigDescriptor(config_id)) {
679
+ return cached_device.copyConfigDescriptor(config, buf, len);
680
+ } else {
681
+ return LIBUSB_ERROR_NOT_FOUND;
682
+ }
683
+ }
684
+
685
+ int em_get_configuration(libusb_device_handle* dev_handle,
686
+ uint8_t* config_value) {
687
+ *config_value = WebUsbDevicePtr(dev_handle)->getActiveConfigValue();
688
+ return LIBUSB_SUCCESS;
689
+ }
690
+
691
+ int em_get_config_descriptor_by_value(libusb_device* dev,
692
+ uint8_t config_value,
693
+ void** buf) {
694
+ auto& cached_device = *WebUsbDevicePtr(dev);
695
+ if (auto config = cached_device.findConfigDescriptorByValue(config_value)) {
696
+ *buf = config;
697
+ return config->wTotalLength;
698
+ } else {
699
+ return LIBUSB_ERROR_NOT_FOUND;
700
+ }
701
+ }
702
+
703
+ int em_set_configuration(libusb_device_handle* dev_handle, int config) {
704
+ return WebUsbDevicePtr(dev_handle)->awaitOnMain("setConfiguration", config);
705
+ }
706
+
707
+ int em_claim_interface(libusb_device_handle* handle, uint8_t iface) {
708
+ return WebUsbDevicePtr(handle)->awaitOnMain("claimInterface", iface);
709
+ }
710
+
711
+ int em_release_interface(libusb_device_handle* handle, uint8_t iface) {
712
+ return WebUsbDevicePtr(handle)->awaitOnMain("releaseInterface", iface);
713
+ }
714
+
715
+ int em_set_interface_altsetting(libusb_device_handle* handle,
716
+ uint8_t iface,
717
+ uint8_t altsetting) {
718
+ return WebUsbDevicePtr(handle)->awaitOnMain("selectAlternateInterface",
719
+ iface, altsetting);
720
+ }
721
+
722
+ int em_clear_halt(libusb_device_handle* handle, unsigned char endpoint) {
723
+ std::string direction = endpoint & LIBUSB_ENDPOINT_IN ? "in" : "out";
724
+ endpoint &= LIBUSB_ENDPOINT_ADDRESS_MASK;
725
+
726
+ return WebUsbDevicePtr(handle)->awaitOnMain("clearHalt", direction,
727
+ endpoint);
728
+ }
729
+
730
+ int em_reset_device(libusb_device_handle* handle) {
731
+ return WebUsbDevicePtr(handle)->awaitOnMain("reset");
732
+ }
733
+
734
+ void em_destroy_device(libusb_device* dev) {
735
+ WebUsbDevicePtr(dev).free();
736
+ }
737
+
738
+ int em_submit_transfer(usbi_transfer* itransfer) {
739
+ return runOnMain([itransfer] {
740
+ auto transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
741
+ auto& web_usb_device = WebUsbDevicePtr(transfer->dev_handle)
742
+ ->getDeviceAssumingMainThread();
743
+ val transfer_promise;
744
+ switch (transfer->type) {
745
+ case LIBUSB_TRANSFER_TYPE_CONTROL: {
746
+ transfer_promise = makeControlTransferPromise(
747
+ web_usb_device,
748
+ libusb_control_transfer_get_setup(transfer));
749
+ break;
750
+ }
751
+ case LIBUSB_TRANSFER_TYPE_BULK:
752
+ case LIBUSB_TRANSFER_TYPE_INTERRUPT: {
753
+ auto endpoint =
754
+ transfer->endpoint & LIBUSB_ENDPOINT_ADDRESS_MASK;
755
+
756
+ if (IS_XFERIN(transfer)) {
757
+ transfer_promise = web_usb_device.call<val>(
758
+ "transferIn", endpoint, transfer->length);
759
+ } else {
760
+ auto data = getUnsharedMemoryView(transfer->buffer,
761
+ transfer->length);
762
+ transfer_promise =
763
+ web_usb_device.call<val>("transferOut", endpoint, data);
764
+ }
765
+
766
+ break;
767
+ }
768
+ // TODO: add implementation for isochronous transfers too.
769
+ default:
770
+ return LIBUSB_ERROR_NOT_SUPPORTED;
771
+ }
772
+ // Not a coroutine because we don't want to block on this promise, just
773
+ // schedule an asynchronous callback.
774
+ promiseThen(CaughtPromise(std::move(transfer_promise)),
775
+ [itransfer](auto&& result) {
776
+ WebUsbTransferPtr(itransfer).emplace(std::move(result));
777
+ usbi_signal_transfer_completion(itransfer);
778
+ });
779
+ return LIBUSB_SUCCESS;
780
+ });
781
+ }
782
+
783
+ void em_clear_transfer_priv(usbi_transfer* itransfer) {
784
+ WebUsbTransferPtr(itransfer).free();
785
+ }
786
+
787
+ int em_cancel_transfer(usbi_transfer* itransfer) {
788
+ return LIBUSB_SUCCESS;
789
+ }
790
+
791
+ int em_handle_transfer_completion(usbi_transfer* itransfer) {
792
+ libusb_transfer_status status = runOnMain([itransfer] {
793
+ auto transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
794
+
795
+ // Take ownership of the transfer result, as `em_clear_transfer_priv` is
796
+ // not called automatically for completed transfers and we must free it
797
+ // to avoid leaks.
798
+
799
+ auto result = WebUsbTransferPtr(itransfer).take();
800
+
801
+ if (itransfer->state_flags & USBI_TRANSFER_CANCELLING) {
802
+ return LIBUSB_TRANSFER_CANCELLED;
803
+ }
804
+
805
+ if (result.error) {
806
+ return LIBUSB_TRANSFER_ERROR;
807
+ }
808
+
809
+ auto& value = result.value;
810
+
811
+ void* dataDest;
812
+ unsigned char endpointDir;
813
+
814
+ if (transfer->type == LIBUSB_TRANSFER_TYPE_CONTROL) {
815
+ dataDest = libusb_control_transfer_get_data(transfer);
816
+ endpointDir =
817
+ libusb_control_transfer_get_setup(transfer)->bmRequestType;
818
+ } else {
819
+ dataDest = transfer->buffer;
820
+ endpointDir = transfer->endpoint;
821
+ }
822
+
823
+ if (endpointDir & LIBUSB_ENDPOINT_IN) {
824
+ auto data = value["data"];
825
+ if (!data.isNull()) {
826
+ itransfer->transferred = data["byteLength"].as<int>();
827
+ copyFromDataView(dataDest, data);
828
+ }
829
+ } else {
830
+ itransfer->transferred = value["bytesWritten"].as<int>();
831
+ }
832
+
833
+ return getTransferStatus(value);
834
+ });
835
+
836
+ // Invoke user's handlers outside of the main thread to reduce pressure.
837
+ return status == LIBUSB_TRANSFER_CANCELLED
838
+ ? usbi_handle_transfer_cancellation(itransfer)
839
+ : usbi_handle_transfer_completion(itransfer, status);
840
+ }
841
+
842
+ } // namespace
843
+
844
+ #pragma clang diagnostic ignored "-Wmissing-field-initializers"
845
+ extern "C" const usbi_os_backend usbi_backend = {
846
+ .name = "Emscripten + WebUSB backend",
847
+ .caps = LIBUSB_CAP_HAS_CAPABILITY,
848
+ .get_device_list = em_get_device_list,
849
+ .open = em_open,
850
+ .close = em_close,
851
+ .get_active_config_descriptor = em_get_active_config_descriptor,
852
+ .get_config_descriptor = em_get_config_descriptor,
853
+ .get_config_descriptor_by_value = em_get_config_descriptor_by_value,
854
+ .get_configuration = em_get_configuration,
855
+ .set_configuration = em_set_configuration,
856
+ .claim_interface = em_claim_interface,
857
+ .release_interface = em_release_interface,
858
+ .set_interface_altsetting = em_set_interface_altsetting,
859
+ .clear_halt = em_clear_halt,
860
+ .reset_device = em_reset_device,
861
+ .destroy_device = em_destroy_device,
862
+ .submit_transfer = em_submit_transfer,
863
+ .cancel_transfer = em_cancel_transfer,
864
+ .clear_transfer_priv = em_clear_transfer_priv,
865
+ .handle_transfer_completion = em_handle_transfer_completion,
866
+ .device_priv_size = sizeof(CachedDevice),
867
+ .transfer_priv_size = sizeof(PromiseResult),
868
+ };
869
+
870
+ #pragma clang diagnostic pop