diff --git a/cpp/channel_discoverer.cpp b/cpp/channel_discoverer.cpp index 3d2c773..4e06cbf 100644 --- a/cpp/channel_discoverer.cpp +++ b/cpp/channel_discoverer.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -55,3 +56,7 @@ bool ChannelDiscoverer::try_parse_key(const char* begin, const char* end, const *val = std::string{val_begin, val_end}; return true; } + +RichStatus ChannelDiscoverer::show_device_dialog() { + return F_MAKE_ERR("not implemented"); +} diff --git a/cpp/fibre.cpp b/cpp/fibre.cpp index 2f28e51..94e6fe8 100644 --- a/cpp/fibre.cpp +++ b/cpp/fibre.cpp @@ -281,6 +281,13 @@ RichStatus Fibre::deregister_backend(std::string name) { } #endif +void Domain::show_device_dialog(std::string backend) { + if (F_LOG_IF(ctx->logger, channel_discovery_handles.find(backend) == channel_discovery_handles.end(), backend << " not running")) { + return; + } + F_LOG_IF_ERR(ctx->logger, ctx->discoverers[backend]->show_device_dialog(), "can't show device dialog"); +} + #if FIBRE_ENABLE_CLIENT void Domain::start_discovery(Callback on_found_object, Callback on_lost_object) { on_found_object_ = on_found_object; diff --git a/cpp/include/fibre/channel_discoverer.hpp b/cpp/include/fibre/channel_discoverer.hpp index 378f6c5..8a01f95 100644 --- a/cpp/include/fibre/channel_discoverer.hpp +++ b/cpp/include/fibre/channel_discoverer.hpp @@ -45,6 +45,7 @@ class ChannelDiscoverer { const char* specs, size_t specs_len, ChannelDiscoveryContext** handle) = 0; virtual RichStatus stop_channel_discovery(ChannelDiscoveryContext* handle) = 0; + virtual RichStatus show_device_dialog(); static bool try_parse_key(const char* begin, const char* end, const char* key, const char** val_begin, const char** val_end); static bool try_parse_key(const char* begin, const char* end, const char* key, int* val); diff --git a/cpp/include/fibre/domain.hpp b/cpp/include/fibre/domain.hpp index b07968a..becb94d 100644 --- a/cpp/include/fibre/domain.hpp +++ b/cpp/include/fibre/domain.hpp @@ -29,6 +29,8 @@ struct LegacyObject; class Domain { friend struct Fibre; public: + void show_device_dialog(std::string backend); + #if FIBRE_ENABLE_CLIENT // TODO: add interface argument // TODO: support multiple discovery instances diff --git a/cpp/include/fibre/libfibre.h b/cpp/include/fibre/libfibre.h index 8db780b..be51e75 100644 --- a/cpp/include/fibre/libfibre.h +++ b/cpp/include/fibre/libfibre.h @@ -85,8 +85,6 @@ struct LibFibreCallContext; struct LibFibreObject; struct LibFibreInterface; struct LibFibreFunction; -struct LibFibreTxStream; -struct LibFibreRxStream; struct LibFibreDomain; // This enum must remain identical to fibre::Status. @@ -290,20 +288,6 @@ struct LibFibreTask { */ typedef void (*run_tasks_cb_t)(LibFibreCtx* ctx, LibFibreTask* tasks, size_t n_tasks, LibFibreTask** out_tasks, size_t* n_out_tasks); -/** - * @brief on_start_discovery callback type for libfibre_register_backend(). - * - * For every channel pair that the application finds that matches the filter of - * this discoverer the application should call libfibre_add_channels(). - * - * @param discovery_handle: An opaque handle that libfibre will pass to the - * corresponding on_stop_discovery callback to stop the discovery. - * @param specs, specs_length: The specs string that specifies discoverer-specific - * filter parameters. - */ -typedef void (*on_start_discovery_cb_t)(void* ctx, LibFibreDomain* domain, const char* specs, size_t specs_length); -typedef void (*on_stop_discovery_cb_t)(void* ctx, LibFibreDomain* domain); - /** * @brief on_found_object callback type for libfibre_start_discovery(). * @param obj: The object handle. @@ -361,48 +345,6 @@ typedef LibFibreStatus (*libfibre_call_cb_t)(void* ctx, const unsigned char** tx_buf, size_t* tx_len, unsigned char** rx_buf, size_t* rx_len); -/** - * @brief TX completion callback type for libfibre_start_tx(). - * - * @param ctx: The user data that was passed to libfibre_start_tx(). - * @param tx_stream: The TX stream on which the TX operation completed. - * @param status: The status of the last TX operation. - * - kFibreOk: The indicated range of the TX buffer was successfully - * transmitted and the stream might accept more data. - * - kFibreClosed: The indicated range of the TX buffer was successfully - * transmitted and the stream will no longer accept any data. - * - Any other status: Successful transmission of the data cannot be - * guaranteed and no more data can be sent on this stream. - * @param tx_end: Points to the address after the last byte read from the - * TX buffer. This pointer always points to a valid position in the - * buffer (or the end of the buffer), even if the transmission failed. - * However if the status is something other than kFibreOk and - * kFibreClosed then the pointer may not precisely indicate the - * transmitted data range. - */ -typedef void (*on_tx_completed_cb_t)(void* ctx, LibFibreTxStream* tx_stream, LibFibreStatus status, const uint8_t* tx_end); - -/** - * @brief RX completion callback type for libfibre_start_rx(). - * - * @param ctx: The user data that was passed to libfibre_start_rx(). - * @param rx_stream: The RX stream on which the RX operation completed. - * @param status: The status of the last RX operation. - * - kFibreOk: The indicated range of the RX buffer was successfully - * filled with received data and the stream might emit more data. - * - kFibreClosed: The indicated range of the RX buffer was successfully - * filled with received data and the stream will emit no more data. - * - Any other status: Successful transmission of the data cannot be - * guaranteed and no more data can be sent on this stream. - * @param rx_end: Points to the address after the last byte written to the - * RX buffer. This pointer always points to a valid position in the - * buffer (or the end of the buffer), even if the reception failed. - * However if the status is something other than kFibreOk and - * kFibreClosed then the pointer may not precisely indicate the - * received data range. - */ -typedef void (*on_rx_completed_cb_t)(void* ctx, LibFibreRxStream* rx_stream, LibFibreStatus status, uint8_t* rx_end); - /** * @brief Returns the version of the libfibre library. * @@ -441,18 +383,6 @@ FIBRE_PUBLIC struct LibFibreCtx* libfibre_open(LibFibreEventLoop event_loop, run */ FIBRE_PUBLIC void libfibre_close(struct LibFibreCtx* ctx); -/** - * @brief Registers an external channel provider. - * - * Libfibre starts and stops the discoverer on demand as a result of calls - * to libfibre_start_discovery() and libfibre_stop_discovery(). - * This can be used by applications to implement transport providers which are - * not supported natively in libfibre. - */ -FIBRE_PUBLIC void libfibre_register_backend(LibFibreCtx* ctx, const char* name, - size_t name_length, on_start_discovery_cb_t on_start_discovery, - on_stop_discovery_cb_t on_stop_discovery, void* cb_ctx); - /** * @brief Creates a communication domain from the specified spec string. * @@ -472,11 +402,22 @@ FIBRE_PUBLIC LibFibreDomain* libfibre_open_domain(LibFibreCtx* ctx, FIBRE_PUBLIC void libfibre_close_domain(LibFibreDomain* domain); /** - * @brief Adds new TX and RX channels to a domain. + * @brief Opens a platform-specific interactive dialog to request access to a + * device or resource. + * + * This is only relevant in some sandboxed environments where libfibre doesn't + * have access to all devices by default. For instance when running in a + * webbrowser, libfibre's usb backend (WebUSB) doesn't have access to any USB + * devices by default. Calling this function will display the browser's USB + * device selection dialog. If the user selects a device, that device will be + * included in the current ongoing (and future) discovery processes. * - * The channels can be closed with libfibre_close_tx() and libfibre_close_rx(). + * Usually this function must be called as a result of user interaction, such + * as a button press. + * + * @param backend: The backend for which to open the dialog. */ -FIBRE_PUBLIC void libfibre_add_channels(LibFibreDomain* domain, LibFibreRxStream** tx_channel, LibFibreTxStream** rx_channel, size_t mtu, bool packetized); +FIBRE_PUBLIC void libfibre_show_device_dialog(LibFibreDomain* domain, const char* backend); /** * @brief Starts looking for Fibre objects that match the specifications. @@ -593,104 +534,6 @@ FIBRE_PUBLIC LibFibreStatus libfibre_get_attribute(LibFibreInterface* intf, LibF */ FIBRE_PUBLIC void libfibre_run_tasks(LibFibreCtx* ctx, LibFibreTask* tasks, size_t n_tasks, LibFibreTask** out_tasks, size_t* n_out_tasks); -/** - * @brief Starts sending data on the specified TX stream. - * - * DEPRECATED! (see top of this file) - * TODO: remove - * - * The TX operation must be considered in progress until the on_completed - * callback is called. Until then the application must not start another TX - * operation on the same stream. In the meantime the application can call - * libfibre_cancel_tx() at any time to abort the operation. - * - * @param tx_stream: The stream on which to send data. - * @param tx_buf: The buffer to transmit. Must remain valid until the operation - * completes. - * @param tx_len: Length of tx_buf. - * @param on_completed: Called when the operation completes, whether successful - * or not. - * @param ctx: Arbitrary user data passed to the on_completed callback. - */ -FIBRE_PUBLIC void libfibre_start_tx(LibFibreTxStream* tx_stream, const uint8_t* tx_buf, size_t tx_len, on_tx_completed_cb_t on_completed, void* ctx); - -/** - * @brief Cancels an ongoing TX operation. - * - * DEPRECATED! (see top of this file) - * TODO: remove - * - * Must only be called if there is actually a TX operation in progress for which - * cancellation has not yet been requested. - * The application must still wait for the on_complete callback to be called - * before the operation can be considered finished. The completion callback may - * be called with kFibreCancelled or any other status. - * - * TODO: specify if streams can be restarted (current doc of on_tx_completed_cb_t implies no) - * - * @param tx_stream: The TX stream on which to cancel the ongoing TX operation. - */ -FIBRE_PUBLIC void libfibre_cancel_tx(LibFibreTxStream* tx_stream); - -/** - * @brief Permanently close TX stream. - * - * DEPRECATED! (see top of this file) - * TODO: remove - * - * Must not be called while a transfer is ongoing. - */ -FIBRE_PUBLIC void libfibre_close_tx(LibFibreTxStream* tx_stream, LibFibreStatus status); - -/** - * @brief Starts receiving data on the specified RX stream. - * - * DEPRECATED! (see top of this file) - * TODO: remove - * - * The RX operation must be considered in progress until the on_completed - * callback is called. Until then the application must not start another RX - * operation on the same stream. In the meantime the application can call - * libfibre_cancel_rx() at any time to abort the operation. - * - * @param rx_stream: The stream on which to receive data. - * @param rx_buf: The buffer to receive to. Must remain valid until the - * operation completes. - * @param rx_len: Length of rx_buf. - * @param on_completed: Called when the operation completes, whether successful - * or not. - * @param ctx: Arbitrary user data passed to the on_completed callback. - */ -FIBRE_PUBLIC void libfibre_start_rx(LibFibreRxStream* rx_stream, uint8_t* rx_buf, size_t rx_len, on_rx_completed_cb_t on_completed, void* ctx); - -/** - * @brief Cancels an ongoing RX operation. - * - * DEPRECATED! (see top of this file) - * TODO: remove - * - * Must only be called if there is actually a RX operation in progress for which - * cancellation has not yet been requested. - * The application must still wait for the on_complete callback to be called - * before the operation can be considered finished. The completion callback may - * be called with kFibreCancelled or any other status. - * - * TODO: specify if streams can be restarted (current doc of on_rx_completed_cb_t implies no) - * - * @param rx_stream: The RX stream on which to cancel the ongoing RX operation. - */ -FIBRE_PUBLIC void libfibre_cancel_rx(LibFibreRxStream* rx_stream); - -/** - * @brief Permanently close RX stream. - * - * DEPRECATED! (see top of this file) - * TODO: remove - * - * Must not be called while a transfer is ongoing. - */ -FIBRE_PUBLIC void libfibre_close_rx(LibFibreRxStream* rx_stream, LibFibreStatus status); - #ifdef __cplusplus } #endif diff --git a/cpp/interfaces/usb.hpp b/cpp/interfaces/usb.hpp index 7e231c7..ad52fca 100644 --- a/cpp/interfaces/usb.hpp +++ b/cpp/interfaces/usb.hpp @@ -16,7 +16,7 @@ enum class UsbTransferType : uint8_t { }; struct UsbEndpointDesc { - uint8_t number; // MSB indicates direction (0: OUT, 1: IN) + uint8_t number; //!< MSB indicates direction (0: OUT, 1: IN) uint16_t max_packet_size; UsbTransferType type; }; @@ -40,11 +40,83 @@ struct UsbConfigDesc { }; struct UsbDevice { + /** + * @brief Returns basic information about the device. + * + * Any parameter can be NULL and in this case no attempt is made to fetch + * the information. + * + * @param bus: Returns the bus number on which the device is connected. + * This value is not available on the WebUSB backend. + * @param address: Returns the address which the device currently has. This + * can change after a replug or device reset event. + * This value is not available on the WebUSB backend. + * @param vendor_id: Returns the vendor ID of the device. + * @param product_id: Returns the product ID of the device. + */ virtual RichStatus get_info(uint8_t* bus, uint8_t* address, uint16_t* vendor_id, uint16_t* product_id) = 0; + + /** + * @brief Loads the configuration descriptor of the currently active + * configuration and passes it to the callback. + * + * This function runs synchronously, meaning that callback is invoked + * before this function returns. The descriptor is freed after the callback + * returns. + * + * If the active config descriptor cannot be loaded, an error is returned + * and `callback` is not invoked. + * + * @param callback: The callback that will be invoked with a pointer to the + * active config descriptor. + */ virtual RichStatus with_active_config_desc(Callback callback) = 0; - virtual RichStatus open(Callback callback) = 0; - virtual RichStatus claim_interface(uint8_t interface_num, Callback callback) = 0; + + /** + * @brief Starts an async operation to open the device. This must be done + * before `claim_interface` can be called. + * + * @param callback: Will be called once the operation completes. The first + * argument indicates whether the operation was successful. + */ + virtual RichStatus open(Callback callback) = 0; + + /** + * @brief Starts an async operation to claim the specified interface. This + * must be done before a transfer can be issued on the associated endpoints. + * + * @param callback: Will be called once the operation completes. The first + * argument indicates whether the operation was successful. + */ + virtual RichStatus claim_interface(uint8_t interface_num, Callback callback) = 0; + + /** + * @brief Starts a bulk IN transfer (device => host). + * + * @param ep_num: The endpoint number. The MSB is always 1 for IN endpoints. + * @param buffer: The buffer into which data should be read. + * @param callback: The callback that will be invoked when the operation + * completes. The first argument indicates whether the operation + * succeeded. The second argument indicates the (exclusive) end of + * the returned range. + * Possible reasons for failure include: stall condition, device + * unplugged, babble condition (device returned more data than + * requested). + */ virtual RichStatus bulk_in_transfer(uint8_t ep_num, bufptr_t buffer, Callback callback) = 0; + + /** + * @brief Starts a bulk OUT transfer (host => device). + * + * @param ep_num: The endpoint number. The MSB is always 0 for IN endpoints. + * @param buffer: The buffer to be transferred. + * @param callback: The callback that will be invoked when the operation + * completes. The first argument indicates whether the operation + * succeeded. The second argument indicates the (exclusive) end of + * the written range. + * Possible reasons for failure include: stall condition, device + * unplugged. + */ virtual RichStatus bulk_out_transfer(uint8_t ep_num, cbufptr_t buffer, Callback callback) = 0; }; @@ -63,6 +135,10 @@ class UsbHostController { * and on_lost(). * * Only one enumeration can be in progress at a time. + * + * When running in a browser with the WebUSB backend this only returns + * devices for which the user has previously authorized the website (also if + * the device is unplugged and replugged). */ virtual RichStatus start(on_found_device_t on_found, on_lost_device_t on_lost) = 0; @@ -76,6 +152,20 @@ class UsbHostController { * call. */ virtual RichStatus stop() = 0; + + /** + * @brief Shows a platform-specific dialog where the user can select a USB + * device to connect to. + * + * This is only implemented on the WebUSB backend. + * + * Once the user completes the dialog, the selected device(s), if any, will + * be announced to the `on_found` callback passed to `start()`. + * + * Returns an error if the dialog cannot be shown (e.g. because it's not + * implemented on this platform). + */ + virtual RichStatus request_device(std::optional vendor_id, std::optional product_id, std::optional intf_class, std::optional intf_subclass, std::optional intf_protocol) = 0; }; } diff --git a/cpp/libfibre.cpp b/cpp/libfibre.cpp index f9aaef3..8e2218b 100644 --- a/cpp/libfibre.cpp +++ b/cpp/libfibre.cpp @@ -125,38 +125,6 @@ class FIBRE_PRIVATE ExternalEventLoop final : public fibre::EventLoop { LibFibreEventLoop impl_; }; -class ExternalDiscoverer : public fibre::ChannelDiscoverer { - void start_channel_discovery( - fibre::Domain* domain, - const char* specs, size_t specs_len, - fibre::ChannelDiscoveryContext** handle) final; - RichStatus stop_channel_discovery(fibre::ChannelDiscoveryContext* handle) final; -public: - on_start_discovery_cb_t on_start_discovery; - on_stop_discovery_cb_t on_stop_discovery; - void* cb_ctx; -}; - - -void ExternalDiscoverer::start_channel_discovery(fibre::Domain* domain, const char* specs, size_t specs_len, fibre::ChannelDiscoveryContext** handle) { - LibFibreChannelDiscoveryCtx* ctx = new LibFibreChannelDiscoveryCtx{}; - if (handle) { - *handle = from_c(ctx); - } - if (on_start_discovery) { - (*on_start_discovery)(cb_ctx, to_c(domain), specs, specs_len); - } -} - -RichStatus ExternalDiscoverer::stop_channel_discovery(fibre::ChannelDiscoveryContext* handle) { - LibFibreChannelDiscoveryCtx* ctx = to_c(handle); - if (on_stop_discovery) { - (*on_stop_discovery)(cb_ctx, to_c(ctx->domain)); - } - delete ctx; - return RichStatus::success(); -} - namespace fibre { class AsyncStreamLink final : public AsyncStreamSink, public AsyncStreamSource { @@ -278,36 +246,6 @@ struct FIBRE_PRIVATE LibFibreDiscoveryCtx { fibre::Domain* domain_; }; -struct LibFibreTxStream { - void on_tx_done(fibre::WriteResult0 result) { - if (on_completed) { - (*on_completed)(ctx, this, convert_status(result.status), result.end); - } - } - - fibre::AsyncStreamSink* sink; - fibre::TransferHandle handle; - on_tx_completed_cb_t on_completed; - void* ctx; - void (*on_closed)(LibFibreTxStream*, void*, fibre::StreamStatus); - void* on_closed_ctx; -}; - -struct LibFibreRxStream { - void on_rx_done(fibre::ReadResult result) { - if (on_completed) { - (*on_completed)(ctx, this, convert_status(result.status), result.end); - } - } - - fibre::AsyncStreamSource* source; - fibre::TransferHandle handle; - on_rx_completed_cb_t on_completed; - void* ctx; - void (*on_closed)(LibFibreRxStream*, void*, fibre::StreamStatus); - void* on_closed_ctx; -}; - void LibFibreDiscoveryCtx::on_found_object(fibre::Object* obj, fibre::Interface* intf, std::string path) { if (on_found_object_) { @@ -508,15 +446,6 @@ void libfibre_close(LibFibreCtx* ctx) { F_LOG_D(logger, "closed (" << fibre::as_hex((uintptr_t)ctx) << ")"); } - -void libfibre_register_backend(LibFibreCtx* ctx, const char* name, size_t name_length, on_start_discovery_cb_t on_start_discovery, on_stop_discovery_cb_t on_stop_discovery, void* cb_ctx) { - auto disc = new ExternalDiscoverer(); - disc->on_start_discovery = on_start_discovery; - disc->on_stop_discovery = on_stop_discovery; - disc->cb_ctx = cb_ctx; - ctx->fibre_ctx->register_backend({name, name + name_length}, disc); -} - FIBRE_PUBLIC LibFibreDomain* libfibre_open_domain(LibFibreCtx* ctx, const char* specs, size_t specs_len) { if (!ctx) { @@ -542,39 +471,8 @@ void libfibre_close_domain(LibFibreDomain* domain) { from_c(domain)->ctx->close_domain(from_c(domain)); } -void libfibre_add_channels(LibFibreDomain* domain, LibFibreRxStream** tx_channel, LibFibreTxStream** rx_channel, size_t mtu, bool packetized) { - fibre::AsyncStreamLink* tx_link = new fibre::AsyncStreamLink(); // libfibre => backend - fibre::AsyncStreamLink* rx_link = new fibre::AsyncStreamLink(); // backend => libfibre - LibFibreRxStream* tx = new LibFibreRxStream(); // libfibre => backend - LibFibreTxStream* rx = new LibFibreTxStream(); // backend => libfibre - tx->source = tx_link; - rx->sink = rx_link; - - tx->on_closed = [](LibFibreRxStream* stream, void* ctx, fibre::StreamStatus status) { - auto link = reinterpret_cast(ctx); - link->close(status); - delete link; - delete stream; - }; - tx->on_closed_ctx = tx_link; - rx->on_closed = [](LibFibreTxStream* stream, void* ctx, fibre::StreamStatus status) { - auto link = reinterpret_cast(ctx); - link->close(status); - delete link; - delete stream; - }; - rx->on_closed_ctx = rx_link; - - if (tx_channel) { - *tx_channel = tx; - } - - if (rx_channel) { - *rx_channel = rx; - } - - fibre::ChannelDiscoveryResult result = {fibre::kFibreOk, rx_link, tx_link, mtu, packetized}; - from_c(domain)->add_legacy_channels(result, "external"); +void libfibre_show_device_dialog(LibFibreDomain* domain, const char* backend) { + from_c(domain)->show_device_dialog(backend); } void libfibre_start_discovery(LibFibreDomain* domain, LibFibreDiscoveryCtx** handle, @@ -737,39 +635,3 @@ void libfibre_run_tasks(LibFibreCtx* ctx, LibFibreTask* tasks, size_t n_tasks, L *out_tasks = ctx->shadow_task_queue.data(); *n_out_tasks = ctx->shadow_task_queue.size(); } - -void libfibre_start_tx(LibFibreTxStream* tx_stream, - const uint8_t* tx_buf, size_t tx_len, on_tx_completed_cb_t on_completed, - void* ctx) { - tx_stream->on_completed = on_completed; - tx_stream->ctx = ctx; - tx_stream->sink->start_write({tx_buf, tx_len}, &tx_stream->handle, MEMBER_CB(tx_stream, on_tx_done)); -} - -void libfibre_cancel_tx(LibFibreTxStream* tx_stream) { - tx_stream->sink->cancel_write(tx_stream->handle); -} - -void libfibre_close_tx(LibFibreTxStream* tx_stream, LibFibreStatus status) { - if (tx_stream->on_closed) { - (tx_stream->on_closed)(tx_stream, tx_stream->on_closed_ctx, convert_status(status)); - } -} - -void libfibre_start_rx(LibFibreRxStream* rx_stream, - uint8_t* rx_buf, size_t rx_len, on_rx_completed_cb_t on_completed, - void* ctx) { - rx_stream->on_completed = on_completed; - rx_stream->ctx = ctx; - rx_stream->source->start_read({rx_buf, rx_len}, &rx_stream->handle, MEMBER_CB(rx_stream, on_rx_done)); -} - -void libfibre_cancel_rx(LibFibreRxStream* rx_stream) { - rx_stream->source->cancel_read(rx_stream->handle); -} - -void libfibre_close_rx(LibFibreRxStream* rx_stream, LibFibreStatus status) { - if (rx_stream->on_closed) { - (rx_stream->on_closed)(rx_stream, rx_stream->on_closed_ctx, convert_status(status)); - } -} diff --git a/cpp/platform_support/dom_connector.hpp b/cpp/platform_support/dom_connector.hpp index 6268f35..a740708 100644 --- a/cpp/platform_support/dom_connector.hpp +++ b/cpp/platform_support/dom_connector.hpp @@ -39,13 +39,14 @@ extern void _js_release(void*); enum class JsType { kUndefined = 0, - kInt = 1, - kString = 2, - kList = 3, - kDict = 4, - kObject = 5, - kFunc = 6, - kArray = 7 + kBool = 1, + kInt = 2, + kString = 3, + kList = 4, + kDict = 5, + kObject = 6, + kFunc = 7, + kArray = 8 }; struct JsStub { @@ -54,6 +55,11 @@ struct JsStub { }; struct JsFuncStub { + JsFuncStub(fibre::Callback cb, unsigned int dict_depth) + : callback((uintptr_t)cb.get_ptr()), + ctx((uintptr_t)cb.get_ctx()), + dict_depth(dict_depth) {} + uintptr_t callback; uintptr_t ctx; unsigned int dict_depth; @@ -67,6 +73,11 @@ struct JsArrayStub { struct JsUndefined {}; static const constexpr JsUndefined js_undefined{}; +//struct JsCallback { +// cb; +// size_t dict_depth; +//} +// class JsTransferStorage { public: JsStub* push(size_t n) { @@ -121,8 +132,8 @@ class JsTransferStorage { return {JsType::kDict, (uintptr_t)arr}; } - JsStub to_js(fibre::Callback val) { - JsFuncStub* func = new JsFuncStub{(uintptr_t)val.get_ptr(), (uintptr_t)val.get_ctx(), 0}; + JsStub to_js(JsFuncStub val) { + JsFuncStub* func = new JsFuncStub{val}; funcs.push_back(func); return {JsType::kFunc, (uintptr_t)func}; } @@ -140,6 +151,7 @@ class JsTransferStorage { class JsObjectTempRef { public: + JsObjectTempRef() : id_(0) {} JsObjectTempRef(unsigned int id) : id_(id) {} JsObjectRef ref() { @@ -189,8 +201,6 @@ class JsObjectTempRef { } private: - JsObjectTempRef(const JsObjectTempRef&) = delete; - unsigned int id_; }; diff --git a/cpp/platform_support/dom_connector.js b/cpp/platform_support/dom_connector.js index 387a688..8502278 100644 --- a/cpp/platform_support/dom_connector.js +++ b/cpp/platform_support/dom_connector.js @@ -2,43 +2,54 @@ mergeInto(LibraryManager.library, { $method_support__postset: 'method_support();', $method_support: function() { - let _root = Function('return this')(); - let _objects = {0: {o: _root, n: 1}}; // key: id, value: object + + function HandleMap() { + this.content = {}; + this.nextId = 0; + this.add = function(val) { + while (this.nextId in this.content) { + this.nextId = (this.nextId + 1) & 0xffffffff; + } + this.content[this.nextId] = val; + return this.nextId++; + } + this.remove = function(id) { + const val = this.content[id]; + delete this.content[id]; + return val; + } + } + + const _root = Function('return this')(); + const _objects = new HandleMap(); + _objects.add({o: _root, n: 1}) // key: id, value: object + const _storages = new HandleMap(); const kTypeUndefined = 0; - const kTypeInt = 1; - const kTypeString = 2; - const kTypeList = 3; - const kTypeDict = 4; - const kTypeObject = 5; - const kTypeFunc = 6; - const kTypeArray = 7; + const kTypeBool = 1; + const kTypeInt = 2; + const kTypeString = 3; + const kTypeList = 4; + const kTypeDict = 5; + const kTypeObject = 6; + const kTypeFunc = 7; + const kTypeArray = 8; function exportObject(obj) { - for (const id in _objects) { - if (_objects[id].o == obj) { - _objects[id].n++; + for (const id in _objects.content) { + if (_objects.content[id].o == obj) { + _objects.content[id].n++; return id; } } - - let id = 0; - while (++id in _objects) {} - _objects[id] = {o: obj, n: 1}; - return id; - } - - function releaseObject(id) { - if (--_objects[id].n == 0) { - delete _objects[id]; - } + return _objects.add({o: obj, n: 1}); } function makeStorage() { return {stubs: [], objects: [], strings: [], arrays: []}; } - function deleteStorage(storage) { + function closeStorage(storage) { for (let i in storage.stubs) { Module._free(storage.stubs[i]); } @@ -50,7 +61,7 @@ mergeInto(LibraryManager.library, { Module._free(storage.arrays[i]); } for (let i in storage.objects) { - releaseObject(storage.objects[i]); + _js_unref(storage.objects[i]); } } @@ -73,8 +84,22 @@ mergeInto(LibraryManager.library, { } else if (type == kTypeFunc) { const callback = Module.HEAPU32[(val >> 2)]; const ctx = Module.HEAPU32[(val >> 2) + 1]; - return () => { - callWasm2(callback, ctx, arguments); + const dictDepth = Module.HEAPU32[(val >> 2) + 2]; + return (...args) => { + const ptr = Module._malloc(8 * args.length); + try { + const storage = makeStorage(); + try { + for (let i = 0; i < args.length; ++i) { + toWasm(storage, args[i], ptr + 8 * i, dictDepth); + } + Module.asm.__indirect_function_table.get(callback)(ctx, ptr, args.length); + } finally { + closeStorage(storage); + } + } finally { + Module._free(ptr); + } }; } else if (type == kTypeArray) { const start = Module.HEAPU32[(val >> 2)]; @@ -91,6 +116,10 @@ mergeInto(LibraryManager.library, { Module.HEAPU32[jsStubPtr >> 2] = kTypeUndefined; Module.HEAPU32[(jsStubPtr >> 2) + 1] = 0; + } else if ((typeof val) == "boolean") { + Module.HEAPU32[jsStubPtr >> 2] = kTypeBool; + Module.HEAPU32[(jsStubPtr >> 2) + 1] = val; + } else if ((typeof val) == "number") { Module.HEAPU32[jsStubPtr >> 2] = kTypeInt; Module.HEAPU32[(jsStubPtr >> 2) + 1] = val; @@ -150,67 +179,62 @@ mergeInto(LibraryManager.library, { } } - function callWasm(callback, ctx, arg, dictDepth) { - const cb = Module.asm.__indirect_function_table.get(callback); - const storage = makeStorage(); - try { - const ptr = Module._malloc(8); - try { - toWasm(storage, arg, ptr, dictDepth); - cb(ctx, ptr); - } finally { - Module._free(ptr); - } - } finally { - deleteStorage(storage); + const _js_ref = function(objId) { + _objects.content[objId].n++; + } + + const _js_unref = function(objId) { + if (--_objects.content[objId].n == 0) { + delete _objects.content[objId]; } } - function callWasm2(callback, ctx, args) { - const cb = Module.asm.__indirect_function_table.get(callback); - const storage = makeStorage(); - try { - const ptr = Module._malloc(8 * args.length); + const _js_call_async = function(objId, func, args, nArgs, callback, ctx, dictDepth) { + const funcName = Module.UTF8ArrayToString(Module.HEAPU8, func); + const argList = [...Array(nArgs).keys()].map((i) => fromWasm(args + 8 * i)); + + const cb = (result, error) => { + const resultStub = Module._malloc(8); try { - for (let i = 0; i < args.length; ++i) { - toWasm(storage, args[i], ptr + 8 * i); + const errorStub = Module._malloc(8); + try { + const storage = makeStorage(); + try { + toWasm(storage, result, resultStub, dictDepth); + toWasm(storage, error, errorStub, dictDepth); + Module.asm.__indirect_function_table.get(callback)(ctx, resultStub, errorStub); + } finally { + closeStorage(storage); + } + } finally { + Module._free(errorStub); } - cb(ctx, ptr, args.length); } finally { - Module._free(ptr); + Module._free(resultStub); } - } finally { - deleteStorage(storage); - } - } - - const _js_ref = function(obj) { - _objects[obj].n++; - } + }; - const _js_unref = function(obj) { - releaseObject(obj); + _objects.content[objId].o[funcName](...argList).then( + (result) => cb(result, undefined), + (error) => { console.log(error); cb(undefined, error)} + ); } - const _js_call_async = function(obj, func, args, nArgs, callback, ctx, dictDepth) { - const funcName = Module.UTF8ArrayToString(Module.HEAPU8, func); - const argList = [...Array(nArgs).keys()].map((i) => fromWasm(args + 8 * i)); - _objects[obj].o[funcName](...argList).then((result) => { - callWasm(callback, ctx, result, dictDepth) - }); + const _js_get_property = function(objId, property, dictDepth, pOut) { + const propName = Module.UTF8ArrayToString(Module.HEAPU8, property); + const result = _objects.content[objId].o[propName]; + const storage = makeStorage(); + toWasm(storage, result, pOut, dictDepth) + return _storages.add(storage); } - const _js_get_property = function(obj, property, callback, ctx, dictDepth) { + const _js_set_property = function(objId, property, arg) { const propName = Module.UTF8ArrayToString(Module.HEAPU8, property); - //console.log("get property ", propName, " of ", obj, _objects[obj]); - const result = _objects[obj].o[propName]; - callWasm(callback, ctx, result, dictDepth); + _objects.content[objId].o[propName] = fromWasm(arg); } - const _js_set_property = function(obj, property, arg) { - const propName = Module.UTF8ArrayToString(Module.HEAPU8, property); - //console.log("set property ", propName, " of ", _objects[obj].o, " to ", fromWasm(arg)); - _objects[obj].o[propName] = fromWasm(arg); + const _js_release = function(storage) { + closeStorage(_storages.remove(storage)); } __js_ref = _js_ref; diff --git a/cpp/platform_support/usb_host_adapter.cpp b/cpp/platform_support/usb_host_adapter.cpp index fb09e47..df2b4d0 100644 --- a/cpp/platform_support/usb_host_adapter.cpp +++ b/cpp/platform_support/usb_host_adapter.cpp @@ -92,6 +92,16 @@ void UsbHostAdapter::stop() { usb_->stop(); } +RichStatus UsbHostAdapter::show_device_dialog() { + return usb_->request_device( + specs_.vendor_id != -1 ? std::make_optional(specs_.vendor_id) : std::nullopt, + specs_.product_id != -1 ? std::make_optional(specs_.product_id) : std::nullopt, + specs_.interface_class != -1 ? std::make_optional(specs_.interface_class) : std::nullopt, + specs_.interface_subclass != -1 ? std::make_optional(specs_.interface_subclass) : std::nullopt, + specs_.interface_protocol != -1 ? std::make_optional(specs_.interface_protocol) : std::nullopt + ); +} + RichStatus UsbHostAdapter::consider(UsbDevice* device, InterfaceSpecs* specs) { uint8_t bus; uint8_t address; @@ -179,12 +189,26 @@ void UsbHostAdapter::on_lost_device(UsbDevice* device) { } } -void UsbHostAdapter::on_opened_device(UsbDevice* device) { +void UsbHostAdapter::on_opened_device(RichStatus status, UsbDevice* device) { OpenDevice* dev = open_devices_[device]; + + if (F_LOG_IF_ERR(logger_, status, "couldn't open device")) { + delete dev; + open_devices_.erase(device); + return; + } + device->claim_interface(dev->interface_num, MEMBER_CB(this, on_claimed_interface)); } -void UsbHostAdapter::on_claimed_interface(UsbDevice* device) { +void UsbHostAdapter::on_claimed_interface(RichStatus status, UsbDevice* device) { OpenDevice* dev = open_devices_[device]; + + if (F_LOG_IF_ERR(logger_, status, "couldn't claim interface " << dev->interface_num)) { + delete dev; + open_devices_.erase(device); + return; + } + domain_->add_legacy_channels({kFibreOk, &dev->ep_in, &dev->ep_out, dev->mtu, true}, "USB"); } diff --git a/cpp/platform_support/usb_host_adapter.hpp b/cpp/platform_support/usb_host_adapter.hpp index 7e43f36..b42d24f 100644 --- a/cpp/platform_support/usb_host_adapter.hpp +++ b/cpp/platform_support/usb_host_adapter.hpp @@ -15,6 +15,7 @@ struct UsbHostAdapter { void start(Domain* domain, const char* specs, size_t specs_len); void stop(); + RichStatus show_device_dialog(); private: struct InterfaceSpecs { @@ -30,8 +31,8 @@ struct UsbHostAdapter { RichStatus consider(UsbDevice* device, InterfaceSpecs* specs); void on_found_device(UsbDevice* device); void on_lost_device(UsbDevice* device); - void on_opened_device(UsbDevice* device); - void on_claimed_interface(UsbDevice* device); + void on_opened_device(RichStatus status, UsbDevice* device); + void on_claimed_interface(RichStatus status, UsbDevice* device); Logger logger_; Domain* domain_; diff --git a/cpp/platform_support/webusb_backend.cpp b/cpp/platform_support/webusb_backend.cpp index 60ef9ca..f43371b 100644 --- a/cpp/platform_support/webusb_backend.cpp +++ b/cpp/platform_support/webusb_backend.cpp @@ -29,7 +29,11 @@ class WebUsb final : public UsbHostController { RichStatus start(on_found_device_t on_found, on_lost_device_t on_lost) final; RichStatus stop() final; + RichStatus request_device(std::optional vendor_id, std::optional product_id, std::optional intf_class, std::optional intf_subclass, std::optional intf_protocol) final; + RichStatus add_device(JsObjectRef ref); + RichStatus remove_device(JsObjectTempRef ref); + void on_request_device_finished(const JsStub& result_stub, const JsStub& error_stub); void on_get_devices_finished(const JsStub& result_stub, const JsStub& error_stub); void on_connect(const JsStub* args, size_t n_args); void on_disconnect(const JsStub* args, size_t n_args); @@ -46,20 +50,21 @@ struct WebUsbDevice final : UsbDevice { RichStatus get_info(uint8_t* bus, uint8_t* address, uint16_t* vendor_id, uint16_t* product_id) final; RichStatus with_active_config_desc(Callback callback) final; - RichStatus open(Callback callback) final; - RichStatus claim_interface(uint8_t interface_num, Callback callback) final; + RichStatus open(Callback callback) final; + RichStatus claim_interface(uint8_t interface_num, Callback callback) final; RichStatus bulk_in_transfer(uint8_t ep_num, bufptr_t buffer, Callback callback) final; RichStatus bulk_out_transfer(uint8_t ep_num, cbufptr_t buffer, Callback callback) final; void on_open_finished(const JsStub& result_stub, const JsStub& error_stub); void on_claim_interface_finished(const JsStub& result_stub, const JsStub& error_stub); + RichStatus wrap_up_transfer(const JsStub& result_stub, const JsStub& error_stub, std::string key, JsStub* val); void on_bulk_in_transfer_finished(const JsStub& result_stub, const JsStub& error_stub); void on_bulk_out_transfer_finished(const JsStub& result_stub, const JsStub& error_stub); WebUsb* webusb_; JsObjectRef ref_; - Callback open_cb_; - Callback claim_interface_cb_; + Callback open_cb_; + Callback claim_interface_cb_; bufptr_t bulk_in_transfer_buf_; Callback bulk_in_transfer_cb_; cbufptr_t bulk_out_transfer_buf_; @@ -76,11 +81,11 @@ RichStatus WebUsb::start(on_found_device_t on_found, on_lost_device_t on_lost) { F_RET_IF_ERR(js_get_root()->get_property("navigator", &navigator), "failed to get navigator object"); F_RET_IF_ERR(navigator->get_property("usb", &usb_), "failed to get WebUSB object (probably not supported by this browser)"); - usb_->set_property("onconnect", MEMBER_CB(this, on_connect)); - usb_->set_property("ondisconnect", MEMBER_CB(this, on_disconnect)); + usb_->set_property("onconnect", JsFuncStub{MEMBER_CB(this, on_connect), 0}); + usb_->set_property("ondisconnect", JsFuncStub{MEMBER_CB(this, on_disconnect), 0}); std::unordered_map< - std::string, std::vector>> + std::string, std::vector>> filters = {{"filters", {}}}; usb_->call_async("getDevices", MEMBER_CB(this, on_get_devices_finished), 0, @@ -101,8 +106,74 @@ RichStatus WebUsb::stop() { return RichStatus::success(); } +RichStatus WebUsb::request_device(std::optional vendor_id, std::optional product_id, std::optional intf_class, std::optional intf_subclass, std::optional intf_protocol) { + std::unordered_map filter; + + if (vendor_id.has_value()) { + filter["vendorId"] = *vendor_id; + } + if (product_id.has_value()) { + filter["productId"] = *product_id; + } + if (intf_class.has_value()) { + filter["classCode"] = *intf_class; + } + if (intf_subclass.has_value()) { + filter["subclassCode"] = *intf_subclass; + } + if (intf_protocol.has_value()) { + filter["protocolCode"] = *intf_protocol; + } + + std::unordered_map< + std::string, std::vector>> + filters = {{"filters", {filter}}}; + + usb_->call_async("requestDevice", MEMBER_CB(this, on_request_device_finished), 0, filters); + + return RichStatus::success(); +} + +RichStatus WebUsb::add_device(JsObjectRef ref) { + F_RET_IF(known_devices_.find(ref->get_id()) != known_devices_.end(), "device already known"); + + WebUsbDevice* dev = new WebUsbDevice(this, ref); + known_devices_[dev->ref_->get_id()] = dev; + on_found_.invoke(dev); + + return RichStatus::success(); +} + +RichStatus WebUsb::remove_device(JsObjectTempRef ref) { + auto it = known_devices_.find(ref.get_id()); + F_RET_IF(it == known_devices_.end(), "unknown device"); + + WebUsbDevice* dev = it->second; + known_devices_.erase(it); + on_lost_.invoke(dev); + delete dev; + + return RichStatus::success(); +} + +void WebUsb::on_request_device_finished(const JsStub& result_stub, const JsStub& error_stub) { + if (error_stub.type != JsType::kUndefined) { + F_LOG_W(logger_, "user did not select any device"); + return; + } + + JsObjectRef device; + if (F_LOG_IF_ERR(logger_, from_js(result_stub, &device), "cannot use result")) { + return; + } + + F_LOG_IF_ERR(logger_, add_device(device), "can't add device"); +} + void WebUsb::on_get_devices_finished(const JsStub& result_stub, const JsStub& error_stub) { - F_LOG_D(logger_, "got devices"); + if (F_LOG_IF(logger_, error_stub.type != JsType::kUndefined, "getDevices() failed")) { + return; + } std::vector devices; if (F_LOG_IF_ERR(logger_, from_js(result_stub, &devices), "in device list")) { @@ -112,26 +183,48 @@ void WebUsb::on_get_devices_finished(const JsStub& result_stub, const JsStub& er F_LOG_D(logger_, "got " << devices.size() << " devices"); for (auto& ref: devices) { - WebUsbDevice* dev = new WebUsbDevice(this, ref); - known_devices_[dev->ref_->get_id()] = dev; - on_found_.invoke(dev); + F_LOG_IF_ERR(logger_, add_device(ref), "can't add device"); } } void WebUsb::on_connect(const JsStub* args, size_t n_args) { - if (n_args != 1) { - F_LOG_W(logger_, "expected 1 args but got " << n_args << " args"); + if (F_LOG_IF(logger_, n_args != 1, "expected 1 args but got " << n_args << " args")) { + return; + } + + JsObjectTempRef event; + if (F_LOG_IF_ERR(logger_, from_js(args[0], &event), "in USBConnectionEvent")) { + return; + } + + JsObjectRef device; + if (F_LOG_IF_ERR(logger_, event.get_property("device", &device), "in USBConnectionEvent")) { return; } - F_LOG_W(logger_, "device connected [TODO]"); + + F_LOG_D(logger_, "device connected"); + + F_LOG_IF_ERR(logger_, add_device(device), "can't add device"); } void WebUsb::on_disconnect(const JsStub* args, size_t n_args) { - if (n_args != 1) { - F_LOG_W(logger_, "expected 1 args but got " << n_args << " args"); + if (F_LOG_IF(logger_, n_args != 1, "expected 1 args but got " << n_args << " args")) { + return; + } + + JsObjectTempRef event; + if (F_LOG_IF_ERR(logger_, from_js(args[0], &event), "in USBConnectionEvent")) { + return; + } + + JsObjectTempRef device; + if (F_LOG_IF_ERR(logger_, event.get_property("device", &device), "in USBConnectionEvent")) { return; } - F_LOG_W(logger_, "device disconnected [TODO]"); + + F_LOG_D(logger_, "device disconnected"); + + F_LOG_IF_ERR(logger_, remove_device(device), "can't remove device"); } RichStatus WebUsbDevice::get_info(uint8_t* bus, uint8_t* address, uint16_t* vendor_id, uint16_t* product_id) { @@ -215,13 +308,13 @@ RichStatus WebUsbDevice::with_active_config_desc(Callback return RichStatus::success(); } -RichStatus WebUsbDevice::open(Callback callback) { +RichStatus WebUsbDevice::open(Callback callback) { open_cb_ = callback; ref_->call_async("open", MEMBER_CB(this, on_open_finished), 0); return RichStatus::success(); } -RichStatus WebUsbDevice::claim_interface(uint8_t interface_num, Callback callback) { +RichStatus WebUsbDevice::claim_interface(uint8_t interface_num, Callback callback) { claim_interface_cb_ = callback; ref_->call_async("claimInterface", MEMBER_CB(this, on_claim_interface_finished), 0, interface_num); return RichStatus::success(); @@ -244,101 +337,96 @@ RichStatus WebUsbDevice::bulk_out_transfer(uint8_t ep_num, cbufptr_t buffer, Cal } void WebUsbDevice::on_open_finished(const JsStub& result_stub, const JsStub& error_stub) { - if (error_stub.type != JsType::kUndefined) { - F_LOG_D(webusb_->logger_, "open failed with type " << (int)error_stub.type); - // TODO: error handling - } else { - F_LOG_D(webusb_->logger_, "open finished"); - open_cb_.invoke_and_clear(this); - } + F_LOG_T(webusb_->logger_, "open() finished"); + RichStatus status = error_stub.type == JsType::kUndefined ? + RichStatus::success() : + F_MAKE_ERR("open() failed with type " << (int)error_stub.type); + open_cb_.invoke_and_clear(status, this); } void WebUsbDevice::on_claim_interface_finished(const JsStub& result_stub, const JsStub& error_stub) { - F_LOG_D(webusb_->logger_, "claim_interface finished"); - claim_interface_cb_.invoke_and_clear(this); + F_LOG_T(webusb_->logger_, "claimInterface() finished"); + RichStatus status = error_stub.type == JsType::kUndefined ? + RichStatus::success() : + F_MAKE_ERR("claimInterface() failed with type " << (int)error_stub.type); + claim_interface_cb_.invoke_and_clear(status, this); } -void WebUsbDevice::on_bulk_in_transfer_finished(const JsStub& result_stub, const JsStub& error_stub) { - F_LOG_T(webusb_->logger_, "bulk_in_transfer finished"); - +RichStatus WebUsbDevice::wrap_up_transfer(const JsStub& result_stub, const JsStub& error_stub, std::string key, JsStub* val) { std::unordered_map result; - if (F_LOG_IF_ERR(webusb_->logger_, from_js(result_stub, &result), "can't parse transfer result")) { - return; // TODO: propagate error to callback - } + F_RET_IF_ERR(from_js(result_stub, &result), "can't parse transfer result"); auto status_it = result.find("status"); - if (F_LOG_IF(webusb_->logger_, status_it == result.end(), "'status' not found")) { - return; - } + F_RET_IF(status_it == result.end(), "'status' not found"); std::string status; - if (F_LOG_IF_ERR(webusb_->logger_, from_js(status_it->second, &status), "can't read status")) { - return; - } + F_RET_IF_ERR(from_js(status_it->second, &status), "can't read status"); + F_RET_IF(status != "ok", "transfer failed: " << status); - if (F_LOG_IF(webusb_->logger_, status != "ok", "transfer failed")) { - return; - } + auto it = result.find(key); + F_RET_IF(it == result.end(), "'" << key << "' not found"); + *val = it->second; - auto data_it = result.find("data"); - if (F_LOG_IF(webusb_->logger_, data_it == result.end(), "'data' not found")) { - return; - } + return RichStatus::success(); +} +void WebUsbDevice::on_bulk_in_transfer_finished(const JsStub& result_stub, const JsStub& error_stub) { + F_LOG_T(webusb_->logger_, "bulk_in_transfer finished"); + + unsigned char* end = bulk_in_transfer_buf_.begin(); + JsStub data_stub; cbufptr_t data; - if (F_LOG_IF_ERR(webusb_->logger_, from_js(data_it->second, &data), "can't read data")) { - return; + + RichStatus status = wrap_up_transfer(result_stub, error_stub, "data", &data_stub); + if (status.is_error()) { + goto done; } - if (F_LOG_IF(webusb_->logger_, data.size() > bulk_in_transfer_buf_.size(), "more data than expected")) { - return; + status = from_js(data_stub, &data); + if (status.is_error()) { + status = F_AMEND_ERR(status, "can't read data"); + goto done; + } + + if (data.size() > bulk_in_transfer_buf_.size()) { + status = F_MAKE_ERR("more data than expected"); + goto done; } std::copy_n(data.begin(), data.size(), bulk_in_transfer_buf_.begin()); - auto end = bulk_in_transfer_buf_.begin() + data.size(); + end += data.size(); +done: bulk_in_transfer_buf_ = {}; - bulk_in_transfer_cb_.invoke_and_clear(RichStatus::success(), end); + bulk_in_transfer_cb_.invoke_and_clear(status, end); } void WebUsbDevice::on_bulk_out_transfer_finished(const JsStub& result_stub, const JsStub& error_stub) { F_LOG_T(webusb_->logger_, "bulk_out_transfer finished"); - std::unordered_map result; - if (F_LOG_IF_ERR(webusb_->logger_, from_js(result_stub, &result), "can't parse transfer result")) { - return; // TODO: propagate error to callback - } - - auto status_it = result.find("status"); - if (F_LOG_IF(webusb_->logger_, status_it == result.end(), "'status' not found")) { - return; - } - - std::string status; - if (F_LOG_IF_ERR(webusb_->logger_, from_js(status_it->second, &status), "can't read status")) { - return; - } - - if (F_LOG_IF(webusb_->logger_, status != "ok", "transfer failed")) { - return; + const unsigned char* end = bulk_in_transfer_buf_.begin(); + JsStub bytes_written_stub; + size_t bytes_written; + + RichStatus status = wrap_up_transfer(result_stub, error_stub, "bytesWritten", &bytes_written_stub); + if (status.is_error()) { + goto done; } - auto bytes_written_it = result.find("bytesWritten"); - if (F_LOG_IF(webusb_->logger_, bytes_written_it == result.end(), "'bytesWritten' not found")) { - return; + status = from_js(bytes_written_stub, &bytes_written); + if (status.is_error()) { + status = F_AMEND_ERR(status, "can't read bytes_written"); + goto done; } - size_t bytes_written; - if (F_LOG_IF_ERR(webusb_->logger_, from_js(bytes_written_it->second, &bytes_written), "can't read bytes_written")) { - return; + if (bytes_written > bulk_out_transfer_buf_.size()) { + status = F_MAKE_ERR("more bytes written than expected"); + goto done; } - if (F_LOG_IF(webusb_->logger_, bytes_written > bulk_out_transfer_buf_.size(), "more bytes written than expected")) { - return; - } - - auto end = bulk_in_transfer_buf_.begin() + bytes_written; + end += bytes_written; +done: bulk_out_transfer_buf_ = {}; bulk_out_transfer_cb_.invoke_and_clear(RichStatus::success(), end); } @@ -361,6 +449,10 @@ RichStatus WebusbBackend::deinit() { return RichStatus::success(); } +RichStatus WebusbBackend::show_device_dialog() { + return adapter_->show_device_dialog(); +} + void WebusbBackend::start_channel_discovery(Domain* domain, const char* specs, size_t specs_len, ChannelDiscoveryContext** handle) { diff --git a/cpp/platform_support/webusb_backend.hpp b/cpp/platform_support/webusb_backend.hpp index 362ff87..547e7eb 100644 --- a/cpp/platform_support/webusb_backend.hpp +++ b/cpp/platform_support/webusb_backend.hpp @@ -22,6 +22,7 @@ class WebusbBackend : public Backend { void start_channel_discovery(Domain* domain, const char* specs, size_t specs_len, ChannelDiscoveryContext** handle) final; RichStatus stop_channel_discovery(ChannelDiscoveryContext* handle) final; + RichStatus show_device_dialog() final; private: Logger logger_ = Logger::none(); diff --git a/js/example.html b/js/example.html index 3cd863c..f66a37a 100644 --- a/js/example.html +++ b/js/example.html @@ -23,15 +23,7 @@

