diff --git a/CMakeLists.txt b/CMakeLists.txt index e54693ee..d2a2ef28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -262,11 +262,19 @@ else() ${source_files} ${include_files}) endif() -target_include_directories(aasdk PUBLIC +if(SKIP_BUILD_PROTOBUF) + target_include_directories(aasdk PUBLIC $ - $ + $ $ -) + ) +else() + target_include_directories(aasdk PUBLIC + $ + $ + $ + ) +endif() # Link libraries - aap_protobuf is always built as subdirectory target_link_libraries(aasdk PUBLIC @@ -354,10 +362,26 @@ install(FILES install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/cert/headunit.key" DESTINATION /etc/aasdk - PERMISSIONS OWNER_READ OWNER_WRITE + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ COMPONENT runtime ) +# Align direct "make install" behavior with package postinst: +# create/use aasdk group and assign cert/key group ownership + key group-read. +install(CODE [[ + set(_cert_dir "$ENV{DESTDIR}/etc/aasdk") + set(_cert_file "${_cert_dir}/headunit.crt") + set(_key_file "${_cert_dir}/headunit.key") + + execute_process( + COMMAND /bin/sh -c "if ! getent group aasdk >/dev/null 2>&1; then if command -v addgroup >/dev/null 2>&1; then addgroup --system aasdk || true; elif command -v groupadd >/dev/null 2>&1; then groupadd --system aasdk || true; fi; fi" + ) + + execute_process( + COMMAND /bin/sh -c "if getent group aasdk >/dev/null 2>&1; then if [ -f '${_cert_file}' ]; then chown root:aasdk '${_cert_file}' || true; chmod 644 '${_cert_file}' || true; fi; if [ -f '${_key_file}' ]; then chown root:aasdk '${_key_file}' || true; chmod 640 '${_key_file}' || true; fi; fi" + ) +]] COMPONENT runtime) + # Export the targets to a script install(EXPORT aasdkTargets FILE aasdkTargets.cmake diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index bc55c876..caf334b8 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -294,6 +294,47 @@ lsusb sudo chmod 666 /dev/bus/usb/001/002 # Replace with your device ``` +### USB Receive Retry Corruption + +#### Issue: transport retries eventually produce zero-length frames or channel storms +``` +Repeated receive retries are followed by malformed frame parsing, +zero-byte payload loops, or channel receive queues being rejected. +``` + +**Typical Symptoms:** + +1. `LIBUSB_ERROR_INTERRUPTED` (`native=-4` / `4294967292`) or transient `LIBUSB_ERROR_NO_DEVICE` during bulk IN receives. +2. A later burst of bogus protocol data, often seen as zero-sized frames or repeated channel wakeups. +3. Session teardown even though the original USB error looked transient. + +**Root Cause:** + +`DataSink::fill()` reserves a 16 KB receive slot before libusb writes into it. If the transfer fails and no matching `commit()` happens, that slot remains in the circular buffer unless it is explicitly rolled back. On the next receive cycle, the stale zero-filled slot can be exposed to `distributeReceivedData()` and parsed as real protocol bytes. + +**Fix:** + +1. Track whether a `fill()` is still pending. +2. Call `rollback()` before re-arming a failed receive. +3. Keep `fill()` self-healing so any missed rollback still discards the dangling slot. + +Relevant implementation points: + +1. `include/aasdk/Transport/DataSink.hpp` +2. `src/Transport/DataSink.cpp` +3. `src/Transport/USBTransport.cpp` + +**Validation:** +```bash +# Enable verbose transport logs and watch for receive retries. +export AASDK_LOG_LEVEL=DEBUG +export AASDK_VERBOSE_USB=1 + +# The important invariant is that every failed receive path either calls +# rollback() explicitly or reaches a later fill() that auto-discards the +# uncommitted slot before allocating another 16 KB chunk. +``` + ### SSL/TLS Certificate Issues #### Issue: SSL handshake failures diff --git a/debian/postinst b/debian/postinst index 7d94bacc..726fe0a1 100755 --- a/debian/postinst +++ b/debian/postinst @@ -11,6 +11,15 @@ case "$1" in cert_file="$cert_dir/headunit.crt" key_file="$cert_dir/headunit.key" + # Ensure dedicated runtime group exists. + if ! getent group aasdk >/dev/null 2>&1; then + if command -v addgroup >/dev/null 2>&1; then + addgroup --system aasdk || true + elif command -v groupadd >/dev/null 2>&1; then + groupadd --system aasdk || true + fi + fi + # Ensure target cert directory exists. mkdir -p "$cert_dir" chmod 755 "$cert_dir" || true @@ -36,14 +45,8 @@ case "$1" in fi if [ -f "$key_file" ]; then - chown root:root "$key_file" || true - chmod 600 "$key_file" || true - - # Optional compatibility mode for non-root runtimes in the aasdk group. - if [ "$cert_group" = "aasdk" ]; then - chown root:aasdk "$key_file" || true - chmod 640 "$key_file" || true - fi + chown root:"$cert_group" "$key_file" || true + chmod 640 "$key_file" || true fi # Update the dynamic linker cache diff --git a/include/aasdk/Transport/DataSink.hpp b/include/aasdk/Transport/DataSink.hpp index 46cd155e..ddbf5c44 100644 --- a/include/aasdk/Transport/DataSink.hpp +++ b/include/aasdk/Transport/DataSink.hpp @@ -29,16 +29,30 @@ namespace aasdk::transport { public: DataSink(); + /// Allocate a cChunkSize slot at the tail of the buffer for the next USB + /// receive transfer to write into. If a previous fill() was never + /// followed by commit() (e.g. the USB transfer returned an error), the + /// dangling uncommitted slot is automatically rolled back first so that + /// stale zero-bytes never appear in the protocol stream. common::DataBuffer fill(); + /// Finalise the most recent fill(): trim the unused tail of the slot to + /// the actual number of bytes transferred and clear the pending flag. void commit(common::Data::size_type size); + /// Discard the uncommitted slot from the most recent fill() that was + /// never followed by a successful commit(). Safe to call even when no + /// fill is pending (no-op in that case). + void rollback(); + common::Data::size_type getAvailableSize(); common::Data consume(common::Data::size_type size); private: boost::circular_buffer data_; + /// True between a fill() call and its matching commit() or rollback(). + bool pendingFill_{false}; static constexpr common::Data::size_type cChunkSize = 16384; }; diff --git a/include/aasdk/Transport/TCPTransport.hpp b/include/aasdk/Transport/TCPTransport.hpp index 5b150e4d..07beff05 100644 --- a/include/aasdk/Transport/TCPTransport.hpp +++ b/include/aasdk/Transport/TCPTransport.hpp @@ -79,9 +79,10 @@ namespace aasdk::transport { * @param ioService Boost ASIO io_service for async socket operations * @param tcpEndpoint Connected TCP socket endpoint (phone or test client) * - * Note: ioService and tcpEndpoint must remain valid until TCPTransport is destroyed. + * The shared_ptr keeps the io_service alive as long as the TCPTransport + * (or any pending handler with a shared_from_this() capture) is alive. */ - TCPTransport(boost::asio::io_service &ioService, tcp::ITCPEndpoint::Pointer tcpEndpoint); + TCPTransport(std::shared_ptr ioService, tcp::ITCPEndpoint::Pointer tcpEndpoint); /** * @brief Stop TCP transport and close socket. diff --git a/include/aasdk/Transport/Transport.hpp b/include/aasdk/Transport/Transport.hpp index 5b7a1def..748904f2 100644 --- a/include/aasdk/Transport/Transport.hpp +++ b/include/aasdk/Transport/Transport.hpp @@ -30,7 +30,9 @@ namespace aasdk::transport { class Transport : public ITransport, public std::enable_shared_from_this { public: - Transport(boost::asio::io_service &ioService); + // ioService is held by shared_ptr so the io_context outlives any + // pending handlers that carry a shared_from_this() capture. + explicit Transport(std::shared_ptr ioService); // Deleted copy operations Transport(const Transport &) = delete; @@ -58,6 +60,9 @@ namespace aasdk::transport { DataSink receivedDataSink_; + // Must be declared before strands so it is initialised first. + std::shared_ptr ioServicePtr_; + boost::asio::io_service::strand receiveStrand_; ReceiveQueue receiveQueue_; diff --git a/include/aasdk/Transport/USBTransport.hpp b/include/aasdk/Transport/USBTransport.hpp index 0dbafbc7..a6c99280 100644 --- a/include/aasdk/Transport/USBTransport.hpp +++ b/include/aasdk/Transport/USBTransport.hpp @@ -73,9 +73,10 @@ namespace aasdk::transport { * @param ioService Boost ASIO io_service for async operations * @param aoapDevice AOAP device handle (from IUSBHub discovery) * - * Note: ioService must outlive the USBTransport instance. + * The shared_ptr keeps the io_service alive as long as the USBTransport + * (or any pending handler with a shared_from_this() capture) is alive. */ - USBTransport(boost::asio::io_service &ioService, usb::IAOAPDevice::Pointer aoapDevice); + USBTransport(std::shared_ptr ioService, usb::IAOAPDevice::Pointer aoapDevice); /** * @brief Stop USB transport and reject pending operations. @@ -98,14 +99,35 @@ namespace aasdk::transport { /// Internal: handle USB OUT transfer completion; check for errors, resolve promise void sendHandler(SendQueue::iterator queueElement, common::Data::size_type offset, size_t bytesTransferred); + /// Internal: re-queue a receive after a short recovery delay (strand-safe) + void scheduleReceiveRetry(uint32_t delayMs); + /// AOAP device handle (provides bulk IN/OUT endpoint access) usb::IAOAPDevice::Pointer aoapDevice_; + /// How many times we have retried a LIBUSB_ERROR_NO_DEVICE on the IN endpoint + uint32_t receiveNoDeviceRetryCount_{0}; + + /// How many times we have retried a LIBUSB_ERROR_INTERRUPTED on the IN endpoint + uint32_t receiveInterruptedRetryCount_{0}; + /// Timeout for send operations (10 seconds) static constexpr uint32_t cSendTimeoutMs = 10000; /// Timeout for receive operations (infinite: 0) static constexpr uint32_t cReceiveTimeoutMs = 0; + + /// Max bounded retries for a post-handshake no-device transient error + static constexpr uint32_t cReceiveNoDeviceRetryMax = 2; + + /// Delay (ms) between no-device IN endpoint retries + static constexpr uint32_t cReceiveNoDeviceRetryDelayMs = 200; + + /// Max bounded retries for LIBUSB_ERROR_INTERRUPTED (-4); resets on success + static constexpr uint32_t cReceiveInterruptedRetryMax = 60; + + /// Delay (ms) between interrupted IN endpoint retries (short — just reschedule) + static constexpr uint32_t cReceiveInterruptedRetryDelayMs = 10; }; } diff --git a/include/aasdk/USB/AOAPDevice.hpp b/include/aasdk/USB/AOAPDevice.hpp index f13407c6..0986967c 100644 --- a/include/aasdk/USB/AOAPDevice.hpp +++ b/include/aasdk/USB/AOAPDevice.hpp @@ -30,7 +30,8 @@ namespace aasdk::usb { class AOAPDevice : public IAOAPDevice { public: AOAPDevice(IUSBWrapper &usbWrapper, boost::asio::io_service &ioService, DeviceHandle handle, - const libusb_interface_descriptor *interfaceDescriptor); + uint8_t interfaceNumber, unsigned char inEndpointAddress, + unsigned char outEndpointAddress); ~AOAPDevice() override; @@ -52,9 +53,12 @@ namespace aasdk::usb { static const libusb_interface_descriptor *getInterfaceDescriptor(const libusb_interface *interface); + static std::tuple + extractEndpointDetails(const libusb_interface_descriptor *interfaceDescriptor); + IUSBWrapper &usbWrapper_; DeviceHandle handle_; - const libusb_interface_descriptor *interfaceDescriptor_; + uint8_t interfaceNumber_; IUSBEndpoint::Pointer inEndpoint_; IUSBEndpoint::Pointer outEndpoint_; diff --git a/src/Transport/DataSink.cpp b/src/Transport/DataSink.cpp index 05c74965..93baee7e 100644 --- a/src/Transport/DataSink.cpp +++ b/src/Transport/DataSink.cpp @@ -25,10 +25,24 @@ namespace aasdk { namespace transport { DataSink::DataSink() - : data_(common::cStaticDataSize) { + : data_(common::cStaticDataSize), pendingFill_(false) { } common::DataBuffer DataSink::fill() { + if (pendingFill_) { + // Safety net: a previous fill() never received a matching commit() — most + // likely a USB transfer retry path where the transfer returned an error + // before writing any data (e.g. EINTR, LIBUSB_ERROR_NO_DEVICE). Roll back + // that dangling allocation now so it can never be mistaken for real protocol + // data by distributeReceivedData(). Callers on the retry path should also + // call rollback() explicitly before fill() to document intent, but this + // auto-rollback acts as a hard safety net for any path that misses that. + if (data_.size() >= cChunkSize) { + data_.erase_end(cChunkSize); + } + } + pendingFill_ = true; + const auto offset = data_.size(); data_.resize(data_.size() + cChunkSize); @@ -41,9 +55,22 @@ namespace aasdk { throw error::Error(error::ErrorCode::DATA_SINK_COMMIT_OVERFLOW); } + pendingFill_ = false; data_.erase_end((cChunkSize - size)); } + void DataSink::rollback() { + if (!pendingFill_) { + return; + } + // Only erase if there's actually a pending chunk to roll back. + // Safety check: circular_buffer::erase_end(n) requires size() >= n. + if (data_.size() >= cChunkSize) { + data_.erase_end(cChunkSize); + } + pendingFill_ = false; + } + common::Data::size_type DataSink::getAvailableSize() { return data_.size(); } diff --git a/src/Transport/SSLWrapper.cpp b/src/Transport/SSLWrapper.cpp index 769588cc..b9ba5838 100644 --- a/src/Transport/SSLWrapper.cpp +++ b/src/Transport/SSLWrapper.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #if defined(__has_include) && __has_include() && !defined(OPENSSL_NO_ENGINE) #include #define HAVE_ENGINE_H @@ -34,6 +35,10 @@ namespace aasdk { namespace transport { + namespace { + std::once_flag gOpenSslInitOnce; + } + static auto sslErrorToString(int sslErrorCode) -> const char* { switch (sslErrorCode) { case SSL_ERROR_NONE: @@ -64,25 +69,20 @@ namespace aasdk { } SSLWrapper::SSLWrapper() { - SSL_library_init(); - SSL_load_error_strings(); // Optional: Can also be removed if not needed. - OpenSSL_add_all_algorithms(); + std::call_once(gOpenSslInitOnce, []() { + #if (OPENSSL_VERSION_NUMBER < 0x10100000L) + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + #else + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, + nullptr); + #endif + }); } SSLWrapper::~SSLWrapper() { -#ifdef FIPS_mode_set - FIPS_mode_set(0); // FIPS_mode_set removed in later versions of OpenSSL. -#endif -#ifdef HAVE_ENGINE_H - ENGINE_cleanup(); -#endif - CONF_modules_unload(1); - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); -#if (OPENSSL_VERSION_NUMBER < 0x10100000L) - ERR_remove_state(0); -#endif - ERR_free_strings(); + // OpenSSL state is process-global. Do not tear it down per wrapper instance. } X509 *SSLWrapper::readCertificate(const std::string &certificate) { diff --git a/src/Transport/TCPTransport.cpp b/src/Transport/TCPTransport.cpp index 706f2803..8d90d7d7 100644 --- a/src/Transport/TCPTransport.cpp +++ b/src/Transport/TCPTransport.cpp @@ -22,10 +22,8 @@ namespace aasdk { namespace transport { - TCPTransport::TCPTransport(boost::asio::io_service &ioService, tcp::ITCPEndpoint::Pointer tcpEndpoint) - : Transport(ioService), tcpEndpoint_(std::move(tcpEndpoint)) { - - } + TCPTransport::TCPTransport(std::shared_ptr ioService, tcp::ITCPEndpoint::Pointer tcpEndpoint) + : Transport(std::move(ioService)), tcpEndpoint_(std::move(tcpEndpoint)) {} void TCPTransport::enqueueReceive(common::DataBuffer buffer) { auto receivePromise = tcp::ITCPEndpoint::Promise::defer(receiveStrand_); diff --git a/src/Transport/Transport.cpp b/src/Transport/Transport.cpp index f144f4e7..973918fc 100644 --- a/src/Transport/Transport.cpp +++ b/src/Transport/Transport.cpp @@ -53,8 +53,10 @@ namespace aasdk { namespace transport { - Transport::Transport(boost::asio::io_service &ioService) - : receiveStrand_(ioService), sendStrand_(ioService) {} + Transport::Transport(std::shared_ptr ioService) + : ioServicePtr_(std::move(ioService)), + receiveStrand_(*ioServicePtr_), + sendStrand_(*ioServicePtr_) {} /** * @brief Queue a receive request for N bytes from the transport. @@ -133,6 +135,9 @@ namespace aasdk { } void Transport::rejectReceivePromises(const error::Error &e) { + // Continue to next receive even if this one failed, to avoid infinite loops. + // The DataSink.pendingFill_ flag prevents dangling slots from being served + // as protocol data. for (auto &queueElement: receiveQueue_) { queueElement.second->reject(e); } diff --git a/src/Transport/USBTransport.cpp b/src/Transport/USBTransport.cpp index bc15370f..22486048 100644 --- a/src/Transport/USBTransport.cpp +++ b/src/Transport/USBTransport.cpp @@ -18,13 +18,15 @@ #include #include +#include +#include namespace aasdk { namespace transport { - USBTransport::USBTransport(boost::asio::io_service &ioService, usb::IAOAPDevice::Pointer aoapDevice) - : Transport(ioService), aoapDevice_(std::move(aoapDevice)) {} + USBTransport::USBTransport(std::shared_ptr ioService, usb::IAOAPDevice::Pointer aoapDevice) + : Transport(std::move(ioService)), aoapDevice_(std::move(aoapDevice)) {} void USBTransport::enqueueReceive(common::DataBuffer buffer) { const auto inEndpoint = aoapDevice_->getInEndpoint().getAddress(); @@ -37,6 +39,8 @@ namespace aasdk { AASDK_LOG(debug) << "[USBTransport] receiveComplete endpoint=0x" << std::hex << static_cast(inEndpoint) << std::dec << " bytesTransferred=" << bytesTransferred; + receiveNoDeviceRetryCount_ = 0; + receiveInterruptedRetryCount_ = 0; this->receiveHandler(bytesTransferred); }, [this, self = this->shared_from_this(), inEndpoint](auto e) { @@ -45,12 +49,72 @@ namespace aasdk { << std::dec << " code=" << static_cast(e.getCode()) << " native=" << e.getNativeCode() << " what=" << e.what(); + + // Bounded recovery for LIBUSB_ERROR_NO_DEVICE (native=5) on the IN + // endpoint: Android briefly resets the USB link after AOAP handshake. + // Re-arm the bulk transfer after a short delay instead of cascading + // the error to all channels immediately. + static constexpr uint32_t kLibusbNoDevice = 5u; + // Bounded recovery for LIBUSB_ERROR_INTERRUPTED (native=-4 / 4294967292) + // on the IN endpoint: Linux EINTR from signal; data still in USB buffer. + // Retry immediately rather than disrupting receive queue. + static constexpr uint32_t kLibusbInterrupted = 4294967292u; + + if (e.getNativeCode() == kLibusbNoDevice && + receiveNoDeviceRetryCount_ < cReceiveNoDeviceRetryMax) { + receiveNoDeviceRetryCount_++; + AASDK_LOG(warning) + << "[USBTransport] receiveError native=5 transient recovery " + << receiveNoDeviceRetryCount_ << "/" << cReceiveNoDeviceRetryMax + << " delayMs=" << cReceiveNoDeviceRetryDelayMs; + this->scheduleReceiveRetry(cReceiveNoDeviceRetryDelayMs); + return; + } + + if (e.getNativeCode() == kLibusbInterrupted && + receiveInterruptedRetryCount_ < cReceiveInterruptedRetryMax) { + receiveInterruptedRetryCount_++; + AASDK_LOG(debug) + << "[USBTransport] receiveError native=-4 interrupted retry " + << receiveInterruptedRetryCount_ << "/" << cReceiveInterruptedRetryMax + << " delayMs=" << cReceiveInterruptedRetryDelayMs; + this->scheduleReceiveRetry(cReceiveInterruptedRetryDelayMs); + return; + } + + receiveNoDeviceRetryCount_ = 0; + receiveInterruptedRetryCount_ = 0; this->rejectReceivePromises(e); }); aoapDevice_->getInEndpoint().bulkTransfer(buffer, cReceiveTimeoutMs, std::move(usbEndpointPromise)); } + void USBTransport::scheduleReceiveRetry(uint32_t delayMs) { + auto timer = std::make_shared( + *ioServicePtr_, std::chrono::milliseconds(delayMs)); + timer->async_wait( + receiveStrand_.wrap([this, self = this->shared_from_this(), timer]( + const boost::system::error_code& timerError) { + if (timerError) { + return; + } + if (!receiveQueue_.empty()) { + AASDK_LOG(warning) << "[USBTransport] receiveRetry re-arming IN endpoint"; + // Discard the uncommitted fill() slot from the failed transfer. + // Without this, each retry would append another 16 KB zero-chunk to + // the DataSink; distributeReceivedData() would later consume those + // zeros as real protocol frames, corrupting the AA session before + // any channel can open. The self-healing fill() in DataSink would + // catch this even without an explicit rollback(), but we call it + // here explicitly to make the retry semantics unambiguous. + receivedDataSink_.rollback(); + auto buf = receivedDataSink_.fill(); + this->enqueueReceive(buf); + } + })); + } + void USBTransport::enqueueSend(SendQueue::iterator queueElement) { this->doSend(queueElement, 0); } diff --git a/src/USB/AOAPDevice.cpp b/src/USB/AOAPDevice.cpp index e424bd41..04bb6f96 100644 --- a/src/USB/AOAPDevice.cpp +++ b/src/USB/AOAPDevice.cpp @@ -52,6 +52,7 @@ */ #include +#include #include #include #include @@ -62,25 +63,17 @@ namespace aasdk { namespace usb { AOAPDevice::AOAPDevice(IUSBWrapper &usbWrapper, boost::asio::io_service &ioService, DeviceHandle handle, - const libusb_interface_descriptor *interfaceDescriptor) - : usbWrapper_(usbWrapper), handle_(std::move(handle)), interfaceDescriptor_(interfaceDescriptor) { - if ((interfaceDescriptor->endpoint[0].bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) { - inEndpoint_ = std::make_shared(usbWrapper_, ioService, handle_, - interfaceDescriptor_->endpoint[0].bEndpointAddress); - outEndpoint_ = std::make_shared(usbWrapper_, ioService, handle_, - interfaceDescriptor_->endpoint[1].bEndpointAddress); - } else { - inEndpoint_ = std::make_shared(usbWrapper_, ioService, handle_, - interfaceDescriptor_->endpoint[1].bEndpointAddress); - outEndpoint_ = std::make_shared(usbWrapper_, ioService, handle_, - interfaceDescriptor_->endpoint[0].bEndpointAddress); - } + uint8_t interfaceNumber, unsigned char inEndpointAddress, + unsigned char outEndpointAddress) + : usbWrapper_(usbWrapper), handle_(std::move(handle)), interfaceNumber_(interfaceNumber) { + inEndpoint_ = std::make_shared(usbWrapper_, ioService, handle_, inEndpointAddress); + outEndpoint_ = std::make_shared(usbWrapper_, ioService, handle_, outEndpointAddress); } AOAPDevice::~AOAPDevice() { inEndpoint_->cancelTransfers(); outEndpoint_->cancelTransfers(); - usbWrapper_.releaseInterface(handle_, interfaceDescriptor_->bInterfaceNumber); + usbWrapper_.releaseInterface(handle_, interfaceNumber_); } IUSBEndpoint &AOAPDevice::getInEndpoint() { @@ -101,22 +94,26 @@ namespace aasdk { throw error::Error(error::ErrorCode::USB_INVALID_DEVICE_ENDPOINTS); } - auto result = usbWrapper.claimInterface(handle, interfaceDescriptor->bInterfaceNumber); + const auto [interfaceNumber, inEndpointAddress, outEndpointAddress] = + AOAPDevice::extractEndpointDetails(interfaceDescriptor); + + auto result = usbWrapper.claimInterface(handle, interfaceNumber); // Recovery path for stale interface ownership after abrupt transport teardown. if (result == LIBUSB_ERROR_BUSY) { AASDK_LOG(warning) << "[AOAPDevice] claimInterface busy on iface=" - << static_cast(interfaceDescriptor->bInterfaceNumber) + << static_cast(interfaceNumber) << ", attempting release+retry"; - usbWrapper.releaseInterface(handle, interfaceDescriptor->bInterfaceNumber); - result = usbWrapper.claimInterface(handle, interfaceDescriptor->bInterfaceNumber); + usbWrapper.releaseInterface(handle, interfaceNumber); + result = usbWrapper.claimInterface(handle, interfaceNumber); } if (result != 0) { throw error::Error(error::ErrorCode::USB_CLAIM_INTERFACE, result); } - return std::make_unique(usbWrapper, ioService, std::move(handle), interfaceDescriptor); + return std::make_unique(usbWrapper, ioService, std::move(handle), interfaceNumber, + inEndpointAddress, outEndpointAddress); } ConfigDescriptorHandle AOAPDevice::getConfigDescriptor(IUSBWrapper &usbWrapper, DeviceHandle handle) { @@ -151,5 +148,17 @@ namespace aasdk { return &interface->altsetting[0]; } + std::tuple + AOAPDevice::extractEndpointDetails(const libusb_interface_descriptor *interfaceDescriptor) { + const auto firstEndpointAddress = interfaceDescriptor->endpoint[0].bEndpointAddress; + const auto secondEndpointAddress = interfaceDescriptor->endpoint[1].bEndpointAddress; + + if ((firstEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) { + return {interfaceDescriptor->bInterfaceNumber, firstEndpointAddress, secondEndpointAddress}; + } + + return {interfaceDescriptor->bInterfaceNumber, secondEndpointAddress, firstEndpointAddress}; + } + } }