not connected

} domain.startDiscovery(onFoundObject); - const showDialog = async (obj) => { - await navigator.usb.requestDevice({filters: [{ - vendorId: 0x1209, - productId: 0x0D32, - classCode: 0, - subclassCode: 1, - protocolCode: 0 - }]}); - } + const showDialog = (obj) => domain.showDeviceDialog("usb"); document.getElementById("connectBtn").onclick = showDialog; document.getElementById("connectBtn").removeAttribute('disabled'); diff --git a/js/fibre.js b/js/fibre.js index 649a0b1..4535ba8 100644 --- a/js/fibre.js +++ b/js/fibre.js @@ -572,24 +572,14 @@ class LibFibre { } } - addChannels(domainHandle, mtu) { - console.log("add channel with mtu " + mtu); - const [txChannelId, rxChannelId] = this._withOutputArg((txChannelIdPtr, rxChannelIdPtr) => - this.wasm.Module._libfibre_add_channels(domainHandle, txChannelIdPtr, rxChannelIdPtr, mtu, true) - ); - return [ - new RxStream(this, txChannelId), // libfibre => backend - new TxStream(this, rxChannelId) // backend => libfibre - ]; - } - openDomain(filter) { - let buf = [this.wasm.malloc(filter.length + 1), filter.length + 1]; + const len = this.wasm.Module.lengthBytesUTF8(filter); + const buf = this.wasm.malloc(len + 1); try { - let len = this.wasm.stringToUTF8(filter, buf[0], buf[1]); - var domainHandle = this.wasm.Module._libfibre_open_domain(this._handle, buf[0], len); + this.wasm.stringToUTF8(filter, buf, len + 1); + var domainHandle = this.wasm.Module._libfibre_open_domain(this._handle, buf, len); } finally { - this.wasm.free(buf[0]); + this.wasm.free(buf); } console.log("opened domain", domainHandle); return new Domain(this, domainHandle); @@ -891,8 +881,15 @@ class Domain { this._libfibre._domainMap[handle] = this; } - addChannels(mtu) { - return this._libfibre.addChannels(this._handle, mtu); + showDeviceDialog(backend) { + const len = this._libfibre.wasm.Module.lengthBytesUTF8(backend); + const buf = this._libfibre.wasm.malloc(len + 1); + try { + this._libfibre.wasm.stringToUTF8(backend, buf, len + 1); + this._libfibre.wasm.Module._libfibre_show_device_dialog(this._handle, buf); + } finally { + this._libfibre.wasm.free(buf); + } } startDiscovery(onFoundObject) { diff --git a/python/fibre/libfibre.py b/python/fibre/libfibre.py index 5c4aa08..ae10d58 100644 --- a/python/fibre/libfibre.py +++ b/python/fibre/libfibre.py @@ -395,165 +395,6 @@ def customresize(array, new_size): # return (array._type_ * new_size).from_address(addressof(array)) return (array._type_ * new_size)(*array) -class TxStream(): - """Python wrapper for libfibre's LibFibreTxStream interface - - DEPRECATED - TODO: remove - """ - - def __init__(self, libfibre, tx_stream_handle): - self._libfibre = libfibre - self._tx_stream_handle = tx_stream_handle - self._future = None - self._tx_buf = None - self._c_on_tx_completed = OnTxCompletedSignature(self._on_tx_completed) - self.is_closed = False - - def _on_tx_completed(self, ctx, tx_stream, status, tx_end): - tx_start = cast(self._tx_buf, c_void_p).value - - n_written = tx_end - tx_start - assert(n_written <= len(self._tx_buf)) - future = self._future - self._future = None - self._tx_buf = None - - if status == kFibreClosed: - self.is_closed = True - - if status == kFibreOk or status == kFibreClosed: - future.set_result(n_written) - else: - future.set_exception(_get_exception(status)) - - def write(self, data): - """ - Writes the provided data to the stream. Not all bytes are guaranteed to - be written. The caller should check the return value to determine the - actual number of bytes written. - - If a non-empty buffer is provided, this function will either write at - least one byte to the output, set is_closed to True or throw an - Exception (through the future). - - Currently only one write call may be active at a time (this may change - in the future). - - Returns: A future that completes with the number of bytes actually - written or an Exception. - """ - assert(self._future is None) - self._future = future = self._libfibre.loop.create_future() - self._tx_buf = data # Retain a reference to the buffer to prevent it from being garbage collected - - libfibre_start_tx(self._tx_stream_handle, - cast(self._tx_buf, c_char_p), len(self._tx_buf), - self._c_on_tx_completed, None) - - return future - - async def write_all(self, data): - """ - Writes all of the provided data to the stream or completes with an - Exception. - - If an empty buffer is provided, the underlying stream's write function - is still called at least once. - - Returns: A future that either completes with an empty result or with - an Exception. - """ - - while True: - n_written = await self.write(data) - data = data[n_written:] - if len(data) == 0: - break - elif self.is_closed: - raise EOFError("the TX stream was closed but there are still {} bytes left to send".format(len(data))) - assert(n_written > 0) # Ensure progress - -class RxStream(): - """Python wrapper for libfibre's LibFibreRxStream interface - - DEPRECATED - TODO: remove - """ - - def __init__(self, libfibre, rx_stream_handle): - self._libfibre = libfibre - self._rx_stream_handle = rx_stream_handle - self._future = None - self._rx_buf = None - self._c_on_rx_completed = OnRxCompletedSignature(self._on_rx_completed) - self.is_closed = False - - def _on_rx_completed(self, ctx, rx_stream, status, rx_end): - rx_start = cast(self._rx_buf, c_void_p).value - - n_read = rx_end - rx_start - assert(n_read <= len(self._rx_buf)) - data = self._rx_buf[:n_read] - future = self._future - self._future = None - self._rx_buf = None - - if status == kFibreClosed: - self.is_closed = True - - if status == kFibreOk or status == kFibreClosed: - future.set_result(data) - else: - future.set_exception(_get_exception(status)) - - def read(self, n_read): - """ - Reads up to the specified number of bytes from the stream. - - If more than zero bytes are requested, this function will either read at - least one byte, set is_closed to True or throw an Exception (through the - future). - - Currently only one write call may be active at a time (this may change - in the future). - - Returns: A future that either completes with a buffer containing the - bytes that were read or completes with an Exception. - """ - assert(self._future is None) - self._future = future = self._libfibre.loop.create_future() - self._rx_buf = bytes(n_read) - - libfibre_start_rx(self._rx_stream_handle, - cast(self._rx_buf, c_char_p), len(self._rx_buf), - self._c_on_rx_completed, None) - - return future - - async def read_all(self, n_read): - """ - Reads the specified number of bytes from the stream or throws an - Exception. - - If zero bytes are requested, the underlying stream's read function - is still called at least once. - - Returns: A future that either completes with a buffer of size n_read or - an Exception. - """ - - data = bytes() - while True: - chunk = await self.read(n_read - len(data)) - data += chunk - if n_read == len(data): - break - elif self.is_closed: - raise EOFError() - assert(len(chunk) > 0) # Ensure progress - return data - class TxSocket(): def __init__(self, call, handle): self._call = call